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