1 // ================================================================
2 // Official capture the flag game mode coding, reworked by Samual
3 // Last updated: March 27th, 2012
4 // ================================================================
6 float ctf_ReadScore(string parameter) // make this obsolete
8 //if(g_ctf_win_mode != 2)
9 return cvar(strcat("g_ctf_personal", parameter));
11 // return cvar(strcat("g_ctf_flag", parameter));
14 void ctf_FakeTimeLimit(entity e, float t)
17 WriteByte(MSG_ONE, 3); // svc_updatestat
18 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
20 WriteCoord(MSG_ONE, autocvar_timelimit);
22 WriteCoord(MSG_ONE, (t + 1) / 60);
25 void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
27 if(autocvar_sv_eventlog)
28 GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
32 // =======================
33 // CaptureShield Functions
34 // =======================
36 float ctf_CaptureShield_CheckStatus(entity p)
40 float players_worseeq, players_total;
42 if(ctf_captureshield_max_ratio <= 0)
45 s = PlayerScore_Add(p, SP_SCORE, 0);
46 if(s >= -ctf_captureshield_min_negscore)
49 players_total = players_worseeq = 0;
54 se = PlayerScore_Add(e, SP_SCORE, 0);
60 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
63 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
69 void ctf_CaptureShield_Update(entity player, float wanted_status)
71 float updated_status = ctf_CaptureShield_CheckStatus(player);
72 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
74 if(updated_status) // TODO csqc notifier for this // Samual: How?
75 Send_CSQC_Centerprint_Generic(player, CPID_CTF_CAPTURESHIELD, "^3You are now ^4shielded^3 from the flag\n^3for ^1too many unsuccessful attempts^3 to capture.\n\n^3Make some defensive scores before trying again.", 5, 0);
77 Send_CSQC_Centerprint_Generic(player, CPID_CTF_CAPTURESHIELD, "^3You are now free.\n\n^3Feel free to ^1try to capture^3 the flag again\n^3if you think you will succeed.", 5, 0);
79 player.ctf_captureshielded = updated_status;
83 float ctf_CaptureShield_Customize()
85 if not(other.ctf_captureshielded)
87 if(self.team == other.team)
92 void ctf_CaptureShield_Touch()
94 if not(other.ctf_captureshielded)
96 if(self.team == other.team)
100 mymid = (self.absmin + self.absmax) * 0.5;
101 othermid = (other.absmin + other.absmax) * 0.5;
102 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
103 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);
106 void ctf_CaptureShield_Spawn(entity flag)
112 e.touch = ctf_CaptureShield_Touch;
113 e.customizeentityforclient = ctf_CaptureShield_Customize;
114 e.classname = "ctf_captureshield";
115 e.effects = EF_ADDITIVE;
116 e.movetype = MOVETYPE_NOCLIP;
117 e.solid = SOLID_TRIGGER;
118 e.avelocity = '7 0 11';
119 setorigin(e, self.origin);
120 setmodel(e, "models/ctf/shield.md3");
122 setsize(e, e.scale * e.mins, e.scale * e.maxs);
130 void ctf_Handle_Drop(entity player)
132 entity flag = player.flagcarried;
134 if(!flag) { return; }
135 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
138 setattachment(flag, world, "");
139 setorigin(flag, player.origin - '0 0 24' + '0 0 37');
140 flag.owner.flagcarried = world;
142 flag.movetype = MOVETYPE_TOSS;
143 flag.solid = SOLID_TRIGGER;
144 flag.takedamage = DAMAGE_YES;
145 flag.velocity = ('0 0 200' + ('0 100 0' * crandom()) + ('100 0 0' * crandom()));
146 flag.pain_finished = time + autocvar_g_ctf_flag_returntime; // replace this later
148 flag.ctf_droptime = time;
149 flag.ctf_dropperid = player.playerid;
150 flag.ctf_status = FLAG_DROPPED;
152 // messages and sounds
153 Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
154 sound(flag, CH_TRIGGER, flag.noise4, VOL_BASE, ATTN_NONE);
155 ctf_EventLog("dropped", player.team, player);
158 PlayerTeamScore_AddScore(player, -ctf_ReadScore("penalty_drop"));
159 PlayerScore_Add(player, SP_CTF_DROPS, 1);
162 WaypointSprite_Spawn("flagdropped", 0, 0, flag, '0 0 64', world, player.team, flag, wps_flagdropped, FALSE, RADARICON_FLAG, '0 1 1'); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team)
163 WaypointSprite_Ping(player.wps_flagcarrier);
164 WaypointSprite_Kill(player.wps_flagcarrier);
167 ctf_CaptureShield_Update(player, 0); // shield only
169 // check if the flag will fall off the map
170 trace_startsolid = FALSE;
171 tracebox(flag.origin, flag.mins, flag.maxs, flag.origin, TRUE, flag);
173 dprint("FLAG FALLTHROUGH will happen SOON\n");
176 void ctf_Handle_Capture(entity flag, entity player)
179 float cap_time, cap_record, success;
180 string cap_message, refername;
183 if((autocvar_g_ctf_captimerecord_always) || (player_count - currentbots)) {
184 cap_record = ctf_captimerecord;
185 cap_time = (time - player.flagcarried.ctf_pickuptime);
187 refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
188 refername = ((refername == player.netname) ? "their" : strcat(refername, "^7's"));
190 if(!ctf_captimerecord)
191 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds"); success = TRUE; }
192 else if(cap_time < cap_record)
193 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, breaking ", refername, " previous record of ", ftos_decimals(cap_record, 2), " seconds"); success = TRUE; }
195 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, failing to break ", refername, " record of ", ftos_decimals(cap_record, 2), " seconds"); success = FALSE; }
198 ctf_captimerecord = cap_time;
199 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
200 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
201 write_recordmarker(player, (time - cap_time), cap_time); } }
203 // messages and sounds
204 Send_KillNotification(player.netname, player.flagcarried.netname, cap_message, INFO_CAPTUREFLAG, MSG_INFO);
205 sound(player, CH_TRIGGER, flag.noise2, VOL_BASE, ATTN_NONE); // "ctf/*_capture.wav"
206 ctf_EventLog("capture", player.flagcarried.team, player);
209 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_capture"));
210 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
213 if (autocvar_g_ctf_flag_capture_effects)
215 pointparticles(particleeffectnum((player.team == COLOR_TEAM1) ? "red_ground_quake" : "blue_ground_quake"), flag.origin, '0 0 0', 1);
216 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
220 WaypointSprite_Kill(player.wps_flagcarrier);
223 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
225 ctf_RespawnFlag(player.flagcarried);
228 void ctf_Handle_Return(entity flag, entity player)
230 // messages and sounds
231 Send_KillNotification (player.netname, flag.netname, "", INFO_RETURNFLAG, MSG_INFO);
232 sound(player, CH_TRIGGER, flag.noise1, VOL_BASE, ATTN_NONE);
233 ctf_EventLog("return", flag.team, player);
236 PlayerTeamScore_AddScore(player, ctf_ReadScore(strcat("score_return", ((player.playerid == flag.playerid) ? "_by_killer" : "")))); // reward for return
237 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
239 TeamScore_AddToTeam(((flag.team == COLOR_TEAM1) ? COLOR_TEAM2 : COLOR_TEAM1), ST_SCORE, -ctf_ReadScore("penalty_returned")); // punish the team who was last carrying it
240 FOR_EACH_PLAYER(player) if(player.playerid == flag.ctf_dropperid) // punish the player who dropped the flag
242 PlayerScore_Add(player, SP_SCORE, -ctf_ReadScore("penalty_returned"));
243 ctf_CaptureShield_Update(player, 0); // shield only
247 WaypointSprite_Kill(flag.wps_flagdropped);
250 ctf_RespawnFlag(flag);
253 void ctf_Handle_Pickup_Base(entity flag, entity player)
255 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
256 string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
258 // attach the flag to the player
260 player.flagcarried = flag;
261 setattachment(flag, player, "");
262 setorigin(flag, FLAG_CARRY_POS);
265 flag.movetype = MOVETYPE_NONE;
266 flag.takedamage = DAMAGE_NO;
267 flag.solid = SOLID_NOT;
268 flag.angles = '0 0 0';
269 flag.ctf_pickuptime = time; // used for timing runs
270 flag.ctf_pickupid = player.playerid;
271 flag.ctf_status = FLAG_CARRY;
273 // messages and sounds
274 Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
275 sound(player, CH_TRIGGER, flag.noise, VOL_BASE, ATTN_NONE);
276 ctf_EventLog("steal", flag.team, player);
277 verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat("(", player.netname, ")") : ""); // replace TRUE with an autocvar for it.
278 FOR_EACH_PLAYER(tmp_player)
279 if(tmp_player.team == flag.team)
280 centerprint(tmp_player, strcat("The enemy ", verbosename, "got your flag! Retrieve it!"));
281 else if((tmp_player.team == player.team) && (tmp_player != player))
282 centerprint(tmp_player, strcat("Your team mate ", verbosename, "got the flag! Protect them!"));
285 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_pickup_base"));
286 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
289 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
290 if((player.speedrunning) && (ctf_captimerecord))
291 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
294 if (autocvar_g_ctf_flag_pickup_effects)
296 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
300 WaypointSprite_Spawn("flagcarrier", 0, 0, player, '0 0 64', world, player.team, player, wps_flagcarrier, FALSE, RADARICON_FLAG, '1 1 0'); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team)
301 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
302 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
303 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0');
304 WaypointSprite_Ping(player.wps_flagcarrier);
307 void ctf_Handle_Pickup_Dropped(entity flag, entity player) // make sure this works
310 float returnscore = bound(0, (flag.pain_finished - time) / autocvar_g_ctf_flag_returntime, 1); // can this be division by zero?
311 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
312 string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
314 // attach the flag to the player
316 player.flagcarried = flag;
317 setattachment(flag, player, "");
318 setorigin(flag, FLAG_CARRY_POS);
321 flag.movetype = MOVETYPE_NONE;
322 flag.takedamage = DAMAGE_NO;
323 flag.solid = SOLID_NOT;
324 flag.angles = '0 0 0';
325 //flag.ctf_pickuptime = time; // don't update pickuptime since this isn't a real steal.
326 flag.ctf_pickupid = player.playerid;
327 flag.ctf_status = FLAG_CARRY;
329 // messages and sounds
330 Send_KillNotification (player.netname, flag.netname, "", INFO_PICKUPFLAG, MSG_INFO);
331 sound (player, CH_TRIGGER, flag.noise, VOL_BASE, ATTN_NONE);
332 ctf_EventLog("pickup", flag.team, player);
333 verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat("(", player.netname, ")") : "");
334 FOR_EACH_PLAYER(tmp_player)
335 if(tmp_player.team == flag.team)
336 centerprint(tmp_player, strcat("The enemy ", verbosename, "got your flag! Retrieve it!"));
337 else if((tmp_player.team == player.team) && (tmp_player != player))
338 centerprint(tmp_player, strcat("Your team mate ", verbosename, "got the flag! Protect them!"));
341 returnscore = floor((ctf_ReadScore("score_pickup_dropped_late") * (1-returnscore) + ctf_ReadScore("score_pickup_dropped_early") * returnscore) + 0.5);
342 print("score is ", ftos(returnscore), "\n");
343 PlayerTeamScore_AddScore(player, returnscore);
344 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
347 if (autocvar_g_ctf_flag_pickup_effects) // field pickup effect
349 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
353 WaypointSprite_Kill(flag.wps_flagdropped);
354 WaypointSprite_Spawn("flagcarrier", 0, 0, player, '0 0 64', world, player.team, player, wps_flagcarrier, FALSE, RADARICON_FLAG, '1 1 0'); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team)
355 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
356 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
357 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0');
358 WaypointSprite_Ping(player.wps_flagcarrier);
362 // ===================
363 // Main Flag Functions
364 // ===================
371 self.nextthink = time + 0.1; // only 10 fps, more is unnecessary.
374 if(self == ctf_worldflaglist) // only for the first flag
375 FOR_EACH_CLIENT(tmp_entity)
376 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
379 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
380 dprint("wtf the flag got squished?\n");
381 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
382 if(!trace_startsolid) // can we resize it without getting stuck?
383 setsize(self, FLAG_MIN, FLAG_MAX); }
385 if(self.owner.classname != "player" || (self.owner.deadflag) || (self.owner.flagcarried != self)) {
386 dprint("CANNOT HAPPEN - player dead and STILL had a flag!\n");
387 ctf_Handle_Drop(self.owner);
391 switch(self.ctf_status)
393 case FLAG_BASE: // nothing to do here
397 // flag fallthrough? FIXME remove this if bug is really fixed now
398 if(self.origin_z < -131072)
400 dprint("FLAG FALLTHROUGH just happened\n");
401 self.pain_finished = 0;
403 setattachment(self, world, "");
404 if(time > self.pain_finished)
406 bprint("The ", self.netname, " has returned to base\n");
407 sound (self, CH_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
408 ctf_EventLog("returned", self.team, world);
409 ctf_RespawnFlag(self);
414 if((self.owner) && (self.speedrunning) && (ctf_captimerecord) && (time >= self.ctf_pickuptime + ctf_captimerecord))
416 bprint("The ", self.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n");
417 sound (self, CH_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
419 self.owner.impulse = 141; // returning!
423 ctf_RespawnFlag(tmp_entity);
429 default: // this should never happen
430 dprint("Think: Flag exists with no status?\n");
437 if(gameover) { return; }
438 if(!self) { return; }
439 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
440 { // The flag fell off the map, respawn it since players can't get to it
441 ctf_RespawnFlag(self);
444 if(other.deadflag != DEAD_NO) { return; }
445 if(other.classname != "player")
446 { // The flag just touched an object, most likely the world
447 pointparticles(particleeffectnum("kaball_sparks"), self.origin, '0 0 0', 1);
448 sound(self, CH_TRIGGER, "keepaway/touch.wav", VOL_BASE, ATTN_NORM);
451 else if(self.wait > time) { return; }
453 switch(self.ctf_status)
456 if((other.team == self.team) && (other.flagcarried) && (other.flagcarried.team != self.team))
457 ctf_Handle_Capture(self, other); // other just captured the enemies flag to his base
458 else if((other.team != self.team) && (!other.flagcarried) && (!other.ctf_captureshielded))
459 ctf_Handle_Pickup_Base(self, other); // other just stole the enemies flag
463 if(other.team == self.team)
464 ctf_Handle_Return(self, other); // other just returned his own flag
465 else if((!other.flagcarried) && ((other.playerid != self.ctf_dropperid) || (time > self.ctf_droptime + autocvar_g_balance_ctf_delay_collect)))
466 ctf_Handle_Pickup_Dropped(self, other); // other just picked up a dropped enemy flag
470 dprint("Someone touched a flag even though it was being carried?\n");
473 default: // this should never happen
474 dprint("Touch: Flag exists with no status?\n");
479 void ctf_RespawnFlag(entity flag)
481 // reset the player (if there is one)
482 if((flag.owner) && (flag.owner.flagcarried == flag))
484 WaypointSprite_Kill(flag.wps_flagcarrier);
485 flag.owner.flagcarried = world;
487 if(flag.speedrunning)
488 ctf_FakeTimeLimit(flag.owner, -1);
492 setattachment(flag, world, "");
493 setorigin(flag, flag.ctf_spawnorigin); // replace with flag.ctf_spawnorigin
494 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
495 flag.takedamage = DAMAGE_NO;
496 flag.solid = SOLID_TRIGGER;
497 flag.velocity = '0 0 0';
498 flag.angles = flag.mangle;
499 flag.ctf_status = FLAG_BASE;
500 flag.flags = FL_ITEM | FL_NOTARGET;
507 if(self.owner.classname == "player")
508 ctf_Handle_Drop(self.owner);
510 ctf_RespawnFlag(self);
513 void ctf_SetupFlag(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
516 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.
519 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist // todo: find out if this can be simplified
520 ctf_worldflaglist = flag;
522 setattachment(flag, world, "");
524 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
525 flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
526 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
527 flag.classname = "item_flag_team";
528 flag.target = "###item###"; // wut?
529 flag.flags = FL_ITEM | FL_NOTARGET;
530 flag.solid = SOLID_TRIGGER;
531 flag.velocity = '0 0 0';
532 flag.ctf_status = FLAG_BASE;
533 flag.ctf_spawnorigin = flag.origin;
534 flag.mangle = flag.angles;
535 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
537 if(flag.spawnflags & 1) // I don't understand what all this is about.
540 flag.movetype = MOVETYPE_NONE;
541 print("This map was loaded with flags using MOVETYPE_NONE\n");
545 flag.noalign = FALSE;
546 flag.movetype = MOVETYPE_TOSS;
547 print("This map was loaded with flags using MOVETYPE_TOSS\n");
550 flag.reset = ctf_Reset;
551 flag.touch = ctf_FlagTouch;
554 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
555 setmodel (flag, flag.model); // precision set below
556 setsize(flag, FLAG_MIN, FLAG_MAX);
557 setorigin(flag, flag.origin);
558 if(!flag.scale) { flag.scale = 0.6; }
560 flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin);
562 if(autocvar_g_ctf_flag_glowtrails)
564 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
569 flag.effects |= EF_LOWPRECISION;
570 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
571 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
574 if(!flag.noise) { flag.noise = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
575 if(!flag.noise1) { flag.noise1 = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
576 if(!flag.noise2) { flag.noise2 = ((teamnumber) ? "ctf/red_capture.wav" : "ctf/blue_capture.wav"); } // blue team scores by capturing the red flag
577 if(!flag.noise3) { flag.noise3 = "ctf/flag_respawn.wav"; } // if there is ever a team-based sound for this, update the code to match.
578 if(!flag.noise4) { flag.noise4 = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
581 precache_sound(flag.noise);
582 precache_sound(flag.noise1);
583 precache_sound(flag.noise2);
584 precache_sound(flag.noise3);
585 precache_sound(flag.noise4);
586 precache_model(flag.model);
587 precache_model("models/ctf/shield.md3");
588 precache_model("models/ctf/shockwavetransring.md3");
591 waypoint_spawnforitem_force(flag, flag.origin);
592 flag.nearestwaypointtimeout = 0; // activate waypointing again
593 flag.bot_basewaypoint = flag.nearestwaypoint;
596 WaypointSprite_SpawnFixed(((teamnumber) ? "redbase" : "bluebase"), flag.origin + '0 0 64', flag, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE));
597 WaypointSprite_UpdateTeamRadar(flag.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE));
599 // captureshield setup
600 ctf_CaptureShield_Spawn(flag);
608 // g_ctf_ignore_frags
610 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
612 if(self.flagcarried) { ctf_Handle_Drop(self); }
616 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
620 // initially clear items so they can be set as necessary later.
621 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
622 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
624 // item for stopping players from capturing the flag too often
625 if(self.ctf_captureshielded)
626 self.items |= IT_CTF_SHIELDED;
628 // scan through all the flags and notify the client about them
629 for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
631 if(flag.ctf_status == FLAG_CARRY)
632 if(flag.owner == self)
633 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
635 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
636 else if(flag.ctf_status == FLAG_DROPPED)
637 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
643 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
645 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
647 if(frag_target == frag_attacker) // damage done to yourself
649 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
650 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
652 else // damage done to noncarriers
654 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
655 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
661 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
663 frag_score = 0; // no frags counted in ctf
664 return (g_ctf_ignore_frags); // you deceptive little bugger ;3 This needs to be true in order for this function to even count.
667 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
669 if(autocvar_g_ctf_allow_drop)
670 ctf_Handle_Drop(self);
679 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
680 CTF Starting point for a player in team one (Red).
681 Keys: "angle" viewing angle when spawning. */
682 void spawnfunc_info_player_team1()
684 if(g_assault) { remove(self); return; }
686 self.team = COLOR_TEAM1; // red
687 spawnfunc_info_player_deathmatch();
691 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
692 CTF Starting point for a player in team two (Blue).
693 Keys: "angle" viewing angle when spawning. */
694 void spawnfunc_info_player_team2()
696 if(g_assault) { remove(self); return; }
698 self.team = COLOR_TEAM2; // blue
699 spawnfunc_info_player_deathmatch();
702 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
703 CTF Starting point for a player in team three (Yellow).
704 Keys: "angle" viewing angle when spawning. */
705 void spawnfunc_info_player_team3()
707 if(g_assault) { remove(self); return; }
709 self.team = COLOR_TEAM3; // yellow
710 spawnfunc_info_player_deathmatch();
714 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
715 CTF Starting point for a player in team four (Purple).
716 Keys: "angle" viewing angle when spawning. */
717 void spawnfunc_info_player_team4()
719 if(g_assault) { remove(self); return; }
721 self.team = COLOR_TEAM4; // purple
722 spawnfunc_info_player_deathmatch();
725 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
726 CTF flag for team one (Red). Multiple flags are allowed.
728 "angle" Angle the flag will point (minus 90 degrees)...
729 "model" model to use, note this needs red and blue as skins 0 and 1 (default models/ctf/flag.md3)...
730 "noise" sound played when flag is picked up (default ctf/take.wav)...
731 "noise1" sound played when flag is returned by a teammate (default ctf/return.wav)...
732 "noise2" sound played when flag is captured (default ctf/redcapture.wav)...
733 "noise3" sound played when flag is lost in the field and respawns itself (default ctf/respawn.wav)... */
734 void spawnfunc_item_flag_team1()
736 if(!g_ctf) { remove(self); return; }
738 ctf_SetupFlag(1, self); // 1 = red
741 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
742 CTF flag for team two (Blue). Multiple flags are allowed.
744 "angle" Angle the flag will point (minus 90 degrees)...
745 "model" model to use, note this needs red and blue as skins 0 and 1 (default models/ctf/flag.md3)...
746 "noise" sound played when flag is picked up (default ctf/take.wav)...
747 "noise1" sound played when flag is returned by a teammate (default ctf/return.wav)...
748 "noise2" sound played when flag is captured (default ctf/redcapture.wav)...
749 "noise3" sound played when flag is lost in the field and respawns itself (default ctf/respawn.wav)... */
750 void spawnfunc_item_flag_team2()
752 if(!g_ctf) { remove(self); return; }
754 ctf_SetupFlag(0, self); // the 0 is misleading, but -- 0 = blue.
757 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
758 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
759 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.
761 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
762 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
763 void spawnfunc_ctf_team()
765 if(!g_ctf) { remove(self); return; }
767 self.classname = "ctf_team";
768 self.team = self.cnt + 1;
776 // code from here on is just to support maps that don't have flag and team entities
777 void ctf_SpawnTeam (string teamname, float teamcolor)
782 self.classname = "ctf_team";
783 self.netname = teamname;
784 self.cnt = teamcolor;
786 spawnfunc_ctf_team();
791 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
793 // if no teams are found, spawn defaults
794 if(find(world, classname, "ctf_team") == world)
796 print("NO TEAMS FOUND FOR CTF! creating them anyway.\n");
797 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
798 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
804 void ctf_Initialize()
806 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
808 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
809 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
810 ctf_captureshield_force = autocvar_g_ctf_shield_force;
812 //g_ctf_win_mode = cvar("g_ctf_win_mode");
814 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
818 MUTATOR_DEFINITION(gamemode_ctf)
820 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
821 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
822 MUTATOR_HOOK(PlayerDies, ctf_RemovePlayer, CBC_ORDER_ANY);
823 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
824 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
825 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
826 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
827 //MUTATOR_HOOK(PlayerPowerups, ctf_PlayerPowerups, CBC_ORDER_ANY);
831 if(time > 1) // game loads at time 1
832 error("This is a game type and it cannot be added at runtime.");
840 error("This is a game type and it cannot be removed at runtime.");