You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

499 lines
14 KiB

/**
* @file player.h
*
* Interface of player functionality, leveling, actions, creation, loading, etc.
*/
#pragma once
#include <stdint.h>
#include "diablo.h"
#include "engine.h"
#include "gendung.h"
#include "items.h"
#include "multi.h"
#include "spelldat.h"
#include "path.h"
#include "interfac.h"
#include "utils/enum_traits.h"
namespace devilution {
// number of inventory grid cells
#define NUM_INV_GRID_ELEM 40
#define MAXBELTITEMS 8
#define MAXRESIST 75
#define MAXCHARLEVEL 51
#define MAX_SPELL_LEVEL 15
#define PLR_NAME_LEN 32
/** Walking directions */
enum {
// clang-format off
WALK_NE = 1,
WALK_NW = 2,
WALK_SE = 3,
WALK_SW = 4,
WALK_N = 5,
WALK_E = 6,
WALK_S = 7,
WALK_W = 8,
WALK_NONE = -1,
// clang-format on
};
enum class HeroClass : uint8_t {
Warrior,
Rogue,
Sorcerer,
Monk,
Bard,
Barbarian,
LAST = Barbarian
};
enum class CharacterAttribute : uint8_t {
Strength,
Magic,
Dexterity,
Vitality,
FIRST = Strength,
LAST = Vitality
};
// Logical equipment locations
enum inv_body_loc : uint8_t {
INVLOC_HEAD,
INVLOC_RING_LEFT,
INVLOC_RING_RIGHT,
INVLOC_AMULET,
INVLOC_HAND_LEFT,
INVLOC_HAND_RIGHT,
INVLOC_CHEST,
NUM_INVLOC,
};
enum player_graphic : uint16_t {
// clang-format off
PFILE_STAND = 1 << 0,
PFILE_WALK = 1 << 1,
PFILE_ATTACK = 1 << 2,
PFILE_HIT = 1 << 3,
PFILE_LIGHTNING = 1 << 4,
PFILE_FIRE = 1 << 5,
PFILE_MAGIC = 1 << 6,
PFILE_DEATH = 1 << 7,
PFILE_BLOCK = 1 << 8,
// everything except PFILE_DEATH
// 0b1_0111_1111
PFILE_NONDEATH = 0x17F
// clang-format on
};
enum anim_weapon_id : uint8_t {
ANIM_ID_UNARMED,
ANIM_ID_UNARMED_SHIELD,
ANIM_ID_SWORD,
ANIM_ID_SWORD_SHIELD,
ANIM_ID_BOW,
ANIM_ID_AXE,
ANIM_ID_MACE,
ANIM_ID_MACE_SHIELD,
ANIM_ID_STAFF,
};
enum PLR_MODE : uint8_t {
PM_STAND,
PM_WALK, //Movement towards N, NW, or NE
PM_WALK2, //Movement towards S, SW, or SE
PM_WALK3, //Movement towards W or E
PM_ATTACK,
PM_RATTACK,
PM_BLOCK,
PM_GOTHIT,
PM_DEATH,
PM_SPELL,
PM_NEWLVL,
PM_QUIT,
};
enum action_id : int8_t {
// clang-format off
ACTION_WALK = -2, // Automatic walk when using gamepad
ACTION_NONE = -1,
ACTION_ATTACK = 9,
ACTION_RATTACK = 10,
ACTION_SPELL = 12,
ACTION_OPERATE = 13,
ACTION_DISARM = 14,
ACTION_PICKUPITEM = 15, // put item in hand (inventory screen open)
ACTION_PICKUPAITEM = 16, // put item in inventory
ACTION_TALK = 17,
ACTION_OPERATETK = 18, // operate via telekinesis
ACTION_ATTACKMON = 20,
ACTION_ATTACKPLR = 21,
ACTION_RATTACKMON = 22,
ACTION_RATTACKPLR = 23,
ACTION_SPELLMON = 24,
ACTION_SPELLPLR = 25,
ACTION_SPELLWALL = 26,
// clang-format on
};
enum player_weapon_type : uint8_t {
WT_MELEE,
WT_RANGED,
};
struct PlayerStruct {
PLR_MODE _pmode;
int8_t walkpath[MAX_PATH_LENGTH];
bool plractive;
action_id destAction;
int destParam1;
int destParam2;
direction destParam3;
int destParam4;
int plrlevel;
ActorPosition position;
direction _pdir; // Direction faced by player (direction enum)
int _pgfxnum; // Bitmask indicating what variant of the sprite the player is using. Lower byte define weapon (anim_weapon_id) and higher values define armour (starting with anim_armor_id)
uint8_t *_pAnimData;
int _pAnimDelay; // Tick length of each frame in the current animation
int _pAnimCnt; // Increases by one each game tick, counting how close we are to _pAnimDelay
int _pAnimLen; // Number of frames in current animation
int _pAnimFrame; // Current frame of animation.
int _pAnimWidth;
int _pAnimWidth2;
/*
* @brief Specifies how many animations-fractions are displayed between two gameticks. this can be > 0, if animations are skipped or < 0 if the same animation is shown in multiple times (delay specified).
*/
float _pAnimGameTickModifier;
/*
* @brief Number of GameTicks after the current animation sequence started
*/
int _pAnimGameTicksSinceSequenceStarted;
/*
* @brief Animation Frames that will be adjusted for the skipped Frames/GameTicks
*/
int _pAnimRelevantAnimationFramesForDistributing;
int _plid;
int _pvid;
spell_id _pSpell;
spell_type _pSplType;
int8_t _pSplFrom; // TODO Create enum
spell_id _pTSpell;
spell_type _pTSplType;
spell_id _pRSpell;
spell_type _pRSplType;
spell_id _pSBkSpell;
spell_type _pSBkSplType;
int8_t _pSplLvl[64];
uint64_t _pMemSpells; // Bitmask of learned spells
uint64_t _pAblSpells; // Bitmask of abilities
uint64_t _pScrlSpells; // Bitmask of spells available via scrolls
uint8_t _pSpellFlags;
spell_id _pSplHotKey[4];
spell_type _pSplTHotKey[4];
player_weapon_type _pwtype;
bool _pBlockFlag;
bool _pInvincible;
int8_t _pLightRad;
bool _pLvlChanging; // True when the player is transitioning between levels
char _pName[PLR_NAME_LEN];
HeroClass _pClass;
int _pStrength;
int _pBaseStr;
int _pMagic;
int _pBaseMag;
int _pDexterity;
int _pBaseDex;
int _pVitality;
int _pBaseVit;
int _pStatPts;
int _pDamageMod;
int _pBaseToBlk;
int _pHPBase;
int _pMaxHPBase;
int _pHitPoints;
int _pMaxHP;
int _pHPPer;
int _pManaBase;
int _pMaxManaBase;
int _pMana;
int _pMaxMana;
int _pManaPer;
int8_t _pLevel;
int8_t _pMaxLvl;
int _pExperience;
int _pMaxExp;
int _pNextExper;
int8_t _pArmorClass;
int8_t _pMagResist;
int8_t _pFireResist;
int8_t _pLghtResist;
int _pGold;
bool _pInfraFlag;
/** Player's direction when ending movement. Also used for casting direction of SPL_FIREWALL. */
direction tempDirection;
/** Used for spell level, and X component of _pVar5 */
int _pVar4;
/** Used for storing position of a tile which should have its BFLAG_PLAYERLR flag removed after walking. When starting to walk the game places the player in the dPlayer array -1 in the Y coordinate, and uses BFLAG_PLAYERLR to check if it should be using -1 to the Y coordinate when rendering the player (also used for storing the level of a spell when the player casts it) */
int _pVar5;
/** Used for counting how close we are to reaching the next tile when walking (usually counts to 8, which is equal to the walk animation length). */
int actionFrame;
/** Used for stalling the appearance of the options screen after dying in singleplayer */
int deathFrame;
bool _pLvlVisited[NUMLEVELS];
bool _pSLvlVisited[NUMLEVELS]; // only 10 used
/** Using player_graphic as bitflags */
int _pGFXLoad;
uint8_t *_pNAnim[8]; // Stand animations
int _pNFrames;
int _pNWidth;
uint8_t *_pWAnim[8]; // Walk animations
int _pWFrames;
int _pWWidth;
uint8_t *_pAAnim[8]; // Attack animations
int _pAFrames;
int _pAWidth;
int _pAFNum;
uint8_t *_pLAnim[8]; // Lightning spell cast animations
uint8_t *_pFAnim[8]; // Fire spell cast animations
uint8_t *_pTAnim[8]; // Generic spell cast animations
int _pSFrames;
int _pSWidth;
int _pSFNum;
uint8_t *_pHAnim[8]; // Getting hit animations
int _pHFrames;
int _pHWidth;
uint8_t *_pDAnim[8]; // Death animations
int _pDFrames;
int _pDWidth;
uint8_t *_pBAnim[8]; // Block animations
int _pBFrames;
int _pBWidth;
ItemStruct InvBody[NUM_INVLOC];
ItemStruct InvList[NUM_INV_GRID_ELEM];
int _pNumInv;
int8_t InvGrid[NUM_INV_GRID_ELEM];
ItemStruct SpdList[MAXBELTITEMS];
ItemStruct HoldItem;
int _pIMinDam;
int _pIMaxDam;
int _pIAC;
int _pIBonusDam;
int _pIBonusToHit;
int _pIBonusAC;
int _pIBonusDamMod;
/** Bitmask of staff spell */
uint64_t _pISpells;
/** Bitmask using item_special_effect */
int _pIFlags;
int _pIGetHit;
int8_t _pISplLvlAdd;
int _pISplDur;
int _pIEnAc;
int _pIFMinDam;
int _pIFMaxDam;
int _pILMinDam;
int _pILMaxDam;
item_misc_id _pOilType;
uint8_t pTownWarps;
uint8_t pDungMsgs;
uint8_t pLvlLoad;
bool pBattleNet;
bool pManaShield;
uint8_t pDungMsgs2;
bool pOriginalCathedral;
uint16_t wReflections;
uint8_t pDiabloKillLevel;
_difficulty pDifficulty;
uint32_t pDamAcFlags;
uint8_t *_pNData;
uint8_t *_pWData;
uint8_t *_pAData;
uint8_t *_pLData;
uint8_t *_pFData;
uint8_t *_pTData;
uint8_t *_pHData;
uint8_t *_pDData;
uint8_t *_pBData;
/**
* @brief Gets the most valuable item out of all the player's items that match the given predicate.
* @param itemPredicate The predicate used to match the items.
* @return The most valuable item out of all the player's items that match the given predicate, or 'nullptr' in case no
* matching items were found.
*/
template <typename TPredicate>
const ItemStruct *GetMostValuableItem(const TPredicate &itemPredicate) const
{
const auto getMostValuableItem = [&itemPredicate](const ItemStruct *begin, const ItemStruct *end, const ItemStruct *mostValuableItem = nullptr) {
for (const auto *item = begin; item < end; item++) {
if (item->isEmpty() || !itemPredicate(*item)) {
continue;
}
if (mostValuableItem == nullptr || item->_iIvalue > mostValuableItem->_iIvalue) {
mostValuableItem = item;
}
}
return mostValuableItem;
};
const ItemStruct *mostValuableItem = getMostValuableItem(SpdList, SpdList + MAXBELTITEMS);
mostValuableItem = getMostValuableItem(InvBody, InvBody + inv_body_loc::NUM_INVLOC, mostValuableItem);
mostValuableItem = getMostValuableItem(InvList, InvList + _pNumInv, mostValuableItem);
return mostValuableItem;
}
/**
* @brief Gets the base value of the player's specified attribute.
* @param attribute The attribute to retrieve the base value for
* @return The base value for the requested attribute.
*/
int GetBaseAttributeValue(CharacterAttribute attribute) const;
/**
* @brief Gets the maximum value of the player's specified attribute.
* @param attribute The attribute to retrieve the maximum value for
* @return The maximum value for the requested attribute.
*/
int GetMaximumAttributeValue(CharacterAttribute attribute) const;
/**
* @brief Get the tile coordinates a player is moving to (if not moving, then it corresponds to current position).
*/
Point GetTargetPosition() const;
/**
* @brief Play a speach file.
* @todo Create enum for speachId
* @todo BUGFIX Prevent more then one speach to be played at a time (reject new requests).
*/
void PlaySpeach(int speachId) const;
/**
* @brief Play a speach file after a given delay.
* @param delay Multiple of 20ms waith before starting the speach
*/
void PlaySpeach(int speachId, int delay) const;
/**
* @brief Play a player speach file, with out random variants.
*/
void PlaySpecificSpeach(int speachId) const;
};
extern int myplr;
extern PlayerStruct plr[MAX_PLRS];
extern bool deathflag;
extern int ToBlkTbl[enum_size<HeroClass>::value];
void LoadPlrGFX(int pnum, player_graphic gfxflag);
void InitPlayerGFX(int pnum);
void InitPlrGFXMem(int pnum);
void FreePlayerGFX(int pnum);
/**
* @brief Specifies what special logics are applied for a Animation
*/
enum class AnimationDistributionParams : uint8_t {
None = 0,
/*
* @brief ProcessAnimation will be called after NewPlrAnim (in same GameTick as NewPlrAnim)
*/
ProcessAnimationPending,
/*
* @brief Delay of last Frame is ignored (for example, because only Frame and not delay is checked in game_logic)
*/
SkipsDelayOfLastFrame,
};
/**
* @brief Sets the new Player Animation with all relevant information for rendering
* @param pnum Player Id
* @param Peq Pointer to Animation Data
* @param numFrames Number of Frames in Animation
* @param Delay Delay after each Animation sequence
* @param width Width of sprite
* @param params Specifies what special logics are applied to this Animation
* @param numSkippedFrames Number of Frames that will be skipped (for example with modifier "faster attack")
* @param distributeFramesBeforeFrame Distribute the numSkippedFrames only before this frame
*/
void NewPlrAnim(int pnum, BYTE *Peq, int numFrames, int Delay, int width, AnimationDistributionParams params = AnimationDistributionParams::None, int numSkippedFrames = 0, int distributeFramesBeforeFrame = 0);
void SetPlrAnims(int pnum);
void ProcessPlayerAnimation(int pnum);
/**
* @brief Calculates the Frame to use for the Animation rendering
* @param pPlayer Player
* @return The Frame to use for rendering
*/
int GetFrameToUseForPlayerRendering(const PlayerStruct *pPlayer);
void CreatePlayer(int pnum, HeroClass c);
int CalcStatDiff(int pnum);
#ifdef _DEBUG
void NextPlrLevel(int pnum);
#endif
void AddPlrExperience(int pnum, int lvl, int exp);
void AddPlrMonstExper(int lvl, int exp, char pmask);
void ApplyPlrDamage(int pnum, int dam, int minHP = 0, int frac = 0, int earflag = 0);
void InitPlayer(int pnum, bool FirstTime);
void InitMultiView();
bool SolidLoc(int x, int y);
void PlrClrTrans(int x, int y);
void PlrDoTrans(int x, int y);
void SetPlayerOld(int pnum);
void FixPlayerLocation(int pnum, direction bDir);
void StartStand(int pnum, direction dir);
void StartAttack(int pnum, direction d);
void StartPlrBlock(int pnum, direction dir);
void FixPlrWalkTags(int pnum);
void RemovePlrFromMap(int pnum);
void StartPlrHit(int pnum, int dam, bool forcehit);
void StartPlayerKill(int pnum, int earflag);
void DropHalfPlayersGold(int pnum);
void StripTopGold(int pnum);
void SyncPlrKill(int pnum, int earflag);
void RemovePlrMissiles(int pnum);
void StartNewLvl(int pnum, interface_mode fom, int lvl);
void RestartTownLvl(int pnum);
void StartWarpLvl(int pnum, int pidx);
void ProcessPlayers();
void ClrPlrPath(int pnum);
bool PosOkPlayer(int pnum, int x, int y);
void MakePlrPath(int pnum, int xx, int yy, bool endspace);
void CheckPlrSpell();
void SyncPlrAnim(int pnum);
void SyncInitPlrPos(int pnum);
void SyncInitPlr(int pnum);
void CheckStats(int p);
void ModifyPlrStr(int p, int l);
void ModifyPlrMag(int p, int l);
void ModifyPlrDex(int p, int l);
void ModifyPlrVit(int p, int l);
void SetPlayerHitPoints(int pnum, int val);
void SetPlrStr(int p, int v);
void SetPlrMag(int p, int v);
void SetPlrDex(int p, int v);
void SetPlrVit(int p, int v);
void InitDungMsgs(int pnum);
void PlayDungMsgs();
/* data */
extern int plrxoff[9];
extern int plryoff[9];
extern int plrxoff2[9];
extern int plryoff2[9];
extern int StrengthTbl[enum_size<HeroClass>::value];
extern int MagicTbl[enum_size<HeroClass>::value];
extern int DexterityTbl[enum_size<HeroClass>::value];
extern int VitalityTbl[enum_size<HeroClass>::value];
extern int ExpLvlsTbl[MAXCHARLEVEL];
} // namespace devilution