749 lines
26 KiB
C++
749 lines
26 KiB
C++
enum // static entity types
|
|
{
|
|
NOTUSED = 0, // entity slot not in use in map (usually seen at deleted entities)
|
|
LIGHT, // lightsource, attr1 = radius, attr2 = intensity (or attr2..4 = r-g-b)
|
|
PLAYERSTART, // attr1 = angle, attr2 = team
|
|
I_CLIPS, I_AMMO, I_GRENADE, // attr1 = elevation
|
|
I_HEALTH, I_HELMET, I_ARMOUR, I_AKIMBO,
|
|
MAPMODEL, // attr1 = angle, attr2 = idx, attr3 = elevation, attr4 = texture, attr5 = pitch, attr6 = roll
|
|
CARROT, // attr1 = tag, attr2 = type
|
|
LADDER, // attr1 = height
|
|
CTF_FLAG, // attr1 = angle, attr2 = red/blue
|
|
SOUND, // attr1 = idx, attr2 = radius, attr3 = size, attr4 = volume
|
|
CLIP, // attr1 = elevation, attr2 = xradius, attr3 = yradius, attr4 = height, attr6 = slope, attr7 = shape
|
|
PLCLIP, // attr1 = elevation, attr2 = xradius, attr3 = yradius, attr4 = height, attr6 = slope, attr7 = shape
|
|
DUMMYENT, // temporary entity without any function - will not be saved to map files, used to mark positions and for scripting
|
|
MAXENTTYPES
|
|
};
|
|
|
|
extern short entwraparound[MAXENTTYPES][7];
|
|
extern uchar entscale[MAXENTTYPES][7];
|
|
#define ENTSCALE10 10
|
|
#define ENTSCALE5 5
|
|
#define VANTAGEDUMMY2 222
|
|
|
|
enum {MAP_IS_BAD, MAP_IS_EDITABLE, MAP_IS_GOOD};
|
|
|
|
extern const char *entnames[];
|
|
#define isitem(i) ((i) >= I_CLIPS && (i) <= I_AKIMBO)
|
|
|
|
struct persistent_entity // map entity
|
|
{
|
|
short x, y, z; // cube aligned position
|
|
short attr1;
|
|
uchar type; // type is one of the above
|
|
uchar attr2, attr3, attr4;
|
|
short attr5;
|
|
char attr6;
|
|
unsigned char attr7;
|
|
persistent_entity(short x, short y, short z, uchar type, short attr1, uchar attr2, uchar attr3, uchar attr4, short attr5, char attr6, unsigned char attr7) : x(x), y(y), z(z), attr1(attr1), type(type), attr2(attr2), attr3(attr3), attr4(attr4), attr5(attr5), attr6(attr6), attr7(attr7) {}
|
|
persistent_entity(short x, short y, short z, uchar type, short attr1, uchar attr2, uchar attr3, uchar attr4) : x(x), y(y), z(z), attr1(attr1), type(type), attr2(attr2), attr3(attr3), attr4(attr4), attr5(0), attr6(0), attr7(0) {}
|
|
persistent_entity() {}
|
|
};
|
|
|
|
struct entity : persistent_entity
|
|
{
|
|
bool spawned; //the dynamic states of a map entity
|
|
int lastmillis;
|
|
entity(short x, short y, short z, uchar type, short attr1, uchar attr2, uchar attr3, uchar attr4, short attr5, char attr6, unsigned char attr7) : persistent_entity(x, y, z, type, attr1, attr2, attr3, attr4, attr5, attr6, attr7), spawned(false) {}
|
|
entity(short x, short y, short z, uchar type, short attr1, uchar attr2, uchar attr3, uchar attr4) : persistent_entity(x, y, z, type, attr1, attr2, attr3, attr4), spawned(false) {}
|
|
entity() {}
|
|
bool fitsmode(int gamemode) { return !m_noitems && isitem(type) && !(m_noitemsnade && type!=I_GRENADE) && !(m_pistol && type==I_AMMO); }
|
|
void transformtype(int gamemode)
|
|
{
|
|
if(m_noitemsnade && type==I_CLIPS) type = I_GRENADE;
|
|
else if(m_pistol && ( type==I_AMMO || type==I_GRENADE )) type = I_CLIPS;
|
|
}
|
|
};
|
|
|
|
enum { GUN_KNIFE = 0, GUN_PISTOL, GUN_CARBINE, GUN_SHOTGUN, GUN_SUBGUN, GUN_SNIPER, GUN_ASSAULT, GUN_GRENADE, GUN_AKIMBO, NUMGUNS };
|
|
#define valid_weapon(g) ((g) >= GUN_KNIFE && (g) < NUMGUNS)
|
|
#define reloadable_gun(g) ((g) != GUN_KNIFE && (g) != GUN_GRENADE)
|
|
|
|
#define SGRAYS 21
|
|
#define SGDMGTOTAL 90
|
|
|
|
#define SGDMGBONUS 65
|
|
#define SGDMGDISTB 50
|
|
|
|
#define SGCCdmg 500
|
|
#define SGCCbase 0
|
|
#define SGCCrange 40
|
|
|
|
#define SGCMdmg 375
|
|
#define SGCMbase 25
|
|
#define SGCMrange 60
|
|
|
|
#define SGCOdmg 125
|
|
#define SGCObase 45
|
|
#define SGCOrange 75
|
|
|
|
#define SGMAXDMGABS 105
|
|
#define SGMAXDMGLOC 84
|
|
#define EXPDAMRAD 10
|
|
// "explosive deaths" match option. Defaults: radius of a grenade, 75% of a grenade's damage at the centre.
|
|
// matchdeathradius / matchdeathpower are percentages of those defaults (server.cpp).
|
|
extern int matchdeathradius, matchdeathpower;
|
|
#define DEATHBLASTRAD (EXPDAMRAD*matchdeathradius/100.0f)
|
|
#define DEATHBLASTDMG (guns[GUN_GRENADE].damage*3/4*matchdeathpower/100)
|
|
|
|
// Match settings. Online they belong to the server: only an admin can change them (SV_MATCHSET), the server keeps
|
|
// the values, sends them to every client when they change and to everybody who joins (SV_MATCHSETTINGS).
|
|
// Offline/bot games have no server, so there you simply set them yourself. Defined in server.cpp (which is part of the client too).
|
|
struct matchsettingdef { const char *name; int *var; int min, def, max; };
|
|
#define NUMMATCHSETTINGS 20
|
|
extern matchsettingdef matchsettings[NUMMATCHSETTINGS];
|
|
extern int matchsynced[NUMMATCHSETTINGS]; // client side: the values the server last announced
|
|
extern void matchsettings_reset(); // everything back to the defaults
|
|
extern bool matchsetting_apply(int idx, int val); // clamps and stores, true if the value changed
|
|
|
|
struct itemstat { int add, start, max, sound; };
|
|
extern itemstat ammostats[NUMGUNS];
|
|
extern itemstat powerupstats[I_ARMOUR-I_HEALTH+1];
|
|
|
|
struct guninfo { char modelname[23], title[42]; short sound, reload, reloadtime, attackdelay, damage, piercing, projspeed, part, spread, recoil, magsize, mdl_kick_rot, mdl_kick_back, recoilincrease, recoilbase, maxrecoil, recoilbackfade, pushfactor; bool isauto; float speedmul; };
|
|
extern guninfo guns[NUMGUNS];
|
|
extern const char *gunnames[];
|
|
|
|
static inline int reloadtime(int gun) { return guns[gun].reloadtime; }
|
|
static inline int attackdelay(int gun) { return guns[gun].attackdelay; }
|
|
static inline int magsize(int gun) { return guns[gun].magsize; }
|
|
|
|
/** roseta stone:
|
|
0000, 0001, 0010, 0011, 0100, 0101, 0110 */
|
|
enum { TEAM_CLA = 0, TEAM_RVSF, TEAM_CLA_SPECT, TEAM_RVSF_SPECT, TEAM_SPECT, TEAM_NUM, TEAM_ANYACTIVE };
|
|
extern const char *teamnames[];
|
|
extern const char *teamnames_s[];
|
|
extern const char *killmessages[2][NUMGUNS];
|
|
|
|
#define isteam(a,b) (m_teammode && (a) == (b))
|
|
#define team_opposite(o) (team_isvalid(o) && (o) < TEAM_SPECT ? (o) ^ 1 : TEAM_SPECT)
|
|
#define team_base(t) ((t) & 1)
|
|
#define team_basestring(t) ((t) == 1 ? teamnames[1] : ((t) == 0 ? teamnames[0] : "SPECT"))
|
|
#define team_isvalid(t) ((int(t)) >= 0 && (t) < TEAM_NUM)
|
|
#define team_isactive(t) ((t) == TEAM_CLA || (t) == TEAM_RVSF)
|
|
#define team_isspect(t) ((t) > TEAM_RVSF && (t) < TEAM_NUM)
|
|
#define team_group(t) ((t) == TEAM_SPECT ? TEAM_SPECT : team_base(t))
|
|
#define team_tospec(t) ((t) == TEAM_SPECT ? TEAM_SPECT : team_base(t) + TEAM_CLA_SPECT - TEAM_CLA)
|
|
// note: team_isactive and team_base can/should be used to check the limits for arrays of size '2'
|
|
static inline const char *team_string(int t, bool abbr = false) { const char **n = abbr ? teamnames_s : teamnames; return team_isvalid(t) ? n[t] : n[TEAM_NUM + 1]; }
|
|
|
|
enum { ENT_PLAYER = 0, ENT_BOT, ENT_CAMERA, ENT_BOUNCE };
|
|
enum { CS_ALIVE = 0, CS_DEAD, CS_SPAWNING, CS_LAGGED, CS_EDITING, CS_SPECTATE };
|
|
enum { CR_DEFAULT = 0, CR_MASTER, CR_ADMIN, CR_OWNER, CR_NUM };
|
|
enum { SM_NONE = 0, SM_DEATHCAM, SM_FOLLOW1ST, SM_FOLLOW3RD, SM_FOLLOW3RD_TRANSPARENT, SM_FLY, SM_OVERVIEW, SM_NUM };
|
|
enum { FPCN_VOID = -4, FPCN_DEATHCAM = -2, FPCN_FLY = -2, FPCN_OVERVIEW = -1 };
|
|
|
|
class physent
|
|
{
|
|
public:
|
|
vec o, vel; // origin, velocity
|
|
vec deltapos, newpos; // movement interpolation
|
|
float yaw, pitch, roll; // used as vec in one place
|
|
float pitchvel;
|
|
float maxspeed; // cubes per second, 24 for player
|
|
int timeinair; // used for fake gravity
|
|
float radius, eyeheight, maxeyeheight, aboveeye; // bounding box size
|
|
bool inwater;
|
|
bool onfloor, onladder, jumpnext, jumpd, crouching, crouchedinair, trycrouch, cancollide, stuck, scoping;
|
|
int lastjump;
|
|
float lastjumpheight;
|
|
int lastsplash;
|
|
char move, strafe;
|
|
uchar state, type;
|
|
float eyeheightvel;
|
|
int last_pos;
|
|
|
|
physent() : o(0, 0, 0), deltapos(0, 0, 0), newpos(0, 0, 0), yaw(270), pitch(0), roll(0), pitchvel(0),
|
|
crouching(false), crouchedinair(false), trycrouch(false), cancollide(true), stuck(false), scoping(false), lastjump(0), lastjumpheight(200), lastsplash(0), state(CS_ALIVE), last_pos(0)
|
|
{
|
|
reset();
|
|
}
|
|
virtual ~physent() {}
|
|
|
|
void resetinterp()
|
|
{
|
|
newpos = o;
|
|
newpos.z -= eyeheight;
|
|
deltapos = vec(0, 0, 0);
|
|
}
|
|
|
|
void reset()
|
|
{
|
|
vel.x = vel.y = vel.z = eyeheightvel = 0.0f;
|
|
move = strafe = 0;
|
|
timeinair = lastjump = lastsplash = 0;
|
|
onfloor = onladder = inwater = jumpnext = jumpd = crouching = crouchedinair = trycrouch = stuck = false;
|
|
last_pos = 0;
|
|
}
|
|
|
|
virtual void oncollision() {}
|
|
virtual void onmoved(const vec &dist) {}
|
|
};
|
|
|
|
class dynent : public physent // animated ent
|
|
{
|
|
public:
|
|
bool k_left, k_right, k_up, k_down; // see input code
|
|
|
|
animstate prev[2], current[2]; // md2's need only [0], md3's need both for the lower&upper model
|
|
int lastanimswitchtime[2];
|
|
void *lastmodel[2];
|
|
int lastrendered;
|
|
|
|
void stopmoving()
|
|
{
|
|
k_left = k_right = k_up = k_down = jumpnext = false;
|
|
move = strafe = 0;
|
|
}
|
|
|
|
void resetanim()
|
|
{
|
|
loopi(2)
|
|
{
|
|
prev[i].reset();
|
|
current[i].reset();
|
|
lastanimswitchtime[i] = -1;
|
|
lastmodel[i] = NULL;
|
|
}
|
|
lastrendered = 0;
|
|
}
|
|
|
|
void reset()
|
|
{
|
|
physent::reset();
|
|
stopmoving();
|
|
}
|
|
|
|
dynent() { reset(); resetanim(); }
|
|
virtual ~dynent() {}
|
|
};
|
|
|
|
#define MAXNAMELEN 15
|
|
|
|
class bounceent;
|
|
|
|
#define POSHIST_SIZE 7
|
|
|
|
struct poshist
|
|
{
|
|
int nextupdate, curpos, numpos;
|
|
vec pos[POSHIST_SIZE];
|
|
|
|
poshist() : nextupdate(0) { reset(); }
|
|
|
|
const int size() const { return numpos; }
|
|
|
|
void reset()
|
|
{
|
|
curpos = 0;
|
|
numpos = 0;
|
|
}
|
|
|
|
void addpos(const vec &o)
|
|
{
|
|
pos[curpos] = o;
|
|
curpos++;
|
|
if(curpos>=POSHIST_SIZE) curpos = 0;
|
|
if(numpos<POSHIST_SIZE) numpos++;
|
|
}
|
|
|
|
const vec &getpos(int i) const
|
|
{
|
|
i = curpos-1-i;
|
|
if(i < 0) i += POSHIST_SIZE;
|
|
return pos[i];
|
|
}
|
|
|
|
void update(const vec &o, int lastmillis)
|
|
{
|
|
if(lastmillis<nextupdate) return;
|
|
if(o.dist(pos[0]) >= 4.0f) addpos(o);
|
|
nextupdate = lastmillis + 100;
|
|
}
|
|
};
|
|
|
|
#define MEDKIT_MAX 2
|
|
#define MEDKIT_HEAL 33
|
|
|
|
class playerstate
|
|
{
|
|
public:
|
|
int health, armour;
|
|
int primary, nextprimary;
|
|
int gunselect;
|
|
bool akimbo;
|
|
int ammo[NUMGUNS], mag[NUMGUNS], gunwait[NUMGUNS];
|
|
int pstatshots[NUMGUNS], pstatdamage[NUMGUNS];
|
|
int medkits; // carryable health pickups when matchmedkit is on (3rd-slot style usable item)
|
|
|
|
playerstate() : armour(0), primary(GUN_ASSAULT), nextprimary(GUN_ASSAULT), akimbo(false), medkits(0) {}
|
|
virtual ~playerstate() {}
|
|
|
|
void resetstats() { loopi(NUMGUNS) pstatshots[i] = pstatdamage[i] = 0; }
|
|
|
|
itemstat *itemstats(int type)
|
|
{
|
|
switch(type)
|
|
{
|
|
case I_CLIPS: return &ammostats[GUN_PISTOL];
|
|
case I_AMMO: return &ammostats[primary];
|
|
case I_GRENADE: return &ammostats[GUN_GRENADE];
|
|
case I_AKIMBO: return &ammostats[GUN_AKIMBO];
|
|
case I_HEALTH:
|
|
case I_HELMET:
|
|
case I_ARMOUR:
|
|
return &powerupstats[type - I_HEALTH];
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
bool canpickup(int type)
|
|
{
|
|
extern int matchmedkit;
|
|
switch(type)
|
|
{
|
|
case I_CLIPS: return ammo[akimbo && ammo[GUN_AKIMBO]<ammostats[GUN_AKIMBO].max ? GUN_AKIMBO : GUN_PISTOL]<ammostats[akimbo && ammo[GUN_AKIMBO]<ammostats[GUN_AKIMBO].max ? GUN_AKIMBO : GUN_PISTOL].max;
|
|
case I_AMMO: return ammo[primary]<ammostats[primary].max;
|
|
case I_GRENADE: return mag[GUN_GRENADE]<ammostats[GUN_GRENADE].max;
|
|
case I_HEALTH: return matchmedkit ? medkits<MEDKIT_MAX : health<powerupstats[type-I_HEALTH].max;
|
|
case I_HELMET:
|
|
case I_ARMOUR: return armour<powerupstats[type-I_HEALTH].max;
|
|
case I_AKIMBO: return !akimbo;
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
void additem(itemstat &is, int &v)
|
|
{
|
|
v += is.add;
|
|
if(v > is.max) v = is.max;
|
|
}
|
|
|
|
void pickup(int type)
|
|
{
|
|
switch(type)
|
|
{
|
|
case I_CLIPS:
|
|
additem(ammostats[GUN_PISTOL], ammo[GUN_PISTOL]);
|
|
additem(ammostats[GUN_AKIMBO], ammo[GUN_AKIMBO]);
|
|
break;
|
|
case I_AMMO: additem(ammostats[primary], ammo[primary]); break;
|
|
case I_GRENADE: additem(ammostats[GUN_GRENADE], mag[GUN_GRENADE]); break;
|
|
case I_HEALTH:
|
|
{
|
|
extern int matchmedkit;
|
|
if(matchmedkit) { if(medkits < MEDKIT_MAX) medkits++; }
|
|
else additem(powerupstats[type-I_HEALTH], health);
|
|
break;
|
|
}
|
|
case I_HELMET:
|
|
case I_ARMOUR:
|
|
additem(powerupstats[type-I_HEALTH], armour); break;
|
|
case I_AKIMBO:
|
|
akimbo = true;
|
|
mag[GUN_AKIMBO] = guns[GUN_AKIMBO].magsize;
|
|
additem(ammostats[GUN_AKIMBO], ammo[GUN_AKIMBO]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void respawn()
|
|
{
|
|
health = 100;
|
|
armour = 0;
|
|
gunselect = GUN_PISTOL;
|
|
akimbo = false;
|
|
medkits = 0;
|
|
loopi(NUMGUNS) ammo[i] = mag[i] = gunwait[i] = 0;
|
|
ammo[GUN_KNIFE] = mag[GUN_KNIFE] = 1;
|
|
}
|
|
|
|
// match settings (defined in server.cpp): -1 = not set, use the normal behaviour
|
|
#define matchgunok(g) (valid_weapon(g) && (g) != GUN_AKIMBO) // akimbo is a timed pickup, it can't be a spawn weapon
|
|
|
|
// gives a gun with its normal starting load (used for the match spawn loadout)
|
|
void givegun(int gun)
|
|
{
|
|
if(!matchgunok(gun)) return;
|
|
switch(gun)
|
|
{
|
|
case GUN_KNIFE: ammo[GUN_KNIFE] = mag[GUN_KNIFE] = 1; break;
|
|
case GUN_GRENADE: ammo[GUN_GRENADE] = 0; mag[GUN_GRENADE] = max(mag[GUN_GRENADE], 2); break;
|
|
default:
|
|
ammo[gun] = max(ammo[gun], ammostats[gun].start-magsize(gun));
|
|
mag[gun] = magsize(gun);
|
|
break;
|
|
}
|
|
}
|
|
|
|
virtual void spawnstate(int gamemode)
|
|
{
|
|
extern int matchspawnprimary, matchspawnsecondary, matchspawntertiary, matchspawnquaternary;
|
|
const bool fixedloadout = m_pistol || m_osok || m_lss; // these modes dictate the weapons
|
|
const bool matchprimary = !fixedloadout && matchgunok(matchspawnprimary);
|
|
|
|
if(m_pistol) primary = GUN_PISTOL;
|
|
else if(m_osok) primary = GUN_SNIPER;
|
|
else if(m_lss) primary = GUN_KNIFE;
|
|
else if(matchprimary) primary = matchspawnprimary;
|
|
else primary = nextprimary;
|
|
|
|
if(!m_nopistol)
|
|
{
|
|
ammo[GUN_PISTOL] = ammostats[GUN_PISTOL].start-magsize(GUN_PISTOL);//ammostats[GUN_PISTOL].max-magsize(GUN_PISTOL);
|
|
mag[GUN_PISTOL] = magsize(GUN_PISTOL);
|
|
}
|
|
|
|
if(!m_noprimary)
|
|
{
|
|
if(matchprimary) givegun(primary);
|
|
else
|
|
{
|
|
ammo[primary] = ammostats[primary].start-magsize(primary);
|
|
mag[primary] = magsize(primary);
|
|
}
|
|
}
|
|
|
|
if(!fixedloadout) // extra guns from the match settings: they can be switched to like the pistol/knife
|
|
{
|
|
givegun(matchspawnsecondary);
|
|
givegun(matchspawntertiary);
|
|
givegun(matchspawnquaternary);
|
|
}
|
|
|
|
gunselect = primary;
|
|
|
|
if(m_osok) health = 1;
|
|
if(m_lms) // Survivor && Team-Survivor : 2010nov19
|
|
{
|
|
health = 100;
|
|
armour = 100;
|
|
ammo[GUN_GRENADE] = 2;
|
|
}
|
|
}
|
|
|
|
// just subtract damage here, can set death, etc. later in code calling this
|
|
int dodamage(int damage, int gun)
|
|
{
|
|
guninfo gi = guns[gun];
|
|
if(damage == INT_MAX)
|
|
{
|
|
damage = health;
|
|
armour = health = 0;
|
|
return damage;
|
|
}
|
|
|
|
// 4-level armour - tiered approach: 16%, 33%, 37%, 41%
|
|
// Please update ./ac_website/htdocs/docs/introduction.html if this changes.
|
|
int armoursection = 0;
|
|
int ad = damage;
|
|
if(armour > 25) armoursection = 1;
|
|
if(armour > 50) armoursection = 2;
|
|
if(armour > 75) armoursection = 3;
|
|
switch(armoursection)
|
|
{
|
|
case 0: ad = (int) (16.0f/25.0f * armour); break; // 16
|
|
case 1: ad = (int) (17.0f/25.0f * armour) - 1; break; // 33
|
|
case 2: ad = (int) (4.0f/25.0f * armour) + 25; break; // 37
|
|
case 3: ad = (int) (4.0f/25.0f * armour) + 25; break; // 41
|
|
default: break;
|
|
}
|
|
|
|
//ra - reduced armor
|
|
//rd - reduced damage
|
|
int ra = (int) (ad * damage/100.0f);
|
|
int rd = ra-(ra*(gi.piercing/100.0f)); //Who cares about rounding errors anyways?
|
|
|
|
armour -= ra;
|
|
damage -= rd;
|
|
|
|
health -= damage;
|
|
return damage;
|
|
}
|
|
};
|
|
|
|
#ifndef STANDALONE
|
|
#define HEADSIZE 0.4f
|
|
|
|
#define ROLLMOVMAX 20
|
|
#define ROLLMOVDEF 0
|
|
#define ROLLEFFMAX 30
|
|
#define ROLLEFFDEF 0
|
|
|
|
class playerent : public dynent, public playerstate
|
|
{
|
|
private:
|
|
int curskin, nextskin[2];
|
|
public:
|
|
int clientnum, lastupdate, plag, ping;
|
|
enet_uint32 address;
|
|
int lifesequence; // sequence id for each respawn, used in damage test
|
|
int frags, flagscore, deaths, tks, assists;
|
|
int lastaction, lastmove, lastpain, lastvoicecom, lastdeath;
|
|
int clientrole;
|
|
bool attacking;
|
|
string name;
|
|
int team;
|
|
int weaponchanging;
|
|
int nextweapon; // weapon we switch to
|
|
int spectatemode, followplayercn;
|
|
int eardamagemillis;
|
|
float maxroll, maxrolleffect, movroll, effroll; // roll added by movement and damage
|
|
int ffov, scopefov;
|
|
bool allowmove() { return (state!=CS_DEAD && state!=CS_SPECTATE) || spectatemode==SM_FLY; }
|
|
|
|
weapon *weapons[NUMGUNS];
|
|
weapon *prevweaponsel, *weaponsel, *nextweaponsel, *primweap, *nextprimweap, *lastattackweapon;
|
|
|
|
poshist history; // Previous stored locations of this player
|
|
|
|
const char *skin_noteam, *skin_cla, *skin_rvsf;
|
|
|
|
float deltayaw, deltapitch, newyaw, newpitch;
|
|
int smoothmillis;
|
|
|
|
vec head;
|
|
|
|
bool ignored, muted;
|
|
bool nocorpse;
|
|
|
|
// Client-side damage tracking for assists (singleplayer/bot mode)
|
|
struct damageinfo
|
|
{
|
|
int attacker;
|
|
int damage;
|
|
damageinfo() : attacker(-1), damage(0) {}
|
|
};
|
|
vector<damageinfo> damagelog;
|
|
|
|
playerent() : curskin(0), clientnum(-1), lastupdate(0), plag(0), ping(0), address(0), lifesequence(0), frags(0), flagscore(0), deaths(0), tks(0), assists(0), lastpain(0), lastvoicecom(0), lastdeath(0), clientrole(CR_DEFAULT),
|
|
team(TEAM_SPECT), spectatemode(SM_NONE), followplayercn(FPCN_VOID), eardamagemillis(0), maxroll(ROLLMOVDEF), maxrolleffect(ROLLEFFDEF), movroll(0), effroll(0), ffov(0), scopefov(0),
|
|
prevweaponsel(NULL), weaponsel(NULL), nextweaponsel(NULL), primweap(NULL), nextprimweap(NULL), lastattackweapon(NULL),
|
|
smoothmillis(-1),
|
|
head(-1, -1, -1), ignored(false), muted(false), nocorpse(false)
|
|
{
|
|
type = ENT_PLAYER;
|
|
name[0] = 0;
|
|
maxeyeheight = 4.5f;
|
|
aboveeye = 0.7f;
|
|
radius = 1.1f;
|
|
maxspeed = 16.0f;
|
|
skin_noteam = skin_cla = skin_rvsf = NULL;
|
|
loopi(2) nextskin[i] = 0;
|
|
respawn();
|
|
}
|
|
|
|
virtual ~playerent()
|
|
{
|
|
extern void removebounceents(playerent *owner);
|
|
extern void removedynlights(physent *owner);
|
|
extern void zapplayerflags(playerent *owner);
|
|
extern void cleanplayervotes(playerent *owner);
|
|
extern physent *camera1;
|
|
extern void spectatemode(int mode);
|
|
removebounceents(this);
|
|
audiomgr.detachsounds(this);
|
|
removedynlights(this);
|
|
zapplayerflags(this);
|
|
cleanplayervotes(this);
|
|
if(this==camera1) spectatemode(SM_FOLLOW1ST);
|
|
}
|
|
|
|
void damageroll(float damage)
|
|
{
|
|
damage *= effroll > 0.001f || (effroll > -0.001f && rnd(2)) ? 2.0f : -2.0f; // give player a kick
|
|
effroll = clamp(effroll + damage, -maxrolleffect, maxrolleffect);
|
|
}
|
|
|
|
void hitpush(int damage, const vec &dir, playerent *actor, int gun)
|
|
{
|
|
if(!valid_weapon(gun)) return;
|
|
vec push(dir);
|
|
push.mul(damage/100.0f*guns[gun].pushfactor);
|
|
vel.add(push);
|
|
extern int lastmillis;
|
|
if(gun==GUN_GRENADE && damage > 50) eardamagemillis = lastmillis+damage*100;
|
|
}
|
|
|
|
void resetspec()
|
|
{
|
|
spectatemode = SM_NONE;
|
|
followplayercn = FPCN_VOID;
|
|
}
|
|
|
|
void respawn()
|
|
{
|
|
dynent::reset();
|
|
playerstate::respawn();
|
|
history.reset();
|
|
if(weaponsel) weaponsel->reset();
|
|
lastaction = 0;
|
|
lastattackweapon = NULL;
|
|
attacking = false;
|
|
extern int lastmillis;
|
|
weaponchanging = lastmillis - weapons[gunselect]->weaponchangetime/2; // 2011jan16:ft: for a little no-shoot after spawn
|
|
resetspec();
|
|
eardamagemillis = 0;
|
|
eyeheight = maxeyeheight;
|
|
curskin = nextskin[team_base(team)];
|
|
nocorpse = false;
|
|
assists = 0;
|
|
damagelog.setsize(0);
|
|
extern void *damageindicatorplayer;
|
|
if(damageindicatorplayer == this)
|
|
{
|
|
extern int damagedirections[8];
|
|
loopi(8) damagedirections[i] = lastmillis;
|
|
}
|
|
}
|
|
|
|
void spawnstate(int gamemode)
|
|
{
|
|
playerstate::spawnstate(gamemode);
|
|
prevweaponsel = weaponsel = weapons[gunselect];
|
|
primweap = weapons[primary];
|
|
nextprimweap = weapons[nextprimary];
|
|
curskin = nextskin[team_base(team)];
|
|
}
|
|
|
|
void selectweapon(int w, bool other = false)
|
|
{
|
|
if(!valid_weapon(w)) return;
|
|
if(weaponsel) prevweaponsel = weaponsel;
|
|
weaponsel = weapons[(gunselect = w)];
|
|
if(!prevweaponsel) prevweaponsel = weaponsel;
|
|
if(other && (prevweaponsel != weaponsel)) weaponswitch(weaponsel, true);
|
|
}
|
|
|
|
void setprimary(int w) { if(!valid_weapon(w)) return; primweap = weapons[(primary = w)]; }
|
|
void setnextprimary(int w) { nextprimweap = weapons[(nextprimary = w)]; }
|
|
bool isspectating() { return state==CS_SPECTATE || (state==CS_DEAD && spectatemode > SM_NONE); }
|
|
void weaponswitch(weapon *w, bool sound = true)
|
|
{
|
|
if(!w) return;
|
|
extern int lastmillis;
|
|
extern int matchreloadcancel;
|
|
// cancelling a reload by switching: drop the pending reload, new weapon gets a 0.2s ready delay
|
|
if(weaponsel && weaponsel->reloading && matchreloadcancel)
|
|
{
|
|
weaponsel->reloading = 0;
|
|
weaponsel->gunwait = 0;
|
|
// the 0.2s ready delay only applies to reloadable guns, so it can never sit in the way of
|
|
// grenade cooking (knife/grenade shots are not affected by reloading) - see grenades::attack()
|
|
if(reloadable_gun(w->type)) w->gunwait = max(w->gunwait, 200);
|
|
}
|
|
weaponsel->ondeselecting();
|
|
weaponchanging = lastmillis;
|
|
nextweaponsel = w;
|
|
w->onselecting(sound);
|
|
}
|
|
int skin(int t = -1) { return nextskin[team_base(t < 0 ? team : t)]; }
|
|
void setskin(int t, int s)
|
|
{
|
|
const int maxskin[2] = { 4, 6 };
|
|
t = team_base(t < 0 ? team : t);
|
|
nextskin[t] = (s & 63) % maxskin[t];
|
|
}
|
|
void startmap()
|
|
{
|
|
frags = flagscore = deaths = lifesequence = tks = 0;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
class CBot;
|
|
|
|
class botent : public playerent
|
|
{
|
|
public:
|
|
// Added by Rick
|
|
CBot *pBot; // Only used if this is a bot, points to the bot class if we are the host,
|
|
// for other clients its NULL
|
|
// End add by Rick
|
|
|
|
playerent *enemy; // monster wants to kill this entity
|
|
// Added by Rick: targetpitch
|
|
float targetpitch; // monster wants to look in this direction
|
|
// End add
|
|
float targetyaw; // monster wants to look in this direction
|
|
|
|
botent() : pBot(NULL), enemy(NULL) { type = ENT_BOT; }
|
|
~botent() { }
|
|
|
|
int deaths() { return lifesequence; }
|
|
};
|
|
#endif //#ifndef STANDALONE
|
|
|
|
// flag-mode entities
|
|
|
|
enum { CTFF_INBASE = 0, CTFF_STOLEN, CTFF_DROPPED, CTFF_IDLE };
|
|
|
|
struct flaginfo
|
|
{
|
|
int team;
|
|
entity *flagent;
|
|
int actor_cn;
|
|
playerent *actor;
|
|
vec pos;
|
|
int state; // one of CTFF_*
|
|
bool ack;
|
|
flaginfo() : flagent(0), actor(0), state(CTFF_INBASE), ack(false) {}
|
|
};
|
|
|
|
// nades, gibs
|
|
|
|
enum { BT_NONE, BT_NADE, BT_GIB };
|
|
|
|
class bounceent : public physent
|
|
{
|
|
public:
|
|
int millis, timetolive, bouncetype; // see enum above
|
|
float rotspeed;
|
|
bool plclipped;
|
|
playerent *owner;
|
|
|
|
bounceent() : bouncetype(BT_NONE), rotspeed(1.0f), plclipped(false), owner(NULL)
|
|
{
|
|
type = ENT_BOUNCE;
|
|
maxspeed = 40;
|
|
radius = 0.2f;
|
|
eyeheight = maxeyeheight = 0.3f;
|
|
aboveeye = 0.0f;
|
|
}
|
|
|
|
virtual ~bounceent() {}
|
|
|
|
bool isalive(int lastmillis) { return lastmillis - millis < timetolive; }
|
|
virtual void destroy() {}
|
|
virtual bool applyphysics() { return true; }
|
|
};
|
|
|
|
struct hitmsg
|
|
{
|
|
int target, lifesequence, info;
|
|
ivec dir;
|
|
};
|
|
|
|
class grenadeent : public bounceent
|
|
{
|
|
public:
|
|
bool local;
|
|
int nadestate;
|
|
float distsincebounce;
|
|
grenadeent(playerent *owner, int millis = 0);
|
|
~grenadeent();
|
|
void activate(const vec &from, const vec &to);
|
|
void _throw(const vec &from, const vec &vel);
|
|
void explode();
|
|
void splash();
|
|
virtual void destroy();
|
|
virtual bool applyphysics();
|
|
void moveoutsidebbox(const vec &direction, playerent *boundingbox);
|
|
void oncollision();
|
|
void onmoved(const vec &dist);
|
|
};
|
|
|