]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_ctf.qc
Remove more redundant code
[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_AnnounceStolenFlag(entity flag, entity player)
64 {
65         entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
66         string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
67         
68         verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat(Team_ColorCode(player.team), "(^7", player.netname, Team_ColorCode(player.team), ") ") : "");
69         
70         FOR_EACH_PLAYER(tmp_player)
71                 if(tmp_player == player)
72                         centerprint(tmp_player, strcat("You got the ", flag.netname, "!"));
73                 else if(tmp_player.team == player.team)
74                         centerprint(tmp_player, strcat("Your ", Team_ColorCode(player.team), "team mate ", verbosename, "^7got the flag! Protect them!"));
75                 else if(tmp_player.team == flag.team)
76                         centerprint(tmp_player, strcat("The ", Team_ColorCode(player.team), "enemy ", verbosename, "^7got your flag! Retrieve it!"));
77 }
78
79 void ctf_FlagcarrierWaypoints(entity player)
80 {
81         WaypointSprite_Spawn("flagcarrier", 0, 0, player, '0 0 64', world, player.team, player, wps_flagcarrier, FALSE, RADARICON_FLAG, '1 1 0');
82         WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
83         WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
84         WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0');
85 }
86
87
88 // =======================
89 // CaptureShield Functions 
90 // =======================
91
92 float ctf_CaptureShield_CheckStatus(entity p) 
93 {
94         float s, se;
95         entity e;
96         float players_worseeq, players_total;
97
98         if(ctf_captureshield_max_ratio <= 0)
99                 return FALSE;
100
101         s = PlayerScore_Add(p, SP_SCORE, 0);
102         if(s >= -ctf_captureshield_min_negscore)
103                 return FALSE;
104
105         players_total = players_worseeq = 0;
106         FOR_EACH_PLAYER(e)
107         {
108                 if(e.team != p.team)
109                         continue;
110                 se = PlayerScore_Add(e, SP_SCORE, 0);
111                 if(se <= s)
112                         ++players_worseeq;
113                 ++players_total;
114         }
115
116         // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
117         // use this rule here
118         
119         if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
120                 return FALSE;
121
122         return TRUE;
123 }
124
125 void ctf_CaptureShield_Update(entity player, float wanted_status)
126 {
127         float updated_status = ctf_CaptureShield_CheckStatus(player);
128         if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
129         {
130                 if(updated_status) // TODO csqc notifier for this // Samual: How?
131                         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);
132                 else
133                         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);
134                         
135                 player.ctf_captureshielded = updated_status;
136         }
137 }
138
139 float ctf_CaptureShield_Customize()
140 {
141         if not(other.ctf_captureshielded) { return FALSE; }
142         if(self.team == other.team) { return FALSE; }
143         
144         return TRUE;
145 }
146
147 void ctf_CaptureShield_Touch()
148 {
149         if not(other.ctf_captureshielded) { return; }
150         if(self.team == other.team) { return; }
151         
152         vector mymid = (self.absmin + self.absmax) * 0.5;
153         vector othermid = (other.absmin + other.absmax) * 0.5;
154
155         Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
156         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);
157 }
158
159 void ctf_CaptureShield_Spawn(entity flag)
160 {
161         entity shield = spawn();
162         
163         shield.enemy = self;
164         shield.team = self.team;
165         shield.touch = ctf_CaptureShield_Touch;
166         shield.customizeentityforclient = ctf_CaptureShield_Customize;
167         shield.classname = "ctf_captureshield";
168         shield.effects = EF_ADDITIVE;
169         shield.movetype = MOVETYPE_NOCLIP;
170         shield.solid = SOLID_TRIGGER;
171         shield.avelocity = '7 0 11';
172         shield.scale = 0.5;
173         
174         setorigin(shield, self.origin);
175         setmodel(shield, "models/ctf/shield.md3");
176         setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
177 }
178
179 // ====================
180 // Drop/Pass/Throw Code
181 // ====================
182
183 void ctf_Handle_Failed_Pass(entity flag)
184 {
185         print("ctf_Handle_Failed_Pass(entity flag) called.\n");
186         entity sender = flag.pass_sender;
187
188         flag.movetype = MOVETYPE_TOSS;
189         flag.takedamage = DAMAGE_YES;
190         flag.health = flag.max_flag_health;
191         flag.ctf_droptime = time;
192         flag.ctf_dropper = sender;
193         flag.ctf_status = FLAG_DROPPED;
194         
195         // messages and sounds
196         Send_KillNotification(sender.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
197         sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
198         ctf_EventLog("dropped", sender.team, sender);
199
200         // scoring
201         PlayerTeamScore_AddScore(sender, -ctf_ReadScore("penalty_drop"));       
202         PlayerScore_Add(sender, SP_CTF_DROPS, 1);
203         
204         // waypoints
205         if(autocvar_g_ctf_flag_dropped_waypoint)
206                 WaypointSprite_Spawn("flagdropped", 0, 0, flag, '0 0 64', world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : sender.team), flag, wps_flagdropped, FALSE, RADARICON_FLAG, '0 0.5 0' + ((flag.team == COLOR_TEAM1) ? '0.75 0 0' : '0 0 0.75'));
207
208         if(autocvar_g_ctf_flag_returntime || (autocvar_g_ctf_flag_take_damage && autocvar_g_ctf_flag_health))
209         {
210                 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
211                 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
212         }
213         
214         sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
215         
216         flag.pass_sender = world;
217         flag.pass_target = world;
218 }
219
220 void ctf_Handle_Retrieve(entity flag, entity player)
221 {
222         print("ctf_Handle_Retrieve(entity flag, entity player) called.\n");
223         entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
224         entity sender = flag.pass_sender;
225         
226         // transfer flag to player
227         flag.ctf_carrier = player;
228         flag.owner = player;
229         flag.owner.flagcarried = flag;
230         
231         // reset flag
232         setattachment(flag, player, "");
233         setorigin(flag, FLAG_CARRY_OFFSET);
234         flag.movetype = MOVETYPE_NONE;
235         flag.takedamage = DAMAGE_NO;
236         flag.solid = SOLID_NOT;
237         flag.ctf_carrier = player;
238         flag.ctf_status = FLAG_CARRY;
239
240         // messages and sounds
241         sound(player, CH_TRIGGER, "keepaway/respawn.wav", VOL_BASE, ATTN_NORM);
242         ctf_EventLog("recieve", flag.team, player);
243         FOR_EACH_PLAYER(tmp_player)
244                 if(tmp_player == sender)
245                         centerprint(tmp_player, strcat("You passed the ", flag.netname, " to ", player.netname));
246                 else if(tmp_player == player)
247                         centerprint(tmp_player, strcat("You recieved the ", flag.netname, " from ", sender.netname));
248                 else if(tmp_player.team == sender.team)
249                         centerprint(tmp_player, strcat(sender.netname, " passed the ", flag.netname, " to ", player.netname));
250         
251         // create new waypoint
252         ctf_FlagcarrierWaypoints(player);
253         
254         sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
255         player.throw_antispam = sender.throw_antispam;
256         
257         flag.pass_sender = world;
258         flag.pass_target = world;
259 }
260
261 void ctf_Handle_Throw(entity player, entity reciever, float droptype)
262 {
263         entity flag = player.flagcarried;
264         
265         if(!flag) { return; }
266         if((droptype == DROPTYPE_PASS) && !reciever) { return; }
267         
268         //if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
269         
270         // reset the flag
271         setattachment(flag, world, "");
272         setorigin(flag, player.origin + FLAG_DROP_OFFSET);
273         flag.owner.flagcarried = world;
274         flag.owner = world;
275         flag.solid = SOLID_TRIGGER;
276         flag.ctf_droptime = time;
277         
278         switch(droptype)
279         {
280                 case DROPTYPE_PASS:
281                 {
282                         vector targ_origin = (0.5 * (reciever.absmin + reciever.absmax));
283                         flag.velocity = (normalize(targ_origin - player.origin) * autocvar_g_ctf_throw_velocity);
284                         break;
285                 }
286                 
287                 case DROPTYPE_THROW:
288                 {
289                         makevectors((player.v_angle_y * '0 1 0') + (player.v_angle_x * '0.5 0 0'));
290                         flag.velocity = W_CalculateProjectileVelocity(player.velocity, ('0 0 200' + (v_forward * autocvar_g_ctf_throw_velocity)), FALSE);
291                         break;
292                 }
293                 
294                 default:
295                 case DROPTYPE_DROP:
296                 {
297                         flag.velocity = ('0 0 200' + ('0 100 0' * crandom()) + ('100 0 0' * crandom()));
298                         break;
299                 }
300         }
301         
302         print("flag.velocity = ", vtos(flag.velocity), ".\n");
303         
304         switch(droptype)
305         {
306                 case DROPTYPE_PASS:
307                 {
308                         // main
309                         flag.movetype = MOVETYPE_FLY;
310                         flag.takedamage = DAMAGE_NO;
311                         flag.pass_sender = player;
312                         flag.pass_target = reciever;
313                         flag.ctf_status = FLAG_PASSING;
314                         
315                         // other
316                         sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTN_NORM);
317                         ctf_EventLog("pass", flag.team, player);
318                         te_lightning2(world, reciever.origin, player.origin);
319                         break;
320                 }
321                 
322                 default:
323                 case DROPTYPE_THROW:
324                 case DROPTYPE_DROP:
325                 {
326                         // main
327                         flag.movetype = MOVETYPE_TOSS;
328                         flag.takedamage = DAMAGE_YES;
329                         flag.health = flag.max_flag_health;
330                         flag.ctf_dropper = player;
331                         flag.ctf_status = FLAG_DROPPED;
332                         
333                         // messages and sounds
334                         Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
335                         sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
336                         ctf_EventLog("dropped", player.team, player);
337
338                         // scoring
339                         PlayerTeamScore_AddScore(player, -ctf_ReadScore("penalty_drop"));       
340                         PlayerScore_Add(player, SP_CTF_DROPS, 1);
341                         
342                         // waypoints
343                         if(autocvar_g_ctf_flag_dropped_waypoint)
344                                 WaypointSprite_Spawn("flagdropped", 0, 0, flag, '0 0 64', world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, FALSE, RADARICON_FLAG, '0 0.5 0' + ((flag.team == COLOR_TEAM1) ? '0.75 0 0' : '0 0 0.75')); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team)
345
346                         if(autocvar_g_ctf_flag_returntime || (autocvar_g_ctf_flag_take_damage && autocvar_g_ctf_flag_health))
347                         {
348                                 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
349                                 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
350                         }
351                         break;
352                 }
353         }
354
355         // kill old waypointsprite
356         WaypointSprite_Ping(player.wps_flagcarrier);
357         WaypointSprite_Kill(player.wps_flagcarrier);
358         
359         // captureshield
360         //ctf_CaptureShield_Update(player, 0); // shield only
361 }
362
363
364 // ==============
365 // Event Handlers
366 // ==============
367
368 void ctf_Handle_Dropped_Capture(entity flag, entity enemy_flag)
369 {
370         // declarations
371         string cap_message;
372         entity player = enemy_flag.ctf_dropper;
373         
374         if not(player) { return; } // without someone to give the reward to, we can't possibly cap
375         
376         // messages and sounds
377         Send_KillNotification(player.netname, enemy_flag.netname, ctf_CaptureRecord(enemy_flag, player), INFO_CAPTUREFLAG, MSG_INFO);
378         sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
379         ctf_EventLog("droppedcapture", enemy_flag.team, player);
380         
381         // scoring
382         PlayerTeamScore_AddScore(player, ctf_ReadScore("score_capture"));
383         PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
384
385         // effects
386         if(autocvar_g_ctf_flag_capture_effects) 
387         {
388                 pointparticles(particleeffectnum((player.team == COLOR_TEAM1) ? "red_ground_quake" : "blue_ground_quake"), flag.origin, '0 0 0', 1);
389                 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
390         }
391         
392         player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
393         
394         // reset the flag
395         ctf_RespawnFlag(enemy_flag);
396 }
397
398 void ctf_Handle_Capture(entity flag, entity player)
399 {
400         // messages and sounds
401         Send_KillNotification(player.netname, player.flagcarried.netname, ctf_CaptureRecord(player.flagcarried, player), INFO_CAPTUREFLAG, MSG_INFO);
402         sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
403         ctf_EventLog("capture", player.flagcarried.team, player);
404         
405         // scoring
406         PlayerTeamScore_AddScore(player, ctf_ReadScore("score_capture"));
407         PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
408
409         // effects
410         if(autocvar_g_ctf_flag_capture_effects) 
411         {
412                 pointparticles(particleeffectnum((player.team == COLOR_TEAM1) ? "red_ground_quake" : "blue_ground_quake"), flag.origin, '0 0 0', 1);
413                 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
414         }
415
416         // waypointsprites
417         WaypointSprite_Kill(player.wps_flagcarrier);
418
419         // reset the flag
420         if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
421         player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
422         
423         ctf_RespawnFlag(player.flagcarried);
424 }
425
426 void ctf_Handle_Return(entity flag, entity player)
427 {
428         // messages and sounds
429         //centerprint(player, strcat("You returned ", flag.netname));
430         Send_KillNotification (player.netname, flag.netname, "", INFO_RETURNFLAG, MSG_INFO);
431         sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
432         ctf_EventLog("return", flag.team, player);
433
434         // scoring
435         PlayerTeamScore_AddScore(player, ctf_ReadScore("score_return")); // reward for return
436         PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
437
438         TeamScore_AddToTeam(((flag.team == COLOR_TEAM1) ? COLOR_TEAM2 : COLOR_TEAM1), ST_SCORE, -ctf_ReadScore("penalty_returned")); // punish the team who was last carrying it
439         FOR_EACH_PLAYER(player) if(player == flag.ctf_dropper) // punish the player who dropped the flag
440         {
441                 PlayerScore_Add(player, SP_SCORE, -ctf_ReadScore("penalty_returned"));
442                 ctf_CaptureShield_Update(player, 0); // shield only
443         }
444         
445         // reset the flag
446         ctf_RespawnFlag(flag);
447 }
448
449 void ctf_Handle_Pickup_Base(entity flag, entity player)
450 {
451         // attach the flag to the player
452         flag.owner = player;
453         player.flagcarried = flag;
454         setattachment(flag, player, "");
455         setorigin(flag, FLAG_CARRY_OFFSET);
456         
457         // set up the flag
458         flag.movetype = MOVETYPE_NONE;
459         flag.takedamage = DAMAGE_NO;
460         flag.solid = SOLID_NOT;
461         flag.angles = '0 0 0';
462         flag.ctf_pickuptime = time; // used for timing runs
463         flag.ctf_carrier = player;
464         flag.ctf_status = FLAG_CARRY;
465         
466         // messages and sounds
467         Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
468         sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
469         ctf_EventLog("steal", flag.team, player);
470         ctf_AnnounceStolenFlag(flag, player);
471         
472         // scoring
473         PlayerTeamScore_AddScore(player, ctf_ReadScore("score_pickup_base"));
474         PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
475         
476         // speedrunning
477         flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
478         if((player.speedrunning) && (ctf_captimerecord))
479                 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
480                 
481         // effects
482         if (autocvar_g_ctf_flag_pickup_effects)
483         {
484                 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
485         }
486         
487         // waypoints 
488         ctf_FlagcarrierWaypoints(player);
489         WaypointSprite_Ping(player.wps_flagcarrier);
490 }
491  
492 void ctf_Handle_Pickup_Dropped(entity flag, entity player)
493 {
494         // declarations
495         float returnscore = (autocvar_g_ctf_flag_returntime ? bound(0, ((flag.ctf_droptime + autocvar_g_ctf_flag_returntime) - time) / autocvar_g_ctf_flag_returntime, 1) : 1);
496
497         // attach the flag to the player
498         flag.owner = player;
499         player.flagcarried = flag;
500         setattachment(flag, player, "");
501         setorigin(flag, FLAG_CARRY_OFFSET);
502         
503         // set up the flag
504         flag.movetype = MOVETYPE_NONE;
505         flag.takedamage = DAMAGE_NO;
506         flag.health = flag.max_flag_health;
507         flag.solid = SOLID_NOT;
508         flag.angles = '0 0 0';
509         //flag.ctf_pickuptime = time; // don't update pickuptime since this isn't a real steal. 
510         flag.ctf_carrier = player;
511         flag.ctf_status = FLAG_CARRY;
512
513         // messages and sounds
514         Send_KillNotification(player.netname, flag.netname, "", INFO_PICKUPFLAG, MSG_INFO);
515         sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
516         ctf_EventLog("pickup", flag.team, player);
517         ctf_AnnounceStolenFlag(flag, player);
518                         
519         // scoring
520         returnscore = floor((ctf_ReadScore("score_pickup_dropped_late") * (1-returnscore) + ctf_ReadScore("score_pickup_dropped_early") * returnscore) + 0.5);
521         print("score is ", ftos(returnscore), "\n");
522         PlayerTeamScore_AddScore(player, returnscore);
523         PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
524
525         // effects
526         if(autocvar_g_ctf_flag_pickup_effects) // field pickup effect
527         {
528                 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1); 
529         }
530
531         // waypoints
532         WaypointSprite_Kill(flag.wps_flagdropped);
533         ctf_FlagcarrierWaypoints(player);
534         WaypointSprite_Ping(player.wps_flagcarrier);
535 }
536
537
538 // ===================
539 // Main Flag Functions
540 // ===================
541
542 void ctf_CheckFlagReturn(entity flag)
543 {
544         if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
545         
546         if((flag.health <= 0) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_returntime))
547         {
548                 bprint("The ", flag.netname, " has returned to base\n");
549                 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
550                 ctf_EventLog("returned", flag.team, world);
551                 ctf_RespawnFlag(flag);
552         }
553 }
554
555 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
556 {
557         if(ITEM_DAMAGE_NEEDKILL(deathtype))
558         {
559                 // automatically kill the flag and return it
560                 self.health = 0;
561                 ctf_CheckFlagReturn(self);
562         }
563         
564         if(autocvar_g_ctf_flag_take_damage) 
565         {
566                 self.health = self.health - damage;
567                 ctf_CheckFlagReturn(self);
568         }
569 }
570
571 void ctf_FlagThink()
572 {
573         // declarations
574         entity tmp_entity;
575
576         self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
577
578         // captureshield
579         if(self == ctf_worldflaglist) // only for the first flag
580                 FOR_EACH_CLIENT(tmp_entity)
581                         ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
582
583         // sanity checks
584         if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
585                 dprint("wtf the flag got squashed?\n");
586                 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
587                 if(!trace_startsolid) // can we resize it without getting stuck?
588                         setsize(self, FLAG_MIN, FLAG_MAX); }
589
590         // main think method
591         switch(self.ctf_status)
592         {       
593                 case FLAG_BASE:
594                 {
595                         if(autocvar_g_ctf_dropped_capture_radius)
596                         {
597                                 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
598                                         if(tmp_entity.ctf_status == FLAG_DROPPED)
599                                                 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
600                                                         ctf_Handle_Dropped_Capture(self, tmp_entity);
601                         }
602                         return;
603                 }
604                 
605                 case FLAG_DROPPED:
606                 {
607                         if(autocvar_g_ctf_flag_returntime)
608                         {
609                                 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_returntime) * FLAG_THINKRATE);
610                                 ctf_CheckFlagReturn(self);
611                         } 
612                         return;
613                 }
614                         
615                 case FLAG_CARRY:
616                 {
617                         if((self.owner) && (self.speedrunning) && (ctf_captimerecord) && (time >= self.ctf_pickuptime + ctf_captimerecord)) 
618                         {
619                                 bprint("The ", self.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n");
620                                 sound(self, CH_TRIGGER, self.snd_flag_respawn, VOL_BASE, ATTN_NONE);
621                                 ctf_EventLog("returned", self.team, world);
622                                 ctf_RespawnFlag(tmp_entity);
623
624                                 tmp_entity = self;
625                                 self = self.owner;
626                                 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
627                                 ImpulseCommands();
628                                 self = tmp_entity;
629                         }
630                         return;
631                 }
632                 
633                 case FLAG_PASSING:
634                 {                       
635                         vector targ_origin = (0.5 * (self.pass_target.absmin + self.pass_target.absmax));
636                         
637                         traceline(self.origin, targ_origin, MOVE_NOMONSTERS, self);
638                         
639                         if((self.pass_target.deadflag != DEAD_NO)
640                                 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
641                                 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
642                                 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
643                         {
644                                 ctf_Handle_Failed_Pass(self);
645                         }
646                         else // still a viable target, go for it
647                         {
648                                 vector desired_direction = normalize(targ_origin - self.origin);
649                                 vector current_direction = normalize(self.velocity);
650                                 
651                                 self.velocity = (normalize(current_direction + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_throw_velocity); 
652                         }
653                         return;
654                 }
655
656                 default: // this should never happen
657                 {
658                         dprint("ctf_FlagThink(): Flag exists with no status?\n");
659                         return;
660                 }
661         }
662 }
663
664 void ctf_FlagTouch()
665 {
666         if(gameover) { return; }
667         if(!self) { return; }
668         if(other.deadflag != DEAD_NO) { return; }
669         if(ITEM_TOUCH_NEEDKILL())
670         {
671                 // automatically kill the flag and return it
672                 self.health = 0;
673                 ctf_CheckFlagReturn(self);
674                 return;
675         }
676         if(other.classname != "player") // The flag just touched an object, most likely the world
677         {
678                 if(time > self.wait) // if we haven't in a while, play a sound/effect
679                 {
680                         pointparticles(particleeffectnum("kaball_sparks"), self.origin, '0 0 0', 1);
681                         sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
682                         self.wait = time + FLAG_TOUCHRATE;
683                 }
684                 return;
685         }
686
687         switch(self.ctf_status) 
688         {       
689                 case FLAG_BASE:
690                 {
691                         if((other.team == self.team) && (other.flagcarried) && (other.flagcarried.team != self.team))
692                                 ctf_Handle_Capture(self, other); // other just captured the enemies flag to his base
693                         else if((other.team != self.team) && (!other.flagcarried) && (!other.ctf_captureshielded) && (time > other.next_take_time))
694                                 ctf_Handle_Pickup_Base(self, other); // other just stole the enemies flag
695                         break;
696                 }
697                 
698                 case FLAG_DROPPED:
699                 {
700                         if(other.team == self.team)
701                                 ctf_Handle_Return(self, other); // other just returned his own flag
702                         else if((!other.flagcarried) && ((other != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
703                                 ctf_Handle_Pickup_Dropped(self, other); // other just picked up a dropped enemy flag
704                         break;
705                 }
706                         
707                 case FLAG_CARRY:
708                 {
709                         dprint("Someone touched a flag even though it was being carried?\n");
710                         break;
711                 }
712                 
713                 case FLAG_PASSING:
714                 {
715                         if((other.classname == "player") && (other.deadflag == DEAD_NO) && (other != self.pass_sender))
716                         {
717                                 if(IsDifferentTeam(other, self.pass_sender))
718                                         ctf_Handle_Return(self, other);
719                                 else
720                                         ctf_Handle_Retrieve(self, other);
721                         }
722                         break;
723                 }
724                 
725                 default: // this should never happen
726                 {
727                         dprint("Touch: Flag exists with no status?\n");
728                         break;
729                 }
730         }
731 }
732
733 void ctf_RespawnFlag(entity flag)
734 {
735         // reset the player (if there is one)
736         if((flag.owner) && (flag.owner.flagcarried == flag))
737         {
738                 WaypointSprite_Kill(flag.wps_flagcarrier);
739                 flag.owner.flagcarried = world;
740
741                 if(flag.speedrunning)
742                         ctf_FakeTimeLimit(flag.owner, -1);
743         }
744
745         if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
746                 { WaypointSprite_Kill(flag.wps_flagdropped); }
747
748         // reset the flag
749         setattachment(flag, world, "");
750         setorigin(flag, flag.ctf_spawnorigin);
751         
752         flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
753         flag.takedamage = DAMAGE_NO;
754         flag.health = flag.max_flag_health;
755         flag.solid = SOLID_TRIGGER;
756         flag.velocity = '0 0 0';
757         flag.angles = flag.mangle;
758         flag.flags = FL_ITEM | FL_NOTARGET;
759         
760         flag.ctf_status = FLAG_BASE;
761         flag.owner = world;
762         flag.pass_sender = world;
763         flag.pass_target = world;
764         flag.ctf_carrier = world;
765         flag.ctf_dropper = world;
766         flag.ctf_pickuptime = 0;
767         flag.ctf_droptime = 0;
768 }
769
770 void ctf_Reset()
771 {
772         if(self.owner)
773                 if(self.owner.classname == "player")
774                         ctf_Handle_Throw(self.owner, world, DROPTYPE_DROP);
775                         
776         ctf_RespawnFlag(self);
777 }
778
779 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
780 {
781         // declarations
782         float teamnumber = ((self.team == COLOR_TEAM1) ? TRUE : FALSE); // if we were originally 1, this will become 0. If we were originally 0, this will become 1. 
783
784         // bot waypoints
785         waypoint_spawnforitem_force(self, self.origin);
786         self.nearestwaypointtimeout = 0; // activate waypointing again
787         self.bot_basewaypoint = self.nearestwaypoint;
788
789         // waypointsprites
790         WaypointSprite_SpawnFixed(((teamnumber) ? "redbase" : "bluebase"), self.origin + '0 0 64', self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE));
791         WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE));
792
793         // captureshield setup
794         ctf_CaptureShield_Spawn(self);
795 }
796
797 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc 
798 {       
799         // declarations
800         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. 
801         self = flag; // for later usage with droptofloor()
802         
803         // main setup
804         flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
805         ctf_worldflaglist = flag;
806
807         setattachment(flag, world, ""); 
808
809         flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
810         flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
811         flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
812         flag.classname = "item_flag_team";
813         flag.target = "###item###"; // wut?
814         flag.flags = FL_ITEM | FL_NOTARGET;
815         flag.solid = SOLID_TRIGGER;
816         flag.takedamage = DAMAGE_NO;
817         flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;   
818         flag.max_flag_health = ((autocvar_g_ctf_flag_take_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
819         flag.health = flag.max_flag_health;
820         flag.event_damage = ctf_FlagDamage;
821         flag.pushable = TRUE;
822         flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
823         flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
824         flag.velocity = '0 0 0';
825         flag.mangle = flag.angles;
826         flag.reset = ctf_Reset;
827         flag.touch = ctf_FlagTouch;
828         flag.think = ctf_FlagThink;
829         flag.nextthink = time + FLAG_THINKRATE;
830         flag.ctf_status = FLAG_BASE;
831         
832         if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
833         if(!flag.scale) { flag.scale = FLAG_SCALE; }
834         if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
835         
836         // sound 
837         if(!flag.snd_flag_taken) { flag.snd_flag_taken  = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
838         if(!flag.snd_flag_returned) { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
839         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
840         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.
841         if(!flag.snd_flag_dropped) { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
842         if(!flag.snd_flag_touch) { flag.snd_flag_touch = "keepaway/touch.wav"; } // again has no team-based sound // FIXME
843         
844         // precache
845         precache_sound(flag.snd_flag_taken);
846         precache_sound(flag.snd_flag_returned);
847         precache_sound(flag.snd_flag_capture);
848         precache_sound(flag.snd_flag_respawn);
849         precache_sound(flag.snd_flag_dropped);
850         precache_sound(flag.snd_flag_touch);
851         precache_model(flag.model);
852         precache_model("models/ctf/shield.md3");
853         precache_model("models/ctf/shockwavetransring.md3");
854
855         // appearence
856         setmodel(flag, flag.model); // precision set below
857         setsize(flag, FLAG_MIN, FLAG_MAX);
858         setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
859         
860         if(autocvar_g_ctf_flag_glowtrails)
861         {
862                 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
863                 flag.glow_size = 25;
864                 flag.glow_trail = 1;
865         }
866         
867         flag.effects |= EF_LOWPRECISION;
868         if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
869         if(autocvar_g_ctf_dynamiclights)   { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
870         
871         // flag placement
872         if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
873         {       
874                 flag.dropped_origin = flag.origin; 
875                 flag.noalign = TRUE;
876                 flag.movetype = MOVETYPE_NONE;
877         }
878         else // drop to floor, automatically find a platform and set that as spawn origin
879         { 
880                 flag.noalign = FALSE;
881                 self = flag;
882                 droptofloor();
883                 flag.movetype = MOVETYPE_TOSS; 
884         }       
885         
886         InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
887 }
888
889
890 // ==============
891 // Hook Functions
892 // ==============
893
894 MUTATOR_HOOKFUNCTION(ctf_HookedDrop)
895 {
896         if(self.flagcarried) { ctf_Handle_Throw(self, world, DROPTYPE_DROP); }
897         return 0;
898 }
899
900 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
901 {
902         entity flag;
903         
904         // initially clear items so they can be set as necessary later.
905         self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST 
906                 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
907
908         // scan through all the flags and notify the client about them 
909         for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
910         {
911                 if(flag.ctf_status == FLAG_CARRY)
912                         if(flag.owner == self)
913                                 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
914                         else 
915                                 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
916                 else if(flag.ctf_status == FLAG_DROPPED) 
917                         self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
918         }
919         
920         // item for stopping players from capturing the flag too often
921         if(self.ctf_captureshielded)
922                 self.items |= IT_CTF_SHIELDED;
923         
924         // update the health of the flag carrier waypointsprite
925         if(self.wps_flagcarrier) 
926                 WaypointSprite_UpdateHealth(self.wps_flagcarrier, self.health);
927         
928         return 0;
929 }
930
931 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
932 {
933         if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
934         {
935                 if(frag_target == frag_attacker) // damage done to yourself
936                 {
937                         frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
938                         frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
939                 }
940                 else // damage done everyone else
941                 {
942                         frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
943                         frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
944                 }
945         }
946         return 0;
947 }
948
949 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
950 {
951         frag_score = 0;
952         return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
953 }
954
955 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
956 {
957         entity player = self;
958
959         if(time > player.throw_antispam)
960         {
961                 // pass the flag to a team mate
962                 if(autocvar_g_ctf_allow_pass)
963                 {
964                         entity head, closest_target;
965                         head = findradius(player.origin, autocvar_g_ctf_pass_radius);
966                         
967                         while(head) // find the closest acceptable target to pass to
968                         {
969                                 if(head.classname == "player" && head.deadflag == DEAD_NO)
970                                 if(head != player && !IsDifferentTeam(head, player))
971                                 if(!player.speedrunning && !head.speedrunning)
972                                 {
973                                         traceline(player.origin, head.origin, MOVE_NOMONSTERS, player);
974                                         if not((trace_fraction < 1) && (trace_ent != head))
975                                         {
976                                                 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried) 
977                                                 { 
978                                                         if(clienttype(head) == CLIENTTYPE_BOT)
979                                                         {
980                                                                 centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname)); 
981                                                                 ctf_Handle_Throw(head, player, DROPTYPE_PASS);
982                                                         }
983                                                         else
984                                                         {
985                                                                 centerprint(head, strcat(player.netname, " requests you to pass the ", head.flagcarried.netname)); 
986                                                                 centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname)); 
987                                                         }
988                                                         player.throw_antispam = time + autocvar_g_ctf_pass_wait; 
989                                                         return 0; 
990                                                 }
991                                                 else if(player.flagcarried)
992                                                 {
993                                                         if(closest_target) { if(vlen(player.origin - head.origin) < vlen(player.origin - closest_target.origin)) { closest_target = head; } }
994                                                         else { closest_target = head; }
995                                                 }
996                                         }
997                                 }
998                                 head = head.chain;
999                         }
1000                         
1001                         if(closest_target) { ctf_Handle_Throw(player, closest_target, DROPTYPE_PASS); return 0; }
1002                 }
1003                 
1004                 // throw the flag in front of you
1005                 if(autocvar_g_ctf_allow_drop && player.flagcarried && !player.speedrunning)
1006                         { ctf_Handle_Throw(player, world, DROPTYPE_THROW); }
1007         }
1008                 
1009         return 0;
1010 }
1011
1012 // ==========
1013 // Spawnfuncs
1014 // ==========
1015
1016 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
1017 CTF Starting point for a player in team one (Red).
1018 Keys: "angle" viewing angle when spawning. */
1019 void spawnfunc_info_player_team1()
1020 {
1021         if(g_assault) { remove(self); return; }
1022         
1023         self.team = COLOR_TEAM1; // red
1024         spawnfunc_info_player_deathmatch();
1025 }
1026
1027
1028 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
1029 CTF Starting point for a player in team two (Blue).
1030 Keys: "angle" viewing angle when spawning. */
1031 void spawnfunc_info_player_team2()
1032 {
1033         if(g_assault) { remove(self); return; }
1034         
1035         self.team = COLOR_TEAM2; // blue
1036         spawnfunc_info_player_deathmatch();
1037 }
1038
1039 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
1040 CTF Starting point for a player in team three (Yellow).
1041 Keys: "angle" viewing angle when spawning. */
1042 void spawnfunc_info_player_team3()
1043 {
1044         if(g_assault) { remove(self); return; }
1045         
1046         self.team = COLOR_TEAM3; // yellow
1047         spawnfunc_info_player_deathmatch();
1048 }
1049
1050
1051 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
1052 CTF Starting point for a player in team four (Purple).
1053 Keys: "angle" viewing angle when spawning. */
1054 void spawnfunc_info_player_team4()
1055 {
1056         if(g_assault) { remove(self); return; }
1057         
1058         self.team = COLOR_TEAM4; // purple
1059         spawnfunc_info_player_deathmatch();
1060 }
1061
1062 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
1063 CTF flag for team one (Red).
1064 Keys: 
1065 "angle" Angle the flag will point (minus 90 degrees)... 
1066 "model" model to use, note this needs red and blue as skins 0 and 1...
1067 "noise" sound played when flag is picked up...
1068 "noise1" sound played when flag is returned by a teammate...
1069 "noise2" sound played when flag is captured...
1070 "noise3" sound played when flag is lost in the field and respawns itself... 
1071 "noise4" sound played when flag is dropped by a player...
1072 "noise5" sound played when flag touches the ground... */
1073 void spawnfunc_item_flag_team1()
1074 {
1075         if(!g_ctf) { remove(self); return; }
1076
1077         ctf_FlagSetup(1, self); // 1 = red
1078 }
1079
1080 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
1081 CTF flag for team two (Blue).
1082 Keys: 
1083 "angle" Angle the flag will point (minus 90 degrees)... 
1084 "model" model to use, note this needs red and blue as skins 0 and 1...
1085 "noise" sound played when flag is picked up...
1086 "noise1" sound played when flag is returned by a teammate...
1087 "noise2" sound played when flag is captured...
1088 "noise3" sound played when flag is lost in the field and respawns itself... 
1089 "noise4" sound played when flag is dropped by a player...
1090 "noise5" sound played when flag touches the ground... */
1091 void spawnfunc_item_flag_team2()
1092 {
1093         if(!g_ctf) { remove(self); return; }
1094
1095         ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
1096 }
1097
1098 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
1099 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
1100 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.
1101 Keys:
1102 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
1103 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
1104 void spawnfunc_ctf_team()
1105 {
1106         if(!g_ctf) { remove(self); return; }
1107         
1108         self.classname = "ctf_team";
1109         self.team = self.cnt + 1;
1110 }
1111
1112
1113 // ==============
1114 // Initialization
1115 // ==============
1116
1117 // code from here on is just to support maps that don't have flag and team entities
1118 void ctf_SpawnTeam (string teamname, float teamcolor)
1119 {
1120         entity oldself;
1121         oldself = self;
1122         self = spawn();
1123         self.classname = "ctf_team";
1124         self.netname = teamname;
1125         self.cnt = teamcolor;
1126
1127         spawnfunc_ctf_team();
1128
1129         self = oldself;
1130 }
1131
1132 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
1133 {
1134         // if no teams are found, spawn defaults
1135         if(find(world, classname, "ctf_team") == world)
1136         {
1137                 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
1138                 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
1139                 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
1140         }
1141         
1142         ScoreRules_ctf();
1143 }
1144
1145 void ctf_Initialize()
1146 {
1147         ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
1148
1149         ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
1150         ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
1151         ctf_captureshield_force = autocvar_g_ctf_shield_force;
1152         
1153         InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
1154 }
1155
1156
1157 MUTATOR_DEFINITION(gamemode_ctf)
1158 {
1159         MUTATOR_HOOK(MakePlayerObserver, ctf_HookedDrop, CBC_ORDER_ANY);
1160         MUTATOR_HOOK(ClientDisconnect, ctf_HookedDrop, CBC_ORDER_ANY);
1161         MUTATOR_HOOK(PlayerDies, ctf_HookedDrop, CBC_ORDER_ANY);
1162         MUTATOR_HOOK(PortalTeleport, ctf_HookedDrop, CBC_ORDER_ANY);
1163         MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
1164         MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
1165         MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
1166         MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
1167
1168         MUTATOR_ONADD
1169         {
1170                 if(time > 1) // game loads at time 1
1171                         error("This is a game type and it cannot be added at runtime.");
1172                 g_ctf = 1;
1173                 ctf_Initialize();
1174         }
1175
1176         MUTATOR_ONREMOVE
1177         {
1178                 g_ctf = 0;
1179                 error("This is a game type and it cannot be removed at runtime.");
1180         }
1181
1182         return 0;
1183 }