.entity sprite; entity ctf_worldflaglist; // CTF flags in the map .entity ctf_worldflagnext; .float next_take_time; // the next time a player can pick up a flag (time + blah) /// I used this, in part, to fix the looping score bug. - avirox //float FLAGSCORE_PICKUP = 1; //float FLAGSCORE_RETURN = 5; // returned by owner team //float FLAGSCORE_RETURNROGUE = 10; // returned by rogue team //float FLAGSCORE_CAPTURE = 5; //float FLAGSCORE_CAPTURE_TEAM = 20; #define FLAG_CARRY_POS '-15 0 7' void FakeTimeLimit(entity e, float t) { msg_entity = e; WriteByte(MSG_ONE, 3); // svc_updatestat WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT if(t < 0) WriteCoord(MSG_ONE, cvar("timelimit")); else WriteCoord(MSG_ONE, (t + 1) / 60); } float flagcaptimerecord; .float flagpickuptime; //.float iscommander; //.float ctf_state; void() FlagThink; void() FlagTouch; void place_flag() { if(!self.t_width) self.t_width = 0.1; // frame animation rate if(!self.t_length) self.t_length = 119; // maximum frame setattachment(self, world, ""); self.mdl = self.model; self.flags = FL_ITEM; self.solid = SOLID_TRIGGER; self.movetype = MOVETYPE_NONE; self.velocity = '0 0 0'; self.origin_z = self.origin_z + 6; self.think = FlagThink; self.touch = FlagTouch; self.nextthink = time + 0.1; self.cnt = FLAG_BASE; self.mangle = self.angles; self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP; //self.effects = self.effects | EF_DIMLIGHT; if(!self.noalign) { self.movetype = MOVETYPE_TOSS; if (!droptofloor()) { dprint("Flag fell out of level at ", vtos(self.origin), "\n"); remove(self); return; } } self.oldorigin = self.origin; }; void LogCTF(string mode, float flagteam, entity actor) { string s; if(!cvar("sv_eventlog")) return; s = strcat(":ctf:", mode); s = strcat(s, ":", ftos(flagteam)); if(actor != world) s = strcat(s, ":", ftos(actor.playerid)); GameLogEcho(s, FALSE); } void RegenFlag(entity e) { setattachment(e, world, ""); e.movetype = MOVETYPE_NONE; if(!self.noalign) e.movetype = MOVETYPE_TOSS; e.solid = SOLID_TRIGGER; // TODO: play a sound here setorigin(e, e.oldorigin); e.angles = e.mangle; e.cnt = FLAG_BASE; e.owner = world; e.flags = FL_ITEM; // clear FL_ONGROUND and any other junk }; void ReturnFlag(entity e) { if (e.owner) if (e.owner.flagcarried == e) { WaypointSprite_DetachCarrier(e.owner); e.owner.flagcarried = world; if(e.speedrunning) FakeTimeLimit(e.owner, -1); } e.owner = world; RegenFlag(e); }; void DropFlag(entity e) { local entity p; if(e.speedrunning) { ReturnFlag(e); return; } if (!e.owner) { dprint("FLAG: drop - no owner?!?!\n"); return; } p = e.owner; if (p.flagcarried != e) { dprint("FLAG: drop - owner is not carrying this flag??\n"); return; } bprint(p.netname, "^7 lost the ", e.netname, "\n"); if(cvar("g_ctf_flagpenalty_drop")) UpdateFrags(p, -cvar("g_ctf_flagpenalty_drop")); //if(e.enemy && e.enemy != e) //UpdateFrags(e.enemy, cvar("g_ctf_flagscore_kill")); WaypointSprite_DetachCarrier(p); LogCTF("dropped", p.team, p.flagcarried); setattachment(e, world, ""); if (p.flagcarried == e) p.flagcarried = world; e.owner = world; e.flags = FL_ITEM; // clear FL_ONGROUND and any other junk e.solid = SOLID_TRIGGER; e.movetype = MOVETYPE_TOSS; // setsize(e, '-16 -16 0', '16 16 74'); setorigin(e, p.origin - '0 0 24' + '0 0 37'); e.cnt = FLAG_DROPPED; e.velocity = '0 0 300'; e.pain_finished = time + cvar("g_ctf_flag_returntime");//30; trace_startsolid = FALSE; tracebox(e.origin, e.mins, e.maxs, e.origin, TRUE, e); if(trace_startsolid) dprint("FLAG FALLTHROUGH will happen SOON\n"); }; void AnimateFlag() { if(self.delay > time) return; self.delay = time + self.t_width; if(self.nextthink > self.delay) self.nextthink = self.delay; self.frame = self.frame + 1; if(self.frame > self.t_length) self.frame = 0; } void FlagThink() { local entity e; self.nextthink = time + 0.1; AnimateFlag(); if(self.speedrunning) if(self.cnt == FLAG_CARRY) { if(self.owner) if(flagcaptimerecord) if(time >= self.flagpickuptime + flagcaptimerecord) { bprint("The ", self.netname, " became impatient after ", ftos_decimals(flagcaptimerecord, 2), " seconds and returned itself\n"); self.owner.impulse = 77; // returning! e = self; self = self.owner; ReturnFlag(e); ImpulseCommands(); self = e; return; } } if (self.cnt == FLAG_BASE) return; if (self.cnt == FLAG_DROPPED) { // flag fallthrough? FIXME remove this if bug is really fixed now if(self.origin_z < -131072) { dprint("FLAG FALLTHROUGH just happened\n"); self.pain_finished = 0; } setattachment(self, world, ""); if (time > self.pain_finished) { bprint("The ", self.netname, " has returned to base\n"); sound (self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE); LogCTF("returned", self.team, world); ReturnFlag(self); } return; } e = self.owner; if (e.classname != "player" || (e.deadflag) || (e.flagcarried != self)) { dprint("CANNOT HAPPEN - player dead and STILL had a flag!\n"); DropFlag(self); return; } }; void FlagTouch() { if(gameover) return; local float t; local entity head; local entity player; local string s, s0, h0, h1; if (other.classname != "player") return; if (other.health < 1) // ignore dead players return; if (self.cnt == FLAG_CARRY) return; if (self.cnt == FLAG_BASE) if (other.team == self.team) if (other.flagcarried) // he's got a flag if (other.flagcarried.team != self.team) // capture { if (other.flagcarried == world) { return; } t = time - other.flagcarried.flagpickuptime; s = ftos_decimals(t, 2); s0 = ftos_decimals(flagcaptimerecord, 2); h0 = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname")); h1 = other.netname; if(h0 == h1) h0 = "his"; else h0 = strcat(h0, "^7's"); // h0: display text for previous netname if (flagcaptimerecord == 0) { bprint(other.netname, "^7 captured the ", other.flagcarried.netname, " in ", s, " seconds\n"); flagcaptimerecord = t; db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(t)); db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), h1); } else if (t < flagcaptimerecord) { bprint(other.netname, "^7 captured the ", other.flagcarried.netname, " in ", s, ", breaking ", strcat(h0, " previous record of ", s0, " seconds\n")); flagcaptimerecord = t; db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(t)); db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), h1); } else { bprint(other.netname, "^7 captured the ", other.flagcarried.netname, " in ", s, ", failing to break ", strcat(h0, " record of ", s0, " seconds\n")); } PlayerTeamScore_Add(other, SP_CTF_CAPS, ST_CTF_CAPS, 1); LogCTF("capture", other.flagcarried.team, other); // give credit to the individual player UpdateFrags(other, cvar("g_ctf_flagscore_capture")); // give credit to all players of the team (rewards large teams) // NOTE: this defaults to 0 FOR_EACH_PLAYER(head) if (head.team == self.team) UpdateFrags(head, cvar("g_ctf_flagscore_capture_team")); sound (other, CHAN_AUTO, self.noise2, VOL_BASE, ATTN_NONE); WaypointSprite_DetachCarrier(other); if(self.speedrunning) FakeTimeLimit(other, -1); RegenFlag (other.flagcarried); other.flagcarried = world; other.next_take_time = time + 1; } if (self.cnt == FLAG_BASE) if (other.team == COLOR_TEAM1 || other.team == COLOR_TEAM2) // only red and blue team can steal flags if (other.team != self.team) if (!other.flagcarried) { if (other.next_take_time > time) return; // pick up self.flagpickuptime = time; // used for timing runs self.speedrunning = other.speedrunning; // if speedrunning, flag will self-return and teleport the owner back after the record if(other.speedrunning) if(flagcaptimerecord) FakeTimeLimit(other, time + flagcaptimerecord); self.solid = SOLID_NOT; setorigin(self, self.origin); // relink self.owner = other; other.flagcarried = self; self.cnt = FLAG_CARRY; self.angles = '0 0 0'; bprint(other.netname, "^7 got the ", self.netname, "\n"); UpdateFrags(other, cvar("g_ctf_flagscore_pickup")); PlayerScore_Add(other, SP_CTF_PICKUPS, 1); LogCTF("steal", self.team, other); sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NONE); FOR_EACH_PLAYER(player) if(player.team == self.team) centerprint(player, "The enemy got your flag! Retrieve it!"); self.movetype = MOVETYPE_NONE; setorigin(self, FLAG_CARRY_POS); setattachment(self, other, ""); WaypointSprite_AttachCarrier("flagcarrier", other); return; } if (self.cnt == FLAG_DROPPED) { self.flags = FL_ITEM; // clear FL_ONGROUND and any other junk if (other.team == self.team || (other.team != COLOR_TEAM1 && other.team != COLOR_TEAM2)) { // return flag bprint(other.netname, "^7 returned the ", self.netname, "\n"); if (other.team == COLOR_TEAM1 || other.team == COLOR_TEAM2) UpdateFrags(other, cvar("g_ctf_flagscore_return")); else UpdateFrags(other, cvar("g_ctf_flagscore_return_rogue")); PlayerScore_Add(other, SP_CTF_RETURNS, 1); LogCTF("return", self.team, other); sound (other, CHAN_AUTO, self.noise1, VOL_BASE, ATTN_NONE); ReturnFlag(self); } else if (!other.flagcarried) { // pick up self.solid = SOLID_NOT; setorigin(self, self.origin); // relink self.owner = other; other.flagcarried = self; self.cnt = FLAG_CARRY; bprint(other.netname, "^7 picked up the ", self.netname, "\n"); UpdateFrags(other, cvar("g_ctf_flagscore_pickup")); LogCTF("pickup", self.team, other); sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NONE); FOR_EACH_PLAYER(player) if(player.team == self.team) centerprint(player, "The enemy got your flag! Retrieve it!"); self.movetype = MOVETYPE_NONE; // flag must have MOVETYPE_NONE here, otherwise it will drop through the floor... setorigin(self, FLAG_CARRY_POS); setattachment(self, other, ""); WaypointSprite_AttachCarrier("flagcarrier", other); } } }; /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24) CTF Starting point for a player in team one (Red). Keys: "angle" viewing angle when spawning */ void spawnfunc_info_player_team1() { self.team = COLOR_TEAM1; // red spawnfunc_info_player_deathmatch(); }; //self.team = 4;self.classname = "info_player_start";spawnfunc_info_player_start();}; /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24) CTF Starting point for a player in team two (Blue). Keys: "angle" viewing angle when spawning */ void spawnfunc_info_player_team2() { self.team = COLOR_TEAM2; // blue spawnfunc_info_player_deathmatch(); }; //self.team = 13;self.classname = "info_player_start";spawnfunc_info_player_start();}; /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24) CTF Starting point for a player in team three (Magenta). Keys: "angle" viewing angle when spawning */ void spawnfunc_info_player_team3() { self.team = COLOR_TEAM3; // purple spawnfunc_info_player_deathmatch(); }; /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24) CTF Starting point for a player in team four (Yellow). Keys: "angle" viewing angle when spawning */ void spawnfunc_info_player_team4() { self.team = COLOR_TEAM4; // yellow spawnfunc_info_player_deathmatch(); }; /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37) CTF flag for team one (Red). Multiple are allowed. Keys: "angle" Angle the flag will point (minus 90 degrees) "model" model to use, note this needs red and blue as skins 0 and 1 (default models/ctf/flag.md3) "noise" sound played when flag is picked up (default ctf/take.wav) "noise1" sound played when flag is returned by a teammate (default ctf/return.wav) "noise2" sound played when flag is captured (default ctf/redcapture.wav) "noise3" sound played when flag is lost in the field and respawns itself (default ctf/respawn.wav) */ void spawnfunc_item_flag_team1() { if (!g_ctf) { remove(self); return; } //if(!cvar("teamplay")) // cvar_set("teamplay", "3"); // link flag into ctf_worldflaglist self.ctf_worldflagnext = ctf_worldflaglist; ctf_worldflaglist = self; self.classname = "item_flag_team"; self.team = COLOR_TEAM1; // color 4 team (red) self.items = IT_KEY2; // gold key (redish enough) self.netname = "^1RED^7 flag"; self.target = "###item###"; self.skin = 0; if(self.spawnflags & 1) self.noalign = 1; if (!self.model) self.model = "models/ctf/flag_red.md3"; if (!self.noise) self.noise = "ctf/take.wav"; if (!self.noise1) self.noise1 = "ctf/return.wav"; if (!self.noise2) self.noise2 = "ctf/redcapture.wav"; // blue team scores by capturing the red flag if (!self.noise3) self.noise3 = "ctf/respawn.wav"; precache_model (self.model); setmodel (self, self.model); // precision set below precache_sound (self.noise); precache_sound (self.noise1); precache_sound (self.noise2); precache_sound (self.noise3); setsize(self, '-16 -16 -37', '16 16 37'); setorigin(self, self.origin + '0 0 37'); self.nextthink = time + 0.2; // start after doors etc self.think = place_flag; if(!self.scale) self.scale = 0.6; //if(!self.glow_size) // self.glow_size = 50; self.effects = self.effects | EF_FULLBRIGHT | EF_LOWPRECISION; if(!self.noalign) droptofloor(); waypoint_spawnforitem(self); WaypointSprite_SpawnFixed("redbase", self.origin + '0 0 37', self, sprite); }; /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -24) (48 48 64) CTF flag for team two (Blue). Multiple are allowed. Keys: "angle" Angle the flag will point (minus 90 degrees) "model" model to use, note this needs red and blue as skins 0 and 1 (default models/ctf/flag.md3) "noise" sound played when flag is picked up (default ctf/take.wav) "noise1" sound played when flag is returned by a teammate (default ctf/return.wav) "noise2" sound played when flag is captured (default ctf/bluecapture.wav) "noise3" sound played when flag is lost in the field and respawns itself (default ctf/respawn.wav) */ void spawnfunc_item_flag_team2() { if (!g_ctf) { remove(self); return; } //if(!cvar("teamplay")) // cvar_set("teamplay", "3"); // link flag into ctf_worldflaglist self.ctf_worldflagnext = ctf_worldflaglist; ctf_worldflaglist = self; self.classname = "item_flag_team"; self.team = COLOR_TEAM2; // color 13 team (blue) self.items = IT_KEY1; // silver key (bluish enough) self.netname = "^4BLUE^7 flag"; self.target = "###item###"; self.skin = 0; if(self.spawnflags & 1) self.noalign = 1; if (!self.model) self.model = "models/ctf/flag_blue.md3"; if (!self.noise) self.noise = "ctf/take.wav"; if (!self.noise1) self.noise1 = "ctf/return.wav"; if (!self.noise2) self.noise2 = "ctf/bluecapture.wav"; // red team scores by capturing the blue flag if (!self.noise3) self.noise3 = "ctf/respawn.wav"; precache_model (self.model); setmodel (self, self.model); // precision set below precache_sound (self.noise); precache_sound (self.noise1); precache_sound (self.noise2); precache_sound (self.noise3); setsize(self, '-16 -16 -37', '16 16 37'); setorigin(self, self.origin + '0 0 37'); self.nextthink = time + 0.2; // start after doors etc self.think = place_flag; if(!self.scale) self.scale = 0.6; //if(!self.glow_size) // self.glow_size = 50; self.effects = self.effects | EF_FULLBRIGHT | EF_LOWPRECISION; if(!self.noalign) droptofloor(); waypoint_spawnforitem(self); WaypointSprite_SpawnFixed("bluebase", self.origin + '0 0 37', self, sprite); }; /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32) Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map. 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. Keys: "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc) "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue) */ void spawnfunc_ctf_team() { if (!g_ctf) { remove(self); return; } self.classname = "ctf_team"; self.team = self.cnt + 1; }; // code from here on is just to support maps that don't have control point and team entities void ctf_spawnteam (string teamname, float teamcolor) { local entity oldself; oldself = self; self = spawn(); self.classname = "ctf_team"; self.netname = teamname; self.cnt = teamcolor; spawnfunc_ctf_team(); self = oldself; }; // spawn some default teams if the map is not set up for ctf void ctf_spawnteams() { float numteams; numteams = 2;//cvar("g_ctf_default_teams"); ctf_spawnteam("Red", COLOR_TEAM1 - 1); ctf_spawnteam("Blue", COLOR_TEAM2 - 1); }; void ctf_delayedinit() { self.think = SUB_Remove; self.nextthink = time; // if no teams are found, spawn defaults if (find(world, classname, "ctf_team") == world) ctf_spawnteams(); ScoreRules_ctf(); }; void ctf_init() { local entity e; e = spawn(); e.think = ctf_delayedinit; e.nextthink = time + 0.1; flagcaptimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"))); }; void ctf_setstatus2(entity flag, float shift) { if (flag.cnt == FLAG_CARRY) if (flag.owner == self) self.items |= shift * 3; else self.items |= shift * 1; else if (flag.cnt == FLAG_DROPPED) self.items |= shift * 2; else { // no status bits } }; void ctf_setstatus() { self.items = self.items - (self.items & IT_RED_FLAG_TAKEN); self.items = self.items - (self.items & IT_RED_FLAG_LOST); self.items = self.items - (self.items & IT_BLUE_FLAG_TAKEN); self.items = self.items - (self.items & IT_BLUE_FLAG_LOST); if (g_ctf) { local entity flag; float redflags, blueflags; redflags = 0; blueflags = 0; for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) if(flag.cnt != FLAG_BASE) { if(flag.team == COLOR_TEAM1) ++redflags; else if(flag.team == COLOR_TEAM2) ++blueflags; } // blinking magic: if there is more than one flag, show one of these in a clever way if(redflags) redflags = mod(floor(time * redflags * 0.75), redflags); if(blueflags) blueflags = mod(floor(time * blueflags * 0.75), blueflags); for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) if(flag.cnt != FLAG_BASE) { if(flag.team == COLOR_TEAM1) { if(--redflags == -1) // happens exactly once (redflags is in 0..count-1, and will --'ed count times) ctf_setstatus2(flag, IT_RED_FLAG_TAKEN); } else if(flag.team == COLOR_TEAM2) { if(--blueflags == -1) // happens exactly once ctf_setstatus2(flag, IT_BLUE_FLAG_TAKEN); } } } }; /* entity(float cteam) ctf_team_has_commander = { entity pl; if(cteam != COLOR_TEAM1 || cteam != COLOR_TEAM2) return world; FOR_EACH_REALPLAYER(pl) { if(pl.team == cteam && pl.iscommander) { return pl; } } return world; }; void(entity e, float st) ctf_setstate = { e.ctf_state = st; ++e.version; }; void(float cteam) ctf_new_commander = { entity pl, plmax; plmax = world; FOR_EACH_REALPLAYER(pl) { if(pl.team == cteam) { if(pl.iscommander) { // don't reassign if alreay there return; } if(plmax == world || plmax.frags < pl.frags) <<<<<<<<<<<<<<<<< BROKEN in new scoring system plmax = pl; } } if(plmax == world) { bprint(strcat(ColoredTeamName(cteam), " Team has no Commander!\n")); return; } plmax.iscommander = TRUE; ctf_setstate(plmax, 3); sprint(plmax, "^3You're the commander now!\n"); centerprint(plmax, "^3You're the commander now!\n"); }; void() ctf_clientconnect = { self.iscommander = FALSE; if(!self.team || self.classname != "player") { ctf_setstate(self, -1); } else ctf_setstate(self, 0); self.team_saved = self.team; if(self.team != 0 && self.classname == "player" && !ctf_team_has_commander(self.team)) { ctf_new_commander(self.team); } }; void() ctf_playerchanged = { if(!self.team || self.classname != "player") { ctf_setstate(self, -1); } else if(self.ctf_state < 0 && self.classname == "player") { ctf_setstate(self, 0); } if(self.iscommander && (self.classname != "player" || self.team != self.team_saved) ) { self.iscommander = FALSE; if(self.classname == "player") ctf_setstate(self, 0); else ctf_setstate(self, -1); ctf_new_commander(self.team_saved); } self.team_saved = self.team; ctf_new_commander(self.team); }; void() ctf_clientdisconnect = { if(self.iscommander) { ctf_new_commander(self.team); } }; entity GetPlayer(string); float() ctf_clientcommand = { entity e; if(argv(0) == "order") { if(!g_ctf) { sprint(self, "This command is not supported in this gamemode.\n"); return TRUE; } if(!self.iscommander) { sprint(self, "^1You are not the commander!\n"); return TRUE; } if(argv(2) == "") { sprint(self, "Usage: order #player status - (playernumber as in status)\n"); return TRUE; } e = GetPlayer(argv(1)); if(e == world) { sprint(self, "Invalid player.\nUsage: order #player status - (playernumber as in status)\n"); return TRUE; } if(e.team != self.team) { sprint(self, "^3You can only give orders to your own team!\n"); return TRUE; } if(argv(2) == "attack") { sprint(self, strcat("Ordering ", e.netname, " to attack!\n")); sprint(e, "^1Attack!\n"); centerprint(e, "^7You've been ordered to^9\n^1Attack!\n"); ctf_setstate(e, 1); } else if(argv(2) == "defend") { sprint(self, strcat("Ordering ", e.netname, " to defend!\n")); sprint(e, "^Defend!\n"); centerprint(e, "^7You've been ordered to^9\n^2Defend!\n"); ctf_setstate(e, 2); } else { sprint(self, "^7Invalid command, use ^3attack^7, or ^3defend^7.\n"); } return TRUE; } return FALSE; }; */