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