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