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