]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_ctf.qc
Improve that check a little bit
[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         {
1744                 if(flag.pass_sender == self) { flag.pass_sender = world; }
1745                 if(flag.pass_target == self) { flag.pass_target = world; }
1746                 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1747         }
1748                 
1749         return FALSE;
1750 }
1751
1752 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1753 {
1754         if(self.flagcarried) 
1755         if(!autocvar_g_ctf_portalteleport)
1756                 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1757
1758         return FALSE;
1759 }
1760
1761 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1762 {
1763         if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1764         
1765         entity player = self;
1766
1767         if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1768         {
1769                 // pass the flag to a team mate
1770                 if(autocvar_g_ctf_pass)
1771                 {
1772                         entity head, closest_target;
1773                         head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1774                         
1775                         while(head) // find the closest acceptable target to pass to
1776                         {
1777                                 if(head.classname == "player" && head.deadflag == DEAD_NO)
1778                                 if(head != player && !IsDifferentTeam(head, player))
1779                                 if(!head.speedrunning && (!head.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1780                                 {
1781                                         if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried) 
1782                                         { 
1783                                                 if(clienttype(head) == CLIENTTYPE_BOT)
1784                                                 {
1785                                                         centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname)); 
1786                                                         ctf_Handle_Throw(head, player, DROP_PASS);
1787                                                 }
1788                                                 else
1789                                                 {
1790                                                         centerprint(head, strcat(player.netname, " requests you to pass the ", head.flagcarried.netname)); 
1791                                                         centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname)); 
1792                                                 }
1793                                                 player.throw_antispam = time + autocvar_g_ctf_pass_wait; 
1794                                                 return TRUE; 
1795                                         }
1796                                         else if(player.flagcarried)
1797                                         {
1798                                                 if(closest_target)
1799                                                 {
1800                                                         if(vlen(player.origin - WarpZone_UnTransformOrigin(head, head.origin)) < vlen(player.origin - WarpZone_UnTransformOrigin(closest_target, closest_target.origin)))
1801                                                                 { closest_target = head; }
1802                                                 }
1803                                                 else { closest_target = head; }
1804                                         }
1805                                 }
1806                                 head = head.chain;
1807                         }
1808                         
1809                         if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1810                 }
1811                 
1812                 // throw the flag in front of you
1813                 if(autocvar_g_ctf_drop && player.flagcarried)
1814                         { ctf_Handle_Throw(player, world, DROP_THROW); return TRUE; }
1815         }
1816                 
1817         return FALSE;
1818 }
1819
1820 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1821 {
1822         if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1823         {
1824                 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1825         } 
1826         else // create a normal help me waypointsprite
1827         {
1828                 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');
1829                 WaypointSprite_Ping(self.wps_helpme);
1830         }
1831
1832         return TRUE;
1833 }
1834
1835 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1836 {
1837         if(vh_player.flagcarried)
1838         {
1839                 if(!autocvar_g_ctf_allow_vehicle_carry)
1840                 {
1841                         ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1842                 }
1843                 else
1844                 {            
1845                         setattachment(vh_player.flagcarried, vh_vehicle, ""); 
1846                         setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1847                         vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1848                         //vh_player.flagcarried.angles = '0 0 0';       
1849                 }
1850                 return TRUE;
1851         }
1852                 
1853         return FALSE;
1854 }
1855
1856 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1857 {
1858         if(vh_player.flagcarried)
1859         {
1860                 setattachment(vh_player.flagcarried, vh_player, ""); 
1861                 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1862                 vh_player.flagcarried.scale = FLAG_SCALE;
1863                 vh_player.flagcarried.angles = '0 0 0';
1864                 return TRUE;
1865         }
1866
1867         return FALSE;
1868 }
1869
1870 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1871 {
1872         if(self.flagcarried)
1873         {
1874                 bprint("The ", self.flagcarried.netname, " was returned to base by its carrier\n");
1875                 ctf_RespawnFlag(self);
1876                 return TRUE;
1877         }
1878         
1879         return FALSE;
1880 }
1881
1882 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1883 {
1884         entity flag; // temporary entity for the search method
1885         
1886         for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1887         {
1888                 switch(flag.ctf_status)
1889                 {
1890                         case FLAG_DROPPED:
1891                         case FLAG_PASSING:
1892                         {
1893                                 // lock the flag, game is over
1894                                 flag.movetype = MOVETYPE_NONE;
1895                                 flag.takedamage = DAMAGE_NO;
1896                                 flag.solid = SOLID_NOT;
1897                                 flag.nextthink = FALSE; // stop thinking
1898                                 
1899                                 print("stopping the ", flag.netname, " from moving.\n");
1900                                 break;
1901                         }
1902                         
1903                         default:
1904                         case FLAG_BASE:
1905                         case FLAG_CARRY:
1906                         {
1907                                 // do nothing for these flags
1908                                 break;
1909                         }
1910                 }
1911         }
1912         
1913         return FALSE;
1914 }
1915
1916 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
1917 {
1918         havocbot_ctf_reset_role(self);
1919         return TRUE;
1920 }
1921
1922
1923 // ==========
1924 // Spawnfuncs
1925 // ==========
1926
1927 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
1928 CTF Starting point for a player in team one (Red).
1929 Keys: "angle" viewing angle when spawning. */
1930 void spawnfunc_info_player_team1()
1931 {
1932         if(g_assault) { remove(self); return; }
1933         
1934         self.team = COLOR_TEAM1; // red
1935         spawnfunc_info_player_deathmatch();
1936 }
1937
1938
1939 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
1940 CTF Starting point for a player in team two (Blue).
1941 Keys: "angle" viewing angle when spawning. */
1942 void spawnfunc_info_player_team2()
1943 {
1944         if(g_assault) { remove(self); return; }
1945         
1946         self.team = COLOR_TEAM2; // blue
1947         spawnfunc_info_player_deathmatch();
1948 }
1949
1950 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
1951 CTF Starting point for a player in team three (Yellow).
1952 Keys: "angle" viewing angle when spawning. */
1953 void spawnfunc_info_player_team3()
1954 {
1955         if(g_assault) { remove(self); return; }
1956         
1957         self.team = COLOR_TEAM3; // yellow
1958         spawnfunc_info_player_deathmatch();
1959 }
1960
1961
1962 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
1963 CTF Starting point for a player in team four (Purple).
1964 Keys: "angle" viewing angle when spawning. */
1965 void spawnfunc_info_player_team4()
1966 {
1967         if(g_assault) { remove(self); return; }
1968         
1969         self.team = COLOR_TEAM4; // purple
1970         spawnfunc_info_player_deathmatch();
1971 }
1972
1973 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
1974 CTF flag for team one (Red).
1975 Keys: 
1976 "angle" Angle the flag will point (minus 90 degrees)... 
1977 "model" model to use, note this needs red and blue as skins 0 and 1...
1978 "noise" sound played when flag is picked up...
1979 "noise1" sound played when flag is returned by a teammate...
1980 "noise2" sound played when flag is captured...
1981 "noise3" sound played when flag is lost in the field and respawns itself... 
1982 "noise4" sound played when flag is dropped by a player...
1983 "noise5" sound played when flag touches the ground... */
1984 void spawnfunc_item_flag_team1()
1985 {
1986         if(!g_ctf) { remove(self); return; }
1987
1988         ctf_FlagSetup(1, self); // 1 = red
1989 }
1990
1991 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
1992 CTF flag for team two (Blue).
1993 Keys: 
1994 "angle" Angle the flag will point (minus 90 degrees)... 
1995 "model" model to use, note this needs red and blue as skins 0 and 1...
1996 "noise" sound played when flag is picked up...
1997 "noise1" sound played when flag is returned by a teammate...
1998 "noise2" sound played when flag is captured...
1999 "noise3" sound played when flag is lost in the field and respawns itself... 
2000 "noise4" sound played when flag is dropped by a player...
2001 "noise5" sound played when flag touches the ground... */
2002 void spawnfunc_item_flag_team2()
2003 {
2004         if(!g_ctf) { remove(self); return; }
2005
2006         ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2007 }
2008
2009 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2010 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2011 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.
2012 Keys:
2013 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2014 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2015 void spawnfunc_ctf_team()
2016 {
2017         if(!g_ctf) { remove(self); return; }
2018         
2019         self.classname = "ctf_team";
2020         self.team = self.cnt + 1;
2021 }
2022
2023 // compatibility for quake maps
2024 void spawnfunc_team_CTF_redflag()    { spawnfunc_item_flag_team1();    }
2025 void spawnfunc_team_CTF_blueflag()   { spawnfunc_item_flag_team2();    }
2026 void spawnfunc_team_CTF_redplayer()  { spawnfunc_info_player_team1();  }
2027 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2();  }
2028 void spawnfunc_team_CTF_redspawn()   { spawnfunc_info_player_team1();  }
2029 void spawnfunc_team_CTF_bluespawn()  { spawnfunc_info_player_team2();  }
2030
2031
2032 // ==============
2033 // Initialization
2034 // ==============
2035
2036 // scoreboard setup
2037 void ctf_ScoreRules()
2038 {
2039         ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2040         ScoreInfo_SetLabel_TeamScore  (ST_CTF_CAPS,     "caps",      SFL_SORT_PRIO_PRIMARY);
2041         ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS,     "caps",      SFL_SORT_PRIO_SECONDARY);
2042         ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME,  "captime",   SFL_LOWER_IS_BETTER | SFL_TIME);
2043         ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS,  "pickups",   0);
2044         ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS,  "fckills",   0);
2045         ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS,  "returns",   0);
2046         ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS,    "drops",     SFL_LOWER_IS_BETTER);
2047         ScoreRules_basics_end();
2048 }
2049
2050 // code from here on is just to support maps that don't have flag and team entities
2051 void ctf_SpawnTeam (string teamname, float teamcolor)
2052 {
2053         entity oldself;
2054         oldself = self;
2055         self = spawn();
2056         self.classname = "ctf_team";
2057         self.netname = teamname;
2058         self.cnt = teamcolor;
2059
2060         spawnfunc_ctf_team();
2061
2062         self = oldself;
2063 }
2064
2065 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2066 {
2067         // if no teams are found, spawn defaults
2068         if(find(world, classname, "ctf_team") == world)
2069         {
2070                 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2071                 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
2072                 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
2073         }
2074         
2075         ctf_ScoreRules();
2076 }
2077
2078 void ctf_Initialize()
2079 {
2080         ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2081
2082         ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2083         ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2084         ctf_captureshield_force = autocvar_g_ctf_shield_force;
2085         
2086         InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2087 }
2088
2089
2090 MUTATOR_DEFINITION(gamemode_ctf)
2091 {
2092         MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2093         MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2094         MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2095         MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2096         MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2097         MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2098         MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2099         MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2100         MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2101         MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2102         MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2103         MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2104         MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2105         MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
2106         
2107         MUTATOR_ONADD
2108         {
2109                 if(time > 1) // game loads at time 1
2110                         error("This is a game type and it cannot be added at runtime.");
2111                 g_ctf = 1;
2112                 ctf_Initialize();
2113         }
2114
2115         MUTATOR_ONREMOVE
2116         {
2117                 g_ctf = 0;
2118                 error("This is a game type and it cannot be removed at runtime.");
2119         }
2120
2121         return 0;
2122 }