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 .vector ctf_spawnorigin; // stored vector for where the flag is placed on the map itself.
21 float ctf_captimerecord; // record time for capturing the flag
22 .float ctf_pickuptime;
24 .float ctf_dropperid; // don't allow spam of dropping the flag
26 .float ctf_status; // status of the flag (FLAG_BASE, FLAG_DROPPED, FLAG_CARRY declared globally)
29 .float next_take_time; // Delay between when the person can pick up a flag // is this obsolete from the stuff above?
31 // CaptureShield: If the player is too bad to be allowed to capture, shield them from taking the flag.
32 .float ctf_captureshielded; // set to 1 if the player is too bad to be allowed to capture
33 float ctf_captureshield_min_negscore; // punish at -20 points
34 float ctf_captureshield_max_ratio; // punish at most 30% of each team
35 float ctf_captureshield_force; // push force of the shield
37 // after game mode is finished, these will be changed to use #define with other entities so as to not create many more.
43 float ctf_ReadScore(string parameter) // make this obsolete
45 //if(g_ctf_win_mode != 2)
46 return cvar(strcat("g_ctf_personal", parameter));
48 // return cvar(strcat("g_ctf_flag", parameter));
51 void ctf_FakeTimeLimit(entity e, float t)
54 WriteByte(MSG_ONE, 3); // svc_updatestat
55 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
57 WriteCoord(MSG_ONE, autocvar_timelimit);
59 WriteCoord(MSG_ONE, (t + 1) / 60);
62 void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
64 if(autocvar_sv_eventlog)
65 GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
69 // =======================
70 // CaptureShield Functions
71 // =======================
73 float ctf_CaptureShield_CheckStatus(entity p)
77 float players_worseeq, players_total;
79 if(ctf_captureshield_max_ratio <= 0)
82 s = PlayerScore_Add(p, SP_SCORE, 0);
83 if(s >= -ctf_captureshield_min_negscore)
86 players_total = players_worseeq = 0;
91 se = PlayerScore_Add(e, SP_SCORE, 0);
97 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
100 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
106 void ctf_CaptureShield_Update(entity player, float wanted_status)
108 float updated_status = ctf_CaptureShield_CheckStatus(player);
109 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
111 if(updated_status) // TODO csqc notifier for this // Samual: How?
112 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);
114 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);
116 player.ctf_captureshielded = updated_status;
120 float ctf_CaptureShield_Customize()
122 if not(other.ctf_captureshielded)
124 if(self.team == other.team)
129 void ctf_CaptureShield_Touch()
131 if not(other.ctf_captureshielded)
133 if(self.team == other.team)
137 mymid = (self.absmin + self.absmax) * 0.5;
138 othermid = (other.absmin + other.absmax) * 0.5;
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);
143 void ctf_CaptureShield_Spawn(entity flag)
149 e.touch = ctf_CaptureShield_Touch;
150 e.customizeentityforclient = ctf_CaptureShield_Customize;
151 e.classname = "ctf_captureshield";
152 e.effects = EF_ADDITIVE;
153 e.movetype = MOVETYPE_NOCLIP;
154 e.solid = SOLID_TRIGGER;
155 e.avelocity = '7 0 11';
156 setorigin(e, self.origin);
157 setmodel(e, "models/ctf/shield.md3");
159 setsize(e, e.scale * e.mins, e.scale * e.maxs);
167 void ctf_Handle_Drop(entity player)
169 entity flag = player.flagcarried;
171 if(!flag) { return; }
172 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
175 setattachment(flag, world, "");
176 setorigin(flag, player.origin - '0 0 24' + '0 0 37');
177 flag.owner.flagcarried = world;
179 flag.movetype = MOVETYPE_TOSS;
180 flag.solid = SOLID_TRIGGER;
181 flag.takedamage = DAMAGE_YES;
182 flag.velocity = ('0 0 200' + ('0 100 0' * crandom()) + ('100 0 0' * crandom()));
183 flag.pain_finished = time + autocvar_g_ctf_flag_returntime; // replace this later
185 flag.ctf_droptime = time;
186 flag.ctf_dropperid = player.playerid;
187 flag.ctf_status = FLAG_DROPPED;
189 // messages and sounds
190 Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
191 sound(flag, CH_TRIGGER, flag.noise4, VOL_BASE, ATTN_NONE);
192 ctf_EventLog("dropped", player.team, player);
195 PlayerTeamScore_AddScore(player, -ctf_ReadScore("penalty_drop"));
196 PlayerScore_Add(player, SP_CTF_DROPS, 1);
199 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)
200 WaypointSprite_Ping(player.wps_flagcarrier);
201 WaypointSprite_Kill(player.wps_flagcarrier);
204 ctf_CaptureShield_Update(player, 0); // shield only
206 // check if the flag will fall off the map
207 trace_startsolid = FALSE;
208 tracebox(flag.origin, flag.mins, flag.maxs, flag.origin, TRUE, flag);
210 dprint("FLAG FALLTHROUGH will happen SOON\n");
213 void ctf_Handle_Capture(entity flag, entity player)
216 float cap_time, cap_record, success;
217 string cap_message, refername;
220 if((autocvar_g_ctf_captimerecord_always) || (player_count - currentbots)) {
221 cap_record = ctf_captimerecord;
222 cap_time = (time - player.flagcarried.ctf_pickuptime);
224 refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
225 refername = ((refername == player.netname) ? "their" : strcat(refername, "^7's"));
227 if(!ctf_captimerecord)
228 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds"); success = TRUE; }
229 else if(cap_time < cap_record)
230 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, breaking ", refername, " previous record of ", ftos_decimals(cap_record, 2), " seconds"); success = TRUE; }
232 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, failing to break ", refername, " record of ", ftos_decimals(cap_record, 2), " seconds"); success = FALSE; }
235 ctf_captimerecord = cap_time;
236 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
237 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
238 write_recordmarker(player, (time - cap_time), cap_time); } }
240 // messages and sounds
241 Send_KillNotification(player.netname, player.flagcarried.netname, cap_message, INFO_CAPTUREFLAG, MSG_INFO);
242 sound(player, CH_TRIGGER, flag.noise2, VOL_BASE, ATTN_NONE); // "ctf/*_capture.wav"
243 ctf_EventLog("capture", player.flagcarried.team, player);
246 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_capture"));
247 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
250 if (autocvar_g_ctf_flag_capture_effects)
252 pointparticles(particleeffectnum((player.team == COLOR_TEAM1) ? "red_ground_quake" : "blue_ground_quake"), flag.origin, '0 0 0', 1);
253 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
257 WaypointSprite_Kill(player.wps_flagcarrier);
260 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
262 ctf_RespawnFlag(player.flagcarried);
265 void ctf_Handle_Return(entity flag, entity player)
267 // messages and sounds
268 Send_KillNotification (player.netname, flag.netname, "", INFO_RETURNFLAG, MSG_INFO);
269 sound(player, CH_TRIGGER, flag.noise1, VOL_BASE, ATTN_NONE);
270 ctf_EventLog("return", flag.team, player);
273 PlayerTeamScore_AddScore(player, ctf_ReadScore(strcat("score_return", ((player.playerid == flag.playerid) ? "_by_killer" : "")))); // reward for return
274 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
276 TeamScore_AddToTeam(((flag.team == COLOR_TEAM1) ? COLOR_TEAM2 : COLOR_TEAM1), ST_SCORE, -ctf_ReadScore("penalty_returned")); // punish the team who was last carrying it
277 FOR_EACH_PLAYER(player) if(player.playerid == flag.ctf_dropperid) // punish the player who dropped the flag
279 PlayerScore_Add(player, SP_SCORE, -ctf_ReadScore("penalty_returned"));
280 ctf_CaptureShield_Update(player, 0); // shield only
284 WaypointSprite_Kill(flag.wps_flagdropped);
287 ctf_RespawnFlag(flag);
290 void ctf_Handle_Pickup_Base(entity flag, entity player)
292 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
293 string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
295 // attach the flag to the player
297 player.flagcarried = flag;
298 setattachment(flag, player, "");
299 setorigin(flag, FLAG_CARRY_POS);
302 flag.movetype = MOVETYPE_NONE;
303 flag.takedamage = DAMAGE_NO;
304 flag.solid = SOLID_NOT;
305 flag.angles = '0 0 0';
306 flag.ctf_pickuptime = time; // used for timing runs
307 flag.ctf_pickupid = player.playerid;
308 flag.ctf_status = FLAG_CARRY;
310 // messages and sounds
311 Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
312 sound(player, CH_TRIGGER, flag.noise, VOL_BASE, ATTN_NONE);
313 ctf_EventLog("steal", flag.team, player);
314 verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat("(", player.netname, ")") : ""); // replace TRUE with an autocvar for it.
315 FOR_EACH_PLAYER(tmp_player)
316 if(tmp_player.team == flag.team)
317 centerprint(tmp_player, strcat("The enemy ", verbosename, "got your flag! Retrieve it!"));
318 else if((tmp_player.team == player.team) && (tmp_player != player))
319 centerprint(tmp_player, strcat("Your team mate ", verbosename, "got the flag! Protect them!"));
322 PlayerTeamScore_AddScore(player, ctf_ReadScore("score_pickup_base"));
323 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
326 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
327 if((player.speedrunning) && (ctf_captimerecord))
328 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
331 if (autocvar_g_ctf_flag_pickup_effects)
333 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
337 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)
338 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
339 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
340 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0');
341 WaypointSprite_Ping(player.wps_flagcarrier);
344 void ctf_Handle_Pickup_Dropped(entity flag, entity player) // make sure this works
347 float returnscore = bound(0, (flag.pain_finished - time) / autocvar_g_ctf_flag_returntime, 1); // can this be division by zero?
348 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
349 string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
351 // attach the flag to the player
353 player.flagcarried = flag;
354 setattachment(flag, player, "");
355 setorigin(flag, FLAG_CARRY_POS);
358 flag.movetype = MOVETYPE_NONE;
359 flag.takedamage = DAMAGE_NO;
360 flag.solid = SOLID_NOT;
361 flag.angles = '0 0 0';
362 //flag.ctf_pickuptime = time; // don't update pickuptime since this isn't a real steal.
363 flag.ctf_pickupid = player.playerid;
364 flag.ctf_status = FLAG_CARRY;
366 // messages and sounds
367 Send_KillNotification (player.netname, flag.netname, "", INFO_PICKUPFLAG, MSG_INFO);
368 sound (player, CH_TRIGGER, flag.noise, VOL_BASE, ATTN_NONE);
369 ctf_EventLog("pickup", flag.team, player);
370 verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat("(", player.netname, ")") : "");
371 FOR_EACH_PLAYER(tmp_player)
372 if(tmp_player.team == flag.team)
373 centerprint(tmp_player, strcat("The enemy ", verbosename, "got your flag! Retrieve it!"));
374 else if((tmp_player.team == player.team) && (tmp_player != player))
375 centerprint(tmp_player, strcat("Your team mate ", verbosename, "got the flag! Protect them!"));
378 returnscore = floor((ctf_ReadScore("score_pickup_dropped_late") * (1-returnscore) + ctf_ReadScore("score_pickup_dropped_early") * returnscore) + 0.5);
379 print("score is ", ftos(returnscore), "\n");
380 PlayerTeamScore_AddScore(player, returnscore);
381 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
384 if (autocvar_g_ctf_flag_pickup_effects) // field pickup effect
386 pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
390 WaypointSprite_Kill(flag.wps_flagdropped);
391 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)
392 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
393 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
394 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0');
395 WaypointSprite_Ping(player.wps_flagcarrier);
399 // ===================
400 // Main Flag Functions
401 // ===================
408 self.nextthink = time + 0.1; // only 10 fps, more is unnecessary.
411 if(self == ctf_worldflaglist) // only for the first flag
412 FOR_EACH_CLIENT(tmp_entity)
413 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
416 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
417 dprint("wtf the flag got squished?\n");
418 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
419 if(!trace_startsolid) // can we resize it without getting stuck?
420 setsize(self, FLAG_MIN, FLAG_MAX); }
422 if(self.owner.classname != "player" || (self.owner.deadflag) || (self.owner.flagcarried != self)) {
423 dprint("CANNOT HAPPEN - player dead and STILL had a flag!\n");
424 ctf_Handle_Drop(self.owner);
428 switch(self.ctf_status)
430 case FLAG_BASE: // nothing to do here
434 // flag fallthrough? FIXME remove this if bug is really fixed now
435 if(self.origin_z < -131072)
437 dprint("FLAG FALLTHROUGH just happened\n");
438 self.pain_finished = 0;
440 setattachment(self, world, "");
441 if(time > self.pain_finished)
443 bprint("The ", self.netname, " has returned to base\n");
444 sound (self, CH_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
445 ctf_EventLog("returned", self.team, world);
446 ctf_RespawnFlag(self);
451 if((self.owner) && (self.speedrunning) && (ctf_captimerecord) && (time >= self.ctf_pickuptime + ctf_captimerecord))
453 bprint("The ", self.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n");
454 sound (self, CH_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
456 self.owner.impulse = 141; // returning!
460 ctf_RespawnFlag(tmp_entity);
466 default: // this should never happen
467 dprint("Think: Flag exists with no status?\n");
474 if(gameover) { return; }
475 if(!self) { return; }
476 if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
477 { // The flag fell off the map, respawn it since players can't get to it
478 //ctf_RespawnFlag(self);
481 if(other.deadflag != DEAD_NO) { return; }
482 if(other.classname != "player")
483 { // The flag just touched an object, most likely the world
484 pointparticles(particleeffectnum("kaball_sparks"), self.origin, '0 0 0', 1);
485 sound(self, CH_TRIGGER, "keepaway/touch.wav", VOL_BASE, ATTN_NORM);
488 else if(self.wait > time) { return; }
490 switch(self.ctf_status)
493 if((other.team == self.team) && (other.flagcarried) && (other.flagcarried.team != self.team))
494 ctf_Handle_Capture(self, other); // other just captured the enemies flag to his base
495 else if((other.team != self.team) && (!other.flagcarried) && (!other.ctf_captureshielded))
496 ctf_Handle_Pickup_Base(self, other); // other just stole the enemies flag
500 if(other.team == self.team)
501 ctf_Handle_Return(self, other); // other just returned his own flag
502 else if((!other.flagcarried) && ((other.playerid != self.ctf_dropperid) || (time > self.ctf_droptime + autocvar_g_balance_ctf_delay_collect)))
503 ctf_Handle_Pickup_Dropped(self, other); // other just picked up a dropped enemy flag
507 dprint("Someone touched a flag even though it was being carried?\n");
510 default: // this should never happen
511 dprint("Touch: Flag exists with no status?\n");
516 void ctf_RespawnFlag(entity flag)
518 // reset the player (if there is one)
519 if((flag.owner) && (flag.owner.flagcarried == flag))
521 WaypointSprite_Kill(flag.wps_flagcarrier);
522 flag.owner.flagcarried = world;
524 if(flag.speedrunning)
525 ctf_FakeTimeLimit(flag.owner, -1);
529 setattachment(flag, world, "");
530 setorigin(flag, flag.ctf_spawnorigin); // replace with flag.ctf_spawnorigin
531 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
532 flag.takedamage = DAMAGE_NO;
533 flag.solid = SOLID_TRIGGER;
534 flag.velocity = '0 0 0';
535 flag.angles = flag.mangle;
536 flag.ctf_status = FLAG_BASE;
537 flag.flags = FL_ITEM | FL_NOTARGET;
544 if(self.owner.classname == "player")
545 ctf_Handle_Drop(self.owner);
547 ctf_RespawnFlag(self);
550 void ctf_SetupFlag(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
553 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.
556 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist // todo: find out if this can be simplified
557 ctf_worldflaglist = flag;
559 setattachment(flag, world, "");
561 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
562 flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
563 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
564 flag.classname = "item_flag_team";
565 flag.target = "###item###"; // wut?
566 flag.flags = FL_ITEM | FL_NOTARGET;
567 flag.solid = SOLID_TRIGGER;
568 flag.velocity = '0 0 0';
569 flag.ctf_status = FLAG_BASE;
570 flag.ctf_spawnorigin = flag.origin;
571 flag.mangle = flag.angles;
572 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
574 if(flag.spawnflags & 1) // I don't understand what all this is about.
577 flag.movetype = MOVETYPE_NONE;
578 print("This map was loaded with flags using MOVETYPE_NONE\n");
582 flag.noalign = FALSE;
583 flag.movetype = MOVETYPE_TOSS;
584 print("This map was loaded with flags using MOVETYPE_TOSS\n");
587 flag.reset = ctf_Reset;
588 flag.touch = ctf_FlagTouch;
591 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
592 setmodel (flag, flag.model); // precision set below
593 setsize(flag, FLAG_MIN, FLAG_MAX);
594 setorigin(flag, flag.origin);
595 if(!flag.scale) { flag.scale = 0.6; }
597 flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin);
599 if(autocvar_g_ctf_flag_glowtrails)
601 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
606 flag.effects |= EF_LOWPRECISION;
607 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
608 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
611 if(!flag.noise) { flag.noise = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
612 if(!flag.noise1) { flag.noise1 = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
613 if(!flag.noise2) { flag.noise2 = ((teamnumber) ? "ctf/red_capture.wav" : "ctf/blue_capture.wav"); } // blue team scores by capturing the red flag
614 if(!flag.noise3) { flag.noise3 = "ctf/flag_respawn.wav"; } // if there is ever a team-based sound for this, update the code to match.
615 if(!flag.noise4) { flag.noise4 = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
618 precache_sound(flag.noise);
619 precache_sound(flag.noise1);
620 precache_sound(flag.noise2);
621 precache_sound(flag.noise3);
622 precache_sound(flag.noise4);
623 precache_model(flag.model);
624 precache_model("models/ctf/shield.md3");
625 precache_model("models/ctf/shockwavetransring.md3");
628 waypoint_spawnforitem_force(flag, flag.origin);
629 flag.nearestwaypointtimeout = 0; // activate waypointing again
630 flag.bot_basewaypoint = flag.nearestwaypoint;
633 WaypointSprite_SpawnFixed(((teamnumber) ? "redbase" : "bluebase"), flag.origin + '0 0 64', flag, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE));
634 WaypointSprite_UpdateTeamRadar(flag.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE));
636 // captureshield setup
637 ctf_CaptureShield_Spawn(flag);
645 // g_ctf_ignore_frags
647 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
649 if(self.flagcarried) { ctf_Handle_Drop(self); }
653 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
657 // initially clear items so they can be set as necessary later.
658 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
659 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
661 // item for stopping players from capturing the flag too often
662 if(self.ctf_captureshielded)
663 self.items |= IT_CTF_SHIELDED;
665 // scan through all the flags and notify the client about them
666 for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
668 if(flag.ctf_status == FLAG_CARRY)
669 if(flag.owner == self)
670 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
672 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
673 else if(flag.ctf_status == FLAG_DROPPED)
674 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
680 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
682 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
684 if(frag_target == frag_attacker) // damage done to yourself
686 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
687 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
689 else // damage done to noncarriers
691 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
692 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
698 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
700 frag_score = 0; // no frags counted in keepaway
701 return (g_ctf_ignore_frags); // you deceptive little bugger ;3 This needs to be true in order for this function to even count.
704 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
706 if(autocvar_g_ctf_allow_drop)
707 ctf_Handle_Drop(self);
716 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
717 CTF Starting point for a player in team one (Red).
718 Keys: "angle" viewing angle when spawning. */
719 void spawnfunc_info_player_team1()
721 if(g_assault) { remove(self); return; }
723 self.team = COLOR_TEAM1; // red
724 spawnfunc_info_player_deathmatch();
728 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
729 CTF Starting point for a player in team two (Blue).
730 Keys: "angle" viewing angle when spawning. */
731 void spawnfunc_info_player_team2()
733 if(g_assault) { remove(self); return; }
735 self.team = COLOR_TEAM2; // blue
736 spawnfunc_info_player_deathmatch();
739 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
740 CTF Starting point for a player in team three (Yellow).
741 Keys: "angle" viewing angle when spawning. */
742 void spawnfunc_info_player_team3()
744 if(g_assault) { remove(self); return; }
746 self.team = COLOR_TEAM3; // yellow
747 spawnfunc_info_player_deathmatch();
751 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
752 CTF Starting point for a player in team four (Purple).
753 Keys: "angle" viewing angle when spawning. */
754 void spawnfunc_info_player_team4()
756 if(g_assault) { remove(self); return; }
758 self.team = COLOR_TEAM4; // purple
759 spawnfunc_info_player_deathmatch();
762 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
763 CTF flag for team one (Red). Multiple flags are allowed.
765 "angle" Angle the flag will point (minus 90 degrees)...
766 "model" model to use, note this needs red and blue as skins 0 and 1 (default models/ctf/flag.md3)...
767 "noise" sound played when flag is picked up (default ctf/take.wav)...
768 "noise1" sound played when flag is returned by a teammate (default ctf/return.wav)...
769 "noise2" sound played when flag is captured (default ctf/redcapture.wav)...
770 "noise3" sound played when flag is lost in the field and respawns itself (default ctf/respawn.wav)... */
771 void spawnfunc_item_flag_team1()
773 if(!g_ctf) { remove(self); return; }
775 ctf_SetupFlag(1, self); // 1 = red
778 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
779 CTF flag for team two (Blue). Multiple flags are allowed.
781 "angle" Angle the flag will point (minus 90 degrees)...
782 "model" model to use, note this needs red and blue as skins 0 and 1 (default models/ctf/flag.md3)...
783 "noise" sound played when flag is picked up (default ctf/take.wav)...
784 "noise1" sound played when flag is returned by a teammate (default ctf/return.wav)...
785 "noise2" sound played when flag is captured (default ctf/redcapture.wav)...
786 "noise3" sound played when flag is lost in the field and respawns itself (default ctf/respawn.wav)... */
787 void spawnfunc_item_flag_team2()
789 if(!g_ctf) { remove(self); return; }
791 ctf_SetupFlag(0, self); // the 0 is misleading, but -- 0 = blue.
794 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
795 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
796 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.
798 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
799 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
800 void spawnfunc_ctf_team()
802 if(!g_ctf) { remove(self); return; }
804 self.classname = "ctf_team";
805 self.team = self.cnt + 1;
813 // code from here on is just to support maps that don't have flag and team entities
814 void ctf_SpawnTeam (string teamname, float teamcolor)
819 self.classname = "ctf_team";
820 self.netname = teamname;
821 self.cnt = teamcolor;
823 spawnfunc_ctf_team();
828 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
830 // if no teams are found, spawn defaults
831 if(find(world, classname, "ctf_team") == world)
833 print("NO TEAMS FOUND FOR CTF! creating them anyway.\n");
834 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
835 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");
851 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
855 MUTATOR_DEFINITION(gamemode_ctf)
857 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
858 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
859 MUTATOR_HOOK(PlayerDies, ctf_RemovePlayer, CBC_ORDER_ANY);
860 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
861 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
862 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
863 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
864 //MUTATOR_HOOK(PlayerPowerups, ctf_PlayerPowerups, CBC_ORDER_ANY);
868 if(time > 1) // game loads at time 1
869 error("This is a game type and it cannot be added at runtime.");
877 error("This is a game type and it cannot be removed at runtime.");