]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_ctf.qc
Merge remote-tracking branch 'origin/master' into samual/notification_rewrite
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_ctf.qc
1 // ================================================================
2 //  Official capture the flag game mode coding, reworked by Samual
3 //  Last updated: September, 2012
4 // ================================================================
5
6 void ctf_FakeTimeLimit(entity e, float t)
7 {
8         msg_entity = e;
9         WriteByte(MSG_ONE, 3); // svc_updatestat
10         WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
11         if(t < 0)
12                 WriteCoord(MSG_ONE, autocvar_timelimit);
13         else
14                 WriteCoord(MSG_ONE, (t + 1) / 60);
15 }
16
17 void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
18 {
19         if(autocvar_sv_eventlog)
20                 GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
21 }
22
23 void ctf_CaptureRecord(entity flag, entity player)
24 {
25         entity tmp_entity;
26         float notification, success;
27         float cap_record = ctf_captimerecord;
28         float cap_time = (time - flag.ctf_pickuptime);
29         float f1, f2 = NO_FL_ARG;
30         string s1, s2 = NO_STR_ARG;
31         string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
32         
33         // figure shit out
34         if(!ctf_captimerecord) 
35                 { notification = APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_TIME_); s1 = player.netname; f1 = (cap_time * 100); success = TRUE; }
36         else if(cap_time < cap_record) 
37                 { notification = APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_BROKEN_); s1 = player.netname; s2 = refername; f1 = (cap_time * 100); f2 = (cap_record * 100); success = TRUE; }
38         else
39                 { notification = APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_UNBROKEN_); s1 = player.netname; s2 = refername; f1 = (cap_time * 100); f2 = (cap_record * 100); success = FALSE; }
40
41         // notify about shit
42         FOR_EACH_REALCLIENT(tmp_entity)
43         {
44                 if not(tmp_entity.CAPTURE_VERBOSE) { notification = APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_); s2 = NO_STR_ARG; f1 = f2 = NO_FL_ARG; }
45                 Send_Notification_Legacy_Wrapper(NOTIF_ONE_ONLY, tmp_entity, MSG_INFO, notification, s1, s2, f1, f2, NO_FL_ARG);
46         }
47
48         // notify server log too
49         if not(autocvar_notification_ctf_capture_verbose) { notification = APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_); s2 = NO_STR_ARG; f1 = f2 = NO_FL_ARG; }
50         Local_Notification_Without_VarArgs(MSG_INFO, notification, Get_Notif_Strnum(MSG_INFO, notification), Get_Notif_Flnum(MSG_INFO, notification), s1, s2, NO_STR_ARG, NO_STR_ARG, f1, f2, NO_FL_ARG, NO_FL_ARG);
51
52         // write that shit in the database
53         if(success) 
54         {
55                 ctf_captimerecord = cap_time;
56                 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
57                 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
58                 write_recordmarker(player, (time - cap_time), cap_time); 
59         } 
60 }
61
62 void ctf_FlagcarrierWaypoints(entity player)
63 {
64         WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
65         WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
66         WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
67         WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
68 }
69
70 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
71 {
72         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
73         float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
74         float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
75         //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
76
77         vector targpos;
78         if(current_height) // make sure we can actually do this arcing path
79         {
80                 targpos = (to + ('0 0 1' * current_height));
81                 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
82                 if(trace_fraction < 1)
83                 {
84                         //print("normal arc line failed, trying to find new pos...");
85                         WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
86                         targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
87                         WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
88                         if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
89                         /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
90                 }
91         }
92         else { targpos = to; }
93
94         //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
95
96         vector desired_direction = normalize(targpos - from);
97         if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
98         else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
99 }
100
101 float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
102 {
103         if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
104         {
105                 // directional tracing only
106                 float spreadlimit;
107                 makevectors(passer_angle);
108
109                 // find the closest point on the enemy to the center of the attack
110                 float ang; // angle between shotdir and h
111                 float h; // hypotenuse, which is the distance between attacker to head
112                 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
113                 
114                 h = vlen(head_center - passer_center);
115                 ang = acos(dotproduct(normalize(head_center - passer_center), v_forward));
116                 a = h * cos(ang);
117
118                 vector nearest_on_line = (passer_center + a * v_forward);
119                 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
120
121                 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
122                 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
123
124                 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
125                         { return TRUE; }
126                 else
127                         { return FALSE; }
128         }
129         else { return TRUE; }
130 }
131
132
133 // =======================
134 // CaptureShield Functions 
135 // =======================
136
137 float ctf_CaptureShield_CheckStatus(entity p) 
138 {
139         float s, se;
140         entity e;
141         float players_worseeq, players_total;
142
143         if(ctf_captureshield_max_ratio <= 0)
144                 return FALSE;
145
146         s = PlayerScore_Add(p, SP_SCORE, 0);
147         if(s >= -ctf_captureshield_min_negscore)
148                 return FALSE;
149
150         players_total = players_worseeq = 0;
151         FOR_EACH_PLAYER(e)
152         {
153                 if(IsDifferentTeam(e, p))
154                         continue;
155                 se = PlayerScore_Add(e, SP_SCORE, 0);
156                 if(se <= s)
157                         ++players_worseeq;
158                 ++players_total;
159         }
160
161         // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
162         // use this rule here
163         
164         if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
165                 return FALSE;
166
167         return TRUE;
168 }
169
170 void ctf_CaptureShield_Update(entity player, float wanted_status)
171 {
172         float updated_status = ctf_CaptureShield_CheckStatus(player);
173         if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
174         {
175                 if(updated_status) // TODO csqc notifier for this // Samual: How?
176                         Send_Notification_Legacy_Wrapper(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
177                 else
178                         Send_Notification_Legacy_Wrapper(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_FREE, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
179                         
180                 player.ctf_captureshielded = updated_status;
181         }
182 }
183
184 float ctf_CaptureShield_Customize()
185 {
186         if(!other.ctf_captureshielded) { return FALSE; }
187         if(!IsDifferentTeam(self, other)) { return FALSE; }
188         
189         return TRUE;
190 }
191
192 void ctf_CaptureShield_Touch()
193 {
194         if(!other.ctf_captureshielded) { return; }
195         if(!IsDifferentTeam(self, other)) { return; }
196         
197         vector mymid = (self.absmin + self.absmax) * 0.5;
198         vector othermid = (other.absmin + other.absmax) * 0.5;
199
200         Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
201         Send_Notification_Legacy_Wrapper(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
202 }
203
204 void ctf_CaptureShield_Spawn(entity flag)
205 {
206         entity shield = spawn();
207         
208         shield.enemy = self;
209         shield.team = self.team;
210         shield.touch = ctf_CaptureShield_Touch;
211         shield.customizeentityforclient = ctf_CaptureShield_Customize;
212         shield.classname = "ctf_captureshield";
213         shield.effects = EF_ADDITIVE;
214         shield.movetype = MOVETYPE_NOCLIP;
215         shield.solid = SOLID_TRIGGER;
216         shield.avelocity = '7 0 11';
217         shield.scale = 0.5;
218         
219         setorigin(shield, self.origin);
220         setmodel(shield, "models/ctf/shield.md3");
221         setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
222 }
223
224
225 // ====================
226 // Drop/Pass/Throw Code
227 // ====================
228
229 void ctf_Handle_Drop(entity flag, entity player, float droptype)
230 {
231         // declarations
232         player = (player ? player : flag.pass_sender);
233
234         // main
235         flag.movetype = MOVETYPE_TOSS;
236         flag.takedamage = DAMAGE_YES;
237         flag.angles = '0 0 0';
238         flag.health = flag.max_flag_health;
239         flag.ctf_droptime = time;
240         flag.ctf_dropper = player;
241         flag.ctf_status = FLAG_DROPPED;
242         
243         // messages and sounds
244         Send_Notification_Legacy_Wrapper(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_LOST_), player.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
245         sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
246         ctf_EventLog("dropped", player.team, player);
247
248         // scoring
249         PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);   
250         PlayerScore_Add(player, SP_CTF_DROPS, 1);
251         
252         // waypoints
253         if(autocvar_g_ctf_flag_dropped_waypoint)
254                 WaypointSprite_Spawn("flagdropped", 0, 0, flag, FLAG_WAYPOINT_OFFSET, world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, TRUE, RADARICON_FLAG, WPCOLOR_DROPPEDFLAG(flag.team));
255
256         if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
257         {
258                 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
259                 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
260         }
261         
262         player.throw_antispam = time + autocvar_g_ctf_pass_wait;
263         
264         if(droptype == DROP_PASS)
265         {
266                 flag.pass_distance = 0;
267                 flag.pass_sender = world;
268                 flag.pass_target = world;
269         }
270 }
271
272 void ctf_Handle_Retrieve(entity flag, entity player)
273 {
274         entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
275         entity sender = flag.pass_sender;
276         
277         // transfer flag to player
278         flag.owner = player;
279         flag.owner.flagcarried = flag;
280         
281         // reset flag
282         setattachment(flag, player, "");
283         setorigin(flag, FLAG_CARRY_OFFSET);
284         flag.movetype = MOVETYPE_NONE;
285         flag.takedamage = DAMAGE_NO;
286         flag.solid = SOLID_NOT;
287         flag.angles = '0 0 0';
288         flag.ctf_status = FLAG_CARRY;
289
290         // messages and sounds
291         sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTN_NORM);
292         ctf_EventLog("receive", flag.team, player);
293         
294         FOR_EACH_REALPLAYER(tmp_player)
295         {
296                 if(tmp_player == sender)
297                         Send_Notification_Legacy_Wrapper(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_SENT_), player.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
298                 else if(tmp_player == player)
299                         Send_Notification_Legacy_Wrapper(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
300                 else if(!IsDifferentTeam(tmp_player, sender))
301                         Send_Notification_Legacy_Wrapper(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
302         }
303         
304         // create new waypoint
305         ctf_FlagcarrierWaypoints(player);
306         
307         sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
308         player.throw_antispam = sender.throw_antispam;
309
310         flag.pass_distance = 0;
311         flag.pass_sender = world;
312         flag.pass_target = world;
313 }
314
315 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
316 {
317         entity flag = player.flagcarried;
318         vector targ_origin, flag_velocity;
319         
320         if(!flag) { return; }
321         if((droptype == DROP_PASS) && !receiver) { return; }
322         
323         if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
324         
325         // reset the flag
326         setattachment(flag, world, "");
327         setorigin(flag, player.origin + FLAG_DROP_OFFSET);
328         flag.owner.flagcarried = world;
329         flag.owner = world;
330         flag.solid = SOLID_TRIGGER;
331         flag.ctf_dropper = player;
332         flag.ctf_droptime = time;
333         
334         flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
335         
336         switch(droptype)
337         {
338                 case DROP_PASS:
339                 {
340                         // warpzone support:
341                         // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
342                         // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
343                         WarpZone_RefSys_Copy(flag, receiver);
344                         WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
345                         targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
346
347                         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
348                         ctf_CalculatePassVelocity(flag, targ_origin, player.origin, FALSE);
349
350                         // main
351                         flag.movetype = MOVETYPE_FLY;
352                         flag.takedamage = DAMAGE_NO;
353                         flag.pass_sender = player;
354                         flag.pass_target = receiver;
355                         flag.ctf_status = FLAG_PASSING;
356                         
357                         // other
358                         sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTN_NORM);
359                         WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
360                         ctf_EventLog("pass", flag.team, player);
361                         break;
362                 }
363                 
364                 case DROP_THROW:
365                 {
366                         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'));
367                                 
368                         flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & IT_STRENGTH) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
369                         flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE);
370                         ctf_Handle_Drop(flag, player, droptype);
371                         break;
372                 }
373                 
374                 case DROP_RESET:
375                 {
376                         flag.velocity = '0 0 0'; // do nothing
377                         break;
378                 }
379                 
380                 default:
381                 case DROP_NORMAL:
382                 {
383                         flag.velocity = W_CalculateProjectileVelocity(player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), FALSE);
384                         ctf_Handle_Drop(flag, player, droptype);
385                         break;
386                 }
387         }
388
389         // kill old waypointsprite
390         WaypointSprite_Ping(player.wps_flagcarrier);
391         WaypointSprite_Kill(player.wps_flagcarrier);
392         
393         if(player.wps_enemyflagcarrier)
394                 WaypointSprite_Kill(player.wps_enemyflagcarrier);
395         
396         // captureshield
397         ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
398 }
399
400
401 // ==============
402 // Event Handlers
403 // ==============
404
405 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
406 {
407         entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
408         entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
409         float old_time, new_time; 
410         
411         if not(player) { return; } // without someone to give the reward to, we can't possibly cap
412         
413         // messages and sounds
414         ctf_CaptureRecord(enemy_flag, player);
415         sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
416         
417         switch(capturetype)
418         {
419                 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
420                 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
421                 default: break;
422         }
423         
424         // scoring
425         PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
426         PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
427
428         old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
429         new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
430         if(!old_time || new_time < old_time)
431                 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
432
433         // effects
434         pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
435         //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
436
437         // other
438         if(capturetype == CAPTURE_NORMAL)
439         {
440                 WaypointSprite_Kill(player.wps_flagcarrier);
441                 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
442                 
443                 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
444                         { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
445         }
446         
447         // reset the flag
448         player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
449         ctf_RespawnFlag(enemy_flag);
450 }
451
452 void ctf_Handle_Return(entity flag, entity player)
453 {
454         // messages and sounds
455         Send_Notification_Legacy_Wrapper(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
456         Send_Notification_Legacy_Wrapper(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
457         sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
458         ctf_EventLog("return", flag.team, player);
459
460         // scoring
461         PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
462         PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
463
464         TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
465         
466         if(flag.ctf_dropper) 
467         {
468                 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
469                 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag 
470                 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
471         }
472         
473         // reset the flag
474         ctf_RespawnFlag(flag);
475 }
476
477 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
478 {
479         // declarations
480         entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
481         float pickup_dropped_score; // used to calculate dropped pickup score
482         
483         // attach the flag to the player
484         flag.owner = player;
485         player.flagcarried = flag;
486         setattachment(flag, player, "");
487         setorigin(flag, FLAG_CARRY_OFFSET);
488         
489         // flag setup
490         flag.movetype = MOVETYPE_NONE;
491         flag.takedamage = DAMAGE_NO;
492         flag.solid = SOLID_NOT;
493         flag.angles = '0 0 0';
494         flag.ctf_status = FLAG_CARRY;
495         
496         switch(pickuptype)
497         {
498                 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
499                 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
500                 default: break;
501         }
502
503         // messages and sounds
504         Send_Notification_Legacy_Wrapper(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
505         sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
506
507         FOR_EACH_REALPLAYER(tmp_player)
508         {
509                 if(tmp_player == player)
510                 {
511                         Send_Notification_Legacy_Wrapper(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
512                         if(ctf_stalemate) { Send_Notification_Legacy_Wrapper(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG); }
513                 }
514                 else if(!IsDifferentTeam(tmp_player, player) && tmp_player != player)
515                         Send_Notification_Legacy_Wrapper(NOTIF_ONE, tmp_player, MSG_CENTER, (tmp_player.PICKUP_TEAM_VERBOSE ? CENTER_CTF_PICKUP_TEAM_VERBOSE : CENTER_CTF_PICKUP_TEAM), Team_ColorCode(player.team), (tmp_player.PICKUP_TEAM_VERBOSE ? player.netname : NO_STR_ARG), NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
516                 else if(IsDifferentTeam(tmp_player, player))
517                         Send_Notification_Legacy_Wrapper(NOTIF_ONE, tmp_player, MSG_CENTER, (tmp_player.PICKUP_ENEMY_VERBOSE ? CENTER_CTF_PICKUP_ENEMY_VERBOSE : CENTER_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), (tmp_player.PICKUP_ENEMY_VERBOSE ? player.netname : NO_STR_ARG), NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
518         }
519         
520         // scoring
521         PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
522         switch(pickuptype)
523         {               
524                 case PICKUP_BASE:
525                 {
526                         PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
527                         ctf_EventLog("steal", flag.team, player);
528                         break;
529                 }
530                 
531                 case PICKUP_DROPPED:
532                 {
533                         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);
534                         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);
535                         dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
536                         PlayerTeamScore_AddScore(player, pickup_dropped_score);
537                         ctf_EventLog("pickup", flag.team, player);
538                         break;
539                 }
540                 
541                 default: break;
542         }
543         
544         // speedrunning
545         if(pickuptype == PICKUP_BASE)
546         {
547                 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
548                 if((player.speedrunning) && (ctf_captimerecord))
549                         ctf_FakeTimeLimit(player, time + ctf_captimerecord);
550         }
551                 
552         // effects
553         pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
554         
555         // waypoints 
556         if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
557         ctf_FlagcarrierWaypoints(player);
558         WaypointSprite_Ping(player.wps_flagcarrier);
559 }
560
561
562 // ===================
563 // Main Flag Functions
564 // ===================
565
566 void ctf_CheckFlagReturn(entity flag, float returntype)
567 {
568         if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
569         {
570                 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
571                 
572                 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
573                 {
574                         switch(returntype)
575                         {
576                                 case RETURN_DROPPED: Send_Notification_Legacy_Wrapper(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DROPPED_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG); break;
577                                 case RETURN_DAMAGE: Send_Notification_Legacy_Wrapper(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG); break;
578                                 case RETURN_SPEEDRUN: Send_Notification_Legacy_Wrapper(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), NO_STR_ARG, NO_STR_ARG, ctf_captimerecord, NO_FL_ARG, NO_FL_ARG); break;
579                                 case RETURN_NEEDKILL: Send_Notification_Legacy_Wrapper(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG); break;
580                                 
581                                 default:
582                                 case RETURN_TIMEOUT:
583                                         { Send_Notification_Legacy_Wrapper(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG); break; }
584                         }
585                         sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
586                         ctf_EventLog("returned", flag.team, world);
587                         ctf_RespawnFlag(flag);
588                 }
589         }
590 }
591
592 void ctf_CheckStalemate(void)
593 {
594         // declarations
595         float stale_red_flags = 0, stale_blue_flags = 0;
596         entity tmp_entity;
597
598         entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
599
600         // build list of stale flags
601         for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
602         {
603                 if(autocvar_g_ctf_stalemate)
604                 if(tmp_entity.ctf_status != FLAG_BASE)
605                 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
606                 {
607                         tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
608                         ctf_staleflaglist = tmp_entity;
609                         
610                         switch(tmp_entity.team)
611                         {
612                                 case FL_TEAM_1: ++stale_red_flags; break;
613                                 case FL_TEAM_2: ++stale_blue_flags; break;
614                         }
615                 }
616         }
617
618         if(stale_red_flags && stale_blue_flags)
619                 ctf_stalemate = TRUE;
620         else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2)
621                 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
622         else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1)
623                 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
624                 
625         // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
626         if(ctf_stalemate)
627         {
628                 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
629                 {
630                         if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
631                                 WaypointSprite_Spawn("enemyflagcarrier", 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, tmp_entity.team, tmp_entity.owner, wps_enemyflagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_ENEMYFC(tmp_entity.owner.team));
632                 }
633                 
634                 if not(wpforenemy_announced)
635                 {
636                         FOR_EACH_REALPLAYER(tmp_entity)
637                                 if(tmp_entity.flagcarried)
638                                         Send_Notification_Legacy_Wrapper(NOTIF_ONE, tmp_entity, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
639                                 else
640                                         Send_Notification_Legacy_Wrapper(NOTIF_ONE, tmp_entity, MSG_CENTER, CENTER_CTF_STALEMATE_OTHER, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
641                         
642                         wpforenemy_announced = TRUE;
643                 }
644         }
645 }
646
647 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
648 {
649         if(ITEM_DAMAGE_NEEDKILL(deathtype))
650         {
651                 // automatically kill the flag and return it
652                 self.health = 0;
653                 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
654                 return;
655         }
656         if(autocvar_g_ctf_flag_return_damage) 
657         {
658                 // reduce health and check if it should be returned
659                 self.health = self.health - damage;
660                 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
661                 return;
662         }
663 }
664
665 void ctf_FlagThink()
666 {
667         // declarations
668         entity tmp_entity;
669
670         self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
671
672         // captureshield
673         if(self == ctf_worldflaglist) // only for the first flag
674                 FOR_EACH_CLIENT(tmp_entity)
675                         ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
676
677         // sanity checks
678         if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
679                 dprint("wtf the flag got squashed?\n");
680                 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
681                 if(!trace_startsolid) // can we resize it without getting stuck?
682                         setsize(self, FLAG_MIN, FLAG_MAX); }
683                         
684         switch(self.ctf_status) // reset flag angles in case warpzones adjust it
685         {
686                 case FLAG_DROPPED:
687                 {
688                         self.angles = '0 0 0';
689                         break;
690                 }
691                 
692                 default: break;
693         }
694
695         // main think method
696         switch(self.ctf_status)
697         {       
698                 case FLAG_BASE:
699                 {
700                         if(autocvar_g_ctf_dropped_capture_radius)
701                         {
702                                 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
703                                         if(tmp_entity.ctf_status == FLAG_DROPPED)
704                                         if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
705                                         if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
706                                                 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
707                         }
708                         return;
709                 }
710                 
711                 case FLAG_DROPPED:
712                 {
713                         if(autocvar_g_ctf_flag_dropped_floatinwater)
714                         {
715                                 vector midpoint = ((self.absmin + self.absmax) * 0.5);
716                                 if(pointcontents(midpoint) == CONTENT_WATER)
717                                 {
718                                         self.velocity = self.velocity * 0.5;
719                                         
720                                         if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
721                                                 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
722                                         else
723                                                 { self.movetype = MOVETYPE_FLY; }
724                                 }
725                                 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
726                         }
727                         if(autocvar_g_ctf_flag_return_dropped)
728                         {
729                                 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
730                                 {
731                                         self.health = 0;
732                                         ctf_CheckFlagReturn(self, RETURN_DROPPED);
733                                         return;
734                                 }
735                         }
736                         if(autocvar_g_ctf_flag_return_time)
737                         {
738                                 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
739                                 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
740                                 return;
741                         } 
742                         return;
743                 }
744                         
745                 case FLAG_CARRY:
746                 {
747                         if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord)) 
748                         {
749                                 self.health = 0;
750                                 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
751
752                                 tmp_entity = self;
753                                 self = self.owner;
754                                 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
755                                 ImpulseCommands();
756                                 self = tmp_entity;
757                         }
758                         if(autocvar_g_ctf_stalemate)
759                         {
760                                 if(time >= wpforenemy_nextthink)
761                                 {
762                                         ctf_CheckStalemate();
763                                         wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
764                                 }
765                         }
766                         return;
767                 }
768                 
769                 case FLAG_PASSING:
770                 {
771                         vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
772                         targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
773                         WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
774                         
775                         if((self.pass_target == world)
776                                 || (self.pass_target.deadflag != DEAD_NO)
777                                 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
778                                 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
779                                 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
780                         {
781                                 // give up, pass failed
782                                 ctf_Handle_Drop(self, world, DROP_PASS);
783                         }
784                         else
785                         {
786                                 // still a viable target, go for it
787                                 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
788                         }
789                         return;
790                 }
791
792                 default: // this should never happen
793                 {
794                         dprint("ctf_FlagThink(): Flag exists with no status?\n");
795                         return;
796                 }
797         }
798 }
799
800 void ctf_FlagTouch()
801 {
802         if(gameover) { return; }
803         
804         entity toucher = other;
805         
806         // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
807         if(ITEM_TOUCH_NEEDKILL())
808         {
809                 self.health = 0;
810                 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
811                 return;
812         }
813         
814         // special touch behaviors
815         if(toucher.vehicle_flags & VHF_ISVEHICLE)
816         {
817                 if(autocvar_g_ctf_allow_vehicle_touch)
818                         toucher = toucher.owner; // the player is actually the vehicle owner, not other
819                 else
820                         return; // do nothing
821         }
822         else if(toucher.classname != "player") // The flag just touched an object, most likely the world
823         {
824                 if(time > self.wait) // if we haven't in a while, play a sound/effect
825                 {
826                         pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
827                         sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
828                         self.wait = time + FLAG_TOUCHRATE;
829                 }
830                 return;
831         }
832         else if(toucher.deadflag != DEAD_NO) { return; }
833
834         switch(self.ctf_status) 
835         {       
836                 case FLAG_BASE:
837                 {
838                         if(!IsDifferentTeam(toucher, self) && (toucher.flagcarried) && IsDifferentTeam(toucher.flagcarried, self))
839                                 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
840                         else if(IsDifferentTeam(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
841                                 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
842                         break;
843                 }
844                 
845                 case FLAG_DROPPED:
846                 {
847                         if(!IsDifferentTeam(toucher, self))
848                                 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
849                         else if((!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
850                                 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
851                         break;
852                 }
853                         
854                 case FLAG_CARRY:
855                 {
856                         dprint("Someone touched a flag even though it was being carried?\n");
857                         break;
858                 }
859                 
860                 case FLAG_PASSING:
861                 {
862                         if((toucher.classname == "player") && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
863                         {
864                                 if(IsDifferentTeam(toucher, self.pass_sender))
865                                         ctf_Handle_Return(self, toucher);
866                                 else
867                                         ctf_Handle_Retrieve(self, toucher);
868                         }
869                         break;
870                 }
871         }
872 }
873
874 .float last_respawn;
875 void ctf_RespawnFlag(entity flag)
876 {
877         // check for flag respawn being called twice in a row
878         if(flag.last_respawn > time - 0.5)
879                 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
880
881         flag.last_respawn = time;
882         
883         // reset the player (if there is one)
884         if((flag.owner) && (flag.owner.flagcarried == flag))
885         {
886                 if(flag.owner.wps_enemyflagcarrier)
887                         WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
888                         
889                 WaypointSprite_Kill(flag.wps_flagcarrier);
890                 
891                 flag.owner.flagcarried = world;
892
893                 if(flag.speedrunning)
894                         ctf_FakeTimeLimit(flag.owner, -1);
895         }
896
897         if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
898                 { WaypointSprite_Kill(flag.wps_flagdropped); }
899
900         // reset the flag
901         setattachment(flag, world, "");
902         setorigin(flag, flag.ctf_spawnorigin);
903         
904         flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
905         flag.takedamage = DAMAGE_NO;
906         flag.health = flag.max_flag_health;
907         flag.solid = SOLID_TRIGGER;
908         flag.velocity = '0 0 0';
909         flag.angles = flag.mangle;
910         flag.flags = FL_ITEM | FL_NOTARGET;
911         
912         flag.ctf_status = FLAG_BASE;
913         flag.owner = world;
914         flag.pass_distance = 0;
915         flag.pass_sender = world;
916         flag.pass_target = world;
917         flag.ctf_dropper = world;
918         flag.ctf_pickuptime = 0;
919         flag.ctf_droptime = 0;
920 }
921
922 void ctf_Reset()
923 {
924         if(self.owner)
925                 if(self.owner.classname == "player")
926                         ctf_Handle_Throw(self.owner, world, DROP_RESET);
927                         
928         ctf_RespawnFlag(self);
929 }
930
931 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
932 {
933         // bot waypoints
934         waypoint_spawnforitem_force(self, self.origin);
935         self.nearestwaypointtimeout = 0; // activate waypointing again
936         self.bot_basewaypoint = self.nearestwaypoint;
937
938         // waypointsprites
939         WaypointSprite_SpawnFixed(((self.team == FL_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
940         WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
941
942         // captureshield setup
943         ctf_CaptureShield_Spawn(self);
944 }
945
946 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc 
947 {       
948         // declarations
949         teamnumber = fabs(teamnumber - bound(0, autocvar_g_ctf_reverse, 1)); // if we were originally 1, this will become 0. If we were originally 0, this will become 1. 
950         self = flag; // for later usage with droptofloor()
951         
952         // main setup
953         flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
954         ctf_worldflaglist = flag;
955
956         setattachment(flag, world, "");
957
958         flag.netname = ((teamnumber) ? "^1REPLACETHIS^7" : "^4REPLACETHIS^7"); // ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
959         flag.team = ((teamnumber) ? FL_TEAM_1 : FL_TEAM_2); // FL_TEAM_1: color 4 team (red) - FL_TEAM_2: color 13 team (blue)
960         flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
961         flag.classname = "item_flag_team";
962         flag.target = "###item###"; // wut?
963         flag.flags = FL_ITEM | FL_NOTARGET;
964         flag.solid = SOLID_TRIGGER;
965         flag.takedamage = DAMAGE_NO;
966         flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;   
967         flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
968         flag.health = flag.max_flag_health;
969         flag.event_damage = ctf_FlagDamage;
970         flag.pushable = TRUE;
971         flag.teleportable = TELEPORT_NORMAL;
972         flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
973         flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
974         flag.velocity = '0 0 0';
975         flag.mangle = flag.angles;
976         flag.reset = ctf_Reset;
977         flag.touch = ctf_FlagTouch;
978         flag.think = ctf_FlagThink;
979         flag.nextthink = time + FLAG_THINKRATE;
980         flag.ctf_status = FLAG_BASE;
981         
982         if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
983         if(!flag.scale) { flag.scale = FLAG_SCALE; }
984         if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
985         if(!flag.toucheffect) { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
986         if(!flag.passeffect) { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
987         if(!flag.capeffect) { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
988         
989         // sound 
990         if(!flag.snd_flag_taken) { flag.snd_flag_taken  = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
991         if(!flag.snd_flag_returned) { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
992         if(!flag.snd_flag_capture) { flag.snd_flag_capture = ((teamnumber) ? "ctf/red_capture.wav" : "ctf/blue_capture.wav"); } // blue team scores by capturing the red flag
993         if(!flag.snd_flag_respawn) { flag.snd_flag_respawn = "ctf/flag_respawn.wav"; } // if there is ever a team-based sound for this, update the code to match.
994         if(!flag.snd_flag_dropped) { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
995         if(!flag.snd_flag_touch) { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
996         if(!flag.snd_flag_pass) { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
997         
998         // precache
999         precache_sound(flag.snd_flag_taken);
1000         precache_sound(flag.snd_flag_returned);
1001         precache_sound(flag.snd_flag_capture);
1002         precache_sound(flag.snd_flag_respawn);
1003         precache_sound(flag.snd_flag_dropped);
1004         precache_sound(flag.snd_flag_touch);
1005         precache_sound(flag.snd_flag_pass);
1006         precache_model(flag.model);
1007         precache_model("models/ctf/shield.md3");
1008         precache_model("models/ctf/shockwavetransring.md3");
1009
1010         // appearence
1011         setmodel(flag, flag.model); // precision set below
1012         setsize(flag, FLAG_MIN, FLAG_MAX);
1013         setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1014         
1015         if(autocvar_g_ctf_flag_glowtrails)
1016         {
1017                 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
1018                 flag.glow_size = 25;
1019                 flag.glow_trail = 1;
1020         }
1021         
1022         flag.effects |= EF_LOWPRECISION;
1023         if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1024         if(autocvar_g_ctf_dynamiclights)   { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
1025         
1026         // flag placement
1027         if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1028         {       
1029                 flag.dropped_origin = flag.origin; 
1030                 flag.noalign = TRUE;
1031                 flag.movetype = MOVETYPE_NONE;
1032         }
1033         else // drop to floor, automatically find a platform and set that as spawn origin
1034         { 
1035                 flag.noalign = FALSE;
1036                 self = flag;
1037                 droptofloor();
1038                 flag.movetype = MOVETYPE_TOSS; 
1039         }       
1040         
1041         InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1042 }
1043
1044
1045 // ================
1046 // Bot player logic
1047 // ================
1048
1049 // NOTE: LEGACY CODE, needs to be re-written!
1050
1051 void havocbot_calculate_middlepoint()
1052 {
1053         entity f;
1054         vector s = '0 0 0';
1055         vector fo = '0 0 0';
1056         float n = 0;
1057
1058         f = ctf_worldflaglist;
1059         while (f)
1060         {
1061                 fo = f.origin;
1062                 s = s + fo;
1063                 f = f.ctf_worldflagnext;
1064         }
1065         if(!n)
1066                 return;
1067         havocbot_ctf_middlepoint = s * (1.0 / n);
1068         havocbot_ctf_middlepoint_radius  = vlen(fo - havocbot_ctf_middlepoint);
1069 }
1070
1071
1072 entity havocbot_ctf_find_flag(entity bot)
1073 {
1074         entity f;
1075         f = ctf_worldflaglist;
1076         while (f)
1077         {
1078                 if (bot.team == f.team)
1079                         return f;
1080                 f = f.ctf_worldflagnext;
1081         }
1082         return world;
1083 }
1084
1085 entity havocbot_ctf_find_enemy_flag(entity bot)
1086 {
1087         entity f;
1088         f = ctf_worldflaglist;
1089         while (f)
1090         {
1091                 if (bot.team != f.team)
1092                         return f;
1093                 f = f.ctf_worldflagnext;
1094         }
1095         return world;
1096 }
1097
1098 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1099 {
1100         if not(teamplay)
1101                 return 0;
1102
1103         float c = 0;
1104         entity head;
1105
1106         FOR_EACH_PLAYER(head)
1107         {
1108                 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1109                         continue;
1110
1111                 if(vlen(head.origin - org) < tc_radius)
1112                         ++c;
1113         }
1114
1115         return c;
1116 }
1117
1118 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1119 {
1120         entity head;
1121         head = ctf_worldflaglist;
1122         while (head)
1123         {
1124                 if (self.team == head.team)
1125                         break;
1126                 head = head.ctf_worldflagnext;
1127         }
1128         if (head)
1129                 navigation_routerating(head, ratingscale, 10000);
1130 }
1131
1132 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1133 {
1134         entity head;
1135         head = ctf_worldflaglist;
1136         while (head)
1137         {
1138                 if (self.team == head.team)
1139                         break;
1140                 head = head.ctf_worldflagnext;
1141         }
1142         if not(head)
1143                 return;
1144
1145         navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1146 }
1147
1148 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1149 {
1150         entity head;
1151         head = ctf_worldflaglist;
1152         while (head)
1153         {
1154                 if (self.team != head.team)
1155                         break;
1156                 head = head.ctf_worldflagnext;
1157         }
1158         if (head)
1159                 navigation_routerating(head, ratingscale, 10000);
1160 }
1161
1162 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1163 {
1164         if not(bot_waypoints_for_items)
1165         {
1166                 havocbot_goalrating_ctf_enemyflag(ratingscale);
1167                 return;
1168         }
1169
1170         entity head;
1171
1172         head = havocbot_ctf_find_enemy_flag(self);
1173
1174         if not(head)
1175                 return;
1176
1177         navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1178 }
1179
1180 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1181 {
1182         entity mf;
1183
1184         mf = havocbot_ctf_find_flag(self);
1185
1186         if(mf.ctf_status == FLAG_BASE)
1187                 return;
1188
1189         if(mf.tag_entity)
1190                 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1191 }
1192
1193 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1194 {
1195         entity head;
1196         head = ctf_worldflaglist;
1197         while (head)
1198         {
1199                 // flag is out in the field
1200                 if(head.ctf_status != FLAG_BASE)
1201                 if(head.tag_entity==world)      // dropped
1202                 {
1203                         if(df_radius)
1204                         {
1205                                 if(vlen(org-head.origin)<df_radius)
1206                                         navigation_routerating(head, ratingscale, 10000);
1207                         }
1208                         else
1209                                 navigation_routerating(head, ratingscale, 10000);
1210                 }
1211
1212                 head = head.ctf_worldflagnext;
1213         }
1214 }
1215
1216 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1217 {
1218         entity head;
1219         float t;
1220         head = findchainfloat(bot_pickup, TRUE);
1221         while (head)
1222         {
1223                 // gather health and armor only
1224                 if (head.solid)
1225                 if (head.health || head.armorvalue)
1226                 if (vlen(head.origin - org) < sradius)
1227                 {
1228                         // get the value of the item
1229                         t = head.bot_pickupevalfunc(self, head) * 0.0001;
1230                         if (t > 0)
1231                                 navigation_routerating(head, t * ratingscale, 500);
1232                 }
1233                 head = head.chain;
1234         }
1235 }
1236
1237 void havocbot_ctf_reset_role(entity bot)
1238 {
1239         float cdefense, cmiddle, coffense;
1240         entity mf, ef, head;
1241         float c;
1242
1243         if(bot.deadflag != DEAD_NO)
1244                 return;
1245
1246         if(vlen(havocbot_ctf_middlepoint)==0)
1247                 havocbot_calculate_middlepoint();
1248
1249         // Check ctf flags
1250         if (bot.flagcarried)
1251         {
1252                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1253                 return;
1254         }
1255
1256         mf = havocbot_ctf_find_flag(bot);
1257         ef = havocbot_ctf_find_enemy_flag(bot);
1258
1259         // Retrieve stolen flag
1260         if(mf.ctf_status!=FLAG_BASE)
1261         {
1262                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1263                 return;
1264         }
1265
1266         // If enemy flag is taken go to the middle to intercept pursuers
1267         if(ef.ctf_status!=FLAG_BASE)
1268         {
1269                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1270                 return;
1271         }
1272
1273         // if there is only me on the team switch to offense
1274         c = 0;
1275         FOR_EACH_PLAYER(head)
1276         if(head.team==bot.team)
1277                 ++c;
1278
1279         if(c==1)
1280         {
1281                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1282                 return;
1283         }
1284
1285         // Evaluate best position to take
1286         // Count mates on middle position
1287         cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1288
1289         // Count mates on defense position
1290         cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1291
1292         // Count mates on offense position
1293         coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1294
1295         if(cdefense<=coffense)
1296                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1297         else if(coffense<=cmiddle)
1298                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1299         else
1300                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1301 }
1302
1303 void havocbot_role_ctf_carrier()
1304 {
1305         if(self.deadflag != DEAD_NO)
1306         {
1307                 havocbot_ctf_reset_role(self);
1308                 return;
1309         }
1310
1311         if (self.flagcarried == world)
1312         {
1313                 havocbot_ctf_reset_role(self);
1314                 return;
1315         }
1316
1317         if (self.bot_strategytime < time)
1318         {
1319                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1320
1321                 navigation_goalrating_start();
1322                 havocbot_goalrating_ctf_ourbase(50000);
1323
1324                 if(self.health<100)
1325                         havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1326
1327                 navigation_goalrating_end();
1328
1329                 if (self.navigation_hasgoals)
1330                         self.havocbot_cantfindflag = time + 10;
1331                 else if (time > self.havocbot_cantfindflag)
1332                 {
1333                         // Can't navigate to my own base, suicide!
1334                         // TODO: drop it and wander around
1335                         Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1336                         return;
1337                 }
1338         }
1339 }
1340
1341 void havocbot_role_ctf_escort()
1342 {
1343         entity mf, ef;
1344
1345         if(self.deadflag != DEAD_NO)
1346         {
1347                 havocbot_ctf_reset_role(self);
1348                 return;
1349         }
1350
1351         if (self.flagcarried)
1352         {
1353                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1354                 return;
1355         }
1356
1357         // If enemy flag is back on the base switch to previous role
1358         ef = havocbot_ctf_find_enemy_flag(self);
1359         if(ef.ctf_status==FLAG_BASE)
1360         {
1361                 self.havocbot_role = self.havocbot_previous_role;
1362                 self.havocbot_role_timeout = 0;
1363                 return;
1364         }
1365
1366         // If the flag carrier reached the base switch to defense
1367         mf = havocbot_ctf_find_flag(self);
1368         if(mf.ctf_status!=FLAG_BASE)
1369         if(vlen(ef.origin - mf.dropped_origin) < 300)
1370         {
1371                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1372                 return;
1373         }
1374
1375         // Set the role timeout if necessary
1376         if (!self.havocbot_role_timeout)
1377         {
1378                 self.havocbot_role_timeout = time + random() * 30 + 60;
1379         }
1380
1381         // If nothing happened just switch to previous role
1382         if (time > self.havocbot_role_timeout)
1383         {
1384                 self.havocbot_role = self.havocbot_previous_role;
1385                 self.havocbot_role_timeout = 0;
1386                 return;
1387         }
1388
1389         // Chase the flag carrier
1390         if (self.bot_strategytime < time)
1391         {
1392                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1393                 navigation_goalrating_start();
1394                 havocbot_goalrating_ctf_enemyflag(30000);
1395                 havocbot_goalrating_ctf_ourstolenflag(40000);
1396                 havocbot_goalrating_items(10000, self.origin, 10000);
1397                 navigation_goalrating_end();
1398         }
1399 }
1400
1401 void havocbot_role_ctf_offense()
1402 {
1403         entity mf, ef;
1404         vector pos;
1405
1406         if(self.deadflag != DEAD_NO)
1407         {
1408                 havocbot_ctf_reset_role(self);
1409                 return;
1410         }
1411
1412         if (self.flagcarried)
1413         {
1414                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1415                 return;
1416         }
1417
1418         // Check flags
1419         mf = havocbot_ctf_find_flag(self);
1420         ef = havocbot_ctf_find_enemy_flag(self);
1421
1422         // Own flag stolen
1423         if(mf.ctf_status!=FLAG_BASE)
1424         {
1425                 if(mf.tag_entity)
1426                         pos = mf.tag_entity.origin;
1427                 else
1428                         pos = mf.origin;
1429
1430                 // Try to get it if closer than the enemy base
1431                 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1432                 {
1433                         havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1434                         return;
1435                 }
1436         }
1437
1438         // Escort flag carrier
1439         if(ef.ctf_status!=FLAG_BASE)
1440         {
1441                 if(ef.tag_entity)
1442                         pos = ef.tag_entity.origin;
1443                 else
1444                         pos = ef.origin;
1445
1446                 if(vlen(pos-mf.dropped_origin)>700)
1447                 {
1448                         havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1449                         return;
1450                 }
1451         }
1452
1453         // About to fail, switch to middlefield
1454         if(self.health<50)
1455         {
1456                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1457                 return;
1458         }
1459
1460         // Set the role timeout if necessary
1461         if (!self.havocbot_role_timeout)
1462                 self.havocbot_role_timeout = time + 120;
1463
1464         if (time > self.havocbot_role_timeout)
1465         {
1466                 havocbot_ctf_reset_role(self);
1467                 return;
1468         }
1469
1470         if (self.bot_strategytime < time)
1471         {
1472                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1473                 navigation_goalrating_start();
1474                 havocbot_goalrating_ctf_ourstolenflag(50000);
1475                 havocbot_goalrating_ctf_enemybase(20000);
1476                 havocbot_goalrating_items(5000, self.origin, 1000);
1477                 havocbot_goalrating_items(1000, self.origin, 10000);
1478                 navigation_goalrating_end();
1479         }
1480 }
1481
1482 // Retriever (temporary role):
1483 void havocbot_role_ctf_retriever()
1484 {
1485         entity mf;
1486
1487         if(self.deadflag != DEAD_NO)
1488         {
1489                 havocbot_ctf_reset_role(self);
1490                 return;
1491         }
1492
1493         if (self.flagcarried)
1494         {
1495                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1496                 return;
1497         }
1498
1499         // If flag is back on the base switch to previous role
1500         mf = havocbot_ctf_find_flag(self);
1501         if(mf.ctf_status==FLAG_BASE)
1502         {
1503                 havocbot_ctf_reset_role(self);
1504                 return;
1505         }
1506
1507         if (!self.havocbot_role_timeout)
1508                 self.havocbot_role_timeout = time + 20;
1509
1510         if (time > self.havocbot_role_timeout)
1511         {
1512                 havocbot_ctf_reset_role(self);
1513                 return;
1514         }
1515
1516         if (self.bot_strategytime < time)
1517         {
1518                 float rt_radius;
1519                 rt_radius = 10000;
1520
1521                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1522                 navigation_goalrating_start();
1523                 havocbot_goalrating_ctf_ourstolenflag(50000);
1524                 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1525                 havocbot_goalrating_ctf_enemybase(30000);
1526                 havocbot_goalrating_items(500, self.origin, rt_radius);
1527                 navigation_goalrating_end();
1528         }
1529 }
1530
1531 void havocbot_role_ctf_middle()
1532 {
1533         entity mf;
1534
1535         if(self.deadflag != DEAD_NO)
1536         {
1537                 havocbot_ctf_reset_role(self);
1538                 return;
1539         }
1540
1541         if (self.flagcarried)
1542         {
1543                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1544                 return;
1545         }
1546
1547         mf = havocbot_ctf_find_flag(self);
1548         if(mf.ctf_status!=FLAG_BASE)
1549         {
1550                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1551                 return;
1552         }
1553
1554         if (!self.havocbot_role_timeout)
1555                 self.havocbot_role_timeout = time + 10;
1556
1557         if (time > self.havocbot_role_timeout)
1558         {
1559                 havocbot_ctf_reset_role(self);
1560                 return;
1561         }
1562
1563         if (self.bot_strategytime < time)
1564         {
1565                 vector org;
1566
1567                 org = havocbot_ctf_middlepoint;
1568                 org_z = self.origin_z;
1569
1570                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1571                 navigation_goalrating_start();
1572                 havocbot_goalrating_ctf_ourstolenflag(50000);
1573                 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1574                 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1575                 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1576                 havocbot_goalrating_items(2500, self.origin, 10000);
1577                 havocbot_goalrating_ctf_enemybase(2500);
1578                 navigation_goalrating_end();
1579         }
1580 }
1581
1582 void havocbot_role_ctf_defense()
1583 {
1584         entity mf;
1585
1586         if(self.deadflag != DEAD_NO)
1587         {
1588                 havocbot_ctf_reset_role(self);
1589                 return;
1590         }
1591
1592         if (self.flagcarried)
1593         {
1594                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1595                 return;
1596         }
1597
1598         // If own flag was captured
1599         mf = havocbot_ctf_find_flag(self);
1600         if(mf.ctf_status!=FLAG_BASE)
1601         {
1602                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1603                 return;
1604         }
1605
1606         if (!self.havocbot_role_timeout)
1607                 self.havocbot_role_timeout = time + 30;
1608
1609         if (time > self.havocbot_role_timeout)
1610         {
1611                 havocbot_ctf_reset_role(self);
1612                 return;
1613         }
1614         if (self.bot_strategytime < time)
1615         {
1616                 float mp_radius;
1617                 vector org;
1618
1619                 org = mf.dropped_origin;
1620                 mp_radius = havocbot_ctf_middlepoint_radius;
1621
1622                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1623                 navigation_goalrating_start();
1624
1625                 // if enemies are closer to our base, go there
1626                 entity head, closestplayer = world;
1627                 float distance, bestdistance = 10000;
1628                 FOR_EACH_PLAYER(head)
1629                 {
1630                         if(head.deadflag!=DEAD_NO)
1631                                 continue;
1632
1633                         distance = vlen(org - head.origin);
1634                         if(distance<bestdistance)
1635                         {
1636                                 closestplayer = head;
1637                                 bestdistance = distance;
1638                         }
1639                 }
1640
1641                 if(closestplayer)
1642                 if(closestplayer.team!=self.team)
1643                 if(vlen(org - self.origin)>1000)
1644                 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1645                         havocbot_goalrating_ctf_ourbase(30000);
1646
1647                 havocbot_goalrating_ctf_ourstolenflag(20000);
1648                 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1649                 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1650                 havocbot_goalrating_items(10000, org, mp_radius);
1651                 havocbot_goalrating_items(5000, self.origin, 10000);
1652                 navigation_goalrating_end();
1653         }
1654 }
1655
1656 void havocbot_role_ctf_setrole(entity bot, float role)
1657 {
1658         dprint(strcat(bot.netname," switched to "));
1659         switch(role)
1660         {
1661                 case HAVOCBOT_CTF_ROLE_CARRIER:
1662                         dprint("carrier");
1663                         bot.havocbot_role = havocbot_role_ctf_carrier;
1664                         bot.havocbot_role_timeout = 0;
1665                         bot.havocbot_cantfindflag = time + 10;
1666                         bot.bot_strategytime = 0;
1667                         break;
1668                 case HAVOCBOT_CTF_ROLE_DEFENSE:
1669                         dprint("defense");
1670                         bot.havocbot_role = havocbot_role_ctf_defense;
1671                         bot.havocbot_role_timeout = 0;
1672                         break;
1673                 case HAVOCBOT_CTF_ROLE_MIDDLE:
1674                         dprint("middle");
1675                         bot.havocbot_role = havocbot_role_ctf_middle;
1676                         bot.havocbot_role_timeout = 0;
1677                         break;
1678                 case HAVOCBOT_CTF_ROLE_OFFENSE:
1679                         dprint("offense");
1680                         bot.havocbot_role = havocbot_role_ctf_offense;
1681                         bot.havocbot_role_timeout = 0;
1682                         break;
1683                 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1684                         dprint("retriever");
1685                         bot.havocbot_previous_role = bot.havocbot_role;
1686                         bot.havocbot_role = havocbot_role_ctf_retriever;
1687                         bot.havocbot_role_timeout = time + 10;
1688                         bot.bot_strategytime = 0;
1689                         break;
1690                 case HAVOCBOT_CTF_ROLE_ESCORT:
1691                         dprint("escort");
1692                         bot.havocbot_previous_role = bot.havocbot_role;
1693                         bot.havocbot_role = havocbot_role_ctf_escort;
1694                         bot.havocbot_role_timeout = time + 30;
1695                         bot.bot_strategytime = 0;
1696                         break;
1697         }
1698         dprint("\n");
1699 }
1700
1701
1702 // ==============
1703 // Hook Functions
1704 // ==============
1705
1706 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1707 {
1708         entity flag;
1709         
1710         // initially clear items so they can be set as necessary later.
1711         self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST 
1712                 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1713
1714         // scan through all the flags and notify the client about them 
1715         for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1716         {
1717                 switch(flag.ctf_status)
1718                 {
1719                         case FLAG_PASSING:
1720                         case FLAG_CARRY:
1721                         {
1722                                 if((flag.owner == self) || (flag.pass_sender == self))
1723                                         self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1724                                 else 
1725                                         self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1726                                 break;
1727                         }
1728                         case FLAG_DROPPED:
1729                         {
1730                                 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1731                                 break;
1732                         }
1733                 }
1734         }
1735         
1736         // item for stopping players from capturing the flag too often
1737         if(self.ctf_captureshielded)
1738                 self.items |= IT_CTF_SHIELDED;
1739         
1740         // update the health of the flag carrier waypointsprite
1741         if(self.wps_flagcarrier) 
1742                 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent));
1743         
1744         return FALSE;
1745 }
1746
1747 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1748 {
1749         if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1750         {
1751                 if(frag_target == frag_attacker) // damage done to yourself
1752                 {
1753                         frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1754                         frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1755                 }
1756                 else // damage done to everyone else
1757                 {
1758                         frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1759                         frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1760                 }
1761         }
1762         else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && IsDifferentTeam(frag_target, frag_attacker)) // if the target is a flagcarrier
1763         {
1764                 if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent)))
1765                 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1766                 {
1767                         frag_target.wps_helpme_time = time;
1768                         WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1769                 }
1770                 // todo: add notification for when flag carrier needs help?
1771         }
1772         return FALSE;
1773 }
1774
1775 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1776 {
1777         if((frag_attacker != frag_target) && (frag_attacker.classname == "player") && (frag_target.flagcarried))
1778         {
1779                 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1780                 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1781         }
1782                                 
1783         if(frag_target.flagcarried)
1784                 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1785                 
1786         return FALSE;
1787 }
1788
1789 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1790 {
1791         frag_score = 0;
1792         return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1793 }
1794
1795 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1796 {
1797         entity flag; // temporary entity for the search method
1798         
1799         if(self.flagcarried)
1800                 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1801         
1802         for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1803         {
1804                 if(flag.pass_sender == self) { flag.pass_sender = world; }
1805                 if(flag.pass_target == self) { flag.pass_target = world; }
1806                 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1807         }
1808                 
1809         return FALSE;
1810 }
1811
1812 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1813 {
1814         if(self.flagcarried) 
1815         if(!autocvar_g_ctf_portalteleport)
1816                 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1817
1818         return FALSE;
1819 }
1820
1821 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1822 {
1823         if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1824         
1825         entity player = self;
1826
1827         if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1828         {
1829                 // pass the flag to a team mate
1830                 if(autocvar_g_ctf_pass)
1831                 {
1832                         entity head, closest_target = world;
1833                         head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1834                         
1835                         while(head) // find the closest acceptable target to pass to
1836                         {
1837                                 if(head.classname == "player" && head.deadflag == DEAD_NO)
1838                                 if(head != player && !IsDifferentTeam(head, player))
1839                                 if(!head.speedrunning && !head.vehicle)
1840                                 {
1841                                         // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) 
1842                                         vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
1843                                         vector passer_center = CENTER_OR_VIEWOFS(player);
1844                                         
1845                                         if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1846                                         {
1847                                                 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried) 
1848                                                 { 
1849                                                         if(clienttype(head) == CLIENTTYPE_BOT)
1850                                                         {
1851                                                                 Send_Notification_Legacy_Wrapper(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
1852                                                                 ctf_Handle_Throw(head, player, DROP_PASS);
1853                                                         }
1854                                                         else
1855                                                         {
1856                                                                 Send_Notification_Legacy_Wrapper(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
1857                                                                 Send_Notification_Legacy_Wrapper(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
1858                                                         }
1859                                                         player.throw_antispam = time + autocvar_g_ctf_pass_wait; 
1860                                                         return TRUE; 
1861                                                 }
1862                                                 else if(player.flagcarried)
1863                                                 {
1864                                                         if(closest_target)
1865                                                         {
1866                                                                 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
1867                                                                 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1868                                                                         { closest_target = head; }
1869                                                         }
1870                                                         else { closest_target = head; }
1871                                                 }
1872                                         }
1873                                 }
1874                                 head = head.chain;
1875                         }
1876                         
1877                         if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1878                 }
1879                 
1880                 // throw the flag in front of you
1881                 if(autocvar_g_ctf_throw && player.flagcarried)
1882                 {
1883                         if(player.throw_count == -1)
1884                         {
1885                                 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
1886                                 {
1887                                         player.throw_prevtime = time;
1888                                         player.throw_count = 1;
1889                                         ctf_Handle_Throw(player, world, DROP_THROW);
1890                                         return TRUE;
1891                                 }
1892                                 else
1893                                 {
1894                                         Send_Notification_Legacy_Wrapper(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, NO_STR_ARG, NO_STR_ARG, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time), NO_FL_ARG, NO_FL_ARG);
1895                                         return FALSE;
1896                                 }
1897                         }
1898                         else
1899                         {
1900                                 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
1901                                 else { player.throw_count += 1; }
1902                                 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
1903                                         
1904                                 player.throw_prevtime = time;
1905                                 ctf_Handle_Throw(player, world, DROP_THROW);
1906                                 return TRUE;
1907                         }
1908                 }
1909         }
1910                 
1911         return FALSE;
1912 }
1913
1914 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1915 {
1916         if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1917         {
1918                 self.wps_helpme_time = time;
1919                 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1920         } 
1921         else // create a normal help me waypointsprite
1922         {
1923                 WaypointSprite_Spawn("helpme", waypointsprite_deployed_lifetime, waypointsprite_limitedrange, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, wps_helpme, FALSE, RADARICON_HELPME, '1 0.5 0');
1924                 WaypointSprite_Ping(self.wps_helpme);
1925         }
1926
1927         return TRUE;
1928 }
1929
1930 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1931 {
1932         if(vh_player.flagcarried)
1933         {
1934                 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
1935                 {
1936                         ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1937                 }
1938                 else
1939                 {            
1940                         setattachment(vh_player.flagcarried, vh_vehicle, ""); 
1941                         setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1942                         vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1943                         //vh_player.flagcarried.angles = '0 0 0';       
1944                 }
1945                 return TRUE;
1946         }
1947                 
1948         return FALSE;
1949 }
1950
1951 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1952 {
1953         if(vh_player.flagcarried)
1954         {
1955                 setattachment(vh_player.flagcarried, vh_player, ""); 
1956                 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1957                 vh_player.flagcarried.scale = FLAG_SCALE;
1958                 vh_player.flagcarried.angles = '0 0 0';
1959                 return TRUE;
1960         }
1961
1962         return FALSE;
1963 }
1964
1965 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1966 {
1967         if(self.flagcarried)
1968         {
1969                 Send_Notification_Legacy_Wrapper(NOTIF_ANY, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
1970                 ctf_RespawnFlag(self.flagcarried);
1971                 return TRUE;
1972         }
1973         
1974         return FALSE;
1975 }
1976
1977 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1978 {
1979         entity flag; // temporary entity for the search method
1980         
1981         for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1982         {
1983                 switch(flag.ctf_status)
1984                 {
1985                         case FLAG_DROPPED:
1986                         case FLAG_PASSING:
1987                         {
1988                                 // lock the flag, game is over
1989                                 flag.movetype = MOVETYPE_NONE;
1990                                 flag.takedamage = DAMAGE_NO;
1991                                 flag.solid = SOLID_NOT;
1992                                 flag.nextthink = FALSE; // stop thinking
1993                                 
1994                                 //dprint("stopping the ", flag.netname, " from moving.\n");
1995                                 break;
1996                         }
1997                         
1998                         default:
1999                         case FLAG_BASE:
2000                         case FLAG_CARRY:
2001                         {
2002                                 // do nothing for these flags
2003                                 break;
2004                         }
2005                 }
2006         }
2007         
2008         return FALSE;
2009 }
2010
2011 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2012 {
2013         havocbot_ctf_reset_role(self);
2014         return TRUE;
2015 }
2016
2017 MUTATOR_HOOKFUNCTION(ctf_GetCvars)
2018 {
2019         GetCvars_handleFloat(get_cvars_s, get_cvars_f, CAPTURE_VERBOSE, "notification_ctf_capture_verbose");
2020         GetCvars_handleFloat(get_cvars_s, get_cvars_f, PICKUP_TEAM_VERBOSE, "notification_ctf_pickup_team_verbose");
2021         GetCvars_handleFloat(get_cvars_s, get_cvars_f, PICKUP_ENEMY_VERBOSE, "notification_ctf_pickup_enemy_verbose");
2022         return TRUE;
2023 }
2024
2025
2026 // ==========
2027 // Spawnfuncs
2028 // ==========
2029
2030 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2031 CTF Starting point for a player in team one (Red).
2032 Keys: "angle" viewing angle when spawning. */
2033 void spawnfunc_info_player_team1()
2034 {
2035         if(g_assault) { remove(self); return; }
2036         
2037         self.team = FL_TEAM_1; // red
2038         spawnfunc_info_player_deathmatch();
2039 }
2040
2041
2042 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2043 CTF Starting point for a player in team two (Blue).
2044 Keys: "angle" viewing angle when spawning. */
2045 void spawnfunc_info_player_team2()
2046 {
2047         if(g_assault) { remove(self); return; }
2048         
2049         self.team = FL_TEAM_2; // blue
2050         spawnfunc_info_player_deathmatch();
2051 }
2052
2053 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2054 CTF Starting point for a player in team three (Yellow).
2055 Keys: "angle" viewing angle when spawning. */
2056 void spawnfunc_info_player_team3()
2057 {
2058         if(g_assault) { remove(self); return; }
2059         
2060         self.team = FL_TEAM_3; // yellow
2061         spawnfunc_info_player_deathmatch();
2062 }
2063
2064
2065 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2066 CTF Starting point for a player in team four (Purple).
2067 Keys: "angle" viewing angle when spawning. */
2068 void spawnfunc_info_player_team4()
2069 {
2070         if(g_assault) { remove(self); return; }
2071         
2072         self.team = FL_TEAM_4; // purple
2073         spawnfunc_info_player_deathmatch();
2074 }
2075
2076 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2077 CTF flag for team one (Red).
2078 Keys: 
2079 "angle" Angle the flag will point (minus 90 degrees)... 
2080 "model" model to use, note this needs red and blue as skins 0 and 1...
2081 "noise" sound played when flag is picked up...
2082 "noise1" sound played when flag is returned by a teammate...
2083 "noise2" sound played when flag is captured...
2084 "noise3" sound played when flag is lost in the field and respawns itself... 
2085 "noise4" sound played when flag is dropped by a player...
2086 "noise5" sound played when flag touches the ground... */
2087 void spawnfunc_item_flag_team1()
2088 {
2089         if(!g_ctf) { remove(self); return; }
2090
2091         ctf_FlagSetup(1, self); // 1 = red
2092 }
2093
2094 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2095 CTF flag for team two (Blue).
2096 Keys: 
2097 "angle" Angle the flag will point (minus 90 degrees)... 
2098 "model" model to use, note this needs red and blue as skins 0 and 1...
2099 "noise" sound played when flag is picked up...
2100 "noise1" sound played when flag is returned by a teammate...
2101 "noise2" sound played when flag is captured...
2102 "noise3" sound played when flag is lost in the field and respawns itself... 
2103 "noise4" sound played when flag is dropped by a player...
2104 "noise5" sound played when flag touches the ground... */
2105 void spawnfunc_item_flag_team2()
2106 {
2107         if(!g_ctf) { remove(self); return; }
2108
2109         ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2110 }
2111
2112 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2113 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2114 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.
2115 Keys:
2116 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2117 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2118 void spawnfunc_ctf_team()
2119 {
2120         if(!g_ctf) { remove(self); return; }
2121         
2122         self.classname = "ctf_team";
2123         self.team = self.cnt + 1;
2124 }
2125
2126 // compatibility for quake maps
2127 void spawnfunc_team_CTF_redflag()    { spawnfunc_item_flag_team1();    }
2128 void spawnfunc_team_CTF_blueflag()   { spawnfunc_item_flag_team2();    }
2129 void spawnfunc_team_CTF_redplayer()  { spawnfunc_info_player_team1();  }
2130 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2();  }
2131 void spawnfunc_team_CTF_redspawn()   { spawnfunc_info_player_team1();  }
2132 void spawnfunc_team_CTF_bluespawn()  { spawnfunc_info_player_team2();  }
2133
2134
2135 // ==============
2136 // Initialization
2137 // ==============
2138
2139 // scoreboard setup
2140 void ctf_ScoreRules()
2141 {
2142         ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2143         ScoreInfo_SetLabel_TeamScore  (ST_CTF_CAPS,     "caps",      SFL_SORT_PRIO_PRIMARY);
2144         ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS,     "caps",      SFL_SORT_PRIO_SECONDARY);
2145         ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME,  "captime",   SFL_LOWER_IS_BETTER | SFL_TIME);
2146         ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS,  "pickups",   0);
2147         ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS,  "fckills",   0);
2148         ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS,  "returns",   0);
2149         ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS,    "drops",     SFL_LOWER_IS_BETTER);
2150         ScoreRules_basics_end();
2151 }
2152
2153 // code from here on is just to support maps that don't have flag and team entities
2154 void ctf_SpawnTeam (string teamname, float teamcolor)
2155 {
2156         entity oldself;
2157         oldself = self;
2158         self = spawn();
2159         self.classname = "ctf_team";
2160         self.netname = teamname;
2161         self.cnt = teamcolor;
2162
2163         spawnfunc_ctf_team();
2164
2165         self = oldself;
2166 }
2167
2168 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2169 {
2170         // if no teams are found, spawn defaults
2171         if(find(world, classname, "ctf_team") == world)
2172         {
2173                 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2174                 ctf_SpawnTeam("Red", FL_TEAM_1 - 1);
2175                 ctf_SpawnTeam("Blue", FL_TEAM_2 - 1);
2176         }
2177         
2178         ctf_ScoreRules();
2179 }
2180
2181 void ctf_Initialize()
2182 {
2183         ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2184
2185         ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2186         ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2187         ctf_captureshield_force = autocvar_g_ctf_shield_force;
2188         
2189         InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2190 }
2191
2192
2193 MUTATOR_DEFINITION(gamemode_ctf)
2194 {
2195         MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2196         MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2197         MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2198         MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2199         MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2200         MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2201         MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2202         MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2203         MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2204         MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2205         MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2206         MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2207         MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2208         MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
2209         MUTATOR_HOOK(GetCvars, ctf_GetCvars, CBC_ORDER_ANY);
2210         
2211         MUTATOR_ONADD
2212         {
2213                 if(time > 1) // game loads at time 1
2214                         error("This is a game type and it cannot be added at runtime.");
2215                 ctf_Initialize();
2216         }
2217
2218         MUTATOR_ONROLLBACK_OR_REMOVE
2219         {
2220                 // we actually cannot roll back ctf_Initialize here
2221                 // BUT: we don't need to! If this gets called, adding always
2222                 // succeeds.
2223         }
2224
2225         MUTATOR_ONREMOVE
2226         {
2227                 print("This is a game type and it cannot be removed at runtime.");
2228                 return -1;
2229         }
2230
2231         return 0;
2232 }