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