]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_ctf.qc
Merge branch 'master' into Mario/monsters
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_ctf.qc
1 // ================================================================
2 //  Official capture the flag game mode coding, reworked by Samual
3 //  Last updated: September, 2012
4 // ================================================================
5
6 void ctf_FakeTimeLimit(entity e, float t)
7 {
8         msg_entity = e;
9         WriteByte(MSG_ONE, 3); // svc_updatestat
10         WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
11         if(t < 0)
12                 WriteCoord(MSG_ONE, autocvar_timelimit);
13         else
14                 WriteCoord(MSG_ONE, (t + 1) / 60);
15 }
16
17 void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
18 {
19         if(autocvar_sv_eventlog)
20                 GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
21 }
22
23 void ctf_CaptureRecord(entity flag, entity player)
24 {
25         entity tmp_entity;
26         float cap_record = ctf_captimerecord;
27         float cap_time = (time - flag.ctf_pickuptime);
28         string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
29         
30         // notify about shit
31         FOR_EACH_REALCLIENT(tmp_entity)
32         {
33                 if(tmp_entity.CAPTURE_VERBOSE)
34                 {
35                         if(!ctf_captimerecord) { Send_Notification(NOTIF_ONE_ONLY, tmp_entity, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
36                         else if(cap_time < cap_record) { Send_Notification(NOTIF_ONE_ONLY, tmp_entity, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
37                         else { Send_Notification(NOTIF_ONE_ONLY, tmp_entity, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
38                 }
39                 else { Send_Notification(NOTIF_ONE_ONLY, tmp_entity, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_), player.netname); }
40         }
41
42         // the previous notification broadcast is only sent to real clients, this will notify server log too
43         if(server_is_dedicated)
44         {
45                 if(autocvar_notification_ctf_capture_verbose)
46                 {
47                         if(!ctf_captimerecord) { Local_Notification(MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
48                         else if(cap_time < cap_record) { Local_Notification(MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
49                         else { Local_Notification(MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
50                 }
51                 else { Local_Notification(MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_), player.netname); }
52         }
53         
54         // write that shit in the database
55         if((!ctf_captimerecord) || (cap_time < cap_record))
56         {
57                 ctf_captimerecord = cap_time;
58                 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
59                 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
60                 write_recordmarker(player, (time - cap_time), cap_time); 
61         } 
62 }
63
64 void ctf_FlagcarrierWaypoints(entity player)
65 {
66         WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
67         WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
68         WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
69         WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
70 }
71
72 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
73 {
74         float current_distance = vlen((('1 0 0' * to_x) + ('0 1 0' * to_y)) - (('1 0 0' * from_x) + ('0 1 0' * from_y))); // for the sake of this check, exclude Z axis
75         float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
76         float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
77         //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
78
79         vector targpos;
80         if(current_height) // make sure we can actually do this arcing path
81         {
82                 targpos = (to + ('0 0 1' * current_height));
83                 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
84                 if(trace_fraction < 1)
85                 {
86                         //print("normal arc line failed, trying to find new pos...");
87                         WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
88                         targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
89                         WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
90                         if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
91                         /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
92                 }
93         }
94         else { targpos = to; }
95
96         //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
97
98         vector desired_direction = normalize(targpos - from);
99         if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
100         else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
101 }
102
103 float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
104 {
105         if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
106         {
107                 // directional tracing only
108                 float spreadlimit;
109                 makevectors(passer_angle);
110
111                 // find the closest point on the enemy to the center of the attack
112                 float ang; // angle between shotdir and h
113                 float h; // hypotenuse, which is the distance between attacker to head
114                 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
115                 
116                 h = vlen(head_center - passer_center);
117                 ang = acos(dotproduct(normalize(head_center - passer_center), v_forward));
118                 a = h * cos(ang);
119
120                 vector nearest_on_line = (passer_center + a * v_forward);
121                 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
122
123                 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
124                 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
125
126                 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
127                         { return TRUE; }
128                 else
129                         { return FALSE; }
130         }
131         else { return TRUE; }
132 }
133
134
135 // =======================
136 // CaptureShield Functions 
137 // =======================
138
139 float ctf_CaptureShield_CheckStatus(entity p) 
140 {
141         float s, se;
142         entity e;
143         float players_worseeq, players_total;
144
145         if(ctf_captureshield_max_ratio <= 0)
146                 return FALSE;
147
148         s = PlayerScore_Add(p, SP_SCORE, 0);
149         if(s >= -ctf_captureshield_min_negscore)
150                 return FALSE;
151
152         players_total = players_worseeq = 0;
153         FOR_EACH_PLAYER(e)
154         {
155                 if(IsDifferentTeam(e, p))
156                         continue;
157                 se = PlayerScore_Add(e, SP_SCORE, 0);
158                 if(se <= s)
159                         ++players_worseeq;
160                 ++players_total;
161         }
162
163         // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
164         // use this rule here
165         
166         if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
167                 return FALSE;
168
169         return TRUE;
170 }
171
172 void ctf_CaptureShield_Update(entity player, float wanted_status)
173 {
174         float updated_status = ctf_CaptureShield_CheckStatus(player);
175         if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
176         {
177                 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
178                 player.ctf_captureshielded = updated_status;
179         }
180 }
181
182 float ctf_CaptureShield_Customize()
183 {
184         if(!other.ctf_captureshielded) { return FALSE; }
185         if(!IsDifferentTeam(self, other)) { return FALSE; }
186         
187         return TRUE;
188 }
189
190 void ctf_CaptureShield_Touch()
191 {
192         if(!other.ctf_captureshielded) { return; }
193         if(!IsDifferentTeam(self, other)) { return; }
194         
195         vector mymid = (self.absmin + self.absmax) * 0.5;
196         vector othermid = (other.absmin + other.absmax) * 0.5;
197
198         Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
199         Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED);
200 }
201
202 void ctf_CaptureShield_Spawn(entity flag)
203 {
204         entity shield = spawn();
205         
206         shield.enemy = self;
207         shield.team = self.team;
208         shield.touch = ctf_CaptureShield_Touch;
209         shield.customizeentityforclient = ctf_CaptureShield_Customize;
210         shield.classname = "ctf_captureshield";
211         shield.effects = EF_ADDITIVE;
212         shield.movetype = MOVETYPE_NOCLIP;
213         shield.solid = SOLID_TRIGGER;
214         shield.avelocity = '7 0 11';
215         shield.scale = 0.5;
216         
217         setorigin(shield, self.origin);
218         setmodel(shield, "models/ctf/shield.md3");
219         setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
220 }
221
222
223 // ====================
224 // Drop/Pass/Throw Code
225 // ====================
226
227 void ctf_Handle_Drop(entity flag, entity player, float droptype)
228 {
229         // declarations
230         player = (player ? player : flag.pass_sender);
231
232         // main
233         flag.movetype = MOVETYPE_TOSS;
234         flag.takedamage = DAMAGE_YES;
235         flag.angles = '0 0 0';
236         flag.health = flag.max_flag_health;
237         flag.ctf_droptime = time;
238         flag.ctf_dropper = player;
239         flag.ctf_status = FLAG_DROPPED;
240         
241         // messages and sounds
242         Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_LOST_), player.netname);
243         sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
244         ctf_EventLog("dropped", player.team, player);
245
246         // scoring
247         PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);   
248         PlayerScore_Add(player, SP_CTF_DROPS, 1);
249         
250         // waypoints
251         if(autocvar_g_ctf_flag_dropped_waypoint)
252                 WaypointSprite_Spawn("flagdropped", 0, 0, flag, FLAG_WAYPOINT_OFFSET, world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, TRUE, RADARICON_FLAG, WPCOLOR_DROPPEDFLAG(flag.team));
253
254         if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
255         {
256                 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
257                 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
258         }
259         
260         player.throw_antispam = time + autocvar_g_ctf_pass_wait;
261         
262         if(droptype == DROP_PASS)
263         {
264                 flag.pass_distance = 0;
265                 flag.pass_sender = world;
266                 flag.pass_target = world;
267         }
268 }
269
270 void ctf_Handle_Retrieve(entity flag, entity player)
271 {
272         entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
273         entity sender = flag.pass_sender;
274         
275         // transfer flag to player
276         flag.owner = player;
277         flag.owner.flagcarried = flag;
278         
279         // reset flag
280         setattachment(flag, player, "");
281         setorigin(flag, FLAG_CARRY_OFFSET);
282         flag.movetype = MOVETYPE_NONE;
283         flag.takedamage = DAMAGE_NO;
284         flag.solid = SOLID_NOT;
285         flag.angles = '0 0 0';
286         flag.ctf_status = FLAG_CARRY;
287
288         // messages and sounds
289         sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTN_NORM);
290         ctf_EventLog("receive", flag.team, player);
291         
292         FOR_EACH_REALPLAYER(tmp_player)
293         {
294                 if(tmp_player == sender)
295                         Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_SENT_), player.netname);
296                 else if(tmp_player == player)
297                         Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname);
298                 else if(!IsDifferentTeam(tmp_player, sender))
299                         Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname);
300         }
301         
302         // create new waypoint
303         ctf_FlagcarrierWaypoints(player);
304         
305         sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
306         player.throw_antispam = sender.throw_antispam;
307
308         flag.pass_distance = 0;
309         flag.pass_sender = world;
310         flag.pass_target = world;
311 }
312
313 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
314 {
315         entity flag = player.flagcarried;
316         vector targ_origin, flag_velocity;
317         
318         if(!flag) { return; }
319         if((droptype == DROP_PASS) && !receiver) { return; }
320         
321         if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
322         
323         // reset the flag
324         setattachment(flag, world, "");
325         setorigin(flag, player.origin + FLAG_DROP_OFFSET);
326         flag.owner.flagcarried = world;
327         flag.owner = world;
328         flag.solid = SOLID_TRIGGER;
329         flag.ctf_dropper = player;
330         flag.ctf_droptime = time;
331         
332         flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
333         
334         switch(droptype)
335         {
336                 case DROP_PASS:
337                 {
338                         // warpzone support:
339                         // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
340                         // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
341                         WarpZone_RefSys_Copy(flag, receiver);
342                         WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
343                         targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
344
345                         flag.pass_distance = vlen((('1 0 0' * targ_origin_x) + ('0 1 0' * targ_origin_y)) - (('1 0 0' *  player.origin_x) + ('0 1 0' *  player.origin_y))); // for the sake of this check, exclude Z axis
346                         ctf_CalculatePassVelocity(flag, targ_origin, player.origin, FALSE);
347
348                         // main
349                         flag.movetype = MOVETYPE_FLY;
350                         flag.takedamage = DAMAGE_NO;
351                         flag.pass_sender = player;
352                         flag.pass_target = receiver;
353                         flag.ctf_status = FLAG_PASSING;
354                         
355                         // other
356                         sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTN_NORM);
357                         WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
358                         ctf_EventLog("pass", flag.team, player);
359                         break;
360                 }
361                 
362                 case DROP_THROW:
363                 {
364                         makevectors((player.v_angle_y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle_x, autocvar_g_ctf_throw_angle_max) * '1 0 0'));
365                                 
366                         flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & IT_STRENGTH) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
367                         flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE);
368                         ctf_Handle_Drop(flag, player, droptype);
369                         break;
370                 }
371                 
372                 case DROP_RESET:
373                 {
374                         flag.velocity = '0 0 0'; // do nothing
375                         break;
376                 }
377                 
378                 default:
379                 case DROP_NORMAL:
380                 {
381                         flag.velocity = W_CalculateProjectileVelocity(player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), FALSE);
382                         ctf_Handle_Drop(flag, player, droptype);
383                         break;
384                 }
385         }
386
387         // kill old waypointsprite
388         WaypointSprite_Ping(player.wps_flagcarrier);
389         WaypointSprite_Kill(player.wps_flagcarrier);
390         
391         if(player.wps_enemyflagcarrier)
392                 WaypointSprite_Kill(player.wps_enemyflagcarrier);
393         
394         // captureshield
395         ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
396 }
397
398
399 // ==============
400 // Event Handlers
401 // ==============
402
403 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
404 {
405         entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
406         entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
407         float old_time, new_time; 
408         
409         if not(player) { return; } // without someone to give the reward to, we can't possibly cap
410         
411         // messages and sounds
412         Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(enemy_flag, CENTER_CTF_CAPTURE_));
413         ctf_CaptureRecord(enemy_flag, player);
414         sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
415         
416         switch(capturetype)
417         {
418                 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
419                 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
420                 default: break;
421         }
422         
423         // scoring
424         PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
425         PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
426
427         old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
428         new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
429         if(!old_time || new_time < old_time)
430                 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
431
432         // effects
433         pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
434         //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
435
436         // other
437         if(capturetype == CAPTURE_NORMAL)
438         {
439                 WaypointSprite_Kill(player.wps_flagcarrier);
440                 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
441                 
442                 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
443                         { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
444         }
445         
446         // reset the flag
447         player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
448         ctf_RespawnFlag(enemy_flag);
449 }
450
451 void ctf_Handle_Return(entity flag, entity player)
452 {
453         // messages and sounds
454         if(IS_PLAYER(player))
455                 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
456                 
457         Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname);
458         sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
459         ctf_EventLog("return", flag.team, player);
460
461         // scoring
462         if(IS_PLAYER(player))
463         {
464                 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
465                 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
466         }
467
468         TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
469         
470         if(flag.ctf_dropper) 
471         {
472                 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
473                 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag 
474                 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
475         }
476         
477         // reset the flag
478         ctf_RespawnFlag(flag);
479 }
480
481 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
482 {
483         // declarations
484         entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
485         float pickup_dropped_score; // used to calculate dropped pickup score
486         
487         // attach the flag to the player
488         flag.owner = player;
489         player.flagcarried = flag;
490         setattachment(flag, player, "");
491         setorigin(flag, FLAG_CARRY_OFFSET);
492         
493         // flag setup
494         flag.movetype = MOVETYPE_NONE;
495         flag.takedamage = DAMAGE_NO;
496         flag.solid = SOLID_NOT;
497         flag.angles = '0 0 0';
498         flag.ctf_status = FLAG_CARRY;
499         
500         switch(pickuptype)
501         {
502                 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
503                 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
504                 default: break;
505         }
506
507         // messages and sounds
508         Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname);
509         sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
510
511         FOR_EACH_REALPLAYER(tmp_player)
512         {
513                 if(tmp_player == player)
514                 {
515                         Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_));
516                         if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
517                 }
518                 else if(!IsDifferentTeam(tmp_player, player) && tmp_player != player)
519                 {
520                         if(tmp_player.PICKUP_TEAM_VERBOSE)
521                                 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, CENTER_CTF_PICKUP_TEAM_VERBOSE, Team_ColorCode(player.team), player.netname);
522                         else
523                                 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, CENTER_CTF_PICKUP_TEAM, Team_ColorCode(player.team));
524                 }
525                 else if(IsDifferentTeam(tmp_player, player))
526                 {
527                         if(tmp_player.PICKUP_ENEMY_VERBOSE)
528                                 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, CENTER_CTF_PICKUP_ENEMY_VERBOSE, Team_ColorCode(player.team), player.netname);
529                         else
530                                 Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, CENTER_CTF_PICKUP_ENEMY, Team_ColorCode(player.team));
531                 }
532         }
533         
534         // scoring
535         PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
536         switch(pickuptype)
537         {               
538                 case PICKUP_BASE:
539                 {
540                         PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
541                         ctf_EventLog("steal", flag.team, player);
542                         break;
543                 }
544                 
545                 case PICKUP_DROPPED:
546                 {
547                         pickup_dropped_score = (autocvar_g_ctf_flag_return_time ? bound(0, ((flag.ctf_droptime + autocvar_g_ctf_flag_return_time) - time) / autocvar_g_ctf_flag_return_time, 1) : 1);
548                         pickup_dropped_score = floor((autocvar_g_ctf_score_pickup_dropped_late * (1 - pickup_dropped_score) + autocvar_g_ctf_score_pickup_dropped_early * pickup_dropped_score) + 0.5);
549                         dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
550                         PlayerTeamScore_AddScore(player, pickup_dropped_score);
551                         ctf_EventLog("pickup", flag.team, player);
552                         break;
553                 }
554                 
555                 default: break;
556         }
557         
558         // speedrunning
559         if(pickuptype == PICKUP_BASE)
560         {
561                 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
562                 if((player.speedrunning) && (ctf_captimerecord))
563                         ctf_FakeTimeLimit(player, time + ctf_captimerecord);
564         }
565                 
566         // effects
567         pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
568         
569         // waypoints 
570         if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
571         ctf_FlagcarrierWaypoints(player);
572         WaypointSprite_Ping(player.wps_flagcarrier);
573 }
574
575
576 // ===================
577 // Main Flag Functions
578 // ===================
579
580 void ctf_CheckFlagReturn(entity flag, float returntype)
581 {
582         if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
583         {
584                 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
585                 
586                 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
587                 {
588                         switch(returntype)
589                         {
590                                 case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DROPPED_)); break;
591                                 case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break;
592                                 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break;
593                                 case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break;
594                                 
595                                 default:
596                                 case RETURN_TIMEOUT:
597                                         { Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; }
598                         }
599                         sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
600                         ctf_EventLog("returned", flag.team, world);
601                         ctf_RespawnFlag(flag);
602                 }
603         }
604 }
605
606 void ctf_CheckStalemate(void)
607 {
608         // declarations
609         float stale_red_flags = 0, stale_blue_flags = 0;
610         entity tmp_entity;
611
612         entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
613
614         // build list of stale flags
615         for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
616         {
617                 if(autocvar_g_ctf_stalemate)
618                 if(tmp_entity.ctf_status != FLAG_BASE)
619                 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
620                 {
621                         tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
622                         ctf_staleflaglist = tmp_entity;
623                         
624                         switch(tmp_entity.team)
625                         {
626                                 case NUM_TEAM_1: ++stale_red_flags; break;
627                                 case NUM_TEAM_2: ++stale_blue_flags; break;
628                         }
629                 }
630         }
631
632         if(stale_red_flags && stale_blue_flags)
633                 ctf_stalemate = TRUE;
634         else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2)
635                 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
636         else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1)
637                 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
638                 
639         // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
640         if(ctf_stalemate)
641         {
642                 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
643                 {
644                         if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
645                                 WaypointSprite_Spawn("enemyflagcarrier", 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, tmp_entity.team, tmp_entity.owner, wps_enemyflagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_ENEMYFC(tmp_entity.owner.team));
646                 }
647                 
648                 if not(wpforenemy_announced)
649                 {
650                         FOR_EACH_REALPLAYER(tmp_entity)
651                                 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
652                         
653                         wpforenemy_announced = TRUE;
654                 }
655         }
656 }
657
658 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
659 {
660         if(ITEM_DAMAGE_NEEDKILL(deathtype))
661         {
662                 // automatically kill the flag and return it
663                 self.health = 0;
664                 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
665                 return;
666         }
667         if(autocvar_g_ctf_flag_return_damage) 
668         {
669                 // reduce health and check if it should be returned
670                 self.health = self.health - damage;
671                 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
672                 return;
673         }
674 }
675
676 void ctf_FlagThink()
677 {
678         // declarations
679         entity tmp_entity;
680
681         self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
682
683         // captureshield
684         if(self == ctf_worldflaglist) // only for the first flag
685                 FOR_EACH_CLIENT(tmp_entity)
686                         ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
687
688         // sanity checks
689         if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
690                 dprint("wtf the flag got squashed?\n");
691                 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
692                 if(!trace_startsolid) // can we resize it without getting stuck?
693                         setsize(self, FLAG_MIN, FLAG_MAX); }
694                         
695         switch(self.ctf_status) // reset flag angles in case warpzones adjust it
696         {
697                 case FLAG_DROPPED:
698                 {
699                         self.angles = '0 0 0';
700                         break;
701                 }
702                 
703                 default: break;
704         }
705
706         // main think method
707         switch(self.ctf_status)
708         {       
709                 case FLAG_BASE:
710                 {
711                         if(autocvar_g_ctf_dropped_capture_radius)
712                         {
713                                 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
714                                         if(tmp_entity.ctf_status == FLAG_DROPPED)
715                                         if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
716                                         if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
717                                                 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
718                         }
719                         return;
720                 }
721                 
722                 case FLAG_DROPPED:
723                 {
724                         if(autocvar_g_ctf_flag_dropped_floatinwater)
725                         {
726                                 vector midpoint = ((self.absmin + self.absmax) * 0.5);
727                                 if(pointcontents(midpoint) == CONTENT_WATER)
728                                 {
729                                         self.velocity = self.velocity * 0.5;
730                                         
731                                         if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
732                                                 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
733                                         else
734                                                 { self.movetype = MOVETYPE_FLY; }
735                                 }
736                                 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
737                         }
738                         if(autocvar_g_ctf_flag_return_dropped)
739                         {
740                                 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
741                                 {
742                                         self.health = 0;
743                                         ctf_CheckFlagReturn(self, RETURN_DROPPED);
744                                         return;
745                                 }
746                         }
747                         if(autocvar_g_ctf_flag_return_time)
748                         {
749                                 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
750                                 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
751                                 return;
752                         } 
753                         return;
754                 }
755                         
756                 case FLAG_CARRY:
757                 {
758                         if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord)) 
759                         {
760                                 self.health = 0;
761                                 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
762
763                                 tmp_entity = self;
764                                 self = self.owner;
765                                 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
766                                 ImpulseCommands();
767                                 self = tmp_entity;
768                         }
769                         if(autocvar_g_ctf_stalemate)
770                         {
771                                 if(time >= wpforenemy_nextthink)
772                                 {
773                                         ctf_CheckStalemate();
774                                         wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
775                                 }
776                         }
777                         return;
778                 }
779                 
780                 case FLAG_PASSING:
781                 {
782                         vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
783                         targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
784                         WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
785                         
786                         if((self.pass_target == world)
787                                 || (self.pass_target.deadflag != DEAD_NO)
788                                 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
789                                 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
790                                 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
791                         {
792                                 // give up, pass failed
793                                 ctf_Handle_Drop(self, world, DROP_PASS);
794                         }
795                         else
796                         {
797                                 // still a viable target, go for it
798                                 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
799                         }
800                         return;
801                 }
802
803                 default: // this should never happen
804                 {
805                         dprint("ctf_FlagThink(): Flag exists with no status?\n");
806                         return;
807                 }
808         }
809 }
810
811 void ctf_FlagTouch()
812 {
813         if(gameover) { return; }
814         
815         entity toucher = other;
816         
817         if(toucher.frozen) { return; }
818         
819         // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
820         if(ITEM_TOUCH_NEEDKILL())
821         {
822                 self.health = 0;
823                 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
824                 return;
825         }
826         
827         // special touch behaviors
828         if(toucher.vehicle_flags & VHF_ISVEHICLE)
829         {
830                 if(autocvar_g_ctf_allow_vehicle_touch)
831                         toucher = toucher.owner; // the player is actually the vehicle owner, not other
832                 else
833                         return; // do nothing
834         }
835         else if(toucher.flags & FL_MONSTER)
836         {
837                 if not(autocvar_g_ctf_allow_monster_touch)
838                         return; // do nothing
839         }
840         else if not(IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
841         {
842                 if(time > self.wait) // if we haven't in a while, play a sound/effect
843                 {
844                         pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
845                         sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
846                         self.wait = time + FLAG_TOUCHRATE;
847                 }
848                 return;
849         }
850         else if(toucher.deadflag != DEAD_NO) { return; }
851
852         switch(self.ctf_status) 
853         {       
854                 case FLAG_BASE:
855                 {
856                         if(!IsDifferentTeam(toucher, self) && (toucher.flagcarried) && IsDifferentTeam(toucher.flagcarried, self) && !(toucher.flags & FL_MONSTER))
857                                 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
858                         else if(IsDifferentTeam(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && !(toucher.flags & FL_MONSTER))
859                                 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
860                         break;
861                 }
862                 
863                 case FLAG_DROPPED:
864                 {
865                         if(!IsDifferentTeam(toucher, self))
866                                 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
867                         else if(!(toucher.flags & FL_MONSTER) && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
868                                 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
869                         break;
870                 }
871                         
872                 case FLAG_CARRY:
873                 {
874                         dprint("Someone touched a flag even though it was being carried?\n");
875                         break;
876                 }
877                 
878                 case FLAG_PASSING:
879                 {
880                         if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
881                         {
882                                 if(IsDifferentTeam(toucher, self.pass_sender))
883                                         ctf_Handle_Return(self, toucher);
884                                 else
885                                         ctf_Handle_Retrieve(self, toucher);
886                         }
887                         break;
888                 }
889         }
890 }
891
892 .float last_respawn;
893 void ctf_RespawnFlag(entity flag)
894 {
895         // check for flag respawn being called twice in a row
896         if(flag.last_respawn > time - 0.5)
897                 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
898
899         flag.last_respawn = time;
900         
901         // reset the player (if there is one)
902         if((flag.owner) && (flag.owner.flagcarried == flag))
903         {
904                 if(flag.owner.wps_enemyflagcarrier)
905                         WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
906                         
907                 WaypointSprite_Kill(flag.wps_flagcarrier);
908                 
909                 flag.owner.flagcarried = world;
910
911                 if(flag.speedrunning)
912                         ctf_FakeTimeLimit(flag.owner, -1);
913         }
914
915         if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
916                 { WaypointSprite_Kill(flag.wps_flagdropped); }
917
918         // reset the flag
919         setattachment(flag, world, "");
920         setorigin(flag, flag.ctf_spawnorigin);
921         
922         flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
923         flag.takedamage = DAMAGE_NO;
924         flag.health = flag.max_flag_health;
925         flag.solid = SOLID_TRIGGER;
926         flag.velocity = '0 0 0';
927         flag.angles = flag.mangle;
928         flag.flags = FL_ITEM | FL_NOTARGET;
929         
930         flag.ctf_status = FLAG_BASE;
931         flag.owner = world;
932         flag.pass_distance = 0;
933         flag.pass_sender = world;
934         flag.pass_target = world;
935         flag.ctf_dropper = world;
936         flag.ctf_pickuptime = 0;
937         flag.ctf_droptime = 0;
938 }
939
940 void ctf_Reset()
941 {
942         if(self.owner)
943                 if(IS_PLAYER(self.owner))
944                         ctf_Handle_Throw(self.owner, world, DROP_RESET);
945                         
946         ctf_RespawnFlag(self);
947 }
948
949 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
950 {
951         // bot waypoints
952         waypoint_spawnforitem_force(self, self.origin);
953         self.nearestwaypointtimeout = 0; // activate waypointing again
954         self.bot_basewaypoint = self.nearestwaypoint;
955
956         // waypointsprites
957         WaypointSprite_SpawnFixed(((self.team == NUM_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
958         WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
959
960         // captureshield setup
961         ctf_CaptureShield_Spawn(self);
962 }
963
964 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc 
965 {       
966         // declarations
967         teamnumber = fabs(teamnumber - bound(0, autocvar_g_ctf_reverse, 1)); // if we were originally 1, this will become 0. If we were originally 0, this will become 1. 
968         self = flag; // for later usage with droptofloor()
969         
970         // main setup
971         flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
972         ctf_worldflaglist = flag;
973
974         setattachment(flag, world, "");
975
976         flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag"); // Primarily only used for debugging or when showing nearby item name
977         flag.team = ((teamnumber) ? NUM_TEAM_1 : NUM_TEAM_2); // NUM_TEAM_1: color 4 team (red) - NUM_TEAM_2: color 13 team (blue)
978         flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
979         flag.classname = "item_flag_team";
980         flag.target = "###item###"; // wut?
981         flag.flags = FL_ITEM | FL_NOTARGET;
982         flag.solid = SOLID_TRIGGER;
983         flag.takedamage = DAMAGE_NO;
984         flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;   
985         flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
986         flag.health = flag.max_flag_health;
987         flag.event_damage = ctf_FlagDamage;
988         flag.pushable = TRUE;
989         flag.teleportable = TELEPORT_NORMAL;
990         flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
991         flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
992         flag.velocity = '0 0 0';
993         flag.mangle = flag.angles;
994         flag.reset = ctf_Reset;
995         flag.touch = ctf_FlagTouch;
996         flag.think = ctf_FlagThink;
997         flag.nextthink = time + FLAG_THINKRATE;
998         flag.ctf_status = FLAG_BASE;
999
1000         // appearence
1001         if(flag.model == "")       { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
1002         if(!flag.scale)            { flag.scale = FLAG_SCALE; }
1003         if(!flag.skin)             { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
1004         if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
1005         if(flag.passeffect == "")  { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
1006         if(flag.capeffect == "")   { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
1007         
1008         // sound 
1009         if(flag.snd_flag_taken == "")    { flag.snd_flag_taken  = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
1010         if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
1011         if(flag.snd_flag_capture == "")  { flag.snd_flag_capture = ((teamnumber) ? "ctf/red_capture.wav" : "ctf/blue_capture.wav"); } // blue team scores by capturing the red flag
1012         if(flag.snd_flag_respawn == "")  { flag.snd_flag_respawn = "ctf/flag_respawn.wav"; } // if there is ever a team-based sound for this, update the code to match.
1013         if(flag.snd_flag_dropped == "")  { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
1014         if(flag.snd_flag_touch == "")    { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
1015         if(flag.snd_flag_pass == "")     { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
1016         
1017         // precache
1018         precache_sound(flag.snd_flag_taken);
1019         precache_sound(flag.snd_flag_returned);
1020         precache_sound(flag.snd_flag_capture);
1021         precache_sound(flag.snd_flag_respawn);
1022         precache_sound(flag.snd_flag_dropped);
1023         precache_sound(flag.snd_flag_touch);
1024         precache_sound(flag.snd_flag_pass);
1025         precache_model(flag.model);
1026         precache_model("models/ctf/shield.md3");
1027         precache_model("models/ctf/shockwavetransring.md3");
1028
1029         // appearence
1030         setmodel(flag, flag.model); // precision set below
1031         setsize(flag, FLAG_MIN, FLAG_MAX);
1032         setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1033         
1034         if(autocvar_g_ctf_flag_glowtrails)
1035         {
1036                 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1037                 flag.glow_size = 25;
1038                 flag.glow_trail = 1;
1039         }
1040         
1041         flag.effects |= EF_LOWPRECISION;
1042         if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1043         if(autocvar_g_ctf_dynamiclights)   { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1044         
1045         // flag placement
1046         if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1047         {       
1048                 flag.dropped_origin = flag.origin; 
1049                 flag.noalign = TRUE;
1050                 flag.movetype = MOVETYPE_NONE;
1051         }
1052         else // drop to floor, automatically find a platform and set that as spawn origin
1053         { 
1054                 flag.noalign = FALSE;
1055                 self = flag;
1056                 droptofloor();
1057                 flag.movetype = MOVETYPE_TOSS; 
1058         }       
1059         
1060         InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1061 }
1062
1063
1064 // ================
1065 // Bot player logic
1066 // ================
1067
1068 // NOTE: LEGACY CODE, needs to be re-written!
1069
1070 void havocbot_calculate_middlepoint()
1071 {
1072         entity f;
1073         vector s = '0 0 0';
1074         vector fo = '0 0 0';
1075         float n = 0;
1076
1077         f = ctf_worldflaglist;
1078         while (f)
1079         {
1080                 fo = f.origin;
1081                 s = s + fo;
1082                 f = f.ctf_worldflagnext;
1083         }
1084         if(!n)
1085                 return;
1086         havocbot_ctf_middlepoint = s * (1.0 / n);
1087         havocbot_ctf_middlepoint_radius  = vlen(fo - havocbot_ctf_middlepoint);
1088 }
1089
1090
1091 entity havocbot_ctf_find_flag(entity bot)
1092 {
1093         entity f;
1094         f = ctf_worldflaglist;
1095         while (f)
1096         {
1097                 if (bot.team == f.team)
1098                         return f;
1099                 f = f.ctf_worldflagnext;
1100         }
1101         return world;
1102 }
1103
1104 entity havocbot_ctf_find_enemy_flag(entity bot)
1105 {
1106         entity f;
1107         f = ctf_worldflaglist;
1108         while (f)
1109         {
1110                 if (bot.team != f.team)
1111                         return f;
1112                 f = f.ctf_worldflagnext;
1113         }
1114         return world;
1115 }
1116
1117 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1118 {
1119         if not(teamplay)
1120                 return 0;
1121
1122         float c = 0;
1123         entity head;
1124
1125         FOR_EACH_PLAYER(head)
1126         {
1127                 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1128                         continue;
1129
1130                 if(vlen(head.origin - org) < tc_radius)
1131                         ++c;
1132         }
1133
1134         return c;
1135 }
1136
1137 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1138 {
1139         entity head;
1140         head = ctf_worldflaglist;
1141         while (head)
1142         {
1143                 if (self.team == head.team)
1144                         break;
1145                 head = head.ctf_worldflagnext;
1146         }
1147         if (head)
1148                 navigation_routerating(head, ratingscale, 10000);
1149 }
1150
1151 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1152 {
1153         entity head;
1154         head = ctf_worldflaglist;
1155         while (head)
1156         {
1157                 if (self.team == head.team)
1158                         break;
1159                 head = head.ctf_worldflagnext;
1160         }
1161         if not(head)
1162                 return;
1163
1164         navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1165 }
1166
1167 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1168 {
1169         entity head;
1170         head = ctf_worldflaglist;
1171         while (head)
1172         {
1173                 if (self.team != head.team)
1174                         break;
1175                 head = head.ctf_worldflagnext;
1176         }
1177         if (head)
1178                 navigation_routerating(head, ratingscale, 10000);
1179 }
1180
1181 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1182 {
1183         if not(bot_waypoints_for_items)
1184         {
1185                 havocbot_goalrating_ctf_enemyflag(ratingscale);
1186                 return;
1187         }
1188
1189         entity head;
1190
1191         head = havocbot_ctf_find_enemy_flag(self);
1192
1193         if not(head)
1194                 return;
1195
1196         navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1197 }
1198
1199 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1200 {
1201         entity mf;
1202
1203         mf = havocbot_ctf_find_flag(self);
1204
1205         if(mf.ctf_status == FLAG_BASE)
1206                 return;
1207
1208         if(mf.tag_entity)
1209                 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1210 }
1211
1212 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1213 {
1214         entity head;
1215         head = ctf_worldflaglist;
1216         while (head)
1217         {
1218                 // flag is out in the field
1219                 if(head.ctf_status != FLAG_BASE)
1220                 if(head.tag_entity==world)      // dropped
1221                 {
1222                         if(df_radius)
1223                         {
1224                                 if(vlen(org-head.origin)<df_radius)
1225                                         navigation_routerating(head, ratingscale, 10000);
1226                         }
1227                         else
1228                                 navigation_routerating(head, ratingscale, 10000);
1229                 }
1230
1231                 head = head.ctf_worldflagnext;
1232         }
1233 }
1234
1235 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1236 {
1237         entity head;
1238         float t;
1239         head = findchainfloat(bot_pickup, TRUE);
1240         while (head)
1241         {
1242                 // gather health and armor only
1243                 if (head.solid)
1244                 if (head.health || head.armorvalue)
1245                 if (vlen(head.origin - org) < sradius)
1246                 {
1247                         // get the value of the item
1248                         t = head.bot_pickupevalfunc(self, head) * 0.0001;
1249                         if (t > 0)
1250                                 navigation_routerating(head, t * ratingscale, 500);
1251                 }
1252                 head = head.chain;
1253         }
1254 }
1255
1256 void havocbot_ctf_reset_role(entity bot)
1257 {
1258         float cdefense, cmiddle, coffense;
1259         entity mf, ef, head;
1260         float c;
1261
1262         if(bot.deadflag != DEAD_NO)
1263                 return;
1264
1265         if(vlen(havocbot_ctf_middlepoint)==0)
1266                 havocbot_calculate_middlepoint();
1267
1268         // Check ctf flags
1269         if (bot.flagcarried)
1270         {
1271                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1272                 return;
1273         }
1274
1275         mf = havocbot_ctf_find_flag(bot);
1276         ef = havocbot_ctf_find_enemy_flag(bot);
1277
1278         // Retrieve stolen flag
1279         if(mf.ctf_status!=FLAG_BASE)
1280         {
1281                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1282                 return;
1283         }
1284
1285         // If enemy flag is taken go to the middle to intercept pursuers
1286         if(ef.ctf_status!=FLAG_BASE)
1287         {
1288                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1289                 return;
1290         }
1291
1292         // if there is only me on the team switch to offense
1293         c = 0;
1294         FOR_EACH_PLAYER(head)
1295         if(head.team==bot.team)
1296                 ++c;
1297
1298         if(c==1)
1299         {
1300                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1301                 return;
1302         }
1303
1304         // Evaluate best position to take
1305         // Count mates on middle position
1306         cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1307
1308         // Count mates on defense position
1309         cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1310
1311         // Count mates on offense position
1312         coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1313
1314         if(cdefense<=coffense)
1315                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1316         else if(coffense<=cmiddle)
1317                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1318         else
1319                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1320 }
1321
1322 void havocbot_role_ctf_carrier()
1323 {
1324         if(self.deadflag != DEAD_NO)
1325         {
1326                 havocbot_ctf_reset_role(self);
1327                 return;
1328         }
1329
1330         if (self.flagcarried == world)
1331         {
1332                 havocbot_ctf_reset_role(self);
1333                 return;
1334         }
1335
1336         if (self.bot_strategytime < time)
1337         {
1338                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1339
1340                 navigation_goalrating_start();
1341                 havocbot_goalrating_ctf_ourbase(50000);
1342
1343                 if(self.health<100)
1344                         havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1345
1346                 navigation_goalrating_end();
1347
1348                 if (self.navigation_hasgoals)
1349                         self.havocbot_cantfindflag = time + 10;
1350                 else if (time > self.havocbot_cantfindflag)
1351                 {
1352                         // Can't navigate to my own base, suicide!
1353                         // TODO: drop it and wander around
1354                         Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1355                         return;
1356                 }
1357         }
1358 }
1359
1360 void havocbot_role_ctf_escort()
1361 {
1362         entity mf, ef;
1363
1364         if(self.deadflag != DEAD_NO)
1365         {
1366                 havocbot_ctf_reset_role(self);
1367                 return;
1368         }
1369
1370         if (self.flagcarried)
1371         {
1372                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1373                 return;
1374         }
1375
1376         // If enemy flag is back on the base switch to previous role
1377         ef = havocbot_ctf_find_enemy_flag(self);
1378         if(ef.ctf_status==FLAG_BASE)
1379         {
1380                 self.havocbot_role = self.havocbot_previous_role;
1381                 self.havocbot_role_timeout = 0;
1382                 return;
1383         }
1384
1385         // If the flag carrier reached the base switch to defense
1386         mf = havocbot_ctf_find_flag(self);
1387         if(mf.ctf_status!=FLAG_BASE)
1388         if(vlen(ef.origin - mf.dropped_origin) < 300)
1389         {
1390                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1391                 return;
1392         }
1393
1394         // Set the role timeout if necessary
1395         if (!self.havocbot_role_timeout)
1396         {
1397                 self.havocbot_role_timeout = time + random() * 30 + 60;
1398         }
1399
1400         // If nothing happened just switch to previous role
1401         if (time > self.havocbot_role_timeout)
1402         {
1403                 self.havocbot_role = self.havocbot_previous_role;
1404                 self.havocbot_role_timeout = 0;
1405                 return;
1406         }
1407
1408         // Chase the flag carrier
1409         if (self.bot_strategytime < time)
1410         {
1411                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1412                 navigation_goalrating_start();
1413                 havocbot_goalrating_ctf_enemyflag(30000);
1414                 havocbot_goalrating_ctf_ourstolenflag(40000);
1415                 havocbot_goalrating_items(10000, self.origin, 10000);
1416                 navigation_goalrating_end();
1417         }
1418 }
1419
1420 void havocbot_role_ctf_offense()
1421 {
1422         entity mf, ef;
1423         vector pos;
1424
1425         if(self.deadflag != DEAD_NO)
1426         {
1427                 havocbot_ctf_reset_role(self);
1428                 return;
1429         }
1430
1431         if (self.flagcarried)
1432         {
1433                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1434                 return;
1435         }
1436
1437         // Check flags
1438         mf = havocbot_ctf_find_flag(self);
1439         ef = havocbot_ctf_find_enemy_flag(self);
1440
1441         // Own flag stolen
1442         if(mf.ctf_status!=FLAG_BASE)
1443         {
1444                 if(mf.tag_entity)
1445                         pos = mf.tag_entity.origin;
1446                 else
1447                         pos = mf.origin;
1448
1449                 // Try to get it if closer than the enemy base
1450                 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1451                 {
1452                         havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1453                         return;
1454                 }
1455         }
1456
1457         // Escort flag carrier
1458         if(ef.ctf_status!=FLAG_BASE)
1459         {
1460                 if(ef.tag_entity)
1461                         pos = ef.tag_entity.origin;
1462                 else
1463                         pos = ef.origin;
1464
1465                 if(vlen(pos-mf.dropped_origin)>700)
1466                 {
1467                         havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1468                         return;
1469                 }
1470         }
1471
1472         // About to fail, switch to middlefield
1473         if(self.health<50)
1474         {
1475                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1476                 return;
1477         }
1478
1479         // Set the role timeout if necessary
1480         if (!self.havocbot_role_timeout)
1481                 self.havocbot_role_timeout = time + 120;
1482
1483         if (time > self.havocbot_role_timeout)
1484         {
1485                 havocbot_ctf_reset_role(self);
1486                 return;
1487         }
1488
1489         if (self.bot_strategytime < time)
1490         {
1491                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1492                 navigation_goalrating_start();
1493                 havocbot_goalrating_ctf_ourstolenflag(50000);
1494                 havocbot_goalrating_ctf_enemybase(20000);
1495                 havocbot_goalrating_items(5000, self.origin, 1000);
1496                 havocbot_goalrating_items(1000, self.origin, 10000);
1497                 navigation_goalrating_end();
1498         }
1499 }
1500
1501 // Retriever (temporary role):
1502 void havocbot_role_ctf_retriever()
1503 {
1504         entity mf;
1505
1506         if(self.deadflag != DEAD_NO)
1507         {
1508                 havocbot_ctf_reset_role(self);
1509                 return;
1510         }
1511
1512         if (self.flagcarried)
1513         {
1514                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1515                 return;
1516         }
1517
1518         // If flag is back on the base switch to previous role
1519         mf = havocbot_ctf_find_flag(self);
1520         if(mf.ctf_status==FLAG_BASE)
1521         {
1522                 havocbot_ctf_reset_role(self);
1523                 return;
1524         }
1525
1526         if (!self.havocbot_role_timeout)
1527                 self.havocbot_role_timeout = time + 20;
1528
1529         if (time > self.havocbot_role_timeout)
1530         {
1531                 havocbot_ctf_reset_role(self);
1532                 return;
1533         }
1534
1535         if (self.bot_strategytime < time)
1536         {
1537                 float rt_radius;
1538                 rt_radius = 10000;
1539
1540                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1541                 navigation_goalrating_start();
1542                 havocbot_goalrating_ctf_ourstolenflag(50000);
1543                 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1544                 havocbot_goalrating_ctf_enemybase(30000);
1545                 havocbot_goalrating_items(500, self.origin, rt_radius);
1546                 navigation_goalrating_end();
1547         }
1548 }
1549
1550 void havocbot_role_ctf_middle()
1551 {
1552         entity mf;
1553
1554         if(self.deadflag != DEAD_NO)
1555         {
1556                 havocbot_ctf_reset_role(self);
1557                 return;
1558         }
1559
1560         if (self.flagcarried)
1561         {
1562                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1563                 return;
1564         }
1565
1566         mf = havocbot_ctf_find_flag(self);
1567         if(mf.ctf_status!=FLAG_BASE)
1568         {
1569                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1570                 return;
1571         }
1572
1573         if (!self.havocbot_role_timeout)
1574                 self.havocbot_role_timeout = time + 10;
1575
1576         if (time > self.havocbot_role_timeout)
1577         {
1578                 havocbot_ctf_reset_role(self);
1579                 return;
1580         }
1581
1582         if (self.bot_strategytime < time)
1583         {
1584                 vector org;
1585
1586                 org = havocbot_ctf_middlepoint;
1587                 org_z = self.origin_z;
1588
1589                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1590                 navigation_goalrating_start();
1591                 havocbot_goalrating_ctf_ourstolenflag(50000);
1592                 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1593                 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1594                 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1595                 havocbot_goalrating_items(2500, self.origin, 10000);
1596                 havocbot_goalrating_ctf_enemybase(2500);
1597                 navigation_goalrating_end();
1598         }
1599 }
1600
1601 void havocbot_role_ctf_defense()
1602 {
1603         entity mf;
1604
1605         if(self.deadflag != DEAD_NO)
1606         {
1607                 havocbot_ctf_reset_role(self);
1608                 return;
1609         }
1610
1611         if (self.flagcarried)
1612         {
1613                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1614                 return;
1615         }
1616
1617         // If own flag was captured
1618         mf = havocbot_ctf_find_flag(self);
1619         if(mf.ctf_status!=FLAG_BASE)
1620         {
1621                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1622                 return;
1623         }
1624
1625         if (!self.havocbot_role_timeout)
1626                 self.havocbot_role_timeout = time + 30;
1627
1628         if (time > self.havocbot_role_timeout)
1629         {
1630                 havocbot_ctf_reset_role(self);
1631                 return;
1632         }
1633         if (self.bot_strategytime < time)
1634         {
1635                 float mp_radius;
1636                 vector org;
1637
1638                 org = mf.dropped_origin;
1639                 mp_radius = havocbot_ctf_middlepoint_radius;
1640
1641                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1642                 navigation_goalrating_start();
1643
1644                 // if enemies are closer to our base, go there
1645                 entity head, closestplayer = world;
1646                 float distance, bestdistance = 10000;
1647                 FOR_EACH_PLAYER(head)
1648                 {
1649                         if(head.deadflag!=DEAD_NO)
1650                                 continue;
1651
1652                         distance = vlen(org - head.origin);
1653                         if(distance<bestdistance)
1654                         {
1655                                 closestplayer = head;
1656                                 bestdistance = distance;
1657                         }
1658                 }
1659
1660                 if(closestplayer)
1661                 if(closestplayer.team!=self.team)
1662                 if(vlen(org - self.origin)>1000)
1663                 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1664                         havocbot_goalrating_ctf_ourbase(30000);
1665
1666                 havocbot_goalrating_ctf_ourstolenflag(20000);
1667                 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1668                 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1669                 havocbot_goalrating_items(10000, org, mp_radius);
1670                 havocbot_goalrating_items(5000, self.origin, 10000);
1671                 navigation_goalrating_end();
1672         }
1673 }
1674
1675 void havocbot_role_ctf_setrole(entity bot, float role)
1676 {
1677         dprint(strcat(bot.netname," switched to "));
1678         switch(role)
1679         {
1680                 case HAVOCBOT_CTF_ROLE_CARRIER:
1681                         dprint("carrier");
1682                         bot.havocbot_role = havocbot_role_ctf_carrier;
1683                         bot.havocbot_role_timeout = 0;
1684                         bot.havocbot_cantfindflag = time + 10;
1685                         bot.bot_strategytime = 0;
1686                         break;
1687                 case HAVOCBOT_CTF_ROLE_DEFENSE:
1688                         dprint("defense");
1689                         bot.havocbot_role = havocbot_role_ctf_defense;
1690                         bot.havocbot_role_timeout = 0;
1691                         break;
1692                 case HAVOCBOT_CTF_ROLE_MIDDLE:
1693                         dprint("middle");
1694                         bot.havocbot_role = havocbot_role_ctf_middle;
1695                         bot.havocbot_role_timeout = 0;
1696                         break;
1697                 case HAVOCBOT_CTF_ROLE_OFFENSE:
1698                         dprint("offense");
1699                         bot.havocbot_role = havocbot_role_ctf_offense;
1700                         bot.havocbot_role_timeout = 0;
1701                         break;
1702                 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1703                         dprint("retriever");
1704                         bot.havocbot_previous_role = bot.havocbot_role;
1705                         bot.havocbot_role = havocbot_role_ctf_retriever;
1706                         bot.havocbot_role_timeout = time + 10;
1707                         bot.bot_strategytime = 0;
1708                         break;
1709                 case HAVOCBOT_CTF_ROLE_ESCORT:
1710                         dprint("escort");
1711                         bot.havocbot_previous_role = bot.havocbot_role;
1712                         bot.havocbot_role = havocbot_role_ctf_escort;
1713                         bot.havocbot_role_timeout = time + 30;
1714                         bot.bot_strategytime = 0;
1715                         break;
1716         }
1717         dprint("\n");
1718 }
1719
1720
1721 // ==============
1722 // Hook Functions
1723 // ==============
1724
1725 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1726 {
1727         entity flag;
1728         
1729         // initially clear items so they can be set as necessary later.
1730         self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST 
1731                 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1732
1733         // scan through all the flags and notify the client about them 
1734         for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1735         {
1736                 switch(flag.ctf_status)
1737                 {
1738                         case FLAG_PASSING:
1739                         case FLAG_CARRY:
1740                         {
1741                                 if((flag.owner == self) || (flag.pass_sender == self))
1742                                         self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1743                                 else 
1744                                         self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1745                                 break;
1746                         }
1747                         case FLAG_DROPPED:
1748                         {
1749                                 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1750                                 break;
1751                         }
1752                 }
1753         }
1754         
1755         // item for stopping players from capturing the flag too often
1756         if(self.ctf_captureshielded)
1757                 self.items |= IT_CTF_SHIELDED;
1758         
1759         // update the health of the flag carrier waypointsprite
1760         if(self.wps_flagcarrier) 
1761                 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent));
1762         
1763         return FALSE;
1764 }
1765
1766 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1767 {
1768         if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1769         {
1770                 if(frag_target == frag_attacker) // damage done to yourself
1771                 {
1772                         frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1773                         frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1774                 }
1775                 else // damage done to everyone else
1776                 {
1777                         frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1778                         frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1779                 }
1780         }
1781         else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && IsDifferentTeam(frag_target, frag_attacker)) // if the target is a flagcarrier
1782         {
1783                 if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent)))
1784                 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1785                 {
1786                         frag_target.wps_helpme_time = time;
1787                         WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1788                 }
1789                 // todo: add notification for when flag carrier needs help?
1790         }
1791         return FALSE;
1792 }
1793
1794 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1795 {
1796         if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
1797         {
1798                 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1799                 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1800         }
1801                                 
1802         if(frag_target.flagcarried)
1803                 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1804                 
1805         return FALSE;
1806 }
1807
1808 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1809 {
1810         frag_score = 0;
1811         return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1812 }
1813
1814 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1815 {
1816         entity flag; // temporary entity for the search method
1817         
1818         if(self.flagcarried)
1819                 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1820         
1821         for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1822         {
1823                 if(flag.pass_sender == self) { flag.pass_sender = world; }
1824                 if(flag.pass_target == self) { flag.pass_target = world; }
1825                 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1826         }
1827                 
1828         return FALSE;
1829 }
1830
1831 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1832 {
1833         if(self.flagcarried) 
1834         if(!autocvar_g_ctf_portalteleport)
1835                 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1836
1837         return FALSE;
1838 }
1839
1840 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1841 {
1842         if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1843         
1844         entity player = self;
1845
1846         if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1847         {
1848                 // pass the flag to a team mate
1849                 if(autocvar_g_ctf_pass)
1850                 {
1851                         entity head, closest_target = world;
1852                         head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1853                         
1854                         while(head) // find the closest acceptable target to pass to
1855                         {
1856                                 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
1857                                 if(head != player && !IsDifferentTeam(head, player))
1858                                 if(!head.speedrunning && !head.vehicle)
1859                                 {
1860                                         // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) 
1861                                         vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1862                                         vector passer_center = CENTER_OR_VIEWOFS(player);
1863                                         
1864                                         if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1865                                         {
1866                                                 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried) 
1867                                                 { 
1868                                                         if(IS_BOT_CLIENT(head))
1869                                                         {
1870                                                                 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1871                                                                 ctf_Handle_Throw(head, player, DROP_PASS);
1872                                                         }
1873                                                         else
1874                                                         {
1875                                                                 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
1876                                                                 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
1877                                                         }
1878                                                         player.throw_antispam = time + autocvar_g_ctf_pass_wait; 
1879                                                         return TRUE; 
1880                                                 }
1881                                                 else if(player.flagcarried)
1882                                                 {
1883                                                         if(closest_target)
1884                                                         {
1885                                                                 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1886                                                                 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1887                                                                         { closest_target = head; }
1888                                                         }
1889                                                         else { closest_target = head; }
1890                                                 }
1891                                         }
1892                                 }
1893                                 head = head.chain;
1894                         }
1895                         
1896                         if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1897                 }
1898                 
1899                 // throw the flag in front of you
1900                 if(autocvar_g_ctf_throw && player.flagcarried)
1901                 {
1902                         if(player.throw_count == -1)
1903                         {
1904                                 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1905                                 {
1906                                         player.throw_prevtime = time;
1907                                         player.throw_count = 1;
1908                                         ctf_Handle_Throw(player, world, DROP_THROW);
1909                                         return TRUE;
1910                                 }
1911                                 else
1912                                 {
1913                                         Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
1914                                         return FALSE;
1915                                 }
1916                         }
1917                         else
1918                         {
1919                                 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1920                                 else { player.throw_count += 1; }
1921                                 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1922                                         
1923                                 player.throw_prevtime = time;
1924                                 ctf_Handle_Throw(player, world, DROP_THROW);
1925                                 return TRUE;
1926                         }
1927                 }
1928         }
1929                 
1930         return FALSE;
1931 }
1932
1933 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1934 {
1935         if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1936         {
1937                 self.wps_helpme_time = time;
1938                 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1939         } 
1940         else // create a normal help me waypointsprite
1941         {
1942                 WaypointSprite_Spawn("helpme", waypointsprite_deployed_lifetime, waypointsprite_limitedrange, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, wps_helpme, FALSE, RADARICON_HELPME, '1 0.5 0');
1943                 WaypointSprite_Ping(self.wps_helpme);
1944         }
1945
1946         return TRUE;
1947 }
1948
1949 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1950 {
1951         if(vh_player.flagcarried)
1952         {
1953                 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1954                 {
1955                         ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1956                 }
1957                 else
1958                 {            
1959                         setattachment(vh_player.flagcarried, vh_vehicle, ""); 
1960                         setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1961                         vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1962                         //vh_player.flagcarried.angles = '0 0 0';       
1963                 }
1964                 return TRUE;
1965         }
1966                 
1967         return FALSE;
1968 }
1969
1970 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1971 {
1972         if(vh_player.flagcarried)
1973         {
1974                 setattachment(vh_player.flagcarried, vh_player, ""); 
1975                 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1976                 vh_player.flagcarried.scale = FLAG_SCALE;
1977                 vh_player.flagcarried.angles = '0 0 0';
1978                 return TRUE;
1979         }
1980
1981         return FALSE;
1982 }
1983
1984 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1985 {
1986         if(self.flagcarried)
1987         {
1988                 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_));
1989                 ctf_RespawnFlag(self.flagcarried);
1990                 return TRUE;
1991         }
1992         
1993         return FALSE;
1994 }
1995
1996 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1997 {
1998         entity flag; // temporary entity for the search method
1999         
2000         for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2001         {
2002                 switch(flag.ctf_status)
2003                 {
2004                         case FLAG_DROPPED:
2005                         case FLAG_PASSING:
2006                         {
2007                                 // lock the flag, game is over
2008                                 flag.movetype = MOVETYPE_NONE;
2009                                 flag.takedamage = DAMAGE_NO;
2010                                 flag.solid = SOLID_NOT;
2011                                 flag.nextthink = FALSE; // stop thinking
2012                                 
2013                                 //dprint("stopping the ", flag.netname, " from moving.\n");
2014                                 break;
2015                         }
2016                         
2017                         default:
2018                         case FLAG_BASE:
2019                         case FLAG_CARRY:
2020                         {
2021                                 // do nothing for these flags
2022                                 break;
2023                         }
2024                 }
2025         }
2026         
2027         return FALSE;
2028 }
2029
2030 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2031 {
2032         havocbot_ctf_reset_role(self);
2033         return TRUE;
2034 }
2035
2036 MUTATOR_HOOKFUNCTION(ctf_GetCvars)
2037 {
2038         GetCvars_handleFloat(get_cvars_s, get_cvars_f, CAPTURE_VERBOSE, "notification_ctf_capture_verbose");
2039         GetCvars_handleFloat(get_cvars_s, get_cvars_f, PICKUP_TEAM_VERBOSE, "notification_ctf_pickup_team_verbose");
2040         GetCvars_handleFloat(get_cvars_s, get_cvars_f, PICKUP_ENEMY_VERBOSE, "notification_ctf_pickup_enemy_verbose");
2041         return TRUE;
2042 }
2043
2044
2045 // ==========
2046 // Spawnfuncs
2047 // ==========
2048
2049 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2050 CTF Starting point for a player in team one (Red).
2051 Keys: "angle" viewing angle when spawning. */
2052 void spawnfunc_info_player_team1()
2053 {
2054         if(g_assault) { remove(self); return; }
2055         
2056         self.team = NUM_TEAM_1; // red
2057         spawnfunc_info_player_deathmatch();
2058 }
2059
2060
2061 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2062 CTF Starting point for a player in team two (Blue).
2063 Keys: "angle" viewing angle when spawning. */
2064 void spawnfunc_info_player_team2()
2065 {
2066         if(g_assault) { remove(self); return; }
2067         
2068         self.team = NUM_TEAM_2; // blue
2069         spawnfunc_info_player_deathmatch();
2070 }
2071
2072 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2073 CTF Starting point for a player in team three (Yellow).
2074 Keys: "angle" viewing angle when spawning. */
2075 void spawnfunc_info_player_team3()
2076 {
2077         if(g_assault) { remove(self); return; }
2078         
2079         self.team = NUM_TEAM_3; // yellow
2080         spawnfunc_info_player_deathmatch();
2081 }
2082
2083
2084 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2085 CTF Starting point for a player in team four (Purple).
2086 Keys: "angle" viewing angle when spawning. */
2087 void spawnfunc_info_player_team4()
2088 {
2089         if(g_assault) { remove(self); return; }
2090         
2091         self.team = NUM_TEAM_4; // purple
2092         spawnfunc_info_player_deathmatch();
2093 }
2094
2095 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2096 CTF flag for team one (Red).
2097 Keys: 
2098 "angle" Angle the flag will point (minus 90 degrees)... 
2099 "model" model to use, note this needs red and blue as skins 0 and 1...
2100 "noise" sound played when flag is picked up...
2101 "noise1" sound played when flag is returned by a teammate...
2102 "noise2" sound played when flag is captured...
2103 "noise3" sound played when flag is lost in the field and respawns itself... 
2104 "noise4" sound played when flag is dropped by a player...
2105 "noise5" sound played when flag touches the ground... */
2106 void spawnfunc_item_flag_team1()
2107 {
2108         if(!g_ctf) { remove(self); return; }
2109
2110         ctf_FlagSetup(1, self); // 1 = red
2111 }
2112
2113 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2114 CTF flag for team two (Blue).
2115 Keys: 
2116 "angle" Angle the flag will point (minus 90 degrees)... 
2117 "model" model to use, note this needs red and blue as skins 0 and 1...
2118 "noise" sound played when flag is picked up...
2119 "noise1" sound played when flag is returned by a teammate...
2120 "noise2" sound played when flag is captured...
2121 "noise3" sound played when flag is lost in the field and respawns itself... 
2122 "noise4" sound played when flag is dropped by a player...
2123 "noise5" sound played when flag touches the ground... */
2124 void spawnfunc_item_flag_team2()
2125 {
2126         if(!g_ctf) { remove(self); return; }
2127
2128         ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2129 }
2130
2131 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2132 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2133 Note: If you use spawnfunc_ctf_team entities you must define at least 2!  However, unlike domination, you don't need to make a blank one too.
2134 Keys:
2135 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2136 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2137 void spawnfunc_ctf_team()
2138 {
2139         if(!g_ctf) { remove(self); return; }
2140         
2141         self.classname = "ctf_team";
2142         self.team = self.cnt + 1;
2143 }
2144
2145 // compatibility for quake maps
2146 void spawnfunc_team_CTF_redflag()    { spawnfunc_item_flag_team1();    }
2147 void spawnfunc_team_CTF_blueflag()   { spawnfunc_item_flag_team2();    }
2148 void spawnfunc_team_CTF_redplayer()  { spawnfunc_info_player_team1();  }
2149 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2();  }
2150 void spawnfunc_team_CTF_redspawn()   { spawnfunc_info_player_team1();  }
2151 void spawnfunc_team_CTF_bluespawn()  { spawnfunc_info_player_team2();  }
2152
2153
2154 // ==============
2155 // Initialization
2156 // ==============
2157
2158 // scoreboard setup
2159 void ctf_ScoreRules()
2160 {
2161         ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2162         ScoreInfo_SetLabel_TeamScore  (ST_CTF_CAPS,     "caps",      SFL_SORT_PRIO_PRIMARY);
2163         ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS,     "caps",      SFL_SORT_PRIO_SECONDARY);
2164         ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME,  "captime",   SFL_LOWER_IS_BETTER | SFL_TIME);
2165         ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS,  "pickups",   0);
2166         ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS,  "fckills",   0);
2167         ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS,  "returns",   0);
2168         ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS,    "drops",     SFL_LOWER_IS_BETTER);
2169         ScoreRules_basics_end();
2170 }
2171
2172 // code from here on is just to support maps that don't have flag and team entities
2173 void ctf_SpawnTeam (string teamname, float teamcolor)
2174 {
2175         entity oldself;
2176         oldself = self;
2177         self = spawn();
2178         self.classname = "ctf_team";
2179         self.netname = teamname;
2180         self.cnt = teamcolor;
2181
2182         spawnfunc_ctf_team();
2183
2184         self = oldself;
2185 }
2186
2187 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2188 {
2189         // if no teams are found, spawn defaults
2190         if(find(world, classname, "ctf_team") == world)
2191         {
2192                 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2193                 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2194                 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2195         }
2196         
2197         ctf_ScoreRules();
2198 }
2199
2200 void ctf_Initialize()
2201 {
2202         ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2203
2204         ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2205         ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2206         ctf_captureshield_force = autocvar_g_ctf_shield_force;
2207         
2208         InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2209 }
2210
2211
2212 MUTATOR_DEFINITION(gamemode_ctf)
2213 {
2214         MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2215         MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2216         MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2217         MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2218         MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2219         MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2220         MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2221         MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2222         MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2223         MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2224         MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2225         MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2226         MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2227         MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
2228         MUTATOR_HOOK(GetCvars, ctf_GetCvars, CBC_ORDER_ANY);
2229         
2230         MUTATOR_ONADD
2231         {
2232                 if(time > 1) // game loads at time 1
2233                         error("This is a game type and it cannot be added at runtime.");
2234                 ctf_Initialize();
2235         }
2236
2237         MUTATOR_ONROLLBACK_OR_REMOVE
2238         {
2239                 // we actually cannot roll back ctf_Initialize here
2240                 // BUT: we don't need to! If this gets called, adding always
2241                 // succeeds.
2242         }
2243
2244         MUTATOR_ONREMOVE
2245         {
2246                 print("This is a game type and it cannot be removed at runtime.");
2247                 return -1;
2248         }
2249
2250         return 0;
2251 }