From 8f2e94ae4d9f6c2f8febccecfba45948e4c253f2 Mon Sep 17 00:00:00 2001 From: ephphatha Date: Thu, 12 Aug 2021 09:56:56 +1000 Subject: [PATCH] Use unsigned types for player experience and related values --- Source/loadsave.cpp | 12 ++++++------ Source/objects.cpp | 20 +++++++++----------- Source/pack.h | 2 +- Source/player.cpp | 19 ++++++++----------- Source/player.h | 7 +++---- Source/qol/xpbar.cpp | 2 +- 6 files changed, 28 insertions(+), 34 deletions(-) diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index 7bfed91aa..025383e98 100644 --- a/Source/loadsave.cpp +++ b/Source/loadsave.cpp @@ -409,9 +409,9 @@ void LoadPlayer(LoadHelper *file, int p) player._pLevel = file->NextLE(); player._pMaxLvl = file->NextLE(); file->Skip(2); // Alignment - player._pExperience = file->NextLE(); - player._pMaxExp = file->NextLE(); - player._pNextExper = file->NextLE(); + player._pExperience = file->NextLE(); + file->Skip(); // Skip _pMaxExp - unused + player._pNextExper = file->NextLE(); // This can be calculated based on pLevel (which in turn could be calculated based on pExperience) player._pArmorClass = file->NextLE(); player._pMagResist = file->NextLE(); player._pFireResist = file->NextLE(); @@ -1081,9 +1081,9 @@ void SavePlayer(SaveHelper *file, int p) file->WriteLE(player._pLevel); file->WriteLE(player._pMaxLvl); file->Skip(2); // Alignment - file->WriteLE(player._pExperience); - file->WriteLE(player._pMaxExp); - file->WriteLE(player._pNextExper); + file->WriteLE(player._pExperience); + file->Skip(); // Skip _pMaxExp + file->WriteLE(player._pNextExper); file->WriteLE(player._pArmorClass); file->WriteLE(player._pMagResist); file->WriteLE(player._pFireResist); diff --git a/Source/objects.cpp b/Source/objects.cpp index d689bb880..34813d6f3 100644 --- a/Source/objects.cpp +++ b/Source/objects.cpp @@ -3408,19 +3408,17 @@ bool OperateShrineGlowing(int pnum) auto &myPlayer = Players[MyPlayerId]; - int playerXP = myPlayer._pExperience; - int magicGain = playerXP / 1000; - int xpLoss = 0; - if (playerXP > 5000) { - magicGain = 5; - xpLoss = static_cast(playerXP * 0.95); - } - ModifyPlrMag(MyPlayerId, magicGain); - myPlayer._pExperience = xpLoss; + // Add 0-5 points to Magic (0.1% of the players XP) + ModifyPlrMag(MyPlayerId, std::min(myPlayer._pExperience / 1000, 5U)); + + // Take 5% of the players experience to offset the bonus, unless they're very low level in which case take all their experience. + if (myPlayer._pExperience > 5000) + myPlayer._pExperience = static_cast(myPlayer._pExperience * 0.95); + else + myPlayer._pExperience = 0; - if (sgOptions.Gameplay.bExperienceBar) { + if (sgOptions.Gameplay.bExperienceBar) force_redraw = 255; - } CheckStats(Players[pnum]); diff --git a/Source/pack.h b/Source/pack.h index 7319e0da8..7bb5ca574 100644 --- a/Source/pack.h +++ b/Source/pack.h @@ -46,7 +46,7 @@ struct PkPlayerStruct { uint8_t pBaseVit; int8_t pLevel; uint8_t pStatPts; - int32_t pExperience; + uint32_t pExperience; int32_t pGold; int32_t pHPBase; int32_t pMaxHPBase; diff --git a/Source/player.cpp b/Source/player.cpp index 2376b6010..defc44539 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -92,7 +92,7 @@ int BlockBonuses[enum_size::value] = { }; /** Specifies the experience point limit of each level. */ -int ExpLvlsTbl[MAXCHARLEVEL] = { +uint32_t ExpLvlsTbl[MAXCHARLEVEL] = { 0, 2000, 4620, @@ -2477,7 +2477,6 @@ void CreatePlayer(int playerId, HeroClass c) player._pMaxLvl = player._pLevel; player._pExperience = 0; - player._pMaxExp = player._pExperience; player._pNextExper = ExpLvlsTbl[1]; player._pArmorClass = 0; if (player._pClass == HeroClass::Barbarian) { @@ -2647,8 +2646,6 @@ void NextPlrLevel(int pnum) CalcPlrInv(pnum, true); } -#define MAXEXP 2000000000 - void AddPlrExperience(int pnum, int lvl, int exp) { if (pnum != MyPlayerId) { @@ -2665,21 +2662,21 @@ void AddPlrExperience(int pnum, int lvl, int exp) } // Adjust xp based on difference in level between player and monster - exp = std::max(static_cast(exp * (1 + (lvl - player._pLevel) / 10.0)), 0); + uint32_t clampedExp = std::max(static_cast(exp * (1 + (lvl - player._pLevel) / 10.0)), 0); // Prevent power leveling if (gbIsMultiplayer) { - auto clampedPlayerLevel = clamp(static_cast(player._pLevel), 0, 50); + const uint32_t clampedPlayerLevel = clamp(static_cast(player._pLevel), 0, 50); // for low level characters experience gain is capped to 1/20 of current levels xp // for high level characters experience gain is capped to 200 * current level - this is a smaller value than 1/20 of the exp needed for the next level after level 5. - exp = std::min({ exp, /* level 0-5: */ ExpLvlsTbl[clampedPlayerLevel] / 20, /* level 6-50: */ 200 * clampedPlayerLevel }); + clampedExp = std::min({ clampedExp, /* level 0-5: */ ExpLvlsTbl[clampedPlayerLevel] / 20U, /* level 6-50: */ 200U * clampedPlayerLevel }); } - player._pExperience += exp; - if (player._pExperience > MAXEXP || player._pExperience < 0) { - player._pExperience = MAXEXP; - } + constexpr uint32_t MaxExperience = 2000000000U; + + // Overflow is only possible if a kill grants more than (2^32-1 - MaxExperience) XP in one go, which doesn't happen in normal gameplay + player._pExperience = std::min(player._pExperience + clampedExp, MaxExperience); if (sgOptions.Gameplay.bExperienceBar) { force_redraw = 255; diff --git a/Source/player.h b/Source/player.h index 956b10f86..29b0fcc47 100644 --- a/Source/player.h +++ b/Source/player.h @@ -227,9 +227,8 @@ struct PlayerStruct { int _pManaPer; int8_t _pLevel; int8_t _pMaxLvl; - int _pExperience; - int _pMaxExp; - int _pNextExper; + uint32_t _pExperience; + uint32_t _pNextExper; int8_t _pArmorClass; int8_t _pMagResist; int8_t _pFireResist; @@ -603,6 +602,6 @@ extern int StrengthTbl[enum_size::value]; extern int MagicTbl[enum_size::value]; extern int DexterityTbl[enum_size::value]; extern int VitalityTbl[enum_size::value]; -extern int ExpLvlsTbl[MAXCHARLEVEL]; +extern uint32_t ExpLvlsTbl[MAXCHARLEVEL]; } // namespace devilution diff --git a/Source/qol/xpbar.cpp b/Source/qol/xpbar.cpp index f668249b4..7657a4c74 100644 --- a/Source/qol/xpbar.cpp +++ b/Source/qol/xpbar.cpp @@ -89,7 +89,7 @@ void DrawXPBar(const Surface &out) return; } - const int prevXp = ExpLvlsTbl[charLevel - 1]; + const uint64_t prevXp = ExpLvlsTbl[charLevel - 1]; if (player._pExperience < prevXp) return;