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