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