]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/mutator/gamemode_ctf.qc
Mutators: combine headers
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / mutator / gamemode_ctf.qc
1 #ifndef GAMEMODE_CTF_H
2 #define GAMEMODE_CTF_H
3
4 #ifdef SVQC
5 // used in cheats.qc
6 void ctf_RespawnFlag(entity flag);
7
8 // score rule declarations
9 const int ST_CTF_CAPS = 1;
10 const int SP_CTF_CAPS = 4;
11 const int SP_CTF_CAPTIME = 5;
12 const int SP_CTF_PICKUPS = 6;
13 const int SP_CTF_DROPS = 7;
14 const int SP_CTF_FCKILLS = 8;
15 const int SP_CTF_RETURNS = 9;
16
17 // flag constants // for most of these, there is just one question to be asked: WHYYYYY?
18 #define FLAG_MIN (PL_MIN_CONST + '0 0 -13')
19 #define FLAG_MAX (PL_MAX_CONST + '0 0 -13')
20
21 const float FLAG_SCALE = 0.6;
22
23 const float FLAG_THINKRATE = 0.2;
24 const float FLAG_TOUCHRATE = 0.5;
25 const float WPFE_THINKRATE = 0.5;
26
27 const vector FLAG_DROP_OFFSET = ('0 0 32');
28 const vector FLAG_CARRY_OFFSET = ('-16 0 8');
29 #define FLAG_SPAWN_OFFSET ('0 0 1' * (PL_MAX_CONST.z - 13))
30 const vector FLAG_WAYPOINT_OFFSET = ('0 0 64');
31 const vector FLAG_FLOAT_OFFSET = ('0 0 32');
32 const vector FLAG_PASS_ARC_OFFSET = ('0 0 -10');
33
34 const vector VEHICLE_FLAG_OFFSET = ('0 0 96');
35 const float VEHICLE_FLAG_SCALE = 1.0;
36
37 // waypoint colors
38 #define WPCOLOR_ENEMYFC(t) ((t) ? colormapPaletteColor(t - 1, false) * 0.75 : '1 1 1')
39 #define WPCOLOR_FLAGCARRIER(t) (WP_FlagCarrier.m_color)
40 #define WPCOLOR_DROPPEDFLAG(t) ((t) ? ('0.25 0.25 0.25' + colormapPaletteColor(t - 1, false)) * 0.5 : '1 1 1')
41
42 // sounds
43 #define snd_flag_taken noise
44 #define snd_flag_returned noise1
45 #define snd_flag_capture noise2
46 #define snd_flag_respawn noise3
47 .string snd_flag_dropped;
48 .string snd_flag_touch;
49 .string snd_flag_pass;
50
51 // effects
52 .string toucheffect;
53 .string passeffect;
54 .string capeffect;
55
56 // list of flags on the map
57 entity ctf_worldflaglist;
58 .entity ctf_worldflagnext;
59 .entity ctf_staleflagnext;
60
61 // waypoint sprites
62 .entity bot_basewaypoint; // flag waypointsprite
63 .entity wps_helpme;
64 .entity wps_flagbase;
65 .entity wps_flagcarrier;
66 .entity wps_flagdropped;
67 .entity wps_enemyflagcarrier;
68 .float wps_helpme_time;
69 bool wpforenemy_announced;
70 float wpforenemy_nextthink;
71
72 // statuses
73 const int FLAG_BASE = 1;
74 const int FLAG_DROPPED = 2;
75 const int FLAG_CARRY = 3;
76 const int FLAG_PASSING = 4;
77
78 const int DROP_NORMAL = 1;
79 const int DROP_THROW = 2;
80 const int DROP_PASS = 3;
81 const int DROP_RESET = 4;
82
83 const int PICKUP_BASE = 1;
84 const int PICKUP_DROPPED = 2;
85
86 const int CAPTURE_NORMAL = 1;
87 const int CAPTURE_DROPPED = 2;
88
89 const int RETURN_TIMEOUT = 1;
90 const int RETURN_DROPPED = 2;
91 const int RETURN_DAMAGE = 3;
92 const int RETURN_SPEEDRUN = 4;
93 const int RETURN_NEEDKILL = 5;
94
95 // flag properties
96 #define ctf_spawnorigin dropped_origin
97 bool ctf_stalemate; // indicates that a stalemate is active
98 float ctf_captimerecord; // record time for capturing the flag
99 .float ctf_pickuptime;
100 .float ctf_droptime;
101 .int ctf_status; // status of the flag (FLAG_BASE, FLAG_DROPPED, FLAG_CARRY declared globally)
102 .entity ctf_dropper; // don't allow spam of dropping the flag
103 .int max_flag_health;
104 .float next_take_time;
105 .bool ctf_flagdamaged;
106 int ctf_teams;
107
108 // passing/throwing properties
109 .float pass_distance;
110 .entity pass_sender;
111 .entity pass_target;
112 .float throw_antispam;
113 .float throw_prevtime;
114 .int throw_count;
115
116 // CaptureShield: If the player is too bad to be allowed to capture, shield them from taking the flag.
117 .bool ctf_captureshielded; // set to 1 if the player is too bad to be allowed to capture
118 float ctf_captureshield_min_negscore; // punish at -20 points
119 float ctf_captureshield_max_ratio; // punish at most 30% of each team
120 float ctf_captureshield_force; // push force of the shield
121
122 // 1 flag ctf
123 bool ctf_oneflag; // indicates whether or not a neutral flag has been found
124
125 // bot player logic
126 const int HAVOCBOT_CTF_ROLE_NONE = 0;
127 const int HAVOCBOT_CTF_ROLE_DEFENSE = 2;
128 const int HAVOCBOT_CTF_ROLE_MIDDLE = 4;
129 const int HAVOCBOT_CTF_ROLE_OFFENSE = 8;
130 const int HAVOCBOT_CTF_ROLE_CARRIER = 16;
131 const int HAVOCBOT_CTF_ROLE_RETRIEVER = 32;
132 const int HAVOCBOT_CTF_ROLE_ESCORT = 64;
133
134 .bool havocbot_cantfindflag;
135
136 vector havocbot_ctf_middlepoint;
137 float havocbot_ctf_middlepoint_radius;
138
139 void havocbot_role_ctf_setrole(entity bot, int role);
140
141 // team checking
142 #define CTF_SAMETEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? DIFF_TEAM(a,b) : SAME_TEAM(a,b))
143 #define CTF_DIFFTEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? SAME_TEAM(a,b) : DIFF_TEAM(a,b))
144
145 // networked flag statuses
146 .int ctf_flagstatus;
147 #endif
148
149 const int CTF_RED_FLAG_TAKEN                    = 1;
150 const int CTF_RED_FLAG_LOST                             = 2;
151 const int CTF_RED_FLAG_CARRYING                 = 3;
152 const int CTF_BLUE_FLAG_TAKEN                   = 4;
153 const int CTF_BLUE_FLAG_LOST                    = 8;
154 const int CTF_BLUE_FLAG_CARRYING                = 12;
155 const int CTF_YELLOW_FLAG_TAKEN                 = 16;
156 const int CTF_YELLOW_FLAG_LOST                  = 32;
157 const int CTF_YELLOW_FLAG_CARRYING              = 48;
158 const int CTF_PINK_FLAG_TAKEN                   = 64;
159 const int CTF_PINK_FLAG_LOST                    = 128;
160 const int CTF_PINK_FLAG_CARRYING                = 192;
161 const int CTF_NEUTRAL_FLAG_TAKEN                = 256;
162 const int CTF_NEUTRAL_FLAG_LOST                 = 512;
163 const int CTF_NEUTRAL_FLAG_CARRYING             = 768;
164 const int CTF_FLAG_NEUTRAL                              = 2048;
165 const int CTF_SHIELDED                                  = 4096;
166 #endif
167
168 #ifdef IMPLEMENTATION
169
170 #ifdef SVQC
171 #include "../../../common/vehicles/all.qh"
172 #include "../../teamplay.qh"
173 #endif
174
175 #include "../../../lib/warpzone/common.qh"
176
177 bool autocvar_g_ctf_allow_vehicle_carry;
178 bool autocvar_g_ctf_allow_vehicle_touch;
179 bool autocvar_g_ctf_allow_monster_touch;
180 bool autocvar_g_ctf_throw;
181 float autocvar_g_ctf_throw_angle_max;
182 float autocvar_g_ctf_throw_angle_min;
183 int autocvar_g_ctf_throw_punish_count;
184 float autocvar_g_ctf_throw_punish_delay;
185 float autocvar_g_ctf_throw_punish_time;
186 float autocvar_g_ctf_throw_strengthmultiplier;
187 float autocvar_g_ctf_throw_velocity_forward;
188 float autocvar_g_ctf_throw_velocity_up;
189 float autocvar_g_ctf_drop_velocity_up;
190 float autocvar_g_ctf_drop_velocity_side;
191 bool autocvar_g_ctf_oneflag_reverse;
192 bool autocvar_g_ctf_portalteleport;
193 bool autocvar_g_ctf_pass;
194 float autocvar_g_ctf_pass_arc;
195 float autocvar_g_ctf_pass_arc_max;
196 float autocvar_g_ctf_pass_directional_max;
197 float autocvar_g_ctf_pass_directional_min;
198 float autocvar_g_ctf_pass_radius;
199 float autocvar_g_ctf_pass_wait;
200 bool autocvar_g_ctf_pass_request;
201 float autocvar_g_ctf_pass_turnrate;
202 float autocvar_g_ctf_pass_timelimit;
203 float autocvar_g_ctf_pass_velocity;
204 bool autocvar_g_ctf_dynamiclights;
205 float autocvar_g_ctf_flag_collect_delay;
206 float autocvar_g_ctf_flag_damageforcescale;
207 bool autocvar_g_ctf_flag_dropped_waypoint;
208 bool autocvar_g_ctf_flag_dropped_floatinwater;
209 bool autocvar_g_ctf_flag_glowtrails;
210 int autocvar_g_ctf_flag_health;
211 bool autocvar_g_ctf_flag_return;
212 float autocvar_g_ctf_flag_return_carried_radius;
213 float autocvar_g_ctf_flag_return_time;
214 bool autocvar_g_ctf_flag_return_when_unreachable;
215 float autocvar_g_ctf_flag_return_damage;
216 float autocvar_g_ctf_flag_return_damage_delay;
217 float autocvar_g_ctf_flag_return_dropped;
218 float autocvar_g_ctf_flagcarrier_auto_helpme_damage;
219 float autocvar_g_ctf_flagcarrier_auto_helpme_time;
220 float autocvar_g_ctf_flagcarrier_selfdamagefactor;
221 float autocvar_g_ctf_flagcarrier_selfforcefactor;
222 float autocvar_g_ctf_flagcarrier_damagefactor;
223 float autocvar_g_ctf_flagcarrier_forcefactor;
224 //float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting;
225 bool autocvar_g_ctf_fullbrightflags;
226 bool autocvar_g_ctf_ignore_frags;
227 int autocvar_g_ctf_score_capture;
228 int autocvar_g_ctf_score_capture_assist;
229 int autocvar_g_ctf_score_kill;
230 int autocvar_g_ctf_score_penalty_drop;
231 int autocvar_g_ctf_score_penalty_returned;
232 int autocvar_g_ctf_score_pickup_base;
233 int autocvar_g_ctf_score_pickup_dropped_early;
234 int autocvar_g_ctf_score_pickup_dropped_late;
235 int autocvar_g_ctf_score_return;
236 float autocvar_g_ctf_shield_force;
237 float autocvar_g_ctf_shield_max_ratio;
238 int autocvar_g_ctf_shield_min_negscore;
239 bool autocvar_g_ctf_stalemate;
240 int autocvar_g_ctf_stalemate_endcondition;
241 float autocvar_g_ctf_stalemate_time;
242 bool autocvar_g_ctf_reverse;
243 float autocvar_g_ctf_dropped_capture_delay;
244 float autocvar_g_ctf_dropped_capture_radius;
245
246 void ctf_FakeTimeLimit(entity e, float t)
247 {
248         msg_entity = e;
249         WriteByte(MSG_ONE, 3); // svc_updatestat
250         WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
251         if(t < 0)
252                 WriteCoord(MSG_ONE, autocvar_timelimit);
253         else
254                 WriteCoord(MSG_ONE, (t + 1) / 60);
255 }
256
257 void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
258 {
259         if(autocvar_sv_eventlog)
260                 GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != world) ? ftos(actor.playerid) : "")));
261                 //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
262 }
263
264 void ctf_CaptureRecord(entity flag, entity player)
265 {
266         float cap_record = ctf_captimerecord;
267         float cap_time = (time - flag.ctf_pickuptime);
268         string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
269
270         // notify about shit
271         if(ctf_oneflag) { Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); }
272         else if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
273         else if(cap_time < cap_record) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
274         else { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
275
276         // write that shit in the database
277         if(!ctf_oneflag) // but not in 1-flag mode
278         if((!ctf_captimerecord) || (cap_time < cap_record))
279         {
280                 ctf_captimerecord = cap_time;
281                 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
282                 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
283                 write_recordmarker(player, (time - cap_time), cap_time);
284         }
285 }
286
287 void ctf_FlagcarrierWaypoints(entity player)
288 {
289         WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
290         WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
291         WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
292         WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
293 }
294
295 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
296 {
297         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
298         float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
299         float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
300         //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
301
302         vector targpos;
303         if(current_height) // make sure we can actually do this arcing path
304         {
305                 targpos = (to + ('0 0 1' * current_height));
306                 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
307                 if(trace_fraction < 1)
308                 {
309                         //print("normal arc line failed, trying to find new pos...");
310                         WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
311                         targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
312                         WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
313                         if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
314                         /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
315                 }
316         }
317         else { targpos = to; }
318
319         //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
320
321         vector desired_direction = normalize(targpos - from);
322         if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
323         else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
324 }
325
326 bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
327 {
328         if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
329         {
330                 // directional tracing only
331                 float spreadlimit;
332                 makevectors(passer_angle);
333
334                 // find the closest point on the enemy to the center of the attack
335                 float h; // hypotenuse, which is the distance between attacker to head
336                 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
337
338                 h = vlen(head_center - passer_center);
339                 a = h * (normalize(head_center - passer_center) * v_forward);
340
341                 vector nearest_on_line = (passer_center + a * v_forward);
342                 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
343
344                 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
345                 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
346
347                 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
348                         { return true; }
349                 else
350                         { return false; }
351         }
352         else { return true; }
353 }
354
355
356 // =======================
357 // CaptureShield Functions
358 // =======================
359
360 bool ctf_CaptureShield_CheckStatus(entity p)
361 {
362         int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
363         entity e;
364         int players_worseeq, players_total;
365
366         if(ctf_captureshield_max_ratio <= 0)
367                 return false;
368
369         s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
370         s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
371         s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
372         s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
373
374         sr = ((s - s2) + (s3 + s4));
375
376         if(sr >= -ctf_captureshield_min_negscore)
377                 return false;
378
379         players_total = players_worseeq = 0;
380         FOR_EACH_PLAYER(e)
381         {
382                 if(DIFF_TEAM(e, p))
383                         continue;
384                 se = PlayerScore_Add(e, SP_CTF_CAPS, 0);
385                 se2 = PlayerScore_Add(e, SP_CTF_PICKUPS, 0);
386                 se3 = PlayerScore_Add(e, SP_CTF_RETURNS, 0);
387                 se4 = PlayerScore_Add(e, SP_CTF_FCKILLS, 0);
388
389                 ser = ((se - se2) + (se3 + se4));
390
391                 if(ser <= sr)
392                         ++players_worseeq;
393                 ++players_total;
394         }
395
396         // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
397         // use this rule here
398
399         if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
400                 return false;
401
402         return true;
403 }
404
405 void ctf_CaptureShield_Update(entity player, bool wanted_status)
406 {
407         bool updated_status = ctf_CaptureShield_CheckStatus(player);
408         if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
409         {
410                 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
411                 player.ctf_captureshielded = updated_status;
412         }
413 }
414
415 bool ctf_CaptureShield_Customize()
416 {SELFPARAM();
417         if(!other.ctf_captureshielded) { return false; }
418         if(CTF_SAMETEAM(self, other)) { return false; }
419
420         return true;
421 }
422
423 void ctf_CaptureShield_Touch()
424 {SELFPARAM();
425         if(!other.ctf_captureshielded) { return; }
426         if(CTF_SAMETEAM(self, other)) { return; }
427
428         vector mymid = (self.absmin + self.absmax) * 0.5;
429         vector othermid = (other.absmin + other.absmax) * 0.5;
430
431         Damage(other, self, self, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
432         if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
433 }
434
435 void ctf_CaptureShield_Spawn(entity flag)
436 {SELFPARAM();
437         entity shield = spawn();
438
439         shield.enemy = self;
440         shield.team = self.team;
441         shield.touch = ctf_CaptureShield_Touch;
442         shield.customizeentityforclient = ctf_CaptureShield_Customize;
443         shield.classname = "ctf_captureshield";
444         shield.effects = EF_ADDITIVE;
445         shield.movetype = MOVETYPE_NOCLIP;
446         shield.solid = SOLID_TRIGGER;
447         shield.avelocity = '7 0 11';
448         shield.scale = 0.5;
449
450         setorigin(shield, self.origin);
451         setmodel(shield, MDL_CTF_SHIELD);
452         setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
453 }
454
455
456 // ====================
457 // Drop/Pass/Throw Code
458 // ====================
459
460 void ctf_Handle_Drop(entity flag, entity player, int droptype)
461 {
462         // declarations
463         player = (player ? player : flag.pass_sender);
464
465         // main
466         flag.movetype = MOVETYPE_TOSS;
467         flag.takedamage = DAMAGE_YES;
468         flag.angles = '0 0 0';
469         flag.health = flag.max_flag_health;
470         flag.ctf_droptime = time;
471         flag.ctf_dropper = player;
472         flag.ctf_status = FLAG_DROPPED;
473
474         // messages and sounds
475         Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_LOST_) : INFO_CTF_LOST_NEUTRAL), player.netname);
476         _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
477         ctf_EventLog("dropped", player.team, player);
478
479         // scoring
480         PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
481         PlayerScore_Add(player, SP_CTF_DROPS, 1);
482
483         // waypoints
484         if(autocvar_g_ctf_flag_dropped_waypoint) {
485                 entity wp = WaypointSprite_Spawn(WP_FlagDropped, 0, 0, flag, FLAG_WAYPOINT_OFFSET, world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, true, RADARICON_FLAG);
486                 wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
487         }
488
489         if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
490         {
491                 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
492                 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
493         }
494
495         player.throw_antispam = time + autocvar_g_ctf_pass_wait;
496
497         if(droptype == DROP_PASS)
498         {
499                 flag.pass_distance = 0;
500                 flag.pass_sender = world;
501                 flag.pass_target = world;
502         }
503 }
504
505 void ctf_Handle_Retrieve(entity flag, entity player)
506 {
507         entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
508         entity sender = flag.pass_sender;
509
510         // transfer flag to player
511         flag.owner = player;
512         flag.owner.flagcarried = flag;
513
514         // reset flag
515         if(player.vehicle)
516         {
517                 setattachment(flag, player.vehicle, "");
518                 setorigin(flag, VEHICLE_FLAG_OFFSET);
519                 flag.scale = VEHICLE_FLAG_SCALE;
520         }
521         else
522         {
523                 setattachment(flag, player, "");
524                 setorigin(flag, FLAG_CARRY_OFFSET);
525         }
526         flag.movetype = MOVETYPE_NONE;
527         flag.takedamage = DAMAGE_NO;
528         flag.solid = SOLID_NOT;
529         flag.angles = '0 0 0';
530         flag.ctf_status = FLAG_CARRY;
531
532         // messages and sounds
533         _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
534         ctf_EventLog("receive", flag.team, player);
535
536         FOR_EACH_REALPLAYER(tmp_player)
537         {
538                 if(tmp_player == sender)
539                         Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_SENT_) : CENTER_CTF_PASS_SENT_NEUTRAL), player.netname);
540                 else if(tmp_player == player)
541                         Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_RECEIVED_) : CENTER_CTF_PASS_RECEIVED_NEUTRAL), sender.netname);
542                 else if(SAME_TEAM(tmp_player, sender))
543                         Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_OTHER_) : CENTER_CTF_PASS_OTHER_NEUTRAL), sender.netname, player.netname);
544         }
545
546         // create new waypoint
547         ctf_FlagcarrierWaypoints(player);
548
549         sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
550         player.throw_antispam = sender.throw_antispam;
551
552         flag.pass_distance = 0;
553         flag.pass_sender = world;
554         flag.pass_target = world;
555 }
556
557 void ctf_Handle_Throw(entity player, entity receiver, int droptype)
558 {
559         entity flag = player.flagcarried;
560         vector targ_origin, flag_velocity;
561
562         if(!flag) { return; }
563         if((droptype == DROP_PASS) && !receiver) { return; }
564
565         if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
566
567         // reset the flag
568         setattachment(flag, world, "");
569         setorigin(flag, player.origin + FLAG_DROP_OFFSET);
570         flag.owner.flagcarried = world;
571         flag.owner = world;
572         flag.solid = SOLID_TRIGGER;
573         flag.ctf_dropper = player;
574         flag.ctf_droptime = time;
575
576         flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
577
578         switch(droptype)
579         {
580                 case DROP_PASS:
581                 {
582                         // warpzone support:
583                         // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
584                         // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
585                         WarpZone_RefSys_Copy(flag, receiver);
586                         WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
587                         targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
588
589                         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
590                         ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
591
592                         // main
593                         flag.movetype = MOVETYPE_FLY;
594                         flag.takedamage = DAMAGE_NO;
595                         flag.pass_sender = player;
596                         flag.pass_target = receiver;
597                         flag.ctf_status = FLAG_PASSING;
598
599                         // other
600                         _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
601                         WarpZone_TrailParticles(world, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
602                         ctf_EventLog("pass", flag.team, player);
603                         break;
604                 }
605
606                 case DROP_THROW:
607                 {
608                         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'));
609
610                         flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & ITEM_Strength.m_itemid) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
611                         flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false);
612                         ctf_Handle_Drop(flag, player, droptype);
613                         break;
614                 }
615
616                 case DROP_RESET:
617                 {
618                         flag.velocity = '0 0 0'; // do nothing
619                         break;
620                 }
621
622                 default:
623                 case DROP_NORMAL:
624                 {
625                         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);
626                         ctf_Handle_Drop(flag, player, droptype);
627                         break;
628                 }
629         }
630
631         // kill old waypointsprite
632         WaypointSprite_Ping(player.wps_flagcarrier);
633         WaypointSprite_Kill(player.wps_flagcarrier);
634
635         if(player.wps_enemyflagcarrier)
636                 WaypointSprite_Kill(player.wps_enemyflagcarrier);
637
638         // captureshield
639         ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
640 }
641
642
643 // ==============
644 // Event Handlers
645 // ==============
646
647 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
648 {
649         entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
650         entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
651         entity player_team_flag = world, tmp_entity;
652         float old_time, new_time;
653
654         if(!player) { return; } // without someone to give the reward to, we can't possibly cap
655         if(CTF_DIFFTEAM(player, flag)) { return; }
656
657         if(ctf_oneflag)
658         for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
659         if(SAME_TEAM(tmp_entity, player))
660         {
661                 player_team_flag = tmp_entity;
662                 break;
663         }
664
665         nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
666
667         player.throw_prevtime = time;
668         player.throw_count = 0;
669
670         // messages and sounds
671         Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_) : CENTER_CTF_CAPTURE_NEUTRAL));
672         ctf_CaptureRecord(enemy_flag, player);
673         _sound(player, CH_TRIGGER, ((ctf_oneflag) ? player_team_flag.snd_flag_capture : ((DIFF_TEAM(player, flag)) ? enemy_flag.snd_flag_capture : flag.snd_flag_capture)), VOL_BASE, ATTEN_NONE);
674
675         switch(capturetype)
676         {
677                 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
678                 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
679                 default: break;
680         }
681
682         // scoring
683         PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
684         PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
685
686         old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
687         new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
688         if(!old_time || new_time < old_time)
689                 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
690
691         // effects
692         Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
693         //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
694
695         // other
696         if(capturetype == CAPTURE_NORMAL)
697         {
698                 WaypointSprite_Kill(player.wps_flagcarrier);
699                 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
700
701                 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
702                         { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
703         }
704
705         // reset the flag
706         player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
707         ctf_RespawnFlag(enemy_flag);
708 }
709
710 void ctf_Handle_Return(entity flag, entity player)
711 {
712         // messages and sounds
713         if(IS_MONSTER(player))
714         {
715                 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
716         }
717         else if(flag.team)
718         {
719                 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_RETURN_));
720                 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_), player.netname);
721         }
722         _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
723         ctf_EventLog("return", flag.team, player);
724
725         // scoring
726         if(IS_PLAYER(player))
727         {
728                 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
729                 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
730
731                 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
732         }
733
734         TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
735
736         if(flag.ctf_dropper)
737         {
738                 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
739                 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
740                 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
741         }
742
743         // other
744         if(player.flagcarried == flag)
745                 WaypointSprite_Kill(player.wps_flagcarrier);
746
747         // reset the flag
748         ctf_RespawnFlag(flag);
749 }
750
751 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
752 {
753         // declarations
754         float pickup_dropped_score; // used to calculate dropped pickup score
755         entity tmp_entity; // temporary entity
756
757         // attach the flag to the player
758         flag.owner = player;
759         player.flagcarried = flag;
760         if(player.vehicle)
761         {
762                 setattachment(flag, player.vehicle, "");
763                 setorigin(flag, VEHICLE_FLAG_OFFSET);
764                 flag.scale = VEHICLE_FLAG_SCALE;
765         }
766         else
767         {
768                 setattachment(flag, player, "");
769                 setorigin(flag, FLAG_CARRY_OFFSET);
770         }
771
772         // flag setup
773         flag.movetype = MOVETYPE_NONE;
774         flag.takedamage = DAMAGE_NO;
775         flag.solid = SOLID_NOT;
776         flag.angles = '0 0 0';
777         flag.ctf_status = FLAG_CARRY;
778
779         switch(pickuptype)
780         {
781                 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
782                 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
783                 default: break;
784         }
785
786         // messages and sounds
787         Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_) : INFO_CTF_PICKUP_NEUTRAL), player.netname);
788         if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
789         if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); }
790         else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); }
791         else { Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_TEAM : CENTER_CTF_PICKUP_TEAM_ENEMY), Team_ColorCode(flag.team)); }
792
793         Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, ((flag.team) ? APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_) : CHOICE_CTF_PICKUP_TEAM_NEUTRAL), Team_ColorCode(player.team), player.netname);
794
795         if(!flag.team)
796         FOR_EACH_PLAYER(tmp_entity)
797         if(tmp_entity != player)
798         if(DIFF_TEAM(player, tmp_entity))
799                 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname);
800
801         if(flag.team)
802         FOR_EACH_PLAYER(tmp_entity)
803         if(tmp_entity != player)
804         if(CTF_SAMETEAM(flag, tmp_entity))
805         if(SAME_TEAM(player, tmp_entity))
806                 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname);
807         else
808                 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, ((SAME_TEAM(flag, player)) ? CHOICE_CTF_PICKUP_ENEMY_TEAM : CHOICE_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), player.netname);
809
810         _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
811
812         // scoring
813         PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
814         nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
815         switch(pickuptype)
816         {
817                 case PICKUP_BASE:
818                 {
819                         PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
820                         ctf_EventLog("steal", flag.team, player);
821                         break;
822                 }
823
824                 case PICKUP_DROPPED:
825                 {
826                         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);
827                         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);
828                         LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
829                         PlayerTeamScore_AddScore(player, pickup_dropped_score);
830                         ctf_EventLog("pickup", flag.team, player);
831                         break;
832                 }
833
834                 default: break;
835         }
836
837         // speedrunning
838         if(pickuptype == PICKUP_BASE)
839         {
840                 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
841                 if((player.speedrunning) && (ctf_captimerecord))
842                         ctf_FakeTimeLimit(player, time + ctf_captimerecord);
843         }
844
845         // effects
846         Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
847
848         // waypoints
849         if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
850         ctf_FlagcarrierWaypoints(player);
851         WaypointSprite_Ping(player.wps_flagcarrier);
852 }
853
854
855 // ===================
856 // Main Flag Functions
857 // ===================
858
859 void ctf_CheckFlagReturn(entity flag, int returntype)
860 {
861         if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
862         {
863                 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
864
865                 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
866                 {
867                         switch(returntype)
868                         {
869                                 case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DROPPED_) : INFO_CTF_FLAGRETURN_DROPPED_NEUTRAL)); break;
870                                 case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DAMAGED_) : INFO_CTF_FLAGRETURN_DAMAGED_NEUTRAL)); break;
871                                 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_) : INFO_CTF_FLAGRETURN_SPEEDRUN_NEUTRAL), ctf_captimerecord); break;
872                                 case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_NEEDKILL_) : INFO_CTF_FLAGRETURN_NEEDKILL_NEUTRAL)); break;
873
874                                 default:
875                                 case RETURN_TIMEOUT:
876                                         { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; }
877                         }
878                         _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
879                         ctf_EventLog("returned", flag.team, world);
880                         ctf_RespawnFlag(flag);
881                 }
882         }
883 }
884
885 bool ctf_Stalemate_Customize()
886 {SELFPARAM();
887         // make spectators see what the player would see
888         entity e, wp_owner;
889         e = WaypointSprite_getviewentity(other);
890         wp_owner = self.owner;
891
892         // team waypoints
893         if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
894         if(SAME_TEAM(wp_owner, e)) { return false; }
895         if(!IS_PLAYER(e)) { return false; }
896
897         return true;
898 }
899
900 void ctf_CheckStalemate(void)
901 {
902         // declarations
903         int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
904         entity tmp_entity;
905
906         entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
907
908         // build list of stale flags
909         for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
910         {
911                 if(autocvar_g_ctf_stalemate)
912                 if(tmp_entity.ctf_status != FLAG_BASE)
913                 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
914                 {
915                         tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
916                         ctf_staleflaglist = tmp_entity;
917
918                         switch(tmp_entity.team)
919                         {
920                                 case NUM_TEAM_1: ++stale_red_flags; break;
921                                 case NUM_TEAM_2: ++stale_blue_flags; break;
922                                 case NUM_TEAM_3: ++stale_yellow_flags; break;
923                                 case NUM_TEAM_4: ++stale_pink_flags; break;
924                                 default: ++stale_neutral_flags; break;
925                         }
926                 }
927         }
928
929         if(ctf_oneflag)
930                 stale_flags = (stale_neutral_flags >= 1);
931         else
932                 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
933
934         if(ctf_oneflag && stale_flags == 1)
935                 ctf_stalemate = true;
936         else if(stale_flags >= 2)
937                 ctf_stalemate = true;
938         else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
939                 { ctf_stalemate = false; wpforenemy_announced = false; }
940         else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
941                 { ctf_stalemate = false; wpforenemy_announced = false; }
942
943         // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
944         if(ctf_stalemate)
945         {
946                 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
947                 {
948                         if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
949                         {
950                                 entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, 0, tmp_entity.owner, wps_enemyflagcarrier, true, RADARICON_FLAG);
951                                 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
952                                 tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_Customize;
953                         }
954                 }
955
956                 if (!wpforenemy_announced)
957                 {
958                         FOR_EACH_REALPLAYER(tmp_entity)
959                                 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
960
961                         wpforenemy_announced = true;
962                 }
963         }
964 }
965
966 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
967 {SELFPARAM();
968         if(ITEM_DAMAGE_NEEDKILL(deathtype))
969         {
970                 if(autocvar_g_ctf_flag_return_damage_delay)
971                 {
972                         self.ctf_flagdamaged = true;
973                 }
974                 else
975                 {
976                         self.health = 0;
977                         ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
978                 }
979                 return;
980         }
981         if(autocvar_g_ctf_flag_return_damage)
982         {
983                 // reduce health and check if it should be returned
984                 self.health = self.health - damage;
985                 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
986                 return;
987         }
988 }
989
990 void ctf_FlagThink()
991 {SELFPARAM();
992         // declarations
993         entity tmp_entity;
994
995         self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
996
997         // captureshield
998         if(self == ctf_worldflaglist) // only for the first flag
999                 FOR_EACH_CLIENT(tmp_entity)
1000                         ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
1001
1002         // sanity checks
1003         if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
1004                 LOG_TRACE("wtf the flag got squashed?\n");
1005                 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
1006                 if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
1007                         setsize(self, FLAG_MIN, FLAG_MAX); }
1008
1009         switch(self.ctf_status) // reset flag angles in case warpzones adjust it
1010         {
1011                 case FLAG_DROPPED:
1012                 {
1013                         self.angles = '0 0 0';
1014                         break;
1015                 }
1016
1017                 default: break;
1018         }
1019
1020         // main think method
1021         switch(self.ctf_status)
1022         {
1023                 case FLAG_BASE:
1024                 {
1025                         if(autocvar_g_ctf_dropped_capture_radius)
1026                         {
1027                                 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
1028                                         if(tmp_entity.ctf_status == FLAG_DROPPED)
1029                                         if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
1030                                         if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
1031                                                 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
1032                         }
1033                         return;
1034                 }
1035
1036                 case FLAG_DROPPED:
1037                 {
1038                         if(autocvar_g_ctf_flag_dropped_floatinwater)
1039                         {
1040                                 vector midpoint = ((self.absmin + self.absmax) * 0.5);
1041                                 if(pointcontents(midpoint) == CONTENT_WATER)
1042                                 {
1043                                         self.velocity = self.velocity * 0.5;
1044
1045                                         if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
1046                                                 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
1047                                         else
1048                                                 { self.movetype = MOVETYPE_FLY; }
1049                                 }
1050                                 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
1051                         }
1052                         if(autocvar_g_ctf_flag_return_dropped)
1053                         {
1054                                 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
1055                                 {
1056                                         self.health = 0;
1057                                         ctf_CheckFlagReturn(self, RETURN_DROPPED);
1058                                         return;
1059                                 }
1060                         }
1061                         if(self.ctf_flagdamaged)
1062                         {
1063                                 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
1064                                 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
1065                                 return;
1066                         }
1067                         else if(autocvar_g_ctf_flag_return_time)
1068                         {
1069                                 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
1070                                 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
1071                                 return;
1072                         }
1073                         return;
1074                 }
1075
1076                 case FLAG_CARRY:
1077                 {
1078                         if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
1079                         {
1080                                 self.health = 0;
1081                                 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
1082
1083                                 setself(self.owner);
1084                                 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
1085                                 ImpulseCommands();
1086                                 setself(this);
1087                         }
1088                         if(autocvar_g_ctf_stalemate)
1089                         {
1090                                 if(time >= wpforenemy_nextthink)
1091                                 {
1092                                         ctf_CheckStalemate();
1093                                         wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
1094                                 }
1095                         }
1096                         if(CTF_SAMETEAM(self, self.owner) && self.team)
1097                         {
1098                                 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
1099                                         ctf_Handle_Throw(self.owner, world, DROP_THROW);
1100                                 else if(vlen(self.owner.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_carried_radius)
1101                                         ctf_Handle_Return(self, self.owner);
1102                         }
1103                         return;
1104                 }
1105
1106                 case FLAG_PASSING:
1107                 {
1108                         vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
1109                         targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
1110                         WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
1111
1112                         if((self.pass_target == world)
1113                                 || (self.pass_target.deadflag != DEAD_NO)
1114                                 || (self.pass_target.flagcarried)
1115                                 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
1116                                 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
1117                                 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
1118                         {
1119                                 // give up, pass failed
1120                                 ctf_Handle_Drop(self, world, DROP_PASS);
1121                         }
1122                         else
1123                         {
1124                                 // still a viable target, go for it
1125                                 ctf_CalculatePassVelocity(self, targ_origin, self.origin, true);
1126                         }
1127                         return;
1128                 }
1129
1130                 default: // this should never happen
1131                 {
1132                         LOG_TRACE("ctf_FlagThink(): Flag exists with no status?\n");
1133                         return;
1134                 }
1135         }
1136 }
1137
1138 void ctf_FlagTouch()
1139 {SELFPARAM();
1140         if(gameover) { return; }
1141         if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
1142
1143         entity toucher = other, tmp_entity;
1144         bool is_not_monster = (!IS_MONSTER(toucher)), num_perteam = 0;
1145
1146         // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1147         if(ITEM_TOUCH_NEEDKILL())
1148         {
1149                 if(!autocvar_g_ctf_flag_return_damage_delay)
1150                 {
1151                         self.health = 0;
1152                         ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
1153                 }
1154                 if(!self.ctf_flagdamaged) { return; }
1155         }
1156
1157         FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; }
1158
1159         // special touch behaviors
1160         if(toucher.frozen) { return; }
1161         else if(IS_VEHICLE(toucher))
1162         {
1163                 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
1164                         toucher = toucher.owner; // the player is actually the vehicle owner, not other
1165                 else
1166                         return; // do nothing
1167         }
1168         else if(IS_MONSTER(toucher))
1169         {
1170                 if(!autocvar_g_ctf_allow_monster_touch)
1171                         return; // do nothing
1172         }
1173         else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1174         {
1175                 if(time > self.wait) // if we haven't in a while, play a sound/effect
1176                 {
1177                         Send_Effect_(self.toucheffect, self.origin, '0 0 0', 1);
1178                         _sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1179                         self.wait = time + FLAG_TOUCHRATE;
1180                 }
1181                 return;
1182         }
1183         else if(toucher.deadflag != DEAD_NO) { return; }
1184
1185         switch(self.ctf_status)
1186         {
1187                 case FLAG_BASE:
1188                 {
1189                         if(ctf_oneflag)
1190                         {
1191                                 if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
1192                                         ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1193                                 else if(!self.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1194                                         ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the neutral flag
1195                         }
1196                         else if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
1197                                 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
1198                         else if(CTF_DIFFTEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1199                                 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
1200                         break;
1201                 }
1202
1203                 case FLAG_DROPPED:
1204                 {
1205                         if(CTF_SAMETEAM(toucher, self) && (autocvar_g_ctf_flag_return || num_perteam <= 1) && self.team) // automatically return if there's only 1 player on the team
1206                                 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
1207                         else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1208                                 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1209                         break;
1210                 }
1211
1212                 case FLAG_CARRY:
1213                 {
1214                         LOG_TRACE("Someone touched a flag even though it was being carried?\n");
1215                         break;
1216                 }
1217
1218                 case FLAG_PASSING:
1219                 {
1220                         if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
1221                         {
1222                                 if(DIFF_TEAM(toucher, self.pass_sender))
1223                                         ctf_Handle_Return(self, toucher);
1224                                 else
1225                                         ctf_Handle_Retrieve(self, toucher);
1226                         }
1227                         break;
1228                 }
1229         }
1230 }
1231
1232 .float last_respawn;
1233 void ctf_RespawnFlag(entity flag)
1234 {
1235         // check for flag respawn being called twice in a row
1236         if(flag.last_respawn > time - 0.5)
1237                 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1238
1239         flag.last_respawn = time;
1240
1241         // reset the player (if there is one)
1242         if((flag.owner) && (flag.owner.flagcarried == flag))
1243         {
1244                 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1245                 WaypointSprite_Kill(flag.wps_flagcarrier);
1246
1247                 flag.owner.flagcarried = world;
1248
1249                 if(flag.speedrunning)
1250                         ctf_FakeTimeLimit(flag.owner, -1);
1251         }
1252
1253         if((flag.owner) && (flag.owner.vehicle))
1254                 flag.scale = FLAG_SCALE;
1255
1256         if(flag.ctf_status == FLAG_DROPPED)
1257                 { WaypointSprite_Kill(flag.wps_flagdropped); }
1258
1259         // reset the flag
1260         setattachment(flag, world, "");
1261         setorigin(flag, flag.ctf_spawnorigin);
1262
1263         flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
1264         flag.takedamage = DAMAGE_NO;
1265         flag.health = flag.max_flag_health;
1266         flag.solid = SOLID_TRIGGER;
1267         flag.velocity = '0 0 0';
1268         flag.angles = flag.mangle;
1269         flag.flags = FL_ITEM | FL_NOTARGET;
1270
1271         flag.ctf_status = FLAG_BASE;
1272         flag.owner = world;
1273         flag.pass_distance = 0;
1274         flag.pass_sender = world;
1275         flag.pass_target = world;
1276         flag.ctf_dropper = world;
1277         flag.ctf_pickuptime = 0;
1278         flag.ctf_droptime = 0;
1279         flag.ctf_flagdamaged = 0;
1280
1281         ctf_CheckStalemate();
1282 }
1283
1284 void ctf_Reset()
1285 {SELFPARAM();
1286         if(self.owner)
1287                 if(IS_PLAYER(self.owner))
1288                         ctf_Handle_Throw(self.owner, world, DROP_RESET);
1289
1290         ctf_RespawnFlag(self);
1291 }
1292
1293 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
1294 {SELFPARAM();
1295         // bot waypoints
1296         waypoint_spawnforitem_force(self, self.origin);
1297         self.nearestwaypointtimeout = 0; // activate waypointing again
1298         self.bot_basewaypoint = self.nearestwaypoint;
1299
1300         // waypointsprites
1301         entity basename;
1302         switch (self.team)
1303         {
1304                 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1305                 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1306                 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1307                 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1308                 default: basename = WP_FlagBaseNeutral; break;
1309         }
1310
1311         entity wp = WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG);
1312         wp.colormod = ((self.team) ? Team_ColorRGB(self.team) : '1 1 1');
1313         WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, ((self.team) ? colormapPaletteColor(self.team - 1, false) : '1 1 1'));
1314
1315         // captureshield setup
1316         ctf_CaptureShield_Spawn(self);
1317 }
1318
1319 void set_flag_string(entity flag, .string field, string value, string teamname)
1320 {
1321         if(flag.(field) == "")
1322                 flag.(field) = strzone(sprintf(value,teamname));
1323 }
1324
1325 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1326 {SELFPARAM();
1327         // declarations
1328         setself(flag); // for later usage with droptofloor()
1329
1330         // main setup
1331         flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1332         ctf_worldflaglist = flag;
1333
1334         setattachment(flag, world, "");
1335
1336         flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1337         flag.team = teamnumber;
1338         flag.classname = "item_flag_team";
1339         flag.target = "###item###"; // wut?
1340         flag.flags = FL_ITEM | FL_NOTARGET;
1341         flag.solid = SOLID_TRIGGER;
1342         flag.takedamage = DAMAGE_NO;
1343         flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1344         flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1345         flag.health = flag.max_flag_health;
1346         flag.event_damage = ctf_FlagDamage;
1347         flag.pushable = true;
1348         flag.teleportable = TELEPORT_NORMAL;
1349         flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
1350         flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1351         flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1352         flag.velocity = '0 0 0';
1353         flag.mangle = flag.angles;
1354         flag.reset = ctf_Reset;
1355         flag.touch = ctf_FlagTouch;
1356         flag.think = ctf_FlagThink;
1357         flag.nextthink = time + FLAG_THINKRATE;
1358         flag.ctf_status = FLAG_BASE;
1359
1360         string teamname = Static_Team_ColorName_Lower(teamnumber);
1361         // appearence
1362         if(!flag.scale)                         { flag.scale = FLAG_SCALE; }
1363         if(flag.skin == 0)                      { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1364         if(flag.model == "")            { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1365         set_flag_string(flag, toucheffect,      "%sflag_touch", teamname);
1366         set_flag_string(flag, passeffect,       "%s_pass",              teamname);
1367         set_flag_string(flag, capeffect,        "%s_cap",               teamname);
1368
1369         // sounds
1370         flag.snd_flag_taken = SND(CTF_TAKEN(teamnumber));
1371         flag.snd_flag_returned = SND(CTF_RETURNED(teamnumber));
1372         flag.snd_flag_capture = SND(CTF_CAPTURE(teamnumber));
1373         flag.snd_flag_dropped = SND(CTF_DROPPED(teamnumber));
1374         if (flag.snd_flag_respawn == "") flag.snd_flag_respawn = SND(CTF_RESPAWN); // if there is ever a team-based sound for this, update the code to match.
1375         precache_sound(flag.snd_flag_respawn);
1376         if (flag.snd_flag_touch == "") flag.snd_flag_touch = SND(CTF_TOUCH); // again has no team-based sound
1377         precache_sound(flag.snd_flag_touch);
1378         if (flag.snd_flag_pass == "") flag.snd_flag_pass = SND(CTF_PASS); // same story here
1379         precache_sound(flag.snd_flag_pass);
1380
1381         // precache
1382         precache_model(flag.model);
1383
1384         // appearence
1385         _setmodel(flag, flag.model); // precision set below
1386         setsize(flag, FLAG_MIN, FLAG_MAX);
1387         setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1388
1389         if(autocvar_g_ctf_flag_glowtrails)
1390         {
1391                 switch(teamnumber)
1392                 {
1393                         case NUM_TEAM_1: flag.glow_color = 251; break;
1394                         case NUM_TEAM_2: flag.glow_color = 210; break;
1395                         case NUM_TEAM_3: flag.glow_color = 110; break;
1396                         case NUM_TEAM_4: flag.glow_color = 145; break;
1397                         default:                 flag.glow_color = 254; break;
1398                 }
1399                 flag.glow_size = 25;
1400                 flag.glow_trail = 1;
1401         }
1402
1403         flag.effects |= EF_LOWPRECISION;
1404         if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1405         if(autocvar_g_ctf_dynamiclights)
1406         {
1407                 switch(teamnumber)
1408                 {
1409                         case NUM_TEAM_1: flag.effects |= EF_RED; break;
1410                         case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1411                         case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1412                         case NUM_TEAM_4: flag.effects |= EF_RED; break;
1413                         default:                 flag.effects |= EF_DIMLIGHT; break;
1414                 }
1415         }
1416
1417         // flag placement
1418         if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1419         {
1420                 flag.dropped_origin = flag.origin;
1421                 flag.noalign = true;
1422                 flag.movetype = MOVETYPE_NONE;
1423         }
1424         else // drop to floor, automatically find a platform and set that as spawn origin
1425         {
1426                 flag.noalign = false;
1427                 setself(flag);
1428                 droptofloor();
1429                 flag.movetype = MOVETYPE_TOSS;
1430         }
1431
1432         InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1433 }
1434
1435
1436 // ================
1437 // Bot player logic
1438 // ================
1439
1440 // NOTE: LEGACY CODE, needs to be re-written!
1441
1442 void havocbot_calculate_middlepoint()
1443 {
1444         entity f;
1445         vector s = '0 0 0';
1446         vector fo = '0 0 0';
1447         float n = 0;
1448
1449         f = ctf_worldflaglist;
1450         while (f)
1451         {
1452                 fo = f.origin;
1453                 s = s + fo;
1454                 f = f.ctf_worldflagnext;
1455         }
1456         if(!n)
1457                 return;
1458         havocbot_ctf_middlepoint = s * (1.0 / n);
1459         havocbot_ctf_middlepoint_radius  = vlen(fo - havocbot_ctf_middlepoint);
1460 }
1461
1462
1463 entity havocbot_ctf_find_flag(entity bot)
1464 {
1465         entity f;
1466         f = ctf_worldflaglist;
1467         while (f)
1468         {
1469                 if (CTF_SAMETEAM(bot, f))
1470                         return f;
1471                 f = f.ctf_worldflagnext;
1472         }
1473         return world;
1474 }
1475
1476 entity havocbot_ctf_find_enemy_flag(entity bot)
1477 {
1478         entity f;
1479         f = ctf_worldflaglist;
1480         while (f)
1481         {
1482                 if(ctf_oneflag)
1483                 {
1484                         if(CTF_DIFFTEAM(bot, f))
1485                         {
1486                                 if(f.team)
1487                                 {
1488                                         if(bot.flagcarried)
1489                                                 return f;
1490                                 }
1491                                 else if(!bot.flagcarried)
1492                                         return f;
1493                         }
1494                 }
1495                 else if (CTF_DIFFTEAM(bot, f))
1496                         return f;
1497                 f = f.ctf_worldflagnext;
1498         }
1499         return world;
1500 }
1501
1502 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1503 {
1504         if (!teamplay)
1505                 return 0;
1506
1507         int c = 0;
1508         entity head;
1509
1510         FOR_EACH_PLAYER(head)
1511         {
1512                 if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot)
1513                         continue;
1514
1515                 if(vlen(head.origin - org) < tc_radius)
1516                         ++c;
1517         }
1518
1519         return c;
1520 }
1521
1522 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1523 {SELFPARAM();
1524         entity head;
1525         head = ctf_worldflaglist;
1526         while (head)
1527         {
1528                 if (CTF_SAMETEAM(self, head))
1529                         break;
1530                 head = head.ctf_worldflagnext;
1531         }
1532         if (head)
1533                 navigation_routerating(head, ratingscale, 10000);
1534 }
1535
1536 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1537 {SELFPARAM();
1538         entity head;
1539         head = ctf_worldflaglist;
1540         while (head)
1541         {
1542                 if (CTF_SAMETEAM(self, head))
1543                         break;
1544                 head = head.ctf_worldflagnext;
1545         }
1546         if (!head)
1547                 return;
1548
1549         navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1550 }
1551
1552 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1553 {SELFPARAM();
1554         entity head;
1555         head = ctf_worldflaglist;
1556         while (head)
1557         {
1558                 if(ctf_oneflag)
1559                 {
1560                         if(CTF_DIFFTEAM(self, head))
1561                         {
1562                                 if(head.team)
1563                                 {
1564                                         if(self.flagcarried)
1565                                                 break;
1566                                 }
1567                                 else if(!self.flagcarried)
1568                                         break;
1569                         }
1570                 }
1571                 else if(CTF_DIFFTEAM(self, head))
1572                         break;
1573                 head = head.ctf_worldflagnext;
1574         }
1575         if (head)
1576                 navigation_routerating(head, ratingscale, 10000);
1577 }
1578
1579 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1580 {SELFPARAM();
1581         if (!bot_waypoints_for_items)
1582         {
1583                 havocbot_goalrating_ctf_enemyflag(ratingscale);
1584                 return;
1585         }
1586
1587         entity head;
1588
1589         head = havocbot_ctf_find_enemy_flag(self);
1590
1591         if (!head)
1592                 return;
1593
1594         navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1595 }
1596
1597 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1598 {SELFPARAM();
1599         entity mf;
1600
1601         mf = havocbot_ctf_find_flag(self);
1602
1603         if(mf.ctf_status == FLAG_BASE)
1604                 return;
1605
1606         if(mf.tag_entity)
1607                 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1608 }
1609
1610 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1611 {
1612         entity head;
1613         head = ctf_worldflaglist;
1614         while (head)
1615         {
1616                 // flag is out in the field
1617                 if(head.ctf_status != FLAG_BASE)
1618                 if(head.tag_entity==world)      // dropped
1619                 {
1620                         if(df_radius)
1621                         {
1622                                 if(vlen(org-head.origin)<df_radius)
1623                                         navigation_routerating(head, ratingscale, 10000);
1624                         }
1625                         else
1626                                 navigation_routerating(head, ratingscale, 10000);
1627                 }
1628
1629                 head = head.ctf_worldflagnext;
1630         }
1631 }
1632
1633 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1634 {SELFPARAM();
1635         entity head;
1636         float t;
1637         head = findchainfloat(bot_pickup, true);
1638         while (head)
1639         {
1640                 // gather health and armor only
1641                 if (head.solid)
1642                 if (head.health || head.armorvalue)
1643                 if (vlen(head.origin - org) < sradius)
1644                 {
1645                         // get the value of the item
1646                         t = head.bot_pickupevalfunc(self, head) * 0.0001;
1647                         if (t > 0)
1648                                 navigation_routerating(head, t * ratingscale, 500);
1649                 }
1650                 head = head.chain;
1651         }
1652 }
1653
1654 void havocbot_ctf_reset_role(entity bot)
1655 {
1656         float cdefense, cmiddle, coffense;
1657         entity mf, ef, head;
1658         float c;
1659
1660         if(bot.deadflag != DEAD_NO)
1661                 return;
1662
1663         if(vlen(havocbot_ctf_middlepoint)==0)
1664                 havocbot_calculate_middlepoint();
1665
1666         // Check ctf flags
1667         if (bot.flagcarried)
1668         {
1669                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1670                 return;
1671         }
1672
1673         mf = havocbot_ctf_find_flag(bot);
1674         ef = havocbot_ctf_find_enemy_flag(bot);
1675
1676         // Retrieve stolen flag
1677         if(mf.ctf_status!=FLAG_BASE)
1678         {
1679                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1680                 return;
1681         }
1682
1683         // If enemy flag is taken go to the middle to intercept pursuers
1684         if(ef.ctf_status!=FLAG_BASE)
1685         {
1686                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1687                 return;
1688         }
1689
1690         // if there is only me on the team switch to offense
1691         c = 0;
1692         FOR_EACH_PLAYER(head)
1693         if(SAME_TEAM(head, bot))
1694                 ++c;
1695
1696         if(c==1)
1697         {
1698                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1699                 return;
1700         }
1701
1702         // Evaluate best position to take
1703         // Count mates on middle position
1704         cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1705
1706         // Count mates on defense position
1707         cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1708
1709         // Count mates on offense position
1710         coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1711
1712         if(cdefense<=coffense)
1713                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1714         else if(coffense<=cmiddle)
1715                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1716         else
1717                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1718 }
1719
1720 void havocbot_role_ctf_carrier()
1721 {SELFPARAM();
1722         if(self.deadflag != DEAD_NO)
1723         {
1724                 havocbot_ctf_reset_role(self);
1725                 return;
1726         }
1727
1728         if (self.flagcarried == world)
1729         {
1730                 havocbot_ctf_reset_role(self);
1731                 return;
1732         }
1733
1734         if (self.bot_strategytime < time)
1735         {
1736                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1737
1738                 navigation_goalrating_start();
1739                 if(ctf_oneflag)
1740                         havocbot_goalrating_ctf_enemybase(50000);
1741                 else
1742                         havocbot_goalrating_ctf_ourbase(50000);
1743
1744                 if(self.health<100)
1745                         havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1746
1747                 navigation_goalrating_end();
1748
1749                 if (self.navigation_hasgoals)
1750                         self.havocbot_cantfindflag = time + 10;
1751                 else if (time > self.havocbot_cantfindflag)
1752                 {
1753                         // Can't navigate to my own base, suicide!
1754                         // TODO: drop it and wander around
1755                         Damage(self, self, self, 100000, DEATH_KILL.m_id, self.origin, '0 0 0');
1756                         return;
1757                 }
1758         }
1759 }
1760
1761 void havocbot_role_ctf_escort()
1762 {SELFPARAM();
1763         entity mf, ef;
1764
1765         if(self.deadflag != DEAD_NO)
1766         {
1767                 havocbot_ctf_reset_role(self);
1768                 return;
1769         }
1770
1771         if (self.flagcarried)
1772         {
1773                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1774                 return;
1775         }
1776
1777         // If enemy flag is back on the base switch to previous role
1778         ef = havocbot_ctf_find_enemy_flag(self);
1779         if(ef.ctf_status==FLAG_BASE)
1780         {
1781                 self.havocbot_role = self.havocbot_previous_role;
1782                 self.havocbot_role_timeout = 0;
1783                 return;
1784         }
1785
1786         // If the flag carrier reached the base switch to defense
1787         mf = havocbot_ctf_find_flag(self);
1788         if(mf.ctf_status!=FLAG_BASE)
1789         if(vlen(ef.origin - mf.dropped_origin) < 300)
1790         {
1791                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1792                 return;
1793         }
1794
1795         // Set the role timeout if necessary
1796         if (!self.havocbot_role_timeout)
1797         {
1798                 self.havocbot_role_timeout = time + random() * 30 + 60;
1799         }
1800
1801         // If nothing happened just switch to previous role
1802         if (time > self.havocbot_role_timeout)
1803         {
1804                 self.havocbot_role = self.havocbot_previous_role;
1805                 self.havocbot_role_timeout = 0;
1806                 return;
1807         }
1808
1809         // Chase the flag carrier
1810         if (self.bot_strategytime < time)
1811         {
1812                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1813                 navigation_goalrating_start();
1814                 havocbot_goalrating_ctf_enemyflag(30000);
1815                 havocbot_goalrating_ctf_ourstolenflag(40000);
1816                 havocbot_goalrating_items(10000, self.origin, 10000);
1817                 navigation_goalrating_end();
1818         }
1819 }
1820
1821 void havocbot_role_ctf_offense()
1822 {SELFPARAM();
1823         entity mf, ef;
1824         vector pos;
1825
1826         if(self.deadflag != DEAD_NO)
1827         {
1828                 havocbot_ctf_reset_role(self);
1829                 return;
1830         }
1831
1832         if (self.flagcarried)
1833         {
1834                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1835                 return;
1836         }
1837
1838         // Check flags
1839         mf = havocbot_ctf_find_flag(self);
1840         ef = havocbot_ctf_find_enemy_flag(self);
1841
1842         // Own flag stolen
1843         if(mf.ctf_status!=FLAG_BASE)
1844         {
1845                 if(mf.tag_entity)
1846                         pos = mf.tag_entity.origin;
1847                 else
1848                         pos = mf.origin;
1849
1850                 // Try to get it if closer than the enemy base
1851                 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1852                 {
1853                         havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1854                         return;
1855                 }
1856         }
1857
1858         // Escort flag carrier
1859         if(ef.ctf_status!=FLAG_BASE)
1860         {
1861                 if(ef.tag_entity)
1862                         pos = ef.tag_entity.origin;
1863                 else
1864                         pos = ef.origin;
1865
1866                 if(vlen(pos-mf.dropped_origin)>700)
1867                 {
1868                         havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1869                         return;
1870                 }
1871         }
1872
1873         // About to fail, switch to middlefield
1874         if(self.health<50)
1875         {
1876                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1877                 return;
1878         }
1879
1880         // Set the role timeout if necessary
1881         if (!self.havocbot_role_timeout)
1882                 self.havocbot_role_timeout = time + 120;
1883
1884         if (time > self.havocbot_role_timeout)
1885         {
1886                 havocbot_ctf_reset_role(self);
1887                 return;
1888         }
1889
1890         if (self.bot_strategytime < time)
1891         {
1892                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1893                 navigation_goalrating_start();
1894                 havocbot_goalrating_ctf_ourstolenflag(50000);
1895                 havocbot_goalrating_ctf_enemybase(20000);
1896                 havocbot_goalrating_items(5000, self.origin, 1000);
1897                 havocbot_goalrating_items(1000, self.origin, 10000);
1898                 navigation_goalrating_end();
1899         }
1900 }
1901
1902 // Retriever (temporary role):
1903 void havocbot_role_ctf_retriever()
1904 {SELFPARAM();
1905         entity mf;
1906
1907         if(self.deadflag != DEAD_NO)
1908         {
1909                 havocbot_ctf_reset_role(self);
1910                 return;
1911         }
1912
1913         if (self.flagcarried)
1914         {
1915                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1916                 return;
1917         }
1918
1919         // If flag is back on the base switch to previous role
1920         mf = havocbot_ctf_find_flag(self);
1921         if(mf.ctf_status==FLAG_BASE)
1922         {
1923                 havocbot_ctf_reset_role(self);
1924                 return;
1925         }
1926
1927         if (!self.havocbot_role_timeout)
1928                 self.havocbot_role_timeout = time + 20;
1929
1930         if (time > self.havocbot_role_timeout)
1931         {
1932                 havocbot_ctf_reset_role(self);
1933                 return;
1934         }
1935
1936         if (self.bot_strategytime < time)
1937         {
1938                 float rt_radius;
1939                 rt_radius = 10000;
1940
1941                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1942                 navigation_goalrating_start();
1943                 havocbot_goalrating_ctf_ourstolenflag(50000);
1944                 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1945                 havocbot_goalrating_ctf_enemybase(30000);
1946                 havocbot_goalrating_items(500, self.origin, rt_radius);
1947                 navigation_goalrating_end();
1948         }
1949 }
1950
1951 void havocbot_role_ctf_middle()
1952 {SELFPARAM();
1953         entity mf;
1954
1955         if(self.deadflag != DEAD_NO)
1956         {
1957                 havocbot_ctf_reset_role(self);
1958                 return;
1959         }
1960
1961         if (self.flagcarried)
1962         {
1963                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1964                 return;
1965         }
1966
1967         mf = havocbot_ctf_find_flag(self);
1968         if(mf.ctf_status!=FLAG_BASE)
1969         {
1970                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1971                 return;
1972         }
1973
1974         if (!self.havocbot_role_timeout)
1975                 self.havocbot_role_timeout = time + 10;
1976
1977         if (time > self.havocbot_role_timeout)
1978         {
1979                 havocbot_ctf_reset_role(self);
1980                 return;
1981         }
1982
1983         if (self.bot_strategytime < time)
1984         {
1985                 vector org;
1986
1987                 org = havocbot_ctf_middlepoint;
1988                 org.z = self.origin.z;
1989
1990                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1991                 navigation_goalrating_start();
1992                 havocbot_goalrating_ctf_ourstolenflag(50000);
1993                 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1994                 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1995                 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1996                 havocbot_goalrating_items(2500, self.origin, 10000);
1997                 havocbot_goalrating_ctf_enemybase(2500);
1998                 navigation_goalrating_end();
1999         }
2000 }
2001
2002 void havocbot_role_ctf_defense()
2003 {SELFPARAM();
2004         entity mf;
2005
2006         if(self.deadflag != DEAD_NO)
2007         {
2008                 havocbot_ctf_reset_role(self);
2009                 return;
2010         }
2011
2012         if (self.flagcarried)
2013         {
2014                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
2015                 return;
2016         }
2017
2018         // If own flag was captured
2019         mf = havocbot_ctf_find_flag(self);
2020         if(mf.ctf_status!=FLAG_BASE)
2021         {
2022                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
2023                 return;
2024         }
2025
2026         if (!self.havocbot_role_timeout)
2027                 self.havocbot_role_timeout = time + 30;
2028
2029         if (time > self.havocbot_role_timeout)
2030         {
2031                 havocbot_ctf_reset_role(self);
2032                 return;
2033         }
2034         if (self.bot_strategytime < time)
2035         {
2036                 float mp_radius;
2037                 vector org;
2038
2039                 org = mf.dropped_origin;
2040                 mp_radius = havocbot_ctf_middlepoint_radius;
2041
2042                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
2043                 navigation_goalrating_start();
2044
2045                 // if enemies are closer to our base, go there
2046                 entity head, closestplayer = world;
2047                 float distance, bestdistance = 10000;
2048                 FOR_EACH_PLAYER(head)
2049                 {
2050                         if(head.deadflag!=DEAD_NO)
2051                                 continue;
2052
2053                         distance = vlen(org - head.origin);
2054                         if(distance<bestdistance)
2055                         {
2056                                 closestplayer = head;
2057                                 bestdistance = distance;
2058                         }
2059                 }
2060
2061                 if(closestplayer)
2062                 if(DIFF_TEAM(closestplayer, self))
2063                 if(vlen(org - self.origin)>1000)
2064                 if(checkpvs(self.origin,closestplayer)||random()<0.5)
2065                         havocbot_goalrating_ctf_ourbase(30000);
2066
2067                 havocbot_goalrating_ctf_ourstolenflag(20000);
2068                 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
2069                 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
2070                 havocbot_goalrating_items(10000, org, mp_radius);
2071                 havocbot_goalrating_items(5000, self.origin, 10000);
2072                 navigation_goalrating_end();
2073         }
2074 }
2075
2076 void havocbot_role_ctf_setrole(entity bot, int role)
2077 {
2078         LOG_TRACE(strcat(bot.netname," switched to "));
2079         switch(role)
2080         {
2081                 case HAVOCBOT_CTF_ROLE_CARRIER:
2082                         LOG_TRACE("carrier");
2083                         bot.havocbot_role = havocbot_role_ctf_carrier;
2084                         bot.havocbot_role_timeout = 0;
2085                         bot.havocbot_cantfindflag = time + 10;
2086                         bot.bot_strategytime = 0;
2087                         break;
2088                 case HAVOCBOT_CTF_ROLE_DEFENSE:
2089                         LOG_TRACE("defense");
2090                         bot.havocbot_role = havocbot_role_ctf_defense;
2091                         bot.havocbot_role_timeout = 0;
2092                         break;
2093                 case HAVOCBOT_CTF_ROLE_MIDDLE:
2094                         LOG_TRACE("middle");
2095                         bot.havocbot_role = havocbot_role_ctf_middle;
2096                         bot.havocbot_role_timeout = 0;
2097                         break;
2098                 case HAVOCBOT_CTF_ROLE_OFFENSE:
2099                         LOG_TRACE("offense");
2100                         bot.havocbot_role = havocbot_role_ctf_offense;
2101                         bot.havocbot_role_timeout = 0;
2102                         break;
2103                 case HAVOCBOT_CTF_ROLE_RETRIEVER:
2104                         LOG_TRACE("retriever");
2105                         bot.havocbot_previous_role = bot.havocbot_role;
2106                         bot.havocbot_role = havocbot_role_ctf_retriever;
2107                         bot.havocbot_role_timeout = time + 10;
2108                         bot.bot_strategytime = 0;
2109                         break;
2110                 case HAVOCBOT_CTF_ROLE_ESCORT:
2111                         LOG_TRACE("escort");
2112                         bot.havocbot_previous_role = bot.havocbot_role;
2113                         bot.havocbot_role = havocbot_role_ctf_escort;
2114                         bot.havocbot_role_timeout = time + 30;
2115                         bot.bot_strategytime = 0;
2116                         break;
2117         }
2118         LOG_TRACE("\n");
2119 }
2120
2121
2122 // ==============
2123 // Hook Functions
2124 // ==============
2125
2126 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2127 {SELFPARAM();
2128         entity flag;
2129         int t = 0, t2 = 0, t3 = 0;
2130
2131         // initially clear items so they can be set as necessary later.
2132         self.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING          | CTF_RED_FLAG_TAKEN            | CTF_RED_FLAG_LOST
2133                                                    | CTF_BLUE_FLAG_CARRYING             | CTF_BLUE_FLAG_TAKEN           | CTF_BLUE_FLAG_LOST
2134                                                    | CTF_YELLOW_FLAG_CARRYING   | CTF_YELLOW_FLAG_TAKEN         | CTF_YELLOW_FLAG_LOST
2135                                                    | CTF_PINK_FLAG_CARRYING     | CTF_PINK_FLAG_TAKEN           | CTF_PINK_FLAG_LOST
2136                                                    | CTF_NEUTRAL_FLAG_CARRYING  | CTF_NEUTRAL_FLAG_TAKEN        | CTF_NEUTRAL_FLAG_LOST
2137                                                    | CTF_FLAG_NEUTRAL | CTF_SHIELDED);
2138
2139         // scan through all the flags and notify the client about them
2140         for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2141         {
2142                 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING;                t2 = CTF_RED_FLAG_TAKEN;                t3 = CTF_RED_FLAG_LOST; }
2143                 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING;               t2 = CTF_BLUE_FLAG_TAKEN;               t3 = CTF_BLUE_FLAG_LOST; }
2144                 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING;     t2 = CTF_YELLOW_FLAG_TAKEN;             t3 = CTF_YELLOW_FLAG_LOST; }
2145                 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING;               t2 = CTF_PINK_FLAG_TAKEN;               t3 = CTF_PINK_FLAG_LOST; }
2146                 if(flag.team == 0)                      { t = CTF_NEUTRAL_FLAG_CARRYING;        t2 = CTF_NEUTRAL_FLAG_TAKEN;    t3 = CTF_NEUTRAL_FLAG_LOST; self.ctf_flagstatus |= CTF_FLAG_NEUTRAL; }
2147
2148                 switch(flag.ctf_status)
2149                 {
2150                         case FLAG_PASSING:
2151                         case FLAG_CARRY:
2152                         {
2153                                 if((flag.owner == self) || (flag.pass_sender == self))
2154                                         self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag
2155                                 else
2156                                         self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2157                                 break;
2158                         }
2159                         case FLAG_DROPPED:
2160                         {
2161                                 self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2162                                 break;
2163                         }
2164                 }
2165         }
2166
2167         // item for stopping players from capturing the flag too often
2168         if(self.ctf_captureshielded)
2169                 self.ctf_flagstatus |= CTF_SHIELDED;
2170
2171         // update the health of the flag carrier waypointsprite
2172         if(self.wps_flagcarrier)
2173                 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2174
2175         return false;
2176 }
2177
2178 MUTATOR_HOOKFUNCTION(ctf, PlayerDamage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2179 {
2180         if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2181         {
2182                 if(frag_target == frag_attacker) // damage done to yourself
2183                 {
2184                         frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2185                         frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2186                 }
2187                 else // damage done to everyone else
2188                 {
2189                         frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2190                         frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2191                 }
2192         }
2193         else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2194         {
2195                 if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id)))
2196                 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2197                 {
2198                         frag_target.wps_helpme_time = time;
2199                         WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2200                 }
2201                 // todo: add notification for when flag carrier needs help?
2202         }
2203         return false;
2204 }
2205
2206 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2207 {
2208         if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2209         {
2210                 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
2211                 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2212         }
2213
2214         if(frag_target.flagcarried)
2215         {
2216                 entity tmp_entity = frag_target.flagcarried;
2217                 ctf_Handle_Throw(frag_target, world, DROP_NORMAL);
2218                 tmp_entity.ctf_dropper = world;
2219         }
2220
2221         return false;
2222 }
2223
2224 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2225 {
2226         frag_score = 0;
2227         return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2228 }
2229
2230 void ctf_RemovePlayer(entity player)
2231 {
2232         if(player.flagcarried)
2233                 { ctf_Handle_Throw(player, world, DROP_NORMAL); }
2234
2235         for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2236         {
2237                 if(flag.pass_sender == player) { flag.pass_sender = world; }
2238                 if(flag.pass_target == player) { flag.pass_target = world; }
2239                 if(flag.ctf_dropper == player) { flag.ctf_dropper = world; }
2240         }
2241 }
2242
2243 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2244 {SELFPARAM();
2245         ctf_RemovePlayer(self);
2246         return false;
2247 }
2248
2249 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2250 {SELFPARAM();
2251         ctf_RemovePlayer(self);
2252         return false;
2253 }
2254
2255 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2256 {SELFPARAM();
2257         if(self.flagcarried)
2258         if(!autocvar_g_ctf_portalteleport)
2259                 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2260
2261         return false;
2262 }
2263
2264 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2265 {SELFPARAM();
2266         if(MUTATOR_RETURNVALUE || gameover) { return false; }
2267
2268         entity player = self;
2269
2270         if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2271         {
2272                 // pass the flag to a team mate
2273                 if(autocvar_g_ctf_pass)
2274                 {
2275                         entity head, closest_target = world;
2276                         head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2277
2278                         while(head) // find the closest acceptable target to pass to
2279                         {
2280                                 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
2281                                 if(head != player && SAME_TEAM(head, player))
2282                                 if(!head.speedrunning && !head.vehicle)
2283                                 {
2284                                         // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2285                                         vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2286                                         vector passer_center = CENTER_OR_VIEWOFS(player);
2287
2288                                         if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2289                                         {
2290                                                 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2291                                                 {
2292                                                         if(IS_BOT_CLIENT(head))
2293                                                         {
2294                                                                 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2295                                                                 ctf_Handle_Throw(head, player, DROP_PASS);
2296                                                         }
2297                                                         else
2298                                                         {
2299                                                                 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2300                                                                 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2301                                                         }
2302                                                         player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2303                                                         return true;
2304                                                 }
2305                                                 else if(player.flagcarried)
2306                                                 {
2307                                                         if(closest_target)
2308                                                         {
2309                                                                 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2310                                                                 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
2311                                                                         { closest_target = head; }
2312                                                         }
2313                                                         else { closest_target = head; }
2314                                                 }
2315                                         }
2316                                 }
2317                                 head = head.chain;
2318                         }
2319
2320                         if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2321                 }
2322
2323                 // throw the flag in front of you
2324                 if(autocvar_g_ctf_throw && player.flagcarried)
2325                 {
2326                         if(player.throw_count == -1)
2327                         {
2328                                 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2329                                 {
2330                                         player.throw_prevtime = time;
2331                                         player.throw_count = 1;
2332                                         ctf_Handle_Throw(player, world, DROP_THROW);
2333                                         return true;
2334                                 }
2335                                 else
2336                                 {
2337                                         Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2338                                         return false;
2339                                 }
2340                         }
2341                         else
2342                         {
2343                                 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2344                                 else { player.throw_count += 1; }
2345                                 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2346
2347                                 player.throw_prevtime = time;
2348                                 ctf_Handle_Throw(player, world, DROP_THROW);
2349                                 return true;
2350                         }
2351                 }
2352         }
2353
2354         return false;
2355 }
2356
2357 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2358 {SELFPARAM();
2359         if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2360         {
2361                 self.wps_helpme_time = time;
2362                 WaypointSprite_HelpMePing(self.wps_flagcarrier);
2363         }
2364         else // create a normal help me waypointsprite
2365         {
2366                 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, wps_helpme, false, RADARICON_HELPME);
2367                 WaypointSprite_Ping(self.wps_helpme);
2368         }
2369
2370         return true;
2371 }
2372
2373 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2374 {
2375         if(vh_player.flagcarried)
2376         {
2377                 vh_player.flagcarried.nodrawtoclient = vh_player; // hide the flag from the driver
2378
2379                 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2380                 {
2381                         ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
2382                 }
2383                 else
2384                 {
2385                         setattachment(vh_player.flagcarried, vh_vehicle, "");
2386                         setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
2387                         vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2388                         //vh_player.flagcarried.angles = '0 0 0';
2389                 }
2390                 return true;
2391         }
2392
2393         return false;
2394 }
2395
2396 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2397 {
2398         if(vh_player.flagcarried)
2399         {
2400                 setattachment(vh_player.flagcarried, vh_player, "");
2401                 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
2402                 vh_player.flagcarried.scale = FLAG_SCALE;
2403                 vh_player.flagcarried.angles = '0 0 0';
2404                 vh_player.flagcarried.nodrawtoclient = world;
2405                 return true;
2406         }
2407
2408         return false;
2409 }
2410
2411 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2412 {SELFPARAM();
2413         if(self.flagcarried)
2414         {
2415                 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((self.flagcarried.team) ? APP_TEAM_ENT_4(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_) : INFO_CTF_FLAGRETURN_ABORTRUN_NEUTRAL));
2416                 ctf_RespawnFlag(self.flagcarried);
2417                 return true;
2418         }
2419
2420         return false;
2421 }
2422
2423 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2424 {
2425         entity flag; // temporary entity for the search method
2426
2427         for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2428         {
2429                 switch(flag.ctf_status)
2430                 {
2431                         case FLAG_DROPPED:
2432                         case FLAG_PASSING:
2433                         {
2434                                 // lock the flag, game is over
2435                                 flag.movetype = MOVETYPE_NONE;
2436                                 flag.takedamage = DAMAGE_NO;
2437                                 flag.solid = SOLID_NOT;
2438                                 flag.nextthink = false; // stop thinking
2439
2440                                 //dprint("stopping the ", flag.netname, " from moving.\n");
2441                                 break;
2442                         }
2443
2444                         default:
2445                         case FLAG_BASE:
2446                         case FLAG_CARRY:
2447                         {
2448                                 // do nothing for these flags
2449                                 break;
2450                         }
2451                 }
2452         }
2453
2454         return false;
2455 }
2456
2457 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2458 {SELFPARAM();
2459         havocbot_ctf_reset_role(self);
2460         return true;
2461 }
2462
2463 MUTATOR_HOOKFUNCTION(ctf, GetTeamCount)
2464 {
2465         //ret_float = ctf_teams;
2466         ret_string = "ctf_team";
2467         return true;
2468 }
2469
2470 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2471 {SELFPARAM();
2472         self.ctf_flagstatus = other.ctf_flagstatus;
2473         return false;
2474 }
2475
2476 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2477 {
2478         for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2479         {
2480                 if (MapInfo_Get_ByID(i))
2481                 {
2482                         float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2483
2484                         if(!r)
2485                                 continue;
2486
2487                         // TODO: uid2name
2488                         string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2489                         ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2490                 }
2491         }
2492
2493         return false;
2494 }
2495
2496 bool superspec_Spectate(entity _player); // TODO
2497 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2498 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2499 {
2500         if(IS_PLAYER(self) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2501
2502         if(cmd_name == "followfc")
2503         {
2504                 if(!g_ctf)
2505                         return true;
2506
2507                 entity _player;
2508                 int _team = 0;
2509                 bool found = false;
2510
2511                 if(cmd_argc == 2)
2512                 {
2513                         switch(argv(1))
2514                         {
2515                                 case "red": _team = NUM_TEAM_1; break;
2516                                 case "blue": _team = NUM_TEAM_2; break;
2517                                 case "yellow": if(ctf_teams >= 3) _team = NUM_TEAM_3; break;
2518                                 case "pink": if(ctf_teams >= 4) _team = NUM_TEAM_4; break;
2519                         }
2520                 }
2521
2522                 FOR_EACH_PLAYER(_player)
2523                 {
2524                         if(_player.flagcarried && (_player.team == _team || _team == 0))
2525                         {
2526                                 found = true;
2527                                 if(_team == 0 && IS_SPEC(self) && self.enemy == _player)
2528                                         continue; // already spectating a fc, try to find the other fc
2529                                 return superspec_Spectate(_player);
2530                         }
2531                 }
2532
2533                 if(!found)
2534                         superspec_msg("", "", self, "No active flag carrier\n", 1);
2535                 return true;
2536         }
2537
2538         return false;
2539 }
2540
2541 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2542 {
2543         if(frag_target.flagcarried)
2544                 ctf_Handle_Throw(frag_target, world, DROP_THROW);
2545
2546         return false;
2547 }
2548
2549
2550 // ==========
2551 // Spawnfuncs
2552 // ==========
2553
2554 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2555 CTF flag for team one (Red).
2556 Keys:
2557 "angle" Angle the flag will point (minus 90 degrees)...
2558 "model" model to use, note this needs red and blue as skins 0 and 1...
2559 "noise" sound played when flag is picked up...
2560 "noise1" sound played when flag is returned by a teammate...
2561 "noise2" sound played when flag is captured...
2562 "noise3" sound played when flag is lost in the field and respawns itself...
2563 "noise4" sound played when flag is dropped by a player...
2564 "noise5" sound played when flag touches the ground... */
2565 spawnfunc(item_flag_team1)
2566 {
2567         if(!g_ctf) { remove(self); return; }
2568
2569         ctf_FlagSetup(NUM_TEAM_1, self);
2570 }
2571
2572 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2573 CTF flag for team two (Blue).
2574 Keys:
2575 "angle" Angle the flag will point (minus 90 degrees)...
2576 "model" model to use, note this needs red and blue as skins 0 and 1...
2577 "noise" sound played when flag is picked up...
2578 "noise1" sound played when flag is returned by a teammate...
2579 "noise2" sound played when flag is captured...
2580 "noise3" sound played when flag is lost in the field and respawns itself...
2581 "noise4" sound played when flag is dropped by a player...
2582 "noise5" sound played when flag touches the ground... */
2583 spawnfunc(item_flag_team2)
2584 {
2585         if(!g_ctf) { remove(self); return; }
2586
2587         ctf_FlagSetup(NUM_TEAM_2, self);
2588 }
2589
2590 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2591 CTF flag for team three (Yellow).
2592 Keys:
2593 "angle" Angle the flag will point (minus 90 degrees)...
2594 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2595 "noise" sound played when flag is picked up...
2596 "noise1" sound played when flag is returned by a teammate...
2597 "noise2" sound played when flag is captured...
2598 "noise3" sound played when flag is lost in the field and respawns itself...
2599 "noise4" sound played when flag is dropped by a player...
2600 "noise5" sound played when flag touches the ground... */
2601 spawnfunc(item_flag_team3)
2602 {
2603         if(!g_ctf) { remove(self); return; }
2604
2605         ctf_FlagSetup(NUM_TEAM_3, self);
2606 }
2607
2608 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2609 CTF flag for team four (Pink).
2610 Keys:
2611 "angle" Angle the flag will point (minus 90 degrees)...
2612 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2613 "noise" sound played when flag is picked up...
2614 "noise1" sound played when flag is returned by a teammate...
2615 "noise2" sound played when flag is captured...
2616 "noise3" sound played when flag is lost in the field and respawns itself...
2617 "noise4" sound played when flag is dropped by a player...
2618 "noise5" sound played when flag touches the ground... */
2619 spawnfunc(item_flag_team4)
2620 {
2621         if(!g_ctf) { remove(self); return; }
2622
2623         ctf_FlagSetup(NUM_TEAM_4, self);
2624 }
2625
2626 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2627 CTF flag (Neutral).
2628 Keys:
2629 "angle" Angle the flag will point (minus 90 degrees)...
2630 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2631 "noise" sound played when flag is picked up...
2632 "noise1" sound played when flag is returned by a teammate...
2633 "noise2" sound played when flag is captured...
2634 "noise3" sound played when flag is lost in the field and respawns itself...
2635 "noise4" sound played when flag is dropped by a player...
2636 "noise5" sound played when flag touches the ground... */
2637 spawnfunc(item_flag_neutral)
2638 {
2639         if(!g_ctf) { remove(self); return; }
2640         if(!cvar("g_ctf_oneflag")) { remove(self); return; }
2641
2642         ctf_FlagSetup(0, self);
2643 }
2644
2645 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2646 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2647 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.
2648 Keys:
2649 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2650 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2651 spawnfunc(ctf_team)
2652 {
2653         if(!g_ctf) { remove(self); return; }
2654
2655         self.classname = "ctf_team";
2656         self.team = self.cnt + 1;
2657 }
2658
2659 // compatibility for quake maps
2660 spawnfunc(team_CTF_redflag)    { spawnfunc_item_flag_team1(this);    }
2661 spawnfunc(team_CTF_blueflag)   { spawnfunc_item_flag_team2(this);    }
2662 spawnfunc(info_player_team1);
2663 spawnfunc(team_CTF_redplayer)  { spawnfunc_info_player_team1(this);  }
2664 spawnfunc(team_CTF_redspawn)   { spawnfunc_info_player_team1(this);  }
2665 spawnfunc(info_player_team2);
2666 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this);  }
2667 spawnfunc(team_CTF_bluespawn)  { spawnfunc_info_player_team2(this);  }
2668
2669 void team_CTF_neutralflag()                      { SELFPARAM(); spawnfunc_item_flag_neutral(self);  }
2670 void team_neutralobelisk()                       { SELFPARAM(); spawnfunc_item_flag_neutral(self);  }
2671
2672
2673 // ==============
2674 // Initialization
2675 // ==============
2676
2677 // scoreboard setup
2678 void ctf_ScoreRules(int teams)
2679 {
2680         CheckAllowedTeams(world);
2681         ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2682         ScoreInfo_SetLabel_TeamScore  (ST_CTF_CAPS,     "caps",      SFL_SORT_PRIO_PRIMARY);
2683         ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS,     "caps",      SFL_SORT_PRIO_SECONDARY);
2684         ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME,  "captime",   SFL_LOWER_IS_BETTER | SFL_TIME);
2685         ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS,  "pickups",   0);
2686         ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS,  "fckills",   0);
2687         ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS,  "returns",   0);
2688         ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS,    "drops",     SFL_LOWER_IS_BETTER);
2689         ScoreRules_basics_end();
2690 }
2691
2692 // code from here on is just to support maps that don't have flag and team entities
2693 void ctf_SpawnTeam (string teamname, int teamcolor)
2694 {
2695         entity this = new(ctf_team);
2696         this.netname = teamname;
2697         this.cnt = teamcolor;
2698         this.spawnfunc_checked = true;
2699         WITH(entity, self, this, spawnfunc_ctf_team(this));
2700 }
2701
2702 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2703 {
2704         ctf_teams = 2;
2705
2706         entity tmp_entity;
2707         for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2708         {
2709                 if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2710                 if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2711                 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2712         }
2713
2714         ctf_teams = bound(2, ctf_teams, 4);
2715
2716         // if no teams are found, spawn defaults
2717         if(find(world, classname, "ctf_team") == world)
2718         {
2719                 LOG_INFO("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2720                 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2721                 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2722                 if(ctf_teams >= 3)
2723                         ctf_SpawnTeam("Yellow", NUM_TEAM_3 - 1);
2724                 if(ctf_teams >= 4)
2725                         ctf_SpawnTeam("Pink", NUM_TEAM_4 - 1);
2726         }
2727
2728         ctf_ScoreRules(ctf_teams);
2729 }
2730
2731 void ctf_Initialize()
2732 {
2733         ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2734
2735         ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2736         ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2737         ctf_captureshield_force = autocvar_g_ctf_shield_force;
2738
2739         addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus);
2740
2741         InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2742 }
2743
2744 REGISTER_MUTATOR(ctf, g_ctf)
2745 {
2746         ActivateTeamplay();
2747         SetLimits(autocvar_capturelimit_override, -1, autocvar_captureleadlimit_override, -1);
2748         have_team_spawns = -1; // request team spawns
2749
2750         MUTATOR_ONADD
2751         {
2752                 if(time > 1) // game loads at time 1
2753                         error("This is a game type and it cannot be added at runtime.");
2754                 ctf_Initialize();
2755         }
2756
2757         MUTATOR_ONROLLBACK_OR_REMOVE
2758         {
2759                 // we actually cannot roll back ctf_Initialize here
2760                 // BUT: we don't need to! If this gets called, adding always
2761                 // succeeds.
2762         }
2763
2764         MUTATOR_ONREMOVE
2765         {
2766                 LOG_INFO("This is a game type and it cannot be removed at runtime.");
2767                 return -1;
2768         }
2769
2770         return 0;
2771 }
2772 #endif