]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mutators/gamemode_ctf.qc
Set up new framework for touch statement, re-write a lot of some other functions...
[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 27th, 2011
4 // ================================================================
5
6 // Flag constants 
7 #define FLAG_MIN (PL_MIN + '0 0 -13')
8 #define FLAG_MAX (PL_MAX + '0 0 -13')
9 #define FLAG_CARRY_POS '-15 0 7'
10
11 .entity bot_basewaypoint; // Flag waypointsprite
12 .entity wps_flagbase; 
13
14 entity ctf_worldflaglist; // CTF flags in the map
15 .entity ctf_worldflagnext;
16
17 .float ctf_dropperid; // Don't allow spam of dropping the flag
18 .float ctf_droptime;
19 .float ctf_status; // status of the flag (FLAG_BASE, FLAG_DROPPED, FLAG_CARRY declared globally)
20
21 // Delay between when the person can pick up a flag // replace with .wait? 
22 .float next_take_time;
23
24 // Record time for capturing the flag
25 float flagcaptimerecord;
26 .float flagpickuptime;
27
28 // CaptureShield: If the player is too bad to be allowed to capture, shield them from taking the flag.
29 .float ctf_captureshielded; // set to 1 if the player is too bad to be allowed to capture
30 float captureshield_min_negscore; // punish at -20 points
31 float captureshield_max_ratio; // punish at most 30% of each team
32 float captureshield_force; // push force of the shield
33
34
35 // declare functions so they can be used in any order in the file
36 void ctf_TouchEvent(void);
37 void ctf_FlagThink(void);
38 void ctf_SetupFlag(float, entity);
39
40
41 // ==================
42 // Misc CTF functions
43 // ==================
44
45 float ctf_ReadScore(string parameter)
46 {
47         if(g_ctf_win_mode != 2)
48                 return cvar(strcat("g_ctf_personal", parameter));
49         else
50                 return cvar(strcat("g_ctf_flag", parameter));
51 }
52
53 void ctf_FakeTimeLimit(entity e, float t)
54 {
55         msg_entity = e;
56         WriteByte(MSG_ONE, 3); // svc_updatestat
57         WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
58         if(t < 0)
59                 WriteCoord(MSG_ONE, autocvar_timelimit);
60         else
61                 WriteCoord(MSG_ONE, (t + 1) / 60);
62 }
63
64 void ctf_EventLog(string mode, float flagteam, entity actor)
65 {
66         string s;
67         if(!autocvar_sv_eventlog)
68                 return;
69         s = strcat(":ctf:", mode);
70         s = strcat(s, ":", ftos(flagteam));
71         if(actor != world)
72                 s = strcat(s, ":", ftos(actor.playerid));
73         GameLogEcho(s);
74 }
75
76 void ctf_CaptureShockwave(vector org)
77 {
78         shockwave_spawn("models/ctf/shockwavetransring.md3", org - '0 0 15', -0.8, 0, 1);
79 }
80
81 void ctf_CreateBaseWaypoints(entity flag, float teamnumber)
82 {
83         // for bots
84         waypoint_spawnforitem_force(flag, flag.origin);
85         flag.nearestwaypointtimeout = 0; // activate waypointing again
86         flag.bot_basewaypoint = flag.nearestwaypoint;
87
88         // waypointsprites
89         WaypointSprite_SpawnFixed(((teamnumber) ? "redbase" : "bluebase"), flag.origin + '0 0 61', flag, wps_flagbase);
90         WaypointSprite_UpdateTeamRadar(flag.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE));
91 }
92
93 void ctf_SetStatus_ForType(entity flag, float type)
94 {
95         if(flag.cnt ==  FLAG_CARRY)
96         {
97                 if(flag.owner == self)
98                         self.items |= type * 3; // carrying: self is currently carrying the flag
99                 else 
100                         self.items |= type * 1; // taken: someone on self's team is carrying the flag
101         }
102         else if(flag.cnt == FLAG_DROPPED) 
103                 self.items |= type * 2; // lost: the flag is dropped somewhere on the map
104 }
105
106 void ctf_SetStatus()
107 {
108         // declarations 
109         float redflags, blueflags;
110         local entity flag;
111         
112         // initially clear items so they can be set as necessary later.
113         self.items &~= (IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
114
115         // item for stopping players from capturing the flag too often
116         if(self.ctf_captureshielded)
117                 self.items |= IT_CTF_SHIELDED;
118
119         // figure out what flags we already own
120         for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) if(flag.cnt != FLAG_BASE)
121         {
122                 if(flag.items & IT_KEY2) // blue
123                         ++redflags;
124                 else if(flag.items & IT_KEY1) // red
125                         ++blueflags;
126         }
127
128         // blinking magic: if there is more than one flag, show one of these in a clever way // wtf?
129         if(redflags)
130                 redflags = mod(floor(time * redflags * 0.75), redflags);
131                 
132         if(blueflags)
133                 blueflags = mod(floor(time * blueflags * 0.75), blueflags);
134
135         for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) if(flag.cnt != FLAG_BASE)
136         {
137                 if(flag.items & IT_KEY2) // blue
138                 {
139                         if(--redflags == -1) // happens exactly once (redflags is in 0..count-1, and will --'ed count times) // WHAT THE FUCK DOES THIS MEAN? whoever wrote this is shitty at explaining things.
140                                 ctf_SetStatus_ForType(flag, IT_RED_FLAG_TAKEN);
141                 }
142                 else if(flag.items & IT_KEY1) // red
143                 {
144                         if(--blueflags == -1) // happens exactly once
145                                 ctf_SetStatus_ForType(flag, IT_BLUE_FLAG_TAKEN);
146                 }
147         }
148 }
149
150 void ctf_Reset()
151 {
152         DropFlag(self, world, world);
153
154         ReturnFlag(self);
155 }
156
157
158 // ===================
159 // Main Flag Functions
160 // ===================
161
162 void ctf_SetupFlag(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc 
163 {
164         // declarations
165         teamnumber = fabs(teamnumber - bound(0, g_ctf_reverse, 1)); // if we were originally 1, this will become 0. If we were originally 0, this will become 1. 
166         string flag_team_by_name;
167         
168         // main setup
169         flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist // todo: find out if this can be simplified
170         ctf_worldflaglist = flag;
171
172         setattachment(flag, world, ""); 
173
174         flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
175         flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
176         flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
177         flag.classname = "item_flag_team";
178         flag.target = "###item###"; // wut?
179         flag.flags = FL_ITEM;
180         flag.solid = SOLID_TRIGGER;
181         flag.velocity = '0 0 0';
182         flag.ctf_status = FLAG_BASE;
183         flag.mangle = flag.angles;
184         flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
185         
186         if((flag.spawnflags & 1) 
187         {       
188                 flag.noalign = TRUE;
189                 flag.dropped_origin = flag.origin; 
190                 flag.movetype = MOVETYPE_NONE;
191         }
192         else 
193         { 
194                 flag.noalign = FALSE;
195                 droptofloor(); 
196                 flag.movetype = MOVETYPE_TOSS; 
197         }       
198         
199         flag.reset = ctf_Reset;
200         flag.touch = ctf_TouchEvent;
201         flag.think = ctf_RespawnFlag;
202         flag.nextthink = time + 0.2; // start after doors etc // Samual: 0.2 though? Why? 
203
204         // appearence
205         if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
206         setmodel (flag, flag.model); // precision set below
207         setsize(flag, FLAG_MIN, FLAG_MAX);
208         setorigin(flag, flag.origin + '0 0 37');
209         flag.origin_z = flag.origin_z + 6; // why 6?
210         if(!flag.scale) { flag.scale = 0.6; }
211         
212         flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin);
213         
214         if(autocvar_g_ctf_flag_glowtrails)
215         {
216                 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
217                 flag.glow_size = 25;
218                 flag.glow_trail = 1;
219         }
220         
221         flag.effects |= EF_LOWPRECISION;
222         if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
223         if(autocvar_g_ctf_dynamiclights)   { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
224         
225         // sound 
226         if(!flag.noise)  { flag.noise  = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
227         if(!flag.noise1) { flag.noise1 = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
228         if(!flag.noise2) { flag.noise2 = ((teamnumber) ? "ctf/red_capture.wav" : "ctf/blue_capture.wav"); } // blue team scores by capturing the red flag
229         if(!flag.noise3) { flag.noise3 = "ctf/flag_respawn.wav"; } // if there is ever a team-based sound for this, update the code to match.
230         if(!flag.noise4) { flag.noise4 = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
231         
232         // precache
233         precache_sound(flag.noise);
234         precache_sound(flag.noise1);
235         precache_sound(flag.noise2);
236         precache_sound(flag.noise3);
237         precache_sound(flag.noise4);
238         precache_model(flag.model);
239         precache_model("models/ctf/shield.md3");
240         precache_model("models/ctf/shockwavetransring.md3");
241
242         // other initialization stuff
243         ctf_CreateBaseWaypoints(flag, teamnumber);
244         ctf_CaptureShield_Spawn(flag, teamnumber);
245         //InitializeEntity(self, ctf_CaptureShield_Spawn, INITPRIO_SETLOCATION);
246 }
247
248 void ctf_RespawnFlag(entity flag)
249 {
250 }
251
252 void ctf_RegenFlag(entity e)
253 {
254         if(self.classname != "item_flag_team") { backtrace("ctf_RegenFlag was called incorrectly."); return; }
255
256         if(e.waypointsprite_attachedforcarrier)
257                 WaypointSprite_DetachCarrier(e);
258
259         setattachment(e, world, "");
260         e.damageforcescale = 0;
261         e.takedamage = DAMAGE_NO;
262         e.movetype = MOVETYPE_NONE;
263         if(!e.noalign)
264                 e.movetype = MOVETYPE_TOSS;
265         e.velocity = '0 0 0';
266         e.solid = SOLID_TRIGGER;
267         // TODO: play a sound here
268         setorigin(e, e.dropped_origin);
269         e.angles = e.mangle;
270         e.cnt = FLAG_BASE;
271         e.owner = world;
272         e.flags = FL_ITEM; // clear FL_ONGROUND and any other junk // there shouldn't be any "junk" set on this... look into it and make sure it's kept clean. 
273 }
274
275 void ctf_ReturnFlag(entity e)
276 {
277         if(e.classname != "item_flag_team") { backtrace("ctf_ReturnFlag was called incorrectly."); return; }
278
279         if(e.owner)
280         if(e.owner.flagcarried == e)
281         {
282                 WaypointSprite_DetachCarrier(e.owner);
283                 e.owner.flagcarried = world;
284
285                 if(e.speedrunning)
286                         ctf_FakeTimeLimit(e.owner, -1);
287         }
288         e.owner = world;
289         RegenFlag(e);
290 }
291
292 void ctf_Handle_Drop(entity player)
293 {
294         entity flag = player.flagcarried;
295
296         if(!flag) { return; }
297         if(flag.speedrunning) { ReturnFlag(flag); return; }
298         
299         // reset the flag
300         setattachment(flag, world, "");
301         flag.owner.flagcarried = world;
302         flag.owner = world;
303         flag.ctf_status = FLAG_DROPPED;
304         flag.movetype = MOVETYPE_TOSS;
305         flag.solid = SOLID_TRIGGER;
306         flag.damageforcescale = autocvar_g_balance_ctf_damageforcescale; // should this really be set here? I don't think so
307         flag.takedamage = DAMAGE_YES;
308         flag.flags = FL_ITEM; // does this need set?
309         setorigin(flag, p.origin - '0 0 24' + '0 0 37');
310         flag.ctf_status = FLAG_DROPPED;
311         flag.velocity = '0 0 200' + '0 100 0'*crandom() + '100 0 0'*crandom();
312         flag.pain_finished = time + autocvar_g_ctf_flag_returntime;//30;
313         
314         flag.ctf_droptime = time;
315         
316         // reset the player
317         
318         
319         // messages and sounds
320         Send_KillNotification(carrier.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
321         sound(flag, CHAN_TRIGGER, flag.noise4, VOL_BASE, ATTN_NONE);
322         ctf_EventLog("dropped", carrier.team, carrier);
323         
324         // scoring
325         PlayerScore_Add(carrier, SP_CTF_DROPS, 1);
326         if(penalty_receiver)
327                 UpdateFrags(penalty_receiver, -ctf_score_value("penalty_suicidedrop"));
328         else
329                 UpdateFrags(carrier, -ctf_score_value("penalty_drop"));
330         
331         // waypoints
332         WaypointSprite_Spawn("flagdropped", 0, 0, flag, '0 0 64', world, (COLOR_TEAM1 + COLOR_TEAM2 - flag.team), flag, waypointsprite_attachedforcarrier, FALSE);
333         WaypointSprite_Ping(carrier.waypointsprite_attachedforcarrier);
334         WaypointSprite_DetachCarrier(carrier);
335
336         ctf_captureshield_update(carrier, 0); // shield only
337
338         // eh? 
339         trace_startsolid = FALSE;
340         tracebox(flag.origin, flag.mins, flag.maxs, flag.origin, TRUE, flag);
341         if(trace_startsolid)
342                 dprint("FLAG FALLTHROUGH will happen SOON\n");
343 }
344
345 void ctf_FlagThink()
346 {
347         local entity e;
348
349         self.nextthink = time + 0.1;
350
351         // sorry, we have to reset the flag size if it got squished by something
352         if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX)
353         {
354                 // if we can grow back, grow back
355                 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
356                 if(!trace_startsolid)
357                         setsize(self, FLAG_MIN, FLAG_MAX);
358         }
359
360         if(self == ctf_worldflaglist) // only for the first flag
361         {
362                 FOR_EACH_CLIENT(e)
363                         ctf_captureshield_update(e, 1); // release shield only
364         }
365
366         if(self.speedrunning)
367         if(self.cnt == FLAG_CARRY)
368         {
369                 if(self.owner)
370                 if(flagcaptimerecord)
371                 if(time >= self.flagpickuptime + flagcaptimerecord)
372                 {
373                         bprint("The ", self.netname, " became impatient after ", ftos_decimals(flagcaptimerecord, 2), " seconds and returned itself\n");
374
375                         sound (self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
376                         self.owner.impulse = 141; // returning!
377
378                         e = self;
379                         self = self.owner;
380                         ReturnFlag(e);
381                         ImpulseCommands();
382                         self = e;
383                         return;
384                 }
385         }
386
387         if(self.cnt == FLAG_BASE)
388                 return;
389
390         if(self.cnt == FLAG_DROPPED)
391         {
392                 // flag fallthrough? FIXME remove this if bug is really fixed now
393                 if(self.origin_z < -131072)
394                 {
395                         dprint("FLAG FALLTHROUGH just happened\n");
396                         self.pain_finished = 0;
397                 }
398                 setattachment(self, world, "");
399                 if(time > self.pain_finished)
400                 {
401                         bprint("The ", self.netname, " has returned to base\n");
402                         sound (self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
403                         ctf_EventLog("returned", self.team, world);
404                         ReturnFlag(self);
405                 }
406                 return;
407         }
408
409         e = self.owner;
410         if(e.classname != "player" || (e.deadflag) || (e.flagcarried != self))
411         {
412                 dprint("CANNOT HAPPEN - player dead and STILL had a flag!\n");
413                 DropFlag(self, world, world);
414                 return;
415         }
416
417         if(autocvar_g_ctf_allow_drop)
418         if(e.BUTTON_USE)
419                 DropFlag(self, e, world);
420 }
421
422 void ctf_TouchEvent()
423 {
424         if(gameover) { return; }
425         if(!self) { return; }
426         if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
427         { // The ball fell off the map, respawn it since players can't get to it
428                 ctf_RespawnFlag();
429                 return;
430         }
431         if(other.deadflag != DEAD_NO) { return; }
432         if(other.classname != "player") 
433         {  // the flag just touched an object, most likely the world
434                 pointparticles(particleeffectnum("kaball_sparks"), self.origin, '0 0 0', 1);
435                 sound(self, CHAN_AUTO, "keepaway/touch.wav", VOL_BASE, ATTN_NORM);
436                 return; 
437         }
438         else if(self.wait > time) { return; }
439
440         switch(self.ctf_status) 
441         {       
442                 case FLAG_BASE:
443                         if((other.team == self.team) && (other.flagcarried) && (other.flagcarried.team != self.team))
444                                 ctf_Handle_Capture(self, other); // other just captured the enemies flag to his base
445                         else if((other.team != self.team) && (!other.flagcarried) && (!other.ctf_captureshielded))
446                                 ctf_Handle_Pickup_Base(self, other); // other just stole the enemies flag
447                         break;
448                 
449                 case FLAG_DROPPED:
450                         if(other.team == self.team)
451                                 ctf_Handle_Return(self, other); // other just returned his own flag
452                         else if((!other.flagcarried) && ((other.playerid != self.ctf_dropperid) || (time > self.ctf_droptime + autocvar_g_balance_ctf_delay_collect)))
453                                 ctf_Handle_Pickup_Dropped(self, other); // other just picked up a dropped enemy flag
454                         break;
455                                 
456                 case FLAG_CARRY:
457                 default:
458                         dprint("Someone touched a flag even though it was being carried? wtf?\n");
459                         break; // this should never happen
460         }
461 }
462
463
464 // =======================
465 // CaptureShield Functions 
466 // =======================
467
468 float ctf_CaptureShield_CheckStatus(entity p) // check to see 
469 {
470         float s, se;
471         entity e;
472         float players_worseeq, players_total;
473
474         if(captureshield_max_ratio <= 0)
475                 return FALSE;
476
477         s = PlayerScore_Add(p, SP_SCORE, 0);
478         if(s >= -captureshield_min_negscore)
479                 return FALSE;
480
481         players_total = players_worseeq = 0;
482         FOR_EACH_PLAYER(e)
483         {
484                 if(e.team != p.team)
485                         continue;
486                 se = PlayerScore_Add(e, SP_SCORE, 0);
487                 if(se <= s)
488                         ++players_worseeq;
489                 ++players_total;
490         }
491
492         // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
493         // use this rule here
494         
495         if(players_worseeq >= players_total * captureshield_max_ratio)
496                 return FALSE;
497
498         return TRUE;
499 }
500
501 void ctf_CaptureShield_Update(entity p, float dir)
502 {
503         float should;
504         if(dir == p.ctf_captureshielded) // 0: shield only, 1: unshield only
505         {
506                 should = ctf_CaptureShield_CheckStatus(p);
507                 if(should != dir)
508                 {
509                         if(should) // TODO csqc notifier for this
510                                 centerprint_atprio(p, CENTERPRIO_SHIELDING, "^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.");
511                         else
512                                 centerprint_atprio(p, CENTERPRIO_SHIELDING, "^3You are now free.\n\n^3Feel free to ^1try to capture^3 the flag again\n^3if you think you will succeed.");
513                         
514                         p.ctf_captureshielded = should;
515                 }
516         }
517 }
518
519 float ctf_CaptureShield_Customize()
520 {
521         if not(other.ctf_captureshielded)
522                 return FALSE;
523         if(self.team == other.team)
524                 return FALSE;
525         return TRUE;
526 }
527
528 void ctf_CaptureShield_Touch()
529 {
530         if not(other.ctf_captureshielded)
531                 return;
532         if(self.team == other.team)
533                 return;
534         vector mymid;
535         vector othermid;
536         mymid = (self.absmin + self.absmax) * 0.5;
537         othermid = (other.absmin + other.absmax) * 0.5;
538         Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * captureshield_force);
539         centerprint_atprio(other, CENTERPRIO_SHIELDING, "^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.");
540 }
541
542 void ctf_CaptureShield_Spawn()
543 {
544         entity e;
545         e = spawn();
546         e.enemy = self;
547         e.team = self.team;
548         e.touch = ctf_CaptureShield_Touch;
549         e.customizeentityforclient = ctf_CaptureShield_Customize;
550         e.classname = "ctf_captureshield";
551         e.effects = EF_ADDITIVE;
552         e.movetype = MOVETYPE_NOCLIP;
553         e.solid = SOLID_TRIGGER;
554         e.avelocity = '7 0 11';
555         setorigin(e, self.origin);
556         setmodel(e, "models/ctf/shield.md3");
557         e.scale = 0.5;
558         setsize(e, e.scale * e.mins, e.scale * e.maxs);
559 }
560
561
562 // ==============
563 // Hook Functions
564 // ==============
565
566 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
567 {
568         if(self.flagcarried) { ctf_DropEvent(self); } // figure this out
569         
570         return TRUE;
571 }
572
573
574 // ==========
575 // Spawnfuncs
576 // ==========
577
578 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
579 CTF Starting point for a player in team one (Red).
580 Keys: "angle" viewing angle when spawning. */
581 void spawnfunc_info_player_team1()
582 {
583         if(g_assault) { remove(self); return; }
584         
585         self.team = COLOR_TEAM1; // red
586         spawnfunc_info_player_deathmatch();
587 }
588
589
590 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
591 CTF Starting point for a player in team two (Blue).
592 Keys: "angle" viewing angle when spawning. */
593 void spawnfunc_info_player_team2()
594 {
595         if(g_assault) { remove(self); return; }
596         
597         self.team = COLOR_TEAM2; // blue
598         spawnfunc_info_player_deathmatch();
599 }
600
601 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
602 CTF Starting point for a player in team three (Yellow).
603 Keys: "angle" viewing angle when spawning. */
604 void spawnfunc_info_player_team3()
605 {
606         if(g_assault) { remove(self); return; }
607         
608         self.team = COLOR_TEAM3; // yellow
609         spawnfunc_info_player_deathmatch();
610 }
611
612
613 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
614 CTF Starting point for a player in team four (Purple).
615 Keys: "angle" viewing angle when spawning. */
616 void spawnfunc_info_player_team4()
617 {
618         if(g_assault) { remove(self); return; }
619         
620         self.team = COLOR_TEAM4; // purple
621         spawnfunc_info_player_deathmatch();
622 }
623
624 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
625 CTF flag for team one (Red). Multiple flags are allowed.
626 Keys: 
627 "angle" Angle the flag will point (minus 90 degrees)... 
628 "model" model to use, note this needs red and blue as skins 0 and 1 (default models/ctf/flag.md3)...
629 "noise" sound played when flag is picked up (default ctf/take.wav)...
630 "noise1" sound played when flag is returned by a teammate (default ctf/return.wav)...
631 "noise2" sound played when flag is captured (default ctf/redcapture.wav)...
632 "noise3" sound played when flag is lost in the field and respawns itself (default ctf/respawn.wav)... */
633 void spawnfunc_item_flag_team1()
634 {
635         if(!g_ctf) { remove(self); return; }
636
637         ctf_SetupFlag(1, self);
638 }
639
640 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
641 CTF flag for team two (Blue). Multiple flags are allowed.
642 Keys: 
643 "angle" Angle the flag will point (minus 90 degrees)... 
644 "model" model to use, note this needs red and blue as skins 0 and 1 (default models/ctf/flag.md3)...
645 "noise" sound played when flag is picked up (default ctf/take.wav)...
646 "noise1" sound played when flag is returned by a teammate (default ctf/return.wav)...
647 "noise2" sound played when flag is captured (default ctf/redcapture.wav)...
648 "noise3" sound played when flag is lost in the field and respawns itself (default ctf/respawn.wav)... */
649 void spawnfunc_item_flag_team2()
650 {
651         if(!g_ctf) { remove(self); return; }
652
653         ctf_SetupFlag(0, self);
654 }
655
656 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
657 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
658 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.
659 Keys:
660 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
661 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
662 void spawnfunc_ctf_team()
663 {
664         if(!g_ctf) { remove(self); return; }
665         
666         self.classname = "ctf_team";
667         self.team = self.cnt + 1;
668 }
669
670
671 // ==============
672 // Initialization
673 // ==============
674
675 // code from here on is just to support maps that don't have flag and team entities
676 void ctf_SpawnTeam (string teamname, float teamcolor)
677 {
678         local entity oldself;
679         oldself = self;
680         self = spawn();
681         self.classname = "ctf_team";
682         self.netname = teamname;
683         self.cnt = teamcolor;
684
685         spawnfunc_ctf_team();
686
687         self = oldself;
688 }
689
690 void ctf_DelayedInit()
691 {
692         // if no teams are found, spawn defaults
693         if(find(world, classname, "ctf_team") == world)
694         {
695                 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
696                 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
697         }
698 }
699
700 void ctf_Initialize()
701 {
702         flagcaptimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
703
704         captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
705         captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
706         captureshield_force = autocvar_g_ctf_shield_force;
707
708         g_ctf_win_mode = cvar("g_ctf_win_mode");
709         
710         ScoreRules_ctf();
711         
712         InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
713 }
714
715
716 MUTATOR_DEFINITION(gamemode_ctf)
717 {
718         MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
719         MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
720         MUTATOR_HOOK(PlayerDies, ctf_RemovePlayer, CBC_ORDER_ANY);
721         //MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
722         //MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
723         //MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
724         //MUTATOR_HOOK(PlayerPowerups, ctf_PlayerPowerups, CBC_ORDER_ANY);
725
726         MUTATOR_ONADD
727         {
728                 if(time > 1) // game loads at time 1
729                         error("This is a game type and it cannot be added at runtime.");
730                 g_ctf = 1;
731                 ctf_Initialize();
732         }
733
734         MUTATOR_ONREMOVE
735         {
736                 g_ctf = 0;
737                 error("This is a game type and it cannot be removed at runtime.");
738         }
739
740         return TRUE;
741 }