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