From 508bfa31f5922d45a8c150e9d9200f2036173795 Mon Sep 17 00:00:00 2001 From: Eric Robinson Date: Tue, 17 Sep 2024 18:00:51 -0400 Subject: [PATCH] Cleanup stores Update Refactor Smith menu Clean up the smith main menu with helper functions, and prevent utilizing the line number to figure out what functions to call. Clean up Main Menu more Separate strings Refactor and general fns Note: There is a bug where wirt's item can only be clicked on the item name Consolidate Sell functions Consolidate canselltotowner Fix bad rebase Fix accidental change Add IsPlayerInStore() Rename/organize Update stores.cpp Update stores.h Rename playerItemIdx Renames and fix IsPlayerInStore() RestoreResource() --- 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 | 101 +- 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 | 2539 ++++++++++------------ Source/stores.h | 337 ++- Source/towners.cpp | 18 +- Source/track.cpp | 2 +- test/fixtures/memory_map/game.txt | 4 +- test/stores_test.cpp | 32 +- 21 files changed, 1599 insertions(+), 1570 deletions(-) diff --git a/Source/control.cpp b/Source/control.cpp index 5cfdec301be..5168cbc9cc2 100644 --- a/Source/control.cpp +++ b/Source/control.cpp @@ -686,7 +686,7 @@ bool IsLevelUpButtonVisible() if (ControlMode == ControlTypes::VirtualGamepad) { return false; } - if (stextflag != TalkID::None || IsStashOpen) { + if (Stores.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 8799de89f4c..8f6399380fd 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 || stextflag != TalkID::None || QuestLogIsOpen) { + } else if (sgpCurrentMenu != nullptr || Stores.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 && stextflag == TalkID::None) + if (!QuestLogIsOpen && !SpellbookFlag && !Stores.IsPlayerInStore()) *action = GameAction(GameActionType_USE_HEALTH_POTION); return true; } if (VirtualGamepadState.manaButton.isHeld && VirtualGamepadState.manaButton.didStateChange) { - if (!QuestLogIsOpen && !SpellbookFlag && stextflag == TalkID::None) + if (!QuestLogIsOpen && !SpellbookFlag && !Stores.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() || stextflag != TalkID::None) + if (gmenu_is_active() || Stores.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 1f586ffca1d..310be126d30 100644 --- a/Source/controls/plrctrls.cpp +++ b/Source/controls/plrctrls.cpp @@ -65,7 +65,7 @@ quest_id pcursquest = Q_INVALID; */ bool InGameMenu() { - return stextflag != TalkID::None + return Stores.IsPlayerInStore() || HelpFlag || ChatLogFlag || ChatFlag @@ -1320,9 +1320,9 @@ void StoreMove(AxisDirection moveDir) static AxisDirectionRepeater repeater; moveDir = repeater.Get(moveDir); if (moveDir.y == AxisDirectionY_UP) - StoreUp(); + Stores.StoreUp(); else if (moveDir.y == AxisDirectionY_DOWN) - StoreDown(); + Stores.StoreDown(); } using HandleLeftStickOrDPadFn = void (*)(devilution::AxisDirection); @@ -1347,7 +1347,7 @@ HandleLeftStickOrDPadFn GetLeftStickOrDPadGameUIHandler() if (QuestLogIsOpen) { return &QuestLogMove; } - if (stextflag != TalkID::None) { + if (Stores.IsPlayerInStore()) { return &StoreMove; } return nullptr; diff --git a/Source/controls/touch/event_handlers.cpp b/Source/controls/touch/event_handlers.cpp index a08f656ac53..d7973238dfd 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 (stextflag == TalkID::None) + if (!Stores.IsPlayerInStore()) return false; if (event.type == SDL_FINGERDOWN) - CheckStoreBtn(); + Stores.CheckStoreButton(); return true; } diff --git a/Source/controls/touch/renderers.cpp b/Source/controls/touch/renderers.cpp index 5790a299ecf..1a0adc75428 100644 --- a/Source/controls/touch/renderers.cpp +++ b/Source/controls/touch/renderers.cpp @@ -430,7 +430,7 @@ VirtualGamepadButtonType PrimaryActionButtonRenderer::GetButtonType() VirtualGamepadButtonType PrimaryActionButtonRenderer::GetTownButtonType() { - if (stextflag != TalkID::None || pcursmonst != -1) + if (Stores.IsPlayerInStore() || pcursmonst != -1) return GetTalkButtonType(virtualPadButton->isHeld); return GetBlankButtonType(virtualPadButton->isHeld); } diff --git a/Source/diablo.cpp b/Source/diablo.cpp index ee39fa8b4aa..13afadc2657 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -197,7 +197,7 @@ void FreeGame() FreeGMenu(); FreeQuestText(); FreeInfoBoxGfx(); - FreeStoreMem(); + Stores.FreeStoreMem(); for (Player &player : Players) ResetPlayerGFX(player); @@ -353,8 +353,8 @@ void LeftMouseDown(uint16_t modState) return; } - if (stextflag != TalkID::None) { - CheckStoreBtn(); + if (Stores.IsPlayerInStore()) { + Stores.CheckStoreButton(); return; } @@ -417,8 +417,8 @@ void LeftMouseUp(uint16_t modState) } if (LevelButtonDown) CheckLevelButtonUp(); - if (stextflag != TalkID::None) - ReleaseStoreBtn(); + if (Stores.IsPlayerInStore()) + Stores.ReleaseStoreButton(); } void RightMouseDown(bool isShiftHeld) @@ -439,7 +439,7 @@ void RightMouseDown(bool isShiftHeld) doom_close(); return; } - if (stextflag != TalkID::None) + if (Stores.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 (stextflag != TalkID::None) { - StoreEnter(); + } else if (Stores.IsPlayerInStore()) { + Stores.StoreEnter(); } else if (QuestLogIsOpen) { QuestlogEnter(); } else { @@ -585,8 +585,8 @@ void PressKey(SDL_Keycode vkey, uint16_t modState) } return; case SDLK_UP: - if (stextflag != TalkID::None) { - StoreUp(); + if (Stores.IsPlayerInStore()) { + Stores.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 (stextflag != TalkID::None) { - StoreDown(); + if (Stores.IsPlayerInStore()) { + Stores.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 (stextflag != TalkID::None) { - StorePrior(); + if (Stores.IsPlayerInStore()) { + Stores.StorePrior(); } else if (ChatLogFlag) { ChatLogScrollTop(); } return; case SDLK_PAGEDOWN: - if (stextflag != TalkID::None) { - StoreNext(); + if (Stores.IsPlayerInStore()) { + Stores.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 (stextflag != TalkID::None && (button == SDL_BUTTON_X1 + if (Stores.IsPlayerInStore() && (button == SDL_BUTTON_X1 #if !SDL_VERSION_ATLEAST(2, 0, 0) || button == 8 #endif )) { - StoreESC(); + Stores.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 (stextflag != TalkID::None) { - StoreUp(); + if (Stores.IsPlayerInStore()) { + Stores.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 (stextflag != TalkID::None) { - StoreDown(); + if (Stores.IsPlayerInStore()) { + Stores.StoreDown(); } else if (QuestLogIsOpen) { QuestlogDown(); } else if (HelpFlag) { @@ -1492,7 +1492,7 @@ void HelpKeyPressed() { if (HelpFlag) { HelpFlag = false; - } else if (stextflag != TalkID::None) { + } else if (Stores.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 (stextflag != TalkID::None) + if (Stores.IsPlayerInStore()) return; invflag = !invflag; if (!IsLeftPanelOpen() && CanPanelsCoverView()) { @@ -1537,7 +1537,7 @@ void InventoryKeyPressed() void CharacterSheetKeyPressed() { - if (stextflag != TalkID::None) + if (Stores.IsPlayerInStore()) return; if (!IsRightPanelOpen() && CanPanelsCoverView()) { if (CharFlag) { // We are closing the character sheet @@ -1555,7 +1555,7 @@ void CharacterSheetKeyPressed() void QuestLogKeyPressed() { - if (stextflag != TalkID::None) + if (Stores.IsPlayerInStore()) return; if (!QuestLogIsOpen) { StartQuestlog(); @@ -1580,7 +1580,7 @@ void QuestLogKeyPressed() void DisplaySpellsKeyPressed() { - if (stextflag != TalkID::None) + if (Stores.IsPlayerInStore()) return; CloseCharPanel(); QuestLogIsOpen = false; @@ -1596,7 +1596,7 @@ void DisplaySpellsKeyPressed() void SpellBookKeyPressed() { - if (stextflag != TalkID::None) + if (Stores.IsPlayerInStore()) return; SpellbookFlag = !SpellbookFlag; if (!IsLeftPanelOpen() && CanPanelsCoverView()) { @@ -1761,7 +1761,7 @@ void InitKeymapActions() SDLK_F3, [] { gamemenu_load_game(false); }, nullptr, - [&]() { return !gbIsMultiplayer && gbValidSaveFile && stextflag == TalkID::None && IsGameRunning(); }); + [&]() { return !gbIsMultiplayer && gbValidSaveFile && !Stores.IsPlayerInStore() && IsGameRunning(); }); #ifndef NOEXIT sgOptions.Keymapper.AddAction( "QuitGame", @@ -2328,7 +2328,7 @@ void InitPadmapActions() ControllerButton_NONE, [] { gamemenu_load_game(false); }, nullptr, - [&]() { return !gbIsMultiplayer && gbValidSaveFile && stextflag == TalkID::None && IsGameRunning(); }); + [&]() { return !gbIsMultiplayer && gbValidSaveFile && !Stores.IsPlayerInStore() && IsGameRunning(); }); sgOptions.Padmapper.AddAction( "Item Highlighting", N_("Item highlighting"), @@ -2778,8 +2778,8 @@ bool PressEscKey() rv = true; } - if (stextflag != TalkID::None) { - StoreESC(); + if (Stores.IsPlayerInStore()) { + Stores.StoreESC(); rv = true; } @@ -2873,7 +2873,7 @@ void LoadGameLevel(bool firstflag, lvl_entry lvldir) InitInfoBoxGfx(); InitHelp(); } - InitStores(); + Stores.InitStores(); InitAutomapOnce(); } if (!setlevel) { @@ -2886,9 +2886,9 @@ void LoadGameLevel(bool firstflag, lvl_entry lvldir) } if (leveltype == DTYPE_TOWN) { - SetupTownStores(); + Stores.SetupTownStores(); } else { - FreeStoreMem(); + Stores.FreeStoreMem(); } if (firstflag || lvldir == ENTRY_LOAD) { diff --git a/Source/engine/render/scrollrt.cpp b/Source/engine/render/scrollrt.cpp index 7f86355e07d..f6708f559a3 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 (stextflag == TalkID::None && (itemIndex == pcursitem || AutoMapShowItems)) { + if (!Stores.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 (stextflag != TalkID::None && !qtextflag) - DrawSText(out); + if (Stores.IsPlayerInStore() && !qtextflag) + Stores.DrawSText(out); if (invflag) { DrawInv(out); } else if (SpellbookFlag) { diff --git a/Source/help.cpp b/Source/help.cpp index b9ecaf318fe..c62aa00eb57 100644 --- a/Source/help.cpp +++ b/Source/help.cpp @@ -189,7 +189,7 @@ void InitHelp() void DrawHelp(const Surface &out) { - DrawSTextHelp(); + Stores.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(); - DrawSLine(out, titleBottom); + Stores.DrawSLine(out, titleBottom); const int numLines = NumVisibleLines(); const int contentY = titleBottom + DividerLineMarginY() + ContentPaddingY(); diff --git a/Source/inv.cpp b/Source/inv.cpp index 89a6f53aa1a..e4af6a92405 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 (stextflag != TalkID::None) + if (Stores.IsPlayerInStore()) return true; if (cii < INVITEM_INV_FIRST) return false; diff --git a/Source/items.cpp b/Source/items.cpp index 5851bb88d91..c2babdbe5c2 100644 --- a/Source/items.cpp +++ b/Source/items.cpp @@ -1891,17 +1891,20 @@ _item_indexes RndSmithItem(const Player &player, int lvl) return RndVendorItem(player, 0, lvl); } -void SortVendor(Item *itemList) +template +void SortVendor(std::array &itemList, size_t startIndex = 0) { - int count = 1; - while (!itemList[count].isEmpty()) - count++; + // Find the count of non-empty items starting from the specified index + auto firstEmpty = std::find_if(itemList.begin() + startIndex, itemList.end(), [](const Item &item) { + return item.isEmpty(); + }); auto cmp = [](const Item &a, const Item &b) { return a.IDidx < b.IDidx; }; - std::sort(itemList, itemList + count, cmp); + // Sort only the non-empty items starting from startIndex + std::sort(itemList.begin() + startIndex, firstEmpty, cmp); } bool PremiumItemOk(const Player &player, const ItemData &item) @@ -4374,7 +4377,7 @@ void SpawnSmith(int lvl) int iCnt = RandomIntBetween(10, maxItems); for (int i = 0; i < iCnt; i++) { - Item &newItem = smithitem[i]; + Item &newItem = Stores.smithItems[i]; do { newItem = {}; @@ -4387,41 +4390,41 @@ void SpawnSmith(int lvl) newItem._iCreateInfo = lvl | CF_SMITH; newItem._iIdentified = true; } - for (int i = iCnt; i < SMITH_ITEMS; i++) - smithitem[i].clear(); + for (int i = iCnt; i < NumSmithItems; i++) + Stores.smithItems[i].clear(); - SortVendor(smithitem + PinnedItemCount); + SortVendor(Stores.smithItems, PinnedItemCount); } void SpawnPremium(const Player &player) { int lvl = player.getCharacterLevel(); - int maxItems = gbIsHellfire ? SMITH_PREMIUM_ITEMS : 6; - if (numpremium < maxItems) { + int maxItems = gbIsHellfire ? NumSmithPremiumItems : 6; + if (Stores.premiumItemCount < maxItems) { for (int i = 0; i < maxItems; i++) { - if (premiumitems[i].isEmpty()) { - int plvl = premiumlevel + (gbIsHellfire ? premiumLvlAddHellfire[i] : premiumlvladd[i]); - SpawnOnePremium(premiumitems[i], plvl, player); + if (Stores.premiumItems[i].isEmpty()) { + int plvl = Stores.premiumItemLevel + (gbIsHellfire ? premiumLvlAddHellfire[i] : premiumlvladd[i]); + SpawnOnePremium(Stores.premiumItems[i], plvl, player); } } - numpremium = maxItems; + Stores.premiumItemCount = maxItems; } - while (premiumlevel < lvl) { - premiumlevel++; + while (Stores.premiumItemLevel < lvl) { + Stores.premiumItemLevel++; if (gbIsHellfire) { // Discard first 3 items and shift next 10 - std::move(&premiumitems[3], &premiumitems[12] + 1, &premiumitems[0]); - SpawnOnePremium(premiumitems[10], premiumlevel + premiumLvlAddHellfire[10], player); - premiumitems[11] = premiumitems[13]; - SpawnOnePremium(premiumitems[12], premiumlevel + premiumLvlAddHellfire[12], player); - premiumitems[13] = premiumitems[14]; - SpawnOnePremium(premiumitems[14], premiumlevel + premiumLvlAddHellfire[14], player); + 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); } else { // Discard first 2 items and shift next 3 - std::move(&premiumitems[2], &premiumitems[4] + 1, &premiumitems[0]); - SpawnOnePremium(premiumitems[3], premiumlevel + premiumlvladd[3], player); - premiumitems[4] = premiumitems[5]; - SpawnOnePremium(premiumitems[5], premiumlevel + premiumlvladd[5], player); + 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); } } } @@ -4438,8 +4441,8 @@ void SpawnWitch(int lvl) const int itemCount = RandomIntBetween(10, gbIsHellfire ? 24 : 17); const int maxValue = gbIsHellfire ? 200000 : 140000; - for (int i = 0; i < WITCH_ITEMS; i++) { - Item &item = witchitem[i]; + for (int i = 0; i < NumWitchItems; i++) { + Item &item = Stores.witchItems[i]; item = {}; if (i < PinnedItemCount) { @@ -4490,7 +4493,7 @@ void SpawnWitch(int lvl) item._iIdentified = true; } - SortVendor(witchitem + PinnedItemCount); + SortVendor(Stores.witchItems, PinnedItemCount); } void SpawnBoy(int lvl) @@ -4509,19 +4512,19 @@ void SpawnBoy(int lvl) dexterity += dexterity / 5; magic += magic / 5; - if (boylevel >= (lvl / 2) && !boyitem.isEmpty()) + if (Stores.boyItemLevel >= (lvl / 2) && !Stores.boyItem.isEmpty()) return; do { keepgoing = false; - boyitem = {}; - boyitem._iSeed = AdvanceRndSeed(); - SetRndSeed(boyitem._iSeed); + Stores.boyItem = {}; + Stores.boyItem._iSeed = AdvanceRndSeed(); + SetRndSeed(Stores.boyItem._iSeed); _item_indexes itype = RndBoyItem(*MyPlayer, lvl); - GetItemAttrs(boyitem, itype, lvl); - GetItemBonus(*MyPlayer, boyitem, lvl, 2 * lvl, true, true); + GetItemAttrs(Stores.boyItem, itype, lvl); + GetItemBonus(*MyPlayer, Stores.boyItem, lvl, 2 * lvl, true, true); if (!gbIsHellfire) { - if (boyitem._iIvalue > 90000) { + if (Stores.boyItem._iIvalue > 90000) { keepgoing = true; // prevent breaking the do/while loop too early by failing hellfire's condition in while continue; } @@ -4530,7 +4533,7 @@ void SpawnBoy(int lvl) ivalue = 0; - ItemType itemType = boyitem._itype; + ItemType itemType = Stores.boyItem._itype; switch (itemType) { case ItemType::LightArmor: @@ -4596,15 +4599,15 @@ void SpawnBoy(int lvl) } } while (keepgoing || (( - boyitem._iIvalue > 200000 - || boyitem._iMinStr > strength - || boyitem._iMinMag > magic - || boyitem._iMinDex > dexterity - || boyitem._iIvalue < ivalue) + Stores.boyItem._iIvalue > 200000 + || Stores.boyItem._iMinStr > strength + || Stores.boyItem._iMinMag > magic + || Stores.boyItem._iMinDex > dexterity + || Stores.boyItem._iIvalue < ivalue) && count < 250)); - boyitem._iCreateInfo = lvl | CF_BOY; - boyitem._iIdentified = true; - boylevel = lvl / 2; + Stores.boyItem._iCreateInfo = lvl | CF_BOY; + Stores.boyItem._iIdentified = true; + Stores.boyItemLevel = lvl / 2; } void SpawnHealer(int lvl) @@ -4613,8 +4616,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(healitem) / sizeof(healitem[0]); ++i) { - Item &item = healitem[i]; + for (size_t i = 0; i < sizeof(Stores.healerItems) / sizeof(Stores.healerItems[0]); ++i) { + Item &item = Stores.healerItems[i]; item = {}; if (i < PinnedItemCount || (gbIsMultiplayer && i == PinnedItemCount)) { @@ -4638,7 +4641,7 @@ void SpawnHealer(int lvl) item._iIdentified = true; } - SortVendor(healitem + PinnedItemCount); + SortVendor(Stores.healerItems, PinnedItemCount); } void MakeGoldStack(Item &goldItem, int value) diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index 9cb06faae6b..0e54dfbd188 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, premiumitems[i]); + LoadAndValidateItemData(file, Stores.premiumItems[i]); } void LoadQuest(LoadHelper *file, int i) @@ -2523,8 +2523,8 @@ void LoadGame(bool firstflag) memset(dLight, 0, sizeof(dLight)); } - numpremium = file.NextBE(); - premiumlevel = file.NextBE(); + Stores.premiumItemCount = file.NextBE(); + Stores.premiumItemLevel = file.NextBE(); for (int i = 0; i < giNumberOfSmithPremiumItems; i++) LoadPremium(file, i); @@ -2786,11 +2786,11 @@ void SaveGameData(SaveWriter &saveWriter) } } - file.WriteBE(numpremium); - file.WriteBE(premiumlevel); + file.WriteBE(Stores.premiumItemCount); + file.WriteBE(Stores.premiumItemLevel); for (int i = 0; i < giNumberOfSmithPremiumItems; i++) - SaveItem(file, premiumitems[i]); + SaveItem(file, Stores.premiumItems[i]); file.WriteLE(AutomapActive ? 1 : 0); file.WriteBE(AutoMapScale); diff --git a/Source/objects.cpp b/Source/objects.cpp index 77234d98281..237add5a786 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); - TakePlrsMoney(gold); + Stores.TakePlrsMoney(gold); RedrawEverything(); @@ -2972,7 +2972,7 @@ void OperateShrineMurphys(DiabloGenerator &rng, Player &player) } } if (!broke) { - TakePlrsMoney(player._pGold / 3); + Stores.TakePlrsMoney(player._pGold / 3); } InitDiabloMsg(EMSG_SHRINE_MURPHYS); diff --git a/Source/qol/chatlog.cpp b/Source/qol/chatlog.cpp index 5e0da7a12b3..aafd72cff01 100644 --- a/Source/qol/chatlog.cpp +++ b/Source/qol/chatlog.cpp @@ -96,7 +96,7 @@ void ToggleChatLog() if (ChatLogFlag) { ChatLogFlag = false; } else { - stextflag = TalkID::None; + Stores.ExitStore(); CloseInventory(); CloseCharPanel(); SpellbookFlag = false; @@ -153,7 +153,7 @@ void AddMessageToChatLog(std::string_view message, Player *player, UiFlags flags void DrawChatLog(const Surface &out) { - DrawSTextHelp(); + Stores.DrawSTextHelp(); DrawQTextBack(out); if (SkipLines == 0) { @@ -179,7 +179,7 @@ void DrawChatLog(const Surface &out) } const int titleBottom = sy + HeaderHeight(); - DrawSLine(out, titleBottom); + Stores.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 7a485129e29..263070e2685 100644 --- a/Source/qol/itemlabels.cpp +++ b/Source/qol/itemlabels.cpp @@ -98,7 +98,7 @@ void ResetItemlabelHighlighted() bool IsHighlightingLabelsEnabled() { - return stextflag == TalkID::None && highlightKeyPressed != *sgOptions.Gameplay.showItemLabels; + return !Stores.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 - && stextflag == TalkID::None + && !Stores.IsPlayerInStore() && IsMouseOverGameArea() && LastMouseButtonAction == MouseActionType::None) { isLabelHighlighted = true; @@ -201,7 +201,7 @@ void DrawItemNameLabels(const Surface &out) pcursitem = label.id; } } - if (pcursitem == label.id && stextflag == TalkID::None) + if (pcursitem == label.id && !Stores.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 a8c77c21c79..3d2c0567d31 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 (stextflag != TalkID::None) + if (Stores.IsPlayerInStore()) return true; Item *item = &Stash.stashList[c]; diff --git a/Source/stores.cpp b/Source/stores.cpp index 5af51f51067..92cb9dd506d 100644 --- a/Source/stores.cpp +++ b/Source/stores.cpp @@ -15,16 +15,12 @@ #include "cursor.h" #include "engine/backbuffer_state.hpp" #include "engine/load_cel.hpp" -#include "engine/random.hpp" #include "engine/render/clx_render.hpp" #include "engine/render/text_render.hpp" #include "engine/trn.hpp" #include "init.h" #include "minitext.h" -#include "options.h" #include "panels/info_box.hpp" -#include "qol/stash.h" -#include "towners.h" #include "utils/format_int.hpp" #include "utils/language.h" #include "utils/str_cat.hpp" @@ -32,184 +28,162 @@ namespace devilution { -TalkID stextflag; +constexpr int PaddingTop = 32; -int storenumh; -int8_t storehidx[48]; -Item storehold[48]; +const int singleLineSpace = 1; +const int doubleLineSpace = 2; +const int tripleLineSpace = 3; -Item smithitem[SMITH_ITEMS]; -int numpremium; -int premiumlevel; -Item premiumitems[SMITH_PREMIUM_ITEMS]; +constexpr int MainMenuDividerLine = 5; +constexpr int BuySellMenuDividerLine = 3; +constexpr int BuyLineSpace = 4; -Item healitem[20]; +constexpr int WirtDialogueDrawLine = 12; +constexpr int NumWirtDialogueLines = 3; -Item witchitem[WITCH_ITEMS]; +const std::string SmithMenuHeader = "Welcome to the\n\nBlacksmith's shop"; -int boylevel; -Item boyitem; +const StoreMenuOption SmithMenuOptions[] = { + { TalkID::Gossip, "Talk to Griswold" }, + { TalkID::SmithBuy, "Buy basic items" }, + { TalkID::SmithPremiumBuy, "Buy premium items" }, + { TalkID::SmithSell, "Sell items" }, + { TalkID::SmithRepair, "Repair items" }, + { TalkID::None, "Leave the shop" } +}; -namespace { +const std::string HealerMenuHeader = "Welcome to the\n\nHealer's home"; -/** The current towner being interacted with */ -_talker_id talker; +const StoreMenuOption HealerMenuOptions[] = { + { TalkID::Gossip, "Talk to Pepin" }, + { TalkID::HealerBuy, "Buy items" }, + { TalkID::None, "Leave Healer's home" } +}; -/** Is the current dialog full size */ -bool stextsize; +const std::string BoyMenuHeader = "Wirt the Peg-legged boy"; -/** Number of text lines in the current dialog */ -int stextsmax; -/** Remember currently selected text line from stext while displaying a dialog */ -int stextlhold; -/** Currently selected text line from stext */ -int stextsel; +const StoreMenuOption BoyMenuOptions[] = { + { TalkID::Gossip, "Talk to Wirt" }, + { TalkID::BoyBuy, "What have you got?" }, + { TalkID::None, "Say goodbye" } +}; -struct STextStruct { - enum Type : uint8_t { - Label, - Divider, - Selectable, - }; +const std::string WitchMenuHeader = "Welcome to the\n\nWitch's shack"; - std::string text; - int _sval; - int y; - UiFlags flags; - Type type; - uint8_t _sx; - uint8_t _syoff; - int cursId; - bool cursIndent; +const StoreMenuOption WitchMenuOptions[] = { + { TalkID::Gossip, "Talk to Adria" }, + { TalkID::WitchBuy, "Buy items" }, + { TalkID::WitchSell, "Sell items" }, + { TalkID::WitchRecharge, "Recharge staves" }, + { TalkID::None, "Leave the shack" } +}; - [[nodiscard]] bool isDivider() const - { - return type == Divider; - } - [[nodiscard]] bool isSelectable() const - { - return type == Selectable; - } +const std::string TavernMenuHeader = "Welcome to the\n\nRising Sun"; - [[nodiscard]] bool hasText() const - { - return !text.empty(); - } +const StoreMenuOption TavernMenuOptions[] = { + { TalkID::Gossip, "Talk to Ogden" }, + { TalkID::None, "Leave the tavern" } }; -/** Text lines */ -STextStruct stext[STORE_LINES]; - -/** Whether to render the player's gold amount in the top left */ -bool RenderGold; - -/** Does the current panel have a scrollbar */ -bool stextscrl; -/** Remember last scroll position */ -int stextvhold; -/** Scroll position */ -int stextsval; -/** Next scroll position */ -int stextdown; -/** Previous scroll position */ -int stextup; -/** Countdown for the push state of the scroll up button */ -int8_t stextscrlubtn; -/** Countdown for the push state of the scroll down button */ -int8_t stextscrldbtn; - -/** Remember current store while displaying a dialog */ -TalkID stextshold; - -/** Temporary item used to hold the item being traded */ -Item StoreItem; - -/** Maps from towner IDs to NPC names. */ -const char *const TownerNames[] = { - N_("Griswold"), - N_("Pepin"), - "", - N_("Ogden"), - N_("Cain"), - N_("Farnham"), - N_("Adria"), - N_("Gillian"), - N_("Wirt"), +const std::string BarmaidMenuHeader = "Gillian"; + +const StoreMenuOption BarmaidMenuOptions[] = { + { TalkID::Gossip, "Talk to Gillian" }, + { TalkID::None, "Say goodbye" } }; -constexpr int PaddingTop = 32; +const std::string DrunkMenuHeader = "Farnham the Drunk"; -// For most languages, line height is always 12. -// This includes blank lines and divider line. -constexpr int SmallLineHeight = 12; -constexpr int SmallTextHeight = 12; +const StoreMenuOption DrunkMenuOptions[] = { + { TalkID::Gossip, "Talk to Farnham" }, + { TalkID::None, "Say goodbye" } +}; -// 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; +const std::string StorytellerMenuHeader = "The Town Elder"; -/** - * 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 stextscrl ? 21 : 20; - } - return 22; -} - -int LineHeight() -{ - return IsSmallFontTall() ? LargeLineHeight : SmallLineHeight; -} +const StoreMenuOption StorytellerMenuOptions[] = { + { TalkID::Gossip, "Talk to Cain" }, + { TalkID::StorytellerIdentify, "Identify an item" }, + { TalkID::None, "Say goodbye" } +}; -int TextHeight() -{ - return IsSmallFontTall() ? LargeTextHeight : SmallTextHeight; -} +const TownerLine TownerLines[] = { + { SmithMenuHeader, SmithMenuOptions, sizeof(SmithMenuOptions) / sizeof(StoreMenuOption) }, + { HealerMenuHeader, HealerMenuOptions, sizeof(HealerMenuOptions) / sizeof(StoreMenuOption) }, + {}, + { TavernMenuHeader, TavernMenuOptions, sizeof(TavernMenuOptions) / sizeof(StoreMenuOption) }, + { StorytellerMenuHeader, StorytellerMenuOptions, sizeof(StorytellerMenuOptions) / sizeof(StoreMenuOption) }, + { DrunkMenuHeader, DrunkMenuOptions, sizeof(DrunkMenuOptions) / sizeof(StoreMenuOption) }, + { WitchMenuHeader, WitchMenuOptions, sizeof(WitchMenuOptions) / sizeof(StoreMenuOption) }, + { BarmaidMenuHeader, BarmaidMenuOptions, sizeof(BarmaidMenuOptions) / sizeof(StoreMenuOption) }, + { BoyMenuHeader, BoyMenuOptions, sizeof(BoyMenuOptions) / sizeof(StoreMenuOption) }, + {}, + {}, + {}, + {}, +}; -void CalculateLineHeights() -{ - stext[0].y = 0; +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() +{ + textLine[0].y = 0; if (IsSmallFontTall()) { - for (int i = 1; i < STORE_LINES; ++i) { + for (int i = 1; i < NumStoreLines; ++i) { // Space out consecutive text lines, unless they are both selectable (never the case currently). - if (stext[i].hasText() && stext[i - 1].hasText() && !(stext[i].isSelectable() && stext[i - 1].isSelectable())) { - stext[i].y = stext[i - 1].y + LargeTextHeight; + if (textLine[i].hasText() && textLine[i - 1].hasText() && !(textLine[i].isSelectable() && textLine[i - 1].isSelectable())) { + textLine[i].y = textLine[i - 1].y + LargeTextHeight; } else { - stext[i].y = i * LargeLineHeight; + textLine[i].y = i * LargeLineHeight; } } } else { - for (int i = 1; i < STORE_LINES; ++i) { - stext[i].y = i * SmallLineHeight; + for (int i = 1; i < NumStoreLines; ++i) { + textLine[i].y = i * SmallLineHeight; } } } -void DrawSTextBack(const Surface &out) +void StoreSession::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 DrawSSlider(const Surface &out, int y1, int y2) +void StoreSession::DrawScrollbar(const Surface &out, int y1, int y2) const { const Point uiPosition = GetUIRectangle().position; int yd1 = y1 * 12 + 44 + uiPosition.y; int yd2 = y2 * 12 + 44 + uiPosition.y; - if (stextscrlubtn != -1) + if (countdownScrollUp != -1) ClxDraw(out, { uiPosition.x + 601, yd1 }, (*pSTextSlidCels)[11]); else ClxDraw(out, { uiPosition.x + 601, yd1 }, (*pSTextSlidCels)[9]); - if (stextscrldbtn != -1) + if (countdownScrollDown != -1) ClxDraw(out, { uiPosition.x + 601, yd2 }, (*pSTextSlidCels)[10]); else ClxDraw(out, { uiPosition.x + 601, yd2 }, (*pSTextSlidCels)[8]); @@ -218,66 +192,61 @@ void DrawSSlider(const Surface &out, int y1, int y2) for (; yd3 < yd2; yd3 += 12) { ClxDraw(out, { uiPosition.x + 601, yd3 }, (*pSTextSlidCels)[13]); } - if (stextsel == BackButtonLine()) - yd3 = stextlhold; + if (currentTextLine == BackButtonLine()) + yd3 = oldTextLine; else - yd3 = stextsel; - if (storenumh > 1) - yd3 = 1000 * (stextsval + ((yd3 - stextup) / 4)) / (storenumh - 1) * (y2 * 12 - y1 * 12 - 24) / 1000; + yd3 = currentTextLine; + if (currentItemIndex > 1) + yd3 = 1000 * (scrollPos + ((yd3 - previousScrollPos) / 4)) / (currentItemIndex - 1) * (y2 * 12 - y1 * 12 - 24) / 1000; else yd3 = 0; ClxDraw(out, { uiPosition.x + 601, (y1 + 1) * 12 + 44 + uiPosition.y + yd3 }, (*pSTextSlidCels)[12]); } -void AddSLine(size_t y) -{ - stext[y]._sx = 0; - stext[y]._syoff = 0; - stext[y].text.clear(); - stext[y].text.shrink_to_fit(); - stext[y].type = STextStruct::Divider; - stext[y].cursId = -1; - stext[y].cursIndent = false; -} - -void AddSTextVal(size_t y, int val) +void StoreSession::SetLineAsDivider(size_t y) { - stext[y]._sval = val; + textLine[y]._sx = 0; + textLine[y]._syoff = 0; + textLine[y].text.clear(); + textLine[y].text.shrink_to_fit(); + textLine[y].type = STextStruct::Divider; + textLine[y].cursId = -1; + textLine[y].cursIndent = false; } -void AddSText(uint8_t x, size_t y, std::string_view text, UiFlags flags, bool sel, int cursId = -1, bool 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*/) { - stext[y]._sx = x; - stext[y]._syoff = 0; - stext[y].text.clear(); - stext[y].text.append(text); - stext[y].flags = flags; - stext[y].type = sel ? STextStruct::Selectable : STextStruct::Label; - stext[y].cursId = cursId; - stext[y].cursIndent = cursIndent; + textLine[y]._sx = x; + textLine[y]._syoff = 0; + textLine[y].text.clear(); + textLine[y].text.append(text); + textLine[y].flags = flags; + textLine[y].type = sel ? STextStruct::Selectable : STextStruct::Label; + textLine[y].cursId = cursId; + textLine[y].cursIndent = cursIndent; } -void AddOptionsBackButton() +void StoreSession::SetLineAsOptionsBackButton() { const int line = BackButtonLine(); - AddSText(0, line, _("Back"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - stext[line]._syoff = IsSmallFontTall() ? 0 : 6; + SetLineText(0, line, _("Back"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); + textLine[line]._syoff = IsSmallFontTall() ? 0 : 6; } -void AddItemListBackButton(bool selectable = false) +void StoreSession::AddItemListBackButton(TalkID talkId, bool selectable /*= false*/) { const int line = BackButtonLine(); - std::string_view text = _("Back"); + std::string_view text = talkId == TalkID::BoyBuy ? _("Leave") : _("Back"); if (!selectable && IsSmallFontTall()) { - AddSText(0, line, text, UiFlags::ColorWhite | UiFlags::AlignRight, selectable); + SetLineText(0, line, text, UiFlags::ColorWhite | UiFlags::AlignRight, selectable); } else { - AddSLine(line - 1); - AddSText(0, line, text, UiFlags::ColorWhite | UiFlags::AlignCenter, selectable); - stext[line]._syoff = 6; + SetLineAsDivider(line - 1); + SetLineText(0, line, text, UiFlags::ColorWhite | UiFlags::AlignCenter, selectable); + textLine[line]._syoff = 6; } } -void PrintStoreItem(const Item &item, int l, UiFlags flags, bool cursIndent = false) +void StoreSession::PrintStoreItem(const Item &item, int l, UiFlags flags, bool cursIndent /*= false*/) { std::string productLine; @@ -299,7 +268,7 @@ void PrintStoreItem(const Item &item, int l, UiFlags flags, bool cursIndent = fa productLine.append(fmt::format(fmt::runtime(_("Charges: {:d}/{:d}")), item._iCharges, item._iMaxCharges)); } if (!productLine.empty()) { - AddSText(40, l, productLine, flags, false, -1, cursIndent); + SetLineText(40, l, productLine, flags, false, -1, cursIndent); l++; productLine.clear(); } @@ -330,256 +299,490 @@ void PrintStoreItem(const Item &item, int l, UiFlags flags, bool cursIndent = fa if (dex != 0) productLine.append(fmt::format(fmt::runtime(_(" {:d} Dex")), dex)); } - AddSText(40, l++, productLine, flags, false, -1, cursIndent); + SetLineText(40, l++, productLine, flags, false, -1, cursIndent); } -bool StoreAutoPlace(Item &item, bool persistItem) +bool StoreSession::GiveItemToPlayer(Item &item, bool persistItem) { - Player &player = *MyPlayer; - if (AutoEquipEnabled(player, item) && AutoEquip(player, item, persistItem, true)) { + if (AutoEquipEnabled(*MyPlayer, item) && AutoEquip(*MyPlayer, item, persistItem, true)) { return true; } - if (AutoPlaceItemInBelt(player, item, persistItem, true)) { + if (AutoPlaceItemInBelt(*MyPlayer, item, persistItem, true)) { return true; } - return AutoPlaceItemInInventory(player, item, persistItem, true); + return AutoPlaceItemInInventory(*MyPlayer, item, persistItem, true); } -void ScrollVendorStore(Item *itemData, int storeLimit, int idx, int selling = true) +void StoreSession::SetupCannotAffordScreen() { - ClearSText(5, 21); - stextup = 5; + StartStore(oldActiveStore); + hasScrollbar = false; + isTextFullSize = true; + renderGold = true; + ClearSText(5, 23); + SetLineText(0, 14, _("You do not have enough gold"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); +} - for (int l = 5; l < 20 && idx < storeLimit; l += 4) { - const Item &item = itemData[idx]; - if (!item.isEmpty()) { - UiFlags itemColor = item.getTextColorWithStatCheck(); - AddSText(20, l, item.getName(), itemColor, true, item._iCurs, true); - AddSTextVal(l, item._iIdentified ? item._iIvalue : item._ivalue); - PrintStoreItem(item, l + 1, itemColor, true); - stextdown = l; - } else { - l -= 4; - } - idx++; - } - if (selling) { - if (stextsel != -1 && !stext[stextsel].isSelectable() && stextsel != BackButtonLine()) - stextsel = stextdown; - } else { - stextsmax = std::max(static_cast(storeLimit) - 4, 0); +void StoreSession::SetupNoRoomScreen() +{ + StartStore(oldActiveStore); + hasScrollbar = false; + ClearSText(5, 23); + SetLineText(0, 14, _("You do not have enough room in inventory"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); +} + +void StoreSession::SetupConfirmScreen(Item &item) +{ + StartStore(oldActiveStore); + hasScrollbar = false; + ClearSText(5, 23); + + UiFlags itemColor = item.getTextColorWithStatCheck(); + SetLineText(20, 8, item.getName(), itemColor, false); + SetLineValue(8, item._iIvalue); + PrintStoreItem(item, 9, itemColor); + + std::string_view prompt; + + switch (oldActiveStore) { + case TalkID::BoyBuy: + prompt = _("Do we have a deal?"); + break; + case TalkID::StorytellerIdentify: + prompt = _("Are you sure you want to identify this item?"); + break; + case TalkID::HealerBuy: + case TalkID::SmithPremiumBuy: + case TalkID::WitchBuy: + case TalkID::SmithBuy: + prompt = _("Are you sure you want to buy this item?"); + break; + case TalkID::WitchRecharge: + prompt = _("Are you sure you want to recharge this item?"); + break; + case TalkID::SmithSell: + case TalkID::WitchSell: + prompt = _("Are you sure you want to sell this item?"); + break; + case TalkID::SmithRepair: + prompt = _("Are you sure you want to repair this item?"); + break; + default: + app_fatal(StrCat("Unknown store dialog ", static_cast(oldActiveStore))); } + SetLineText(0, 15, prompt, UiFlags::ColorWhite | UiFlags::AlignCenter, false); + SetLineText(0, 18, _("Yes"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); + SetLineText(0, 20, _("No"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); } -void StartSmith() +void StoreSession::SetupGossipScreen() { - stextsize = false; - stextscrl = false; - AddSText(0, 1, _("Welcome to the"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); - AddSText(0, 3, _("Blacksmith's shop"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); - AddSText(0, 7, _("Would you like to:"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); - AddSText(0, 10, _("Talk to Griswold"), UiFlags::ColorBlue | UiFlags::AlignCenter, true); - AddSText(0, 12, _("Buy basic items"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - AddSText(0, 14, _("Buy premium items"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - AddSText(0, 16, _("Sell items"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - AddSText(0, 18, _("Repair items"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - AddSText(0, 20, _("Leave the shop"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - AddSLine(5); - storenumh = 20; + int la; + + isTextFullSize = false; + hasScrollbar = false; + + SetLineText(0, 2, fmt::format(fmt::runtime(_("Talk to {:s}")), "" /*towner name*/), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); + SetLineAsDivider(5); + if (gbIsSpawn) { + SetLineText(0, 10, fmt::format(fmt::runtime(_("Talking to {:s}")), "" /*towner name*/), UiFlags::ColorWhite | UiFlags::AlignCenter, false); + + SetLineText(0, 12, _("is not available"), UiFlags::ColorWhite | UiFlags::AlignCenter, false); + SetLineText(0, 14, _("in the shareware"), UiFlags::ColorWhite | UiFlags::AlignCenter, false); + SetLineText(0, 16, _("version"), UiFlags::ColorWhite | UiFlags::AlignCenter, false); + SetLineAsOptionsBackButton(); + return; + } + + int sn = 0; + for (auto &quest : Quests) { + if (quest._qactive == QUEST_ACTIVE && QuestDialogTable[townerId][quest._qidx] != TEXT_NONE && quest._qlog) + sn++; + } + + if (sn > 6) { + sn = 14 - (sn / 2); + la = 1; + } else { + sn = 15 - sn; + la = 2; + } + + int sn2 = sn - 2; + + for (auto &quest : Quests) { + if (quest._qactive == QUEST_ACTIVE && QuestDialogTable[townerId][quest._qidx] != TEXT_NONE && quest._qlog) { + SetLineText(0, sn, _(QuestsData[quest._qidx]._qlstr), UiFlags::ColorWhite | UiFlags::AlignCenter, true); + sn += la; + } + } + SetLineText(0, sn2, _("Gossip"), UiFlags::ColorBlue | UiFlags::AlignCenter, true); + SetLineAsOptionsBackButton(); } -void ScrollSmithBuy(int idx) +void StoreSession::SetMenuHeader(const std::string &header) { - ScrollVendorStore(smithitem, static_cast(std::size(smithitem)), idx); + // Check if the header contains "\n\n", which indicates a two-line header + std::string::size_type pos = header.find("\n\n"); + + if (pos != std::string::npos) { + // Split the header into two parts for a two-line header + std::string header1 = header.substr(0, pos); + std::string header2 = header.substr(pos + 2); + + // Set the headers on lines 1 and 3 + SetLineText(0, 1, header1, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); + SetLineText(0, 3, header2, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); + } else { + // If there's no "\n\n", treat it as a single-line header + SetLineText(0, 2, header, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); + } } -uint32_t TotalPlayerGold() +void StoreSession::SetMenuText(const TownerLine &townerInfo) { - return MyPlayer->_pGold + Stash.gold; + const UiFlags flags = UiFlags::ColorWhitegold | UiFlags::AlignCenter; + + int startLine = MainMenuDividerLine + singleLineSpace; + + if (townerId != TOWN_PEGBOY) { + currentMenuDrawLine = townerInfo.numOptions > 5 ? startLine + singleLineSpace : startLine + tripleLineSpace; + SetLineText(0, currentMenuDrawLine, _("Would you like to:"), flags, false); + currentMenuDrawLine += tripleLineSpace; + } else if (!boyItem.isEmpty()) { + currentMenuDrawLine = WirtDialogueDrawLine; + SetLineText(0, currentMenuDrawLine, _("I have something for sale,"), flags, false); + currentMenuDrawLine += doubleLineSpace; + SetLineText(0, currentMenuDrawLine, _("but it will cost 50 gold"), flags, false); + currentMenuDrawLine += doubleLineSpace; + SetLineText(0, currentMenuDrawLine, _("just to take a look. "), flags, false); + currentMenuDrawLine = WirtDialogueDrawLine - (doubleLineSpace * 2); // Needed to draw first Wirt menu option far away enough from dialogue lines. + } else { + currentMenuDrawLine = startLine + (tripleLineSpace * 2); + } } -// TODO: Change `_iIvalue` to be unsigned instead of passing `int` here. -bool PlayerCanAfford(int price) +void StoreSession::SetMenuOption(TalkID action, const std::string_view &text) { - return TotalPlayerGold() >= static_cast(price); + UiFlags flags = action == TalkID::Gossip ? UiFlags::ColorBlue | UiFlags::AlignCenter : UiFlags::ColorWhite | UiFlags::AlignCenter; + + // Set leave option as the last menu option, trying for line 18 if there's room, otherwise line 20. + if (action == TalkID::None) { + currentMenuDrawLine = currentMenuDrawLine < 18 ? 18 : 20; + } + + SetLineText(0, currentMenuDrawLine, text, flags, true); + storeLineMapping[currentMenuDrawLine] = action; + currentMenuDrawLine += 2; + + if (townerId == TOWN_PEGBOY && !boyItem.isEmpty() && currentMenuDrawLine == (WirtDialogueDrawLine - doubleLineSpace)) { + currentMenuDrawLine = WirtDialogueDrawLine + (doubleLineSpace * 3); + } } -void StartSmithBuy() +void StoreSession::SetupTownerMenuScreen() { - stextsize = true; - stextscrl = true; - stextsval = 0; + RestoreResource(); - RenderGold = true; - AddSText(20, 1, _("I have these items for sale:"), UiFlags::ColorWhitegold, false); - AddSLine(3); - ScrollSmithBuy(stextsval); - AddItemListBackButton(); + isTextFullSize = false; + hasScrollbar = false; - storenumh = 0; - for (Item &item : smithitem) { - if (item.isEmpty()) - continue; + const TownerLine &lines = TownerLines[townerId]; - item._iStatFlag = MyPlayer->CanUseItem(item); - storenumh++; + SetMenuHeader(lines.menuHeader); + + SetLineAsDivider(MainMenuDividerLine); + + SetMenuText(lines); + + for (size_t i = 0; i < lines.numOptions; i++) { + const StoreMenuOption &option = lines.menuOptions[i]; + if (option.action == TalkID::BoyBuy && boyItem.isEmpty()) + continue; + SetMenuOption(option.action, option.text); } - stextsmax = std::max(storenumh - 4, 0); + currentItemIndex = 20; } -void ScrollSmithPremiumBuy(int boughtitems) +void StoreSession::SetupItemList(TalkID talkId, Item *itemData, int storeLimit, int idx, bool selling /*= true*/) { - int idx = 0; - for (; boughtitems != 0; idx++) { - if (!premiumitems[idx].isEmpty()) - boughtitems--; - } + ClearSText(5, 21); + previousScrollPos = 5; + + if (talkId == TalkID::BoyBuy) { + const Item &item = itemData[idx]; + UiFlags itemColor = item.getTextColorWithStatCheck(); + SetLineText(20, 10, item.getName(), itemColor, true, item._iCurs, true); + if (gbIsHellfire) + SetLineValue(10, item._iIvalue - (item._iIvalue / 4)); + else + SetLineValue(10, item._iIvalue + (item._iIvalue / 2)); + PrintStoreItem(item, 11, itemColor, true); + } else { + for (int l = 5; l < 20 && idx < storeLimit; l += 4) { + const Item &item = itemData[idx]; + if (!item.isEmpty()) { + UiFlags itemColor = item.getTextColorWithStatCheck(); + SetLineText(20, l, item.getName(), itemColor, true, item._iCurs, true); + SetLineValue(l, item._iIdentified ? item._iIvalue : item._ivalue); + PrintStoreItem(item, l + 1, itemColor, true); + nextScrollPos = l; + } else { + l -= 4; + } + idx++; + } - ScrollVendorStore(premiumitems, static_cast(std::size(premiumitems)), idx); + if (selling) { + if (currentTextLine != -1 && !textLine[currentTextLine].isSelectable() && currentTextLine != BackButtonLine()) + currentTextLine = nextScrollPos; + } else { + numTextLines = std::max(storeLimit - BuyLineSpace, 0); + } + } } -bool StartSmithPremiumBuy() +void StoreSession::SetupTownerItemList(TalkID talkId, int idx, bool selling /*= true*/) { - storenumh = 0; - for (Item &item : premiumitems) { - if (item.isEmpty()) - continue; + Item *itemArray = nullptr; + int storeLimit = 0; - item._iStatFlag = MyPlayer->CanUseItem(item); - storenumh++; + switch (talkId) { + case TalkID::SmithBuy: + itemArray = smithItems.data(); + storeLimit = static_cast(std::size(smithItems)); + break; + case TalkID::SmithPremiumBuy: + itemArray = premiumItems.data(); + storeLimit = premiumItemCount; + break; + case TalkID::HealerBuy: + itemArray = healerItems.data(); + storeLimit = static_cast(std::size(healerItems)); + break; + case TalkID::WitchBuy: + itemArray = witchItems.data(); + storeLimit = static_cast(std::size(witchItems)); + break; + case TalkID::BoyBuy: + itemArray = &boyItem; + storeLimit = 1; // Wirt only sells one item at a time + break; + case TalkID::SmithSell: + case TalkID::SmithRepair: + case TalkID::WitchSell: + case TalkID::WitchRecharge: + case TalkID::StorytellerIdentify: + itemArray = playerItems.data(); + storeLimit = currentItemIndex; + break; + default: + return; // Invalid TalkID, do nothing } - if (storenumh == 0) { - StartStore(TalkID::Smith); - stextsel = 14; - return false; + + if (itemArray != nullptr) { + SetupItemList(talkId, itemArray, storeLimit, idx, selling); } +} - stextsize = true; - stextscrl = true; - stextsval = 0; +void StoreSession::SetBuyScreenHeader() +{ + const UiFlags flags = UiFlags::ColorWhitegold; - RenderGold = true; - AddSText(20, 1, _("I have these premium items for sale:"), UiFlags::ColorWhitegold, false); - AddSLine(3); - AddItemListBackButton(); + if (currentItemIndex > 1) + SetLineText(20, 1, _("I have these items for sale:"), flags, false); + else + SetLineText(20, 1, _("I have this item for sale:"), flags, false); +} - stextsmax = std::max(storenumh - 4, 0); +void StoreSession::UpdateBookMinMagic(Item &bookItem) +{ + if (bookItem._iMiscId != IMISC_BOOK) + return; + bookItem._iMinMag = GetSpellData(bookItem._iSpell).minInt; + uint8_t spellLevel = MyPlayer->_pSplLvl[static_cast(bookItem._iSpell)]; + while (spellLevel > 0) { + bookItem._iMinMag += 20 * bookItem._iMinMag / 100; + spellLevel--; + if (bookItem._iMinMag + 20 * bookItem._iMinMag / 100 > 255) { + bookItem._iMinMag = 255; + spellLevel = 0; + } + } +} - ScrollSmithPremiumBuy(stextsval); +void StoreSession::UpdateItemStatFlags(TalkID talkId) +{ + currentItemIndex = 0; - return true; + switch (talkId) { + case TalkID::SmithBuy: + for (Item &item : smithItems) { + if (!item.isEmpty()) { + item._iStatFlag = MyPlayer->CanUseItem(item); + currentItemIndex++; + } + } + break; + case TalkID::SmithPremiumBuy: + for (Item &item : premiumItems) { + if (!item.isEmpty()) { + item._iStatFlag = MyPlayer->CanUseItem(item); + currentItemIndex++; + } + } + break; + case TalkID::WitchBuy: + for (Item &item : witchItems) { + if (!item.isEmpty()) { + UpdateBookMinMagic(item); + item._iStatFlag = MyPlayer->CanUseItem(item); + currentItemIndex++; + } + } + break; + case TalkID::HealerBuy: + for (Item &item : healerItems) { + if (!item.isEmpty()) { + item._iStatFlag = MyPlayer->CanUseItem(item); + currentItemIndex++; + } + } + break; + case TalkID::BoyBuy: + if (!boyItem.isEmpty()) { + boyItem._iStatFlag = MyPlayer->CanUseItem(boyItem); + currentItemIndex = 1; + } + break; + default: + break; + } } -bool SmithSellOk(int i) +void StoreSession::SetupTownerBuyScreen(TalkID talkId) { - Item *pI; + isTextFullSize = true; + scrollPos = 0; + renderGold = true; - if (i >= 0) { - pI = &MyPlayer->InvList[i]; - } else { - pI = &MyPlayer->SpdList[-(i + 1)]; + if (talkId == TalkID::WitchBuy) { + numTextLines = 20; } - if (pI->isEmpty()) - return false; + SetLineAsDivider(BuySellMenuDividerLine); + SetupTownerItemList(talkId, scrollPos); + UpdateItemStatFlags(talkId); + hasScrollbar = currentItemIndex > BuyLineSpace ? true : false; + numTextLines = std::max(currentItemIndex - BuyLineSpace, 0); + SetBuyScreenHeader(); + AddItemListBackButton(talkId, true); +} - if (pI->_iMiscId > IMISC_OILFIRST && pI->_iMiscId < IMISC_OILLAST) - return true; +bool StoreSession::CanSellToTowner(TalkID talkId, int i) +{ + Item *pI = (i >= 0) ? &MyPlayer->InvList[i] : &MyPlayer->SpdList[-(i + 1)]; - if (pI->_itype == ItemType::Misc) - return false; - if (pI->_itype == ItemType::Gold) - return false; - if (pI->_itype == ItemType::Staff && (!gbIsHellfire || IsValidSpell(pI->_iSpell))) - return false; - if (pI->_iClass == ICLASS_QUEST) + if (pI->isEmpty()) return false; - if (pI->IDidx == IDI_LAZSTAFF) + + if (pI->_itype == ItemType::Gold || pI->_iClass == ICLASS_QUEST || pI->IDidx == IDI_LAZSTAFF) return false; - return true; -} + switch (talkId) { + case TalkID::SmithSell: + if (pI->_iMiscId > IMISC_OILFIRST && pI->_iMiscId < IMISC_OILLAST) + return true; + if (pI->_itype == ItemType::Misc) + return false; + if (pI->_itype == ItemType::Staff && (!gbIsHellfire || IsValidSpell(pI->_iSpell))) + return false; + return true; -void ScrollSmithSell(int idx) -{ - ScrollVendorStore(storehold, storenumh, idx, false); + case TalkID::WitchSell: + if (pI->_itype == ItemType::Misc && (pI->_iMiscId <= 29 || pI->_iMiscId >= 41)) + return false; + if (pI->_itype == ItemType::Staff && (!gbIsHellfire || IsValidSpell(pI->_iSpell))) + return true; + return pI->_itype == ItemType::Misc; + + default: + return false; + } } -void StartSmithSell() +void StoreSession::SetupTownerSellScreen(TalkID talkId) { - stextsize = true; + isTextFullSize = true; bool sellOk = false; - storenumh = 0; + currentItemIndex = 0; - for (auto &item : storehold) { + for (auto &item : playerItems) { item.clear(); } - const Player &myPlayer = *MyPlayer; - - for (int8_t i = 0; i < myPlayer._pNumInv; i++) { - if (storenumh >= 48) + for (int8_t i = 0; i < MyPlayer->_pNumInv; i++) { + if (currentItemIndex >= 48) break; - if (SmithSellOk(i)) { + if (CanSellToTowner(talkId, i)) { sellOk = true; - storehold[storenumh] = myPlayer.InvList[i]; + playerItems[currentItemIndex] = MyPlayer->InvList[i]; - if (storehold[storenumh]._iMagical != ITEM_QUALITY_NORMAL && storehold[storenumh]._iIdentified) - storehold[storenumh]._ivalue = storehold[storenumh]._iIvalue; + if (playerItems[currentItemIndex]._iMagical != ITEM_QUALITY_NORMAL && playerItems[currentItemIndex]._iIdentified) + playerItems[currentItemIndex]._ivalue = playerItems[currentItemIndex]._iIvalue; - storehold[storenumh]._ivalue = std::max(storehold[storenumh]._ivalue / 4, 1); - storehold[storenumh]._iIvalue = storehold[storenumh]._ivalue; - storehidx[storenumh] = i; - storenumh++; + playerItems[currentItemIndex]._ivalue = std::max(playerItems[currentItemIndex]._ivalue / 4, 1); + playerItems[currentItemIndex]._iIvalue = playerItems[currentItemIndex]._ivalue; + playerItemIndexes[currentItemIndex] = i; + currentItemIndex++; } } for (int i = 0; i < MaxBeltItems; i++) { - if (storenumh >= 48) + if (currentItemIndex >= 48) break; - if (SmithSellOk(-(i + 1))) { + if (CanSellToTowner(talkId, i)) { sellOk = true; - storehold[storenumh] = myPlayer.SpdList[i]; + playerItems[currentItemIndex] = MyPlayer->SpdList[i]; - if (storehold[storenumh]._iMagical != ITEM_QUALITY_NORMAL && storehold[storenumh]._iIdentified) - storehold[storenumh]._ivalue = storehold[storenumh]._iIvalue; + if (playerItems[currentItemIndex]._iMagical != ITEM_QUALITY_NORMAL && playerItems[currentItemIndex]._iIdentified) + playerItems[currentItemIndex]._ivalue = playerItems[currentItemIndex]._iIvalue; - storehold[storenumh]._ivalue = std::max(storehold[storenumh]._ivalue / 4, 1); - storehold[storenumh]._iIvalue = storehold[storenumh]._ivalue; - storehidx[storenumh] = -(i + 1); - storenumh++; + playerItems[currentItemIndex]._ivalue = std::max(playerItems[currentItemIndex]._ivalue / 4, 1); + playerItems[currentItemIndex]._iIvalue = playerItems[currentItemIndex]._ivalue; + playerItemIndexes[currentItemIndex] = -(i + 1); + currentItemIndex++; } } if (!sellOk) { - stextscrl = false; + hasScrollbar = false; - RenderGold = true; - AddSText(20, 1, _("You have nothing I want."), UiFlags::ColorWhitegold, false); - AddSLine(3); - AddItemListBackButton(/*selectable=*/true); + renderGold = true; + SetLineText(20, 1, _("You have nothing I want."), UiFlags::ColorWhitegold, false); + SetLineAsDivider(BuySellMenuDividerLine); + AddItemListBackButton(talkId, /*selectable=*/true); return; } - stextscrl = true; - stextsval = 0; - stextsmax = myPlayer._pNumInv; + hasScrollbar = true; + scrollPos = 0; + numTextLines = MyPlayer->_pNumInv; - RenderGold = true; - AddSText(20, 1, _("Which item is for sale?"), UiFlags::ColorWhitegold, false); - AddSLine(3); - ScrollSmithSell(stextsval); - AddItemListBackButton(); + renderGold = true; + SetLineText(20, 1, _("Which item is for sale?"), UiFlags::ColorWhitegold, false); + SetLineAsDivider(BuySellMenuDividerLine); + SetupTownerItemList(talkId, scrollPos, false); + AddItemListBackButton(talkId); } -bool SmithRepairOk(int i) +bool StoreSession::CanTownerRepair(int i) const { - const Player &myPlayer = *MyPlayer; - const Item &item = myPlayer.InvList[i]; + const Item &item = MyPlayer->InvList[i]; if (item.isEmpty()) return false; @@ -595,239 +798,89 @@ bool SmithRepairOk(int i) return true; } -void StartSmithRepair() +void StoreSession::AddPlayerItemToRepairList(Item *itm, int8_t i) { - stextsize = true; - storenumh = 0; - - for (auto &item : storehold) { - item.clear(); - } - - Player &myPlayer = *MyPlayer; - - auto &helmet = myPlayer.InvBody[INVLOC_HEAD]; - if (!helmet.isEmpty() && helmet._iDurability != helmet._iMaxDur) { - AddStoreHoldRepair(&helmet, -1); - } - - auto &armor = myPlayer.InvBody[INVLOC_CHEST]; - if (!armor.isEmpty() && armor._iDurability != armor._iMaxDur) { - AddStoreHoldRepair(&armor, -2); - } - - auto &leftHand = myPlayer.InvBody[INVLOC_HAND_LEFT]; - if (!leftHand.isEmpty() && leftHand._iDurability != leftHand._iMaxDur) { - AddStoreHoldRepair(&leftHand, -3); - } - - auto &rightHand = myPlayer.InvBody[INVLOC_HAND_RIGHT]; - if (!rightHand.isEmpty() && rightHand._iDurability != rightHand._iMaxDur) { - AddStoreHoldRepair(&rightHand, -4); - } - - for (int i = 0; i < myPlayer._pNumInv; i++) { - if (storenumh >= 48) - break; - if (SmithRepairOk(i)) { - AddStoreHoldRepair(&myPlayer.InvList[i], i); - } - } + Item *item; + int v; - if (storenumh == 0) { - stextscrl = false; + item = &playerItems[currentItemIndex]; + playerItems[currentItemIndex] = *itm; - RenderGold = true; - AddSText(20, 1, _("You have nothing to repair."), UiFlags::ColorWhitegold, false); - AddSLine(3); - AddItemListBackButton(/*selectable=*/true); - return; + int dur = item->_iMaxDur - item->_iDurability; + if (item->_iMagical != ITEM_QUALITY_NORMAL && item->_iIdentified) { + v = 30 * item->_iIvalue * dur / (item->_iMaxDur * 100 * 2); + if (v == 0) + return; + } else { + v = item->_ivalue * dur / (item->_iMaxDur * 2); + v = std::max(v, 1); } - - stextscrl = true; - stextsval = 0; - stextsmax = myPlayer._pNumInv; - - RenderGold = true; - AddSText(20, 1, _("Repair which item?"), UiFlags::ColorWhitegold, false); - AddSLine(3); - - ScrollSmithSell(stextsval); - AddItemListBackButton(); + item->_iIvalue = v; + item->_ivalue = v; + playerItemIndexes[currentItemIndex] = i; + currentItemIndex++; } -void FillManaPlayer() +void StoreSession::SetupTownerRepairScreen() { - if (!*sgOptions.Gameplay.adriaRefillsMana) - return; + isTextFullSize = true; + currentItemIndex = 0; - Player &myPlayer = *MyPlayer; - - if (myPlayer._pMana != myPlayer._pMaxMana) { - PlaySFX(SfxID::CastHealing); + for (auto &item : playerItems) { + item.clear(); } - myPlayer._pMana = myPlayer._pMaxMana; - myPlayer._pManaBase = myPlayer._pMaxManaBase; - RedrawComponent(PanelDrawComponent::Mana); -} -void StartWitch() -{ - FillManaPlayer(); - stextsize = false; - stextscrl = false; - AddSText(0, 2, _("Witch's shack"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); - AddSText(0, 9, _("Would you like to:"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); - AddSText(0, 12, _("Talk to Adria"), UiFlags::ColorBlue | UiFlags::AlignCenter, true); - AddSText(0, 14, _("Buy items"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - AddSText(0, 16, _("Sell items"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - AddSText(0, 18, _("Recharge staves"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - AddSText(0, 20, _("Leave the shack"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - AddSLine(5); - storenumh = 20; -} - -void ScrollWitchBuy(int idx) -{ - ScrollVendorStore(witchitem, static_cast(std::size(witchitem)), idx); -} - -void WitchBookLevel(Item &bookItem) -{ - if (bookItem._iMiscId != IMISC_BOOK) - return; - bookItem._iMinMag = GetSpellData(bookItem._iSpell).minInt; - uint8_t spellLevel = MyPlayer->_pSplLvl[static_cast(bookItem._iSpell)]; - while (spellLevel > 0) { - bookItem._iMinMag += 20 * bookItem._iMinMag / 100; - spellLevel--; - if (bookItem._iMinMag + 20 * bookItem._iMinMag / 100 > 255) { - bookItem._iMinMag = 255; - spellLevel = 0; - } + auto &helmet = MyPlayer->InvBody[INVLOC_HEAD]; + if (!helmet.isEmpty() && helmet._iDurability != helmet._iMaxDur) { + AddPlayerItemToRepairList(&helmet, -1); } -} - -void StartWitchBuy() -{ - stextsize = true; - stextscrl = true; - stextsval = 0; - stextsmax = 20; - - RenderGold = true; - AddSText(20, 1, _("I have these items for sale:"), UiFlags::ColorWhitegold, false); - AddSLine(3); - ScrollWitchBuy(stextsval); - AddItemListBackButton(); - - storenumh = 0; - for (Item &item : witchitem) { - if (item.isEmpty()) - continue; - WitchBookLevel(item); - item._iStatFlag = MyPlayer->CanUseItem(item); - storenumh++; + auto &armor = MyPlayer->InvBody[INVLOC_CHEST]; + if (!armor.isEmpty() && armor._iDurability != armor._iMaxDur) { + AddPlayerItemToRepairList(&armor, -2); } - stextsmax = std::max(storenumh - 4, 0); -} - -bool WitchSellOk(int i) -{ - Item *pI; - - bool rv = false; - if (i >= 0) - pI = &MyPlayer->InvList[i]; - else - pI = &MyPlayer->SpdList[-(i + 1)]; - - if (pI->_itype == ItemType::Misc) - rv = true; - if (pI->_iMiscId > 29 && pI->_iMiscId < 41) - rv = false; - if (pI->_iClass == ICLASS_QUEST) - rv = false; - if (pI->_itype == ItemType::Staff && (!gbIsHellfire || IsValidSpell(pI->_iSpell))) - rv = true; - if (pI->IDidx >= IDI_FIRSTQUEST && pI->IDidx <= IDI_LASTQUEST) - rv = false; - if (pI->IDidx == IDI_LAZSTAFF) - rv = false; - return rv; -} - -void StartWitchSell() -{ - stextsize = true; - bool sellok = false; - storenumh = 0; - - for (auto &item : storehold) { - item.clear(); + auto &leftHand = MyPlayer->InvBody[INVLOC_HAND_LEFT]; + if (!leftHand.isEmpty() && leftHand._iDurability != leftHand._iMaxDur) { + AddPlayerItemToRepairList(&leftHand, -3); } - const Player &myPlayer = *MyPlayer; - - for (int i = 0; i < myPlayer._pNumInv; i++) { - if (storenumh >= 48) - break; - if (WitchSellOk(i)) { - sellok = true; - storehold[storenumh] = myPlayer.InvList[i]; - - if (storehold[storenumh]._iMagical != ITEM_QUALITY_NORMAL && storehold[storenumh]._iIdentified) - storehold[storenumh]._ivalue = storehold[storenumh]._iIvalue; - - storehold[storenumh]._ivalue = std::max(storehold[storenumh]._ivalue / 4, 1); - storehold[storenumh]._iIvalue = storehold[storenumh]._ivalue; - storehidx[storenumh] = i; - storenumh++; - } + auto &rightHand = MyPlayer->InvBody[INVLOC_HAND_RIGHT]; + if (!rightHand.isEmpty() && rightHand._iDurability != rightHand._iMaxDur) { + AddPlayerItemToRepairList(&rightHand, -4); } - for (int i = 0; i < MaxBeltItems; i++) { - if (storenumh >= 48) + for (int i = 0; i < MyPlayer->_pNumInv; i++) { + if (currentItemIndex >= 48) break; - if (!myPlayer.SpdList[i].isEmpty() && WitchSellOk(-(i + 1))) { - sellok = true; - storehold[storenumh] = myPlayer.SpdList[i]; - - if (storehold[storenumh]._iMagical != ITEM_QUALITY_NORMAL && storehold[storenumh]._iIdentified) - storehold[storenumh]._ivalue = storehold[storenumh]._iIvalue; - - storehold[storenumh]._ivalue = std::max(storehold[storenumh]._ivalue / 4, 1); - storehold[storenumh]._iIvalue = storehold[storenumh]._ivalue; - storehidx[storenumh] = -(i + 1); - storenumh++; + if (CanTownerRepair(i)) { + AddPlayerItemToRepairList(&MyPlayer->InvList[i], i); } } - if (!sellok) { - stextscrl = false; + if (currentItemIndex == 0) { + hasScrollbar = false; - RenderGold = true; - AddSText(20, 1, _("You have nothing I want."), UiFlags::ColorWhitegold, false); - - AddSLine(3); - AddItemListBackButton(/*selectable=*/true); + renderGold = true; + SetLineText(20, 1, _("You have nothing to repair."), UiFlags::ColorWhitegold, false); + SetLineAsDivider(3); + AddItemListBackButton(TalkID::SmithRepair, /*selectable=*/true); return; } - stextscrl = true; - stextsval = 0; - stextsmax = myPlayer._pNumInv; + hasScrollbar = true; + scrollPos = 0; + numTextLines = MyPlayer->_pNumInv; + + renderGold = true; + SetLineText(20, 1, _("Repair which item?"), UiFlags::ColorWhitegold, false); + SetLineAsDivider(3); - RenderGold = true; - AddSText(20, 1, _("Which item is for sale?"), UiFlags::ColorWhitegold, false); - AddSLine(3); - ScrollSmithSell(stextsval); - AddItemListBackButton(); + SetupTownerItemList(TalkID::SmithRepair, scrollPos, false); + AddItemListBackButton(TalkID::SmithRepair); } -bool WitchRechargeOk(int i) +bool StoreSession::CanTownerRecharge(int i) { const auto &item = MyPlayer->InvList[i]; @@ -842,243 +895,105 @@ bool WitchRechargeOk(int i) return false; } -void AddStoreHoldRecharge(Item itm, int8_t i) +void StoreSession::AddPlayerItemToRechargeList(Item itm, int8_t i) { - storehold[storenumh] = itm; - storehold[storenumh]._ivalue += GetSpellData(itm._iSpell).staffCost(); - storehold[storenumh]._ivalue = storehold[storenumh]._ivalue * (storehold[storenumh]._iMaxCharges - storehold[storenumh]._iCharges) / (storehold[storenumh]._iMaxCharges * 2); - storehold[storenumh]._iIvalue = storehold[storenumh]._ivalue; - storehidx[storenumh] = i; - storenumh++; + playerItems[currentItemIndex] = itm; + playerItems[currentItemIndex]._ivalue += GetSpellData(itm._iSpell).staffCost(); + playerItems[currentItemIndex]._ivalue = playerItems[currentItemIndex]._ivalue * (playerItems[currentItemIndex]._iMaxCharges - playerItems[currentItemIndex]._iCharges) / (playerItems[currentItemIndex]._iMaxCharges * 2); + playerItems[currentItemIndex]._iIvalue = playerItems[currentItemIndex]._ivalue; + playerItemIndexes[currentItemIndex] = i; + currentItemIndex++; } -void StartWitchRecharge() +void StoreSession::SetupTownerRechargeScreen() { - stextsize = true; + isTextFullSize = true; bool rechargeok = false; - storenumh = 0; + currentItemIndex = 0; - for (auto &item : storehold) { + for (auto &item : playerItems) { item.clear(); } - const Player &myPlayer = *MyPlayer; - const auto &leftHand = myPlayer.InvBody[INVLOC_HAND_LEFT]; + const auto &leftHand = MyPlayer->InvBody[INVLOC_HAND_LEFT]; if ((leftHand._itype == ItemType::Staff || leftHand._iMiscId == IMISC_UNIQUE) && leftHand._iCharges != leftHand._iMaxCharges) { rechargeok = true; - AddStoreHoldRecharge(leftHand, -1); + AddPlayerItemToRechargeList(leftHand, -1); } - for (int i = 0; i < myPlayer._pNumInv; i++) { - if (storenumh >= 48) + for (int i = 0; i < MyPlayer->_pNumInv; i++) { + if (currentItemIndex >= 48) break; - if (WitchRechargeOk(i)) { + if (CanTownerRecharge(i)) { rechargeok = true; - AddStoreHoldRecharge(myPlayer.InvList[i], i); + AddPlayerItemToRechargeList(MyPlayer->InvList[i], i); } } if (!rechargeok) { - stextscrl = false; + hasScrollbar = false; - RenderGold = true; - AddSText(20, 1, _("You have nothing to recharge."), UiFlags::ColorWhitegold, false); - AddSLine(3); - AddItemListBackButton(/*selectable=*/true); + renderGold = true; + SetLineText(20, 1, _("You have nothing to recharge."), UiFlags::ColorWhitegold, false); + SetLineAsDivider(3); + AddItemListBackButton(TalkID::WitchRecharge, /*selectable=*/true); return; } - stextscrl = true; - stextsval = 0; - stextsmax = myPlayer._pNumInv; - - RenderGold = true; - AddSText(20, 1, _("Recharge which item?"), UiFlags::ColorWhitegold, false); - AddSLine(3); - ScrollSmithSell(stextsval); - AddItemListBackButton(); -} - -void StoreNoMoney() -{ - StartStore(stextshold); - stextscrl = false; - stextsize = true; - RenderGold = true; - ClearSText(5, 23); - AddSText(0, 14, _("You do not have enough gold"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); -} + hasScrollbar = true; + scrollPos = 0; + numTextLines = MyPlayer->_pNumInv; -void StoreNoRoom() -{ - StartStore(stextshold); - stextscrl = false; - ClearSText(5, 23); - AddSText(0, 14, _("You do not have enough room in inventory"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); + renderGold = true; + SetLineText(20, 1, _("Recharge which item?"), UiFlags::ColorWhitegold, false); + SetLineAsDivider(3); + SetupTownerItemList(TalkID::WitchRecharge, scrollPos, false); + AddItemListBackButton(TalkID::WitchRecharge); } -void StoreConfirm(Item &item) +void StoreSession::RestoreResource() { - StartStore(stextshold); - stextscrl = false; - ClearSText(5, 23); - - UiFlags itemColor = item.getTextColorWithStatCheck(); - AddSText(20, 8, item.getName(), itemColor, false); - AddSTextVal(8, item._iIvalue); - PrintStoreItem(item, 9, itemColor); + int *resource = nullptr; + int *maxResource = nullptr; + int *baseResource = nullptr; + int *baseMaxResource = nullptr; + SfxID sfx; + PanelDrawComponent component; - std::string_view prompt; - - switch (stextshold) { - case TalkID::BoyBuy: - prompt = _("Do we have a deal?"); - break; - case TalkID::StorytellerIdentify: - prompt = _("Are you sure you want to identify this item?"); - break; - case TalkID::HealerBuy: - case TalkID::SmithPremiumBuy: - case TalkID::WitchBuy: - case TalkID::SmithBuy: - prompt = _("Are you sure you want to buy this item?"); - break; - case TalkID::WitchRecharge: - prompt = _("Are you sure you want to recharge this item?"); + 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 TalkID::SmithSell: - case TalkID::WitchSell: - prompt = _("Are you sure you want to sell this item?"); - break; - case TalkID::SmithRepair: - prompt = _("Are you sure you want to repair this item?"); + 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: - app_fatal(StrCat("Unknown store dialog ", static_cast(stextshold))); - } - AddSText(0, 15, prompt, UiFlags::ColorWhite | UiFlags::AlignCenter, false); - AddSText(0, 18, _("Yes"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - AddSText(0, 20, _("No"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); -} - -void StartBoy() -{ - stextsize = false; - stextscrl = false; - AddSText(0, 2, _("Wirt the Peg-legged boy"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); - AddSLine(5); - if (!boyitem.isEmpty()) { - AddSText(0, 8, _("Talk to Wirt"), UiFlags::ColorBlue | UiFlags::AlignCenter, true); - AddSText(0, 12, _("I have something for sale,"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); - AddSText(0, 14, _("but it will cost 50 gold"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); - AddSText(0, 16, _("just to take a look. "), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); - AddSText(0, 18, _("What have you got?"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - AddSText(0, 20, _("Say goodbye"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - } else { - AddSText(0, 12, _("Talk to Wirt"), UiFlags::ColorBlue | UiFlags::AlignCenter, true); - AddSText(0, 18, _("Say goodbye"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - } -} - -void SStartBoyBuy() -{ - stextsize = true; - stextscrl = false; - - RenderGold = true; - AddSText(20, 1, _("I have this item for sale:"), UiFlags::ColorWhitegold, false); - AddSLine(3); - - boyitem._iStatFlag = MyPlayer->CanUseItem(boyitem); - UiFlags itemColor = boyitem.getTextColorWithStatCheck(); - AddSText(20, 10, boyitem.getName(), itemColor, true, boyitem._iCurs, true); - if (gbIsHellfire) - AddSTextVal(10, boyitem._iIvalue - (boyitem._iIvalue / 4)); - else - AddSTextVal(10, boyitem._iIvalue + (boyitem._iIvalue / 2)); - PrintStoreItem(boyitem, 11, itemColor, true); - - { - // Add a Leave button. Unlike the other item list back buttons, - // this one has different text and different layout in LargerSmallFont locales. - const int line = BackButtonLine(); - AddSLine(line - 1); - AddSText(0, line, _("Leave"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - stext[line]._syoff = 6; - } -} - -void HealPlayer() -{ - Player &myPlayer = *MyPlayer; - - if (myPlayer._pHitPoints != myPlayer._pMaxHP) { - PlaySFX(SfxID::CastHealing); - } - myPlayer._pHitPoints = myPlayer._pMaxHP; - myPlayer._pHPBase = myPlayer._pMaxHPBase; - RedrawComponent(PanelDrawComponent::Health); -} - -void StartHealer() -{ - HealPlayer(); - stextsize = false; - stextscrl = false; - AddSText(0, 1, _("Welcome to the"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); - AddSText(0, 3, _("Healer's home"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); - AddSText(0, 9, _("Would you like to:"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); - AddSText(0, 12, _("Talk to Pepin"), UiFlags::ColorBlue | UiFlags::AlignCenter, true); - AddSText(0, 14, _("Buy items"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - AddSText(0, 18, _("Leave Healer's home"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - AddSLine(5); - storenumh = 20; -} - -void ScrollHealerBuy(int idx) -{ - ScrollVendorStore(healitem, static_cast(std::size(healitem)), idx); -} - -void StartHealerBuy() -{ - stextsize = true; - stextscrl = true; - stextsval = 0; - - RenderGold = true; - AddSText(20, 1, _("I have these items for sale:"), UiFlags::ColorWhitegold, false); - AddSLine(3); - - ScrollHealerBuy(stextsval); - AddItemListBackButton(); - - storenumh = 0; - for (Item &item : healitem) { - if (item.isEmpty()) - continue; - - item._iStatFlag = MyPlayer->CanUseItem(item); - storenumh++; + return; } - stextsmax = std::max(storenumh - 4, 0); -} + if (*resource == *maxResource) + return; -void StartStoryteller() -{ - stextsize = false; - stextscrl = false; - AddSText(0, 2, _("The Town Elder"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); - AddSText(0, 9, _("Would you like to:"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); - AddSText(0, 12, _("Talk to Cain"), UiFlags::ColorBlue | UiFlags::AlignCenter, true); - AddSText(0, 14, _("Identify an item"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - AddSText(0, 18, _("Say goodbye"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - AddSLine(5); + PlaySFX(sfx); + *resource = *maxResource; + *baseResource = *baseMaxResource; + RedrawComponent(component); } -bool IdItemOk(Item *i) +bool StoreSession::CanTownerIdentify(Item *i) { if (i->isEmpty()) { return false; @@ -1089,219 +1004,137 @@ bool IdItemOk(Item *i) return !i->_iIdentified; } -void AddStoreHoldId(Item itm, int8_t i) +void StoreSession::AddPlayerItemToIdentifyList(Item itm, int8_t i) { - storehold[storenumh] = itm; - storehold[storenumh]._ivalue = 100; - storehold[storenumh]._iIvalue = 100; - storehidx[storenumh] = i; - storenumh++; + playerItems[currentItemIndex] = itm; + playerItems[currentItemIndex]._ivalue = 100; + playerItems[currentItemIndex]._iIvalue = 100; + playerItemIndexes[currentItemIndex] = i; + currentItemIndex++; } -void StartStorytellerIdentify() +void StoreSession::SetupTownerIdentifyScreen() { bool idok = false; - stextsize = true; - storenumh = 0; + isTextFullSize = true; + currentItemIndex = 0; - for (auto &item : storehold) { + for (auto &item : playerItems) { item.clear(); } - Player &myPlayer = *MyPlayer; - - auto &helmet = myPlayer.InvBody[INVLOC_HEAD]; - if (IdItemOk(&helmet)) { + auto &helmet = MyPlayer->InvBody[INVLOC_HEAD]; + if (CanTownerIdentify(&helmet)) { idok = true; - AddStoreHoldId(helmet, -1); + AddPlayerItemToIdentifyList(helmet, -1); } - auto &armor = myPlayer.InvBody[INVLOC_CHEST]; - if (IdItemOk(&armor)) { + auto &armor = MyPlayer->InvBody[INVLOC_CHEST]; + if (CanTownerIdentify(&armor)) { idok = true; - AddStoreHoldId(armor, -2); + AddPlayerItemToIdentifyList(armor, -2); } - auto &leftHand = myPlayer.InvBody[INVLOC_HAND_LEFT]; - if (IdItemOk(&leftHand)) { + auto &leftHand = MyPlayer->InvBody[INVLOC_HAND_LEFT]; + if (CanTownerIdentify(&leftHand)) { idok = true; - AddStoreHoldId(leftHand, -3); + AddPlayerItemToIdentifyList(leftHand, -3); } - auto &rightHand = myPlayer.InvBody[INVLOC_HAND_RIGHT]; - if (IdItemOk(&rightHand)) { + auto &rightHand = MyPlayer->InvBody[INVLOC_HAND_RIGHT]; + if (CanTownerIdentify(&rightHand)) { idok = true; - AddStoreHoldId(rightHand, -4); + AddPlayerItemToIdentifyList(rightHand, -4); } - auto &leftRing = myPlayer.InvBody[INVLOC_RING_LEFT]; - if (IdItemOk(&leftRing)) { + auto &leftRing = MyPlayer->InvBody[INVLOC_RING_LEFT]; + if (CanTownerIdentify(&leftRing)) { idok = true; - AddStoreHoldId(leftRing, -5); + AddPlayerItemToIdentifyList(leftRing, -5); } - auto &rightRing = myPlayer.InvBody[INVLOC_RING_RIGHT]; - if (IdItemOk(&rightRing)) { + auto &rightRing = MyPlayer->InvBody[INVLOC_RING_RIGHT]; + if (CanTownerIdentify(&rightRing)) { idok = true; - AddStoreHoldId(rightRing, -6); + AddPlayerItemToIdentifyList(rightRing, -6); } - auto &amulet = myPlayer.InvBody[INVLOC_AMULET]; - if (IdItemOk(&amulet)) { + auto &amulet = MyPlayer->InvBody[INVLOC_AMULET]; + if (CanTownerIdentify(&amulet)) { idok = true; - AddStoreHoldId(amulet, -7); + AddPlayerItemToIdentifyList(amulet, -7); } - for (int i = 0; i < myPlayer._pNumInv; i++) { - if (storenumh >= 48) + for (int i = 0; i < MyPlayer->_pNumInv; i++) { + if (currentItemIndex >= 48) break; - auto &item = myPlayer.InvList[i]; - if (IdItemOk(&item)) { + auto &item = MyPlayer->InvList[i]; + if (CanTownerIdentify(&item)) { idok = true; - AddStoreHoldId(item, i); + AddPlayerItemToIdentifyList(item, i); } } if (!idok) { - stextscrl = false; + hasScrollbar = false; - RenderGold = true; - AddSText(20, 1, _("You have nothing to identify."), UiFlags::ColorWhitegold, false); - AddSLine(3); - AddItemListBackButton(/*selectable=*/true); + renderGold = true; + SetLineText(20, 1, _("You have nothing to identify."), UiFlags::ColorWhitegold, false); + SetLineAsDivider(3); + AddItemListBackButton(TalkID::StorytellerIdentify, /*selectable=*/true); return; } - stextscrl = true; - stextsval = 0; - stextsmax = myPlayer._pNumInv; + hasScrollbar = true; + scrollPos = 0; + numTextLines = MyPlayer->_pNumInv; - RenderGold = true; - AddSText(20, 1, _("Identify which item?"), UiFlags::ColorWhitegold, false); - AddSLine(3); + renderGold = true; + SetLineText(20, 1, _("Identify which item?"), UiFlags::ColorWhitegold, false); + SetLineAsDivider(3); - ScrollSmithSell(stextsval); - AddItemListBackButton(); + SetupTownerItemList(TalkID::StorytellerIdentify, scrollPos, false); + AddItemListBackButton(TalkID::StorytellerIdentify); } -void StartStorytellerIdentifyShow(Item &item) +void StoreSession::SetupTownerIdentifyResultScreen(Item &item) { - StartStore(stextshold); - stextscrl = false; + StartStore(oldActiveStore); + hasScrollbar = false; ClearSText(5, 23); UiFlags itemColor = item.getTextColorWithStatCheck(); - AddSText(0, 7, _("This item is:"), UiFlags::ColorWhite | UiFlags::AlignCenter, false); - AddSText(20, 11, item.getName(), itemColor, false); + SetLineText(0, 7, _("This item is:"), UiFlags::ColorWhite | UiFlags::AlignCenter, false); + SetLineText(20, 11, item.getName(), itemColor, false); PrintStoreItem(item, 12, itemColor); - AddSText(0, 18, _("Done"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); -} - -void StartTalk() -{ - int la; - - stextsize = false; - stextscrl = false; - AddSText(0, 2, fmt::format(fmt::runtime(_("Talk to {:s}")), _(TownerNames[talker])), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); - AddSLine(5); - if (gbIsSpawn) { - AddSText(0, 10, fmt::format(fmt::runtime(_("Talking to {:s}")), _(TownerNames[talker])), UiFlags::ColorWhite | UiFlags::AlignCenter, false); - AddSText(0, 12, _("is not available"), UiFlags::ColorWhite | UiFlags::AlignCenter, false); - AddSText(0, 14, _("in the shareware"), UiFlags::ColorWhite | UiFlags::AlignCenter, false); - AddSText(0, 16, _("version"), UiFlags::ColorWhite | UiFlags::AlignCenter, false); - AddOptionsBackButton(); - return; - } - - int sn = 0; - for (auto &quest : Quests) { - if (quest._qactive == QUEST_ACTIVE && QuestDialogTable[talker][quest._qidx] != TEXT_NONE && quest._qlog) - sn++; - } - - if (sn > 6) { - sn = 14 - (sn / 2); - la = 1; - } else { - sn = 15 - sn; - la = 2; - } - - int sn2 = sn - 2; - - for (auto &quest : Quests) { - if (quest._qactive == QUEST_ACTIVE && QuestDialogTable[talker][quest._qidx] != TEXT_NONE && quest._qlog) { - AddSText(0, sn, _(QuestsData[quest._qidx]._qlstr), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - sn += la; - } - } - AddSText(0, sn2, _("Gossip"), UiFlags::ColorBlue | UiFlags::AlignCenter, true); - AddOptionsBackButton(); -} - -void StartTavern() -{ - stextsize = false; - stextscrl = false; - AddSText(0, 1, _("Welcome to the"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); - AddSText(0, 3, _("Rising Sun"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); - AddSText(0, 9, _("Would you like to:"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); - AddSText(0, 12, _("Talk to Ogden"), UiFlags::ColorBlue | UiFlags::AlignCenter, true); - AddSText(0, 18, _("Leave the tavern"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - AddSLine(5); - storenumh = 20; -} - -void StartBarmaid() -{ - stextsize = false; - stextscrl = false; - AddSText(0, 2, _("Gillian"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); - AddSText(0, 9, _("Would you like to:"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); - AddSText(0, 12, _("Talk to Gillian"), UiFlags::ColorBlue | UiFlags::AlignCenter, true); - AddSText(0, 14, _("Access Storage"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - AddSText(0, 18, _("Say goodbye"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - AddSLine(5); - storenumh = 20; + SetLineText(0, 18, _("Done"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); } -void StartDrunk() +void StoreSession::SmithEnter() { - stextsize = false; - stextscrl = false; - AddSText(0, 2, _("Farnham the Drunk"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); - AddSText(0, 9, _("Would you like to:"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); - AddSText(0, 12, _("Talk to Farnham"), UiFlags::ColorBlue | UiFlags::AlignCenter, true); - AddSText(0, 18, _("Say Goodbye"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - AddSLine(5); - storenumh = 20; -} + TalkID selectedAction = storeLineMapping[currentTextLine]; -void SmithEnter() -{ - switch (stextsel) { - case 10: - talker = TOWN_SMITH; - stextlhold = 10; - stextshold = TalkID::Smith; - StartStore(TalkID::Gossip); + switch (selectedAction) { + case TalkID::Gossip: + oldTextLine = 10; + oldActiveStore = TalkID::Smith; + StartStore(selectedAction); break; - case 12: - StartStore(TalkID::SmithBuy); + case TalkID::SmithBuy: + StartStore(selectedAction); break; - case 14: - StartStore(TalkID::SmithPremiumBuy); + case TalkID::SmithPremiumBuy: + StartStore(selectedAction); break; - case 16: - StartStore(TalkID::SmithSell); + case TalkID::SmithSell: + StartStore(selectedAction); break; - case 18: - StartStore(TalkID::SmithRepair); + case TalkID::SmithRepair: + StartStore(selectedAction); break; - case 20: - stextflag = TalkID::None; + case TalkID::None: + activeStore = selectedAction; break; } } @@ -1309,111 +1142,111 @@ void SmithEnter() /** * @brief Purchases an item from the smith. */ -void SmithBuyItem(Item &item) +void StoreSession::SmithBuyItem(Item &item) { TakePlrsMoney(item._iIvalue); if (item._iMagical == ITEM_QUALITY_NORMAL) item._iIdentified = false; - StoreAutoPlace(item, true); - int idx = stextvhold + ((stextlhold - stextup) / 4); - if (idx == SMITH_ITEMS - 1) { - smithitem[SMITH_ITEMS - 1].clear(); + GiveItemToPlayer(item, true); + int idx = oldScrollPos + ((oldTextLine - previousScrollPos) / 4); + if (idx == NumSmithItems - 1) { + smithItems[NumSmithItems - 1].clear(); } else { - for (; !smithitem[idx + 1].isEmpty(); idx++) { - smithitem[idx] = std::move(smithitem[idx + 1]); + for (; !smithItems[idx + 1].isEmpty(); idx++) { + smithItems[idx] = std::move(smithItems[idx + 1]); } - smithitem[idx].clear(); + smithItems[idx].clear(); } CalcPlrInv(*MyPlayer, true); } -void SmithBuyEnter() +void StoreSession::SmithBuyEnter() { - if (stextsel == BackButtonLine()) { + if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Smith); - stextsel = 12; + currentTextLine = 12; return; } - stextlhold = stextsel; - stextvhold = stextsval; - stextshold = TalkID::SmithBuy; + oldTextLine = currentTextLine; + oldScrollPos = scrollPos; + oldActiveStore = TalkID::SmithBuy; - int idx = stextsval + ((stextsel - stextup) / 4); - if (!PlayerCanAfford(smithitem[idx]._iIvalue)) { + int idx = scrollPos + ((currentTextLine - previousScrollPos) / 4); + if (!CanPlayerAfford(smithItems[idx]._iIvalue)) { StartStore(TalkID::NoMoney); return; } - if (!StoreAutoPlace(smithitem[idx], false)) { + if (!GiveItemToPlayer(smithItems[idx], false)) { StartStore(TalkID::NoRoom); return; } - StoreItem = smithitem[idx]; + tempItem = smithItems[idx]; StartStore(TalkID::Confirm); } /** * @brief Purchases a premium item from the smith. */ -void SmithBuyPItem(Item &item) +void StoreSession::SmithBuyPItem(Item &item) { TakePlrsMoney(item._iIvalue); if (item._iMagical == ITEM_QUALITY_NORMAL) item._iIdentified = false; - StoreAutoPlace(item, true); + GiveItemToPlayer(item, true); - int idx = stextvhold + ((stextlhold - stextup) / 4); + int idx = oldScrollPos + ((oldTextLine - previousScrollPos) / 4); int xx = 0; for (int i = 0; idx >= 0; i++) { - if (!premiumitems[i].isEmpty()) { + if (!premiumItems[i].isEmpty()) { idx--; xx = i; } } - premiumitems[xx].clear(); - numpremium--; + premiumItems[xx].clear(); + premiumItemCount--; SpawnPremium(*MyPlayer); } -void SmithPremiumBuyEnter() +void StoreSession::SmithPremiumBuyEnter() { - if (stextsel == BackButtonLine()) { + if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Smith); - stextsel = 14; + currentTextLine = 14; return; } - stextshold = TalkID::SmithPremiumBuy; - stextlhold = stextsel; - stextvhold = stextsval; + oldActiveStore = TalkID::SmithPremiumBuy; + oldTextLine = currentTextLine; + oldScrollPos = scrollPos; - int xx = stextsval + ((stextsel - stextup) / 4); + int xx = scrollPos + ((currentTextLine - previousScrollPos) / 4); int idx = 0; for (int i = 0; xx >= 0; i++) { - if (!premiumitems[i].isEmpty()) { + if (!premiumItems[i].isEmpty()) { xx--; idx = i; } } - if (!PlayerCanAfford(premiumitems[idx]._iIvalue)) { + if (!CanPlayerAfford(premiumItems[idx]._iIvalue)) { StartStore(TalkID::NoMoney); return; } - if (!StoreAutoPlace(premiumitems[idx], false)) { + if (!GiveItemToPlayer(premiumItems[idx], false)) { StartStore(TalkID::NoRoom); return; } - StoreItem = premiumitems[idx]; + tempItem = premiumItems[idx]; StartStore(TalkID::Confirm); } -bool StoreGoldFit(Item &item) +bool StoreSession::StoreGoldFit(Item &item) { int cost = item._iIvalue; @@ -1430,113 +1263,107 @@ bool StoreGoldFit(Item &item) /** * @brief Sells an item from the player's inventory or belt. */ -void StoreSellItem() +void StoreSession::StoreSellItem() { - Player &myPlayer = *MyPlayer; - - int idx = stextvhold + ((stextlhold - stextup) / 4); - if (storehidx[idx] >= 0) - myPlayer.RemoveInvItem(storehidx[idx]); + int idx = oldScrollPos + ((oldTextLine - previousScrollPos) / 4); + if (playerItemIndexes[idx] >= 0) + MyPlayer->RemoveInvItem(playerItemIndexes[idx]); else - myPlayer.RemoveSpdBarItem(-(storehidx[idx] + 1)); - - int cost = storehold[idx]._iIvalue; - storenumh--; - if (idx != storenumh) { - while (idx < storenumh) { - storehold[idx] = storehold[idx + 1]; - storehidx[idx] = storehidx[idx + 1]; + MyPlayer->RemoveSpdBarItem(-(playerItemIndexes[idx] + 1)); + + int cost = playerItems[idx]._iIvalue; + currentItemIndex--; + if (idx != currentItemIndex) { + while (idx < currentItemIndex) { + playerItems[idx] = playerItems[idx + 1]; + playerItemIndexes[idx] = playerItemIndexes[idx + 1]; idx++; } } - AddGoldToInventory(myPlayer, cost); + AddGoldToInventory(*MyPlayer, cost); - myPlayer._pGold += cost; + MyPlayer->_pGold += cost; } -void SmithSellEnter() +void StoreSession::SmithSellEnter() { - if (stextsel == BackButtonLine()) { + if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Smith); - stextsel = 16; + currentTextLine = 16; return; } - stextlhold = stextsel; - stextshold = TalkID::SmithSell; - stextvhold = stextsval; + oldTextLine = currentTextLine; + oldActiveStore = TalkID::SmithSell; + oldScrollPos = scrollPos; - int idx = stextsval + ((stextsel - stextup) / 4); + int idx = scrollPos + ((currentTextLine - previousScrollPos) / 4); - if (!StoreGoldFit(storehold[idx])) { + if (!StoreGoldFit(playerItems[idx])) { StartStore(TalkID::NoRoom); return; } - StoreItem = storehold[idx]; + tempItem = playerItems[idx]; StartStore(TalkID::Confirm); } /** * @brief Repairs an item in the player's inventory or body in the smith. */ -void SmithRepairItem(int price) +void StoreSession::SmithRepairItem(int price) { - int idx = stextvhold + ((stextlhold - stextup) / 4); - storehold[idx]._iDurability = storehold[idx]._iMaxDur; + int idx = oldScrollPos + ((oldTextLine - previousScrollPos) / 4); + playerItems[idx]._iDurability = playerItems[idx]._iMaxDur; - int8_t i = storehidx[idx]; - - Player &myPlayer = *MyPlayer; + int8_t i = playerItemIndexes[idx]; if (i < 0) { if (i == -1) - myPlayer.InvBody[INVLOC_HEAD]._iDurability = myPlayer.InvBody[INVLOC_HEAD]._iMaxDur; + MyPlayer->InvBody[INVLOC_HEAD]._iDurability = MyPlayer->InvBody[INVLOC_HEAD]._iMaxDur; if (i == -2) - myPlayer.InvBody[INVLOC_CHEST]._iDurability = myPlayer.InvBody[INVLOC_CHEST]._iMaxDur; + MyPlayer->InvBody[INVLOC_CHEST]._iDurability = MyPlayer->InvBody[INVLOC_CHEST]._iMaxDur; if (i == -3) - myPlayer.InvBody[INVLOC_HAND_LEFT]._iDurability = myPlayer.InvBody[INVLOC_HAND_LEFT]._iMaxDur; + MyPlayer->InvBody[INVLOC_HAND_LEFT]._iDurability = MyPlayer->InvBody[INVLOC_HAND_LEFT]._iMaxDur; if (i == -4) - myPlayer.InvBody[INVLOC_HAND_RIGHT]._iDurability = myPlayer.InvBody[INVLOC_HAND_RIGHT]._iMaxDur; + MyPlayer->InvBody[INVLOC_HAND_RIGHT]._iDurability = MyPlayer->InvBody[INVLOC_HAND_RIGHT]._iMaxDur; TakePlrsMoney(price); return; } - myPlayer.InvList[i]._iDurability = myPlayer.InvList[i]._iMaxDur; - TakePlrsMoney(price); + MyPlayer->InvList[i]._iDurability = MyPlayer->InvList[i]._iMaxDur; } -void SmithRepairEnter() +void StoreSession::SmithRepairEnter() { - if (stextsel == BackButtonLine()) { + if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Smith); - stextsel = 18; + currentTextLine = 18; return; } - stextshold = TalkID::SmithRepair; - stextlhold = stextsel; - stextvhold = stextsval; + oldActiveStore = TalkID::SmithRepair; + oldTextLine = currentTextLine; + oldScrollPos = scrollPos; - int idx = stextsval + ((stextsel - stextup) / 4); + int idx = scrollPos + ((currentTextLine - previousScrollPos) / 4); - if (!PlayerCanAfford(storehold[idx]._iIvalue)) { + if (!CanPlayerAfford(playerItems[idx]._iIvalue)) { StartStore(TalkID::NoMoney); return; } - StoreItem = storehold[idx]; + tempItem = playerItems[idx]; StartStore(TalkID::Confirm); } -void WitchEnter() +void StoreSession::WitchEnter() { - switch (stextsel) { + switch (currentTextLine) { case 12: - stextlhold = 12; - talker = TOWN_WITCH; - stextshold = TalkID::Witch; + oldTextLine = 12; + oldActiveStore = TalkID::Witch; StartStore(TalkID::Gossip); break; case 14: @@ -1549,7 +1376,7 @@ void WitchEnter() StartStore(TalkID::WitchRecharge); break; case 20: - stextflag = TalkID::None; + activeStore = TalkID::None; break; } } @@ -1557,134 +1384,132 @@ void WitchEnter() /** * @brief Purchases an item from the witch. */ -void WitchBuyItem(Item &item) +void StoreSession::WitchBuyItem(Item &item) { - int idx = stextvhold + ((stextlhold - stextup) / 4); + int idx = oldScrollPos + ((oldTextLine - previousScrollPos) / 4); if (idx < 3) item._iSeed = AdvanceRndSeed(); TakePlrsMoney(item._iIvalue); - StoreAutoPlace(item, true); + GiveItemToPlayer(item, true); if (idx >= 3) { - if (idx == WITCH_ITEMS - 1) { - witchitem[WITCH_ITEMS - 1].clear(); + if (idx == NumWitchItems - 1) { + witchItems[NumWitchItems - 1].clear(); } else { - for (; !witchitem[idx + 1].isEmpty(); idx++) { - witchitem[idx] = std::move(witchitem[idx + 1]); + for (; !witchItems[idx + 1].isEmpty(); idx++) { + witchItems[idx] = std::move(witchItems[idx + 1]); } - witchitem[idx].clear(); + witchItems[idx].clear(); } } CalcPlrInv(*MyPlayer, true); } -void WitchBuyEnter() +void StoreSession::WitchBuyEnter() { - if (stextsel == BackButtonLine()) { + if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Witch); - stextsel = 14; + currentTextLine = 14; return; } - stextlhold = stextsel; - stextvhold = stextsval; - stextshold = TalkID::WitchBuy; + oldTextLine = currentTextLine; + oldScrollPos = scrollPos; + oldActiveStore = TalkID::WitchBuy; - int idx = stextsval + ((stextsel - stextup) / 4); + int idx = scrollPos + ((currentTextLine - previousScrollPos) / 4); - if (!PlayerCanAfford(witchitem[idx]._iIvalue)) { + if (!CanPlayerAfford(witchItems[idx]._iIvalue)) { StartStore(TalkID::NoMoney); return; } - if (!StoreAutoPlace(witchitem[idx], false)) { + if (!GiveItemToPlayer(witchItems[idx], false)) { StartStore(TalkID::NoRoom); return; } - StoreItem = witchitem[idx]; + tempItem = witchItems[idx]; StartStore(TalkID::Confirm); } -void WitchSellEnter() +void StoreSession::WitchSellEnter() { - if (stextsel == BackButtonLine()) { + if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Witch); - stextsel = 16; + currentTextLine = 16; return; } - stextlhold = stextsel; - stextshold = TalkID::WitchSell; - stextvhold = stextsval; + oldTextLine = currentTextLine; + oldActiveStore = TalkID::WitchSell; + oldScrollPos = scrollPos; - int idx = stextsval + ((stextsel - stextup) / 4); + int idx = scrollPos + ((currentTextLine - previousScrollPos) / 4); - if (!StoreGoldFit(storehold[idx])) { + if (!StoreGoldFit(playerItems[idx])) { StartStore(TalkID::NoRoom); return; } - StoreItem = storehold[idx]; + tempItem = playerItems[idx]; StartStore(TalkID::Confirm); } /** * @brief Recharges an item in the player's inventory or body in the witch. */ -void WitchRechargeItem(int price) +void StoreSession::WitchRechargeItem(int price) { - int idx = stextvhold + ((stextlhold - stextup) / 4); - storehold[idx]._iCharges = storehold[idx]._iMaxCharges; + int idx = oldScrollPos + ((oldTextLine - previousScrollPos) / 4); + playerItems[idx]._iCharges = playerItems[idx]._iMaxCharges; - Player &myPlayer = *MyPlayer; - - int8_t i = storehidx[idx]; + int8_t i = playerItemIndexes[idx]; if (i < 0) { - myPlayer.InvBody[INVLOC_HAND_LEFT]._iCharges = myPlayer.InvBody[INVLOC_HAND_LEFT]._iMaxCharges; + MyPlayer->InvBody[INVLOC_HAND_LEFT]._iCharges = MyPlayer->InvBody[INVLOC_HAND_LEFT]._iMaxCharges; NetSendCmdChItem(true, INVLOC_HAND_LEFT); } else { - myPlayer.InvList[i]._iCharges = myPlayer.InvList[i]._iMaxCharges; - NetSyncInvItem(myPlayer, i); + MyPlayer->InvList[i]._iCharges = MyPlayer->InvList[i]._iMaxCharges; + NetSyncInvItem(*MyPlayer, i); } TakePlrsMoney(price); - CalcPlrInv(myPlayer, true); + CalcPlrInv(*MyPlayer, true); } -void WitchRechargeEnter() +void StoreSession::WitchRechargeEnter() { - if (stextsel == BackButtonLine()) { + if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Witch); - stextsel = 18; + currentTextLine = 18; return; } - stextshold = TalkID::WitchRecharge; - stextlhold = stextsel; - stextvhold = stextsval; + oldActiveStore = TalkID::WitchRecharge; + oldTextLine = currentTextLine; + oldScrollPos = scrollPos; - int idx = stextsval + ((stextsel - stextup) / 4); + int idx = scrollPos + ((currentTextLine - previousScrollPos) / 4); - if (!PlayerCanAfford(storehold[idx]._iIvalue)) { + if (!CanPlayerAfford(playerItems[idx]._iIvalue)) { StartStore(TalkID::NoMoney); return; } - StoreItem = storehold[idx]; + tempItem = playerItems[idx]; StartStore(TalkID::Confirm); } -void BoyEnter() +void StoreSession::BoyEnter() { - if (!boyitem.isEmpty() && stextsel == 18) { - if (!PlayerCanAfford(50)) { - stextshold = TalkID::Boy; - stextlhold = 18; - stextvhold = stextsval; + if (!boyItem.isEmpty() && currentTextLine == 18) { + if (!CanPlayerAfford(50)) { + oldActiveStore = TalkID::Boy; + oldTextLine = 18; + oldScrollPos = scrollPos; StartStore(TalkID::NoMoney); } else { TakePlrsMoney(50); @@ -1693,33 +1518,32 @@ void BoyEnter() return; } - if ((stextsel != 8 && !boyitem.isEmpty()) || (stextsel != 12 && boyitem.isEmpty())) { - stextflag = TalkID::None; + if ((currentTextLine != 8 && !boyItem.isEmpty()) || (currentTextLine != 12 && boyItem.isEmpty())) { + activeStore = TalkID::None; return; } - talker = TOWN_PEGBOY; - stextshold = TalkID::Boy; - stextlhold = stextsel; + oldActiveStore = TalkID::Boy; + oldTextLine = currentTextLine; StartStore(TalkID::Gossip); } -void BoyBuyItem(Item &item) +void StoreSession::BoyBuyItem(Item &item) { TakePlrsMoney(item._iIvalue); - StoreAutoPlace(item, true); - boyitem.clear(); - stextshold = TalkID::Boy; + GiveItemToPlayer(item, true); + boyItem.clear(); + oldActiveStore = TalkID::Boy; CalcPlrInv(*MyPlayer, true); - stextlhold = 12; + oldTextLine = 12; } /** * @brief Purchases an item from the healer. */ -void HealerBuyItem(Item &item) +void StoreSession::HealerBuyItem(Item &item) { - int idx = stextvhold + ((stextlhold - stextup) / 4); + int idx = oldScrollPos + ((oldTextLine - previousScrollPos) / 4); if (!gbIsMultiplayer) { if (idx < 2) item._iSeed = AdvanceRndSeed(); @@ -1731,7 +1555,7 @@ void HealerBuyItem(Item &item) TakePlrsMoney(item._iIvalue); if (item._iMagical == ITEM_QUALITY_NORMAL) item._iIdentified = false; - StoreAutoPlace(item, true); + GiveItemToPlayer(item, true); if (!gbIsMultiplayer) { if (idx < 2) @@ -1740,81 +1564,79 @@ void HealerBuyItem(Item &item) if (idx < 3) return; } - idx = stextvhold + ((stextlhold - stextup) / 4); + idx = oldScrollPos + ((oldTextLine - previousScrollPos) / 4); if (idx == 19) { - healitem[19].clear(); + healerItems[19].clear(); } else { - for (; !healitem[idx + 1].isEmpty(); idx++) { - healitem[idx] = std::move(healitem[idx + 1]); + for (; !healerItems[idx + 1].isEmpty(); idx++) { + healerItems[idx] = std::move(healerItems[idx + 1]); } - healitem[idx].clear(); + healerItems[idx].clear(); } CalcPlrInv(*MyPlayer, true); } -void BoyBuyEnter() +void StoreSession::BoyBuyEnter() { - if (stextsel != 10) { - stextflag = TalkID::None; + if (currentTextLine != 10) { + activeStore = TalkID::None; return; } - stextshold = TalkID::BoyBuy; - stextvhold = stextsval; - stextlhold = 10; - int price = boyitem._iIvalue; + oldActiveStore = TalkID::BoyBuy; + oldScrollPos = scrollPos; + oldTextLine = 10; + int price = boyItem._iIvalue; if (gbIsHellfire) - price -= boyitem._iIvalue / 4; + price -= boyItem._iIvalue / 4; else - price += boyitem._iIvalue / 2; + price += boyItem._iIvalue / 2; - if (!PlayerCanAfford(price)) { + if (!CanPlayerAfford(price)) { StartStore(TalkID::NoMoney); return; } - if (!StoreAutoPlace(boyitem, false)) { + if (!GiveItemToPlayer(boyItem, false)) { StartStore(TalkID::NoRoom); return; } - StoreItem = boyitem; - StoreItem._iIvalue = price; + tempItem = boyItem; + tempItem._iIvalue = price; StartStore(TalkID::Confirm); } -void StorytellerIdentifyItem(Item &item) +void StoreSession::StorytellerIdentifyItem(Item &item) { - Player &myPlayer = *MyPlayer; - - int8_t idx = storehidx[((stextlhold - stextup) / 4) + stextvhold]; + int8_t idx = playerItemIndexes[((oldTextLine - previousScrollPos) / 4) + oldScrollPos]; if (idx < 0) { if (idx == -1) - myPlayer.InvBody[INVLOC_HEAD]._iIdentified = true; + MyPlayer->InvBody[INVLOC_HEAD]._iIdentified = true; if (idx == -2) - myPlayer.InvBody[INVLOC_CHEST]._iIdentified = true; + MyPlayer->InvBody[INVLOC_CHEST]._iIdentified = true; if (idx == -3) - myPlayer.InvBody[INVLOC_HAND_LEFT]._iIdentified = true; + MyPlayer->InvBody[INVLOC_HAND_LEFT]._iIdentified = true; if (idx == -4) - myPlayer.InvBody[INVLOC_HAND_RIGHT]._iIdentified = true; + MyPlayer->InvBody[INVLOC_HAND_RIGHT]._iIdentified = true; if (idx == -5) - myPlayer.InvBody[INVLOC_RING_LEFT]._iIdentified = true; + MyPlayer->InvBody[INVLOC_RING_LEFT]._iIdentified = true; if (idx == -6) - myPlayer.InvBody[INVLOC_RING_RIGHT]._iIdentified = true; + MyPlayer->InvBody[INVLOC_RING_RIGHT]._iIdentified = true; if (idx == -7) - myPlayer.InvBody[INVLOC_AMULET]._iIdentified = true; + MyPlayer->InvBody[INVLOC_AMULET]._iIdentified = true; } else { - myPlayer.InvList[idx]._iIdentified = true; + MyPlayer->InvList[idx]._iIdentified = true; } item._iIdentified = true; TakePlrsMoney(item._iIvalue); - CalcPlrInv(myPlayer, true); + CalcPlrInv(*MyPlayer, true); } -void ConfirmEnter(Item &item) +void StoreSession::ConfirmEnter(Item &item) { - if (stextsel == 18) { - switch (stextshold) { + if (currentTextLine == 18) { + switch (oldActiveStore) { case TalkID::SmithBuy: SmithBuyItem(item); break; @@ -1849,117 +1671,115 @@ void ConfirmEnter(Item &item) } } - StartStore(stextshold); + StartStore(oldActiveStore); - if (stextsel == BackButtonLine()) + if (currentTextLine == BackButtonLine()) return; - stextsel = stextlhold; - stextsval = std::min(stextvhold, stextsmax); + currentTextLine = oldTextLine; + scrollPos = std::min(oldScrollPos, numTextLines); - while (stextsel != -1 && !stext[stextsel].isSelectable()) { - stextsel--; + while (currentTextLine != -1 && !textLine[currentTextLine].isSelectable()) { + currentTextLine--; } } -void HealerEnter() +void StoreSession::HealerEnter() { - switch (stextsel) { + switch (currentTextLine) { case 12: - stextlhold = 12; - talker = TOWN_HEALER; - stextshold = TalkID::Healer; + oldTextLine = 12; + oldActiveStore = TalkID::Healer; StartStore(TalkID::Gossip); break; case 14: StartStore(TalkID::HealerBuy); break; case 18: - stextflag = TalkID::None; + activeStore = TalkID::None; break; } } -void HealerBuyEnter() +void StoreSession::HealerBuyEnter() { - if (stextsel == BackButtonLine()) { + if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Healer); - stextsel = 14; + currentTextLine = 14; return; } - stextlhold = stextsel; - stextvhold = stextsval; - stextshold = TalkID::HealerBuy; + oldTextLine = currentTextLine; + oldScrollPos = scrollPos; + oldActiveStore = TalkID::HealerBuy; - int idx = stextsval + ((stextsel - stextup) / 4); + int idx = scrollPos + ((currentTextLine - previousScrollPos) / 4); - if (!PlayerCanAfford(healitem[idx]._iIvalue)) { + if (!CanPlayerAfford(healerItems[idx]._iIvalue)) { StartStore(TalkID::NoMoney); return; } - if (!StoreAutoPlace(healitem[idx], false)) { + if (!GiveItemToPlayer(healerItems[idx], false)) { StartStore(TalkID::NoRoom); return; } - StoreItem = healitem[idx]; + tempItem = healerItems[idx]; StartStore(TalkID::Confirm); } -void StorytellerEnter() +void StoreSession::StorytellerEnter() { - switch (stextsel) { + switch (currentTextLine) { case 12: - stextlhold = 12; - talker = TOWN_STORY; - stextshold = TalkID::Storyteller; + oldTextLine = 12; + oldActiveStore = TalkID::Storyteller; StartStore(TalkID::Gossip); break; case 14: StartStore(TalkID::StorytellerIdentify); break; case 18: - stextflag = TalkID::None; + activeStore = TalkID::None; break; } } -void StorytellerIdentifyEnter() +void StoreSession::StorytellerIdentifyEnter() { - if (stextsel == BackButtonLine()) { + if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Storyteller); - stextsel = 14; + currentTextLine = 14; return; } - stextshold = TalkID::StorytellerIdentify; - stextlhold = stextsel; - stextvhold = stextsval; + oldActiveStore = TalkID::StorytellerIdentify; + oldTextLine = currentTextLine; + oldScrollPos = scrollPos; - int idx = stextsval + ((stextsel - stextup) / 4); + int idx = scrollPos + ((currentTextLine - previousScrollPos) / 4); - if (!PlayerCanAfford(storehold[idx]._iIvalue)) { + if (!CanPlayerAfford(playerItems[idx]._iIvalue)) { StartStore(TalkID::NoMoney); return; } - StoreItem = storehold[idx]; + tempItem = playerItems[idx]; StartStore(TalkID::Confirm); } -void TalkEnter() +void StoreSession::TalkEnter() { - if (stextsel == BackButtonLine()) { - StartStore(stextshold); - stextsel = stextlhold; + if (currentTextLine == BackButtonLine()) { + StartStore(oldActiveStore); + currentTextLine = oldTextLine; return; } int sn = 0; for (auto &quest : Quests) { - if (quest._qactive == QUEST_ACTIVE && QuestDialogTable[talker][quest._qidx] != TEXT_NONE && quest._qlog) + if (quest._qactive == QUEST_ACTIVE && QuestDialogTable[townerId][quest._qidx] != TEXT_NONE && quest._qlog) sn++; } int la = 2; @@ -1970,49 +1790,47 @@ void TalkEnter() sn = 15 - sn; } - if (stextsel == sn - 2) { - Towner *target = GetTowner(talker); + if (currentTextLine == sn - 2) { + Towner *target = GetTowner(townerId); assert(target != nullptr); InitQTextMsg(target->gossip); return; } for (auto &quest : Quests) { - if (quest._qactive == QUEST_ACTIVE && QuestDialogTable[talker][quest._qidx] != TEXT_NONE && quest._qlog) { - if (sn == stextsel) { - InitQTextMsg(QuestDialogTable[talker][quest._qidx]); + if (quest._qactive == QUEST_ACTIVE && QuestDialogTable[townerId][quest._qidx] != TEXT_NONE && quest._qlog) { + if (sn == currentTextLine) { + InitQTextMsg(QuestDialogTable[townerId][quest._qidx]); } sn += la; } } } -void TavernEnter() +void StoreSession::TavernEnter() { - switch (stextsel) { + switch (currentTextLine) { case 12: - stextlhold = 12; - talker = TOWN_TAVERN; - stextshold = TalkID::Tavern; + oldTextLine = 12; + oldActiveStore = TalkID::Tavern; StartStore(TalkID::Gossip); break; case 18: - stextflag = TalkID::None; + activeStore = TalkID::None; break; } } -void BarmaidEnter() +void StoreSession::BarmaidEnter() { - switch (stextsel) { + switch (currentTextLine) { case 12: - stextlhold = 12; - talker = TOWN_BMAID; - stextshold = TalkID::Barmaid; + oldTextLine = 12; + oldActiveStore = TalkID::Barmaid; StartStore(TalkID::Gossip); break; case 14: - stextflag = TalkID::None; + activeStore = TalkID::None; IsStashOpen = true; Stash.RefreshItemStatFlags(); invflag = true; @@ -2023,27 +1841,26 @@ void BarmaidEnter() } break; case 18: - stextflag = TalkID::None; + activeStore = TalkID::None; break; } } -void DrunkEnter() +void StoreSession::DrunkEnter() { - switch (stextsel) { + switch (currentTextLine) { case 12: - stextlhold = 12; - talker = TOWN_DRUNK; - stextshold = TalkID::Drunk; + oldTextLine = 12; + oldActiveStore = TalkID::Drunk; StartStore(TalkID::Gossip); break; case 18: - stextflag = TalkID::None; + activeStore = TalkID::None; break; } } -int TakeGold(Player &player, int cost, bool skipMaxPiles) +int StoreSession::TakeGold(Player &player, int cost, bool skipMaxPiles) { for (int i = 0; i < player._pNumInv; i++) { auto &item = player.InvList[i]; @@ -2064,7 +1881,7 @@ int TakeGold(Player &player, int cost, bool skipMaxPiles) return cost; } -void DrawSelector(const Surface &out, const Rectangle &rect, std::string_view text, UiFlags flags) +void StoreSession::DrawSelector(const Surface &out, const Rectangle &rect, std::string_view text, UiFlags flags) { int lineWidth = GetLineWidth(text); @@ -2081,56 +1898,29 @@ void DrawSelector(const Surface &out, const Rectangle &rect, std::string_view te ClxDraw(out, { x2, rect.position.y + 13 }, (*pSPentSpn2Cels)[PentSpn2Spin()]); } -} // namespace - -void AddStoreHoldRepair(Item *itm, int8_t i) -{ - Item *item; - int v; - - item = &storehold[storenumh]; - storehold[storenumh] = *itm; - - int due = item->_iMaxDur - item->_iDurability; - if (item->_iMagical != ITEM_QUALITY_NORMAL && item->_iIdentified) { - v = 30 * item->_iIvalue * due / (item->_iMaxDur * 100 * 2); - if (v == 0) - return; - } else { - v = item->_ivalue * due / (item->_iMaxDur * 2); - v = std::max(v, 1); - } - item->_iIvalue = v; - item->_ivalue = v; - storehidx[storenumh] = i; - storenumh++; -} - -void InitStores() +void StoreSession::InitStores() { - ClearSText(0, STORE_LINES); - stextflag = TalkID::None; - stextsize = false; - stextscrl = false; - numpremium = 0; - premiumlevel = 1; + ClearSText(0, NumStoreLines); + activeStore = TalkID::None; + isTextFullSize = false; + hasScrollbar = false; + premiumItemCount = 0; + premiumItemLevel = 1; - for (auto &premiumitem : premiumitems) + for (auto &premiumitem : premiumItems) premiumitem.clear(); - boyitem.clear(); - boylevel = 0; + boyItem.clear(); + boyItemLevel = 0; } -void SetupTownStores() +void StoreSession::SetupTownStores() { - Player &myPlayer = *MyPlayer; - - int l = myPlayer.getCharacterLevel() / 2; + int l = MyPlayer->getCharacterLevel() / 2; if (!gbIsMultiplayer) { l = 0; for (int i = 0; i < NUMLEVELS; i++) { - if (myPlayer._pLvlVisited[i]) + if (MyPlayer->_pLvlVisited[i]) l = i; } } @@ -2139,34 +1929,34 @@ void SetupTownStores() SpawnSmith(l); SpawnWitch(l); SpawnHealer(l); - SpawnBoy(myPlayer.getCharacterLevel()); - SpawnPremium(myPlayer); + SpawnBoy(MyPlayer->getCharacterLevel()); + SpawnPremium(*MyPlayer); } -void FreeStoreMem() +void StoreSession::FreeStoreMem() { if (*sgOptions.Gameplay.showItemGraphicsInStores) { FreeHalfSizeItemSprites(); } - stextflag = TalkID::None; - for (STextStruct &entry : stext) { + activeStore = TalkID::None; + for (STextStruct &entry : textLine) { entry.text.clear(); entry.text.shrink_to_fit(); } } -void PrintSString(const Surface &out, int margin, int line, std::string_view text, UiFlags flags, int price, int cursId, bool cursIndent) +void StoreSession::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; - if (!stextsize) { + if (!isTextFullSize) { sx += 320; } - const int sy = uiPosition.y + PaddingTop + stext[line].y + stext[line]._syoff; + const int sy = uiPosition.y + PaddingTop + textLine[line].y + textLine[line]._syoff; - int width = stextsize ? 575 : 255; - if (stextscrl && line >= 4 && line <= 20) { + int width = isTextFullSize ? 575 : 255; + if (hasScrollbar && line >= 4 && line <= 20) { width -= 9; // Space for the selector } width -= margin * 2; @@ -2205,18 +1995,18 @@ void PrintSString(const Surface &out, int margin, int line, std::string_view tex if (price > 0) DrawString(out, FormatInteger(price), rect, { .flags = flags | UiFlags::AlignRight }); - if (stextsel == line) { + if (currentTextLine == line) { DrawSelector(out, rect, text, flags); } } -void DrawSLine(const Surface &out, int sy) +void StoreSession::DrawSLine(const Surface &out, int sy) { const Point uiPosition = GetUIRectangle().position; int sx = 26; int width = 587; - if (!stextsize) { + if (!isTextFullSize) { sx += SidePanelSize.width; width -= SidePanelSize.width; } @@ -2228,26 +2018,28 @@ void DrawSLine(const Surface &out, int sy) memcpy(dst, src, width); } -void DrawSTextHelp() +void StoreSession::DrawSTextHelp() { - stextsel = -1; - stextsize = true; + currentTextLine = -1; + isTextFullSize = true; } -void ClearSText(int s, int e) +void StoreSession::ClearSText(int s, int e) { for (int i = s; i < e; i++) { - stext[i]._sx = 0; - stext[i]._syoff = 0; - stext[i].text.clear(); - stext[i].text.shrink_to_fit(); - stext[i].flags = UiFlags::None; - stext[i].type = STextStruct::Label; - stext[i]._sval = 0; + textLine[i]._sx = 0; + textLine[i]._syoff = 0; + textLine[i].text.clear(); + textLine[i].text.shrink_to_fit(); + textLine[i].flags = UiFlags::None; + textLine[i].type = STextStruct::Label; + textLine[i]._sval = 0; } + + // std::fill(storeLineMapping.begin(), storeLineMapping.end(), TalkID::None); } -void StartStore(TalkID s) +void StoreSession::StartStore(TalkID s) { if (*sgOptions.Gameplay.showItemGraphicsInStores) { CreateHalfSizeItemSprites(); @@ -2255,139 +2047,146 @@ void StartStore(TalkID s) SpellbookFlag = false; CloseInventory(); CloseCharPanel(); - RenderGold = false; + renderGold = false; QuestLogIsOpen = false; CloseGoldDrop(); - ClearSText(0, STORE_LINES); - ReleaseStoreBtn(); + ClearSText(0, NumStoreLines); + ReleaseStoreButton(); switch (s) { case TalkID::Smith: - StartSmith(); + townerId = TOWN_SMITH; + SetupTownerMenuScreen(); break; case TalkID::SmithBuy: { bool hasAnyItems = false; - for (int i = 0; !smithitem[i].isEmpty(); i++) { + for (int i = 0; !smithItems[i].isEmpty(); i++) { hasAnyItems = true; break; } if (hasAnyItems) - StartSmithBuy(); + SetupTownerBuyScreen(s); else { - stextflag = TalkID::SmithBuy; - stextlhold = 12; + activeStore = TalkID::SmithBuy; + oldTextLine = 12; StoreESC(); return; } break; } case TalkID::SmithSell: - StartSmithSell(); + SetupTownerSellScreen(s); break; case TalkID::SmithRepair: - StartSmithRepair(); + SetupTownerRepairScreen(); break; case TalkID::Witch: - StartWitch(); + townerId = TOWN_WITCH; + SetupTownerMenuScreen(); break; case TalkID::WitchBuy: - if (storenumh > 0) - StartWitchBuy(); + if (currentItemIndex > 0) + SetupTownerBuyScreen(s); break; case TalkID::WitchSell: - StartWitchSell(); + SetupTownerSellScreen(s); break; case TalkID::WitchRecharge: - StartWitchRecharge(); + SetupTownerRechargeScreen(); break; case TalkID::NoMoney: - StoreNoMoney(); + SetupCannotAffordScreen(); break; case TalkID::NoRoom: - StoreNoRoom(); + SetupNoRoomScreen(); break; case TalkID::Confirm: - StoreConfirm(StoreItem); + SetupConfirmScreen(tempItem); break; case TalkID::Boy: - StartBoy(); + townerId = TOWN_PEGBOY; + SetupTownerMenuScreen(); break; case TalkID::BoyBuy: - SStartBoyBuy(); + SetupTownerBuyScreen(s); break; case TalkID::Healer: - StartHealer(); + townerId = TOWN_HEALER; + SetupTownerMenuScreen(); break; case TalkID::Storyteller: - StartStoryteller(); + townerId = TOWN_STORY; + SetupTownerMenuScreen(); break; case TalkID::HealerBuy: - if (storenumh > 0) - StartHealerBuy(); + if (currentItemIndex > 0) + SetupTownerBuyScreen(s); break; case TalkID::StorytellerIdentify: - StartStorytellerIdentify(); + SetupTownerIdentifyScreen(); break; case TalkID::SmithPremiumBuy: - if (!StartSmithPremiumBuy()) - return; + SetupTownerBuyScreen(s); break; case TalkID::Gossip: - StartTalk(); + SetupGossipScreen(); break; case TalkID::StorytellerIdentifyShow: - StartStorytellerIdentifyShow(StoreItem); + SetupTownerIdentifyResultScreen(tempItem); break; case TalkID::Tavern: - StartTavern(); + townerId = TOWN_TAVERN; + SetupTownerMenuScreen(); break; case TalkID::Drunk: - StartDrunk(); + townerId = TOWN_DRUNK; + SetupTownerMenuScreen(); break; case TalkID::Barmaid: - StartBarmaid(); + townerId = TOWN_BMAID; + SetupTownerMenuScreen(); break; case TalkID::None: break; } - stextsel = -1; - for (int i = 0; i < STORE_LINES; i++) { - if (stext[i].isSelectable()) { - stextsel = i; + currentTextLine = -1; + for (int i = 0; i < NumStoreLines; i++) { + if (textLine[i].isSelectable()) { + currentTextLine = i; break; } } - stextflag = s; + activeStore = s; } -void DrawSText(const Surface &out) +void StoreSession::DrawSText(const Surface &out) { - if (!stextsize) - DrawSTextBack(out); + if (!isTextFullSize) + DrawTextUI(out); else DrawQTextBack(out); - if (stextscrl) { - switch (stextflag) { + if (hasScrollbar) { + switch (activeStore) { case TalkID::SmithBuy: - ScrollSmithBuy(stextsval); + SetupTownerItemList(TalkID::SmithBuy, scrollPos); break; case TalkID::SmithSell: case TalkID::SmithRepair: case TalkID::WitchSell: case TalkID::WitchRecharge: case TalkID::StorytellerIdentify: - ScrollSmithSell(stextsval); + SetupTownerItemList(activeStore, scrollPos, false); break; case TalkID::WitchBuy: - ScrollWitchBuy(stextsval); + SetupTownerItemList(activeStore, scrollPos); break; case TalkID::HealerBuy: - ScrollHealerBuy(stextsval); + SetupTownerItemList(activeStore, scrollPos); break; case TalkID::SmithPremiumBuy: - ScrollSmithPremiumBuy(stextsval); + SetupTownerItemList(activeStore, scrollPos); break; default: break; @@ -2396,22 +2195,22 @@ void DrawSText(const Surface &out) CalculateLineHeights(); const Point uiPosition = GetUIRectangle().position; - for (int i = 0; i < STORE_LINES; i++) { - if (stext[i].isDivider()) - DrawSLine(out, uiPosition.y + PaddingTop + stext[i].y + TextHeight() / 2); - else if (stext[i].hasText()) - PrintSString(out, stext[i]._sx, i, stext[i].text, stext[i].flags, stext[i]._sval, stext[i].cursId, stext[i].cursIndent); + for (int i = 0; i < NumStoreLines; i++) { + if (textLine[i].isDivider()) + DrawSLine(out, uiPosition.y + PaddingTop + textLine[i].y + TextHeight() / 2); + else if (textLine[i].hasText()) + PrintSString(out, textLine[i]._sx, i, textLine[i].text, textLine[i].flags, textLine[i]._sval, textLine[i].cursId, textLine[i].cursIndent); } - if (RenderGold) { - PrintSString(out, 28, 1, fmt::format(fmt::runtime(_("Your gold: {:s}")), FormatInteger(TotalPlayerGold())).c_str(), UiFlags::ColorWhitegold | UiFlags::AlignRight); + if (renderGold) { + PrintSString(out, 28, 1, fmt::format(fmt::runtime(_("Your gold: {:s}")), FormatInteger(GetTotalPlayerGold())).c_str(), UiFlags::ColorWhitegold | UiFlags::AlignRight); } - if (stextscrl) - DrawSSlider(out, 4, 20); + if (hasScrollbar) + DrawScrollbar(out, 4, 20); } -void StoreESC() +void StoreSession::StoreESC() { if (qtextflag) { qtextflag = false; @@ -2420,7 +2219,7 @@ void StoreESC() return; } - switch (stextflag) { + switch (activeStore) { case TalkID::Smith: case TalkID::Witch: case TalkID::Boy: @@ -2430,47 +2229,47 @@ void StoreESC() case TalkID::Tavern: case TalkID::Drunk: case TalkID::Barmaid: - stextflag = TalkID::None; + activeStore = TalkID::None; break; case TalkID::Gossip: - StartStore(stextshold); - stextsel = stextlhold; + StartStore(oldActiveStore); + currentTextLine = oldTextLine; break; case TalkID::SmithBuy: StartStore(TalkID::Smith); - stextsel = 12; + currentTextLine = 12; break; case TalkID::SmithPremiumBuy: StartStore(TalkID::Smith); - stextsel = 14; + currentTextLine = 14; break; case TalkID::SmithSell: StartStore(TalkID::Smith); - stextsel = 16; + currentTextLine = 16; break; case TalkID::SmithRepair: StartStore(TalkID::Smith); - stextsel = 18; + currentTextLine = 18; break; case TalkID::WitchBuy: StartStore(TalkID::Witch); - stextsel = 14; + currentTextLine = 14; break; case TalkID::WitchSell: StartStore(TalkID::Witch); - stextsel = 16; + currentTextLine = 16; break; case TalkID::WitchRecharge: StartStore(TalkID::Witch); - stextsel = 18; + currentTextLine = 18; break; case TalkID::HealerBuy: StartStore(TalkID::Healer); - stextsel = 14; + currentTextLine = 14; break; case TalkID::StorytellerIdentify: StartStore(TalkID::Storyteller); - stextsel = 14; + currentTextLine = 14; break; case TalkID::StorytellerIdentifyShow: StartStore(TalkID::StorytellerIdentify); @@ -2478,132 +2277,130 @@ void StoreESC() case TalkID::NoMoney: case TalkID::NoRoom: case TalkID::Confirm: - StartStore(stextshold); - stextsel = stextlhold; - stextsval = stextvhold; + StartStore(oldActiveStore); + currentTextLine = oldTextLine; + scrollPos = oldScrollPos; break; case TalkID::None: break; } } -void StoreUp() +void StoreSession::StoreUp() { PlaySFX(SfxID::MenuMove); - if (stextsel == -1) { + if (currentTextLine == -1) { return; } - if (stextscrl) { - if (stextsel == stextup) { - if (stextsval != 0) - stextsval--; + if (hasScrollbar) { + if (currentTextLine == previousScrollPos) { + if (scrollPos != 0) + scrollPos--; return; } - stextsel--; - while (!stext[stextsel].isSelectable()) { - if (stextsel == 0) - stextsel = STORE_LINES - 1; + currentTextLine--; + while (!textLine[currentTextLine].isSelectable()) { + if (currentTextLine == 0) + currentTextLine = NumStoreLines - 1; else - stextsel--; + currentTextLine--; } return; } - if (stextsel == 0) - stextsel = STORE_LINES - 1; + if (currentTextLine == 0) + currentTextLine = NumStoreLines - 1; else - stextsel--; + currentTextLine--; - while (!stext[stextsel].isSelectable()) { - if (stextsel == 0) - stextsel = STORE_LINES - 1; + while (!textLine[currentTextLine].isSelectable()) { + if (currentTextLine == 0) + currentTextLine = NumStoreLines - 1; else - stextsel--; + currentTextLine--; } } -void StoreDown() +void StoreSession::StoreDown() { PlaySFX(SfxID::MenuMove); - if (stextsel == -1) { + if (currentTextLine == -1) { return; } - if (stextscrl) { - if (stextsel == stextdown) { - if (stextsval < stextsmax) - stextsval++; + if (hasScrollbar) { + if (currentTextLine == nextScrollPos) { + if (scrollPos < numTextLines) + scrollPos++; return; } - stextsel++; - while (!stext[stextsel].isSelectable()) { - if (stextsel == STORE_LINES - 1) - stextsel = 0; + currentTextLine++; + while (!textLine[currentTextLine].isSelectable()) { + if (currentTextLine == NumStoreLines - 1) + currentTextLine = 0; else - stextsel++; + currentTextLine++; } return; } - if (stextsel == STORE_LINES - 1) - stextsel = 0; + if (currentTextLine == NumStoreLines - 1) + currentTextLine = 0; else - stextsel++; + currentTextLine++; - while (!stext[stextsel].isSelectable()) { - if (stextsel == STORE_LINES - 1) - stextsel = 0; + while (!textLine[currentTextLine].isSelectable()) { + if (currentTextLine == NumStoreLines - 1) + currentTextLine = 0; else - stextsel++; + currentTextLine++; } } -void StorePrior() +void StoreSession::StorePrior() { PlaySFX(SfxID::MenuMove); - if (stextsel != -1 && stextscrl) { - if (stextsel == stextup) { - stextsval = std::max(stextsval - 4, 0); + if (currentTextLine != -1 && hasScrollbar) { + if (currentTextLine == previousScrollPos) { + scrollPos = std::max(scrollPos - 4, 0); } else { - stextsel = stextup; + currentTextLine = previousScrollPos; } } } -void StoreNext() +void StoreSession::StoreNext() { PlaySFX(SfxID::MenuMove); - if (stextsel != -1 && stextscrl) { - if (stextsel == stextdown) { - if (stextsval < stextsmax) - stextsval += 4; - if (stextsval > stextsmax) - stextsval = stextsmax; + if (currentTextLine != -1 && hasScrollbar) { + if (currentTextLine == nextScrollPos) { + if (scrollPos < numTextLines) + scrollPos += 4; + if (scrollPos > numTextLines) + scrollPos = numTextLines; } else { - stextsel = stextdown; + currentTextLine = nextScrollPos; } } } -void TakePlrsMoney(int cost) +void StoreSession::TakePlrsMoney(int cost) { - Player &myPlayer = *MyPlayer; + MyPlayer->_pGold -= std::min(cost, MyPlayer->_pGold); - myPlayer._pGold -= std::min(cost, myPlayer._pGold); - - cost = TakeGold(myPlayer, cost, true); + cost = TakeGold(*MyPlayer, cost, true); if (cost != 0) { - cost = TakeGold(myPlayer, cost, false); + cost = TakeGold(*MyPlayer, cost, false); } Stash.gold -= cost; Stash.dirty = true; } -void StoreEnter() +void StoreSession::StoreEnter() { if (qtextflag) { qtextflag = false; @@ -2614,7 +2411,7 @@ void StoreEnter() } PlaySFX(SfxID::MenuSelect); - switch (stextflag) { + switch (activeStore) { case TalkID::Smith: SmithEnter(); break; @@ -2644,12 +2441,12 @@ void StoreEnter() break; case TalkID::NoMoney: case TalkID::NoRoom: - StartStore(stextshold); - stextsel = stextlhold; - stextsval = stextvhold; + StartStore(oldActiveStore); + currentTextLine = oldTextLine; + scrollPos = oldScrollPos; break; case TalkID::Confirm: - ConfirmEnter(StoreItem); + ConfirmEnter(tempItem); break; case TalkID::Boy: BoyEnter(); @@ -2689,20 +2486,20 @@ void StoreEnter() } } -void CheckStoreBtn() +void StoreSession::CheckStoreButton() { const Point uiPosition = GetUIRectangle().position; const Rectangle windowRect { { uiPosition.x + 344, uiPosition.y + PaddingTop - 7 }, { 271, 303 } }; const Rectangle windowRectFull { { uiPosition.x + 24, uiPosition.y + PaddingTop - 7 }, { 591, 303 } }; - if (!stextsize) { + if (!isTextFullSize) { if (!windowRect.contains(MousePosition)) { - while (stextflag != TalkID::None) + while (activeStore != TalkID::None) StoreESC(); } } else { if (!windowRectFull.contains(MousePosition)) { - while (stextflag != TalkID::None) + while (activeStore != TalkID::None) StoreESC(); } } @@ -2711,26 +2508,26 @@ void CheckStoreBtn() qtextflag = false; if (leveltype == DTYPE_TOWN) stream_stop(); - } else if (stextsel != -1) { + } else if (currentTextLine != -1) { const int relativeY = MousePosition.y - (uiPosition.y + PaddingTop); - if (stextscrl && MousePosition.x > 600 + uiPosition.x) { + if (hasScrollbar && MousePosition.x > 600 + uiPosition.x) { // Scroll bar is always measured in terms of the small line height. int y = relativeY / SmallLineHeight; if (y == 4) { - if (stextscrlubtn <= 0) { + if (countdownScrollUp <= 0) { StoreUp(); - stextscrlubtn = 10; + countdownScrollUp = 10; } else { - stextscrlubtn--; + countdownScrollUp--; } } if (y == 20) { - if (stextscrldbtn <= 0) { + if (countdownScrollDown <= 0) { StoreDown(); - stextscrldbtn = 10; + countdownScrollDown = 10; } else { - stextscrldbtn--; + countdownScrollDown--; } } return; @@ -2739,34 +2536,44 @@ void CheckStoreBtn() int y = relativeY / LineHeight(); // Large small fonts draw beyond LineHeight. Check if the click was on the overflow text. - if (IsSmallFontTall() && y > 0 && y < STORE_LINES - && stext[y - 1].hasText() && !stext[y].hasText() - && relativeY < stext[y - 1].y + LargeTextHeight) { + if (IsSmallFontTall() && y > 0 && y < NumStoreLines + && textLine[y - 1].hasText() && !textLine[y].hasText() + && relativeY < textLine[y - 1].y + LargeTextHeight) { --y; } if (y >= 5) { if (y >= BackButtonLine() + 1) y = BackButtonLine(); - if (stextscrl && y <= 20 && !stext[y].isSelectable()) { - if (stext[y - 2].isSelectable()) { + if (hasScrollbar && y <= 20 && !textLine[y].isSelectable()) { + if (textLine[y - 2].isSelectable()) { y -= 2; - } else if (stext[y - 1].isSelectable()) { + } else if (textLine[y - 1].isSelectable()) { y--; } } - if (stext[y].isSelectable() || (stextscrl && y == BackButtonLine())) { - stextsel = y; + if (textLine[y].isSelectable() || (hasScrollbar && y == BackButtonLine())) { + currentTextLine = y; StoreEnter(); } } } } -void ReleaseStoreBtn() +void StoreSession::ReleaseStoreButton() +{ + countdownScrollUp = -1; + countdownScrollDown = -1; +} + +bool StoreSession::IsPlayerInStore() const +{ + return activeStore != TalkID::None; +} + +void StoreSession::ExitStore() { - stextscrlubtn = -1; - stextscrldbtn = -1; + activeStore = TalkID::None; } } // namespace devilution diff --git a/Source/stores.h b/Source/stores.h index 631ca9a285a..75371813453 100644 --- a/Source/stores.h +++ b/Source/stores.h @@ -12,14 +12,32 @@ #include "control.h" #include "engine.h" #include "engine/clx_sprite.hpp" +#include "engine/random.hpp" +#include "options.h" +#include "qol/stash.h" +#include "towners.h" #include "utils/attributes.h" namespace devilution { +/* 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 NumHealerItems = 20; +constexpr int NumWitchItems = 25; -#define WITCH_ITEMS 25 -#define SMITH_ITEMS 25 -#define SMITH_PREMIUM_ITEMS 15 -#define STORE_LINES 104 +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, @@ -48,60 +66,261 @@ enum class TalkID : uint8_t { Barmaid, }; -/** Currently active store */ -extern TalkID stextflag; - -/** Current index into storehidx/storehold */ -extern DVL_API_FOR_TEST int storenumh; -/** Map of inventory items being presented in the store */ -extern int8_t storehidx[48]; -/** Copies of the players items as presented in the store */ -extern DVL_API_FOR_TEST Item storehold[48]; - -/** Items sold by Griswold */ -extern Item smithitem[SMITH_ITEMS]; -/** Number of premium items for sale by Griswold */ -extern int numpremium; -/** Base level of current premium items sold by Griswold */ -extern int premiumlevel; -/** Premium items sold by Griswold */ -extern Item premiumitems[SMITH_PREMIUM_ITEMS]; - -/** Items sold by Pepin */ -extern Item healitem[20]; - -/** Items sold by Adria */ -extern Item witchitem[WITCH_ITEMS]; - -/** Current level of the item sold by Wirt */ -extern int boylevel; -/** Current item sold by Wirt */ -extern Item boyitem; - -void AddStoreHoldRepair(Item *itm, int8_t i); - -/** 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 ClearSText(int s, int e); -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 CheckStoreBtn(); -void ReleaseStoreBtn(); +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(); + } +}; + +struct StoreMenuOption { + TalkID action; + std::string text; +}; + +struct TownerLine { + const std::string menuHeader; + const StoreMenuOption *menuOptions; + size_t numOptions; +}; + +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 SetupCannotAffordScreen(); + void SetupNoRoomScreen(); + void SetupConfirmScreen(Item &item); + void SetupItemList(TalkID talkId, Item *itemData, int storeLimit, 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(); + + /* Gossip */ + + void SetupGossipScreen(); + + /* Buy */ + + void SetBuyScreenHeader(); + void UpdateBookMinMagic(Item &bookItem); + void UpdateItemStatFlags(TalkID talkId); + void SetupTownerBuyScreen(TalkID talkId); + + /* Sell */ + + bool CanSellToTowner(TalkID talkId, int i); + void SetupTownerSellScreen(TalkID talkId); + + /* Repair */ + + bool CanTownerRepair(int i) const; + void AddPlayerItemToRepairList(Item *itm, int8_t i); + void SetupTownerRepairScreen(); + + /* Recharge */ + + bool CanTownerRecharge(int i); + void AddPlayerItemToRechargeList(Item itm, int8_t i); + void SetupTownerRechargeScreen(); + + /* Identify */ + + bool CanTownerIdentify(Item *i); + void AddPlayerItemToIdentifyList(Item itm, int8_t i); + 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 ClearSText(int s, int e); + 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::array playerItems; // Copies of the players items as presented in the store + std::array 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::array premiumItems; // Premium items sold by Griswold + std::array healerItems; // Items sold by Pepin + std::array 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; +}; + +extern StoreSession Stores; } // namespace devilution diff --git a/Source/towners.cpp b/Source/towners.cpp index b89786a185e..5468d078c01 100644 --- a/Source/towners.cpp +++ b/Source/towners.cpp @@ -340,7 +340,7 @@ void TalkToBarOwner(Player &player, Towner &barOwner) } TownerTalk(TEXT_OGDEN1); - StartStore(TalkID::Tavern); + Stores.StartStore(TalkID::Tavern); } void TalkToDeadguy(Player &player, Towner & /*deadguy*/) @@ -408,7 +408,7 @@ void TalkToBlackSmith(Player &player, Towner &blackSmith) } TownerTalk(TEXT_GRISWOLD1); - StartStore(TalkID::Smith); + Stores.StartStore(TalkID::Smith); } void TalkToWitch(Player &player, Towner & /*witch*/) @@ -458,7 +458,7 @@ void TalkToWitch(Player &player, Towner & /*witch*/) } TownerTalk(TEXT_ADRIA1); - StartStore(TalkID::Witch); + Stores.StartStore(TalkID::Witch); } void TalkToBarmaid(Player &player, Towner & /*barmaid*/) @@ -473,13 +473,13 @@ void TalkToBarmaid(Player &player, Towner & /*barmaid*/) } TownerTalk(TEXT_GILLIAN1); - StartStore(TalkID::Barmaid); + Stores.StartStore(TalkID::Barmaid); } void TalkToDrunk(Player & /*player*/, Towner & /*drunk*/) { TownerTalk(TEXT_FARNHAM1); - StartStore(TalkID::Drunk); + Stores.StartStore(TalkID::Drunk); } void TalkToHealer(Player &player, Towner &healer) @@ -517,13 +517,13 @@ void TalkToHealer(Player &player, Towner &healer) } TownerTalk(TEXT_PEPIN1); - StartStore(TalkID::Healer); + Stores.StartStore(TalkID::Healer); } void TalkToBoy(Player & /*player*/, Towner & /*boy*/) { TownerTalk(TEXT_WIRT1); - StartStore(TalkID::Boy); + Stores.StartStore(TalkID::Boy); } void TalkToStoryteller(Player &player, Towner & /*storyteller*/) @@ -559,7 +559,7 @@ void TalkToStoryteller(Player &player, Towner & /*storyteller*/) } TownerTalk(TEXT_STORY1); - StartStore(TalkID::Storyteller); + Stores.StartStore(TalkID::Storyteller); } void TalkToCow(Player &player, Towner &cow) @@ -912,7 +912,7 @@ void UpdateCowFarmerAnimAfterQuestComplete() #ifdef _DEBUG bool DebugTalkToTowner(std::string_view targetName) { - SetupTownStores(); + Stores.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 3096da77cf4..44fe4e33aac 100644 --- a/Source/track.cpp +++ b/Source/track.cpp @@ -66,7 +66,7 @@ void RepeatMouseAction() if (sgbMouseDown == CLICK_NONE && ControllerActionHeld == GameActionType_NONE) return; - if (stextflag != TalkID::None) + if (Stores.IsPlayerInStore()) return; if (LastMouseButtonAction == MouseActionType::None) diff --git a/test/fixtures/memory_map/game.txt b/test/fixtures/memory_map/game.txt index c6e6402ac3c..f1c98aac768 100644 --- a/test/fixtures/memory_map/game.txt +++ b/test/fixtures/memory_map/game.txt @@ -47,8 +47,8 @@ M_DL 12544 8 dLight M_DL 12544 8 dPreLight M_DL 1600 8 AutomapView M_DL 12544 8 dMissile -R 32 numpremium -R 32 premiumlevel +R 32 numPremiumItems +R 32 premiumItemLevel C_DA 6 item PremiumItems C_HF 15 item PremiumItems R 8 AutomapActive diff --git a/test/stores_test.cpp b/test/stores_test.cpp index b321cf87ecd..3403457d3fa 100644 --- a/test/stores_test.cpp +++ b/test/stores_test.cpp @@ -6,11 +6,11 @@ using namespace devilution; namespace { -TEST(Stores, AddStoreHoldRepair_magic) +TEST(Stores, AddPlayerItemToRepairList_magic) { Item *item; - item = &storehold[0]; + item = &playerItem[0]; item->_iMaxDur = 60; item->_iDurability = item->_iMaxDur; @@ -23,27 +23,27 @@ TEST(Stores, AddStoreHoldRepair_magic) item->_ivalue = 2000; item->_iIvalue = 19000; item->_iDurability = i; - storenumh = 0; - AddStoreHoldRepair(item, 0); - EXPECT_EQ(1, storenumh); + currentItemIndex = 0; + AddPlayerItemToRepairList(item, 0); + EXPECT_EQ(1, currentItemIndex); EXPECT_EQ(95 * (item->_iMaxDur - i) / 2, item->_ivalue); } item->_iDurability = 59; - storenumh = 0; + currentItemIndex = 0; item->_ivalue = 500; item->_iIvalue = 30; // To cheap to repair - AddStoreHoldRepair(item, 0); - EXPECT_EQ(0, storenumh); + AddPlayerItemToRepairList(item, 0); + EXPECT_EQ(0, currentItemIndex); EXPECT_EQ(30, item->_iIvalue); EXPECT_EQ(500, item->_ivalue); } -TEST(Stores, AddStoreHoldRepair_normal) +TEST(Stores, AddPlayerItemToRepairList_normal) { Item *item; - item = &storehold[0]; + item = &playerItem[0]; item->_iMaxDur = 20; item->_iDurability = item->_iMaxDur; @@ -56,18 +56,18 @@ TEST(Stores, AddStoreHoldRepair_normal) item->_ivalue = 2000; item->_iIvalue = item->_ivalue; item->_iDurability = i; - storenumh = 0; - AddStoreHoldRepair(item, 0); - EXPECT_EQ(1, storenumh); + currentItemIndex = 0; + AddPlayerItemToRepairList(item, 0); + EXPECT_EQ(1, currentItemIndex); EXPECT_EQ(50 * (item->_iMaxDur - i), item->_ivalue); } item->_iDurability = 19; - storenumh = 0; + currentItemIndex = 0; item->_ivalue = 10; // less than 1 per dur item->_iIvalue = item->_ivalue; - AddStoreHoldRepair(item, 0); - EXPECT_EQ(1, storenumh); + AddPlayerItemToRepairList(item, 0); + EXPECT_EQ(1, currentItemIndex); EXPECT_EQ(1, item->_ivalue); EXPECT_EQ(1, item->_iIvalue); }