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