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