1 // ================================================================
2 // Official capture the flag game mode coding, reworked by Samual
3 // Last updated: March 28th, 2011
4 // ================================================================
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'
11 .entity bot_basewaypoint; // flag waypointsprite
13 .entity wps_flagcarrier;
14 .entity wps_flagdropped;
16 entity ctf_worldflaglist; // CTF flags in the map
17 .entity ctf_worldflagnext;
19 float ctf_captimerecord; // record time for capturing the flag
20 .float ctf_pickuptime;
22 .float ctf_dropperid; // don't allow spam of dropping the flag
24 .float ctf_status; // status of the flag (FLAG_BASE, FLAG_DROPPED, FLAG_CARRY declared globally)
26 .float next_take_time; // Delay between when the person can pick up a flag // is this obsolete from the stuff above?
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 ctf_captureshield_min_negscore; // punish at -20 points
31 float ctf_captureshield_max_ratio; // punish at most 30% of each team
32 float ctf_captureshield_force; // push force of the shield
34 // declare functions so they can be used in any order in the file
36 void ctf_FlagTouch(void);
37 void ctf_FlagThink(void);
38 void ctf_SetupFlag(float, entity);
39 void ctf_RespawnFlag(entity);
46 float ctf_ReadScore(string parameter) // make this obsolete
48 if(g_ctf_win_mode != 2)
49 return cvar(strcat("g_ctf_personal", parameter));
51 return cvar(strcat("g_ctf_flag", parameter));
54 void ctf_FakeTimeLimit(entity e, float t)
57 WriteByte(MSG_ONE, 3); // svc_updatestat
58 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
60 WriteCoord(MSG_ONE, autocvar_timelimit);
62 WriteCoord(MSG_ONE, (t + 1) / 60);
65 void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
67 if(autocvar_sv_eventlog)
68 GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
72 // =======================
73 // CaptureShield Functions
74 // =======================
76 float ctf_CaptureShield_CheckStatus(entity p)
80 float players_worseeq, players_total;
82 if(ctf_captureshield_max_ratio <= 0)
85 s = PlayerScore_Add(p, SP_SCORE, 0);
86 if(s >= -ctf_captureshield_min_negscore)
89 players_total = players_worseeq = 0;
94 se = PlayerScore_Add(e, SP_SCORE, 0);
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
103 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
109 void ctf_CaptureShield_Update(entity player, float wanted_status)
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
114 if(updated_status) // TODO csqc notifier for this // Samual: How?
115 centerprint_atprio(player, 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.");
117 centerprint_atprio(player, 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.");
119 player.ctf_captureshielded = updated_status;
123 float ctf_CaptureShield_Customize()
125 if not(other.ctf_captureshielded)
127 if(self.team == other.team)
132 void ctf_CaptureShield_Touch()
134 if not(other.ctf_captureshielded)
136 if(self.team == other.team)
140 mymid = (self.absmin + self.absmax) * 0.5;
141 othermid = (other.absmin + other.absmax) * 0.5;
142 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
143 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.");
146 void ctf_CaptureShield_Spawn(entity flag)
152 e.touch = ctf_CaptureShield_Touch;
153 e.customizeentityforclient = ctf_CaptureShield_Customize;
154 e.classname = "ctf_captureshield";
155 e.effects = EF_ADDITIVE;
156 e.movetype = MOVETYPE_NOCLIP;
157 e.solid = SOLID_TRIGGER;
158 e.avelocity = '7 0 11';
159 setorigin(e, self.origin);
160 setmodel(e, "models/ctf/shield.md3");
162 setsize(e, e.scale * e.mins, e.scale * e.maxs);
170 void ctf_Handle_Drop(entity player) // make sure this works
172 entity flag = player.flagcarried;
174 if(!flag) { return; }
175 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
178 setattachment(flag, world, "");
179 setorigin(flag, player.origin - '0 0 24' + '0 0 37');
180 flag.owner.flagcarried = world;
182 flag.movetype = MOVETYPE_TOSS;
183 flag.solid = SOLID_TRIGGER;
184 flag.takedamage = DAMAGE_YES;
185 flag.velocity = ('0 0 200' + ('0 100 0' * crandom()) + ('100 0 0' * crandom()));
186 flag.pain_finished = time + autocvar_g_ctf_flag_returntime; // replace this later
188 flag.ctf_droptime = time;
189 flag.ctf_dropperid = player.playerid;
190 flag.ctf_status = FLAG_DROPPED;
192 // messages and sounds
193 Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
194 sound(flag, CHAN_TRIGGER, flag.noise4, VOL_BASE, ATTN_NONE);
195 ctf_EventLog("dropped", player.team, player);
198 PlayerTeamScore_AddScore(player, -ctf_ReadScore("penalty_drop"));
199 PlayerScore_Add(player, SP_CTF_DROPS, 1);
202 WaypointSprite_Spawn("flagdropped", 0, 0, flag, '0 0 64', world, player.team, flag, wps_flagdropped, FALSE); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team)
203 WaypointSprite_Ping(player.wps_flagcarrier);
204 WaypointSprite_Kill(player.wps_flagcarrier);
207 ctf_CaptureShield_Update(player, 0); // shield only
209 // check if the flag will fall off the map
210 trace_startsolid = FALSE;
211 tracebox(flag.origin, flag.mins, flag.maxs, flag.origin, TRUE, flag);
213 dprint("FLAG FALLTHROUGH will happen SOON\n");
216 void ctf_Handle_Capture(entity flag, entity player) // make sure this works
219 float cap_time, cap_record, success;
220 string cap_message, refername;
223 if((autocvar_g_ctf_captimerecord_always) || (player_count - currentbots)) {
224 cap_record = ctf_captimerecord;
225 cap_time = (time - player.flagcarried.ctf_pickuptime);
227 refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
228 refername = ((refername == player.netname) ? "their" : strcat(refername, "^7's"));
230 if(!ctf_captimerecord)
231 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds"); success = TRUE; }
232 else if(cap_time < cap_record)
233 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, breaking ", refername, " previous record of ", ftos_decimals(cap_record, 2), " seconds"); success = TRUE; }
235 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, failing to break ", refername, " record of ", ftos_decimals(cap_record, 2), " seconds"); success = FALSE; }
238 ctf_captimerecord = cap_time;
239 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
240 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
241 write_recordmarker(player, (time - cap_time), cap_time); } }
243 // messages and sounds
244 Send_KillNotification(player.netname, player.flagcarried.netname, cap_message, INFO_CAPTUREFLAG, MSG_INFO);
245 sound(player, CHAN_AUTO, flag.noise2, VOL_BASE, ATTN_NONE); // "ctf/*_capture.wav"
246 ctf_EventLog("capture", player.flagcarried.team, player);
249 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_capture"));
250 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
253 if (autocvar_g_ctf_flag_capture_effects)
255 pointparticles(particleeffectnum((player.team == COLOR_TEAM1) ? "red_ground_quake" : "blue_ground_quake"), flag.origin, '0 0 0', 1);
256 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
260 WaypointSprite_Kill(player.wps_flagcarrier);
263 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
265 ctf_RespawnFlag(player.flagcarried);
268 void ctf_Handle_Return(entity flag, entity player) // make sure this works
270 // messages and sounds
271 Send_KillNotification (player.netname, flag.netname, "", INFO_RETURNFLAG, MSG_INFO);
272 sound(player, CHAN_AUTO, flag.noise1, VOL_BASE, ATTN_NONE);
273 ctf_EventLog("return", flag.team, player);
276 PlayerTeamScore_AddScore(player, ctf_ReadScore(strcat("score_return", ((player.playerid == flag.playerid) ? "_by_killer" : "")))); // reward for return
277 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
279 TeamScore_AddToTeam(((flag.team == COLOR_TEAM1) ? COLOR_TEAM2 : COLOR_TEAM1), ST_SCORE, -ctf_ReadScore("penalty_returned")); // punish the team who was last carrying it
280 FOR_EACH_PLAYER(player) if(player.playerid == flag.ctf_dropperid) // punish the player who dropped the flag
282 PlayerScore_Add(player, SP_SCORE, -ctf_ReadScore("penalty_returned"));
283 ctf_CaptureShield_Update(player, 0); // shield only
287 WaypointSprite_Kill(flag.wps_flagdropped);
290 ctf_RespawnFlag(flag);
293 void ctf_Handle_Pickup_Base(entity flag, entity player) // make sure this works
295 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
296 string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
298 // attach the flag to the player
300 player.flagcarried = flag;
301 setattachment(flag, player, "");
302 setorigin(flag, FLAG_CARRY_POS);
305 flag.movetype = MOVETYPE_NONE;
306 flag.takedamage = DAMAGE_NO;
307 flag.solid = SOLID_NOT;
308 flag.angles = '0 0 0';
309 flag.ctf_pickuptime = time; // used for timing runs
310 flag.ctf_pickupid = player.playerid;
311 flag.ctf_status = FLAG_CARRY;
313 // messages and sounds
314 Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
315 sound(player, CHAN_AUTO, flag.noise, VOL_BASE, ATTN_NONE);
316 ctf_EventLog("steal", flag.team, player);
317 verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat("(", player.netname, ")") : ""); // replace TRUE with an autocvar for it.
318 FOR_EACH_PLAYER(tmp_player)
319 if(tmp_player.team == flag.team)
320 centerprint(tmp_player, strcat("The enemy ", verbosename, "got your flag! Retrieve it!"));
321 else if((tmp_player.team == player.team) && (tmp_player != player))
322 centerprint(tmp_player, strcat("Your team mate ", verbosename, "got the flag! Protect them!"));
325 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_pickup_base"));
326 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
329 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
330 if((player.speedrunning) && (ctf_captimerecord))
331 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
334 if (autocvar_g_ctf_flag_pickup_effects)
336 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
340 WaypointSprite_Spawn("flagcarrier", 0, 0, player, '0 0 64', world, player.team, player, wps_flagcarrier, FALSE); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team)
341 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
342 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
343 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0');
344 WaypointSprite_Ping(player.wps_flagcarrier);
347 void ctf_Handle_Pickup_Dropped(entity flag, entity player) // make sure this works
350 float returnscore = bound(0, (flag.pain_finished - time) / autocvar_g_ctf_flag_returntime, 1); // can this be division by zero?
351 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
352 string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
354 // attach the flag to the player
356 player.flagcarried = flag;
357 setattachment(flag, player, "");
358 setorigin(flag, FLAG_CARRY_POS);
361 flag.movetype = MOVETYPE_NONE;
362 flag.takedamage = DAMAGE_NO;
363 flag.solid = SOLID_NOT;
364 flag.angles = '0 0 0';
365 flag.ctf_pickuptime = time; // used for timing runs
366 flag.ctf_pickupid = player.playerid;
367 flag.ctf_status = FLAG_CARRY;
369 // messages and sounds
370 Send_KillNotification (player.netname, flag.netname, "", INFO_PICKUPFLAG, MSG_INFO);
371 sound (player, CHAN_AUTO, flag.noise, VOL_BASE, ATTN_NONE);
372 ctf_EventLog("pickup", flag.team, player);
373 verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat("(", player.netname, ")") : "");
374 FOR_EACH_PLAYER(tmp_player)
375 if(tmp_player.team == flag.team)
376 centerprint(tmp_player, strcat("The enemy ", verbosename, "got your flag! Retrieve it!"));
377 else if((tmp_player.team == player.team) && (tmp_player != player))
378 centerprint(tmp_player, strcat("Your team mate ", verbosename, "got the flag! Protect them!"));
381 returnscore = floor((ctf_ReadScore("score_pickup_dropped_late") * (1-returnscore) + ctf_ReadScore("score_pickup_dropped_early") * returnscore) + 0.5);
382 print("score is ", ftos(returnscore), "\n");
383 PlayerTeamScore_AddScore(player, returnscore);
384 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
387 if (autocvar_g_ctf_flag_pickup_effects) // field pickup effect
389 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
393 WaypointSprite_Kill(flag.wps_flagdropped);
394 WaypointSprite_Spawn("flagcarrier", 0, 0, player, '0 0 64', world, player.team, player, wps_flagcarrier, FALSE); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team)
395 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
396 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
397 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0');
398 WaypointSprite_Ping(player.wps_flagcarrier);
402 // ===================
403 // Main Flag Functions
404 // ===================
406 void ctf_FlagThink() // make sure this works
411 self.nextthink = time + 0.1;
414 if(self == ctf_worldflaglist) // only for the first flag
415 FOR_EACH_CLIENT(tmp_entity)
416 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
419 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
420 dprint("wtf the flag got squished?\n");
421 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
422 if(!trace_startsolid) // can we resize it without getting stuck?
423 setsize(self, FLAG_MIN, FLAG_MAX); }
425 if(self.owner.classname != "player" || (self.owner.deadflag) || (self.owner.flagcarried != self)) {
426 dprint("CANNOT HAPPEN - player dead and STILL had a flag!\n");
427 ctf_Handle_Drop(self.owner);
431 switch(self.ctf_status)
433 case FLAG_BASE: // nothing to do here
437 // flag fallthrough? FIXME remove this if bug is really fixed now
438 if(self.origin_z < -131072)
440 dprint("FLAG FALLTHROUGH just happened\n");
441 self.pain_finished = 0;
443 setattachment(self, world, "");
444 if(time > self.pain_finished)
446 bprint("The ", self.netname, " has returned to base\n");
447 sound (self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
448 ctf_EventLog("returned", self.team, world);
449 ctf_RespawnFlag(self);
454 if((self.owner) && (self.speedrunning) && (ctf_captimerecord) && (time >= self.ctf_pickuptime + ctf_captimerecord))
456 bprint("The ", self.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n");
457 sound (self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
459 self.owner.impulse = 141; // returning!
463 ctf_RespawnFlag(tmp_entity);
470 dprint("Think: Flag exists with no status?\n");
471 return; // this should never happen
477 if(gameover) { return; }
478 if(!self) { return; }
479 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
480 { // The flag fell off the map, respawn it since players can't get to it
481 //ctf_RespawnFlag(self);
484 if(other.deadflag != DEAD_NO) { return; }
485 if(other.classname != "player")
486 { // The flag just touched an object, most likely the world
487 pointparticles(particleeffectnum("kaball_sparks"), self.origin, '0 0 0', 1);
488 sound(self, CHAN_AUTO, "keepaway/touch.wav", VOL_BASE, ATTN_NORM);
491 else if(self.wait > time) { return; }
493 switch(self.ctf_status)
496 if((other.team == self.team) && (other.flagcarried) && (other.flagcarried.team != self.team))
497 ctf_Handle_Capture(self, other); // other just captured the enemies flag to his base
498 else if((other.team != self.team) && (!other.flagcarried) && (!other.ctf_captureshielded))
499 ctf_Handle_Pickup_Base(self, other); // other just stole the enemies flag
503 if(other.team == self.team)
504 ctf_Handle_Return(self, other); // other just returned his own flag
505 else if((!other.flagcarried) && ((other.playerid != self.ctf_dropperid) || (time > self.ctf_droptime + autocvar_g_balance_ctf_delay_collect)))
506 ctf_Handle_Pickup_Dropped(self, other); // other just picked up a dropped enemy flag
510 dprint("Someone touched a flag even though it was being carried?\n");
514 dprint("Flag exists with no status?\n");
515 break; // this should never happen
519 void ctf_RespawnFlag(entity flag) // make sure this works
521 if(flag.classname != "item_flag_team") { backtrace("ctf_RespawnFlag was called incorrectly."); return; }
523 // reset the player (if there is one)
524 if((flag.owner) && (flag.owner.flagcarried == flag))
526 WaypointSprite_Kill(flag.wps_flagcarrier);
527 flag.owner.flagcarried = world;
529 if(flag.speedrunning)
530 ctf_FakeTimeLimit(flag.owner, -1);
534 setattachment(flag, world, "");
535 setorigin(flag, flag.dropped_origin); // replace with flag.ctf_spawnorigin
536 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
537 flag.takedamage = DAMAGE_NO;
538 flag.solid = SOLID_TRIGGER;
539 flag.velocity = '0 0 0';
540 flag.angles = flag.mangle;
541 flag.ctf_status = FLAG_BASE;
542 flag.flags = FL_ITEM;
549 if(self.owner.classname == "player")
550 ctf_Handle_Drop(self.owner);
552 ctf_RespawnFlag(self);
555 void ctf_SetupFlag(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
558 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.
561 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist // todo: find out if this can be simplified
562 ctf_worldflaglist = flag;
564 setattachment(flag, world, "");
566 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
567 flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
568 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
569 flag.classname = "item_flag_team";
570 flag.target = "###item###"; // wut?
571 flag.flags = FL_ITEM;
572 flag.solid = SOLID_TRIGGER;
573 flag.velocity = '0 0 0';
574 flag.ctf_status = FLAG_BASE;
575 flag.mangle = flag.angles;
576 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
578 if(flag.spawnflags & 1) // I don't understand what all this is about.
581 flag.dropped_origin = flag.origin;
582 flag.movetype = MOVETYPE_NONE;
583 print("This map was loaded with flags using MOVETYPE_NONE\n");
587 flag.noalign = FALSE;
589 flag.movetype = MOVETYPE_TOSS;
590 print("This map was loaded with flags using MOVETYPE_TOSS\n");
593 flag.reset = ctf_Reset;
594 flag.touch = ctf_FlagTouch;
597 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
598 setmodel (flag, flag.model); // precision set below
599 setsize(flag, FLAG_MIN, FLAG_MAX);
600 setorigin(flag, flag.origin);
601 if(!flag.scale) { flag.scale = 0.6; }
603 flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin);
605 if(autocvar_g_ctf_flag_glowtrails)
607 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
612 flag.effects |= EF_LOWPRECISION;
613 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
614 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
617 if(!flag.noise) { flag.noise = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
618 if(!flag.noise1) { flag.noise1 = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
619 if(!flag.noise2) { flag.noise2 = ((teamnumber) ? "ctf/red_capture.wav" : "ctf/blue_capture.wav"); } // blue team scores by capturing the red flag
620 if(!flag.noise3) { flag.noise3 = "ctf/flag_respawn.wav"; } // if there is ever a team-based sound for this, update the code to match.
621 if(!flag.noise4) { flag.noise4 = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
624 precache_sound(flag.noise);
625 precache_sound(flag.noise1);
626 precache_sound(flag.noise2);
627 precache_sound(flag.noise3);
628 precache_sound(flag.noise4);
629 precache_model(flag.model);
630 precache_model("models/ctf/shield.md3");
631 precache_model("models/ctf/shockwavetransring.md3");
634 waypoint_spawnforitem_force(flag, flag.origin);
635 flag.nearestwaypointtimeout = 0; // activate waypointing again
636 flag.bot_basewaypoint = flag.nearestwaypoint;
639 WaypointSprite_SpawnFixed(((teamnumber) ? "redbase" : "bluebase"), flag.origin + '0 0 64', flag, wps_flagbase);
640 WaypointSprite_UpdateTeamRadar(flag.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE));
642 // captureshield setup
643 ctf_CaptureShield_Spawn(flag);
651 // g_ctf_ignore_frags
653 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
655 if(self.flagcarried) { ctf_Handle_Drop(self); }
659 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
663 // initially clear items so they can be set as necessary later.
664 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
665 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
667 // item for stopping players from capturing the flag too often
668 if(self.ctf_captureshielded)
669 self.items |= IT_CTF_SHIELDED;
671 // scan through all the flags and notify the client about them
672 for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
674 if(flag.ctf_status == FLAG_CARRY)
675 if(flag.owner == self)
676 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
678 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
679 else if(flag.ctf_status == FLAG_DROPPED)
680 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
683 if((autocvar_g_ctf_allow_drop) && (self.BUTTON_USE))
684 ctf_Handle_Drop(self);
689 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
691 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
693 if(frag_target == frag_attacker) // damage done to yourself
695 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
696 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
698 else // damage done to noncarriers
700 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
701 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
707 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
709 frag_score = 0; // no frags counted in keepaway
710 return (g_ctf_ignore_frags); // you deceptive little bugger ;3 This needs to be true in order for this function to even count.
718 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
719 CTF Starting point for a player in team one (Red).
720 Keys: "angle" viewing angle when spawning. */
721 void spawnfunc_info_player_team1()
723 if(g_assault) { remove(self); return; }
725 self.team = COLOR_TEAM1; // red
726 spawnfunc_info_player_deathmatch();
730 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
731 CTF Starting point for a player in team two (Blue).
732 Keys: "angle" viewing angle when spawning. */
733 void spawnfunc_info_player_team2()
735 if(g_assault) { remove(self); return; }
737 self.team = COLOR_TEAM2; // blue
738 spawnfunc_info_player_deathmatch();
741 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
742 CTF Starting point for a player in team three (Yellow).
743 Keys: "angle" viewing angle when spawning. */
744 void spawnfunc_info_player_team3()
746 if(g_assault) { remove(self); return; }
748 self.team = COLOR_TEAM3; // yellow
749 spawnfunc_info_player_deathmatch();
753 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
754 CTF Starting point for a player in team four (Purple).
755 Keys: "angle" viewing angle when spawning. */
756 void spawnfunc_info_player_team4()
758 if(g_assault) { remove(self); return; }
760 self.team = COLOR_TEAM4; // purple
761 spawnfunc_info_player_deathmatch();
764 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
765 CTF flag for team one (Red). Multiple flags are allowed.
767 "angle" Angle the flag will point (minus 90 degrees)...
768 "model" model to use, note this needs red and blue as skins 0 and 1 (default models/ctf/flag.md3)...
769 "noise" sound played when flag is picked up (default ctf/take.wav)...
770 "noise1" sound played when flag is returned by a teammate (default ctf/return.wav)...
771 "noise2" sound played when flag is captured (default ctf/redcapture.wav)...
772 "noise3" sound played when flag is lost in the field and respawns itself (default ctf/respawn.wav)... */
773 void spawnfunc_item_flag_team1()
775 if(!g_ctf) { remove(self); return; }
777 ctf_SetupFlag(1, self); // 1 = red
780 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
781 CTF flag for team two (Blue). Multiple flags are allowed.
783 "angle" Angle the flag will point (minus 90 degrees)...
784 "model" model to use, note this needs red and blue as skins 0 and 1 (default models/ctf/flag.md3)...
785 "noise" sound played when flag is picked up (default ctf/take.wav)...
786 "noise1" sound played when flag is returned by a teammate (default ctf/return.wav)...
787 "noise2" sound played when flag is captured (default ctf/redcapture.wav)...
788 "noise3" sound played when flag is lost in the field and respawns itself (default ctf/respawn.wav)... */
789 void spawnfunc_item_flag_team2()
791 if(!g_ctf) { remove(self); return; }
793 ctf_SetupFlag(0, self); // the 0 is misleading, but -- 0 = blue.
796 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
797 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
798 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.
800 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
801 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
802 void spawnfunc_ctf_team()
804 if(!g_ctf) { remove(self); return; }
806 self.classname = "ctf_team";
807 self.team = self.cnt + 1;
815 // code from here on is just to support maps that don't have flag and team entities
816 void ctf_SpawnTeam (string teamname, float teamcolor)
821 self.classname = "ctf_team";
822 self.netname = teamname;
823 self.cnt = teamcolor;
825 spawnfunc_ctf_team();
830 void ctf_DelayedInit()
832 // if no teams are found, spawn defaults
833 if(find(world, classname, "ctf_team") == world)
835 print("NO TEAMS FOUND FOR CTF! creating them anyway.\n");
836 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
837 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
841 void ctf_Initialize()
843 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
845 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
846 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
847 ctf_captureshield_force = autocvar_g_ctf_shield_force;
849 g_ctf_win_mode = cvar("g_ctf_win_mode");
853 // does it really need to be delayed? todo: Find out with a map that is broken.
855 //InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
859 MUTATOR_DEFINITION(gamemode_ctf)
861 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
862 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
863 MUTATOR_HOOK(PlayerDies, ctf_RemovePlayer, CBC_ORDER_ANY);
864 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
865 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
866 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
867 //MUTATOR_HOOK(PlayerPowerups, ctf_PlayerPowerups, CBC_ORDER_ANY);
871 if(time > 1) // game loads at time 1
872 error("This is a game type and it cannot be added at runtime.");
880 error("This is a game type and it cannot be removed at runtime.");