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