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