/* teamplay.qc This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA $Id$ */ /**************************************************************************** * ThreeWave Capture The Flag **************************************************************************** * Based on John Spikles Complete Enhanced Teamplay **************************************************************************** * Version 4.0 rewrite Mar 21, 1997 ****************************************************************************/ /** Defs **/ /** MODIFIABLE CONSTANTS **/ float TEAM_DEFAULT_PENALTY = 1; // Default frag penalty float TEAM_STRICT_COOP = 0; // Strict Coop // Team colors float TEAM_COLOR1 = 4; float TEAM_COLOR2 = 13; /** End of MODIFIABLE CONSTANTS **/ // Globals entity team1_lastspawn; entity team2_lastspawn; float nextteamupdtime; // time until next team update float last_flag_capture; // time of last capture float last_capture_team; // last team that captured float teamscr1; // team 1's teamscr score float teamscr2; // team 2's teamscr score float lastteamscrtime; // last time we calculated it float TEAMSCRTIME = 1; // Teamplay bitfield entries float TEAM_HEALTH_PROTECT = 1; // No health damage from friendly fire float TEAM_ARMOR_PROTECT = 2; // No armor damage from friendly fire float TEAM_ATTACKER_DAMAGE = 4; // Attacker takes damage from hitting teammates float TEAM_FRAG_PENALTY = 8; // One frag penalty for killing teammate float TEAM_DEATH_PENALTY = 16; // Die when you kill a teammate. float TEAM_LOCK_COLORS = 32; // Allow only team colors float TEAM_STATIC_TEAMS = 64; // Don't allow players to switch teams float TEAM_DROP_ITEMS = 128; // Allow players to drop packs and float TEAM_CAPTURE_FLAG = 256; // Play capture the flag float TEAM_DISABLE_GRAPPLE = 2048; // team selection float TEAM_CAPTURE_CAPTURE_BONUS = 15; // what you get for capture float TEAM_CAPTURE_TEAM_BONUS = 10; // what your team gets for capture float TEAM_CAPTURE_RECOVERY_BONUS = 1; // what you get for recovery float TEAM_CAPTURE_FLAG_BONUS = 0; // what you get for picking up enemy flag float TEAM_CAPTURE_FRAG_CARRIER_BONUS = 2; // what you get for fragging //enemy flag carrier float TEAM_CAPTURE_FLAG_RETURN_TIME = 40; // seconds until auto return // XXX EXPERT CTF Additional scoring system // bonuses float TEAM_CAPTURE_CARRIER_DANGER_PROTECT_BONUS = 2; // bonus for fraggin someone // who has recently hurt your flag carrier float TEAM_CAPTURE_CARRIER_PROTECT_BONUS = 1; // bonus for fraggin someone while // either you or your target are near your flag carrier float TEAM_CAPTURE_FLAG_DEFENSE_BONUS = 1; // bonus for fraggin someone while // either you or your target are near your flag float TEAM_CAPTURE_RETURN_FLAG_ASSIST_BONUS = 1; // awarded for returning a flag that causes a // capture to happen almost immediately float TEAM_CAPTURE_FRAG_CARRIER_ASSIST_BONUS = 2; // award for fragging a flag carrier if a // capture happens almost immediately // radii float TEAM_CAPTURE_TARGET_PROTECT_RADIUS = 400; // the radius around an object being // defended where a target will be worth extra frags float TEAM_CAPTURE_ATTACKER_PROTECT_RADIUS = 400; // the radius around an object being // defended where an attacker will get extra frags when making kills // timeouts float TEAM_CAPTURE_CARRIER_DANGER_PROTECT_TIMEOUT = 4; float TEAM_CAPTURE_CARRIER_FLAG_SINCE_TIMEOUT = 2; float TEAM_CAPTURE_FRAG_CARRIER_ASSIST_TIMEOUT = 6; float TEAM_CAPTURE_RETURN_FLAG_ASSIST_TIMEOUT = 4; float TEAM_CAPTURE_UPDATE_TIME = 120; // END EXPERT CTF // flag status used in cnt field of flag float FLAG_AT_BASE = 0; float FLAG_CARRIED = 1; float FLAG_DROPPED = 2; // Prototypes float() W_BestWeapon; void() W_SetCurrentAmmo; void() bound_other_ammo; void(float o, float n) Deathmatch_Weapon; void() BackpackTouch; void(entity who, string s) TeamPlayerUpdate; void() TeamCaptureResetUpdate; // Return a name for the color of a team string (float Team) GetTeamColor = { switch (Team) { case 0: return "Grey"; case 1: return "Brown"; case 2: return "Steel blue"; case 3: return "Green"; case 4: return "Red"; case 5: return "Olive"; case 6: return "Orange"; case 7: return "Peach"; case 8: return "Purple"; case 9: return "Majenta"; case 10: return "Tan"; case 11: return "Aqua"; case 12: return "Yellow"; case 13: return "Blue"; case 14: return "Bright Orange"; case 15: return "Bright Red"; default: return "Unknown"; } }; // Return a name for the color of a team string (float Team) GetShortTeamColor = { Team = Team & 15; // color loops switch (Team) { case 0: return "grey"; case 1: return "brwn"; case 2: return "sblu"; case 3: return "grn"; case 4: return "red"; case 5: return "olve"; case 6: return "orng"; case 7: return "pch"; case 8: return "purp"; case 9: return "majn"; case 10: return "tan"; case 11: return "aqua"; case 12: return "ylw"; case 13: return "blue"; case 14: return "bojn"; case 15: return "bred"; default: return "Unknown"; } }; // *XXX* EXPERT CTF // Just a quickie to return the ASCII-ized team names for CTF string (float Team) GetCTFTeam = { switch (Team) { case TEAM_COLOR1: return "ÒÅÄ"; case TEAM_COLOR2: return "ÂÌÕÅ"; default: return ""; } }; /* ================ TeamPrintSettings Print out current teamplay options ================ */ void () TeamPrintSettings = { local string s; sprint (self, PRINT_HIGH, "The following Teamplay options are set:\n"); if (teamplay < 0) { sprint (self, PRINT_HIGH, "Frag penalty manually set to "); s = ftos (teamplay); sprint (self, PRINT_HIGH, s); sprint (self, PRINT_HIGH, "\n"); return; } if (!teamplay) { sprint (self, PRINT_HIGH, "None\n"); return; } if (1 == teamplay) { sprint (self, PRINT_HIGH, "ID's original teamplay 1\n"); return; } if (teamplay & TEAM_HEALTH_PROTECT) sprint (self, PRINT_HIGH, "Health-Protect "); if (teamplay & TEAM_ARMOR_PROTECT) sprint (self, PRINT_HIGH, "Armor-Protect "); if (teamplay & TEAM_ATTACKER_DAMAGE) sprint (self, PRINT_HIGH, "Mirror-Damage "); if (teamplay & TEAM_FRAG_PENALTY) sprint (self, PRINT_HIGH, "Frag-Penalty "); if (teamplay & TEAM_DEATH_PENALTY) sprint (self, PRINT_HIGH, "Death-Penalty "); if (teamplay & TEAM_LOCK_COLORS) sprint (self, PRINT_HIGH, "Lock-Colors "); if (teamplay & TEAM_STATIC_TEAMS) sprint (self, PRINT_HIGH, "Static-Teams "); if (teamplay & TEAM_DROP_ITEMS) sprint (self, PRINT_HIGH, "Drop-Items (Backpack Impulse 20, Weapon " "Impulse 21) "); if (teamplay & TEAM_CAPTURE_FLAG) sprint (self, PRINT_HIGH, "Capture-The-Flag "); sprint(self, PRINT_HIGH, "\n"); }; /* ================ TeamArmorDam Return TRUE if the target's armor can take damage from this attacker. ================ */ float (entity targ, entity inflictor, entity attacker, float damage) TeamArmorDam = { if (teamplay < 0 || gamestart) return TRUE; if ((teamplay & TEAM_ARMOR_PROTECT) && attacker.steam == targ.steam && attacker != targ) { // Armor is protected return FALSE; } return TRUE; }; /* ================ TeamHealthDam Return TRUE if the target can take health damage from this attacker. ================ */ float (entity targ, entity inflictor, entity attacker, float damage) TeamHealthDam = { if (teamplay < 0 || gamestart) return TRUE; if (attacker.steam == targ.steam && attacker != targ) { // Attacker and target are on the same team. if (teamplay & TEAM_ATTACKER_DAMAGE) { // Damage applied to teammate. T_Damage (attacker, inflictor, attacker, damage); } if (teamplay & TEAM_HEALTH_PROTECT) { // Health is protected return FALSE; } } return TRUE; }; /* ================ TeamPFrags Return the number of frags we should penalize attacker for killing targ. ================ */ float (entity targ, entity attacker) TeamPFrags = { if (teamplay < 0) return (-teamplay); if (targ != attacker && targ.steam == attacker.steam) { // targ and attacker are on the same team if (teamplay < 0) { // teamplay indicates frag penalty return (-teamplay); } if (teamplay & TEAM_FRAG_PENALTY) { // default penalty return TEAM_DEFAULT_PENALTY; } } // No frag penalty return 0; }; /* ================ TeamFragPenalty If attacker should be penalized for killing targ, penalize attacker and return TRUE. ================ */ float (entity targ, entity attacker) TeamFragPenalty = { local float f; f = TeamPFrags(targ, attacker); if (f) { // We should penalize some frags. attacker.frags = attacker.frags - f; return TRUE; } // No penalty return FALSE; }; /* ================= TeamDeathPenalty If attacker should be killed for killing targ, kill attacker and add a frag to offset the one attacker will lose for killing himself. */ void (entity targ, entity attacker) TeamDeathPenalty = { //Don't kill anyone if teamplay is negative. if (teamplay < 0) return; if ((teamplay & TEAM_DEATH_PENALTY) && attacker != targ && attacker.steam == targ.steam) { //We should kill the attacker. T_Damage (attacker, attacker, attacker, 1000); //Add a frag to offset the self-kill penalty. attacker.frags++; } }; /* ================== TeamColorIsLegal Return TRUE if the indicated color is legal ================== */ float (float color) TeamColorIsLegal = { // All colors are legal if teamplay is negative. if (teamplay < 0) return TRUE; // All colors are legal if TEAM_LOCK_COLORS is off. if (!(teamplay & TEAM_LOCK_COLORS)) return TRUE; if (color == TEAM_COLOR1) return TRUE; if (color == TEAM_COLOR2) return TRUE; return FALSE; }; void () TeamSkinSet = { switch (self.ctfskinno) { case 1: self.ctfskin = "ctfr1"; return; case 2: self.ctfskin = "ctfr2"; return; case 3: self.ctfskin = "ctfb1"; return; case 4: self.ctfskin = "ctfb2"; return; default: switch (self.steam) { case TEAM_COLOR1: self.ctfskin = "ctfr1"; return; case TEAM_COLOR2: self.ctfskin = "ctfb1"; return; default: self.ctfskin = "base"; return; } } }; void () TeamSkinAssign = { if (!(teamplay & TEAM_CAPTURE_FLAG)) return; if (self.steam == TEAM_COLOR1) self.ctfskinno = 1; else self.ctfskinno = 3; if (random () < 0.5) self.ctfskinno++; self.player_flag &= ~65280; self.player_flag |= self.ctfskinno * 256; TeamSkinSet (); }; float (float tcolor, float bcolor) CrossDressCheck = { // All colors are legal if teamplay is negative. if (teamplay < 0) return FALSE; // All colors are legal is TEAM_LOCK_COLORS is off. if (!(teamplay & TEAM_LOCK_COLORS)) return FALSE; if (tcolor == TEAM_COLOR1 && bcolor == TEAM_COLOR2) return TRUE; if (tcolor == TEAM_COLOR2 && bcolor == TEAM_COLOR1) return TRUE; return FALSE; }; /* ================== TeamAssign Check if the team self is on is legal, and put self in a legal team if not. ================== */ void () TeamAssign = { local entity p; local float TEAM1, TEAM2, newcolor; local string n; if (self.steam >= 0) { if (TeamColorIsLegal (self.steam)) { TeamSkinAssign (); return; } } // Assign the player to a team. // Sum the players on all the teams. TEAM1 = 0; TEAM2 = 0; p = find (world, classname, "player"); while (p) { if (p != self && !(p.player_flag & PF_GHOST)) { if (p.steam == TEAM_COLOR1) TEAM1 = TEAM1 + 1; else if (p.steam == TEAM_COLOR2) TEAM2 = TEAM2 + 1; } p = find (p, classname, "player"); } // Find the team with the least players. if (TEAM1 < TEAM2 || ((TEAM1 == TEAM2) && (random () < 0.5))) newcolor = TEAM_COLOR1; else newcolor = TEAM_COLOR2; // Put the player on a the new team. sprint (self, PRINT_HIGH, "You have been assigned to "); n = GetTeamColor (newcolor); sprint (self, PRINT_HIGH, n); sprint (self, PRINT_HIGH, " team.\n"); self.steam = newcolor; TeamSkinAssign (); self.player_flag |= TEAM_STUFF_COLOR; }; /* =============== TeamCheckLock Check for team changing and perform whatever actions are neccessary. =============== */ void () TeamCheckLock = { local float pteam, n; local string s; // Don't do anything if teamplay is negative if (teamplay < 0) return; if (gamestart) { s = infokey (self, "bottomcolor"); pteam = stof (s); if (pteam != 0) stuffcmd (self, "setinfo bottomcolor 0\n"); self.steam = -1; return; } if (self.player_flag & TEAM_STUFF_COLOR) { self.player_flag = self.player_flag - TEAM_STUFF_COLOR; s = ftos (self.steam); stuffcmd (self, "setinfo bottomcolor "); stuffcmd (self, s); stuffcmd (self, "\n"); s = infokey (self, "topcolor"); n = stof (s); if (CrossDressCheck(n, self.steam)) { s = ftos (self.steam); stuffcmd (self, "setinfo topcolor "); stuffcmd (self, s); stuffcmd (self, "\n"); } s = GetShortTeamColor (self.steam); stuffcmd (self, "setinfo team "); stuffcmd (self, s); stuffcmd (self, "\n"); if (teamplay & TEAM_CAPTURE_FLAG) { stuffcmd (self, "setinfo skin "); stuffcmd (self, self.ctfskin); stuffcmd (self, "\n"); } self.lastteamset = time; return; } s = infokey (self, "bottomcolor"); pteam = stof (s); // Check to see if the player has changed skins if (self.steam > 0 && time - self.lastteamset > 2) { if (teamplay & TEAM_CAPTURE_FLAG) if (infokey (self, "skin") != self.ctfskin) { stuffcmd (self, "setinfo skin "); stuffcmd (self, self.ctfskin); stuffcmd (self, "\n"); self.lastteamset = time; } s = GetShortTeamColor (self.steam); if (infokey (self, "team") != s) { stuffcmd (self, "setinfo team "); stuffcmd (self, s); stuffcmd (self, "\n"); self.lastteamset = time; } } // check for crossdressing s = infokey (self, "topcolor"); n = stof (s); if (CrossDressCheck (n, self.steam)) { s = ftos (self.steam); stuffcmd (self, "setinfo topcolor "); stuffcmd (self, s); stuffcmd (self, "\n"); return; } // Check to see if the player has changed colors if (time - self.lastteamset > 2 && pteam != self.steam) { // Player has changed colors // If teams are static and we've been on some team already, // put us back on the team we were on. if ((teamplay & TEAM_STATIC_TEAMS) && (self.steam >= 0)) { if (TeamColorIsLegal (self.steam)) { // changing teams sucks, kill him // if he has tried to change teams several times, // kick him off the server. if (self.suicide_count > 3) { sprint(self, PRINT_HIGH, "You were told you can't change " "teams.\nGo play color games somewhere else.\n"); stuffcmd (self, "disconnect\n"); bprint (PRINT_MEDIUM, self.netname); bprint (PRINT_MEDIUM, " has bad color sense\n"); } // case base respawn self.killed = 99; T_Damage (self, self, self, 1000); // Kill the player self.killed = 2; // trying to change teams counts as a suicide self.suicide_count++; logfrag (self, self); // he pays for it sprint (self, PRINT_HIGH, "You cannot change teams.\n"); stuffcmd (self, "setinfo bottomcolor "); s = ftos (self.steam); stuffcmd (self, s); stuffcmd (self, "\n"); self.lastteamset = time; return; } else { // If we're on an illegal team, force a change. self.steam = -1; } } else // If teamlock is turned off, don't do anything more. if (!(teamplay & TEAM_LOCK_COLORS)) return; if (self.steam >= 0) { // case base respawn self.killed = 99; T_Damage (self, self, self, 1000); // Kill the player self.killed = 0; } self.steam = pteam; self.frags = 0; // Zero out frags TeamAssign (); } }; /* ======================= TossBackPack Original idea by Vhold Rewritten by John Spickes Toss out a backpack containing some ammo from your current weapon, and any weapons you don't have. ======================= */ void () TossBackpack = { local entity item; // If we don't have any ammo, return (except AXE/GRAPPLE) if (self.currentammo <= 0) { if ((self.weapon != IT_AXE) && (self.weapon != IT_GRAPPLE)) return; } item = spawn (); // See if you have the Shotgun or Super Shotgun on if ((self.weapon == IT_SHOTGUN) || (self.weapon == IT_SUPER_SHOTGUN)) { if (self.ammo_shells >= 20) { item.ammo_shells = 20; self.ammo_shells -= 20; } else { item.ammo_shells = self.ammo_shells; self.ammo_shells = 0; } } // See if you have neither the Shotgun or Super Shotgun if (!(self.items & IT_SHOTGUN) && !(self.items & IT_SUPER_SHOTGUN)) { if (self.ammo_shells >= 20) { item.ammo_shells = 20; self.ammo_shells -= 20; } else { item.ammo_shells = self.ammo_shells; self.ammo_shells = 0; } } // See if we are using a nailgun if ((self.weapon == IT_NAILGUN) || (self.weapon == IT_SUPER_NAILGUN)) { if (self.ammo_nails >= 20) { item.ammo_nails = 20; self.ammo_nails -= 20; } else { item.ammo_nails = self.ammo_nails; self.ammo_nails = 0; } } // Check to see if we have neither nailgun if (!(self.items & IT_NAILGUN) && !(self.items & IT_SUPER_NAILGUN)) { if (self.ammo_nails >= 20) { item.ammo_nails = 20; self.ammo_nails -= 20; } else { item.ammo_nails = self.ammo_nails; self.ammo_nails = 0; } } // See if we are using a grenade or rocket launcher if ((self.weapon == IT_GRENADE_LAUNCHER) || (self.weapon == IT_ROCKET_LAUNCHER)) { if (self.ammo_rockets >= 10) { item.ammo_rockets = 10; self.ammo_rockets -= 10; } else { item.ammo_rockets = self.ammo_rockets; self.ammo_rockets = 0; } } // See if we have neither the Grenade or rocket launcher if (!(self.items & IT_GRENADE_LAUNCHER) && !(self.items & IT_ROCKET_LAUNCHER)) { if (self.ammo_rockets >= 10) { item.ammo_rockets = 10; self.ammo_rockets -= 10; } else { item.ammo_rockets = self.ammo_rockets; self.ammo_rockets = 0; } } // See if we're using the lightning gun if (self.weapon == IT_LIGHTNING) { if (self.ammo_cells >= 20) { item.ammo_cells = 20; self.ammo_cells -= 20; } else { item.ammo_cells = self.ammo_cells; self.ammo_cells = 0; } } // see if we don't have the lightning gun if (!(self.items & IT_LIGHTNING)) { if (self.ammo_cells >= 20) { item.ammo_cells = 20; self.ammo_cells -= 20; } else { item.ammo_cells = self.ammo_cells; self.ammo_cells = 0; } } if (!item.ammo_shells && !item.ammo_nails && !item.ammo_rockets && !item.ammo_cells) { // we didn't put anything in remove (item); return; } item.owner = self; makevectors (self.v_angle); setorigin (item, self.origin + '0 0 16'); item.velocity = aim (self, 1000); item.velocity *= 500; item.flags = FL_ITEM; item.solid = SOLID_TRIGGER; item.movetype = MOVETYPE_BOUNCE; setmodel (item, "progs/backpack.mdl"); setsize (item, '-16 -16 0', '16 16 56'); item.touch = BackpackTouch; item.nextthink = time + 120; // remove after 2 minutes item.think = SUB_Remove; W_SetCurrentAmmo(); }; void () Team_weapon_touch = { local entity stemp; local float hadammo, best, new = 0, old; if (!(other.flags & FL_CLIENT)) return; // Don't let the owner pick up his own weapon for a second. if ((other == self.owner) && ((self.nextthink - time) > 119)) return; // if the player was using his best weapon, change to the new one if better stemp = self; self = other; best = W_BestWeapon (); self = stemp; switch (self.classname) { case "weapon_nailgun": hadammo = other.ammo_nails; new = IT_NAILGUN; break; case "weapon_supernailgun": hadammo = other.ammo_rockets; new = IT_SUPER_NAILGUN; break; case "weapon_supershotgun": hadammo = other.ammo_rockets; new = IT_SUPER_SHOTGUN; break; case "weapon_rocketlauncher": hadammo = other.ammo_rockets; new = IT_ROCKET_LAUNCHER; break; case "weapon_grenadelauncher": hadammo = other.ammo_rockets; new = IT_GRENADE_LAUNCHER; break; case "weapon_lightning": hadammo = other.ammo_rockets; new = IT_LIGHTNING; break; default: objerror ("Team_weapon_touch: unknown classname"); break; } sprint (other, PRINT_LOW, "You got the "); sprint (other, PRINT_LOW, self.netname); sprint (other, PRINT_LOW, "\n"); // weapon touch sound sound (other, CHAN_ITEM, "weapons/pkup.wav", 1, ATTN_NORM); stuffcmd (other, "bf\n"); bound_other_ammo (); // change to the weapon old = other.items; other.items |= new; remove (self); self = other; if (!deathmatch) self.weapon = new; else Deathmatch_Weapon (old, new); W_SetCurrentAmmo(); activator = other; SUB_UseTargets (); // fire all targets / killtargets }; void () TossWeapon = { local entity item; if (deathmatch != 1) return; // only in deathmatch 1 if ((self.weapon == IT_AXE) || (self.weapon == IT_SHOTGUN) || (self.weapon == IT_GRAPPLE)) return; item = spawn (); item.owner = self; makevectors (self.v_angle); setorigin (item, self.origin + '0 0 16'); item.velocity = aim (self, 1000); item.velocity = item.velocity * 500; item.flags = FL_ITEM; item.solid = SOLID_TRIGGER; item.movetype = MOVETYPE_BOUNCE; switch (self.weapon) { case IT_SUPER_SHOTGUN: setmodel (item, "progs/g_shot.mdl"); item.weapon = IT_SUPER_SHOTGUN; item.netname = "Double-barrelled Shotgun"; item.classname = "weapon_supershotgun"; self.items &= ~IT_SUPER_SHOTGUN; break; case IT_NAILGUN: setmodel (item, "progs/g_nail.mdl"); item.weapon = IT_NAILGUN; item.netname = "nailgun"; item.classname = "weapon_nailgun"; self.items &= ~IT_NAILGUN; break; case IT_SUPER_NAILGUN: setmodel (item, "progs/g_nail2.mdl"); item.weapon = IT_SUPER_NAILGUN; item.netname = "Super Nailgun"; item.classname = "weapon_supernailgun"; self.items &= ~IT_SUPER_NAILGUN; break; case IT_GRENADE_LAUNCHER: setmodel (item, "progs/g_rock.mdl"); item.weapon = 3; item.netname = "Grenade Launcher"; item.classname = "weapon_grenadelauncher"; self.items &= ~IT_GRENADE_LAUNCHER; break; case IT_ROCKET_LAUNCHER: setmodel (item, "progs/g_rock2.mdl"); item.weapon = 3; item.netname = "Rocket Launcher"; item.classname = "weapon_rocketlauncher"; self.items &= ~IT_ROCKET_LAUNCHER; break; case IT_LIGHTNING: setmodel (item, "progs/g_light.mdl"); item.weapon = 3; item.netname = "Thunderbolt"; item.classname = "weapon_lightning"; self.items &= ~IT_LIGHTNING; default: break; } setsize (item, '-16 -16 0', '16 16 56'); item.touch = Team_weapon_touch; item.think = SUB_Remove; item.nextthink = time + 120; self.weapon = W_BestWeapon (); W_SetCurrentAmmo (); }; void (entity flg) RegenFlag = { flg.movetype = MOVETYPE_TOSS; flg.solid = SOLID_TRIGGER; setmodel (flg, flg.mdl); setorigin (flg, flg.oldorigin); flg.angles = flg.mangle; flg.cnt = FLAG_AT_BASE; // it's at home base flg.owner = world; flg.velocity = '0 0 0'; // play respawn sound sound (flg, CHAN_VOICE, "items/itembk2.wav", 1, ATTN_NORM); }; void (entity flg) TeamCaptureReturnFlag = { local entity p; RegenFlag (flg); p = find (world, classname, "player"); while (p != world) { if (p.steam != flg.steam) TeamPlayerUpdate (p, "Enemy flag has been returned to base!"); else if (p.steam == flg.steam) TeamPlayerUpdate (p, "Your flag has been returned to base!"); p = find (p, classname, "player"); } }; void () TeamCaptureRegenFlags = { local entity f; f = find (world, classname, "item_flag_team1"); if (f != world) RegenFlag (f); f = find (world, classname, "item_flag_team2"); if (f != world) RegenFlag (f); }; void (entity flg) TeamDropFlag = { local entity p; p = flg.owner; bprint (PRINT_HIGH, p.netname); if (p.steam == TEAM_COLOR1) bprint (PRINT_HIGH, " ìïóô the ÂÌÕÅ flag!\n"); // blue else bprint (PRINT_HIGH, " ìïóô the ÒÅÄ flag!\n"); // red p.effects &= ~(EF_FLAG1 | EF_FLAG2); flg.origin = p.origin - '0 0 24'; flg.cnt = FLAG_DROPPED; flg.velocity = '0 0 300'; flg.flags = FL_ITEM; flg.solid = SOLID_TRIGGER; flg.movetype = MOVETYPE_TOSS; setmodel (flg, flg.mdl); setsize (self, '-16 -16 0', '16 16 74'); // return it after so long flg.super_time = time + TEAM_CAPTURE_FLAG_RETURN_TIME; }; void (entity player) TeamCaptureDropFlagOfPlayer = { local entity e; local string kn; if (!(player.player_flag & ITEM_ENEMY_FLAG)) return; if (player.steam == TEAM_COLOR1) kn = "item_flag_team2"; else kn = "item_flag_team1"; player.player_flag &= ~ITEM_ENEMY_FLAG; e = find (world, classname, kn); if (e != world) TeamDropFlag (e); }; // possum: 3 loops in one. Hopefully this will help prevent an // occasional server lockup when a player picks up and then captures the // flag in rapid succession void () LoopThroughPlayersAfterCapture = { local entity p; // count up teamscr lastteamscrtime = time + TEAMSCRTIME; teamscr1 = teamscr2 = 0; // Ok, let's do the player loop, hand out the bonuses, add up // the scores, and inform the players about the capture p = find (world, classname, "player"); while (p != world) { // Ok, let's do the player loop, hand out the bonuses self = p; self.killed = 0; if (self.steam == other.steam && self != other) self.frags += TEAM_CAPTURE_TEAM_BONUS; if (self.steam != other.steam) { // *XXX* EXPERT CTF // reset the last_hurt_carrier variable in all enemy // players, so that you don't get bonuses for defending // the flag carrier if the flag carrier has already // completed a capture self.last_hurt_carrier = -5; } else if (self.steam == other.steam) { // done to all players on the capturing team // *XXX* EXPERT CTF // award extra points for capture assists if (self.last_returned_flag + TEAM_CAPTURE_RETURN_FLAG_ASSIST_TIMEOUT > time) { bprint (PRINT_HIGH, self.netname); bprint (PRINT_HIGH, " gets an assist for returning his " "flag!\n"); self.frags += TEAM_CAPTURE_RETURN_FLAG_ASSIST_BONUS; } if (self.last_fragged_carrier + TEAM_CAPTURE_FRAG_CARRIER_ASSIST_TIMEOUT > time) { bprint (PRINT_HIGH, self.netname); bprint (PRINT_HIGH, " gets an assist for fragging the flag " "carrier!\n"); self.frags += TEAM_CAPTURE_FRAG_CARRIER_ASSIST_BONUS; } } self.player_flag &= ~ITEM_ENEMY_FLAG; // count up teamscr if (p.steam == TEAM_COLOR1) teamscr1 = teamscr1 + p.frags; else if (p.steam == TEAM_COLOR2) teamscr2 = teamscr2 + p.frags; // inform players about capture if (p.steam == other.steam) TeamPlayerUpdate (p, "Your team captured the flag!"); else if ((p.steam == TEAM_COLOR1 && other.steam == TEAM_COLOR2) || (p.steam == TEAM_COLOR2 && other.steam == TEAM_COLOR1)) TeamPlayerUpdate (p, "Your flag was captured!"); // remove any flags p.effects &= ~(EF_FLAG1 | EF_FLAG2); p = find (p, classname, "player"); } }; void () TeamCaptureFlagTouch = { local entity p; if (other.classname != "player") return; if (other.health <= 0) return; if (self.steam == other.steam) { // same team, if the flag is *not* at the base, return it to base. // we overload the 'cnt' field for this if (self.cnt == FLAG_AT_BASE) { // the flag is at home base. if the player has the enemy flag, // he's just won! if (other.player_flag & ITEM_ENEMY_FLAG) { bprint (PRINT_HIGH, other.netname); if (other.steam == TEAM_COLOR1) bprint (PRINT_HIGH, " ãáðôõòåä the ÂÌÕÅ flag!\n"); // blue else bprint (PRINT_HIGH, " ãáðôõòåä the ÒÅÄ flag!\n"); // red other.items &= ~(IT_KEY1 | IT_KEY2); last_flag_capture = time; last_capture_team = other.steam; sound (other, CHAN_VOICE, "misc/flagcap.wav", 1, ATTN_NONE); // other gets another 10 frag bonus other.frags += TEAM_CAPTURE_CAPTURE_BONUS; // possum: 3 loops in one LoopThroughPlayersAfterCapture (); // respawn flags TeamCaptureRegenFlags (); return; } return; // its at home base already } // hey, its not home. return it by teleporting it back bprint (PRINT_HIGH, other.netname); if (other.steam == TEAM_COLOR1) bprint(PRINT_HIGH, " òåôõòîåä the ÒÅÄ flag!\n"); // red else bprint(PRINT_HIGH, " òåôõòîåä the ÂÌÕÅ flag!\n"); // blue other.frags += TEAM_CAPTURE_RECOVERY_BONUS; // *XXX* EXPERT CTF set time when player last returned his flag other.last_returned_flag = time; sound (other, CHAN_ITEM, self.noise1, 1, ATTN_NORM); TeamCaptureReturnFlag (self); return; } // hey, its not our flag, pick it up bprint (PRINT_HIGH, other.netname); if (other.steam == TEAM_COLOR1) bprint (PRINT_HIGH, " çïô the ÂÌÕÅ flag!\n"); // blue else bprint (PRINT_HIGH, " çïô the ÒÅÄ flag!\n"); // red if (TEAM_CAPTURE_FLAG_BONUS) other.frags += TEAM_CAPTURE_FLAG_BONUS; // TeamPlayerUpdate (other, "YOU GOT THE ENEMY FLAG!\n\nRETURN TO BASE!"); TeamPlayerUpdate (other, "ÙÏÕ ÇÏÔ ÔÈÅ ÅÎÅÍÙ ÆÌÁÇ\n\nÒÅÔÕÒÎ ÔÏ ÂÁÓÅ"); sound (other, CHAN_ITEM, self.noise, 1, ATTN_NORM); other.player_flag += ITEM_ENEMY_FLAG; other.items |= self.items; // *XXX* EXPERT CTF set the time at which the carrier picked up the flag other.flag_since = time; // pick up the flag self.cnt = FLAG_CARRIED; self.solid = SOLID_NOT; self.owner = other; if (self.steam == TEAM_COLOR1) self.owner.effects |= EF_FLAG1; else // must be other team self.owner.effects |= EF_FLAG2; setmodel (self, ""); p = find (world, classname, "player"); while (p != world) { if (p != other) { if (p.steam != other.steam) TeamPlayerUpdate (p, "Your flag has been taken!"); else if (p.steam == other.steam) TeamPlayerUpdate (p, "Your team has the enemy flag!"); } p = find (p, classname, "player"); } }; void () TeamCaptureFlagThink = { self.nextthink = time + 0.1; switch (self.cnt) { case FLAG_AT_BASE: return; // just sitting around waiting to be picked up case FLAG_DROPPED: if (time - self.super_time > TEAM_CAPTURE_FLAG_RETURN_TIME) TeamCaptureReturnFlag (self); return; case FLAG_CARRIED: break; default: objerror ("Flag in invalid state\n"); break; } }; // self is player entity () TeamCaptureSpawn = { if (!(teamplay & TEAM_CAPTURE_FLAG)) return world; if (self.steam == TEAM_COLOR1) { team1_lastspawn = find (team1_lastspawn, classname, "info_player_team1"); if (team1_lastspawn == world) team1_lastspawn = find (team1_lastspawn, classname, "info_player_team1"); return team1_lastspawn; } else if (self.steam == TEAM_COLOR2) { team2_lastspawn = find (team2_lastspawn, classname, "info_player_team2"); if (team2_lastspawn == world) team2_lastspawn = find (team2_lastspawn, classname, "info_player_team2"); return team2_lastspawn; } return world; }; /* From byron@caseware.com Wed Oct 16 18:57:44 1996 Date: Wed, 16 Oct 1996 21:22:37 -0400 From: Byron Long To: zoid@mindlink.net Subject: Team Status Command (source code included) :-) A co-worker of mine wondered if it was possible to add a function to your capture the flag code that would give a status report on an impulse. I think he may have mailed you, but I wrote a quick version myself, which your welcome to use if you like the feature (it offsets some of the problems with the chat capabilities in Quake so it seems like a worthwhile feature). Feel free to change it as necessary. */ // *Capture The Flag - Status report by Wonko void () TeamFlagStatusReport = { local entity flag1, flag2, p; if (!(teamplay & TEAM_CAPTURE_FLAG)) { sprint (self, PRINT_HIGH, "Capture the Flag is not enabled.\n"); return; } // Find the flags at home base flag1 = find (world,classname, "item_flag_team1"); flag2 = find (world,classname, "item_flag_team2"); if (self.classname == "spectator") { if (flag1 != world && flag1.cnt == FLAG_CARRIED) { sprint (self, PRINT_HIGH, flag1.owner.netname); sprint (self, PRINT_HIGH, " has the ÒÅÄ flag.\n"); } else { sprint (self, PRINT_HIGH, "The ÒÅÄ flag is "); if (flag1 == world) sprint(self, PRINT_HIGH, "missing!\n"); if (flag1.cnt == FLAG_AT_BASE) sprint (self, PRINT_HIGH, "at base.\n"); else if (flag1.cnt == FLAG_DROPPED) sprint (self, PRINT_HIGH, "lying about.\n"); else sprint (self, PRINT_HIGH, " corrupt.\n"); } if (flag2 != world && flag2.cnt == FLAG_CARRIED) { sprint (self, PRINT_HIGH, flag2.owner.netname); sprint (self, PRINT_HIGH, " has the ÂÌÕÅ flag. "); } else { sprint (self, PRINT_HIGH, "The ÂÌÕÅ flag is "); if (flag2 == world) sprint (self, PRINT_HIGH, "missing!\n"); if (flag2.cnt == FLAG_AT_BASE) sprint (self, PRINT_HIGH, "at base.\n"); else if (flag2.cnt == FLAG_DROPPED) sprint (self, PRINT_HIGH, "lying about.\n"); else sprint (self, PRINT_HIGH, " corrupt.\n"); } return; } // If on team 2 switch meanings of flags if (self.steam != TEAM_COLOR1) { p = flag1; flag1 = flag2; flag2 = p; } if (flag1 != world && flag1.cnt == FLAG_CARRIED) { sprint (self, PRINT_HIGH, flag1.owner.netname); sprint (self, PRINT_HIGH, " has your flag. "); } else { sprint (self, PRINT_HIGH, "Your flag is "); if (flag1 == world) sprint (self, PRINT_HIGH, "missing! "); if (flag1.cnt == FLAG_AT_BASE) sprint (self, PRINT_HIGH, "in your base. "); else if (flag1.cnt == FLAG_DROPPED) sprint (self, PRINT_HIGH, "lying about. "); else sprint (self, PRINT_HIGH, " corrupt. "); } if (flag2 != world && flag2.cnt == FLAG_CARRIED) { if (self == flag2.owner) sprint (self, PRINT_HIGH, "You have the enemy flag.\n"); else { sprint (self, PRINT_HIGH, flag2.owner.netname); sprint (self, PRINT_HIGH, " has the enemy flag.\n"); } } else { sprint (self, PRINT_HIGH, "The enemy flag is "); if (flag2 == world) sprint (self, PRINT_HIGH, "missing!\n"); if (flag2.cnt == FLAG_AT_BASE) sprint (self, PRINT_HIGH, "in their base.\n"); else if (flag2.cnt == FLAG_DROPPED) sprint (self, PRINT_HIGH, "lying about.\n"); else sprint (self, PRINT_HIGH, " corrupt.\n"); } }; ///////////////////////////////////////////////////////////////////////// $cd id1/models/flag $base base $skin skin void () place_flag = { self.mdl = self.model; // so it can be restored on respawn self.flags = FL_ITEM; // make extra wide self.solid = SOLID_TRIGGER; self.movetype = MOVETYPE_TOSS; self.velocity = '0 0 0'; self.origin_z += 6; self.think = TeamCaptureFlagThink; self.touch = TeamCaptureFlagTouch; self.nextthink = time + 0.1; self.cnt = FLAG_AT_BASE; self.mangle = self.angles; self.effects |= EF_DIMLIGHT; if (!droptofloor()) { dprint ("Flag fell out of level at "); dprint (vtos (self.origin)); dprint ("\n"); remove (self); return; } self.oldorigin = self.origin; // save for flag return }; // ZOID Capture the flag void () item_flag_team1 = { if (!deathmatch || !(cvar ("teamplay") & TEAM_CAPTURE_FLAG)) { remove (self); return; } self.steam = TEAM_COLOR1; self.items = IT_KEY2; precache_model ("progs/flag.mdl"); setmodel (self, "progs/flag.mdl"); self.skin = 0; precache_sound ("misc/flagtk.wav"); // flag taken precache_sound ("misc/flagcap.wav"); // flag capture precache_sound ("doors/runetry.wav"); self.noise = "misc/flagtk.wav"; self.noise1 = "doors/runetry.wav"; setsize (self, '-16 -16 0', '16 16 74'); self.nextthink = time + 0.2; // items start after other solids self.think = place_flag; }; void () item_flag_team2 = { if (!deathmatch || !(cvar("teamplay") & TEAM_CAPTURE_FLAG)) { remove(self); return; } self.steam = TEAM_COLOR2; self.items = IT_KEY1; precache_model ("progs/flag.mdl"); setmodel (self, "progs/flag.mdl"); self.skin = 1; precache_sound ("misc/flagtk.wav"); // flag taken precache_sound ("misc/flagcap.wav"); // flag capture precache_sound ("doors/runetry.wav"); self.noise = "misc/flagtk.wav"; self.noise1 = "doors/runetry.wav"; setsize(self, '-16 -16 0', '16 16 74'); // make it glow self.nextthink = time + 0.2; // items start after other solids self.think = place_flag; }; // Team base starting locations void () info_player_team1 = { }; void () info_player_team2 = { }; /*QUAKED func_ctf_wall (0 .5 .8) ? This is just a solid wall if not inhibitted Only appears in CTF teamplay */ void () func_ctf_wall = { if (cvar ("teamplay") & TEAM_CAPTURE_FLAG) { self.angles = '0 0 0'; self.movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything self.solid = SOLID_BSP; setmodel (self, self.model); } else remove (self); };