enum // static entity types { NOTUSED = 0, // entity slot not in use in map LIGHT, // lightsource, attr1 = radius, attr2 = intensity PLAYERSTART, // attr1 = angle, attr2 = team I_CLIPS, I_AMMO, I_GRENADE, I_HEALTH, I_HELMET, I_ARMOUR, I_AKIMBO, // helmet : 2010may16 -> mapversion:8 MAPMODEL, // attr1 = angle, attr2 = idx CARROT, // attr1 = tag, attr2 = type LADDER, CTF_FLAG, // attr1 = angle, attr2 = red/blue SOUND, CLIP, PLCLIP, MAXENTTYPES }; enum {MAP_IS_BAD, MAP_IS_EDITABLE, MAP_IS_GOOD}; extern const char *entnames[MAXENTTYPES]; #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; 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) {} 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_CPISTOL, GUN_GRENADE, GUN_AKIMBO, 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 SGBONUSDIST 80 #define SGSEGDMG_O 3 #define SGSEGDMG_M 6 #define SGSEGDMG_C 4 #define SGSPREAD 2.25 #define EXPDAMRAD 10 struct itemstat { int add, start, max, sound; }; extern itemstat ammostats[NUMGUNS]; extern itemstat powerupstats[I_ARMOUR-I_HEALTH+1]; struct guninfo { string modelname; 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]; 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[TEAM_NUM+1]; extern const char *teamnames_s[TEAM_NUM+1]; #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]; } 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_ADMIN }; enum { SM_NONE = 0, SM_DEATHCAM, SM_FOLLOW1ST, SM_FOLLOW3RD, SM_FOLLOW3RD_TRANSPARENT, SM_FLY, SM_OVERVIEW, SM_NUM }; class worldobject { public: virtual ~worldobject() {}; }; class physent : public worldobject { public: vec o, vel, vel_t; // 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, crouching, crouchedinair, trycrouch, cancollide, stuck, scoping, shoot; 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), shoot(false), lastjump(0), lastjumpheight(0), 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 = vel_t.x = vel_t.y = vel_t.z = 0.0f; move = strafe = 0; timeinair = lastjump = lastsplash = 0; onfloor = onladder = inwater = jumpnext = 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= 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 *(itemstat *)0; } } bool canpickup(int type) { switch(type) { case I_CLIPS: return ammo[akimbo ? GUN_AKIMBO : GUN_PISTOL] 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 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; int clientrole; bool attacking; string name; int team; int weaponchanging; int nextweapon; // weapon we switch to int spectatemode, followplayercn; int eardamagemillis; int respawnoffset; bool allowmove() { return state!=CS_DEAD || 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; 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), clientrole(CR_DEFAULT), team(TEAM_SPECT), spectatemode(SM_NONE), eardamagemillis(0), respawnoffset(0), prevweaponsel(NULL), weaponsel(NULL), nextweaponsel(NULL), primweap(NULL), nextprimweap(NULL), lastattackweapon(NULL), smoothmillis(-1), head(-1, -1, -1), ignored(false), muted(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 togglespect(); removebounceents(this); audiomgr.detachsounds(this); removedynlights(this); zapplayerflags(this); cleanplayervotes(this); if(this==camera1) togglespect(); } void damageroll(float damage) { extern void clamproll(physent *pl); float damroll = 2.0f*damage; roll += roll>0 ? damroll : (roll<0 ? -damroll : (rnd(2) ? damroll : -damroll)); // give player a kick clamproll(this); } void hitpush(int damage, const vec &dir, playerent *actor, int gun) { if(gun<0 || gun>NUMGUNS) 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; } 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)]; } 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] = abs(s) % maxskin[t]; } }; 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); }; enum {MD_FRAGS = 0, MD_DEATHS, END_MDS}; struct medalsst {bool assigned; int cn; int item;}; #define MAXKILLMSGLEN 16 extern char killmessages[2][NUMGUNS][MAXKILLMSGLEN]; inline const char *killmessage(int gun, bool gib = false) { if(gun<0 || gun>=NUMGUNS) return ""; return killmessages[gib?1:0][gun]; } #ifndef STANDALONE struct pckserver { char *addr; bool pending, responsive; int ping; pckserver() : addr(NULL), pending(false), responsive(true), ping(-1) {} }; enum { PCK_TEXTURE, PCK_SKYBOX, PCK_MAPMODEL, PCK_AUDIO, PCK_MAP, PCK_NUM }; struct package { char *name; int type, number; bool pending; pckserver *source; CURL *curl; package() : name(NULL), type(-1), number(0), pending(false), source(NULL), curl(NULL) {} }; #endif