]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_ctf.qc
Automatically do the "help me" ping if flag carrier gets damaged. TODO: make it based...
[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: March 30th, 2012
4 // ================================================================
5
6 float ctf_ReadScore(string parameter) // make this obsolete
7 {
8         //if(g_ctf_win_mode != 2)
9                 return cvar(strcat("g_ctf_personal", parameter));
10         //else
11         //      return cvar(strcat("g_ctf_flag", parameter));
12 }
13
14 void ctf_FakeTimeLimit(entity e, float t)
15 {
16         msg_entity = e;
17         WriteByte(MSG_ONE, 3); // svc_updatestat
18         WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
19         if(t < 0)
20                 WriteCoord(MSG_ONE, autocvar_timelimit);
21         else
22                 WriteCoord(MSG_ONE, (t + 1) / 60);
23 }
24
25 void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
26 {
27         if(autocvar_sv_eventlog)
28                 GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
29 }
30
31 string ctf_CaptureRecord(entity flag, entity player)
32 {
33         float cap_time, cap_record, success;
34         string cap_message, refername;
35         
36         if((autocvar_g_ctf_captimerecord_always) || (player_count - currentbots)) 
37         {
38                 cap_record = ctf_captimerecord;
39                 cap_time = (time - flag.ctf_pickuptime);
40
41                 refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
42                 refername = ((refername == player.netname) ? "their" : strcat(refername, "^7's"));
43
44                 if(!ctf_captimerecord) 
45                         { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds"); success = TRUE; }
46                 else if(cap_time < cap_record) 
47                         { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, breaking ", refername, " previous record of ", ftos_decimals(cap_record, 2), " seconds"); success = TRUE; }
48                 else
49                         { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, failing to break ", refername, " record of ", ftos_decimals(cap_record, 2), " seconds"); success = FALSE; }
50
51                 if(success) 
52                 {
53                         ctf_captimerecord = cap_time;
54                         db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
55                         db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
56                         write_recordmarker(player, (time - cap_time), cap_time); 
57                 } 
58         }
59         
60         return cap_message;
61 }
62
63 void ctf_FlagcarrierWaypoints(entity player)
64 {
65         WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
66         WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
67         WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
68         WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
69 }
70
71
72 // =======================
73 // CaptureShield Functions 
74 // =======================
75
76 float ctf_CaptureShield_CheckStatus(entity p) 
77 {
78         float s, se;
79         entity e;
80         float players_worseeq, players_total;
81
82         if(ctf_captureshield_max_ratio <= 0)
83                 return FALSE;
84
85         s = PlayerScore_Add(p, SP_SCORE, 0);
86         if(s >= -ctf_captureshield_min_negscore)
87                 return FALSE;
88
89         players_total = players_worseeq = 0;
90         FOR_EACH_PLAYER(e)
91         {
92                 if(IsDifferentTeam(e, p))
93                         continue;
94                 se = PlayerScore_Add(e, SP_SCORE, 0);
95                 if(se <= s)
96                         ++players_worseeq;
97                 ++players_total;
98         }
99
100         // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
101         // use this rule here
102         
103         if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
104                 return FALSE;
105
106         return TRUE;
107 }
108
109 void ctf_CaptureShield_Update(entity player, float wanted_status)
110 {
111         float updated_status = ctf_CaptureShield_CheckStatus(player);
112         if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
113         {
114                 if(updated_status) // TODO csqc notifier for this // Samual: How?
115                         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);
116                 else
117                         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);
118                         
119                 player.ctf_captureshielded = updated_status;
120         }
121 }
122
123 float ctf_CaptureShield_Customize()
124 {
125         if(!other.ctf_captureshielded) { return FALSE; }
126         if(!IsDifferentTeam(self, other)) { return FALSE; }
127         
128         return TRUE;
129 }
130
131 void ctf_CaptureShield_Touch()
132 {
133         if(!other.ctf_captureshielded) { return; }
134         if(!IsDifferentTeam(self, other)) { return; }
135         
136         vector mymid = (self.absmin + self.absmax) * 0.5;
137         vector othermid = (other.absmin + other.absmax) * 0.5;
138
139         Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
140         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);
141 }
142
143 void ctf_CaptureShield_Spawn(entity flag)
144 {
145         entity shield = spawn();
146         
147         shield.enemy = self;
148         shield.team = self.team;
149         shield.touch = ctf_CaptureShield_Touch;
150         shield.customizeentityforclient = ctf_CaptureShield_Customize;
151         shield.classname = "ctf_captureshield";
152         shield.effects = EF_ADDITIVE;
153         shield.movetype = MOVETYPE_NOCLIP;
154         shield.solid = SOLID_TRIGGER;
155         shield.avelocity = '7 0 11';
156         shield.scale = 0.5;
157         
158         setorigin(shield, self.origin);
159         setmodel(shield, "models/ctf/shield.md3");
160         setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
161 }
162
163
164 // ====================
165 // Drop/Pass/Throw Code
166 // ====================
167
168 void ctf_Handle_Drop(entity flag, entity player, float droptype)
169 {
170         // declarations
171         player = (player ? player : flag.pass_sender);
172
173         // main
174         flag.movetype = MOVETYPE_TOSS;
175         flag.takedamage = DAMAGE_YES;
176         flag.health = flag.max_flag_health;
177         flag.ctf_droptime = time;
178         flag.ctf_dropper = player;
179         flag.ctf_status = FLAG_DROPPED;
180         
181         // messages and sounds
182         Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
183         sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
184         ctf_EventLog("dropped", player.team, player);
185
186         // scoring
187         PlayerTeamScore_AddScore(player, -ctf_ReadScore("penalty_drop"));       
188         PlayerScore_Add(player, SP_CTF_DROPS, 1);
189         
190         // waypoints
191         if(autocvar_g_ctf_flag_dropped_waypoint)
192                 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));
193
194         if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
195         {
196                 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
197                 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
198         }
199         
200         player.throw_antispam = time + autocvar_g_ctf_pass_wait;
201         
202         if(droptype == DROP_PASS)
203         {
204                 flag.pass_sender = world;
205                 flag.pass_target = world;
206         }
207 }
208
209 void ctf_Handle_Retrieve(entity flag, entity player)
210 {
211         entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
212         entity sender = flag.pass_sender;
213         
214         // transfer flag to player
215         flag.ctf_carrier = player;
216         flag.owner = player;
217         flag.owner.flagcarried = flag;
218         
219         // reset flag
220         setattachment(flag, player, "");
221         setorigin(flag, FLAG_CARRY_OFFSET);
222         flag.movetype = MOVETYPE_NONE;
223         flag.takedamage = DAMAGE_NO;
224         flag.solid = SOLID_NOT;
225         flag.ctf_carrier = player;
226         flag.ctf_status = FLAG_CARRY;
227
228         // messages and sounds
229         sound(player, CH_TRIGGER, "keepaway/respawn.wav", VOL_BASE, ATTN_NORM); // FIXCTF
230         ctf_EventLog("recieve", flag.team, player);
231         
232         FOR_EACH_REALPLAYER(tmp_player)
233         {
234                 if(tmp_player == sender)
235                         centerprint(tmp_player, strcat("You passed the ", flag.netname, " to ", player.netname));
236                 else if(tmp_player == player)
237                         centerprint(tmp_player, strcat("You recieved the ", flag.netname, " from ", sender.netname));
238                 else if(!IsDifferentTeam(tmp_player, sender))
239                         centerprint(tmp_player, strcat(sender.netname, " passed the ", flag.netname, " to ", player.netname));
240         }
241         
242         // create new waypoint
243         ctf_FlagcarrierWaypoints(player);
244         
245         sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
246         player.throw_antispam = sender.throw_antispam;
247         
248         flag.pass_sender = world;
249         flag.pass_target = world;
250 }
251
252 void ctf_Handle_Throw(entity player, entity reciever, float droptype)
253 {
254         entity flag = player.flagcarried;
255         
256         if(!flag) { return; }
257         if((droptype == DROP_PASS) && !reciever) { return; }
258         
259         if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
260         
261         // reset the flag
262         setattachment(flag, world, "");
263         setorigin(flag, player.origin + FLAG_DROP_OFFSET);
264         flag.owner.flagcarried = world;
265         flag.owner = world;
266         flag.solid = SOLID_TRIGGER;
267         flag.ctf_droptime = time;
268         
269         flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
270         
271         switch(droptype)
272         {
273                 case DROP_PASS:
274                 {
275                         vector targ_origin = (0.5 * (reciever.absmin + reciever.absmax));
276                         flag.velocity = (normalize(targ_origin - player.origin) * autocvar_g_ctf_pass_velocity);
277                         break;
278                 }
279                 
280                 case DROP_THROW:
281                 {
282                         makevectors((player.v_angle_y * '0 1 0') + (player.v_angle_x * '0.5 0 0'));
283                         flag.velocity = W_CalculateProjectileVelocity(player.velocity, ('0 0 200' + (v_forward * autocvar_g_ctf_drop_velocity)), FALSE);
284                         break;
285                 }
286                 
287                 case DROP_RESET:
288                 {
289                         flag.velocity = '0 0 0'; // do nothing
290                         break;
291                 }
292                 
293                 default:
294                 case DROP_NORMAL:
295                 {
296                         flag.velocity = W_CalculateProjectileVelocity(player.velocity, ('0 0 200' + ('0 100 0' * crandom()) + ('100 0 0' * crandom())), FALSE);
297                         break;
298                 }
299         }
300         
301         switch(droptype)
302         {
303                 case DROP_PASS:
304                 {
305                         // main
306                         flag.movetype = MOVETYPE_FLY;
307                         flag.takedamage = DAMAGE_NO;
308                         flag.pass_sender = player;
309                         flag.pass_target = reciever;
310                         flag.ctf_status = FLAG_PASSING;
311                         
312                         // other
313                         sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTN_NORM);
314                         ctf_EventLog("pass", flag.team, player);
315                         te_lightning2(world, reciever.origin, player.origin);
316                         break;
317                 }
318
319                 case DROP_RESET: 
320                 {
321                         // do nothing
322                         break;
323                 }
324                 
325                 default:
326                 case DROP_THROW:
327                 case DROP_NORMAL:
328                 {
329                         ctf_Handle_Drop(flag, player, droptype);
330                         break;
331                 }
332         }
333
334         // kill old waypointsprite
335         WaypointSprite_Ping(player.wps_flagcarrier);
336         WaypointSprite_Kill(player.wps_flagcarrier);
337         
338         if(player.wps_enemyflagcarrier)
339                         WaypointSprite_Kill(player.wps_enemyflagcarrier);
340         
341         // captureshield
342         ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
343 }
344
345
346 // ==============
347 // Event Handlers
348 // ==============
349
350 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
351 {
352         entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
353         entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
354         
355         if not(player) { return; } // without someone to give the reward to, we can't possibly cap
356         
357         // messages and sounds
358         Send_KillNotification(player.netname, enemy_flag.netname, ctf_CaptureRecord(enemy_flag, player), INFO_CAPTUREFLAG, MSG_INFO);
359         sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
360         
361         switch(capturetype)
362         {
363                 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
364                 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
365                 default: break;
366         }
367         
368         // scoring
369         PlayerTeamScore_AddScore(player, ctf_ReadScore("score_capture"));
370         PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
371
372         // effects
373         if(autocvar_g_ctf_flag_capture_effects) 
374         {
375                 pointparticles(particleeffectnum((player.team == COLOR_TEAM1) ? "red_ground_quake" : "blue_ground_quake"), flag.origin, '0 0 0', 1);
376                 shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
377         }
378
379         // other
380         if(capturetype == CAPTURE_NORMAL)
381         {
382                 WaypointSprite_Kill(player.wps_flagcarrier);
383                 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
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, ctf_ReadScore("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, -ctf_ReadScore("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, -ctf_ReadScore("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_carrier = player;
435         flag.ctf_status = FLAG_CARRY;
436         
437         switch(pickuptype)
438         {
439                 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
440                 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
441                 default: break;
442         }
443
444         // messages and sounds
445         Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
446         sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
447         verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat(Team_ColorCode(player.team), "(^7", player.netname, Team_ColorCode(player.team), ") ") : "");
448         
449         FOR_EACH_REALPLAYER(tmp_player)
450         {
451                 if(tmp_player == player)
452                         centerprint(tmp_player, strcat("You got the ", flag.netname, "!"));
453                 else if(!IsDifferentTeam(tmp_player, player))
454                         centerprint(tmp_player, strcat("Your ", Team_ColorCode(player.team), "team mate ", verbosename, "^7got the flag! Protect them!"));
455                 else if(!IsDifferentTeam(tmp_player, flag))
456                         centerprint(tmp_player, strcat("The ", Team_ColorCode(player.team), "enemy ", verbosename, "^7got your flag! Retrieve it!"));
457         }
458                 
459         switch(pickuptype)
460         {
461                 case PICKUP_BASE: ctf_EventLog("steal", flag.team, player); break;
462                 case PICKUP_DROPPED: ctf_EventLog("pickup", flag.team, player); break;
463                 default: break;
464         }
465         
466         // scoring
467         PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
468         switch(pickuptype)
469         {               
470                 case PICKUP_BASE:
471                 {
472                         PlayerTeamScore_AddScore(player, ctf_ReadScore("score_pickup_base"));
473                         break;
474                 }
475                 
476                 case PICKUP_DROPPED:
477                 {
478                         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);
479                         pickup_dropped_score = floor((ctf_ReadScore("score_pickup_dropped_late") * (1 - pickup_dropped_score) + ctf_ReadScore("score_pickup_dropped_early") * pickup_dropped_score) + 0.5);
480                         print("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
481                         PlayerTeamScore_AddScore(player, pickup_dropped_score);
482                         break;
483                 }
484                 
485                 default: break;
486         }
487         
488         // speedrunning
489         if(pickuptype == PICKUP_BASE)
490         {
491                 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
492                 if((player.speedrunning) && (ctf_captimerecord))
493                         ctf_FakeTimeLimit(player, time + ctf_captimerecord);
494         }
495                 
496         // effects
497         if(autocvar_g_ctf_flag_pickup_effects)
498                 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
499         
500         // waypoints 
501         if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
502         ctf_FlagcarrierWaypoints(player);
503         WaypointSprite_Ping(player.wps_flagcarrier);
504 }
505
506
507 // ===================
508 // Main Flag Functions
509 // ===================
510
511 void ctf_CheckFlagReturn(entity flag, float returntype)
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 void ctf_CheckStalemate(void)
535 {
536         // declarations
537         float stale_red_flags, stale_blue_flags;
538         entity tmp_entity;
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(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
545                 {
546                         tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
547                         ctf_staleflaglist = tmp_entity;
548                         
549                         switch(tmp_entity.team)
550                         {
551                                 case COLOR_TEAM1: ++stale_red_flags; break;
552                                 case COLOR_TEAM2: ++stale_blue_flags; break;
553                         }
554                 }
555         }
556         
557         // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
558         if(stale_red_flags && stale_blue_flags)
559         {
560                 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
561                 {
562                         if not(tmp_entity.owner.wps_enemyflagcarrier)   
563                                 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));
564                 }
565                 
566                 if not(wpforenemy_announced)
567                 {
568                         FOR_EACH_REALPLAYER(tmp_entity)
569                         {
570                                 if(tmp_entity.flagcarried)
571                                         centerprint(tmp_entity, "Stalemate! Enemies can now see you on radar!");
572                                 else
573                                         centerprint(tmp_entity, "Stalemate! Flag carriers can now be seen by enemies on radar!");
574                         }
575                         
576                         wpforenemy_announced = TRUE;
577                 }
578         }
579 }
580
581 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
582 {
583         if(ITEM_DAMAGE_NEEDKILL(deathtype))
584         {
585                 // automatically kill the flag and return it
586                 self.health = 0;
587                 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
588                 return;
589         }
590         
591         if(autocvar_g_ctf_flag_return_damage) 
592         {
593                 // reduce health and check if it should be returned
594                 self.health = self.health - damage;
595                 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
596                 return;
597         }
598 }
599
600 void ctf_FlagThink()
601 {
602         // declarations
603         entity tmp_entity;
604
605         self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
606
607         // captureshield
608         if(self == ctf_worldflaglist) // only for the first flag
609                 FOR_EACH_CLIENT(tmp_entity)
610                         ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
611
612         // sanity checks
613         if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
614                 dprint("wtf the flag got squashed?\n");
615                 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
616                 if(!trace_startsolid) // can we resize it without getting stuck?
617                         setsize(self, FLAG_MIN, FLAG_MAX); }
618                         
619         switch(self.ctf_status) // reset flag angles in case warpzones adjust it
620         {
621                 case FLAG_DROPPED:
622                 case FLAG_PASSING:
623                 {
624                         self.angles = '0 0 0';
625                         break;
626                 }
627                 
628                 default: break;
629         }
630
631         // main think method
632         switch(self.ctf_status)
633         {       
634                 case FLAG_BASE:
635                 {
636                         if(autocvar_g_ctf_dropped_capture_radius)
637                         {
638                                 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
639                                         if(tmp_entity.ctf_status == FLAG_DROPPED)
640                                                 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
641                                                         ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
642                         }
643                         return;
644                 }
645                 
646                 case FLAG_DROPPED:
647                 {
648                         if(autocvar_g_ctf_flag_dropped_floatinwater && (self.flags & FL_INWATER))
649                                         self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater;
650                 
651                         if(autocvar_g_ctf_flag_return_dropped)
652                         {
653                                 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
654                                 {
655                                         self.health = 0;
656                                         ctf_CheckFlagReturn(self, RETURN_DROPPED);
657                                         return;
658                                 }
659                         }
660                         if(autocvar_g_ctf_flag_return_time)
661                         {
662                                 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
663                                 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
664                                 return;
665                         } 
666                         return;
667                 }
668                         
669                 case FLAG_CARRY:
670                 {
671                         if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord)) 
672                         {
673                                 self.health = 0;
674                                 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
675
676                                 tmp_entity = self;
677                                 self = self.owner;
678                                 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
679                                 ImpulseCommands();
680                                 self = tmp_entity;
681                         }
682                         if(autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
683                         {
684                                 if(time >= wpforenemy_nextthink)
685                                 {
686                                         ctf_CheckStalemate();
687                                         wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
688                                 }
689                         }
690                         return;
691                 }
692                 
693                 case FLAG_PASSING: // todo make work with warpzones
694                 {                       
695                         vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
696                         traceline(self.origin, targ_origin, MOVE_NOMONSTERS, self);
697                         
698                         if((self.pass_target.deadflag != DEAD_NO)
699                                 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
700                                 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
701                                 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
702                         {
703                                 ctf_Handle_Drop(self, world, DROP_PASS);
704                         }
705                         else // still a viable target, go for it
706                         {
707                                 vector desired_direction = normalize(targ_origin - self.origin);
708                                 vector current_direction = normalize(self.velocity);
709                                 
710                                 self.velocity = (normalize(current_direction + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); 
711                         }
712                         return;
713                 }
714
715                 default: // this should never happen
716                 {
717                         dprint("ctf_FlagThink(): Flag exists with no status?\n");
718                         return;
719                 }
720         }
721 }
722
723 void ctf_FlagTouch()
724 {
725         if(gameover) { return; }
726         
727         entity toucher = other;
728         
729         // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
730         if(ITEM_TOUCH_NEEDKILL())
731         {
732                 self.health = 0;
733                 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
734                 return;
735         }
736         
737         // special touch behaviors
738         if(toucher.vehicle_flags & VHF_ISVEHICLE)
739         {
740                 if(autocvar_g_ctf_allow_vehicle_touch)
741                         toucher = toucher.owner; // the player is actually the vehicle owner, not other
742                 else
743                         return; // do nothing
744         }
745         else if(toucher.classname != "player") // The flag just touched an object, most likely the world
746         {
747                 if(time > self.wait) // if we haven't in a while, play a sound/effect
748                 {
749                         pointparticles(particleeffectnum("kaball_sparks"), self.origin, '0 0 0', 1);
750                         sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
751                         self.wait = time + FLAG_TOUCHRATE;
752                 }
753                 return;
754         }
755         else if(toucher.deadflag != DEAD_NO) { return; }
756
757         switch(self.ctf_status) 
758         {       
759                 case FLAG_BASE:
760                 {
761                         if(!IsDifferentTeam(toucher, self) && (toucher.flagcarried) && IsDifferentTeam(toucher.flagcarried, self))
762                                 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
763                         else if(IsDifferentTeam(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
764                                 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
765                         break;
766                 }
767                 
768                 case FLAG_DROPPED:
769                 {
770                         if(!IsDifferentTeam(toucher, self))
771                                 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
772                         else if((!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
773                                 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
774                         break;
775                 }
776                         
777                 case FLAG_CARRY:
778                 {
779                         dprint("Someone touched a flag even though it was being carried?\n");
780                         break;
781                 }
782                 
783                 case FLAG_PASSING:
784                 {
785                         if((toucher.classname == "player") && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
786                         {
787                                 if(IsDifferentTeam(toucher, self.pass_sender))
788                                         ctf_Handle_Return(self, toucher);
789                                 else
790                                         ctf_Handle_Retrieve(self, toucher);
791                         }
792                         break;
793                 }
794                 
795                 default: // this should never happen
796                 {
797                         dprint("Touch: Flag exists with no status?\n");
798                         break;
799                 }
800         }
801 }
802
803 void ctf_RespawnFlag(entity flag)
804 {
805         // reset the player (if there is one)
806         if((flag.owner) && (flag.owner.flagcarried == flag))
807         {
808                 if(flag.owner.wps_enemyflagcarrier)
809                         WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
810                         
811                 WaypointSprite_Kill(flag.wps_flagcarrier);
812                 
813                 flag.owner.flagcarried = world;
814
815                 if(flag.speedrunning)
816                         ctf_FakeTimeLimit(flag.owner, -1);
817         }
818
819         if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
820                 { WaypointSprite_Kill(flag.wps_flagdropped); }
821
822         // reset the flag
823         setattachment(flag, world, "");
824         setorigin(flag, flag.ctf_spawnorigin);
825         
826         flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
827         flag.takedamage = DAMAGE_NO;
828         flag.health = flag.max_flag_health;
829         flag.solid = SOLID_TRIGGER;
830         flag.velocity = '0 0 0';
831         flag.angles = flag.mangle;
832         flag.flags = FL_ITEM | FL_NOTARGET;
833         
834         flag.ctf_status = FLAG_BASE;
835         flag.owner = world;
836         flag.pass_sender = world;
837         flag.pass_target = world;
838         flag.ctf_carrier = world;
839         flag.ctf_dropper = world;
840         flag.ctf_pickuptime = 0;
841         flag.ctf_droptime = 0;
842
843         wpforenemy_announced = FALSE;
844 }
845
846 void ctf_Reset()
847 {
848         if(self.owner)
849                 if(self.owner.classname == "player")
850                         ctf_Handle_Throw(self.owner, world, DROP_RESET);
851                         
852         ctf_RespawnFlag(self);
853 }
854
855 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
856 {
857         // bot waypoints
858         waypoint_spawnforitem_force(self, self.origin);
859         self.nearestwaypointtimeout = 0; // activate waypointing again
860         self.bot_basewaypoint = self.nearestwaypoint;
861
862         // waypointsprites
863         WaypointSprite_SpawnFixed(((self.team == COLOR_TEAM1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
864         WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
865
866         // captureshield setup
867         ctf_CaptureShield_Spawn(self);
868 }
869
870 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc 
871 {       
872         // declarations
873         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. 
874         self = flag; // for later usage with droptofloor()
875         
876         // main setup
877         flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
878         ctf_worldflaglist = flag;
879
880         setattachment(flag, world, ""); 
881
882         flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
883         flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
884         flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
885         flag.classname = "item_flag_team";
886         flag.target = "###item###"; // wut?
887         flag.flags = FL_ITEM | FL_NOTARGET;
888         flag.solid = SOLID_TRIGGER;
889         flag.takedamage = DAMAGE_NO;
890         flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;   
891         flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
892         flag.health = flag.max_flag_health;
893         flag.event_damage = ctf_FlagDamage;
894         flag.pushable = TRUE;
895         flag.teleportable = TELEPORT_NORMAL;
896         flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
897         flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
898         flag.velocity = '0 0 0';
899         flag.mangle = flag.angles;
900         flag.reset = ctf_Reset;
901         flag.touch = ctf_FlagTouch;
902         flag.think = ctf_FlagThink;
903         flag.nextthink = time + FLAG_THINKRATE;
904         flag.ctf_status = FLAG_BASE;
905         
906         if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
907         if(!flag.scale) { flag.scale = FLAG_SCALE; }
908         if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
909         
910         // sound 
911         if(!flag.snd_flag_taken) { flag.snd_flag_taken  = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
912         if(!flag.snd_flag_returned) { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
913         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
914         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.
915         if(!flag.snd_flag_dropped) { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
916         if(!flag.snd_flag_touch) { flag.snd_flag_touch = "keepaway/touch.wav"; } // again has no team-based sound // FIXME
917         
918         // precache
919         precache_sound(flag.snd_flag_taken);
920         precache_sound(flag.snd_flag_returned);
921         precache_sound(flag.snd_flag_capture);
922         precache_sound(flag.snd_flag_respawn);
923         precache_sound(flag.snd_flag_dropped);
924         precache_sound(flag.snd_flag_touch);
925         precache_model(flag.model);
926         precache_model("models/ctf/shield.md3");
927         precache_model("models/ctf/shockwavetransring.md3");
928
929         // appearence
930         setmodel(flag, flag.model); // precision set below
931         setsize(flag, FLAG_MIN, FLAG_MAX);
932         setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
933         
934         if(autocvar_g_ctf_flag_glowtrails)
935         {
936                 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
937                 flag.glow_size = 25;
938                 flag.glow_trail = 1;
939         }
940         
941         flag.effects |= EF_LOWPRECISION;
942         if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
943         if(autocvar_g_ctf_dynamiclights)   { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
944         
945         // flag placement
946         if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
947         {       
948                 flag.dropped_origin = flag.origin; 
949                 flag.noalign = TRUE;
950                 flag.movetype = MOVETYPE_NONE;
951         }
952         else // drop to floor, automatically find a platform and set that as spawn origin
953         { 
954                 flag.noalign = FALSE;
955                 self = flag;
956                 droptofloor();
957                 flag.movetype = MOVETYPE_TOSS; 
958         }       
959         
960         InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
961 }
962
963
964 // ==============
965 // Hook Functions
966 // ==============
967
968 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
969 {
970         entity flag;
971         
972         // initially clear items so they can be set as necessary later.
973         self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST 
974                 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
975
976         // scan through all the flags and notify the client about them 
977         for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
978         {
979                 if(flag.ctf_status == FLAG_CARRY)
980                         if(flag.owner == self)
981                                 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
982                         else 
983                                 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
984                 else if(flag.ctf_status == FLAG_DROPPED) 
985                         self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
986         }
987         
988         // item for stopping players from capturing the flag too often
989         if(self.ctf_captureshielded)
990                 self.items |= IT_CTF_SHIELDED;
991         
992         // update the health of the flag carrier waypointsprite
993         if(self.wps_flagcarrier) 
994                 WaypointSprite_UpdateHealth(self.wps_flagcarrier, self.health);
995         
996         return 0;
997 }
998
999 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1000 {
1001         if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1002         {
1003                 if(frag_target == frag_attacker) // damage done to yourself
1004                 {
1005                         frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1006                         frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1007                 }
1008                 else // damage done to everyone else
1009                 {
1010                         frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1011                         frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1012                 }
1013         }
1014         else if(frag_target.flagcarried) // if the target is a flagcarrier
1015         {
1016                 if(autocvar_g_ctf_flagcarrier_auto_helpme_when_damaged)
1017                         WaypointSprite_HelpMePing(self.wps_flagcarrier); // TODO: only do this if there is a significant loss of health?
1018         }
1019         return 0;
1020 }
1021
1022 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1023 {
1024         if((frag_attacker != frag_target) && (frag_attacker.classname == "player") && (frag_target.flagcarried))
1025         {
1026                 PlayerTeamScore_AddScore(frag_attacker, ctf_ReadScore("score_kill"));
1027                 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1028         }
1029                                 
1030         if(frag_target.flagcarried)
1031                 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1032                 
1033         return 0;
1034 }
1035
1036 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1037 {
1038         frag_score = 0;
1039         return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1040 }
1041
1042 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1043 {
1044         if(self.flagcarried)
1045                 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1046                 
1047         return 0;
1048 }
1049
1050 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1051 {
1052         if(self.flagcarried) 
1053         if(!autocvar_g_ctf_portalteleport)
1054                 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1055
1056         return 0;
1057 }
1058
1059 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1060 {
1061         entity player = self;
1062
1063         if((time > player.throw_antispam) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1064         {
1065                 // pass the flag to a team mate
1066                 if(autocvar_g_ctf_pass)
1067                 {
1068                         entity head, closest_target;
1069                         head = findradius(player.origin, autocvar_g_ctf_pass_radius);
1070                         
1071                         while(head) // find the closest acceptable target to pass to
1072                         {
1073                                 if(head.classname == "player" && head.deadflag == DEAD_NO)
1074                                 if(head != player && !IsDifferentTeam(head, player))
1075                                 if(!head.speedrunning && (!head.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1076                                 {
1077                                         traceline(player.origin, head.origin, MOVE_NOMONSTERS, player);
1078                                         if not((trace_fraction < 1) && (trace_ent != head))
1079                                         {
1080                                                 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried) 
1081                                                 { 
1082                                                         if(clienttype(head) == CLIENTTYPE_BOT)
1083                                                         {
1084                                                                 centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname)); 
1085                                                                 ctf_Handle_Throw(head, player, DROP_PASS);
1086                                                         }
1087                                                         else
1088                                                         {
1089                                                                 centerprint(head, strcat(player.netname, " requests you to pass the ", head.flagcarried.netname)); 
1090                                                                 centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname)); 
1091                                                         }
1092                                                         player.throw_antispam = time + autocvar_g_ctf_pass_wait; 
1093                                                         return 0; 
1094                                                 }
1095                                                 else if(player.flagcarried)
1096                                                 {
1097                                                         if(closest_target) { if(vlen(player.origin - head.origin) < vlen(player.origin - closest_target.origin)) { closest_target = head; } }
1098                                                         else { closest_target = head; }
1099                                                 }
1100                                         }
1101                                 }
1102                                 head = head.chain;
1103                         }
1104                         
1105                         if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return 0; }
1106                 }
1107                 
1108                 // throw the flag in front of you
1109                 if(autocvar_g_ctf_drop && player.flagcarried)
1110                         { ctf_Handle_Throw(player, world, DROP_THROW); }
1111         }
1112                 
1113         return 0;
1114 }
1115
1116 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1117 {
1118         if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1119         {
1120                 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1121         } 
1122         else // create a normal help me waypointsprite
1123         {
1124                 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');
1125                 WaypointSprite_Ping(self.wps_helpme);
1126         }
1127
1128         return 1;
1129 }
1130
1131 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1132 {
1133         if(other.flagcarried)
1134         {
1135                 if(!autocvar_g_ctf_flagcarrier_allow_vehicle_carry)
1136                 {
1137                         ctf_Handle_Throw(self, world, DROP_NORMAL);
1138                 }
1139                 else
1140                 {            
1141                         setattachment(other.flagcarried, self, ""); 
1142                         setorigin(other, VEHICLE_FLAG_OFFSET);
1143                         other.flagcarried.scale = VEHICLE_FLAG_SCALE;
1144                         //other.flagcarried.angles = '0 0 0';   
1145                 }
1146         }
1147                 
1148         return 0;
1149 }
1150
1151 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1152 {
1153         if(self.owner.flagcarried)
1154         {
1155                 setattachment(self.owner.flagcarried, self.owner, ""); 
1156                 setorigin(self.owner.flagcarried, FLAG_CARRY_OFFSET);
1157                 self.owner.flagcarried.scale = FLAG_SCALE;
1158                 self.owner.flagcarried.angles = '0 0 0';        
1159         }
1160
1161         return 0;
1162 }
1163
1164 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1165 {
1166         if(self.flagcarried)
1167         {
1168                 bprint("The ", self.flagcarried.netname, " was returned to base by its carrier\n");
1169                 ctf_RespawnFlag(self);
1170         }
1171         
1172         return 0;
1173 }
1174
1175 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1176 {
1177         entity flag; // temporary entity for the search method
1178         
1179         for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1180         {
1181                 switch(flag.ctf_status)
1182                 {
1183                         case FLAG_DROPPED:
1184                         case FLAG_PASSING:
1185                         {
1186                                 // lock the flag, game is over
1187                                 flag.movetype = MOVETYPE_NONE;
1188                                 flag.takedamage = DAMAGE_NO;
1189                                 flag.solid = SOLID_NOT;
1190                                 flag.nextthink = 0; // stop thinking
1191                                 
1192                                 print("stopping the ", flag.netname, " from moving.\n");
1193                                 break;
1194                         }
1195                         
1196                         default:
1197                         case FLAG_BASE:
1198                         case FLAG_CARRY:
1199                         {
1200                                 // do nothing for these flags
1201                                 break;
1202                         }
1203                 }
1204         }
1205         
1206         return 0;
1207 }
1208
1209
1210 // ==========
1211 // Spawnfuncs
1212 // ==========
1213
1214 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
1215 CTF Starting point for a player in team one (Red).
1216 Keys: "angle" viewing angle when spawning. */
1217 void spawnfunc_info_player_team1()
1218 {
1219         if(g_assault) { remove(self); return; }
1220         
1221         self.team = COLOR_TEAM1; // red
1222         spawnfunc_info_player_deathmatch();
1223 }
1224
1225
1226 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
1227 CTF Starting point for a player in team two (Blue).
1228 Keys: "angle" viewing angle when spawning. */
1229 void spawnfunc_info_player_team2()
1230 {
1231         if(g_assault) { remove(self); return; }
1232         
1233         self.team = COLOR_TEAM2; // blue
1234         spawnfunc_info_player_deathmatch();
1235 }
1236
1237 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
1238 CTF Starting point for a player in team three (Yellow).
1239 Keys: "angle" viewing angle when spawning. */
1240 void spawnfunc_info_player_team3()
1241 {
1242         if(g_assault) { remove(self); return; }
1243         
1244         self.team = COLOR_TEAM3; // yellow
1245         spawnfunc_info_player_deathmatch();
1246 }
1247
1248
1249 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
1250 CTF Starting point for a player in team four (Purple).
1251 Keys: "angle" viewing angle when spawning. */
1252 void spawnfunc_info_player_team4()
1253 {
1254         if(g_assault) { remove(self); return; }
1255         
1256         self.team = COLOR_TEAM4; // purple
1257         spawnfunc_info_player_deathmatch();
1258 }
1259
1260 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
1261 CTF flag for team one (Red).
1262 Keys: 
1263 "angle" Angle the flag will point (minus 90 degrees)... 
1264 "model" model to use, note this needs red and blue as skins 0 and 1...
1265 "noise" sound played when flag is picked up...
1266 "noise1" sound played when flag is returned by a teammate...
1267 "noise2" sound played when flag is captured...
1268 "noise3" sound played when flag is lost in the field and respawns itself... 
1269 "noise4" sound played when flag is dropped by a player...
1270 "noise5" sound played when flag touches the ground... */
1271 void spawnfunc_item_flag_team1()
1272 {
1273         if(!g_ctf) { remove(self); return; }
1274
1275         ctf_FlagSetup(1, self); // 1 = red
1276 }
1277
1278 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
1279 CTF flag for team two (Blue).
1280 Keys: 
1281 "angle" Angle the flag will point (minus 90 degrees)... 
1282 "model" model to use, note this needs red and blue as skins 0 and 1...
1283 "noise" sound played when flag is picked up...
1284 "noise1" sound played when flag is returned by a teammate...
1285 "noise2" sound played when flag is captured...
1286 "noise3" sound played when flag is lost in the field and respawns itself... 
1287 "noise4" sound played when flag is dropped by a player...
1288 "noise5" sound played when flag touches the ground... */
1289 void spawnfunc_item_flag_team2()
1290 {
1291         if(!g_ctf) { remove(self); return; }
1292
1293         ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
1294 }
1295
1296 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
1297 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
1298 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.
1299 Keys:
1300 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
1301 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
1302 void spawnfunc_ctf_team()
1303 {
1304         if(!g_ctf) { remove(self); return; }
1305         
1306         self.classname = "ctf_team";
1307         self.team = self.cnt + 1;
1308 }
1309
1310
1311 // ==============
1312 // Initialization
1313 // ==============
1314
1315 // code from here on is just to support maps that don't have flag and team entities
1316 void ctf_SpawnTeam (string teamname, float teamcolor)
1317 {
1318         entity oldself;
1319         oldself = self;
1320         self = spawn();
1321         self.classname = "ctf_team";
1322         self.netname = teamname;
1323         self.cnt = teamcolor;
1324
1325         spawnfunc_ctf_team();
1326
1327         self = oldself;
1328 }
1329
1330 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
1331 {
1332         // if no teams are found, spawn defaults
1333         if(find(world, classname, "ctf_team") == world)
1334         {
1335                 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
1336                 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
1337                 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
1338         }
1339         
1340         ScoreRules_ctf();
1341 }
1342
1343 void ctf_Initialize()
1344 {
1345         ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
1346
1347         ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
1348         ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
1349         ctf_captureshield_force = autocvar_g_ctf_shield_force;
1350         
1351         InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
1352 }
1353
1354
1355 MUTATOR_DEFINITION(gamemode_ctf)
1356 {
1357         MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
1358         MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
1359         MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
1360         MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
1361         MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
1362         MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
1363         MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
1364         MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
1365         MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
1366         MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
1367         MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
1368         MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
1369         MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
1370         
1371         MUTATOR_ONADD
1372         {
1373                 if(time > 1) // game loads at time 1
1374                         error("This is a game type and it cannot be added at runtime.");
1375                 g_ctf = 1;
1376                 ctf_Initialize();
1377         }
1378
1379         MUTATOR_ONREMOVE
1380         {
1381                 g_ctf = 0;
1382                 error("This is a game type and it cannot be removed at runtime.");
1383         }
1384
1385         return 0;
1386 }