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