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