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