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