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