diff --git a/Packaging/resources/assets/txtdata/classes/README.md b/Packaging/resources/assets/txtdata/classes/README.md index 92ab3c2fd99..7d1b9199154 100644 --- a/Packaging/resources/assets/txtdata/classes/README.md +++ b/Packaging/resources/assets/txtdata/classes/README.md @@ -4,22 +4,25 @@ There is one folder per class. ### attributes.tsv - Attribute | Description --------------:|-------------------------------------- - `baseStr` | Class Starting Strength Stat, uint8_t - `baseMag` | Class Starting Magic Stat, uint8_t - `baseDex` | Class Starting Dexterity Stat, uint8_t - `baseVit` | Class Starting Vitality Stat, uint8_t - `maxStr` | Class Maximum Strength Stat, uint8_t - `maxMag` | Class Maximum Magic Stat, uint8_t - `maxDex` | Class Maximum Dexterity Stat, uint8_t - `maxVit` | Class Maximum Vitality Stat, uint8_t - `blockBonus` | Class Block Bonus, % - `adjLife` | Class Life Adjustment, decimal - `adjMana` | Class Mana Adjustment, decimal - `lvlLife` | Life gained on level up, decimal - `lvlMana` | Mana gained on level up, decimal - `chrLife` | Life from base Vitality, decimal - `chrMana` | Mana from base Magic, decimal - `itmLife` | Life from item bonus Vitality, decimal - `itmMana` | Mana from item bonus Magic, decimal + Attribute | Description +------------------:|-------------------------------------- + `baseStr` | Class Starting Strength Stat, uint8_t + `baseMag` | Class Starting Magic Stat, uint8_t + `baseDex` | Class Starting Dexterity Stat, uint8_t + `baseVit` | Class Starting Vitality Stat, uint8_t + `maxStr` | Class Maximum Strength Stat, uint8_t + `maxMag` | Class Maximum Magic Stat, uint8_t + `maxDex` | Class Maximum Dexterity Stat, uint8_t + `maxVit` | Class Maximum Vitality Stat, uint8_t + `blockBonus` | Class Block Bonus, % + `adjLife` | Class Life Adjustment, decimal + `adjMana` | Class Mana Adjustment, decimal + `lvlLife` | Life gained on level up, decimal + `lvlMana` | Mana gained on level up, decimal + `chrLife` | Life from base Vitality, decimal + `chrMana` | Mana from base Magic, decimal + `itmLife` | Life from item bonus Vitality, decimal + `itmMana` | Mana from item bonus Magic, decimal + `baseMagicToHit` | Starting chance to hit with spells/scrolls, % + `baseMeleeToHit` | Starting chance to hit with melee weapons/fists, % + `baseRangedToHit` | Starting chance to hit with ranged weapons, % diff --git a/Packaging/resources/assets/txtdata/classes/barbarian/attributes.tsv b/Packaging/resources/assets/txtdata/classes/barbarian/attributes.tsv index 58d2dff0ca4..2da44432de7 100644 --- a/Packaging/resources/assets/txtdata/classes/barbarian/attributes.tsv +++ b/Packaging/resources/assets/txtdata/classes/barbarian/attributes.tsv @@ -16,3 +16,6 @@ chrLife 2 chrMana 1 itmLife 2.5 itmMana 1 +baseMagicToHit 50 +baseMeleeToHit 50 +baseRangedToHit 50 diff --git a/Packaging/resources/assets/txtdata/classes/bard/attributes.tsv b/Packaging/resources/assets/txtdata/classes/bard/attributes.tsv index 42046db254a..4548b016a57 100644 --- a/Packaging/resources/assets/txtdata/classes/bard/attributes.tsv +++ b/Packaging/resources/assets/txtdata/classes/bard/attributes.tsv @@ -16,3 +16,6 @@ chrLife 1 chrMana 1.5 itmLife 1.5 itmMana 1.75 +baseMagicToHit 60 +baseMeleeToHit 50 +baseRangedToHit 60 diff --git a/Packaging/resources/assets/txtdata/classes/monk/attributes.tsv b/Packaging/resources/assets/txtdata/classes/monk/attributes.tsv index bb6e98d41fe..4d27f9ad851 100644 --- a/Packaging/resources/assets/txtdata/classes/monk/attributes.tsv +++ b/Packaging/resources/assets/txtdata/classes/monk/attributes.tsv @@ -16,3 +16,6 @@ chrLife 1 chrMana 1 itmLife 1.5 itmMana 1.5 +baseMagicToHit 50 +baseMeleeToHit 50 +baseRangedToHit 50 diff --git a/Packaging/resources/assets/txtdata/classes/rogue/attributes.tsv b/Packaging/resources/assets/txtdata/classes/rogue/attributes.tsv index 15a7e2e1101..8731a9a3e7a 100644 --- a/Packaging/resources/assets/txtdata/classes/rogue/attributes.tsv +++ b/Packaging/resources/assets/txtdata/classes/rogue/attributes.tsv @@ -16,3 +16,6 @@ chrLife 1 chrMana 1 itmLife 1.5 itmMana 1.5 +baseMagicToHit 50 +baseMeleeToHit 50 +baseRangedToHit 70 diff --git a/Packaging/resources/assets/txtdata/classes/sorcerer/attributes.tsv b/Packaging/resources/assets/txtdata/classes/sorcerer/attributes.tsv index 188d2d49740..4cc0445e15c 100644 --- a/Packaging/resources/assets/txtdata/classes/sorcerer/attributes.tsv +++ b/Packaging/resources/assets/txtdata/classes/sorcerer/attributes.tsv @@ -16,3 +16,6 @@ chrLife 1 chrMana 2 itmLife 1 itmMana 2 +baseMagicToHit 70 +baseMeleeToHit 50 +baseRangedToHit 50 diff --git a/Packaging/resources/assets/txtdata/classes/warrior/attributes.tsv b/Packaging/resources/assets/txtdata/classes/warrior/attributes.tsv index cf3fec85d04..447298fb652 100644 --- a/Packaging/resources/assets/txtdata/classes/warrior/attributes.tsv +++ b/Packaging/resources/assets/txtdata/classes/warrior/attributes.tsv @@ -16,3 +16,6 @@ chrLife 2 chrMana 1 itmLife 2 itmMana 1 +baseMagicToHit 50 +baseMeleeToHit 70 +baseRangedToHit 60 diff --git a/Source/DiabloUI/diabloui.h b/Source/DiabloUI/diabloui.h index 65a4f927c30..7cbea8d5b84 100644 --- a/Source/DiabloUI/diabloui.h +++ b/Source/DiabloUI/diabloui.h @@ -83,8 +83,8 @@ void UiTitleDialog(); void UnloadUiGFX(); void UiInitialize(); bool UiValidPlayerName(std::string_view name); /* check */ -void UiSelHeroMultDialog(bool (*fninfo)(bool (*fninfofunc)(_uiheroinfo *)), bool (*fncreate)(_uiheroinfo *), bool (*fnremove)(_uiheroinfo *), void (*fnstats)(unsigned int, _uidefaultstats *), _selhero_selections *dlgresult, uint32_t *saveNumber); -void UiSelHeroSingDialog(bool (*fninfo)(bool (*fninfofunc)(_uiheroinfo *)), bool (*fncreate)(_uiheroinfo *), bool (*fnremove)(_uiheroinfo *), void (*fnstats)(unsigned int, _uidefaultstats *), _selhero_selections *dlgresult, uint32_t *saveNumber, _difficulty *difficulty); +void UiSelHeroMultDialog(bool (*fninfo)(bool (*fninfofunc)(_uiheroinfo *)), bool (*fncreate)(_uiheroinfo *), bool (*fnremove)(_uiheroinfo *), void (*fnstats)(HeroClass, _uidefaultstats *), _selhero_selections *dlgresult, uint32_t *saveNumber); +void UiSelHeroSingDialog(bool (*fninfo)(bool (*fninfofunc)(_uiheroinfo *)), bool (*fncreate)(_uiheroinfo *), bool (*fnremove)(_uiheroinfo *), void (*fnstats)(HeroClass, _uidefaultstats *), _selhero_selections *dlgresult, uint32_t *saveNumber, _difficulty *difficulty); bool UiCreditsDialog(); bool UiSupportDialog(); bool UiMainMenuDialog(const char *name, _mainmenu_selections *pdwResult, int attractTimeOut); diff --git a/Source/DiabloUI/hero/selhero.cpp b/Source/DiabloUI/hero/selhero.cpp index e63478602e3..3e667b5fe2a 100644 --- a/Source/DiabloUI/hero/selhero.cpp +++ b/Source/DiabloUI/hero/selhero.cpp @@ -29,7 +29,7 @@ bool selhero_isMultiPlayer; bool (*gfnHeroInfo)(bool (*fninfofunc)(_uiheroinfo *)); bool (*gfnHeroCreate)(_uiheroinfo *); -void (*gfnHeroStats)(unsigned int, _uidefaultstats *); +void (*gfnHeroStats)(HeroClass, _uidefaultstats *); namespace { @@ -226,7 +226,7 @@ void SelheroClassSelectorFocus(int value) const auto heroClass = static_cast(vecSelHeroDlgItems[value]->m_value); _uidefaultstats defaults; - gfnHeroStats(static_cast(heroClass), &defaults); + gfnHeroStats(heroClass, &defaults); selhero_heroInfo.level = 1; selhero_heroInfo.heroclass = heroClass; @@ -546,7 +546,7 @@ void selhero_List_Init() static void UiSelHeroDialog( bool (*fninfo)(bool (*fninfofunc)(_uiheroinfo *)), bool (*fncreate)(_uiheroinfo *), - void (*fnstats)(unsigned int, _uidefaultstats *), + void (*fnstats)(HeroClass, _uidefaultstats *), bool (*fnremove)(_uiheroinfo *), _selhero_selections *dlgresult, uint32_t *saveNumber) @@ -607,7 +607,7 @@ void UiSelHeroSingDialog( bool (*fninfo)(bool (*fninfofunc)(_uiheroinfo *)), bool (*fncreate)(_uiheroinfo *), bool (*fnremove)(_uiheroinfo *), - void (*fnstats)(unsigned int, _uidefaultstats *), + void (*fnstats)(HeroClass, _uidefaultstats *), _selhero_selections *dlgresult, uint32_t *saveNumber, _difficulty *difficulty) @@ -621,7 +621,7 @@ void UiSelHeroMultDialog( bool (*fninfo)(bool (*fninfofunc)(_uiheroinfo *)), bool (*fncreate)(_uiheroinfo *), bool (*fnremove)(_uiheroinfo *), - void (*fnstats)(unsigned int, _uidefaultstats *), + void (*fnstats)(HeroClass, _uidefaultstats *), _selhero_selections *dlgresult, uint32_t *saveNumber) { diff --git a/Source/control.cpp b/Source/control.cpp index c619a9625c7..0034f1bdeaf 100644 --- a/Source/control.cpp +++ b/Source/control.cpp @@ -424,7 +424,7 @@ std::string TextCmdArenaPot(const std::string_view parameter) GenerateNewSeed(item); item.updateRequiredStatsCacheForPlayer(myPlayer); - if (!AutoPlaceItemInBelt(myPlayer, item, true) && !AutoPlaceItemInInventory(myPlayer, item, true)) { + if (!AutoPlaceItemInBelt(myPlayer, item, true, true) && !AutoPlaceItemInInventory(myPlayer, item, true, true)) { break; // inventory is full } } @@ -1212,7 +1212,7 @@ void DrawInfoBox(const Surface &out) InfoColor = UiFlags::ColorWhitegold; auto &target = Players[pcursplr]; InfoString = std::string_view(target._pName); - AddPanelString(fmt::format(fmt::runtime(_("{:s}, Level: {:d}")), _(PlayersData[static_cast(target._pClass)].className), target.getCharacterLevel())); + AddPanelString(fmt::format(fmt::runtime(_("{:s}, Level: {:d}")), target.getClassName(), target.getCharacterLevel())); AddPanelString(fmt::format(fmt::runtime(_("Hit Points {:d} of {:d}")), target._pHitPoints >> 6, target._pMaxHP >> 6)); } } diff --git a/Source/controls/plrctrls.cpp b/Source/controls/plrctrls.cpp index 72af03300eb..482bae81e80 100644 --- a/Source/controls/plrctrls.cpp +++ b/Source/controls/plrctrls.cpp @@ -1153,7 +1153,7 @@ void StashMove(AxisDirection dir) } else if (dir.y == AxisDirectionY_DOWN) { if (ActiveStashSlot.y < 10 - itemSize.height) { ActiveStashSlot.y++; - } else if ((holdItem.isEmpty() || CanBePlacedOnBelt(holdItem)) && ActiveStashSlot.x > 1) { + } else if ((holdItem.isEmpty() || CanBePlacedOnBelt(*MyPlayer, holdItem)) && ActiveStashSlot.x > 1) { int beltSlot = ActiveStashSlot.x - 2; Slot = SLOTXY_BELT_FIRST + beltSlot; ActiveStashSlot = InvalidStashPoint; diff --git a/Source/discord/discord.cpp b/Source/discord/discord.cpp index 63d267d6ae7..fdce401ba86 100644 --- a/Source/discord/discord.cpp +++ b/Source/discord/discord.cpp @@ -88,8 +88,7 @@ std::string GetLocationString() std::string GetCharacterString() { - const std::string_view charClassStr = _(PlayersData[static_cast(MyPlayer->_pClass)].className); - return fmt::format(fmt::runtime(_(/* TRANSLATORS: Discord character, i.e. "Lv 6 Warrior" */ "Lv {} {}")), tracked_data.playerLevel, charClassStr); + return fmt::format(fmt::runtime(_(/* TRANSLATORS: Discord character, i.e. "Lv 6 Warrior" */ "Lv {} {}")), tracked_data.playerLevel, MyPlayer->getClassName()); } std::string GetDetailString() diff --git a/Source/inv.cpp b/Source/inv.cpp index 972b67c72d5..4c5044723bf 100644 --- a/Source/inv.cpp +++ b/Source/inv.cpp @@ -134,7 +134,7 @@ OptionalOwnedClxSpriteList pInvCels; * @param invListIndex The item's InvList index (it's expected this already has +1 added to it since InvGrid can't store a 0 index) * @param itemSize Size of item */ -void AddItemToInvGrid(Player &player, int invGridIndex, int invListIndex, Size itemSize) +void AddItemToInvGrid(Player &player, int invGridIndex, int invListIndex, Size itemSize, bool sendNetworkMessage) { const int pitch = 10; for (int y = 0; y < itemSize.height; y++) { @@ -147,7 +147,7 @@ void AddItemToInvGrid(Player &player, int invGridIndex, int invListIndex, Size i } } - if (&player == MyPlayer) { + if (sendNetworkMessage) { NetSendCmdChInvItem(false, invGridIndex); } } @@ -256,25 +256,25 @@ bool CanEquip(Player &player, const Item &item, inv_body_loc bodyLocation) } } -void ChangeEquipment(Player &player, inv_body_loc bodyLocation, const Item &item) +void ChangeEquipment(Player &player, inv_body_loc bodyLocation, const Item &item, bool sendNetworkMessage) { player.InvBody[bodyLocation] = item; - if (&player == MyPlayer) { + if (sendNetworkMessage) { NetSendCmdChItem(false, bodyLocation, true); } } -bool AutoEquip(Player &player, const Item &item, inv_body_loc bodyLocation, bool persistItem) +bool AutoEquip(Player &player, const Item &item, inv_body_loc bodyLocation, bool persistItem, bool sendNetworkMessage) { if (!CanEquip(player, item, bodyLocation)) { return false; } if (persistItem) { - ChangeEquipment(player, bodyLocation, item); + ChangeEquipment(player, bodyLocation, item, sendNetworkMessage); - if (*sgOptions.Audio.autoEquipSound && &player == MyPlayer) { + if (sendNetworkMessage && *sgOptions.Audio.autoEquipSound) { PlaySFX(ItemInvSnds[ItemCAnimTbl[item._iCurs]]); } @@ -388,7 +388,7 @@ void CheckInvPaste(Player &player, Point cursorPosition) } } } else if (il == ILOC_BELT) { - if (!CanBePlacedOnBelt(player.HoldItem)) + if (!CanBePlacedOnBelt(player, player.HoldItem)) return; } else if (desiredIl != il) { return; @@ -426,7 +426,7 @@ void CheckInvPaste(Player &player, Point cursorPosition) }; inv_body_loc slot = iLocToInvLoc(il); Item previouslyEquippedItem = player.InvBody[slot]; - ChangeEquipment(player, slot, player.HoldItem.pop()); + ChangeEquipment(player, slot, player.HoldItem.pop(), &player == MyPlayer); if (!previouslyEquippedItem.isEmpty()) { player.HoldItem = previouslyEquippedItem; } @@ -446,7 +446,7 @@ void CheckInvPaste(Player &player, Point cursorPosition) if (dequipTwoHandedWeapon) { RemoveEquipment(player, otherHand, false); } - ChangeEquipment(player, pasteHand, player.HoldItem.pop()); + ChangeEquipment(player, pasteHand, player.HoldItem.pop(), &player == MyPlayer); if (!previouslyEquippedItem.isEmpty()) { player.HoldItem = previouslyEquippedItem; } @@ -472,14 +472,14 @@ void CheckInvPaste(Player &player, Point cursorPosition) if (player.InvBody[INVLOC_HAND_RIGHT].isEmpty()) { Item previouslyEquippedItem = player.InvBody[INVLOC_HAND_LEFT]; - ChangeEquipment(player, INVLOC_HAND_LEFT, player.HoldItem.pop()); + ChangeEquipment(player, INVLOC_HAND_LEFT, player.HoldItem.pop(), &player == MyPlayer); if (!previouslyEquippedItem.isEmpty()) { player.HoldItem = previouslyEquippedItem; } } else { Item previouslyEquippedItem = player.InvBody[INVLOC_HAND_RIGHT]; RemoveEquipment(player, INVLOC_HAND_RIGHT, false); - ChangeEquipment(player, INVLOC_HAND_LEFT, player.HoldItem); + ChangeEquipment(player, INVLOC_HAND_LEFT, player.HoldItem, &player == MyPlayer); player.HoldItem = previouslyEquippedItem; } break; @@ -533,7 +533,7 @@ void CheckInvPaste(Player &player, Point cursorPosition) } } - AddItemToInvGrid(player, slot - SLOTXY_INV_FIRST, it, itemSize); + AddItemToInvGrid(player, slot - SLOTXY_INV_FIRST, it, itemSize, &player == MyPlayer); } break; case ILOC_BELT: { @@ -697,8 +697,8 @@ void CheckInvCut(Player &player, Point cursorPosition, bool automaticMove, bool holdItem = player.InvList[iv - 1]; if (automaticMove) { - if (CanBePlacedOnBelt(holdItem)) { - automaticallyMoved = AutoPlaceItemInBelt(player, holdItem, true); + if (CanBePlacedOnBelt(player, holdItem)) { + automaticallyMoved = AutoPlaceItemInBelt(player, holdItem, true, &player == MyPlayer); } else if (CanEquip(holdItem)) { /* * Move the respective InvBodyItem to inventory before moving the item from inventory @@ -768,7 +768,7 @@ void CheckInvCut(Player &player, Point cursorPosition, bool automaticMove, bool } } holdItem = player.InvList[iv - 1]; - automaticallyMoved = automaticallyEquipped = AutoEquip(player, holdItem); + automaticallyMoved = automaticallyEquipped = AutoEquip(player, holdItem, true, &player == MyPlayer); } } @@ -809,7 +809,7 @@ void CheckInvCut(Player &player, Point cursorPosition, bool automaticMove, bool if (automaticMove) { if (!automaticallyMoved) { - if (CanBePlacedOnBelt(holdItem) || automaticallyUnequip) { + if (CanBePlacedOnBelt(player, holdItem) || automaticallyUnequip) { player.SaySpecific(HeroSpeech::IHaveNoRoom); } else { player.SaySpecific(HeroSpeech::ICantDoThat); @@ -1029,11 +1029,11 @@ void InvDrawSlotBack(const Surface &out, Point targetPosition, Size size, item_q } } -bool CanBePlacedOnBelt(const Item &item) +bool CanBePlacedOnBelt(const Player &player, const Item &item) { return FitsInBeltSlot(item) && item._itype != ItemType::Gold - && MyPlayer->CanUseItem(item) + && player.CanUseItem(item) && item.isUsable(); } @@ -1199,9 +1199,9 @@ void RemoveEquipment(Player &player, inv_body_loc bodyLocation, bool hiPri) player.InvBody[bodyLocation].clear(); } -bool AutoPlaceItemInBelt(Player &player, const Item &item, bool persistItem) +bool AutoPlaceItemInBelt(Player &player, const Item &item, bool persistItem, bool sendNetworkMessage) { - if (!CanBePlacedOnBelt(item)) { + if (!CanBePlacedOnBelt(player, item)) { return false; } @@ -1211,7 +1211,7 @@ bool AutoPlaceItemInBelt(Player &player, const Item &item, bool persistItem) beltItem = item; player.CalcScrolls(); RedrawComponent(PanelDrawComponent::Belt); - if (&player == MyPlayer) { + if (sendNetworkMessage) { size_t beltIndex = std::distance(&player.SpdList[0], &beltItem); NetSendCmdChBeltItem(false, beltIndex); } @@ -1224,14 +1224,14 @@ bool AutoPlaceItemInBelt(Player &player, const Item &item, bool persistItem) return false; } -bool AutoEquip(Player &player, const Item &item, bool persistItem) +bool AutoEquip(Player &player, const Item &item, bool persistItem, bool sendNetworkMessage) { if (!CanEquip(item)) { return false; } for (int bodyLocation = INVLOC_HEAD; bodyLocation < NUM_INVLOC; bodyLocation++) { - if (AutoEquip(player, item, (inv_body_loc)bodyLocation, persistItem)) { + if (AutoEquip(player, item, (inv_body_loc)bodyLocation, persistItem, sendNetworkMessage)) { return true; } } @@ -1266,18 +1266,59 @@ bool AutoEquipEnabled(const Player &player, const Item &item) return true; } -bool AutoPlaceItemInInventory(Player &player, const Item &item, bool persistItem) +namespace { +/** + * @brief Checks whether the given item can be placed on the specified player's inventory slot. + * If 'persistItem' is 'True', the item is also placed in the inventory slot. + * @param player The player whose inventory will be checked. + * @param slotIndex The 0-based index of the slot to put the item on. + * @param item The item to be checked. + * @param persistItem Pass 'True' to actually place the item in the inventory slot. The default is 'False'. + * @return 'True' in case the item can be placed on the specified player's inventory slot and 'False' otherwise. + */ +bool AutoPlaceItemInInventorySlot(Player &player, int slotIndex, const Item &item, bool persistItem, bool sendNetworkMessage) +{ + int yy = (slotIndex > 0) ? (10 * (slotIndex / 10)) : 0; + + Size itemSize = GetInventorySize(item); + for (int j = 0; j < itemSize.height; j++) { + if (yy >= InventoryGridCells) { + return false; + } + int xx = (slotIndex > 0) ? (slotIndex % 10) : 0; + for (int i = 0; i < itemSize.width; i++) { + if (xx >= 10 || player.InvGrid[xx + yy] != 0) { + return false; + } + xx++; + } + yy += 10; + } + + if (persistItem) { + player.InvList[player._pNumInv] = item; + player._pNumInv++; + + AddItemToInvGrid(player, slotIndex, player._pNumInv, itemSize, sendNetworkMessage); + player.CalcScrolls(); + } + + return true; +} +} // namespace + +bool AutoPlaceItemInInventory(Player &player, const Item &item, bool persistItem, bool sendNetworkMessage) { Size itemSize = GetInventorySize(item); if (itemSize.height == 1) { for (int i = 30; i <= 39; i++) { - if (AutoPlaceItemInInventorySlot(player, i, item, persistItem)) + if (AutoPlaceItemInInventorySlot(player, i, item, persistItem, sendNetworkMessage)) return true; } for (int x = 9; x >= 0; x--) { for (int y = 2; y >= 0; y--) { - if (AutoPlaceItemInInventorySlot(player, 10 * y + x, item, persistItem)) + if (AutoPlaceItemInInventorySlot(player, 10 * y + x, item, persistItem, sendNetworkMessage)) return true; } } @@ -1287,14 +1328,14 @@ bool AutoPlaceItemInInventory(Player &player, const Item &item, bool persistItem if (itemSize.height == 2) { for (int x = 10 - itemSize.width; x >= 0; x -= itemSize.width) { for (int y = 0; y < 3; y++) { - if (AutoPlaceItemInInventorySlot(player, 10 * y + x, item, persistItem)) + if (AutoPlaceItemInInventorySlot(player, 10 * y + x, item, persistItem, sendNetworkMessage)) return true; } } if (itemSize.width == 2) { for (int x = 7; x >= 0; x -= 2) { for (int y = 0; y < 3; y++) { - if (AutoPlaceItemInInventorySlot(player, 10 * y + x, item, persistItem)) + if (AutoPlaceItemInInventorySlot(player, 10 * y + x, item, persistItem, sendNetworkMessage)) return true; } } @@ -1304,7 +1345,7 @@ bool AutoPlaceItemInInventory(Player &player, const Item &item, bool persistItem if (itemSize == Size { 1, 3 }) { for (int i = 0; i < 20; i++) { - if (AutoPlaceItemInInventorySlot(player, i, item, persistItem)) + if (AutoPlaceItemInInventorySlot(player, i, item, persistItem, sendNetworkMessage)) return true; } return false; @@ -1312,12 +1353,12 @@ bool AutoPlaceItemInInventory(Player &player, const Item &item, bool persistItem if (itemSize == Size { 2, 3 }) { for (int i = 0; i < 9; i++) { - if (AutoPlaceItemInInventorySlot(player, i, item, persistItem)) + if (AutoPlaceItemInInventorySlot(player, i, item, persistItem, sendNetworkMessage)) return true; } for (int i = 10; i < 19; i++) { - if (AutoPlaceItemInInventorySlot(player, i, item, persistItem)) + if (AutoPlaceItemInInventorySlot(player, i, item, persistItem, sendNetworkMessage)) return true; } return false; @@ -1326,36 +1367,6 @@ bool AutoPlaceItemInInventory(Player &player, const Item &item, bool persistItem app_fatal(StrCat("Unknown item size: ", itemSize.width, "x", itemSize.height)); } -bool AutoPlaceItemInInventorySlot(Player &player, int slotIndex, const Item &item, bool persistItem) -{ - int yy = (slotIndex > 0) ? (10 * (slotIndex / 10)) : 0; - - Size itemSize = GetInventorySize(item); - for (int j = 0; j < itemSize.height; j++) { - if (yy >= InventoryGridCells) { - return false; - } - int xx = (slotIndex > 0) ? (slotIndex % 10) : 0; - for (int i = 0; i < itemSize.width; i++) { - if (xx >= 10 || player.InvGrid[xx + yy] != 0) { - return false; - } - xx++; - } - yy += 10; - } - - if (persistItem) { - player.InvList[player._pNumInv] = item; - player._pNumInv++; - - AddItemToInvGrid(player, slotIndex, player._pNumInv, itemSize); - player.CalcScrolls(); - } - - return true; -} - int RoomForGold() { int amount = 0; @@ -1639,16 +1650,16 @@ void AutoGetItem(Player &player, Item *itemPointer, int ii) SetPlrHandGoldCurs(item); } } else { - done = AutoEquipEnabled(player, item) && AutoEquip(player, item); + done = AutoEquipEnabled(player, item) && AutoEquip(player, item, true, &player == MyPlayer); if (done) { autoEquipped = true; } if (!done) { - done = AutoPlaceItemInBelt(player, item, true); + done = AutoPlaceItemInBelt(player, item, true, &player == MyPlayer); } if (!done) { - done = AutoPlaceItemInInventory(player, item, true); + done = AutoPlaceItemInInventory(player, item, true, &player == MyPlayer); } } @@ -2113,8 +2124,8 @@ void CloseStash() if (itemTile) { NetSendCmdPItem(true, CMD_PUTITEM, *itemTile, myPlayer.HoldItem); } else { - if (!AutoPlaceItemInBelt(myPlayer, myPlayer.HoldItem, true) - && !AutoPlaceItemInInventory(myPlayer, myPlayer.HoldItem, true) + if (!AutoPlaceItemInBelt(myPlayer, myPlayer.HoldItem, true, true) + && !AutoPlaceItemInInventory(myPlayer, myPlayer.HoldItem, true, true) && !AutoPlaceItemInStash(myPlayer, myPlayer.HoldItem, true)) { // This can fail for max gold, arena potions and a stash that has been arranged // to not have room for the item all 3 cases are extremely unlikely diff --git a/Source/inv.h b/Source/inv.h index ac3e98eb5c8..e2adba649f1 100644 --- a/Source/inv.h +++ b/Source/inv.h @@ -92,7 +92,7 @@ void InvDrawSlotBack(const Surface &out, Point targetPosition, Size size, item_q * @param item The item to be checked. * @return 'True' in case the item can be placed on the belt and 'False' otherwise. */ -bool CanBePlacedOnBelt(const Item &item); +bool CanBePlacedOnBelt(const Player &player, const Item &item); /** * @brief Function type which performs an operation on the given item. @@ -134,9 +134,11 @@ bool AutoEquipEnabled(const Player &player, const Item &item); * @param item The item to equip. * @param persistItem Indicates whether or not the item should be persisted in the player's body. Pass 'False' to check * whether the player can equip the item but you don't want the item to actually be equipped. 'True' by default. + * @param sendNetworkMessage Set to true if you want an equip sound and network message to be generated if the equipment + * changes. Should only be set if a local player is equipping an item in a play session (not when creating a new game) * @return 'True' if the item was equipped and 'False' otherwise. */ -bool AutoEquip(Player &player, const Item &item, bool persistItem = true); +bool AutoEquip(Player &player, const Item &item, bool persistItem = true, bool sendNetworkMessage = false); /** * @brief Checks whether the given item can be placed on the specified player's inventory. @@ -144,20 +146,11 @@ bool AutoEquip(Player &player, const Item &item, bool persistItem = true); * @param player The player whose inventory will be checked. * @param item The item to be checked. * @param persistItem Pass 'True' to actually place the item in the inventory. The default is 'False'. + * @param sendNetworkMessage Set to true if you want a network message to be generated if the item is persisted. + * Should only be set if a local player is placing an item in a play session (not when creating a new game) * @return 'True' in case the item can be placed on the player's inventory and 'False' otherwise. */ -bool AutoPlaceItemInInventory(Player &player, const Item &item, bool persistItem = false); - -/** - * @brief Checks whether the given item can be placed on the specified player's inventory slot. - * If 'persistItem' is 'True', the item is also placed in the inventory slot. - * @param player The player whose inventory will be checked. - * @param slotIndex The 0-based index of the slot to put the item on. - * @param item The item to be checked. - * @param persistItem Pass 'True' to actually place the item in the inventory slot. The default is 'False'. - * @return 'True' in case the item can be placed on the specified player's inventory slot and 'False' otherwise. - */ -bool AutoPlaceItemInInventorySlot(Player &player, int slotIndex, const Item &item, bool persistItem); +bool AutoPlaceItemInInventory(Player &player, const Item &item, bool persistItem = false, bool sendNetworkMessage = false); /** * @brief Checks whether the given item can be placed on the specified player's belt. Returns 'True' when the item can be placed @@ -166,9 +159,11 @@ bool AutoPlaceItemInInventorySlot(Player &player, int slotIndex, const Item &ite * @param player The player on whose belt will be checked. * @param item The item to be checked. * @param persistItem Pass 'True' to actually place the item in the belt. The default is 'False'. + * @param sendNetworkMessage Set to true if you want a network message to be generated if the item is persisted. + * Should only be set if a local player is placing an item in a play session (not when creating a new game) * @return 'True' in case the item can be placed on the player's belt and 'False' otherwise. */ -bool AutoPlaceItemInBelt(Player &player, const Item &item, bool persistItem = false); +bool AutoPlaceItemInBelt(Player &player, const Item &item, bool persistItem = false, bool sendNetworkMessage = false); /** * @brief Calculate the maximum aditional gold that may fit in the user's inventory diff --git a/Source/items.cpp b/Source/items.cpp index 569727b4753..bf4141187b8 100644 --- a/Source/items.cpp +++ b/Source/items.cpp @@ -2748,10 +2748,11 @@ void CalcPlrItemVals(Player &player, bool loadgfx) player._pFireResist = std::clamp(fr, 0, MaxResistance); player._pLghtResist = std::clamp(lr, 0, MaxResistance); - vadd = (vadd * GetClassAttributes(player._pClass).itmLife) >> 6; + const ClassAttributes &playerClassAttributes = player.getClassAttributes(); + vadd = (vadd * playerClassAttributes.itmLife) >> 6; ihp += (vadd << 6); // BUGFIX: blood boil can cause negative shifts here (see line 757) - madd = (madd * GetClassAttributes(player._pClass).itmMana) >> 6; + madd = (madd * playerClassAttributes.itmMana) >> 6; imana += (madd << 6); player._pMaxHP = ihp + player._pMaxHPBase; @@ -2972,6 +2973,17 @@ void SetPlrHandGoldCurs(Item &gold) gold._iCurs = GetGoldCursor(gold._ivalue); } +namespace { +void CreateStartingItem(Player &player, _item_indexes itemData) +{ + Item item; + InitializeItem(item, itemData); + GenerateNewSeed(item); + item.updateRequiredStatsCacheForPlayer(player); + AutoEquip(player, item) || AutoPlaceItemInBelt(player, item, true) || AutoPlaceItemInInventory(player, item, true); +} +} // namespace + void CreatePlrItems(Player &player) { for (auto &item : player.InvBody) { @@ -2992,90 +3004,40 @@ void CreatePlrItems(Player &player) item.clear(); } - switch (player._pClass) { - case HeroClass::Warrior: - InitializeItem(player.InvBody[INVLOC_HAND_LEFT], IDI_WARRIOR); - GenerateNewSeed(player.InvBody[INVLOC_HAND_LEFT]); + const PlayerStartingLoadoutData &loadout = GetPlayerStartingLoadoutForClass(player._pClass); - InitializeItem(player.InvBody[INVLOC_HAND_RIGHT], IDI_WARRSHLD); - GenerateNewSeed(player.InvBody[INVLOC_HAND_RIGHT]); + if (loadout.spell != SpellID::Null && loadout.spellLevel > 0) { + player._pMemSpells = GetSpellBitmask(loadout.spell); + player._pRSplType = SpellType::Spell; + player._pRSpell = loadout.spell; + player._pSplLvl[static_cast(loadout.spell)] = loadout.spellLevel; + } else { + player._pMemSpells = 0; + } - { - Item club; - InitializeItem(club, IDI_WARRCLUB); - GenerateNewSeed(club); - AutoPlaceItemInInventorySlot(player, 0, club, true); + if (loadout.skill != SpellID::Null) { + player._pAblSpells = GetSpellBitmask(loadout.skill); + if (player._pRSplType == SpellType::Invalid) { + player._pRSplType = SpellType::Skill; + player._pRSpell = loadout.skill; } + } - InitializeItem(player.SpdList[0], IDI_HEAL); - GenerateNewSeed(player.SpdList[0]); - - InitializeItem(player.SpdList[1], IDI_HEAL); - GenerateNewSeed(player.SpdList[1]); - break; - case HeroClass::Rogue: - InitializeItem(player.InvBody[INVLOC_HAND_LEFT], IDI_ROGUE); - GenerateNewSeed(player.InvBody[INVLOC_HAND_LEFT]); - - InitializeItem(player.SpdList[0], IDI_HEAL); - GenerateNewSeed(player.SpdList[0]); - - InitializeItem(player.SpdList[1], IDI_HEAL); - GenerateNewSeed(player.SpdList[1]); - break; - case HeroClass::Sorcerer: - InitializeItem(player.InvBody[INVLOC_HAND_LEFT], gbIsHellfire ? IDI_SORCERER : IDI_SORCERER_DIABLO); - GenerateNewSeed(player.InvBody[INVLOC_HAND_LEFT]); - - InitializeItem(player.SpdList[0], gbIsHellfire ? IDI_HEAL : IDI_MANA); - GenerateNewSeed(player.SpdList[0]); - - InitializeItem(player.SpdList[1], gbIsHellfire ? IDI_HEAL : IDI_MANA); - GenerateNewSeed(player.SpdList[1]); - break; - - case HeroClass::Monk: - InitializeItem(player.InvBody[INVLOC_HAND_LEFT], IDI_SHORTSTAFF); - GenerateNewSeed(player.InvBody[INVLOC_HAND_LEFT]); - InitializeItem(player.SpdList[0], IDI_HEAL); - GenerateNewSeed(player.SpdList[0]); - - InitializeItem(player.SpdList[1], IDI_HEAL); - GenerateNewSeed(player.SpdList[1]); - break; - case HeroClass::Bard: - InitializeItem(player.InvBody[INVLOC_HAND_LEFT], IDI_BARDSWORD); - GenerateNewSeed(player.InvBody[INVLOC_HAND_LEFT]); - - InitializeItem(player.InvBody[INVLOC_HAND_RIGHT], IDI_BARDDAGGER); - GenerateNewSeed(player.InvBody[INVLOC_HAND_RIGHT]); - InitializeItem(player.SpdList[0], IDI_HEAL); - GenerateNewSeed(player.SpdList[0]); - - InitializeItem(player.SpdList[1], IDI_HEAL); - GenerateNewSeed(player.SpdList[1]); - break; - case HeroClass::Barbarian: - InitializeItem(player.InvBody[INVLOC_HAND_LEFT], IDI_BARBARIAN); - GenerateNewSeed(player.InvBody[INVLOC_HAND_LEFT]); - - InitializeItem(player.InvBody[INVLOC_HAND_RIGHT], IDI_WARRSHLD); - GenerateNewSeed(player.InvBody[INVLOC_HAND_RIGHT]); - InitializeItem(player.SpdList[0], IDI_HEAL); - GenerateNewSeed(player.SpdList[0]); - - InitializeItem(player.SpdList[1], IDI_HEAL); - GenerateNewSeed(player.SpdList[1]); - break; + for (auto &itemChoice : loadout.items) { + _item_indexes itemData = gbIsHellfire && itemChoice.hellfire != _item_indexes::IDI_NONE ? itemChoice.hellfire : itemChoice.diablo; + if (itemData != _item_indexes::IDI_NONE) + CreateStartingItem(player, itemData); } - Item &goldItem = player.InvList[player._pNumInv]; - MakeGoldStack(goldItem, 100); + if (loadout.gold > 0) { + Item &goldItem = player.InvList[player._pNumInv]; + MakeGoldStack(goldItem, loadout.gold); - player._pNumInv++; - player.InvGrid[30] = player._pNumInv; + player._pNumInv++; + player.InvGrid[30] = player._pNumInv; - player._pGold = goldItem._ivalue; + player._pGold = goldItem._ivalue; + } CalcPlrItemVals(player, false); } diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index e7889704e88..6845827201c 100644 --- a/Source/loadsave.cpp +++ b/Source/loadsave.cpp @@ -412,9 +412,7 @@ void LoadPlayer(LoadHelper &file, Player &player) player._pBaseVit = file.NextLE(); player._pStatPts = file.NextLE(); player._pDamageMod = file.NextLE(); - player._pBaseToBlk = file.NextLE(); - if (player._pBaseToBlk == 0) - player._pBaseToBlk = GetClassAttributes(player._pClass).blockBonus; + file.Skip(); // Skip _pBaseToBlk - always a copy of PlayerData.blockBonus player._pHPBase = file.NextLE(); player._pMaxHPBase = file.NextLE(); player._pHitPoints = file.NextLE(); @@ -1224,7 +1222,7 @@ void SavePlayer(SaveHelper &file, const Player &player) file.WriteLE(player._pStatPts); file.WriteLE(player._pDamageMod); - file.WriteLE(player._pBaseToBlk); + file.WriteLE(player.getBaseToBlock()); // set _pBaseToBlk for backwards compatibility file.WriteLE(player._pHPBase); file.WriteLE(player._pMaxHPBase); file.WriteLE(player._pHitPoints); diff --git a/Source/pack.cpp b/Source/pack.cpp index ddb420fcf65..3ee80dc17a9 100644 --- a/Source/pack.cpp +++ b/Source/pack.cpp @@ -341,7 +341,8 @@ void PackNetPlayer(PlayerNetPack &packed, const Player &player) packed.pMana = SDL_SwapLE32(player._pMana); packed.pMaxMana = SDL_SwapLE32(player._pMaxMana); packed.pDamageMod = SDL_SwapLE32(player._pDamageMod); - packed.pBaseToBlk = SDL_SwapLE32(player._pBaseToBlk); + // we pack base to block as a basic check that remote players are using the same playerdat values as we are + packed.pBaseToBlk = SDL_SwapLE32(player.getBaseToBlock()); packed.pIMinDam = SDL_SwapLE32(player._pIMinDam); packed.pIMaxDam = SDL_SwapLE32(player._pIMaxDam); packed.pIAC = SDL_SwapLE32(player._pIAC); @@ -453,7 +454,6 @@ void UnPackPlayer(const PlayerPack &packed, Player &player) player._pExperience = SDL_SwapLE32(packed.pExperience); player._pGold = SDL_SwapLE32(packed.pGold); - player._pBaseToBlk = GetClassAttributes(player._pClass).blockBonus; if ((int)(player._pHPBase & 0xFFFFFFC0) < 64) player._pHPBase = 64; @@ -542,7 +542,6 @@ bool UnPackNetPlayer(const PlayerNetPack &packed, Player &player) player._pStatPts = packed.pStatPts; player._pExperience = SDL_SwapLE32(packed.pExperience); - player._pBaseToBlk = GetClassAttributes(player._pClass).blockBonus; player._pMaxManaBase = baseManaMax; player._pManaBase = baseMana; player._pMemSpells = SDL_SwapLE64(packed.pMemSpells); @@ -616,7 +615,7 @@ bool UnPackNetPlayer(const PlayerNetPack &packed, Player &player) ValidateFields(player._pMana, SDL_SwapLE32(packed.pMana), player._pMana == SDL_SwapLE32(packed.pMana)); ValidateFields(player._pMaxMana, SDL_SwapLE32(packed.pMaxMana), player._pMaxMana == SDL_SwapLE32(packed.pMaxMana)); ValidateFields(player._pDamageMod, SDL_SwapLE32(packed.pDamageMod), player._pDamageMod == SDL_SwapLE32(packed.pDamageMod)); - ValidateFields(player._pBaseToBlk, SDL_SwapLE32(packed.pBaseToBlk), player._pBaseToBlk == SDL_SwapLE32(packed.pBaseToBlk)); + ValidateFields(player.getBaseToBlock(), SDL_SwapLE32(packed.pBaseToBlk), player.getBaseToBlock() == SDL_SwapLE32(packed.pBaseToBlk)); ValidateFields(player._pIMinDam, SDL_SwapLE32(packed.pIMinDam), player._pIMinDam == SDL_SwapLE32(packed.pIMinDam)); ValidateFields(player._pIMaxDam, SDL_SwapLE32(packed.pIMaxDam), player._pIMaxDam == SDL_SwapLE32(packed.pIMaxDam)); ValidateFields(player._pIAC, SDL_SwapLE32(packed.pIAC), player._pIAC == SDL_SwapLE32(packed.pIAC)); diff --git a/Source/panels/charpanel.cpp b/Source/panels/charpanel.cpp index 080b5970f3c..6a4e0b504cd 100644 --- a/Source/panels/charpanel.cpp +++ b/Source/panels/charpanel.cpp @@ -123,7 +123,7 @@ PanelEntry panelEntries[] = { { "", { 9, 14 }, 150, 0, []() { return StyledText { UiFlags::ColorWhite, InspectPlayer->_pName }; } }, { "", { 161, 14 }, 149, 0, - []() { return StyledText { UiFlags::ColorWhite, std::string(_(PlayersData[static_cast(InspectPlayer->_pClass)].className)) }; } }, + []() { return StyledText { UiFlags::ColorWhite, std::string(InspectPlayer->getClassName()) }; } }, { N_("Level"), { 57, 52 }, 57, 45, []() { return StyledText { UiFlags::ColorWhite, StrCat(InspectPlayer->getCharacterLevel()) }; } }, diff --git a/Source/pfile.cpp b/Source/pfile.cpp index 9c5c1c68a61..6ff84254702 100644 --- a/Source/pfile.cpp +++ b/Source/pfile.cpp @@ -676,9 +676,9 @@ bool pfile_ui_set_hero_infos(bool (*uiAddHeroInfo)(_uiheroinfo *)) return true; } -void pfile_ui_set_class_stats(unsigned int playerClass, _uidefaultstats *classStats) +void pfile_ui_set_class_stats(HeroClass playerClass, _uidefaultstats *classStats) { - const ClassAttributes &classAttributes = GetClassAttributes(static_cast(playerClass)); + const ClassAttributes &classAttributes = GetClassAttributes(playerClass); classStats->strength = classAttributes.baseStr; classStats->magic = classAttributes.baseMag; classStats->dexterity = classAttributes.baseDex; diff --git a/Source/pfile.h b/Source/pfile.h index 5d84729d61e..c025f0c9349 100644 --- a/Source/pfile.h +++ b/Source/pfile.h @@ -116,7 +116,7 @@ HeroCompareResult pfile_compare_hero_demo(int demo, bool logDetails); void sfile_write_stash(); bool pfile_ui_set_hero_infos(bool (*uiAddHeroInfo)(_uiheroinfo *)); -void pfile_ui_set_class_stats(unsigned int playerClass, _uidefaultstats *classStats); +void pfile_ui_set_class_stats(HeroClass playerClass, _uidefaultstats *classStats); uint32_t pfile_ui_get_first_unused_save_num(); bool pfile_ui_save_create(_uiheroinfo *heroinfo); bool pfile_delete_save(_uiheroinfo *heroInfo); diff --git a/Source/player.cpp b/Source/player.cpp index addae961297..d6b0f4bba82 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -37,7 +37,6 @@ #include "objects.h" #include "options.h" #include "player.h" -#include "playerdat.hpp" #include "qol/autopickup.h" #include "qol/floatingnumbers.h" #include "qol/stash.h" @@ -1703,7 +1702,7 @@ int Player::GetCurrentAttributeValue(CharacterAttribute attribute) const int Player::GetMaximumAttributeValue(CharacterAttribute attribute) const { - const ClassAttributes &attr = GetClassAttributes(_pClass); + const ClassAttributes &attr = getClassAttributes(); switch (attribute) { case CharacterAttribute::Strength: return attr.maxStr; @@ -2062,13 +2061,13 @@ uint32_t Player::getNextExperienceThreshold() const int32_t Player::calculateBaseLife() const { - const ClassAttributes &attr = GetClassAttributes(_pClass); + const ClassAttributes &attr = getClassAttributes(); return attr.adjLife + (attr.lvlLife * getCharacterLevel()) + (attr.chrLife * _pBaseVit); } int32_t Player::calculateBaseMana() const { - const ClassAttributes &attr = GetClassAttributes(_pClass); + const ClassAttributes &attr = getClassAttributes(); return attr.adjMana + (attr.lvlMana * getCharacterLevel()) + (attr.chrMana * _pBaseMag); } @@ -2096,7 +2095,7 @@ void LoadPlrGFX(Player &player, player_graphic graphic) const HeroClass cls = GetPlayerSpriteClass(player._pClass); const PlayerWeaponGraphic animWeaponId = GetPlayerWeaponGraphic(graphic, static_cast(player._pgfxnum & 0xF)); - const char *path = PlayersData[static_cast(cls)].classPath; + const char *path = PlayersSpriteData[static_cast(cls)].classPath; const char *szCel; switch (graphic) { @@ -2279,11 +2278,11 @@ void CreatePlayer(Player &player, HeroClass c) player = {}; SetRndSeed(SDL_GetTicks()); - const ClassAttributes &attr = GetClassAttributes(c); - player.setCharacterLevel(1); player._pClass = c; + const ClassAttributes &attr = player.getClassAttributes(); + player._pBaseStr = attr.baseStr; player._pStrength = player._pBaseStr; @@ -2296,8 +2295,6 @@ void CreatePlayer(Player &player, HeroClass c) player._pBaseVit = attr.baseVit; player._pVitality = player._pBaseVit; - player._pBaseToBlk = attr.blockBonus; - player._pHitPoints = player.calculateBaseLife(); player._pMaxHP = player._pHitPoints; player._pHPBase = player._pHitPoints; @@ -2313,48 +2310,18 @@ void CreatePlayer(Player &player, HeroClass c) player._pLightRad = 10; player._pInfraFlag = false; - player._pRSplType = SpellType::Skill; - SpellID s = PlayersData[static_cast(c)].skill; - player._pAblSpells = GetSpellBitmask(s); - player._pRSpell = s; - - if (c == HeroClass::Sorcerer) { - player._pMemSpells = GetSpellBitmask(SpellID::Firebolt); - player._pRSplType = SpellType::Spell; - player._pRSpell = SpellID::Firebolt; - } else { - player._pMemSpells = 0; - } - for (uint8_t &spellLevel : player._pSplLvl) { spellLevel = 0; } player._pSpellFlags = SpellFlag::None; - - if (player._pClass == HeroClass::Sorcerer) { - player._pSplLvl[static_cast(SpellID::Firebolt)] = 2; - } + player._pRSplType = SpellType::Invalid; // Initializing the hotkey bindings to no selection std::fill(player._pSplHotKey, player._pSplHotKey + NumHotkeys, SpellID::Invalid); - PlayerWeaponGraphic animWeaponId = PlayerWeaponGraphic::Unarmed; - switch (c) { - case HeroClass::Warrior: - case HeroClass::Bard: - case HeroClass::Barbarian: - animWeaponId = PlayerWeaponGraphic::SwordShield; - break; - case HeroClass::Rogue: - animWeaponId = PlayerWeaponGraphic::Bow; - break; - case HeroClass::Sorcerer: - case HeroClass::Monk: - animWeaponId = PlayerWeaponGraphic::Staff; - break; - } - player._pgfxnum = static_cast(animWeaponId); + // CreatePlrItems calls AutoEquip which will overwrite the player graphic if required + player._pgfxnum = static_cast(PlayerWeaponGraphic::Unarmed); for (bool &levelVisited : player._pLvlVisited) { levelVisited = false; @@ -2397,7 +2364,7 @@ void NextPlrLevel(Player &player) } else { player._pStatPts += 5; } - int hp = GetClassAttributes(player._pClass).lvlLife; + int hp = player.getClassAttributes().lvlLife; player._pMaxHP += hp; player._pHitPoints = player._pMaxHP; @@ -2408,7 +2375,7 @@ void NextPlrLevel(Player &player) RedrawComponent(PanelDrawComponent::Health); } - int mana = GetClassAttributes(player._pClass).lvlMana; + int mana = player.getClassAttributes().lvlMana; player._pMaxMana += mana; player._pMaxManaBase += mana; @@ -2534,8 +2501,7 @@ void InitPlayer(Player &player, bool firstTime) ActivateVision(player.position.tile, player._pLightRad, player.getId()); } - SpellID s = PlayersData[static_cast(player._pClass)].skill; - player._pAblSpells = GetSpellBitmask(s); + player._pAblSpells = GetSpellBitmask(GetPlayerStartingLoadoutForClass(player._pClass).skill); player._pInvincible = false; @@ -3329,7 +3295,7 @@ void ModifyPlrMag(Player &player, int l) player._pBaseMag += l; int ms = l; - ms *= GetClassAttributes(player._pClass).chrMana; + ms *= player.getClassAttributes().chrMana; player._pMaxManaBase += ms; player._pMaxMana += ms; @@ -3366,7 +3332,7 @@ void ModifyPlrVit(Player &player, int l) player._pBaseVit += l; int ms = l; - ms *= GetClassAttributes(player._pClass).chrLife; + ms *= player.getClassAttributes().chrLife; player._pHPBase += ms; player._pMaxHPBase += ms; @@ -3401,7 +3367,7 @@ void SetPlrMag(Player &player, int v) player._pBaseMag = v; int m = v; - m *= GetClassAttributes(player._pClass).chrMana; + m *= player.getClassAttributes().chrMana; player._pMaxManaBase = m; player._pMaxMana = m; @@ -3419,7 +3385,7 @@ void SetPlrVit(Player &player, int v) player._pBaseVit = v; int hp = v; - hp *= GetClassAttributes(player._pClass).chrLife; + hp *= player.getClassAttributes().chrLife; player._pHPBase = hp; player._pMaxHPBase = hp; diff --git a/Source/player.h b/Source/player.h index e3d2345aca5..d24cd893a9e 100644 --- a/Source/player.h +++ b/Source/player.h @@ -22,6 +22,7 @@ #include "items.h" #include "levels/gendung.h" #include "multi.h" +#include "playerdat.hpp" #include "spelldat.h" #include "utils/attributes.h" #include "utils/enum_traits.h" @@ -35,7 +36,6 @@ constexpr uint8_t MaxSpellLevel = 15; constexpr int PlayerNameLength = 32; constexpr size_t NumHotkeys = 12; -constexpr int BaseHitChance = 50; /** Walking directions */ enum { @@ -52,17 +52,6 @@ enum { // clang-format on }; -enum class HeroClass : uint8_t { - Warrior, - Rogue, - Sorcerer, - Monk, - Bard, - Barbarian, - - LAST = Barbarian -}; - enum class CharacterAttribute : uint8_t { Strength, Magic, @@ -247,7 +236,6 @@ struct Player { int _pBaseVit; int _pStatPts; int _pDamageMod; - int _pBaseToBlk; int _pHPBase; int _pMaxHPBase; int _pHitPoints; @@ -377,6 +365,37 @@ struct Player { uint16_t wReflections; ItemSpecialEffectHf pDamAcFlags; + /** + * @brief Convenience function to get the base stats/bonuses for this player's class + */ + [[nodiscard]] const ClassAttributes &getClassAttributes() const + { + return GetClassAttributes(_pClass); + } + + [[nodiscard]] const PlayerCombatData &getPlayerCombatData() const + { + return GetPlayerCombatDataForClass(_pClass); + } + + [[nodiscard]] const PlayerData &getPlayerData() const + { + return GetPlayerDataForClass(_pClass); + } + + /** + * @brief Gets the translated name for the character's class + */ + [[nodiscard]] std::string_view getClassName() const + { + return _(getPlayerData().className); + } + + [[nodiscard]] int getBaseToBlock() const + { + return getPlayerCombatData().baseToBlock; + } + void CalcScrolls(); bool CanUseItem(const Item &item) const @@ -556,10 +575,7 @@ struct Player { */ int GetMeleeToHit() const { - int hper = getCharacterLevel() + _pDexterity / 2 + _pIBonusToHit + BaseHitChance; - if (_pClass == HeroClass::Warrior) - hper += 20; - return hper; + return getCharacterLevel() + _pDexterity / 2 + _pIBonusToHit + getPlayerCombatData().baseMeleeToHit; } /** @@ -579,12 +595,7 @@ struct Player { */ int GetRangedToHit() const { - int hper = getCharacterLevel() + _pDexterity + _pIBonusToHit + BaseHitChance; - if (_pClass == HeroClass::Rogue) - hper += 20; - else if (_pClass == HeroClass::Warrior || _pClass == HeroClass::Bard) - hper += 10; - return hper; + return getCharacterLevel() + _pDexterity + _pIBonusToHit + getPlayerCombatData().baseRangedToHit; } int GetRangedPiercingToHit() const @@ -601,12 +612,7 @@ struct Player { */ int GetMagicToHit() const { - int hper = _pMagic + BaseHitChance; - if (_pClass == HeroClass::Sorcerer) - hper += 20; - else if (_pClass == HeroClass::Bard) - hper += 10; - return hper; + return _pMagic + getPlayerCombatData().baseMagicToHit; } /** @@ -615,7 +621,7 @@ struct Player { */ int GetBlockChance(bool useLevel = true) const { - int blkper = _pDexterity + _pBaseToBlk; + int blkper = _pDexterity + getBaseToBlock(); if (useLevel) blkper += getCharacterLevel() * 2; return blkper; diff --git a/Source/playerdat.cpp b/Source/playerdat.cpp index addab81c6f4..c3f322ac2ac 100644 --- a/Source/playerdat.cpp +++ b/Source/playerdat.cpp @@ -153,7 +153,7 @@ void ReloadExperienceData() } } -void LoadClassAttributes(std::string_view classPath, ClassAttributes &out) +void LoadClassData(std::string_view classPath, ClassAttributes &attributes, PlayerCombatData &combat) { const std::string filename = StrCat("txtdata\\classes\\", classPath, "\\attributes.tsv"); tl::expected dataFileResult = DataFile::load(filename); @@ -208,37 +208,71 @@ void LoadClassAttributes(std::string_view classPath, ClassAttributes &out) return valueField.parseFixed6(outValue); }); - readInt("baseStr", out.baseStr); - readInt("baseMag", out.baseMag); - readInt("baseDex", out.baseDex); - readInt("baseVit", out.baseVit); - readInt("maxStr", out.maxStr); - readInt("maxMag", out.maxMag); - readInt("maxDex", out.maxDex); - readInt("maxVit", out.maxVit); - readInt("blockBonus", out.blockBonus); - readDecimal("adjLife", out.adjLife); - readDecimal("adjMana", out.adjMana); - readDecimal("lvlLife", out.lvlLife); - readDecimal("lvlMana", out.lvlMana); - readDecimal("chrLife", out.chrLife); - readDecimal("chrMana", out.chrMana); - readDecimal("itmLife", out.itmLife); - readDecimal("itmMana", out.itmMana); + readInt("baseStr", attributes.baseStr); + readInt("baseMag", attributes.baseMag); + readInt("baseDex", attributes.baseDex); + readInt("baseVit", attributes.baseVit); + readInt("maxStr", attributes.maxStr); + readInt("maxMag", attributes.maxMag); + readInt("maxDex", attributes.maxDex); + readInt("maxVit", attributes.maxVit); + readInt("blockBonus", combat.baseToBlock); + readDecimal("adjLife", attributes.adjLife); + readDecimal("adjMana", attributes.adjMana); + readDecimal("lvlLife", attributes.lvlLife); + readDecimal("lvlMana", attributes.lvlMana); + readDecimal("chrLife", attributes.chrLife); + readDecimal("chrMana", attributes.chrMana); + readDecimal("itmLife", attributes.itmLife); + readDecimal("itmMana", attributes.itmMana); + readInt("baseMagicToHit", combat.baseMagicToHit); + readInt("baseMeleeToHit", combat.baseMeleeToHit); + readInt("baseRangedToHit", combat.baseRangedToHit); } std::vector ClassAttributesPerClass; +std::vector PlayersCombatData; + void LoadClassesAttributes() { const std::array classPaths { "warrior", "rogue", "sorcerer", "monk", "bard", "barbarian" }; ClassAttributesPerClass.clear(); ClassAttributesPerClass.reserve(classPaths.size()); + PlayersCombatData.clear(); + PlayersCombatData.reserve(classPaths.size()); for (std::string_view path : classPaths) { - LoadClassAttributes(path, ClassAttributesPerClass.emplace_back()); + LoadClassData(path, ClassAttributesPerClass.emplace_back(), PlayersCombatData.emplace_back()); } } +/** Contains the data related to each player class. */ +const PlayerData PlayersData[] = { + // clang-format off +// HeroClass className +// TRANSLATORS: Player Block start +/* HeroClass::Warrior */ { N_("Warrior"), }, +/* HeroClass::Rogue */ { N_("Rogue"), }, +/* HeroClass::Sorcerer */ { N_("Sorcerer"), }, +/* HeroClass::Monk */ { N_("Monk"), }, +/* HeroClass::Bard */ { N_("Bard"), }, +// TRANSLATORS: Player Block end +/* HeroClass::Barbarian */ { N_("Barbarian"), }, + // clang-format on +}; + +const std::array::value> PlayersStartingLoadoutData { { + // clang-format off +// HeroClass skill, spell, spellLevel, items[0].diablo, items[0].hellfire, items[1].diablo, items[1].hellfire, items[2].diablo, items[2].hellfire, items[3].diablo, items[3].hellfire, items[4].diablo, items[4].hellfire, gold, +/* HeroClass::Warrior */ { SpellID::ItemRepair, SpellID::Null, 0, { { { IDI_WARRIOR, IDI_WARRIOR, }, { IDI_WARRSHLD, IDI_WARRSHLD, }, { IDI_WARRCLUB, IDI_WARRCLUB, }, { IDI_HEAL, IDI_HEAL, }, { IDI_HEAL, IDI_HEAL, }, }, }, 100, }, +/* HeroClass::Rogue */ { SpellID::TrapDisarm, SpellID::Null, 0, { { { IDI_ROGUE, IDI_ROGUE, }, { IDI_HEAL, IDI_HEAL, }, { IDI_HEAL, IDI_HEAL, }, { IDI_NONE, IDI_NONE, }, { IDI_NONE, IDI_NONE, }, }, }, 100, }, +/* HeroClass::Sorcerer */ { SpellID::StaffRecharge, SpellID::Fireball, 2, { { { IDI_SORCERER_DIABLO, IDI_SORCERER, }, { IDI_MANA, IDI_HEAL, }, { IDI_MANA, IDI_HEAL, }, { IDI_NONE, IDI_NONE, }, { IDI_NONE, IDI_NONE, }, }, }, 100, }, +/* HeroClass::Monk */ { SpellID::Search, SpellID::Null, 0, { { { IDI_SHORTSTAFF, IDI_SHORTSTAFF, }, { IDI_HEAL, IDI_HEAL, }, { IDI_HEAL, IDI_HEAL, }, { IDI_NONE, IDI_NONE, }, { IDI_NONE, IDI_NONE, }, }, }, 100, }, +/* HeroClass::Bard */ { SpellID::Identify, SpellID::Null, 0, { { { IDI_BARDSWORD, IDI_BARDSWORD, }, { IDI_BARDDAGGER, IDI_BARDDAGGER, }, { IDI_HEAL, IDI_HEAL, }, { IDI_HEAL, IDI_HEAL, }, { IDI_NONE, IDI_NONE, }, }, }, 100, }, +/* HeroClass::Barbarian */ { SpellID::Rage, SpellID::Null, 0, { { { IDI_BARBARIAN, IDI_BARBARIAN, }, { IDI_WARRSHLD, IDI_WARRSHLD, }, { IDI_HEAL, IDI_HEAL, }, { IDI_HEAL, IDI_HEAL, }, { IDI_NONE, IDI_NONE, }, }, }, 100, } + // clang-format on +} }; + } // namespace const ClassAttributes &GetClassAttributes(HeroClass playerClass) @@ -262,6 +296,11 @@ uint8_t GetMaximumCharacterLevel() return ExperienceData.getMaxLevel(); } +const PlayerData &GetPlayerDataForClass(HeroClass playerClass) +{ + return PlayersData[static_cast(playerClass)]; +} + const _sfx_id herosounds[enum_size::value][enum_size::value] = { // clang-format off { PS_WARR1, PS_WARR2, PS_WARR3, PS_WARR4, PS_WARR5, PS_WARR6, PS_WARR7, PS_WARR8, PS_WARR9, PS_WARR10, PS_WARR11, PS_WARR12, PS_WARR13, PS_WARR14, PS_WARR15, PS_WARR16, PS_WARR17, PS_WARR18, PS_WARR19, PS_WARR20, PS_WARR21, PS_WARR22, PS_WARR23, PS_WARR24, PS_WARR25, PS_WARR26, PS_WARR27, PS_WARR28, PS_WARR29, PS_WARR30, PS_WARR31, PS_WARR32, PS_WARR33, PS_WARR34, PS_WARR35, PS_WARR36, PS_WARR37, PS_WARR38, PS_WARR39, PS_WARR40, PS_WARR41, PS_WARR42, PS_WARR43, PS_WARR44, PS_WARR45, PS_WARR46, PS_WARR47, PS_WARR48, PS_WARR49, PS_WARR50, PS_WARR51, PS_WARR52, PS_WARR53, PS_WARR54, PS_WARR55, PS_WARR56, PS_WARR57, PS_WARR58, PS_WARR59, PS_WARR60, PS_WARR61, PS_WARR62, PS_WARR63, PS_WARR64, PS_WARR65, PS_WARR66, PS_WARR67, PS_WARR68, PS_WARR69, PS_WARR70, PS_WARR71, PS_WARR72, PS_WARR73, PS_WARR74, PS_WARR75, PS_WARR76, PS_WARR77, PS_WARR78, PS_WARR79, PS_WARR80, PS_WARR81, PS_WARR82, PS_WARR83, PS_WARR84, PS_WARR85, PS_WARR86, PS_WARR87, PS_WARR88, PS_WARR89, PS_WARR90, PS_WARR91, PS_WARR92, PS_WARR93, PS_WARR94, PS_WARR95, PS_WARR96B, PS_WARR97, PS_WARR98, PS_WARR99, PS_WARR100, PS_WARR101, PS_WARR102, PS_DEAD }, @@ -273,32 +312,27 @@ const _sfx_id herosounds[enum_size::value][enum_size::val // clang-format on }; -/** Contains the data related to each player class. */ -const PlayerData PlayersData[] = { - // clang-format off -// HeroClass className, classPath, skill -// TRANSLATORS: Player Block start -/* HeroClass::Warrior */ { N_("Warrior"), "warrior", SpellID::ItemRepair }, -/* HeroClass::Rogue */ { N_("Rogue"), "rogue", SpellID::TrapDisarm }, -/* HeroClass::Sorcerer */ { N_("Sorcerer"), "sorceror", SpellID::StaffRecharge }, -/* HeroClass::Monk */ { N_("Monk"), "monk", SpellID::Search }, -/* HeroClass::Bard */ { N_("Bard"), "rogue", SpellID::Identify }, -/* HeroClass::Barbarian */ { N_("Barbarian"), "warrior", SpellID::Rage }, - // clang-format on -}; +const PlayerCombatData &GetPlayerCombatDataForClass(HeroClass clazz) +{ + return PlayersCombatData[static_cast(clazz)]; +} + +const PlayerStartingLoadoutData &GetPlayerStartingLoadoutForClass(HeroClass clazz) +{ + return PlayersStartingLoadoutData[static_cast(clazz)]; +} /** Contains the data related to each player class. */ const PlayerSpriteData PlayersSpriteData[] = { // clang-format off -// HeroClass stand, walk, attack, bow, swHit, block, lightning, fire, magic, death - -// TRANSLATORS: Player Block -/* HeroClass::Warrior */ { 96, 96, 128, 96, 96, 96, 96, 96, 96, 128 }, -/* HeroClass::Rogue */ { 96, 96, 128, 128, 96, 96, 96, 96, 96, 128 }, -/* HeroClass::Sorcerer */ { 96, 96, 128, 128, 96, 96, 128, 128, 128, 128 }, -/* HeroClass::Monk */ { 112, 112, 130, 130, 98, 98, 114, 114, 114, 160 }, -/* HeroClass::Bard */ { 96, 96, 128, 128, 96, 96, 96, 96, 96, 128 }, -/* HeroClass::Barbarian */ { 96, 96, 128, 96, 96, 96, 96, 96, 96, 128 }, +// HeroClass classPath, stand, walk, attack, bow, swHit, block, lightning, fire, magic, death + +/* HeroClass::Warrior */ { "warrior", 96, 96, 128, 96, 96, 96, 96, 96, 96, 128 }, +/* HeroClass::Rogue */ { "rogue", 96, 96, 128, 128, 96, 96, 96, 96, 96, 128 }, +/* HeroClass::Sorcerer */ { "sorceror", 96, 96, 128, 128, 96, 96, 128, 128, 128, 128 }, +/* HeroClass::Monk */ { "monk", 112, 112, 130, 130, 98, 98, 114, 114, 114, 160 }, +/* HeroClass::Bard */ { "rogue", 96, 96, 128, 128, 96, 96, 96, 96, 96, 128 }, +/* HeroClass::Barbarian */ { "warrior", 96, 96, 128, 96, 96, 96, 96, 96, 96, 128 }, // clang-format on }; diff --git a/Source/playerdat.hpp b/Source/playerdat.hpp index 88ec37652b9..86bcd689d4d 100644 --- a/Source/playerdat.hpp +++ b/Source/playerdat.hpp @@ -7,16 +7,26 @@ #include -#include "player.h" -#include "textdat.h" +#include "effects.h" +#include "itemdat.h" +#include "spelldat.h" namespace devilution { +enum class HeroClass : uint8_t { + Warrior, + Rogue, + Sorcerer, + Monk, + Bard, + Barbarian, + + LAST = Barbarian +}; + struct PlayerData { /* Class Name */ const char *className; - /* Class Directory Path */ - const char *classPath; /* Class Skill */ SpellID skill; }; @@ -38,8 +48,6 @@ struct ClassAttributes { uint8_t maxDex; /* Class Maximum Vitality Stat */ uint8_t maxVit; - /* Class Block Bonus % */ - uint8_t blockBonus; /* Class Life Adjustment */ int16_t adjLife; /* Class Mana Adjustment */ @@ -60,7 +68,49 @@ struct ClassAttributes { const ClassAttributes &GetClassAttributes(HeroClass playerClass); +struct PlayerCombatData { + /* Class starting chance to Block (used as a %) */ + uint8_t baseToBlock; + /* Class starting chance to hit when using melee attacks (used as a %) */ + uint8_t baseMeleeToHit; + /* Class starting chance to hit when using ranged weapons (used as a %) */ + uint8_t baseRangedToHit; + /* Class starting chance to hit when using spells (used as a %) */ + uint8_t baseMagicToHit; +}; + +/** + * @brief Data used to set known skills and provide initial equipment when starting a new game + * + * Items will be created in order starting with item 1, 2, etc. If the item can be equipped it + * will be placed in the first available slot, otherwise if it fits on the belt it will be + * placed in the first free space, finally being placed in the first free inventory position. + * + * The active game mode at the time we're creating a new character controls the choice of item + * type. ItemType.hellfire is used if we're in Hellfire mode, ItemType.diablo otherwise. + */ +struct PlayerStartingLoadoutData { + /* Class Skill */ + SpellID skill; + /* Starting Spell (if any) */ + SpellID spell; + /* Initial level of the starting spell */ + uint8_t spellLevel; + + struct ItemType { + _item_indexes diablo; + _item_indexes hellfire; + }; + + std::array items; + + /* Initial gold amount, up to a single stack (5000 gold) */ + uint16_t gold; +}; + struct PlayerSpriteData { + /* Class Directory Path */ + const char *classPath; /* Sprite width: Stand */ uint8_t stand; /* Sprite width: Walk */ @@ -148,7 +198,9 @@ void LoadPlayerDataFiles(); extern const _sfx_id herosounds[enum_size::value][enum_size::value]; uint32_t GetNextExperienceThresholdForLevel(unsigned level); uint8_t GetMaximumCharacterLevel(); -extern const PlayerData PlayersData[]; +const PlayerData &GetPlayerDataForClass(HeroClass clazz); +const PlayerCombatData &GetPlayerCombatDataForClass(HeroClass clazz); +const PlayerStartingLoadoutData &GetPlayerStartingLoadoutForClass(HeroClass clazz); extern const PlayerSpriteData PlayersSpriteData[]; extern const PlayerAnimData PlayersAnimData[]; diff --git a/Source/qol/autopickup.cpp b/Source/qol/autopickup.cpp index c66ffd936bf..4b204c267b1 100644 --- a/Source/qol/autopickup.cpp +++ b/Source/qol/autopickup.cpp @@ -47,7 +47,7 @@ bool DoPickup(Item item) return true; if (item._itype == ItemType::Misc - && (AutoPlaceItemInInventory(*MyPlayer, item, false) || AutoPlaceItemInBelt(*MyPlayer, item, false))) { + && (AutoPlaceItemInInventory(*MyPlayer, item) || AutoPlaceItemInBelt(*MyPlayer, item))) { switch (item._iMiscId) { case IMISC_HEAL: return *sgOptions.Gameplay.numHealPotionPickup > NumMiscItemsInInv(item._iMiscId); diff --git a/Source/qol/stash.cpp b/Source/qol/stash.cpp index 1a8d39c57bc..906c27ca256 100644 --- a/Source/qol/stash.cpp +++ b/Source/qol/stash.cpp @@ -215,10 +215,10 @@ void CheckStashCut(Point cursorPosition, bool automaticMove) if (iv != StashStruct::EmptyCell) { holdItem = Stash.stashList[iv]; if (automaticMove) { - if (CanBePlacedOnBelt(holdItem)) { - automaticallyMoved = AutoPlaceItemInBelt(player, holdItem, true); + if (CanBePlacedOnBelt(player, holdItem)) { + automaticallyMoved = AutoPlaceItemInBelt(player, holdItem, true, true); } else { - automaticallyMoved = automaticallyEquipped = AutoEquip(player, holdItem); + automaticallyMoved = AutoEquip(player, holdItem, true, true); } } @@ -238,7 +238,7 @@ void CheckStashCut(Point cursorPosition, bool automaticMove) if (automaticMove) { if (!automaticallyMoved) { - if (CanBePlacedOnBelt(holdItem)) { + if (CanBePlacedOnBelt(player, holdItem)) { player.SaySpecific(HeroSpeech::IHaveNoRoom); } else { player.SaySpecific(HeroSpeech::ICantDoThat); diff --git a/Source/stores.cpp b/Source/stores.cpp index 015e7f3aa8e..aeb44c47bff 100644 --- a/Source/stores.cpp +++ b/Source/stores.cpp @@ -337,15 +337,15 @@ bool StoreAutoPlace(Item &item, bool persistItem) { Player &player = *MyPlayer; - if (AutoEquipEnabled(player, item) && AutoEquip(player, item, persistItem)) { + if (AutoEquipEnabled(player, item) && AutoEquip(player, item, persistItem, true)) { return true; } - if (AutoPlaceItemInBelt(player, item, persistItem)) { + if (AutoPlaceItemInBelt(player, item, persistItem, true)) { return true; } - return AutoPlaceItemInInventory(player, item, persistItem); + return AutoPlaceItemInInventory(player, item, persistItem, true); } void ScrollVendorStore(Item *itemData, int storeLimit, int idx, int selling = true) diff --git a/test/pack_test.cpp b/test/pack_test.cpp index 3cc70c4a9e5..432efca6de6 100644 --- a/test/pack_test.cpp +++ b/test/pack_test.cpp @@ -961,8 +961,10 @@ TEST_F(NetPackTest, UnPackNetPlayer_valid) TEST_F(NetPackTest, UnPackNetPlayer_invalid_class) { - MyPlayer->_pClass = static_cast(-1); - ASSERT_FALSE(TestNetPackValidation()); + PlayerNetPack packed; + PackNetPlayer(packed, *MyPlayer); + packed.pClass = std::numeric_limits::max(); + ASSERT_FALSE(UnPackNetPlayer(packed, Players[1])); } TEST_F(NetPackTest, UnPackNetPlayer_invalid_oob) @@ -1084,8 +1086,10 @@ TEST_F(NetPackTest, UnPackNetPlayer_invalid_damageMod) TEST_F(NetPackTest, UnPackNetPlayer_invalid_baseToBlk) { - MyPlayer->_pBaseToBlk++; - ASSERT_FALSE(TestNetPackValidation()); + PlayerNetPack packed; + PackNetPlayer(packed, *MyPlayer); + packed.pBaseToBlk++; + ASSERT_FALSE(UnPackNetPlayer(packed, Players[1])); } TEST_F(NetPackTest, UnPackNetPlayer_invalid_iMinDam) diff --git a/test/player_test.cpp b/test/player_test.cpp index 3301d0914a7..4e993406263 100644 --- a/test/player_test.cpp +++ b/test/player_test.cpp @@ -119,7 +119,7 @@ static void AssertPlayer(Player &player) ASSERT_EQ(player._pGold, 100); ASSERT_EQ(player._pMaxHPBase, 2880); ASSERT_EQ(player._pHPBase, 2880); - ASSERT_EQ(player._pBaseToBlk, 20); + ASSERT_EQ(player.getBaseToBlock(), 20); ASSERT_EQ(player._pMaxManaBase, 1440); ASSERT_EQ(player._pManaBase, 1440); ASSERT_EQ(player._pMemSpells, 0); @@ -158,8 +158,9 @@ static void AssertPlayer(Player &player) 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, 1); + ASSERT_EQ(player._pIMaxDam, 4); ASSERT_EQ(player._pIAC, 0); ASSERT_EQ(player._pIBonusDam, 0); ASSERT_EQ(player._pIBonusToHit, 0); diff --git a/test/writehero_test.cpp b/test/writehero_test.cpp index 5f6c7cc5105..8591aa26c36 100644 --- a/test/writehero_test.cpp +++ b/test/writehero_test.cpp @@ -285,7 +285,7 @@ void AssertPlayer(Player &player) ASSERT_EQ(player._pGold, 0); ASSERT_EQ(player._pMaxHPBase, 12864); ASSERT_EQ(player._pHPBase, 12864); - ASSERT_EQ(player._pBaseToBlk, 20); + ASSERT_EQ(player.getBaseToBlock(), 20); ASSERT_EQ(player._pMaxManaBase, 11104); ASSERT_EQ(player._pManaBase, 11104); ASSERT_EQ(player._pMemSpells, 66309357295);