Files
assaultcube-/source/src/entity.h
T
aurhatanddrian b8b0eb96bd Hide spectator reflection and fix moving in 3rd person perspective
Earlier in third person perspective:
1. reflection of the spectator (player1) was visible on the water (at least
after full cycle of spectate mode switching);
2. it was possible to move position of the invisible spectator's (player1)
ghost, although he should be attached to spectated player. Bug was visible
on the water (the reflection moved) and in some cases on minimap - when
player executed "spectate" and later "spectatemode 1", then camera stayed
in the same position, but player position changed.

Thanks BrutalSystem
2021-02-15 05:29:36 +01:00

657 lines
21 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
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) : 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) : 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
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; };
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 TEAM_VOID TEAM_NUM
#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_VOID)
#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;
}
};
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];
playerstate() : armour(0), primary(GUN_ASSAULT), nextprimary(GUN_ASSAULT), akimbo(false) {}
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)
{
switch(type)
{
case I_CLIPS: return ammo[akimbo ? GUN_AKIMBO : GUN_PISTOL]<ammostats[akimbo ? 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 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: 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;
loopi(NUMGUNS) ammo[i] = mag[i] = gunwait[i] = 0;
ammo[GUN_KNIFE] = mag[GUN_KNIFE] = 1;
}
virtual void spawnstate(int gamemode)
{
if(m_pistol) primary = GUN_PISTOL;
else if(m_osok) primary = GUN_SNIPER;
else if(m_lss) primary = GUN_KNIFE;
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)
{
ammo[primary] = ammostats[primary].start-magsize(primary);
mag[primary] = magsize(primary);
}
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, points, tks;
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;
playerent() : curskin(0), clientnum(-1), lastupdate(0), plag(0), ping(0), address(0), lifesequence(0), frags(0), flagscore(0), deaths(0), points(0), tks(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;
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) { if (weaponsel) prevweaponsel = weaponsel; weaponsel = weapons[(gunselect = w)]; if (!prevweaponsel) prevweaponsel = weaponsel; }
void setprimary(int w) { 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)
{
if(!w) return;
extern int lastmillis;
weaponsel->ondeselecting();
weaponchanging = lastmillis;
nextweaponsel = w;
w->onselecting();
}
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] = iabs(s) % maxskin[t];
}
void startmap()
{
frags = flagscore = deaths = lifesequence = points = 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);
};