From 2ae006da6bfc1616b9d794e9e8bcaeba0971c95c Mon Sep 17 00:00:00 2001 From: Eric Robinson Date: Thu, 26 Sep 2024 19:06:26 -0400 Subject: [PATCH] Major refactor --- Source/control.cpp | 2 +- Source/controls/game_controls.cpp | 8 +- Source/controls/plrctrls.cpp | 8 +- Source/controls/touch/event_handlers.cpp | 4 +- Source/controls/touch/renderers.cpp | 2 +- Source/diablo.cpp | 70 +-- Source/engine/render/scrollrt.cpp | 6 +- Source/help.cpp | 4 +- Source/inv.cpp | 2 +- Source/items.cpp | 90 +-- Source/loadsave.cpp | 12 +- Source/objects.cpp | 4 +- Source/qol/chatlog.cpp | 6 +- Source/qol/itemlabels.cpp | 6 +- Source/qol/stash.cpp | 2 +- Source/stores.cpp | 750 +++++++++++++---------- Source/stores.h | 316 ++-------- Source/towners.cpp | 18 +- Source/track.cpp | 2 +- 19 files changed, 598 insertions(+), 714 deletions(-) diff --git a/Source/control.cpp b/Source/control.cpp index 46896c81663..c807f46b677 100644 --- a/Source/control.cpp +++ b/Source/control.cpp @@ -686,7 +686,7 @@ bool IsLevelUpButtonVisible() if (ControlMode == ControlTypes::VirtualGamepad) { return false; } - if (Stores.IsPlayerInStore() || IsStashOpen) { + if (IsPlayerInStore() || IsStashOpen) { return false; } if (QuestLogIsOpen && GetLeftPanel().contains(GetMainPanel().position + Displacement { 0, -74 })) { diff --git a/Source/controls/game_controls.cpp b/Source/controls/game_controls.cpp index 8f6399380fd..8117a3212b7 100644 --- a/Source/controls/game_controls.cpp +++ b/Source/controls/game_controls.cpp @@ -134,7 +134,7 @@ bool GetGameAction(const SDL_Event &event, ControllerButtonEvent ctrlEvent, Game if (ControllerActionHeld == GameActionType_NONE) { ControllerActionHeld = GameActionType_PRIMARY_ACTION; } - } else if (sgpCurrentMenu != nullptr || Stores.IsPlayerInStore() || QuestLogIsOpen) { + } else if (sgpCurrentMenu != nullptr || IsPlayerInStore() || QuestLogIsOpen) { *action = GameActionSendKey { SDLK_RETURN, false }; } else { *action = GameActionSendKey { SDLK_SPACE, false }; @@ -171,12 +171,12 @@ bool GetGameAction(const SDL_Event &event, ControllerButtonEvent ctrlEvent, Game return true; } if (VirtualGamepadState.healthButton.isHeld && VirtualGamepadState.healthButton.didStateChange) { - if (!QuestLogIsOpen && !SpellbookFlag && !Stores.IsPlayerInStore()) + if (!QuestLogIsOpen && !SpellbookFlag && !IsPlayerInStore()) *action = GameAction(GameActionType_USE_HEALTH_POTION); return true; } if (VirtualGamepadState.manaButton.isHeld && VirtualGamepadState.manaButton.didStateChange) { - if (!QuestLogIsOpen && !SpellbookFlag && !Stores.IsPlayerInStore()) + if (!QuestLogIsOpen && !SpellbookFlag && !IsPlayerInStore()) *action = GameAction(GameActionType_USE_MANA_POTION); return true; } @@ -196,7 +196,7 @@ bool GetGameAction(const SDL_Event &event, ControllerButtonEvent ctrlEvent, Game SDL_Keycode translation = SDLK_UNKNOWN; - if (gmenu_is_active() || Stores.IsPlayerInStore()) + if (gmenu_is_active() || IsPlayerInStore()) translation = TranslateControllerButtonToGameMenuKey(ctrlEvent.button); else if (inGameMenu) translation = TranslateControllerButtonToMenuKey(ctrlEvent.button); diff --git a/Source/controls/plrctrls.cpp b/Source/controls/plrctrls.cpp index 310be126d30..8c65c969c4f 100644 --- a/Source/controls/plrctrls.cpp +++ b/Source/controls/plrctrls.cpp @@ -65,7 +65,7 @@ quest_id pcursquest = Q_INVALID; */ bool InGameMenu() { - return Stores.IsPlayerInStore() + return IsPlayerInStore() || HelpFlag || ChatLogFlag || ChatFlag @@ -1320,9 +1320,9 @@ void StoreMove(AxisDirection moveDir) static AxisDirectionRepeater repeater; moveDir = repeater.Get(moveDir); if (moveDir.y == AxisDirectionY_UP) - Stores.StoreUp(); + StoreUp(); else if (moveDir.y == AxisDirectionY_DOWN) - Stores.StoreDown(); + StoreDown(); } using HandleLeftStickOrDPadFn = void (*)(devilution::AxisDirection); @@ -1347,7 +1347,7 @@ HandleLeftStickOrDPadFn GetLeftStickOrDPadGameUIHandler() if (QuestLogIsOpen) { return &QuestLogMove; } - if (Stores.IsPlayerInStore()) { + if (IsPlayerInStore()) { return &StoreMove; } return nullptr; diff --git a/Source/controls/touch/event_handlers.cpp b/Source/controls/touch/event_handlers.cpp index d7973238dfd..7e93a129ed6 100644 --- a/Source/controls/touch/event_handlers.cpp +++ b/Source/controls/touch/event_handlers.cpp @@ -63,10 +63,10 @@ bool HandleGameMenuInteraction(const SDL_Event &event) bool HandleStoreInteraction(const SDL_Event &event) { - if (!Stores.IsPlayerInStore()) + if (!IsPlayerInStore()) return false; if (event.type == SDL_FINGERDOWN) - Stores.CheckStoreButton(); + CheckStoreButton(); return true; } diff --git a/Source/controls/touch/renderers.cpp b/Source/controls/touch/renderers.cpp index 1a0adc75428..378aac33a3b 100644 --- a/Source/controls/touch/renderers.cpp +++ b/Source/controls/touch/renderers.cpp @@ -430,7 +430,7 @@ VirtualGamepadButtonType PrimaryActionButtonRenderer::GetButtonType() VirtualGamepadButtonType PrimaryActionButtonRenderer::GetTownButtonType() { - if (Stores.IsPlayerInStore() || pcursmonst != -1) + if (IsPlayerInStore() || pcursmonst != -1) return GetTalkButtonType(virtualPadButton->isHeld); return GetBlankButtonType(virtualPadButton->isHeld); } diff --git a/Source/diablo.cpp b/Source/diablo.cpp index 13afadc2657..72bacfa4023 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -197,7 +197,7 @@ void FreeGame() FreeGMenu(); FreeQuestText(); FreeInfoBoxGfx(); - Stores.FreeStoreMem(); + FreeStoreMem(); for (Player &player : Players) ResetPlayerGFX(player); @@ -353,8 +353,8 @@ void LeftMouseDown(uint16_t modState) return; } - if (Stores.IsPlayerInStore()) { - Stores.CheckStoreButton(); + if (IsPlayerInStore()) { + CheckStoreButton(); return; } @@ -417,8 +417,8 @@ void LeftMouseUp(uint16_t modState) } if (LevelButtonDown) CheckLevelButtonUp(); - if (Stores.IsPlayerInStore()) - Stores.ReleaseStoreButton(); + if (IsPlayerInStore()) + ReleaseStoreButton(); } void RightMouseDown(bool isShiftHeld) @@ -439,7 +439,7 @@ void RightMouseDown(bool isShiftHeld) doom_close(); return; } - if (Stores.IsPlayerInStore()) + if (IsPlayerInStore()) return; if (SpellSelectFlag) { SetSpell(); @@ -576,8 +576,8 @@ void PressKey(SDL_Keycode vkey, uint16_t modState) if ((modState & KMOD_ALT) != 0) { sgOptions.Graphics.fullscreen.SetValue(!IsFullScreen()); SaveOptions(); - } else if (Stores.IsPlayerInStore()) { - Stores.StoreEnter(); + } else if (IsPlayerInStore()) { + StoreEnter(); } else if (QuestLogIsOpen) { QuestlogEnter(); } else { @@ -585,8 +585,8 @@ void PressKey(SDL_Keycode vkey, uint16_t modState) } return; case SDLK_UP: - if (Stores.IsPlayerInStore()) { - Stores.StoreUp(); + if (IsPlayerInStore()) { + StoreUp(); } else if (QuestLogIsOpen) { QuestlogUp(); } else if (HelpFlag) { @@ -600,8 +600,8 @@ void PressKey(SDL_Keycode vkey, uint16_t modState) } return; case SDLK_DOWN: - if (Stores.IsPlayerInStore()) { - Stores.StoreDown(); + if (IsPlayerInStore()) { + StoreDown(); } else if (QuestLogIsOpen) { QuestlogDown(); } else if (HelpFlag) { @@ -615,15 +615,15 @@ void PressKey(SDL_Keycode vkey, uint16_t modState) } return; case SDLK_PAGEUP: - if (Stores.IsPlayerInStore()) { - Stores.StorePrior(); + if (IsPlayerInStore()) { + StorePrior(); } else if (ChatLogFlag) { ChatLogScrollTop(); } return; case SDLK_PAGEDOWN: - if (Stores.IsPlayerInStore()) { - Stores.StoreNext(); + if (IsPlayerInStore()) { + StoreNext(); } else if (ChatLogFlag) { ChatLogScrollBottom(); } @@ -643,12 +643,12 @@ void PressKey(SDL_Keycode vkey, uint16_t modState) void HandleMouseButtonDown(Uint8 button, uint16_t modState) { - if (Stores.IsPlayerInStore() && (button == SDL_BUTTON_X1 + if (IsPlayerInStore() && (button == SDL_BUTTON_X1 #if !SDL_VERSION_ATLEAST(2, 0, 0) || button == 8 #endif )) { - Stores.StoreESC(); + StoreESC(); return; } @@ -752,8 +752,8 @@ void GameEventHandler(const SDL_Event &event, uint16_t modState) #if SDL_VERSION_ATLEAST(2, 0, 0) case SDL_MOUSEWHEEL: if (event.wheel.y > 0) { // Up - if (Stores.IsPlayerInStore()) { - Stores.StoreUp(); + if (IsPlayerInStore()) { + StoreUp(); } else if (QuestLogIsOpen) { QuestlogUp(); } else if (HelpFlag) { @@ -766,8 +766,8 @@ void GameEventHandler(const SDL_Event &event, uint16_t modState) sgOptions.Keymapper.KeyPressed(MouseScrollUpButton); } } else if (event.wheel.y < 0) { // down - if (Stores.IsPlayerInStore()) { - Stores.StoreDown(); + if (IsPlayerInStore()) { + StoreDown(); } else if (QuestLogIsOpen) { QuestlogDown(); } else if (HelpFlag) { @@ -1492,7 +1492,7 @@ void HelpKeyPressed() { if (HelpFlag) { HelpFlag = false; - } else if (Stores.IsPlayerInStore()) { + } else if (IsPlayerInStore()) { InfoString = StringOrView {}; AddInfoBoxString(_("No help available")); /// BUGFIX: message isn't displayed AddInfoBoxString(_("while in stores")); @@ -1516,7 +1516,7 @@ void HelpKeyPressed() void InventoryKeyPressed() { - if (Stores.IsPlayerInStore()) + if (IsPlayerInStore()) return; invflag = !invflag; if (!IsLeftPanelOpen() && CanPanelsCoverView()) { @@ -1537,7 +1537,7 @@ void InventoryKeyPressed() void CharacterSheetKeyPressed() { - if (Stores.IsPlayerInStore()) + if (IsPlayerInStore()) return; if (!IsRightPanelOpen() && CanPanelsCoverView()) { if (CharFlag) { // We are closing the character sheet @@ -1555,7 +1555,7 @@ void CharacterSheetKeyPressed() void QuestLogKeyPressed() { - if (Stores.IsPlayerInStore()) + if (IsPlayerInStore()) return; if (!QuestLogIsOpen) { StartQuestlog(); @@ -1580,7 +1580,7 @@ void QuestLogKeyPressed() void DisplaySpellsKeyPressed() { - if (Stores.IsPlayerInStore()) + if (IsPlayerInStore()) return; CloseCharPanel(); QuestLogIsOpen = false; @@ -1596,7 +1596,7 @@ void DisplaySpellsKeyPressed() void SpellBookKeyPressed() { - if (Stores.IsPlayerInStore()) + if (IsPlayerInStore()) return; SpellbookFlag = !SpellbookFlag; if (!IsLeftPanelOpen() && CanPanelsCoverView()) { @@ -1761,7 +1761,7 @@ void InitKeymapActions() SDLK_F3, [] { gamemenu_load_game(false); }, nullptr, - [&]() { return !gbIsMultiplayer && gbValidSaveFile && !Stores.IsPlayerInStore() && IsGameRunning(); }); + [&]() { return !gbIsMultiplayer && gbValidSaveFile && !IsPlayerInStore() && IsGameRunning(); }); #ifndef NOEXIT sgOptions.Keymapper.AddAction( "QuitGame", @@ -2328,7 +2328,7 @@ void InitPadmapActions() ControllerButton_NONE, [] { gamemenu_load_game(false); }, nullptr, - [&]() { return !gbIsMultiplayer && gbValidSaveFile && !Stores.IsPlayerInStore() && IsGameRunning(); }); + [&]() { return !gbIsMultiplayer && gbValidSaveFile && !IsPlayerInStore() && IsGameRunning(); }); sgOptions.Padmapper.AddAction( "Item Highlighting", N_("Item highlighting"), @@ -2778,8 +2778,8 @@ bool PressEscKey() rv = true; } - if (Stores.IsPlayerInStore()) { - Stores.StoreESC(); + if (IsPlayerInStore()) { + StoreESC(); rv = true; } @@ -2873,7 +2873,7 @@ void LoadGameLevel(bool firstflag, lvl_entry lvldir) InitInfoBoxGfx(); InitHelp(); } - Stores.InitStores(); + InitStores(); InitAutomapOnce(); } if (!setlevel) { @@ -2886,9 +2886,9 @@ void LoadGameLevel(bool firstflag, lvl_entry lvldir) } if (leveltype == DTYPE_TOWN) { - Stores.SetupTownStores(); + SetupTownStores(); } else { - Stores.FreeStoreMem(); + FreeStoreMem(); } if (firstflag || lvldir == ENTRY_LOAD) { diff --git a/Source/engine/render/scrollrt.cpp b/Source/engine/render/scrollrt.cpp index daa96d1a857..1bb93629b7c 100644 --- a/Source/engine/render/scrollrt.cpp +++ b/Source/engine/render/scrollrt.cpp @@ -613,7 +613,7 @@ void DrawItem(const Surface &out, int8_t itemIndex, Point targetBufferPosition, const Item &item = Items[itemIndex]; const ClxSprite sprite = item.AnimInfo.currentSprite(); const Point position = targetBufferPosition + item.getRenderingOffset(sprite); - if (!Stores.IsPlayerInStore() && (itemIndex == pcursitem || AutoMapShowItems)) { + if (!IsPlayerInStore() && (itemIndex == pcursitem || AutoMapShowItems)) { ClxDrawOutlineSkipColorZero(out, GetOutlineColor(item, false), position, sprite); } ClxDrawLight(out, position, sprite, lightTableIndex); @@ -1197,8 +1197,8 @@ void DrawView(const Surface &out, Point startPosition) DrawMonsterHealthBar(out); DrawFloatingNumbers(out, startPosition, offset); - if (Stores.IsPlayerInStore() && !qtextflag) - Stores.DrawSText(out); + if (IsPlayerInStore() && !qtextflag) + DrawSText(out); if (invflag) { DrawInv(out); } else if (SpellbookFlag) { diff --git a/Source/help.cpp b/Source/help.cpp index c62aa00eb57..b9ecaf318fe 100644 --- a/Source/help.cpp +++ b/Source/help.cpp @@ -189,7 +189,7 @@ void InitHelp() void DrawHelp(const Surface &out) { - Stores.DrawSTextHelp(); + DrawSTextHelp(); DrawQTextBack(out); const int lineHeight = LineHeight(); @@ -210,7 +210,7 @@ void DrawHelp(const Surface &out) { .flags = UiFlags::ColorWhitegold | UiFlags::AlignCenter }); const int titleBottom = sy + HeaderHeight(); - Stores.DrawSLine(out, titleBottom); + DrawSLine(out, titleBottom); const int numLines = NumVisibleLines(); const int contentY = titleBottom + DividerLineMarginY() + ContentPaddingY(); diff --git a/Source/inv.cpp b/Source/inv.cpp index e4af6a92405..fd35f679358 100644 --- a/Source/inv.cpp +++ b/Source/inv.cpp @@ -2007,7 +2007,7 @@ bool UseInvItem(int cii) return true; if (pcurs != CURSOR_HAND) return true; - if (Stores.IsPlayerInStore()) + if (IsPlayerInStore()) return true; if (cii < INVITEM_INV_FIRST) return false; diff --git a/Source/items.cpp b/Source/items.cpp index 63577fdec5f..b5c89afb554 100644 --- a/Source/items.cpp +++ b/Source/items.cpp @@ -321,7 +321,7 @@ SfxID ItemDropSnds[] = { SfxID::ItemLeatherFlip, }; /** Maps from Griswold premium item number to a quality level delta as added to the base quality level. */ -int premiumlvladd[] = { +int itemLevelAdd[] = { // clang-format off -1, -1, @@ -332,7 +332,7 @@ int premiumlvladd[] = { // clang-format on }; /** Maps from Griswold premium item number to a quality level delta as added to the base quality level. */ -int premiumLvlAddHellfire[] = { +int itemLevelAddHf[] = { // clang-format off -1, -1, @@ -4388,7 +4388,7 @@ void SpawnSmith(int lvl) int iCnt = RandomIntBetween(10, maxItems); for (int i = 0; i < iCnt; i++) { - Item &newItem = Stores.smithItems[i]; + Item &newItem = Blacksmith.basicItems[i]; do { newItem = {}; @@ -4401,41 +4401,41 @@ void SpawnSmith(int lvl) newItem._iCreateInfo = lvl | CF_SMITH; newItem._iIdentified = true; } - for (int i = iCnt; i < NumSmithItems; i++) - Stores.smithItems[i].clear(); + for (int i = iCnt; i < NumSmithItemsHf; i++) + Blacksmith.basicItems[i].clear(); - SortVendor(Stores.smithItems, PinnedItemCount); + SortVendor(Blacksmith.basicItems, PinnedItemCount); } void SpawnPremium(const Player &player) { int lvl = player.getCharacterLevel(); - int maxItems = gbIsHellfire ? NumSmithPremiumItems : 6; - if (Stores.premiumItemCount < maxItems) { + int maxItems = gbIsHellfire ? NumSmithItemsHf : NumSmithItems; + if (Blacksmith.itemCount < maxItems) { for (int i = 0; i < maxItems; i++) { - if (Stores.premiumItems[i].isEmpty()) { - int plvl = Stores.premiumItemLevel + (gbIsHellfire ? premiumLvlAddHellfire[i] : premiumlvladd[i]); - SpawnOnePremium(Stores.premiumItems[i], plvl, player); + if (Blacksmith.items[i].isEmpty()) { + int plvl = Blacksmith.itemLevel + (gbIsHellfire ? itemLevelAddHf[i] : itemLevelAdd[i]); + SpawnOnePremium(Blacksmith.items[i], plvl, player); } } - Stores.premiumItemCount = maxItems; + Blacksmith.itemCount = maxItems; } - while (Stores.premiumItemLevel < lvl) { - Stores.premiumItemLevel++; + while (Blacksmith.itemLevel < lvl) { + Blacksmith.itemLevel++; if (gbIsHellfire) { // Discard first 3 items and shift next 10 - std::move(&Stores.premiumItems[3], &Stores.premiumItems[12] + 1, &Stores.premiumItems[0]); - SpawnOnePremium(Stores.premiumItems[10], Stores.premiumItemLevel + premiumLvlAddHellfire[10], player); - Stores.premiumItems[11] = Stores.premiumItems[13]; - SpawnOnePremium(Stores.premiumItems[12], Stores.premiumItemLevel + premiumLvlAddHellfire[12], player); - Stores.premiumItems[13] = Stores.premiumItems[14]; - SpawnOnePremium(Stores.premiumItems[14], Stores.premiumItemLevel + premiumLvlAddHellfire[14], player); + std::move(&Blacksmith.items[3], &Blacksmith.items[12] + 1, &Blacksmith.items[0]); + SpawnOnePremium(Blacksmith.items[10], Blacksmith.itemLevel + itemLevelAddHf[10], player); + Blacksmith.items[11] = Blacksmith.items[13]; + SpawnOnePremium(Blacksmith.items[12], Blacksmith.itemLevel + itemLevelAddHf[12], player); + Blacksmith.items[13] = Blacksmith.items[14]; + SpawnOnePremium(Blacksmith.items[14], Blacksmith.itemLevel + itemLevelAddHf[14], player); } else { // Discard first 2 items and shift next 3 - std::move(&Stores.premiumItems[2], &Stores.premiumItems[4] + 1, &Stores.premiumItems[0]); - SpawnOnePremium(Stores.premiumItems[3], Stores.premiumItemLevel + premiumlvladd[3], player); - Stores.premiumItems[4] = Stores.premiumItems[5]; - SpawnOnePremium(Stores.premiumItems[5], Stores.premiumItemLevel + premiumlvladd[5], player); + std::move(&Blacksmith.items[2], &Blacksmith.items[4] + 1, &Blacksmith.items[0]); + SpawnOnePremium(Blacksmith.items[3], Blacksmith.itemLevel + itemLevelAdd[3], player); + Blacksmith.items[4] = Blacksmith.items[5]; + SpawnOnePremium(Blacksmith.items[5], Blacksmith.itemLevel + itemLevelAdd[5], player); } } } @@ -4453,7 +4453,7 @@ void SpawnWitch(int lvl) const int maxValue = gbIsHellfire ? 200000 : 140000; for (int i = 0; i < NumWitchItems; i++) { - Item &item = Stores.witchItems[i]; + Item &item = Witch.items[i]; item = {}; if (i < PinnedItemCount) { @@ -4504,7 +4504,7 @@ void SpawnWitch(int lvl) item._iIdentified = true; } - SortVendor(Stores.witchItems, PinnedItemCount); + SortVendor(Witch.items, PinnedItemCount); } void SpawnBoy(int lvl) @@ -4523,19 +4523,19 @@ void SpawnBoy(int lvl) dexterity += dexterity / 5; magic += magic / 5; - if (Stores.boyItemLevel >= (lvl / 2) && !Stores.boyItem.isEmpty()) + if (Boy.itemLevel >= (lvl / 2) && !Boy.items[0].isEmpty()) // FIXME: Make this work properly with vectors return; do { keepgoing = false; - Stores.boyItem = {}; - Stores.boyItem._iSeed = AdvanceRndSeed(); - SetRndSeed(Stores.boyItem._iSeed); + Boy.items[0] = {}; + Boy.items[0]._iSeed = AdvanceRndSeed(); + SetRndSeed(Boy.items[0]._iSeed); _item_indexes itype = RndBoyItem(*MyPlayer, lvl); - GetItemAttrs(Stores.boyItem, itype, lvl); - GetItemBonus(*MyPlayer, Stores.boyItem, lvl, 2 * lvl, true, true); + GetItemAttrs(Boy.items[0], itype, lvl); + GetItemBonus(*MyPlayer, Boy.items[0], lvl, 2 * lvl, true, true); if (!gbIsHellfire) { - if (Stores.boyItem._iIvalue > 90000) { + if (Boy.items[0]._iIvalue > 90000) { keepgoing = true; // prevent breaking the do/while loop too early by failing hellfire's condition in while continue; } @@ -4544,7 +4544,7 @@ void SpawnBoy(int lvl) ivalue = 0; - ItemType itemType = Stores.boyItem._itype; + ItemType itemType = Boy.items[0]._itype; switch (itemType) { case ItemType::LightArmor: @@ -4610,15 +4610,15 @@ void SpawnBoy(int lvl) } } while (keepgoing || (( - Stores.boyItem._iIvalue > 200000 - || Stores.boyItem._iMinStr > strength - || Stores.boyItem._iMinMag > magic - || Stores.boyItem._iMinDex > dexterity - || Stores.boyItem._iIvalue < ivalue) + Boy.items[0]._iIvalue > 200000 + || Boy.items[0]._iMinStr > strength + || Boy.items[0]._iMinMag > magic + || Boy.items[0]._iMinDex > dexterity + || Boy.items[0]._iIvalue < ivalue) && count < 250)); - Stores.boyItem._iCreateInfo = lvl | CF_BOY; - Stores.boyItem._iIdentified = true; - Stores.boyItemLevel = lvl / 2; + Boy.items[0]._iCreateInfo = lvl | CF_BOY; + Boy.items[0]._iIdentified = true; + Boy.itemLevel = lvl / 2; } void SpawnHealer(int lvl) @@ -4627,8 +4627,8 @@ void SpawnHealer(int lvl) constexpr std::array<_item_indexes, PinnedItemCount + 1> PinnedItemTypes = { IDI_HEAL, IDI_FULLHEAL, IDI_RESURRECT }; const auto itemCount = static_cast(RandomIntBetween(10, gbIsHellfire ? 19 : 17)); - for (size_t i = 0; i < sizeof(Stores.healerItems) / sizeof(Stores.healerItems[0]); ++i) { - Item &item = Stores.healerItems[i]; + for (size_t i = 0; i < sizeof(Healer.items) / sizeof(Healer.items[0]); ++i) { + Item &item = Healer.items[i]; item = {}; if (i < PinnedItemCount || (gbIsMultiplayer && i == PinnedItemCount)) { @@ -4652,7 +4652,7 @@ void SpawnHealer(int lvl) item._iIdentified = true; } - SortVendor(Stores.healerItems, PinnedItemCount); + SortVendor(Healer.items, PinnedItemCount); } void MakeGoldStack(Item &goldItem, int value) diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index 97c7770a7e0..15e498dd72f 100644 --- a/Source/loadsave.cpp +++ b/Source/loadsave.cpp @@ -867,7 +867,7 @@ void LoadItem(LoadHelper &file, Item &item) void LoadPremium(LoadHelper &file, int i) { - LoadAndValidateItemData(file, Stores.premiumItems[i]); + LoadAndValidateItemData(file, Blacksmith.items[i]); } void LoadQuest(LoadHelper *file, int i) @@ -2523,8 +2523,8 @@ void LoadGame(bool firstflag) memset(dLight, 0, sizeof(dLight)); } - Stores.premiumItemCount = file.NextBE(); - Stores.premiumItemLevel = file.NextBE(); + Blacksmith.itemCount = file.NextBE(); + Blacksmith.itemLevel = file.NextBE(); for (int i = 0; i < giNumberOfSmithPremiumItems; i++) LoadPremium(file, i); @@ -2786,11 +2786,11 @@ void SaveGameData(SaveWriter &saveWriter) } } - file.WriteBE(Stores.premiumItemCount); - file.WriteBE(Stores.premiumItemLevel); + file.WriteBE(Blacksmith.itemCount); + file.WriteBE(Blacksmith.itemLevel); for (int i = 0; i < giNumberOfSmithPremiumItems; i++) - SaveItem(file, Stores.premiumItems[i]); + SaveItem(file, Blacksmith.items[i]); file.WriteLE(AutomapActive ? 1 : 0); file.WriteBE(AutoMapScale); diff --git a/Source/objects.cpp b/Source/objects.cpp index 237add5a786..77234d98281 100644 --- a/Source/objects.cpp +++ b/Source/objects.cpp @@ -2857,7 +2857,7 @@ void OperateShrineMendicant(Player &player) int gold = player._pGold / 2; player.addExperience(gold); - Stores.TakePlrsMoney(gold); + TakePlrsMoney(gold); RedrawEverything(); @@ -2972,7 +2972,7 @@ void OperateShrineMurphys(DiabloGenerator &rng, Player &player) } } if (!broke) { - Stores.TakePlrsMoney(player._pGold / 3); + TakePlrsMoney(player._pGold / 3); } InitDiabloMsg(EMSG_SHRINE_MURPHYS); diff --git a/Source/qol/chatlog.cpp b/Source/qol/chatlog.cpp index aafd72cff01..4146c006685 100644 --- a/Source/qol/chatlog.cpp +++ b/Source/qol/chatlog.cpp @@ -96,7 +96,7 @@ void ToggleChatLog() if (ChatLogFlag) { ChatLogFlag = false; } else { - Stores.ExitStore(); + ExitStore(); CloseInventory(); CloseCharPanel(); SpellbookFlag = false; @@ -153,7 +153,7 @@ void AddMessageToChatLog(std::string_view message, Player *player, UiFlags flags void DrawChatLog(const Surface &out) { - Stores.DrawSTextHelp(); + DrawSTextHelp(); DrawQTextBack(out); if (SkipLines == 0) { @@ -179,7 +179,7 @@ void DrawChatLog(const Surface &out) } const int titleBottom = sy + HeaderHeight(); - Stores.DrawSLine(out, titleBottom); + DrawSLine(out, titleBottom); const int numLines = NumVisibleLines(); const int contentY = titleBottom + DividerLineMarginY() + ContentPaddingY(); diff --git a/Source/qol/itemlabels.cpp b/Source/qol/itemlabels.cpp index 263070e2685..f02ba3ca24f 100644 --- a/Source/qol/itemlabels.cpp +++ b/Source/qol/itemlabels.cpp @@ -98,7 +98,7 @@ void ResetItemlabelHighlighted() bool IsHighlightingLabelsEnabled() { - return !Stores.IsPlayerInStore() && highlightKeyPressed != *sgOptions.Gameplay.showItemLabels; + return !IsPlayerInStore() && highlightKeyPressed != *sgOptions.Gameplay.showItemLabels; } void AddItemToLabelQueue(int id, Point position) @@ -193,7 +193,7 @@ void DrawItemNameLabels(const Surface &out) if (!gmenu_is_active() && PauseMode == 0 && !MyPlayerIsDead - && !Stores.IsPlayerInStore() + && !IsPlayerInStore() && IsMouseOverGameArea() && LastMouseButtonAction == MouseActionType::None) { isLabelHighlighted = true; @@ -201,7 +201,7 @@ void DrawItemNameLabels(const Surface &out) pcursitem = label.id; } } - if (pcursitem == label.id && !Stores.IsPlayerInStore()) + if (pcursitem == label.id && !IsPlayerInStore()) FillRect(clippedOut, label.pos.x, label.pos.y, label.width, labelHeight, PAL8_BLUE + 6); else DrawHalfTransparentRectTo(clippedOut, label.pos.x, label.pos.y, label.width, labelHeight); diff --git a/Source/qol/stash.cpp b/Source/qol/stash.cpp index 3d2c0567d31..71274a97d67 100644 --- a/Source/qol/stash.cpp +++ b/Source/qol/stash.cpp @@ -462,7 +462,7 @@ bool UseStashItem(uint16_t c) return true; if (pcurs != CURSOR_HAND) return true; - if (Stores.IsPlayerInStore()) + if (IsPlayerInStore()) return true; Item *item = &Stash.stashList[c]; diff --git a/Source/stores.cpp b/Source/stores.cpp index 27718087632..0910fb490a3 100644 --- a/Source/stores.cpp +++ b/Source/stores.cpp @@ -28,6 +28,20 @@ namespace devilution { +TownerStore Blacksmith; +TownerStore Healer; +TownerStore Witch; +TownerStore Boy; +TownerStore Storyteller; + +TalkID activeStore; + +int currentItemIndex; +std::array playerItemIndexes; +std::vector playerItems; + +namespace { + constexpr int PaddingTop = 32; const int singleLineSpace = 1; @@ -39,7 +53,64 @@ constexpr int BuySellMenuDividerLine = 3; constexpr int BuyLineSpace = 4; constexpr int WirtDialogueDrawLine = 12; -constexpr int NumWirtDialogueLines = 3; + +_talker_id townerId; // The current towner being interacted with + +bool isTextFullSize; // Is the current dialog full size +int numTextLines; // Number of text lines in the current dialog +int oldTextLine; // Remember currently selected text line from textLine while displaying a dialog +int currentTextLine; // Currently selected text line from textLine + +struct STextStruct { + enum Type : uint8_t { + Label, + Divider, + Selectable, + }; + + std::string text; + int _sval; + int y; + UiFlags flags; + Type type; + uint8_t _sx; + uint8_t _syoff; + int cursId; + bool cursIndent; + + [[nodiscard]] bool isDivider() const + { + return type == Divider; + } + [[nodiscard]] bool isSelectable() const + { + return type == Selectable; + } + + [[nodiscard]] bool hasText() const + { + return !text.empty(); + } +}; + +std::array textLine; // Text lines + +bool renderGold; // Whether to render the player's gold amount in the top left + +bool hasScrollbar; // Does the current panel have a scrollbar +int oldScrollPos; // Remember last scroll position +int scrollPos; // Scroll position +int nextScrollPos; // Next scroll position +int previousScrollPos; // Previous scroll position +int8_t countdownScrollUp; // Countdown for the push state of the scroll up button +int8_t countdownScrollDown; // Countdown for the push state of the scroll down button + +TalkID oldActiveStore; // Remember current store while displaying a dialog + +Item tempItem; // Temporary item used to hold the item being traded + +std::array storeLineMapping; +int currentMenuDrawLine; const std::string SmithMenuHeader = "Welcome to the\n\nBlacksmith's shop"; @@ -123,32 +194,42 @@ const TownerLine TownerLines[] = { {}, }; -StoreSession Stores; - -StoreSession::StoreSession() - : activeStore(TalkID::None) - , currentItemIndex(0) - , premiumItemCount(0) - , premiumItemLevel(0) - , boyItemLevel(0) - , townerId(TOWN_SMITH) - , isTextFullSize(false) - , numTextLines(0) - , oldTextLine(0) - , currentTextLine(0) - , renderGold(false) - , hasScrollbar(false) - , oldScrollPos(0) - , scrollPos(0) - , nextScrollPos(0) - , previousScrollPos(0) - , countdownScrollUp(0) - , countdownScrollDown(0) - , oldActiveStore(TalkID::None) -{ -} - -void StoreSession::CalculateLineHeights() +// For most languages, line height is always 12. +// This includes blank lines and divider line. +constexpr int SmallLineHeight = 12; +constexpr int SmallTextHeight = 12; + +// For larger small fonts (Chinese and Japanese), text lines are +// taller and overflow. +// We space out blank lines a bit more to give space to 3-line store items. +constexpr int LargeLineHeight = SmallLineHeight + 1; +constexpr int LargeTextHeight = 18; + +/** + * The line index with the Back / Leave button. + * This is a special button that is always the last line. + * + * For lists with a scrollbar, it is not selectable (mouse-only). + */ +int BackButtonLine() +{ + if (IsSmallFontTall()) { + return hasScrollbar ? 21 : 20; + } + return 22; +} + +int LineHeight() +{ + return IsSmallFontTall() ? LargeLineHeight : SmallLineHeight; +} + +int TextHeight() +{ + return IsSmallFontTall() ? LargeTextHeight : SmallTextHeight; +} + +void CalculateLineHeights() { textLine[0].y = 0; if (IsSmallFontTall()) { @@ -167,14 +248,14 @@ void StoreSession::CalculateLineHeights() } } -void StoreSession::DrawTextUI(const Surface &out) +void DrawTextUI(const Surface &out) { const Point uiPosition = GetUIRectangle().position; ClxDraw(out, { uiPosition.x + 320 + 24, 327 + uiPosition.y }, (*pSTextBoxCels)[0]); DrawHalfTransparentRectTo(out, uiPosition.x + 347, uiPosition.y + 28, 265, 297); } -void StoreSession::DrawScrollbar(const Surface &out, int y1, int y2) const +void DrawScrollbar(const Surface &out, int y1, int y2) { const Point uiPosition = GetUIRectangle().position; int yd1 = y1 * 12 + 44 + uiPosition.y; @@ -203,7 +284,7 @@ void StoreSession::DrawScrollbar(const Surface &out, int y1, int y2) const ClxDraw(out, { uiPosition.x + 601, (y1 + 1) * 12 + 44 + uiPosition.y + yd3 }, (*pSTextSlidCels)[12]); } -void StoreSession::SetLineAsDivider(size_t y) +void SetLineAsDivider(size_t y) { textLine[y]._sx = 0; textLine[y]._syoff = 0; @@ -214,7 +295,12 @@ void StoreSession::SetLineAsDivider(size_t y) textLine[y].cursIndent = false; } -void StoreSession::SetLineText(uint8_t x, size_t y, std::string_view text, UiFlags flags, bool sel, int cursId /*= -1*/, bool cursIndent /*= false*/) +void SetLineValue(size_t y, int val) +{ + textLine[y]._sval = val; +} + +void SetLineText(uint8_t x, size_t y, std::string_view text, UiFlags flags, bool sel, int cursId = -1, bool cursIndent = false) { textLine[y]._sx = x; textLine[y]._syoff = 0; @@ -226,14 +312,14 @@ void StoreSession::SetLineText(uint8_t x, size_t y, std::string_view text, UiFla textLine[y].cursIndent = cursIndent; } -void StoreSession::SetLineAsOptionsBackButton() +void SetLineAsOptionsBackButton() { const int line = BackButtonLine(); SetLineText(0, line, _("Back"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); textLine[line]._syoff = IsSmallFontTall() ? 0 : 6; } -void StoreSession::AddItemListBackButton(TalkID talkId, bool selectable /*= false*/) +void AddItemListBackButton(TalkID talkId, bool selectable = false) { const int line = BackButtonLine(); std::string_view text = talkId == TalkID::BoyBuy ? _("Leave") : _("Back"); @@ -246,7 +332,7 @@ void StoreSession::AddItemListBackButton(TalkID talkId, bool selectable /*= fals } } -void StoreSession::PrintStoreItem(const Item &item, int l, UiFlags flags, bool cursIndent /*= false*/) +void PrintStoreItem(const Item &item, int l, UiFlags flags, bool cursIndent = false) { std::string productLine; @@ -302,7 +388,7 @@ void StoreSession::PrintStoreItem(const Item &item, int l, UiFlags flags, bool c SetLineText(40, l++, productLine, flags, false, -1, cursIndent); } -bool StoreSession::GiveItemToPlayer(Item &item, bool persistItem) +bool GiveItemToPlayer(Item &item, bool persistItem) { if (AutoEquipEnabled(*MyPlayer, item) && AutoEquip(*MyPlayer, item, persistItem, true)) { @@ -316,7 +402,7 @@ bool StoreSession::GiveItemToPlayer(Item &item, bool persistItem) return AutoPlaceItemInInventory(*MyPlayer, item, persistItem, true); } -void StoreSession::SetupErrorScreen(TalkID talkId) +void SetupErrorScreen(TalkID talkId) { StartStore(oldActiveStore); hasScrollbar = false; @@ -337,7 +423,7 @@ void StoreSession::SetupErrorScreen(TalkID talkId) SetLineText(0, 14, text, UiFlags::ColorWhite | UiFlags::AlignCenter, true); } -void StoreSession::SetupConfirmScreen(Item &item) +void SetupConfirmScreen(Item &item) { StartStore(oldActiveStore); hasScrollbar = false; @@ -381,7 +467,7 @@ void StoreSession::SetupConfirmScreen(Item &item) SetLineText(0, 20, _("No"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); } -void StoreSession::SetupGossipScreen() +void SetupGossipScreen() { int la; @@ -426,7 +512,7 @@ void StoreSession::SetupGossipScreen() SetLineAsOptionsBackButton(); } -void StoreSession::SetMenuHeader(const std::string &header) +void SetMenuHeader(const std::string &header) { // Check if the header contains "\n\n", which indicates a two-line header std::string::size_type pos = header.find("\n\n"); @@ -445,7 +531,7 @@ void StoreSession::SetMenuHeader(const std::string &header) } } -void StoreSession::SetMenuText(const TownerLine &townerInfo) +void SetMenuText(const TownerLine &townerInfo) { const UiFlags flags = UiFlags::ColorWhitegold | UiFlags::AlignCenter; @@ -455,7 +541,7 @@ void StoreSession::SetMenuText(const TownerLine &townerInfo) currentMenuDrawLine = townerInfo.numOptions > 5 ? startLine + singleLineSpace : startLine + tripleLineSpace; SetLineText(0, currentMenuDrawLine, _("Would you like to:"), flags, false); currentMenuDrawLine += tripleLineSpace; - } else if (!boyItem.isEmpty()) { + } else if (!Boy.items[0].isEmpty()) { currentMenuDrawLine = WirtDialogueDrawLine; SetLineText(0, currentMenuDrawLine, _("I have something for sale,"), flags, false); currentMenuDrawLine += doubleLineSpace; @@ -468,7 +554,7 @@ void StoreSession::SetMenuText(const TownerLine &townerInfo) } } -void StoreSession::SetMenuOption(TalkID action, const std::string_view &text) +void SetMenuOption(TalkID action, const std::string_view &text) { UiFlags flags = action == TalkID::Gossip ? UiFlags::ColorBlue | UiFlags::AlignCenter : UiFlags::ColorWhite | UiFlags::AlignCenter; @@ -481,12 +567,53 @@ void StoreSession::SetMenuOption(TalkID action, const std::string_view &text) storeLineMapping[currentMenuDrawLine] = action; currentMenuDrawLine += 2; - if (townerId == TOWN_PEGBOY && !boyItem.isEmpty() && currentMenuDrawLine == (WirtDialogueDrawLine - doubleLineSpace)) { + if (townerId == TOWN_PEGBOY && !Boy.items[0].isEmpty() && currentMenuDrawLine == (WirtDialogueDrawLine - doubleLineSpace)) { currentMenuDrawLine = WirtDialogueDrawLine + (doubleLineSpace * 3); } } -void StoreSession::SetupTownerMenuScreen() +void RestoreResource() +{ + int *resource = nullptr; + int *maxResource = nullptr; + int *baseResource = nullptr; + int *baseMaxResource = nullptr; + SfxID sfx; + PanelDrawComponent component; + + switch (townerId) { + case TOWN_HEALER: + resource = &MyPlayer->_pHitPoints; + maxResource = &MyPlayer->_pMaxHP; + baseResource = &MyPlayer->_pHPBase; + baseMaxResource = &MyPlayer->_pMaxHPBase; + component = PanelDrawComponent::Health; + sfx = SfxID::CastHealing; + break; + case TOWN_WITCH: + if (!*sgOptions.Gameplay.adriaRefillsMana) + return; + resource = &MyPlayer->_pMana; + maxResource = &MyPlayer->_pMaxMana; + baseResource = &MyPlayer->_pManaBase; + baseMaxResource = &MyPlayer->_pMaxManaBase; + component = PanelDrawComponent::Mana; + sfx = SfxID::CastHealing; + break; + default: + return; + } + + if (*resource == *maxResource) + return; + + PlaySFX(sfx); + *resource = *maxResource; + *baseResource = *baseMaxResource; + RedrawComponent(component); +} + +void SetupTownerMenuScreen() { RestoreResource(); @@ -503,13 +630,13 @@ void StoreSession::SetupTownerMenuScreen() for (size_t i = 0; i < lines.numOptions; i++) { const StoreMenuOption &option = lines.menuOptions[i]; - if (option.action == TalkID::BoyBuy && boyItem.isEmpty()) + if (option.action == TalkID::BoyBuy && Boy.items[0].isEmpty()) continue; SetMenuOption(option.action, option.text); } } -void StoreSession::BuildPlayerItemsVector() +void BuildPlayerItemsVector() { playerItems.clear(); @@ -535,7 +662,100 @@ void StoreSession::BuildPlayerItemsVector() } } -void StoreSession::FilterPlayerItemsForAction(TalkID talkId) +void FilterSellableItems(TalkID talkId) +{ + playerItems.erase(std::remove_if(playerItems.begin(), playerItems.end(), + [talkId](const IndexedItem &indexedItem) { + Item *pI = indexedItem.itemPtr; + + // Common conditions for both Smith and Witch + if (pI->isEmpty() || pI->_itype == ItemType::Gold || pI->_iClass == ICLASS_QUEST || pI->IDidx == IDI_LAZSTAFF) + return true; // Remove this item + + switch (talkId) { + case TalkID::SmithSell: + if (pI->_iMiscId > IMISC_OILFIRST && pI->_iMiscId < IMISC_OILLAST) + return false; // Keep this item + if (pI->_itype == ItemType::Misc || (pI->_itype == ItemType::Staff && (!gbIsHellfire || IsValidSpell(pI->_iSpell)))) + return true; // Remove this item + return false; // Keep this item + + case TalkID::WitchSell: + if (pI->_itype == ItemType::Misc && (pI->_iMiscId > 29 && pI->_iMiscId < 41)) + return true; // Remove this item + if (pI->_itype == ItemType::Staff && (!gbIsHellfire || IsValidSpell(pI->_iSpell))) + return false; // Keep this item + return pI->_itype != ItemType::Misc; // Keep if it's not Misc + + default: + return true; // Remove this item for unsupported TalkID + } + }), + playerItems.end()); +} + +void FilterRepairableItems() +{ + // Filter playerItems in place to only include items that can be repaired + playerItems.erase(std::remove_if(playerItems.begin(), playerItems.end(), + [](const IndexedItem &indexedItem) { + const Item &itemPtr = *indexedItem.itemPtr; + return itemPtr.isEmpty() || itemPtr._iDurability == itemPtr._iMaxDur || itemPtr._iMaxDur == DUR_INDESTRUCTIBLE; + }), + playerItems.end()); + + // Calculate repair costs and update displayValue in the vector + for (IndexedItem &indexedItem : playerItems) { + const Item &itemPtr = *indexedItem.itemPtr; + int dur = itemPtr._iMaxDur - itemPtr._iDurability; + int repairCost = 0; + + if (itemPtr._iMagical != ITEM_QUALITY_NORMAL && itemPtr._iIdentified) { + repairCost = 30 * itemPtr._iIvalue * dur / (itemPtr._iMaxDur * 100 * 2); + } else { + repairCost = std::max(itemPtr._ivalue * dur / (itemPtr._iMaxDur * 2), 1); + } + + indexedItem.displayValue = repairCost; + } +} + +void FilterRechargeableItems() +{ + // Filter playerItems to include only items that can be recharged + playerItems.erase(std::remove_if(playerItems.begin(), playerItems.end(), + [](const IndexedItem &indexedItem) { + const Item &itemPtr = *indexedItem.itemPtr; + return itemPtr.isEmpty() || itemPtr._iCharges == itemPtr._iMaxCharges || (itemPtr._itype != ItemType::Staff && itemPtr._iMiscId != IMISC_UNIQUE && itemPtr._iMiscId != IMISC_STAFF); + }), + playerItems.end()); + + // Calculate recharge costs and update displayValue in the vector + for (IndexedItem &indexedItem : playerItems) { + const Item &itemPtr = *indexedItem.itemPtr; + int rechargeCost = GetSpellData(itemPtr._iSpell).staffCost(); + rechargeCost = (rechargeCost * (itemPtr._iMaxCharges - itemPtr._iCharges)) / (itemPtr._iMaxCharges * 2); + indexedItem.displayValue = rechargeCost; + } +} + +void FilterIdentifiableItems() +{ + // Filter playerItems to include only items that can be identified + playerItems.erase(std::remove_if(playerItems.begin(), playerItems.end(), + [](const IndexedItem &indexedItem) { + const Item &itemPtr = *indexedItem.itemPtr; + return itemPtr.isEmpty() || itemPtr._iMagical == ITEM_QUALITY_NORMAL || itemPtr._iIdentified; + }), + playerItems.end()); + + // Set the identification cost for all identifiable items + for (IndexedItem &indexedItem : playerItems) { + indexedItem.displayValue = 100; // Identification cost is 100 gold + } +} + +void FilterPlayerItemsForAction(TalkID talkId) { BuildPlayerItemsVector(); @@ -564,7 +784,7 @@ void StoreSession::FilterPlayerItemsForAction(TalkID talkId) } } -void StoreSession::SetupItemList(TalkID talkId, std::vector &items, int idx, bool selling /*= true*/) +void SetupItemList(TalkID talkId, std::vector &items, int idx, bool selling /*= true*/) { ClearTextLines(5, 21); previousScrollPos = 5; @@ -602,7 +822,7 @@ void StoreSession::SetupItemList(TalkID talkId, std::vector &items, int id } } -void StoreSession::SetupItemList(TalkID talkId, std::vector &items, int idx, bool selling /*= true*/) +void SetupItemList(TalkID talkId, std::vector &items, int idx, bool selling /*= true*/) { ClearTextLines(5, 21); previousScrollPos = 5; @@ -629,27 +849,23 @@ void StoreSession::SetupItemList(TalkID talkId, std::vector &items, } } -void StoreSession::SetupTownerItemList(TalkID talkId, int idx, bool selling /*= true*/) +void SetupTownerItemList(TalkID talkId, int idx, bool selling = true) { - std::vector boyItems = { boyItem }; - switch (talkId) { case TalkID::SmithBuy: - SetupItemList(talkId, smithItems, idx, selling); + SetupItemList(talkId, Blacksmith.basicItems, idx, selling); break; case TalkID::SmithPremiumBuy: - SetupItemList(talkId, premiumItems, idx, selling); + SetupItemList(talkId, Blacksmith.items, idx, selling); break; case TalkID::HealerBuy: - SetupItemList(talkId, healerItems, idx, selling); + SetupItemList(talkId, Healer.items, idx, selling); break; case TalkID::WitchBuy: - SetupItemList(talkId, witchItems, idx, selling); + SetupItemList(talkId, Witch.items, idx, selling); break; case TalkID::BoyBuy: - // Special case for Boy's single item - SetupItemList(talkId, boyItems, idx, selling); - break; + SetupItemList(talkId, Boy.items, idx, selling); case TalkID::SmithSell: case TalkID::SmithRepair: case TalkID::WitchSell: @@ -662,7 +878,7 @@ void StoreSession::SetupTownerItemList(TalkID talkId, int idx, bool selling /*= } } -void StoreSession::SetBuyScreenHeader() +void SetBuyScreenHeader() { const UiFlags flags = UiFlags::ColorWhitegold; @@ -672,7 +888,7 @@ void StoreSession::SetBuyScreenHeader() SetLineText(20, 1, _("I have this item for sale:"), flags, false); } -void StoreSession::UpdateBookMinMagic(Item &bookItem) +void UpdateBookMinMagic(Item &bookItem) { if (bookItem._iMiscId != IMISC_BOOK) return; @@ -688,56 +904,56 @@ void StoreSession::UpdateBookMinMagic(Item &bookItem) } } -void StoreSession::UpdateItemStatFlags(TalkID talkId) +// FIXME: Move to anonymous namespace +static void UpdateItemStatFlag(Item &item) +{ + if (!item.isEmpty()) { + item._iStatFlag = MyPlayer->CanUseItem(item); + currentItemIndex++; + } +} + +void UpdateItemStatFlags(TalkID talkId) { - currentItemIndex = 0; + currentItemIndex = 0; // FIXME: Remove this?? switch (talkId) { case TalkID::SmithBuy: - for (Item &item : smithItems) { - if (!item.isEmpty()) { - item._iStatFlag = MyPlayer->CanUseItem(item); - currentItemIndex++; - } - } + for (Item &item : Blacksmith.basicItems) + UpdateItemStatFlag(item); break; case TalkID::SmithPremiumBuy: - for (Item &item : premiumItems) { - if (!item.isEmpty()) { - item._iStatFlag = MyPlayer->CanUseItem(item); - currentItemIndex++; - } - } + for (Item &item : Blacksmith.items) + UpdateItemStatFlag(item); break; case TalkID::WitchBuy: - for (Item &item : witchItems) { - if (!item.isEmpty()) { - UpdateBookMinMagic(item); - item._iStatFlag = MyPlayer->CanUseItem(item); - currentItemIndex++; - } - } + for (Item &item : Witch.items) + UpdateItemStatFlag(item); break; case TalkID::HealerBuy: - for (Item &item : healerItems) { - if (!item.isEmpty()) { - item._iStatFlag = MyPlayer->CanUseItem(item); - currentItemIndex++; - } - } + for (Item &item : Healer.items) + UpdateItemStatFlag(item); break; case TalkID::BoyBuy: - if (!boyItem.isEmpty()) { - boyItem._iStatFlag = MyPlayer->CanUseItem(boyItem); - currentItemIndex = 1; - } + for (Item &item : Boy.items) + UpdateItemStatFlag(item); break; default: break; } } -void StoreSession::SetupTownerBuyScreen(TalkID talkId) +uint32_t GetTotalPlayerGold() +{ + return MyPlayer->_pGold + Stash.gold; +} + +bool CanPlayerAfford(uint32_t price) +{ + return GetTotalPlayerGold() >= price; +} + +void SetupTownerBuyScreen(TalkID talkId) { isTextFullSize = true; scrollPos = 0; @@ -756,39 +972,7 @@ void StoreSession::SetupTownerBuyScreen(TalkID talkId) AddItemListBackButton(talkId, true); } -void StoreSession::FilterSellableItems(TalkID talkId) -{ - playerItems.erase(std::remove_if(playerItems.begin(), playerItems.end(), - [talkId](const IndexedItem &indexedItem) { - Item *pI = indexedItem.itemPtr; - - // Common conditions for both Smith and Witch - if (pI->isEmpty() || pI->_itype == ItemType::Gold || pI->_iClass == ICLASS_QUEST || pI->IDidx == IDI_LAZSTAFF) - return true; // Remove this item - - switch (talkId) { - case TalkID::SmithSell: - if (pI->_iMiscId > IMISC_OILFIRST && pI->_iMiscId < IMISC_OILLAST) - return false; // Keep this item - if (pI->_itype == ItemType::Misc || (pI->_itype == ItemType::Staff && (!gbIsHellfire || IsValidSpell(pI->_iSpell)))) - return true; // Remove this item - return false; // Keep this item - - case TalkID::WitchSell: - if (pI->_itype == ItemType::Misc && (pI->_iMiscId > 29 && pI->_iMiscId < 41)) - return true; // Remove this item - if (pI->_itype == ItemType::Staff && (!gbIsHellfire || IsValidSpell(pI->_iSpell))) - return false; // Keep this item - return pI->_itype != ItemType::Misc; // Keep if it's not Misc - - default: - return true; // Remove this item for unsupported TalkID - } - }), - playerItems.end()); -} - -void StoreSession::SetupTownerSellScreen(TalkID talkId) +void SetupTownerSellScreen(TalkID talkId) { isTextFullSize = true; hasScrollbar = true; @@ -814,33 +998,7 @@ void StoreSession::SetupTownerSellScreen(TalkID talkId) AddItemListBackButton(talkId); } -void StoreSession::FilterRepairableItems() -{ - // Filter playerItems in place to only include items that can be repaired - playerItems.erase(std::remove_if(playerItems.begin(), playerItems.end(), - [](const IndexedItem &indexedItem) { - const Item &itemPtr = *indexedItem.itemPtr; - return itemPtr.isEmpty() || itemPtr._iDurability == itemPtr._iMaxDur || itemPtr._iMaxDur == DUR_INDESTRUCTIBLE; - }), - playerItems.end()); - - // Calculate repair costs and update displayValue in the vector - for (IndexedItem &indexedItem : playerItems) { - const Item &itemPtr = *indexedItem.itemPtr; - int dur = itemPtr._iMaxDur - itemPtr._iDurability; - int repairCost = 0; - - if (itemPtr._iMagical != ITEM_QUALITY_NORMAL && itemPtr._iIdentified) { - repairCost = 30 * itemPtr._iIvalue * dur / (itemPtr._iMaxDur * 100 * 2); - } else { - repairCost = std::max(itemPtr._ivalue * dur / (itemPtr._iMaxDur * 2), 1); - } - - indexedItem.displayValue = repairCost; - } -} - -void StoreSession::SetupTownerRepairScreen() +void SetupTownerRepairScreen() { isTextFullSize = true; @@ -871,26 +1029,7 @@ void StoreSession::SetupTownerRepairScreen() AddItemListBackButton(TalkID::SmithRepair); } -void StoreSession::FilterRechargeableItems() -{ - // Filter playerItems to include only items that can be recharged - playerItems.erase(std::remove_if(playerItems.begin(), playerItems.end(), - [](const IndexedItem &indexedItem) { - const Item &itemPtr = *indexedItem.itemPtr; - return itemPtr.isEmpty() || itemPtr._iCharges == itemPtr._iMaxCharges || (itemPtr._itype != ItemType::Staff && itemPtr._iMiscId != IMISC_UNIQUE && itemPtr._iMiscId != IMISC_STAFF); - }), - playerItems.end()); - - // Calculate recharge costs and update displayValue in the vector - for (IndexedItem &indexedItem : playerItems) { - const Item &itemPtr = *indexedItem.itemPtr; - int rechargeCost = GetSpellData(itemPtr._iSpell).staffCost(); - rechargeCost = (rechargeCost * (itemPtr._iMaxCharges - itemPtr._iCharges)) / (itemPtr._iMaxCharges * 2); - indexedItem.displayValue = rechargeCost; - } -} - -void StoreSession::SetupTownerRechargeScreen() +void SetupTownerRechargeScreen() { isTextFullSize = true; @@ -921,64 +1060,7 @@ void StoreSession::SetupTownerRechargeScreen() AddItemListBackButton(TalkID::WitchRecharge); } -void StoreSession::RestoreResource() -{ - int *resource = nullptr; - int *maxResource = nullptr; - int *baseResource = nullptr; - int *baseMaxResource = nullptr; - SfxID sfx; - PanelDrawComponent component; - - switch (townerId) { - case TOWN_HEALER: - resource = &MyPlayer->_pHitPoints; - maxResource = &MyPlayer->_pMaxHP; - baseResource = &MyPlayer->_pHPBase; - baseMaxResource = &MyPlayer->_pMaxHPBase; - component = PanelDrawComponent::Health; - sfx = SfxID::CastHealing; - break; - case TOWN_WITCH: - if (!*sgOptions.Gameplay.adriaRefillsMana) - return; - resource = &MyPlayer->_pMana; - maxResource = &MyPlayer->_pMaxMana; - baseResource = &MyPlayer->_pManaBase; - baseMaxResource = &MyPlayer->_pMaxManaBase; - component = PanelDrawComponent::Mana; - sfx = SfxID::CastHealing; - break; - default: - return; - } - - if (*resource == *maxResource) - return; - - PlaySFX(sfx); - *resource = *maxResource; - *baseResource = *baseMaxResource; - RedrawComponent(component); -} - -void StoreSession::FilterIdentifiableItems() -{ - // Filter playerItems to include only items that can be identified - playerItems.erase(std::remove_if(playerItems.begin(), playerItems.end(), - [](const IndexedItem &indexedItem) { - const Item &itemPtr = *indexedItem.itemPtr; - return itemPtr.isEmpty() || itemPtr._iMagical == ITEM_QUALITY_NORMAL || itemPtr._iIdentified; - }), - playerItems.end()); - - // Set the identification cost for all identifiable items - for (IndexedItem &indexedItem : playerItems) { - indexedItem.displayValue = 100; // Identification cost is 100 gold - } -} - -void StoreSession::SetupTownerIdentifyScreen() +void SetupTownerIdentifyScreen() { isTextFullSize = true; @@ -1009,7 +1091,7 @@ void StoreSession::SetupTownerIdentifyScreen() AddItemListBackButton(TalkID::StorytellerIdentify); } -void StoreSession::SetupTownerIdentifyResultScreen(Item &item) +void SetupTownerIdentifyResultScreen(Item &item) { StartStore(oldActiveStore); hasScrollbar = false; @@ -1023,7 +1105,7 @@ void StoreSession::SetupTownerIdentifyResultScreen(Item &item) SetLineText(0, 18, _("Done"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); } -void StoreSession::SmithEnter() +void SmithEnter() { TalkID selectedAction = storeLineMapping[currentTextLine]; @@ -1054,25 +1136,25 @@ void StoreSession::SmithEnter() /** * @brief Purchases an item from the smith. */ -void StoreSession::SmithBuyItem(Item &item) +void SmithBuyItem(Item &item) { TakePlrsMoney(item._iIvalue); if (item._iMagical == ITEM_QUALITY_NORMAL) item._iIdentified = false; GiveItemToPlayer(item, true); int idx = oldScrollPos + ((oldTextLine - previousScrollPos) / 4); - if (idx == NumSmithItems - 1) { - smithItems[NumSmithItems - 1].clear(); + if (idx == NumSmithBasicItems - 1) { + Blacksmith.basicItems[NumSmithBasicItems - 1].clear(); } else { - for (; !smithItems[idx + 1].isEmpty(); idx++) { - smithItems[idx] = std::move(smithItems[idx + 1]); + for (; !Blacksmith.basicItems[idx + 1].isEmpty(); idx++) { + Blacksmith.basicItems[idx] = std::move(Blacksmith.basicItems[idx + 1]); } - smithItems[idx].clear(); + Blacksmith.basicItems[idx].clear(); } CalcPlrInv(*MyPlayer, true); } -void StoreSession::SmithBuyEnter() +void SmithBuyEnter() { if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Smith); @@ -1085,24 +1167,24 @@ void StoreSession::SmithBuyEnter() oldActiveStore = TalkID::SmithBuy; int idx = scrollPos + ((currentTextLine - previousScrollPos) / 4); - if (!CanPlayerAfford(smithItems[idx]._iIvalue)) { + if (!CanPlayerAfford(Blacksmith.basicItems[idx]._iIvalue)) { StartStore(TalkID::NoMoney); return; } - if (!GiveItemToPlayer(smithItems[idx], false)) { + if (!GiveItemToPlayer(Blacksmith.basicItems[idx], false)) { StartStore(TalkID::NoRoom); return; } - tempItem = smithItems[idx]; + tempItem = Blacksmith.basicItems[idx]; StartStore(TalkID::Confirm); } /** * @brief Purchases a premium item from the smith. */ -void StoreSession::SmithBuyPItem(Item &item) +void SmithBuyPItem(Item &item) { TakePlrsMoney(item._iIvalue); if (item._iMagical == ITEM_QUALITY_NORMAL) @@ -1112,18 +1194,18 @@ void StoreSession::SmithBuyPItem(Item &item) int idx = oldScrollPos + ((oldTextLine - previousScrollPos) / 4); int xx = 0; for (int i = 0; idx >= 0; i++) { - if (!premiumItems[i].isEmpty()) { + if (!Blacksmith.items[i].isEmpty()) { idx--; xx = i; } } - premiumItems[xx].clear(); - premiumItemCount--; + Blacksmith.items[xx].clear(); + Blacksmith.itemCount--; SpawnPremium(*MyPlayer); } -void StoreSession::SmithPremiumBuyEnter() +void SmithPremiumBuyEnter() { if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Smith); @@ -1138,27 +1220,27 @@ void StoreSession::SmithPremiumBuyEnter() int xx = scrollPos + ((currentTextLine - previousScrollPos) / 4); int idx = 0; for (int i = 0; xx >= 0; i++) { - if (!premiumItems[i].isEmpty()) { + if (!Blacksmith.items[i].isEmpty()) { xx--; idx = i; } } - if (!CanPlayerAfford(premiumItems[idx]._iIvalue)) { + if (!CanPlayerAfford(Blacksmith.items[idx]._iIvalue)) { StartStore(TalkID::NoMoney); return; } - if (!GiveItemToPlayer(premiumItems[idx], false)) { + if (!GiveItemToPlayer(Blacksmith.items[idx], false)) { StartStore(TalkID::NoRoom); return; } - tempItem = premiumItems[idx]; + tempItem = Blacksmith.items[idx]; StartStore(TalkID::Confirm); } -bool StoreSession::StoreGoldFit(Item &item) +bool StoreGoldFit(Item &item) { int cost = item._iIvalue; @@ -1175,7 +1257,7 @@ bool StoreSession::StoreGoldFit(Item &item) /** * @brief Sells an item from the player's inventory or belt. */ -void StoreSession::StoreSellItem() +void StoreSellItem() { int idx = oldScrollPos + ((oldTextLine - previousScrollPos) / 4); @@ -1197,7 +1279,7 @@ void StoreSession::StoreSellItem() MyPlayer->_pGold += cost; } -void StoreSession::SmithSellEnter() +void SmithSellEnter() { if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Smith); @@ -1227,7 +1309,7 @@ void StoreSession::SmithSellEnter() /** * @brief Repairs an item in the player's inventory or body in the smith. */ -void StoreSession::SmithRepairItem(int price) +void SmithRepairItem(int price) { int idx = oldScrollPos + ((oldTextLine - previousScrollPos) / 4); @@ -1270,7 +1352,7 @@ void StoreSession::SmithRepairItem(int price) TakePlrsMoney(price); } -void StoreSession::SmithRepairEnter() +void SmithRepairEnter() { if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Smith); @@ -1297,7 +1379,7 @@ void StoreSession::SmithRepairEnter() StartStore(TalkID::Confirm); } -void StoreSession::WitchEnter() +void WitchEnter() { switch (currentTextLine) { case 12: @@ -1323,7 +1405,7 @@ void StoreSession::WitchEnter() /** * @brief Purchases an item from the witch. */ -void StoreSession::WitchBuyItem(Item &item) +void WitchBuyItem(Item &item) { int idx = oldScrollPos + ((oldTextLine - previousScrollPos) / 4); @@ -1335,19 +1417,19 @@ void StoreSession::WitchBuyItem(Item &item) if (idx >= 3) { if (idx == NumWitchItems - 1) { - witchItems[NumWitchItems - 1].clear(); + Witch.items[NumWitchItems - 1].clear(); } else { - for (; !witchItems[idx + 1].isEmpty(); idx++) { - witchItems[idx] = std::move(witchItems[idx + 1]); + for (; !Witch.items[idx + 1].isEmpty(); idx++) { + Witch.items[idx] = std::move(Witch.items[idx + 1]); } - witchItems[idx].clear(); + Witch.items[idx].clear(); } } CalcPlrInv(*MyPlayer, true); } -void StoreSession::WitchBuyEnter() +void WitchBuyEnter() { if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Witch); @@ -1361,21 +1443,21 @@ void StoreSession::WitchBuyEnter() int idx = scrollPos + ((currentTextLine - previousScrollPos) / 4); - if (!CanPlayerAfford(witchItems[idx]._iIvalue)) { + if (!CanPlayerAfford(Witch.items[idx]._iIvalue)) { StartStore(TalkID::NoMoney); return; } - if (!GiveItemToPlayer(witchItems[idx], false)) { + if (!GiveItemToPlayer(Witch.items[idx], false)) { StartStore(TalkID::NoRoom); return; } - tempItem = witchItems[idx]; + tempItem = Witch.items[idx]; StartStore(TalkID::Confirm); } -void StoreSession::WitchSellEnter() +void WitchSellEnter() { if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Witch); @@ -1405,7 +1487,7 @@ void StoreSession::WitchSellEnter() /** * @brief Recharges an item in the player's inventory or body in the witch. */ -void StoreSession::WitchRechargeItem(int price) +void WitchRechargeItem(int price) { int idx = oldScrollPos + ((oldTextLine - previousScrollPos) / 4); Item *itemPtr = playerItems[idx].itemPtr; @@ -1433,7 +1515,7 @@ void StoreSession::WitchRechargeItem(int price) CalcPlrInv(*MyPlayer, true); } -void StoreSession::WitchRechargeEnter() +void WitchRechargeEnter() { if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Witch); @@ -1458,9 +1540,9 @@ void StoreSession::WitchRechargeEnter() StartStore(TalkID::Confirm); } -void StoreSession::BoyEnter() +void BoyEnter() { - if (!boyItem.isEmpty() && currentTextLine == 18) { + if (!Boy.items[0].isEmpty() && currentTextLine == 18) { if (!CanPlayerAfford(50)) { oldActiveStore = TalkID::Boy; oldTextLine = 18; @@ -1473,7 +1555,7 @@ void StoreSession::BoyEnter() return; } - if ((currentTextLine != 8 && !boyItem.isEmpty()) || (currentTextLine != 12 && boyItem.isEmpty())) { + if ((currentTextLine != 8 && !Boy.items[0].isEmpty()) || (currentTextLine != 12 && Boy.items[0].isEmpty())) { activeStore = TalkID::None; return; } @@ -1483,11 +1565,11 @@ void StoreSession::BoyEnter() StartStore(TalkID::Gossip); } -void StoreSession::BoyBuyItem(Item &item) +void BoyBuyItem(Item &item) { TakePlrsMoney(item._iIvalue); GiveItemToPlayer(item, true); - boyItem.clear(); + Boy.items[0].clear(); // FIXME: Properly clear the items vector!!! oldActiveStore = TalkID::Boy; CalcPlrInv(*MyPlayer, true); oldTextLine = 12; @@ -1496,7 +1578,7 @@ void StoreSession::BoyBuyItem(Item &item) /** * @brief Purchases an item from the healer. */ -void StoreSession::HealerBuyItem(Item &item) +void HealerBuyItem(Item &item) { int idx = oldScrollPos + ((oldTextLine - previousScrollPos) / 4); if (!gbIsMultiplayer) { @@ -1521,17 +1603,18 @@ void StoreSession::HealerBuyItem(Item &item) } idx = oldScrollPos + ((oldTextLine - previousScrollPos) / 4); if (idx == 19) { - healerItems[19].clear(); + Healer.items[19].clear(); } else { - for (; !healerItems[idx + 1].isEmpty(); idx++) { - healerItems[idx] = std::move(healerItems[idx + 1]); + for (; !Healer.items[idx + 1].isEmpty(); idx++) { + Healer.items[idx] = std::move(Healer.items[idx + 1]); } - healerItems[idx].clear(); + Healer.items[idx].clear(); } CalcPlrInv(*MyPlayer, true); } -void StoreSession::BoyBuyEnter() +// FIXME: Make this work with the vector properly +void BoyBuyEnter() { if (currentTextLine != 10) { activeStore = TalkID::None; @@ -1541,28 +1624,28 @@ void StoreSession::BoyBuyEnter() oldActiveStore = TalkID::BoyBuy; oldScrollPos = scrollPos; oldTextLine = 10; - int price = boyItem._iIvalue; + int price = Boy.items[0]._iIvalue; if (gbIsHellfire) - price -= boyItem._iIvalue / 4; + price -= Boy.items[0]._iIvalue / 4; else - price += boyItem._iIvalue / 2; + price += Boy.items[0]._iIvalue / 2; if (!CanPlayerAfford(price)) { StartStore(TalkID::NoMoney); return; } - if (!GiveItemToPlayer(boyItem, false)) { + if (!GiveItemToPlayer(Boy.items[0], false)) { StartStore(TalkID::NoRoom); return; } - tempItem = boyItem; + tempItem = Boy.items[0]; tempItem._iIvalue = price; StartStore(TalkID::Confirm); } -void StoreSession::StorytellerIdentifyItem(Item &item) +void StorytellerIdentifyItem(Item &item) { int8_t idx = playerItemIndexes[((oldTextLine - previousScrollPos) / 4) + oldScrollPos]; if (idx < 0) { @@ -1588,7 +1671,7 @@ void StoreSession::StorytellerIdentifyItem(Item &item) CalcPlrInv(*MyPlayer, true); } -void StoreSession::ConfirmEnter(Item &item) +void ConfirmEnter(Item &item) { if (currentTextLine == 18) { switch (oldActiveStore) { @@ -1639,7 +1722,7 @@ void StoreSession::ConfirmEnter(Item &item) } } -void StoreSession::HealerEnter() +void HealerEnter() { switch (currentTextLine) { case 12: @@ -1656,7 +1739,7 @@ void StoreSession::HealerEnter() } } -void StoreSession::HealerBuyEnter() +void HealerBuyEnter() { if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Healer); @@ -1670,21 +1753,21 @@ void StoreSession::HealerBuyEnter() int idx = scrollPos + ((currentTextLine - previousScrollPos) / 4); - if (!CanPlayerAfford(healerItems[idx]._iIvalue)) { + if (!CanPlayerAfford(Healer.items[idx]._iIvalue)) { StartStore(TalkID::NoMoney); return; } - if (!GiveItemToPlayer(healerItems[idx], false)) { + if (!GiveItemToPlayer(Healer.items[idx], false)) { StartStore(TalkID::NoRoom); return; } - tempItem = healerItems[idx]; + tempItem = Healer.items[idx]; StartStore(TalkID::Confirm); } -void StoreSession::StorytellerEnter() +void StorytellerEnter() { switch (currentTextLine) { case 12: @@ -1701,7 +1784,7 @@ void StoreSession::StorytellerEnter() } } -void StoreSession::StorytellerIdentifyEnter() +void StorytellerIdentifyEnter() { if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Storyteller); @@ -1726,7 +1809,7 @@ void StoreSession::StorytellerIdentifyEnter() StartStore(TalkID::Confirm); } -void StoreSession::TalkEnter() +void TalkEnter() { if (currentTextLine == BackButtonLine()) { StartStore(oldActiveStore); @@ -1764,7 +1847,7 @@ void StoreSession::TalkEnter() } } -void StoreSession::TavernEnter() +void TavernEnter() { switch (currentTextLine) { case 12: @@ -1778,7 +1861,7 @@ void StoreSession::TavernEnter() } } -void StoreSession::BarmaidEnter() +void BarmaidEnter() { switch (currentTextLine) { case 12: @@ -1803,7 +1886,7 @@ void StoreSession::BarmaidEnter() } } -void StoreSession::DrunkEnter() +void DrunkEnter() { switch (currentTextLine) { case 12: @@ -1817,7 +1900,7 @@ void StoreSession::DrunkEnter() } } -int StoreSession::TakeGold(Player &player, int cost, bool skipMaxPiles) +int TakeGold(Player &player, int cost, bool skipMaxPiles) { for (int i = 0; i < player._pNumInv; i++) { auto &item = player.InvList[i]; @@ -1838,7 +1921,7 @@ int StoreSession::TakeGold(Player &player, int cost, bool skipMaxPiles) return cost; } -void StoreSession::DrawSelector(const Surface &out, const Rectangle &rect, std::string_view text, UiFlags flags) +void DrawSelector(const Surface &out, const Rectangle &rect, std::string_view text, UiFlags flags) { int lineWidth = GetLineWidth(text); @@ -1855,30 +1938,35 @@ void StoreSession::DrawSelector(const Surface &out, const Rectangle &rect, std:: ClxDraw(out, { x2, rect.position.y + 13 }, (*pSPentSpn2Cels)[PentSpn2Spin()]); } -void StoreSession::InitStores() +} // namespace + +void InitStores() { ClearTextLines(0, NumStoreLines); activeStore = TalkID::None; isTextFullSize = false; hasScrollbar = false; - premiumItemCount = 0; - premiumItemLevel = 1; + Blacksmith.itemCount = 0; + Blacksmith.itemLevel = 1; // Resize the vectors to their intended sizes - smithItems.resize(NumSmithItems); - premiumItems.resize(NumSmithPremiumItems); - healerItems.resize(NumHealerItems); - witchItems.resize(NumWitchItems); + Blacksmith.basicItems.resize(NumSmithBasicItems); + Blacksmith.items.resize(NumSmithItems); + Healer.items.resize(NumHealerItems); + Witch.items.resize(NumWitchItems); + Boy.items.resize(NumBoyItems); // Clear the item data - for (auto &item : premiumItems) + for (auto &item : Blacksmith.items) + item.clear(); + + for (auto &item : Boy.items) item.clear(); - boyItem.clear(); - boyItemLevel = 0; + Boy.itemLevel = 0; } -void StoreSession::SetupTownStores() +void SetupTownStores() { int l = MyPlayer->getCharacterLevel() / 2; if (!gbIsMultiplayer) { @@ -1897,7 +1985,7 @@ void StoreSession::SetupTownStores() SpawnPremium(*MyPlayer); } -void StoreSession::FreeStoreMem() +void FreeStoreMem() { if (*sgOptions.Gameplay.showItemGraphicsInStores) { FreeHalfSizeItemSprites(); @@ -1909,7 +1997,7 @@ void StoreSession::FreeStoreMem() } } -void StoreSession::PrintSString(const Surface &out, int margin, int line, std::string_view text, UiFlags flags, int price, int cursId, bool cursIndent) +void PrintSString(const Surface &out, int margin, int line, std::string_view text, UiFlags flags, int price, int cursId, bool cursIndent) { const Point uiPosition = GetUIRectangle().position; int sx = uiPosition.x + 32 + margin; @@ -1964,7 +2052,7 @@ void StoreSession::PrintSString(const Surface &out, int margin, int line, std::s } } -void StoreSession::DrawSLine(const Surface &out, int sy) +void DrawSLine(const Surface &out, int sy) { const Point uiPosition = GetUIRectangle().position; int sx = 26; @@ -1982,13 +2070,13 @@ void StoreSession::DrawSLine(const Surface &out, int sy) memcpy(dst, src, width); } -void StoreSession::DrawSTextHelp() +void DrawSTextHelp() { currentTextLine = -1; isTextFullSize = true; } -void StoreSession::ClearTextLines(int start, int end) +void ClearTextLines(int start, int end) { for (int i = start; i < end; i++) { textLine[i]._sx = 0; @@ -2003,7 +2091,7 @@ void StoreSession::ClearTextLines(int start, int end) // std::fill(storeLineMapping.begin(), storeLineMapping.end(), TalkID::None); } -void StoreSession::StartStore(TalkID s) +void StartStore(TalkID s) { if (*sgOptions.Gameplay.showItemGraphicsInStores) { CreateHalfSizeItemSprites(); @@ -2023,7 +2111,7 @@ void StoreSession::StartStore(TalkID s) break; case TalkID::SmithBuy: { bool hasAnyItems = false; - for (int i = 0; !smithItems[i].isEmpty(); i++) { + for (int i = 0; !Blacksmith.basicItems[i].isEmpty(); i++) { hasAnyItems = true; break; } @@ -2122,7 +2210,7 @@ void StoreSession::StartStore(TalkID s) activeStore = s; } -void StoreSession::DrawSText(const Surface &out) +void DrawSText(const Surface &out) { if (!isTextFullSize) DrawTextUI(out); @@ -2172,7 +2260,7 @@ void StoreSession::DrawSText(const Surface &out) DrawScrollbar(out, 4, 20); } -void StoreSession::StoreESC() +void StoreESC() { if (qtextflag) { qtextflag = false; @@ -2248,7 +2336,7 @@ void StoreSession::StoreESC() } } -void StoreSession::StoreUp() +void StoreUp() { PlaySFX(SfxID::MenuMove); if (currentTextLine == -1) { @@ -2285,7 +2373,7 @@ void StoreSession::StoreUp() } } -void StoreSession::StoreDown() +void StoreDown() { PlaySFX(SfxID::MenuMove); if (currentTextLine == -1) { @@ -2322,7 +2410,7 @@ void StoreSession::StoreDown() } } -void StoreSession::StorePrior() +void StorePrior() { PlaySFX(SfxID::MenuMove); if (currentTextLine != -1 && hasScrollbar) { @@ -2334,7 +2422,7 @@ void StoreSession::StorePrior() } } -void StoreSession::StoreNext() +void StoreNext() { PlaySFX(SfxID::MenuMove); if (currentTextLine != -1 && hasScrollbar) { @@ -2349,7 +2437,7 @@ void StoreSession::StoreNext() } } -void StoreSession::TakePlrsMoney(int cost) +void TakePlrsMoney(int cost) { MyPlayer->_pGold -= std::min(cost, MyPlayer->_pGold); @@ -2362,7 +2450,7 @@ void StoreSession::TakePlrsMoney(int cost) Stash.dirty = true; } -void StoreSession::StoreEnter() +void StoreEnter() { if (qtextflag) { qtextflag = false; @@ -2448,7 +2536,7 @@ void StoreSession::StoreEnter() } } -void StoreSession::CheckStoreButton() +void CheckStoreButton() { const Point uiPosition = GetUIRectangle().position; const Rectangle windowRect { { uiPosition.x + 344, uiPosition.y + PaddingTop - 7 }, { 271, 303 } }; @@ -2522,18 +2610,18 @@ void StoreSession::CheckStoreButton() } } -void StoreSession::ReleaseStoreButton() +void ReleaseStoreButton() { countdownScrollUp = -1; countdownScrollDown = -1; } -bool StoreSession::IsPlayerInStore() const +bool IsPlayerInStore() { return activeStore != TalkID::None; } -void StoreSession::ExitStore() +void ExitStore() { activeStore = TalkID::None; } diff --git a/Source/stores.h b/Source/stores.h index b8a069c857b..10482e53ac0 100644 --- a/Source/stores.h +++ b/Source/stores.h @@ -19,26 +19,17 @@ #include "utils/attributes.h" namespace devilution { -/* Number of player items that display in stores (Inventory slots and belt slots) */ + +/** @brief Number of player items that display in stores (Inventory slots and belt slots) */ const int NumPlayerItems = (NUM_XY_SLOTS - (SLOTXY_EQUIPPED_LAST + 1)); -constexpr int NumSmithItems = 25; -constexpr int NumSmithPremiumItems = 15; +constexpr int NumSmithBasicItems = 25; +constexpr int NumSmithItems = 6; +constexpr int NumSmithItemsHf = 15; constexpr int NumHealerItems = 20; constexpr int NumWitchItems = 25; - +constexpr int NumBoyItems = 1; constexpr int NumStoreLines = 104; -// For most languages, line height is always 12. -// This includes blank lines and divider line. -constexpr int SmallLineHeight = 12; -constexpr int SmallTextHeight = 12; - -// For larger small fonts (Chinese and Japanese), text lines are -// taller and overflow. -// We space out blank lines a bit more to give space to 3-line store items. -constexpr int LargeLineHeight = SmallLineHeight + 1; -constexpr int LargeTextHeight = 18; - enum class TalkID : uint8_t { None, Smith, @@ -66,36 +57,10 @@ enum class TalkID : uint8_t { Barmaid, }; -struct STextStruct { - enum Type : uint8_t { - Label, - Divider, - Selectable, - }; - - std::string text; - int _sval; - int y; - UiFlags flags; - Type type; - uint8_t _sx; - uint8_t _syoff; - int cursId; - bool cursIndent; - - [[nodiscard]] bool isDivider() const - { - return type == Divider; - } - [[nodiscard]] bool isSelectable() const - { - return type == Selectable; - } - - [[nodiscard]] bool hasText() const - { - return !text.empty(); - } +enum class ItemLocation { + Inventory, + Belt, + Body }; struct StoreMenuOption { @@ -109,12 +74,6 @@ struct TownerLine { size_t numOptions; }; -enum class ItemLocation { - Inventory, - Belt, - Body -}; - struct IndexedItem { Item *itemPtr; // Pointer to the original item ItemLocation location; // Location in the player's inventory (Inventory, Belt, or Body) @@ -122,217 +81,54 @@ struct IndexedItem { int displayValue; // Modified value for display purposes }; -class StoreSession { -public: - StoreSession(); - - /** - * The line index with the Back / Leave button. - * This is a special button that is always the last line. - * - * For lists with a scrollbar, it is not selectable (mouse-only). - */ - int BackButtonLine() const - { - if (IsSmallFontTall()) { - return hasScrollbar ? 21 : 20; - } - return 22; - } - - int LineHeight() const - { - return IsSmallFontTall() ? LargeLineHeight : SmallLineHeight; - } - - int TextHeight() const - { - return IsSmallFontTall() ? LargeTextHeight : SmallTextHeight; - } - - void CalculateLineHeights(); - void DrawTextUI(const Surface &out); - void DrawScrollbar(const Surface &out, int y1, int y2) const; - void SetLineAsDivider(size_t y); - - void SetLineValue(size_t y, int val) - { - textLine[y]._sval = val; - } - - void SetLineText(uint8_t x, size_t y, std::string_view text, UiFlags flags, bool sel, int cursId = -1, bool cursIndent = false); - void SetLineAsOptionsBackButton(); - void AddItemListBackButton(TalkID talkId, bool selectable = false); - void PrintStoreItem(const Item &item, int l, UiFlags flags, bool cursIndent = false); - bool GiveItemToPlayer(Item &item, bool persistItem); - - uint32_t GetTotalPlayerGold() const - { - return MyPlayer->_pGold + Stash.gold; - } - - bool CanPlayerAfford(uint32_t price) const - { - return GetTotalPlayerGold() >= price; - } - - /* General */ - - void SetupErrorScreen(TalkID talkId); - void SetupConfirmScreen(Item &item); - void BuildPlayerItemsVector(); - void FilterPlayerItemsForAction(TalkID talkId); - void SetupItemList(TalkID talkId, std::vector &items, int idx, bool selling /*= true*/); - void SetupItemList(TalkID talkId, std::vector &items, int idx, bool selling /*= true*/); - void SetupTownerItemList(TalkID talkId, int idx, bool selling = true); - - /* Main Menu */ - - void SetMenuHeader(const std::string &header); - void SetMenuText(const TownerLine &townerInfo); - void SetMenuOption(TalkID action, const std::string_view &text); - void SetupTownerMenuScreen(); +extern TalkID activeStore; // Currently active store +extern DVL_API_FOR_TEST int currentItemIndex; // Current index into playerItemIndexes/playerItems +extern std::array playerItemIndexes; // Map of inventory items being presented in the store +extern DVL_API_FOR_TEST std::vector playerItems; // Pointers to player items, coupled with necessary information - /* Gossip */ - - void SetupGossipScreen(); - - /* Buy */ - - void SetBuyScreenHeader(); - void UpdateBookMinMagic(Item &bookItem); - void UpdateItemStatFlags(TalkID talkId); - void SetupTownerBuyScreen(TalkID talkId); - - /* Sell */ - - void FilterSellableItems(TalkID talkId); - void SetupTownerSellScreen(TalkID talkId); - - /* Repair */ - - void FilterRepairableItems(); - void SetupTownerRepairScreen(); - - /* Recharge */ - - void FilterRechargeableItems(); - void SetupTownerRechargeScreen(); - - /* Identify */ - - void FilterIdentifiableItems(); - void SetupTownerIdentifyScreen(); - void SetupTownerIdentifyResultScreen(Item &item); - - /* Restore Resources */ - - void RestoreResource(); - - /* Logic */ - - void SmithEnter(); - void SmithBuyItem(Item &item); - void SmithBuyEnter(); - void SmithBuyPItem(Item &item); - void SmithPremiumBuyEnter(); - bool StoreGoldFit(Item &item); - void StoreSellItem(); - void SmithSellEnter(); - void SmithRepairItem(int price); - void SmithRepairEnter(); - - void WitchEnter(); - void WitchBuyItem(Item &item); - void WitchBuyEnter(); - void WitchSellEnter(); - void WitchRechargeItem(int price); - void WitchRechargeEnter(); - - void BoyEnter(); - void BoyBuyItem(Item &item); - - void HealerBuyItem(Item &item); - - void BoyBuyEnter(); - - void StorytellerIdentifyItem(Item &item); - - void ConfirmEnter(Item &item); - - void HealerEnter(); - void HealerBuyEnter(); - - void StorytellerEnter(); - void StorytellerIdentifyEnter(); - - void TalkEnter(); - - void TavernEnter(); - - void BarmaidEnter(); - - void DrunkEnter(); - - int TakeGold(Player &player, int cost, bool skipMaxPiles); - void DrawSelector(const Surface &out, const Rectangle &rect, std::string_view text, UiFlags flags); - /* Clears premium items sold by Griswold and Wirt. */ - void InitStores(); - /* Spawns items sold by vendors, including premium items sold by Griswold and Wirt. */ - void SetupTownStores(); - void FreeStoreMem(); - void PrintSString(const Surface &out, int margin, int line, std::string_view text, UiFlags flags, int price = 0, int cursId = -1, bool cursIndent = false); - void DrawSLine(const Surface &out, int sy); - void DrawSTextHelp(); - void ClearTextLines(int start, int end); - void StartStore(TalkID s); - void DrawSText(const Surface &out); - void StoreESC(); - void StoreUp(); - void StoreDown(); - void StorePrior(); - void StoreNext(); - void TakePlrsMoney(int cost); - void StoreEnter(); - void CheckStoreButton(); - void ReleaseStoreButton(); - bool IsPlayerInStore() const; - void ExitStore(); - - TalkID activeStore; // Currently active store - int currentItemIndex; // Current index into playerItemIndexes/playerItems - std::array playerItemIndexes; // Map of inventory items being presented in the store - std::vector playerItems; // Pointers to player items, coupled with necessary information - std::vector smithItems; // Items sold by Griswold - int premiumItemCount; // Number of premium items for sale by Griswold - int premiumItemLevel; // Base level of current premium items sold by Griswold - std::vector premiumItems; // Premium items sold by Griswold - std::vector healerItems; // Items sold by Pepin - std::vector witchItems; // Items sold by Adria - int boyItemLevel; // Current level of the item sold by Wirt - Item boyItem; // Current item sold by Wirt - -private: - _talker_id townerId; // The current towner being interacted with - bool isTextFullSize; // Is the current dialog full size - int numTextLines; // Number of text lines in the current dialog - int oldTextLine; // Remember currently selected text line from textLine while displaying a dialog - int currentTextLine; // Currently selected text line from textLine - std::array textLine; // Text lines - bool renderGold; // Whether to render the player's gold amount in the top left - bool hasScrollbar; // Does the current panel have a scrollbar - int oldScrollPos; // Remember last scroll position - int scrollPos; // Scroll position - int nextScrollPos; // Next scroll position - int previousScrollPos; // Previous scroll position - int8_t countdownScrollUp; // Countdown for the push state of the scroll up button - int8_t countdownScrollDown; // Countdown for the push state of the scroll down button - TalkID oldActiveStore; // Remember current store while displaying a dialog - Item tempItem; // Temporary item used to hold the item being traded - std::array storeLineMapping; - int currentMenuDrawLine; +class TownerStore { + // public: + // Store(); +public: + std::vector basicItems; // Used for the blacksmith store that only displays non-magical items + std::vector items; + uint8_t itemLevel; + int itemCount; + + //_talker_id townerId; + TalkID buyAction; + TalkID buyPremiumAction; + TalkID sellAction; + TalkID specialAction; }; -extern StoreSession Stores; +extern TownerStore Blacksmith; +extern TownerStore Healer; +extern TownerStore Witch; +extern TownerStore Boy; +extern TownerStore Storyteller; + +/* Clears premium items sold by Griswold and Wirt. */ +void InitStores(); +/* Spawns items sold by vendors, including premium items sold by Griswold and Wirt. */ +void SetupTownStores(); +void FreeStoreMem(); +void PrintSString(const Surface &out, int margin, int line, std::string_view text, UiFlags flags, int price = 0, int cursId = -1, bool cursIndent = false); +void DrawSLine(const Surface &out, int sy); +void DrawSTextHelp(); +void ClearTextLines(int start, int end); +void StartStore(TalkID s); +void DrawSText(const Surface &out); +void StoreESC(); +void StoreUp(); +void StoreDown(); +void StorePrior(); +void StoreNext(); +void TakePlrsMoney(int cost); +void StoreEnter(); +void CheckStoreButton(); +void ReleaseStoreButton(); +bool IsPlayerInStore(); +void ExitStore(); } // namespace devilution diff --git a/Source/towners.cpp b/Source/towners.cpp index 5468d078c01..b89786a185e 100644 --- a/Source/towners.cpp +++ b/Source/towners.cpp @@ -340,7 +340,7 @@ void TalkToBarOwner(Player &player, Towner &barOwner) } TownerTalk(TEXT_OGDEN1); - Stores.StartStore(TalkID::Tavern); + StartStore(TalkID::Tavern); } void TalkToDeadguy(Player &player, Towner & /*deadguy*/) @@ -408,7 +408,7 @@ void TalkToBlackSmith(Player &player, Towner &blackSmith) } TownerTalk(TEXT_GRISWOLD1); - Stores.StartStore(TalkID::Smith); + StartStore(TalkID::Smith); } void TalkToWitch(Player &player, Towner & /*witch*/) @@ -458,7 +458,7 @@ void TalkToWitch(Player &player, Towner & /*witch*/) } TownerTalk(TEXT_ADRIA1); - Stores.StartStore(TalkID::Witch); + StartStore(TalkID::Witch); } void TalkToBarmaid(Player &player, Towner & /*barmaid*/) @@ -473,13 +473,13 @@ void TalkToBarmaid(Player &player, Towner & /*barmaid*/) } TownerTalk(TEXT_GILLIAN1); - Stores.StartStore(TalkID::Barmaid); + StartStore(TalkID::Barmaid); } void TalkToDrunk(Player & /*player*/, Towner & /*drunk*/) { TownerTalk(TEXT_FARNHAM1); - Stores.StartStore(TalkID::Drunk); + StartStore(TalkID::Drunk); } void TalkToHealer(Player &player, Towner &healer) @@ -517,13 +517,13 @@ void TalkToHealer(Player &player, Towner &healer) } TownerTalk(TEXT_PEPIN1); - Stores.StartStore(TalkID::Healer); + StartStore(TalkID::Healer); } void TalkToBoy(Player & /*player*/, Towner & /*boy*/) { TownerTalk(TEXT_WIRT1); - Stores.StartStore(TalkID::Boy); + StartStore(TalkID::Boy); } void TalkToStoryteller(Player &player, Towner & /*storyteller*/) @@ -559,7 +559,7 @@ void TalkToStoryteller(Player &player, Towner & /*storyteller*/) } TownerTalk(TEXT_STORY1); - Stores.StartStore(TalkID::Storyteller); + StartStore(TalkID::Storyteller); } void TalkToCow(Player &player, Towner &cow) @@ -912,7 +912,7 @@ void UpdateCowFarmerAnimAfterQuestComplete() #ifdef _DEBUG bool DebugTalkToTowner(std::string_view targetName) { - Stores.SetupTownStores(); + SetupTownStores(); const std::string lowercaseName = AsciiStrToLower(targetName); Player &myPlayer = *MyPlayer; for (const TownerData &townerData : TownersData) { diff --git a/Source/track.cpp b/Source/track.cpp index 44fe4e33aac..244b239bce0 100644 --- a/Source/track.cpp +++ b/Source/track.cpp @@ -66,7 +66,7 @@ void RepeatMouseAction() if (sgbMouseDown == CLICK_NONE && ControllerActionHeld == GameActionType_NONE) return; - if (Stores.IsPlayerInStore()) + if (IsPlayerInStore()) return; if (LastMouseButtonAction == MouseActionType::None)