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