#include "player_test.h" #include #include "cursor.h" #include "init.h" #include "playerdat.hpp" using namespace devilution; namespace devilution { extern bool TestPlayerDoGotHit(Player &player); } int RunBlockTest(int frames, ItemSpecialEffect flags) { devilution::Player &player = Players[0]; player._pHFrames = frames; player._pIFlags = flags; // StartPlrHit compares damage (a 6 bit fixed point value) to character level to determine if the player shrugs off the hit. // We don't initialise player so this comparison can't be relied on, instead we use forcehit to ensure the player enters hit mode StartPlrHit(player, 0, true); int i = 1; for (; i < 100; i++) { TestPlayerDoGotHit(player); if (player._pmode != PM_GOTHIT) break; player.AnimInfo.currentFrame++; } return i; } constexpr ItemSpecialEffect Normal = ItemSpecialEffect::None; constexpr ItemSpecialEffect Balance = ItemSpecialEffect::FastHitRecovery; constexpr ItemSpecialEffect Stability = ItemSpecialEffect::FasterHitRecovery; constexpr ItemSpecialEffect Harmony = ItemSpecialEffect::FastestHitRecovery; constexpr ItemSpecialEffect BalanceStability = Balance | Stability; constexpr ItemSpecialEffect BalanceHarmony = Balance | Harmony; constexpr ItemSpecialEffect StabilityHarmony = Stability | Harmony; constexpr int Warrior = 6; constexpr int Rogue = 7; constexpr int Sorcerer = 8; struct BlockTestCase { int expectedRecoveryFrame; int maxRecoveryFrame; ItemSpecialEffect itemFlags; }; BlockTestCase BlockData[] = { { 6, Warrior, Normal }, { 7, Rogue, Normal }, { 8, Sorcerer, Normal }, { 5, Warrior, Balance }, { 6, Rogue, Balance }, { 7, Sorcerer, Balance }, { 4, Warrior, Stability }, { 5, Rogue, Stability }, { 6, Sorcerer, Stability }, { 3, Warrior, Harmony }, { 4, Rogue, Harmony }, { 5, Sorcerer, Harmony }, { 4, Warrior, BalanceStability }, { 5, Rogue, BalanceStability }, { 6, Sorcerer, BalanceStability }, { 3, Warrior, BalanceHarmony }, { 4, Rogue, BalanceHarmony }, { 5, Sorcerer, BalanceHarmony }, { 3, Warrior, StabilityHarmony }, { 4, Rogue, StabilityHarmony }, { 5, Sorcerer, StabilityHarmony }, }; TEST(Player, PM_DoGotHit) { Players.resize(1); MyPlayer = &Players[0]; for (size_t i = 0; i < sizeof(BlockData) / sizeof(*BlockData); i++) { EXPECT_EQ(BlockData[i].expectedRecoveryFrame, RunBlockTest(BlockData[i].maxRecoveryFrame, BlockData[i].itemFlags)); } } static void AssertPlayer(devilution::Player &player) { ASSERT_EQ(CountU8(player._pSplLvl, 64), 0); ASSERT_EQ(Count8(player.InvGrid, InventoryGridCells), 1); ASSERT_EQ(CountItems(player.InvBody, NUM_INVLOC), 1); ASSERT_EQ(CountItems(player.InvList, InventoryGridCells), 1); ASSERT_EQ(CountItems(player.SpdList, MaxBeltItems), 2); ASSERT_EQ(CountItems(&player.HoldItem, 1), 0); ASSERT_EQ(player.position.tile.x, 0); ASSERT_EQ(player.position.tile.y, 0); ASSERT_EQ(player.position.future.x, 0); ASSERT_EQ(player.position.future.y, 0); ASSERT_EQ(player.plrlevel, 0); ASSERT_EQ(player.destAction, 0); ASSERT_STREQ(player._pName, ""); ASSERT_EQ(player._pClass, HeroClass::Rogue); ASSERT_EQ(player._pBaseStr, 20); ASSERT_EQ(player._pStrength, 20); ASSERT_EQ(player._pBaseMag, 15); ASSERT_EQ(player._pMagic, 15); ASSERT_EQ(player._pBaseDex, 30); ASSERT_EQ(player._pDexterity, 30); ASSERT_EQ(player._pBaseVit, 20); ASSERT_EQ(player._pVitality, 20); ASSERT_EQ(player.getCharacterLevel(), 1); ASSERT_EQ(player._pStatPts, 0); ASSERT_EQ(player._pExperience, 0); ASSERT_EQ(player._pGold, 100); ASSERT_EQ(player._pMaxHPBase, 2880); ASSERT_EQ(player._pHPBase, 2880); ASSERT_EQ(player.getBaseToBlock(), 20); ASSERT_EQ(player._pMaxManaBase, 1440); ASSERT_EQ(player._pManaBase, 1440); ASSERT_EQ(player._pMemSpells, 0); ASSERT_EQ(player._pNumInv, 1); ASSERT_EQ(player.wReflections, 0); ASSERT_EQ(player.pTownWarps, 0); ASSERT_EQ(player.pDungMsgs, 0); ASSERT_EQ(player.pDungMsgs2, 0); ASSERT_EQ(player.pLvlLoad, 0); ASSERT_EQ(player.pDiabloKillLevel, 0); ASSERT_EQ(player.pManaShield, 0); ASSERT_EQ(player.pDamAcFlags, ItemSpecialEffectHf::None); ASSERT_EQ(player._pmode, 0); ASSERT_EQ(Count8(player.walkpath, MaxPathLength), 0); ASSERT_EQ(player.queuedSpell.spellId, SpellID::Null); ASSERT_EQ(player.queuedSpell.spellType, SpellType::Skill); ASSERT_EQ(player.queuedSpell.spellFrom, 0); ASSERT_EQ(player.inventorySpell, SpellID::Null); ASSERT_EQ(player._pRSpell, SpellID::TrapDisarm); ASSERT_EQ(player._pRSplType, SpellType::Skill); ASSERT_EQ(player._pSBkSpell, SpellID::Null); ASSERT_EQ(player._pAblSpells, 134217728); ASSERT_EQ(player._pScrlSpells, 0); ASSERT_EQ(player._pSpellFlags, SpellFlag::None); ASSERT_EQ(player._pBlockFlag, 0); ASSERT_EQ(player._pLightRad, 10); ASSERT_EQ(player._pDamageMod, 0); ASSERT_EQ(player._pHitPoints, 2880); ASSERT_EQ(player._pMaxHP, 2880); ASSERT_EQ(player._pMana, 1440); ASSERT_EQ(player._pMaxMana, 1440); ASSERT_EQ(player.getNextExperienceThreshold(), 2000); ASSERT_EQ(player._pMagResist, 0); ASSERT_EQ(player._pFireResist, 0); ASSERT_EQ(player._pLghtResist, 0); ASSERT_EQ(CountBool(player._pLvlVisited, NUMLEVELS), 0); ASSERT_EQ(CountBool(player._pSLvlVisited, NUMLEVELS), 0); // This test case uses a Rogue, starting loadout is a short bow with damage 1-4 ASSERT_EQ(player._pIMinDam, 1); ASSERT_EQ(player._pIMaxDam, 4); ASSERT_EQ(player._pIAC, 0); ASSERT_EQ(player._pIBonusDam, 0); ASSERT_EQ(player._pIBonusToHit, 0); ASSERT_EQ(player._pIBonusAC, 0); ASSERT_EQ(player._pIBonusDamMod, 0); ASSERT_EQ(player._pISpells, 0); ASSERT_EQ(player._pIFlags, ItemSpecialEffect::None); ASSERT_EQ(player._pIGetHit, 0); ASSERT_EQ(player._pISplLvlAdd, 0); ASSERT_EQ(player._pIEnAc, 0); ASSERT_EQ(player._pIFMinDam, 0); ASSERT_EQ(player._pIFMaxDam, 0); ASSERT_EQ(player._pILMinDam, 0); ASSERT_EQ(player._pILMaxDam, 0); } TEST(Player, CreatePlayer) { LoadCoreArchives(); LoadGameArchives(); // The tests need spawn.mpq or diabdat.mpq // Please provide them so that the tests can run successfully ASSERT_TRUE(HaveSpawn() || HaveDiabdat()); LoadPlayerDataFiles(); LoadMonsterData(); LoadItemData(); Players.resize(1); CreatePlayer(Players[0], HeroClass::Rogue); AssertPlayer(Players[0]); }