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