mirror of
https://github.com/id-Software/quake-rerelease-qc.git
synced 2025-02-17 09:11:04 +00:00
1249 lines
33 KiB
C++
1249 lines
33 KiB
C++
/* Copyright (C) 1996-2022 id Software LLC
|
|
|
|
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 the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
See file, 'COPYING', for details.
|
|
*/
|
|
|
|
/** Defs **/
|
|
|
|
/** MODIFIABLE CONSTANTS **/
|
|
|
|
float TEAM_DEFAULT_PENALTY = 1; // Default frag penalty
|
|
float TEAM_STRICT_COOP = 0; // Strict Coop
|
|
|
|
// Allowed team colors
|
|
// -1 indicates no color
|
|
|
|
float TEAM_COLOR1 = 5;
|
|
float TEAM_COLOR2 = 14;
|
|
|
|
float team1shirt;
|
|
float team2shirt;
|
|
|
|
/** 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
|
|
|
|
// 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_STATIC_TEAMS = 64; // Don't allow players to switch teams
|
|
float TEAM_DROP_ITEMS = 128; // Allow players to drop packs and
|
|
float TEAM_CAPTURE_SELECT_TEAM = 1024; // team selection
|
|
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 = 15; // 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 = 550; // the radius around an object being
|
|
// defended where a target will be worth extra frags
|
|
float TEAM_CAPTURE_ATTACKER_PROTECT_RADIUS = 550; // 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;
|
|
|
|
// Return a name for the color of a team
|
|
string(float Team) GetTeamColor =
|
|
{
|
|
if(Team == 0) return("Blue");
|
|
else if(Team == 1) return("Steel blue");
|
|
else if(Team == 2) return("Brown");
|
|
else if(Team == 3) return("Baby blue");
|
|
else if(Team == 4) return("Green");
|
|
else if(Team == 5) return("Red");
|
|
else if(Team == 6) return("Olive");
|
|
else if(Team == 7) return("Orange");
|
|
else if(Team == 8) return("Peech");
|
|
else if(Team == 9) return("Purple");
|
|
else if(Team == 10) return("Majenta");
|
|
else if(Team == 11) return("Grey");
|
|
else if(Team == 12) return("Aqua");
|
|
else if(Team == 13) return("Yellow");
|
|
else if(Team == 14) return("Blue");
|
|
return "Unknown";
|
|
};
|
|
|
|
// *XXX* EXPERT CTF
|
|
// Just a quickie to return the ASCII-ized team names for CTF
|
|
string(float Team) GetCTFTeam =
|
|
{
|
|
|
|
if (Team == TEAM_COLOR1) return "$qc_ctf_redteam";
|
|
if (Team == TEAM_COLOR2) return "$qc_ctf_blueteam";
|
|
return "";
|
|
|
|
};
|
|
|
|
void(entity e, float top, float bottom) TeamSetColor =
|
|
{
|
|
if (cvar("pr_checkextension"))
|
|
{
|
|
if (checkextension("DP_SV_SETCOLOR"))
|
|
{
|
|
local float color = bottom + top * 16
|
|
setcolor(e, color);
|
|
return;
|
|
}
|
|
}
|
|
|
|
local string n;
|
|
|
|
stuffcmd(self, "color ");
|
|
n = ftos(top);
|
|
stuffcmd(self, n);
|
|
stuffcmd(self, " ");
|
|
n = ftos(bottom);
|
|
stuffcmd(self, n);
|
|
stuffcmd(self, "\n");
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
TeamPrintSettings
|
|
|
|
Print out current teamplay options
|
|
================
|
|
*/
|
|
|
|
void() TeamPrintSettings =
|
|
{
|
|
local string s;
|
|
|
|
sprint(self,"The following Teamplay options are set:\n");
|
|
|
|
if(teamplay < 0)
|
|
{
|
|
sprint(self, "Frag penalty manually set to ");
|
|
s = ftos(teamplay);
|
|
sprint(self, s);
|
|
sprint(self, "\n");
|
|
return;
|
|
}
|
|
|
|
if(!teamplay)
|
|
{
|
|
sprint(self, "None\n");
|
|
return;
|
|
}
|
|
|
|
if(1 == teamplay)
|
|
{
|
|
sprint(self, "ID's original teamplay 1\n");
|
|
return;
|
|
}
|
|
|
|
if(teamplay & TEAM_HEALTH_PROTECT)
|
|
sprint(self, "Health-Protect ");
|
|
|
|
if(teamplay & TEAM_ARMOR_PROTECT)
|
|
sprint(self, "Armor-Protect ");
|
|
|
|
if(teamplay & TEAM_ATTACKER_DAMAGE)
|
|
sprint(self, "Mirror-Damage ");
|
|
|
|
if(teamplay & TEAM_FRAG_PENALTY)
|
|
sprint(self, "Frag-Penalty ");
|
|
|
|
if(teamplay & TEAM_DEATH_PENALTY)
|
|
sprint(self, "Death-Penalty ");
|
|
|
|
if(teamplay & TEAM_STATIC_TEAMS)
|
|
sprint(self, "Static-Teams ");
|
|
|
|
if(teamplay & TEAM_DROP_ITEMS)
|
|
sprint(self, "Drop-Items (Backpack Impulse 20, Weapon Impulse 21) ");
|
|
|
|
sprint(self, "\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.lastteam == targ.lastteam) &&
|
|
(attacker != targ) && (targ.lastteam > 0) )
|
|
{
|
|
// 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.lastteam == targ.lastteam) &&
|
|
(attacker != targ) && (targ.lastteam > 0) ) {
|
|
// 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 (-1 * teamplay);
|
|
if( (targ.lastteam > 0) && (targ != attacker) && (targ.lastteam ==
|
|
attacker.lastteam) )
|
|
{
|
|
// targ and attacker are on the same team
|
|
if( teamplay < 0 )
|
|
{
|
|
// teamplay indicates frag penalty
|
|
return ( -1 * 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) && (targ.lastteam > 0) &&
|
|
(attacker != targ) && (attacker.lastteam == targ.lastteam) )
|
|
{
|
|
//We should kill the attacker.
|
|
T_Damage(attacker,attacker,attacker,1000);
|
|
//Add a frag to offset the self-kill penalty.
|
|
attacker.frags = attacker.frags + 1;
|
|
}
|
|
};
|
|
|
|
/*
|
|
==================
|
|
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;
|
|
if( (color == TEAM_COLOR1) && (TEAM_COLOR1 >= 0) )
|
|
return TRUE;
|
|
if( (color == TEAM_COLOR2) && (TEAM_COLOR2 >= 0) )
|
|
return TRUE;
|
|
};
|
|
|
|
float(float t) TeamGetShirt =
|
|
{
|
|
if (t == TEAM_COLOR1) {
|
|
team1shirt = team1shirt + 1;
|
|
if (team1shirt > 14)
|
|
team1shirt = 0;
|
|
if (team1shirt == TEAM_COLOR2 - 1)
|
|
team1shirt = team2shirt + 1;
|
|
return team1shirt;
|
|
} else { // color2
|
|
team2shirt = team2shirt + 1;
|
|
if (team2shirt > 14)
|
|
team2shirt = 0;
|
|
if (team2shirt == TEAM_COLOR1 - 1)
|
|
team2shirt = team1shirt + 1;
|
|
return team2shirt;
|
|
}
|
|
return t - 1;
|
|
};
|
|
|
|
/*
|
|
==================
|
|
TeamCheckTeam
|
|
|
|
Check if the team self is on is legal, and put self in a legal team if not.
|
|
==================
|
|
*/
|
|
void() TeamCheckTeam =
|
|
{
|
|
local float TEAM1;
|
|
local float TEAM2;
|
|
|
|
local float newcolor;
|
|
|
|
local entity p;
|
|
|
|
local string n;
|
|
|
|
if ( self.lastteam >= 0 ) {
|
|
if( TeamColorIsLegal(self.team ) ) {
|
|
self.lastteam = self.team;
|
|
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) {
|
|
if (p.team == TEAM_COLOR1)
|
|
TEAM1 = TEAM1 + 1;
|
|
else if (p.team == TEAM_COLOR2)
|
|
TEAM2 = TEAM2 + 1;
|
|
}
|
|
p = find(p, classname, "player");
|
|
}
|
|
|
|
// Find the team with the least players.
|
|
newcolor = TEAM_COLOR1;
|
|
if (((TEAM2 < TEAM1) || (TEAM2 == TEAM1 && random() < 0.5)))
|
|
newcolor = TEAM_COLOR2;
|
|
|
|
// Put the player on a the new team.
|
|
TeamSetColor(self, newcolor - 1, newcolor - 1);
|
|
|
|
self.lastteam = newcolor; // Remember what team we're on
|
|
self.team = newcolor;
|
|
};
|
|
|
|
/*
|
|
===============
|
|
TeamCheckLock
|
|
|
|
Check for team changing and perform whatever actions are neccessary.
|
|
===============
|
|
*/
|
|
void() TeamCheckLock =
|
|
{
|
|
local float n;
|
|
local string s;
|
|
|
|
// Don't do anything if teamplay is negative
|
|
if ( teamplay < 0 )
|
|
return;
|
|
|
|
if (self.observer || self.do_observer || gamestart) {
|
|
if (self.lastteam != 1)
|
|
TeamSetColor(self, 0, 0);
|
|
self.lastteam = 1;
|
|
return;
|
|
}
|
|
|
|
if (self.player_flag & TEAM_STUFF_COLOR) {
|
|
self.player_flag = self.player_flag - TEAM_STUFF_COLOR;
|
|
TeamSetColor(self, self.lastteam - 1, self.lastteam - 1);
|
|
return;
|
|
}
|
|
|
|
if ( !TeamColorIsLegal(self.team) && (self.team == self.lastteam)) {
|
|
self.lastteam = -1;
|
|
}
|
|
|
|
// Check to see if the player has changed colors
|
|
if (self.team != self.lastteam)
|
|
{
|
|
// 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.lastteam >= 0) )
|
|
{
|
|
if ( TeamColorIsLegal(self.lastteam) )
|
|
{
|
|
// changing teams sucks, kill him
|
|
|
|
// if he has tried to change teams several
|
|
// times, kick him off the server.
|
|
if (self.suicide_count > 3) {
|
|
stuffcmd(self, "disconnect\n");
|
|
}
|
|
// case base respawn
|
|
if (self.killed != 1)
|
|
self.killed = 2;
|
|
self.invincible_finished = 0;
|
|
T_Damage(self,self,self,1000); // Kill the player
|
|
// trying to change teams counts as a suicide
|
|
self.suicide_count = self.suicide_count + 1;
|
|
|
|
stuffcmd(self, "color ");
|
|
n = TeamGetShirt(self.lastteam);
|
|
TeamSetColor(self, self.lastteam - 1, self.lastteam - 1);
|
|
return;
|
|
}
|
|
else {
|
|
// If we're on an illegal team, force a change.
|
|
self.lastteam = -50;
|
|
}
|
|
}
|
|
|
|
if(self.lastteam > 0) {
|
|
// case base respawn
|
|
if (self.killed != 1)
|
|
self.killed = 2;
|
|
T_Damage(self,self,self,1000); // Kill the player
|
|
}
|
|
self.frags = 0; // Zero out frags
|
|
TeamCheckTeam();
|
|
}
|
|
};
|
|
|
|
/*
|
|
=======================
|
|
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
|
|
if(self.currentammo <= 0)
|
|
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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = self.ammo_cells - 20;
|
|
}
|
|
else
|
|
{
|
|
item.ammo_cells = self.ammo_cells;
|
|
self.ammo_cells = 0;
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
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 float hadammo, best, new, old;
|
|
local entity stemp;
|
|
|
|
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 up to the new one if better
|
|
stemp = self;
|
|
self = other;
|
|
best = W_BestWeapon();
|
|
self = stemp;
|
|
|
|
if (self.classname == "weapon_nailgun")
|
|
{
|
|
hadammo = other.ammo_nails;
|
|
new = IT_NAILGUN;
|
|
}
|
|
else if (self.classname == "weapon_supernailgun")
|
|
{
|
|
hadammo = other.ammo_rockets;
|
|
new = IT_SUPER_NAILGUN;
|
|
}
|
|
else if (self.classname == "weapon_supershotgun")
|
|
{
|
|
hadammo = other.ammo_rockets;
|
|
new = IT_SUPER_SHOTGUN;
|
|
}
|
|
else if (self.classname == "weapon_rocketlauncher")
|
|
{
|
|
hadammo = other.ammo_rockets;
|
|
new = IT_ROCKET_LAUNCHER;
|
|
}
|
|
else if (self.classname == "weapon_grenadelauncher")
|
|
{
|
|
hadammo = other.ammo_rockets;
|
|
new = IT_GRENADE_LAUNCHER;
|
|
}
|
|
else if (self.classname == "weapon_lightning")
|
|
{
|
|
hadammo = other.ammo_rockets;
|
|
new = IT_LIGHTNING;
|
|
}
|
|
else
|
|
objerror ("Team_weapon_touch: unknown classname");
|
|
|
|
sprint(other, "$qc_got_item", self.netname);
|
|
// 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 = 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_HOOK))
|
|
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;
|
|
|
|
if(self.weapon == 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 = self.items - IT_SUPER_SHOTGUN;
|
|
}
|
|
|
|
if( self.weapon == IT_NAILGUN )
|
|
{
|
|
setmodel (item, "progs/g_nail.mdl");
|
|
item.weapon = IT_NAILGUN;
|
|
item.netname = "nailgun";
|
|
item.classname = "weapon_nailgun";
|
|
self.items = self.items - IT_NAILGUN;
|
|
}
|
|
|
|
if( self.weapon == IT_SUPER_NAILGUN)
|
|
{
|
|
setmodel (item, "progs/g_nail2.mdl");
|
|
item.weapon = IT_SUPER_NAILGUN;
|
|
item.netname = "Super Nailgun";
|
|
item.classname = "weapon_supernailgun";
|
|
self.items = self.items - IT_SUPER_NAILGUN;
|
|
}
|
|
|
|
if( self.weapon == IT_GRENADE_LAUNCHER)
|
|
{
|
|
setmodel (item, "progs/g_rock.mdl");
|
|
item.weapon = IT_GRENADE_LAUNCHER;
|
|
item.netname = "Grenade Launcher";
|
|
item.classname = "weapon_grenadelauncher";
|
|
self.items = self.items - IT_GRENADE_LAUNCHER;
|
|
}
|
|
|
|
if( self.weapon == IT_ROCKET_LAUNCHER )
|
|
{
|
|
setmodel (item, "progs/g_rock2.mdl");
|
|
item.weapon = IT_ROCKET_LAUNCHER;
|
|
item.netname = "Rocket Launcher";
|
|
item.classname = "weapon_rocketlauncher";
|
|
self.items = self.items - IT_ROCKET_LAUNCHER;
|
|
}
|
|
|
|
if( self.weapon == IT_LIGHTNING )
|
|
{
|
|
setmodel (item, "progs/g_light.mdl");
|
|
item.weapon = IT_LIGHTNING;
|
|
item.netname = "Thunderbolt";
|
|
item.classname = "weapon_lightning";
|
|
self.items = self.items - IT_LIGHTNING;
|
|
}
|
|
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;
|
|
sound (flg, CHAN_VOICE, "items/itembk2.wav", 1, ATTN_NORM); // play respawn sound
|
|
setorigin(flg, flg.oldorigin);
|
|
flg.angles = flg.mangle;
|
|
flg.cnt = FLAG_AT_BASE; // it's at home base
|
|
flg.owner = world;
|
|
};
|
|
|
|
void(entity flg) TeamCaptureReturnFlag =
|
|
{
|
|
local entity p;
|
|
|
|
RegenFlag(flg);
|
|
|
|
p = find(world, classname, "player");
|
|
while (p != world) {
|
|
if (p.team != flg.team)
|
|
centerprint(p, "$qc_ctf_enemy_returned");
|
|
else if (p.team == flg.team)
|
|
centerprint(p, "$qc_ctf_your_returned");
|
|
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 item, f, oself;
|
|
local entity p;
|
|
|
|
p = flg.owner;
|
|
|
|
if (p.lastteam == TEAM_COLOR1)
|
|
bprint("$qc_ks_blue_dropped", p.netname); // blue
|
|
else
|
|
bprint("$qc_ks_red_dropped", p.netname); // red
|
|
LogMsg(p, "FLAG-DROP");
|
|
|
|
flg.origin = p.origin - '0 0 24';
|
|
flg.cnt = FLAG_DROPPED;
|
|
//NOTE! We check lastteam here instead of team--this is because
|
|
//in the mode where we change colors, we get killed
|
|
flg.velocity_z = 300;
|
|
flg.velocity_x = 0;
|
|
flg.velocity_y = 0;
|
|
flg.flags = FL_ITEM | FL_OBJECTIVE;
|
|
flg.solid = SOLID_TRIGGER;
|
|
flg.movetype = MOVETYPE_TOSS;
|
|
setsize(flg, '-16 -16 0', '16 16 74');
|
|
// return it after so long
|
|
flg.super_time = time + TEAM_CAPTURE_FLAG_RETURN_TIME;
|
|
SendCTFScoresUpdateAll();
|
|
};
|
|
|
|
void(entity player) TeamCaptureDropFlagOfPlayer =
|
|
{
|
|
local string kn;
|
|
local entity e;
|
|
|
|
if (!(player.player_flag & ITEM_ENEMY_FLAG))
|
|
return;
|
|
if (player.lastteam == TEAM_COLOR1)
|
|
kn = "item_flag_team2";
|
|
else
|
|
kn = "item_flag_team1";
|
|
player.player_flag = player.player_flag - ITEM_ENEMY_FLAG;
|
|
e = find(world, classname, kn);
|
|
if (e != world)
|
|
TeamDropFlag(e);
|
|
};
|
|
|
|
void() TeamCaptureFlagTouch =
|
|
{
|
|
local entity p, oself;
|
|
|
|
if (other.classname != "player")
|
|
return;
|
|
if (other.health <= 0)
|
|
return;
|
|
|
|
if (other.team != other.lastteam)
|
|
return; // something is fishy, somebody is playing with colors
|
|
|
|
if (self.lastteam == other.lastteam) {
|
|
// 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) {
|
|
if (other.team == TEAM_COLOR1)
|
|
bprint("$qc_ks_blue_captured", other.netname); // blue
|
|
else
|
|
bprint("$qc_ks_red_captured", other.netname); // red
|
|
LogMsg(other, "FLAG-CAPTURE");
|
|
other.items = other.items - (other.items & (IT_KEY1 | IT_KEY2));
|
|
|
|
last_flag_capture = time;
|
|
last_capture_team = other.team;
|
|
|
|
sound (other, CHAN_VOICE, "misc/flagcap.wav", 1, ATTN_NONE);
|
|
if (other.team == TEAM_COLOR1) {
|
|
teamscr1 += 1;
|
|
} else {
|
|
teamscr2 += 1;
|
|
}
|
|
|
|
// other gets another 10 frag bonus
|
|
other.frags = other.frags + TEAM_CAPTURE_CAPTURE_BONUS;
|
|
|
|
// Ok, let's do the player loop, hand out the bonuses
|
|
p = find(world, classname, "player");
|
|
while (p != world) {
|
|
self = p;
|
|
self.killed = 0;
|
|
if (self.lastteam == other.lastteam && self != other)
|
|
self.frags = self.frags + TEAM_CAPTURE_TEAM_BONUS;
|
|
if (self.lastteam != other.lastteam) {
|
|
// *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.lastteam == other.lastteam) {
|
|
// 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("$qc_ks_assist", self.netname);
|
|
self.frags = self.frags + TEAM_CAPTURE_RETURN_FLAG_ASSIST_BONUS;
|
|
}
|
|
if (self.last_fragged_carrier + TEAM_CAPTURE_FRAG_CARRIER_ASSIST_TIMEOUT > time) {
|
|
bprint("$qc_ks_assist_carrier", self.netname);
|
|
self.frags = self.frags + TEAM_CAPTURE_FRAG_CARRIER_ASSIST_BONUS;
|
|
}
|
|
}
|
|
self.player_flag = self.player_flag - (self.player_flag & ITEM_ENEMY_FLAG);
|
|
|
|
p = find(p, classname, "player");
|
|
}
|
|
|
|
|
|
p = find(world, classname, "player");
|
|
while (p != world) {
|
|
if ((p.lastteam == TEAM_COLOR1 && other.lastteam == TEAM_COLOR2) ||
|
|
(p.lastteam == TEAM_COLOR2 && other.lastteam == TEAM_COLOR1))
|
|
centerprint(p, "$qc_ctf_your_captured");
|
|
else if (p.lastteam == other.lastteam)
|
|
centerprint(p, "$qc_ctf_team_captured");
|
|
p = find(p, classname, "player");
|
|
}
|
|
// respawn flags
|
|
TeamCaptureRegenFlags();
|
|
SendCTFScoresUpdateAll();
|
|
return;
|
|
}
|
|
|
|
return; // its at home base already
|
|
}
|
|
// hey, its not home. return it by teleporting it back
|
|
if (other.team == TEAM_COLOR1)
|
|
bprint("$qc_ks_red_returned", other.netname); // red
|
|
else
|
|
bprint("$qc_ks_blue_returned", other.netname); // blue
|
|
LogMsg(other, "FLAG-RECOVERY");
|
|
other.frags = 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);
|
|
SendCTFScoresUpdateAll();
|
|
return;
|
|
}
|
|
|
|
// hey, its not our flag, pick it up
|
|
if (other.team == TEAM_COLOR1)
|
|
bprint("$qc_ks_blue_picked_up", other.netname); // blue
|
|
else
|
|
bprint("$qc_ks_blue_picked_up", other.netname); // red
|
|
LogMsg(other, "FLAG-PICKUP");
|
|
if (TEAM_CAPTURE_FLAG_BONUS)
|
|
other.frags = other.frags + TEAM_CAPTURE_FLAG_BONUS;
|
|
centerprint(other, "$qc_ctf_have_flag");
|
|
sound (other, CHAN_ITEM, self.noise, 1, ATTN_NORM);
|
|
|
|
other.player_flag = other.player_flag + ITEM_ENEMY_FLAG;
|
|
other.items = 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.movetype = MOVETYPE_NOCLIP;
|
|
self.solid = SOLID_NOT;
|
|
self.owner = other;
|
|
|
|
p = find(world, classname, "player");
|
|
while (p != world) {
|
|
if (p != other) {
|
|
if ((p.team == TEAM_COLOR1 && other.team == TEAM_COLOR2) ||
|
|
(p.team == TEAM_COLOR2 && other.team == TEAM_COLOR1))
|
|
centerprint(p, "$qc_ctf_your_taken");
|
|
else if (p.team == other.team)
|
|
centerprint(p, "$qc_ctf_your_has");
|
|
}
|
|
p = find(p, classname, "player");
|
|
}
|
|
|
|
SendCTFScoresUpdateAll();
|
|
};
|
|
|
|
void() TeamCaptureFlagThink =
|
|
{
|
|
local entity e;
|
|
local vector v;
|
|
local float f;
|
|
local string s;
|
|
|
|
self.nextthink = time + 0.1;
|
|
|
|
if (self.cnt == FLAG_AT_BASE)
|
|
return; // just sitting around waiting to be picked up
|
|
|
|
if (self.cnt == FLAG_DROPPED) {
|
|
if (time - self.super_time > TEAM_CAPTURE_FLAG_RETURN_TIME)
|
|
TeamCaptureReturnFlag(self);
|
|
SendCTFScoresUpdateAll();
|
|
return;
|
|
}
|
|
|
|
if (self.cnt != FLAG_CARRIED)
|
|
objerror("Flag in invalid state\n");
|
|
|
|
e = self.owner;
|
|
if ((e.classname != "player") || (e.deadflag) ||
|
|
(!(e.player_flag & ITEM_ENEMY_FLAG))) {
|
|
TeamDropFlag(self);
|
|
return;
|
|
}
|
|
makevectors (e.angles);
|
|
v = v_forward;
|
|
//dprint("fwd: ");
|
|
//s = vtos(v);
|
|
//dprint(s);
|
|
//dprint("\n");
|
|
// if (v_z < 0) {
|
|
// v_z = (-1)*v_z;
|
|
// v = v + v * 1.2 * v_z;
|
|
// }
|
|
//dprint("adj: ");
|
|
//s = vtos(v);
|
|
//dprint(s);
|
|
//dprint("\n");
|
|
v_z = (-1) * v_z; // reverse z component
|
|
|
|
f = 14;
|
|
if (self.owner.frame >= 29 && self.owner.frame <= 40) {
|
|
if (self.owner.frame >= 29 && self.owner.frame <= 34) { //axpain
|
|
if (self.owner.frame == 29) f = f + 2;
|
|
else if (self.owner.frame == 30) f = f + 8;
|
|
else if (self.owner.frame == 31) f = f + 12;
|
|
else if (self.owner.frame == 32) f = f + 11;
|
|
else if (self.owner.frame == 33) f = f + 10;
|
|
else if (self.owner.frame == 34) f = f + 4;
|
|
} else if (self.owner.frame >= 35 && self.owner.frame <= 40) { // pain
|
|
if (self.owner.frame == 35) f = f + 2;
|
|
else if (self.owner.frame == 36) f = f + 10;
|
|
else if (self.owner.frame == 37) f = f + 10;
|
|
else if (self.owner.frame == 38) f = f + 8;
|
|
else if (self.owner.frame == 39) f = f + 4;
|
|
else if (self.owner.frame == 40) f = f + 2;
|
|
}
|
|
} else if (self.owner.frame >= 103 && self.owner.frame <= 118) {
|
|
if (self.owner.frame >= 103 && self.owner.frame <= 104) f = f + 6; //nailattack
|
|
else if (self.owner.frame >= 105 && self.owner.frame <= 106) f = f + 6; //light
|
|
else if (self.owner.frame >= 107 && self.owner.frame <= 112) f = f + 7; //rocketattack
|
|
else if (self.owner.frame >= 112 && self.owner.frame <= 118) f = f + 7; //shotattack
|
|
}
|
|
self.origin = e.origin + '0 0 -16' - f*v + v_right * 22;
|
|
self.angles = e.angles + '0 0 -45';
|
|
setorigin (self, self.origin);
|
|
self.nextthink = time + 0.01;
|
|
};
|
|
|
|
// self is player
|
|
entity() TeamCaptureSpawn =
|
|
{
|
|
if (self.team == 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.team == 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;
|
|
};
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
$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 | FL_OBJECTIVE; // make extra wide
|
|
self.solid = SOLID_TRIGGER;
|
|
self.movetype = MOVETYPE_TOSS;
|
|
self.velocity = '0 0 0';
|
|
self.origin_z = 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 = 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 =
|
|
{
|
|
self.team = TEAM_COLOR1;
|
|
self.lastteam = 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";
|
|
self.effects = EF_PENTALIGHT;
|
|
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 =
|
|
{
|
|
self.team = TEAM_COLOR2;
|
|
self.lastteam = 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";
|
|
self.effects = EF_QUADLIGHT;
|
|
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;
|
|
};
|
|
|
|
|
|
/*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 =
|
|
{
|
|
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);
|
|
};
|
|
|