diff --git a/Source/control.cpp b/Source/control.cpp index 5af03b80cd5a..5168cbc9cc20 100644 --- a/Source/control.cpp +++ b/Source/control.cpp @@ -686,7 +686,7 @@ bool IsLevelUpButtonVisible() if (ControlMode == ControlTypes::VirtualGamepad) { return false; } - if (ActiveStore != 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 9c6cfd3bb562..8f6399380fdb 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 || ActiveStore != 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 && ActiveStore == 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 && ActiveStore == 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() || ActiveStore != 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 65982b7d55f2..310be126d304 100644 --- a/Source/controls/plrctrls.cpp +++ b/Source/controls/plrctrls.cpp @@ -65,7 +65,7 @@ quest_id pcursquest = Q_INVALID; */ bool InGameMenu() { - return ActiveStore != 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 (ActiveStore != 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 5e2b39067908..d7973238dfdd 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 (ActiveStore == 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 6bbe710c4d8b..1a0adc75428a 100644 --- a/Source/controls/touch/renderers.cpp +++ b/Source/controls/touch/renderers.cpp @@ -430,7 +430,7 @@ VirtualGamepadButtonType PrimaryActionButtonRenderer::GetButtonType() VirtualGamepadButtonType PrimaryActionButtonRenderer::GetTownButtonType() { - if (ActiveStore != 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 e50b62433bdb..13afadc2657c 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 (ActiveStore != TalkID::None) { - CheckStoreBtn(); + if (Stores.IsPlayerInStore()) { + Stores.CheckStoreButton(); return; } @@ -417,8 +417,8 @@ void LeftMouseUp(uint16_t modState) } if (LevelButtonDown) CheckLevelButtonUp(); - if (ActiveStore != TalkID::None) - ReleaseStoreBtn(); + if (Stores.IsPlayerInStore()) + Stores.ReleaseStoreButton(); } void RightMouseDown(bool isShiftHeld) @@ -439,7 +439,7 @@ void RightMouseDown(bool isShiftHeld) doom_close(); return; } - if (ActiveStore != 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 (ActiveStore != 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 (ActiveStore != 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 (ActiveStore != 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 (ActiveStore != TalkID::None) { - StorePrior(); + if (Stores.IsPlayerInStore()) { + Stores.StorePrior(); } else if (ChatLogFlag) { ChatLogScrollTop(); } return; case SDLK_PAGEDOWN: - if (ActiveStore != 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 (ActiveStore != 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 (ActiveStore != 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 (ActiveStore != 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 (ActiveStore != 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 (ActiveStore != TalkID::None) + if (Stores.IsPlayerInStore()) return; invflag = !invflag; if (!IsLeftPanelOpen() && CanPanelsCoverView()) { @@ -1537,7 +1537,7 @@ void InventoryKeyPressed() void CharacterSheetKeyPressed() { - if (ActiveStore != 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 (ActiveStore != TalkID::None) + if (Stores.IsPlayerInStore()) return; if (!QuestLogIsOpen) { StartQuestlog(); @@ -1580,7 +1580,7 @@ void QuestLogKeyPressed() void DisplaySpellsKeyPressed() { - if (ActiveStore != TalkID::None) + if (Stores.IsPlayerInStore()) return; CloseCharPanel(); QuestLogIsOpen = false; @@ -1596,7 +1596,7 @@ void DisplaySpellsKeyPressed() void SpellBookKeyPressed() { - if (ActiveStore != 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 && ActiveStore == 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 && ActiveStore == 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 (ActiveStore != 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 dcd4fde5bf72..f6708f559a3e 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 (ActiveStore == 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 (ActiveStore != 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 b9ecaf318fe7..c62aa00eb57b 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 02384c43bc4a..e4af6a924059 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 (ActiveStore != 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 2c7effdee298..c2babdbe5c22 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 = SmithItems[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++) - SmithItems[i].clear(); + for (int i = iCnt; i < NumSmithItems; i++) + Stores.smithItems[i].clear(); - SortVendor(SmithItems + PinnedItemCount); + SortVendor(Stores.smithItems, PinnedItemCount); } void SpawnPremium(const Player &player) { int lvl = player.getCharacterLevel(); - int maxItems = gbIsHellfire ? SMITH_PREMIUM_ITEMS : 6; - if (PremiumItemCount < maxItems) { + int maxItems = gbIsHellfire ? NumSmithPremiumItems : 6; + if (Stores.premiumItemCount < maxItems) { for (int i = 0; i < maxItems; i++) { - if (PremiumItems[i].isEmpty()) { - int plvl = PremiumItemLevel + (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); } } - PremiumItemCount = maxItems; + Stores.premiumItemCount = maxItems; } - while (PremiumItemLevel < lvl) { - PremiumItemLevel++; + 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], PremiumItemLevel + premiumLvlAddHellfire[10], player); - PremiumItems[11] = PremiumItems[13]; - SpawnOnePremium(PremiumItems[12], PremiumItemLevel + premiumLvlAddHellfire[12], player); - PremiumItems[13] = PremiumItems[14]; - SpawnOnePremium(PremiumItems[14], PremiumItemLevel + 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], PremiumItemLevel + premiumlvladd[3], player); - PremiumItems[4] = PremiumItems[5]; - SpawnOnePremium(PremiumItems[5], PremiumItemLevel + 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 = WitchItems[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(WitchItems + PinnedItemCount); + SortVendor(Stores.witchItems, PinnedItemCount); } void SpawnBoy(int lvl) @@ -4509,19 +4512,19 @@ void SpawnBoy(int lvl) dexterity += dexterity / 5; magic += magic / 5; - if (BoyItemLevel >= (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; - BoyItemLevel = 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(HealerItems) / sizeof(HealerItems[0]); ++i) { - Item &item = HealerItems[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(HealerItems + PinnedItemCount); + SortVendor(Stores.healerItems, PinnedItemCount); } void MakeGoldStack(Item &goldItem, int value) diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index 9281fc07d490..0e54dfbd188a 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)); } - PremiumItemCount = file.NextBE(); - PremiumItemLevel = 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(PremiumItemCount); - file.WriteBE(PremiumItemLevel); + 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 77234d98281a..237add5a786e 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 4fde2956aac2..aafd72cff017 100644 --- a/Source/qol/chatlog.cpp +++ b/Source/qol/chatlog.cpp @@ -96,7 +96,7 @@ void ToggleChatLog() if (ChatLogFlag) { ChatLogFlag = false; } else { - ActiveStore = 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 6d0d78394f02..263070e2685d 100644 --- a/Source/qol/itemlabels.cpp +++ b/Source/qol/itemlabels.cpp @@ -98,7 +98,7 @@ void ResetItemlabelHighlighted() bool IsHighlightingLabelsEnabled() { - return ActiveStore == 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 - && ActiveStore == 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 && ActiveStore == 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 c4a5320871ff..3d2c0567d310 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 (ActiveStore != TalkID::None) + if (Stores.IsPlayerInStore()) return true; Item *item = &Stash.stashList[c]; diff --git a/Source/stores.cpp b/Source/stores.cpp index 588eec545952..92cb9dd506df 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 ActiveStore; +constexpr int PaddingTop = 32; -int CurrentItemIndex; -int8_t PlayerItemIndexes[48]; -Item PlayerItems[48]; +const int singleLineSpace = 1; +const int doubleLineSpace = 2; +const int tripleLineSpace = 3; -Item SmithItems[SMITH_ITEMS]; -int PremiumItemCount; -int PremiumItemLevel; -Item PremiumItems[SMITH_PREMIUM_ITEMS]; +constexpr int MainMenuDividerLine = 5; +constexpr int BuySellMenuDividerLine = 3; +constexpr int BuyLineSpace = 4; -Item HealerItems[20]; +constexpr int WirtDialogueDrawLine = 12; +constexpr int NumWirtDialogueLines = 3; -Item WitchItems[WITCH_ITEMS]; +const std::string SmithMenuHeader = "Welcome to the\n\nBlacksmith's shop"; -int BoyItemLevel; -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 TownerId; +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 IsTextFullSize; +const std::string BoyMenuHeader = "Wirt the Peg-legged boy"; -/** Number of text lines in the current dialog */ -int NumTextLines; -/** Remember currently selected text line from TextLine while displaying a dialog */ -int OldTextLine; -/** Currently selected text line from TextLine */ -int CurrentTextLine; +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 TextLine[STORE_LINES]; - -/** Whether to render the player's gold amount in the top left */ -bool RenderGold; - -/** Does the current panel have a scrollbar */ -bool HasScrollbar; -/** Remember last scroll position */ -int OldScrollPos; -/** Scroll position */ -int ScrollPos; -/** Next scroll position */ -int NextScrollPos; -/** Previous scroll position */ -int PreviousScrollPos; -/** Countdown for the push state of the scroll up button */ -int8_t CountdownScrollUp; -/** Countdown for the push state of the scroll down button */ -int8_t CountdownScrollDown; - -/** Remember current store while displaying a dialog */ -TalkID OldActiveStore; - -/** Temporary item used to hold the item being traded */ -Item TempItem; - -/** 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 HasScrollbar ? 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() -{ - TextLine[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 (TextLine[i].hasText() && TextLine[i - 1].hasText() && !(TextLine[i].isSelectable() && TextLine[i - 1].isSelectable())) { - TextLine[i].y = TextLine[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 { - TextLine[i].y = i * LargeLineHeight; + textLine[i].y = i * LargeLineHeight; } } } else { - for (int i = 1; i < STORE_LINES; ++i) { - TextLine[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 (CountdownScrollUp != -1) + if (countdownScrollUp != -1) ClxDraw(out, { uiPosition.x + 601, yd1 }, (*pSTextSlidCels)[11]); else ClxDraw(out, { uiPosition.x + 601, yd1 }, (*pSTextSlidCels)[9]); - if (CountdownScrollDown != -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 (CurrentTextLine == BackButtonLine()) - yd3 = OldTextLine; + if (currentTextLine == BackButtonLine()) + yd3 = oldTextLine; else - yd3 = CurrentTextLine; - if (CurrentItemIndex > 1) - yd3 = 1000 * (ScrollPos + ((yd3 - PreviousScrollPos) / 4)) / (CurrentItemIndex - 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) -{ - 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 AddSTextVal(size_t y, int val) +void StoreSession::SetLineAsDivider(size_t y) { - TextLine[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*/) { - 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; + 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); - TextLine[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); - TextLine[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); - PreviousScrollPos = 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); - NextScrollPos = l; - } else { - l -= 4; - } - idx++; - } - if (selling) { - if (CurrentTextLine != -1 && !TextLine[CurrentTextLine].isSelectable() && CurrentTextLine != BackButtonLine()) - CurrentTextLine = NextScrollPos; - } else { - NumTextLines = 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() { - IsTextFullSize = false; - HasScrollbar = 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); - CurrentItemIndex = 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(SmithItems, static_cast(std::size(SmithItems)), 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() { - IsTextFullSize = true; - HasScrollbar = true; - ScrollPos = 0; + RestoreResource(); - RenderGold = true; - AddSText(20, 1, _("I have these items for sale:"), UiFlags::ColorWhitegold, false); - AddSLine(3); - ScrollSmithBuy(ScrollPos); - AddItemListBackButton(); + isTextFullSize = false; + hasScrollbar = false; - CurrentItemIndex = 0; - for (Item &item : SmithItems) { - if (item.isEmpty()) - continue; + const TownerLine &lines = TownerLines[townerId]; - item._iStatFlag = MyPlayer->CanUseItem(item); - CurrentItemIndex++; + 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); } - NumTextLines = std::max(CurrentItemIndex - 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*/) { - CurrentItemIndex = 0; - for (Item &item : PremiumItems) { - if (item.isEmpty()) - continue; + Item *itemArray = nullptr; + int storeLimit = 0; - item._iStatFlag = MyPlayer->CanUseItem(item); - CurrentItemIndex++; + 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 (CurrentItemIndex == 0) { - StartStore(TalkID::Smith); - CurrentTextLine = 14; - return false; + + if (itemArray != nullptr) { + SetupItemList(talkId, itemArray, storeLimit, idx, selling); } +} - IsTextFullSize = true; - HasScrollbar = true; - ScrollPos = 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); +} - NumTextLines = std::max(CurrentItemIndex - 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(ScrollPos); +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(PlayerItems, CurrentItemIndex, 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) { - IsTextFullSize = true; + isTextFullSize = true; bool sellOk = false; - CurrentItemIndex = 0; + currentItemIndex = 0; - for (auto &item : PlayerItems) { + for (auto &item : playerItems) { item.clear(); } - const Player &myPlayer = *MyPlayer; - - for (int8_t i = 0; i < myPlayer._pNumInv; i++) { - if (CurrentItemIndex >= 48) + for (int8_t i = 0; i < MyPlayer->_pNumInv; i++) { + if (currentItemIndex >= 48) break; - if (SmithSellOk(i)) { + if (CanSellToTowner(talkId, i)) { sellOk = true; - PlayerItems[CurrentItemIndex] = myPlayer.InvList[i]; + playerItems[currentItemIndex] = MyPlayer->InvList[i]; - if (PlayerItems[CurrentItemIndex]._iMagical != ITEM_QUALITY_NORMAL && PlayerItems[CurrentItemIndex]._iIdentified) - PlayerItems[CurrentItemIndex]._ivalue = PlayerItems[CurrentItemIndex]._iIvalue; + if (playerItems[currentItemIndex]._iMagical != ITEM_QUALITY_NORMAL && playerItems[currentItemIndex]._iIdentified) + playerItems[currentItemIndex]._ivalue = playerItems[currentItemIndex]._iIvalue; - PlayerItems[CurrentItemIndex]._ivalue = std::max(PlayerItems[CurrentItemIndex]._ivalue / 4, 1); - PlayerItems[CurrentItemIndex]._iIvalue = PlayerItems[CurrentItemIndex]._ivalue; - PlayerItemIndexes[CurrentItemIndex] = i; - CurrentItemIndex++; + 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 (CurrentItemIndex >= 48) + if (currentItemIndex >= 48) break; - if (SmithSellOk(-(i + 1))) { + if (CanSellToTowner(talkId, i)) { sellOk = true; - PlayerItems[CurrentItemIndex] = myPlayer.SpdList[i]; + playerItems[currentItemIndex] = MyPlayer->SpdList[i]; - if (PlayerItems[CurrentItemIndex]._iMagical != ITEM_QUALITY_NORMAL && PlayerItems[CurrentItemIndex]._iIdentified) - PlayerItems[CurrentItemIndex]._ivalue = PlayerItems[CurrentItemIndex]._iIvalue; + if (playerItems[currentItemIndex]._iMagical != ITEM_QUALITY_NORMAL && playerItems[currentItemIndex]._iIdentified) + playerItems[currentItemIndex]._ivalue = playerItems[currentItemIndex]._iIvalue; - PlayerItems[CurrentItemIndex]._ivalue = std::max(PlayerItems[CurrentItemIndex]._ivalue / 4, 1); - PlayerItems[CurrentItemIndex]._iIvalue = PlayerItems[CurrentItemIndex]._ivalue; - PlayerItemIndexes[CurrentItemIndex] = -(i + 1); - CurrentItemIndex++; + playerItems[currentItemIndex]._ivalue = std::max(playerItems[currentItemIndex]._ivalue / 4, 1); + playerItems[currentItemIndex]._iIvalue = playerItems[currentItemIndex]._ivalue; + playerItemIndexes[currentItemIndex] = -(i + 1); + currentItemIndex++; } } if (!sellOk) { - HasScrollbar = 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; } - HasScrollbar = true; - ScrollPos = 0; - NumTextLines = 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(ScrollPos); - 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) { - IsTextFullSize = true; - CurrentItemIndex = 0; - - for (auto &item : PlayerItems) { - 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 (CurrentItemIndex >= 48) - break; - if (SmithRepairOk(i)) { - AddStoreHoldRepair(&myPlayer.InvList[i], i); - } - } + Item *item; + int v; - if (CurrentItemIndex == 0) { - HasScrollbar = 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); } - - HasScrollbar = true; - ScrollPos = 0; - NumTextLines = myPlayer._pNumInv; - - RenderGold = true; - AddSText(20, 1, _("Repair which item?"), UiFlags::ColorWhitegold, false); - AddSLine(3); - - ScrollSmithSell(ScrollPos); - 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(); - IsTextFullSize = false; - HasScrollbar = 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); - CurrentItemIndex = 20; -} - -void ScrollWitchBuy(int idx) -{ - ScrollVendorStore(WitchItems, static_cast(std::size(WitchItems)), 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() -{ - IsTextFullSize = true; - HasScrollbar = true; - ScrollPos = 0; - NumTextLines = 20; - - RenderGold = true; - AddSText(20, 1, _("I have these items for sale:"), UiFlags::ColorWhitegold, false); - AddSLine(3); - ScrollWitchBuy(ScrollPos); - AddItemListBackButton(); - - CurrentItemIndex = 0; - for (Item &item : WitchItems) { - if (item.isEmpty()) - continue; - WitchBookLevel(item); - item._iStatFlag = MyPlayer->CanUseItem(item); - CurrentItemIndex++; + auto &armor = MyPlayer->InvBody[INVLOC_CHEST]; + if (!armor.isEmpty() && armor._iDurability != armor._iMaxDur) { + AddPlayerItemToRepairList(&armor, -2); } - NumTextLines = std::max(CurrentItemIndex - 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() -{ - IsTextFullSize = true; - bool sellok = false; - CurrentItemIndex = 0; - - for (auto &item : PlayerItems) { - 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 (CurrentItemIndex >= 48) - break; - if (WitchSellOk(i)) { - sellok = true; - PlayerItems[CurrentItemIndex] = myPlayer.InvList[i]; - - if (PlayerItems[CurrentItemIndex]._iMagical != ITEM_QUALITY_NORMAL && PlayerItems[CurrentItemIndex]._iIdentified) - PlayerItems[CurrentItemIndex]._ivalue = PlayerItems[CurrentItemIndex]._iIvalue; - - PlayerItems[CurrentItemIndex]._ivalue = std::max(PlayerItems[CurrentItemIndex]._ivalue / 4, 1); - PlayerItems[CurrentItemIndex]._iIvalue = PlayerItems[CurrentItemIndex]._ivalue; - PlayerItemIndexes[CurrentItemIndex] = i; - CurrentItemIndex++; - } + 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 (CurrentItemIndex >= 48) + for (int i = 0; i < MyPlayer->_pNumInv; i++) { + if (currentItemIndex >= 48) break; - if (!myPlayer.SpdList[i].isEmpty() && WitchSellOk(-(i + 1))) { - sellok = true; - PlayerItems[CurrentItemIndex] = myPlayer.SpdList[i]; - - if (PlayerItems[CurrentItemIndex]._iMagical != ITEM_QUALITY_NORMAL && PlayerItems[CurrentItemIndex]._iIdentified) - PlayerItems[CurrentItemIndex]._ivalue = PlayerItems[CurrentItemIndex]._iIvalue; - - PlayerItems[CurrentItemIndex]._ivalue = std::max(PlayerItems[CurrentItemIndex]._ivalue / 4, 1); - PlayerItems[CurrentItemIndex]._iIvalue = PlayerItems[CurrentItemIndex]._ivalue; - PlayerItemIndexes[CurrentItemIndex] = -(i + 1); - CurrentItemIndex++; + if (CanTownerRepair(i)) { + AddPlayerItemToRepairList(&MyPlayer->InvList[i], i); } } - if (!sellok) { - HasScrollbar = 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; } - HasScrollbar = true; - ScrollPos = 0; - NumTextLines = 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(ScrollPos); - 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) { - 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++; + 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() { - IsTextFullSize = true; + isTextFullSize = true; bool rechargeok = false; - CurrentItemIndex = 0; + currentItemIndex = 0; - for (auto &item : PlayerItems) { + 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 (CurrentItemIndex >= 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) { - HasScrollbar = 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; } - HasScrollbar = true; - ScrollPos = 0; - NumTextLines = myPlayer._pNumInv; - - RenderGold = true; - AddSText(20, 1, _("Recharge which item?"), UiFlags::ColorWhitegold, false); - AddSLine(3); - ScrollSmithSell(ScrollPos); - AddItemListBackButton(); -} - -void StoreNoMoney() -{ - StartStore(OldActiveStore); - HasScrollbar = false; - IsTextFullSize = 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(OldActiveStore); - HasScrollbar = 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(OldActiveStore); - HasScrollbar = 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 (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?"); + 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(OldActiveStore))); - } - 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() -{ - IsTextFullSize = false; - HasScrollbar = 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() -{ - IsTextFullSize = true; - HasScrollbar = 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); - TextLine[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(); - IsTextFullSize = false; - HasScrollbar = 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); - CurrentItemIndex = 20; -} - -void ScrollHealerBuy(int idx) -{ - ScrollVendorStore(HealerItems, static_cast(std::size(HealerItems)), idx); -} - -void StartHealerBuy() -{ - IsTextFullSize = true; - HasScrollbar = true; - ScrollPos = 0; - - RenderGold = true; - AddSText(20, 1, _("I have these items for sale:"), UiFlags::ColorWhitegold, false); - AddSLine(3); - - ScrollHealerBuy(ScrollPos); - AddItemListBackButton(); - - CurrentItemIndex = 0; - for (Item &item : HealerItems) { - if (item.isEmpty()) - continue; - - item._iStatFlag = MyPlayer->CanUseItem(item); - CurrentItemIndex++; + return; } - NumTextLines = std::max(CurrentItemIndex - 4, 0); -} + if (*resource == *maxResource) + return; -void StartStoryteller() -{ - IsTextFullSize = false; - HasScrollbar = 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) { - PlayerItems[CurrentItemIndex] = itm; - PlayerItems[CurrentItemIndex]._ivalue = 100; - PlayerItems[CurrentItemIndex]._iIvalue = 100; - PlayerItemIndexes[CurrentItemIndex] = i; - CurrentItemIndex++; + playerItems[currentItemIndex] = itm; + playerItems[currentItemIndex]._ivalue = 100; + playerItems[currentItemIndex]._iIvalue = 100; + playerItemIndexes[currentItemIndex] = i; + currentItemIndex++; } -void StartStorytellerIdentify() +void StoreSession::SetupTownerIdentifyScreen() { bool idok = false; - IsTextFullSize = true; - CurrentItemIndex = 0; + isTextFullSize = true; + currentItemIndex = 0; - for (auto &item : PlayerItems) { + 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 (CurrentItemIndex >= 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) { - HasScrollbar = 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; } - HasScrollbar = true; - ScrollPos = 0; - NumTextLines = 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(ScrollPos); - AddItemListBackButton(); + SetupTownerItemList(TalkID::StorytellerIdentify, scrollPos, false); + AddItemListBackButton(TalkID::StorytellerIdentify); } -void StartStorytellerIdentifyShow(Item &item) +void StoreSession::SetupTownerIdentifyResultScreen(Item &item) { - StartStore(OldActiveStore); - HasScrollbar = 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; - - IsTextFullSize = false; - HasScrollbar = false; - AddSText(0, 2, fmt::format(fmt::runtime(_("Talk to {:s}")), _(TownerNames[TownerId])), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); - AddSLine(5); - if (gbIsSpawn) { - AddSText(0, 10, fmt::format(fmt::runtime(_("Talking to {:s}")), _(TownerNames[TownerId])), 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[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) { - 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() -{ - IsTextFullSize = false; - HasScrollbar = 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); - CurrentItemIndex = 20; -} - -void StartBarmaid() -{ - IsTextFullSize = false; - HasScrollbar = 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); - CurrentItemIndex = 20; + SetLineText(0, 18, _("Done"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); } -void StartDrunk() +void StoreSession::SmithEnter() { - IsTextFullSize = false; - HasScrollbar = 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); - CurrentItemIndex = 20; -} + TalkID selectedAction = storeLineMapping[currentTextLine]; -void SmithEnter() -{ - switch (CurrentTextLine) { - case 10: - TownerId = TOWN_SMITH; - OldTextLine = 10; - OldActiveStore = 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: - ActiveStore = 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 = OldScrollPos + ((OldTextLine - PreviousScrollPos) / 4); - if (idx == SMITH_ITEMS - 1) { - SmithItems[SMITH_ITEMS - 1].clear(); + GiveItemToPlayer(item, true); + int idx = oldScrollPos + ((oldTextLine - previousScrollPos) / 4); + if (idx == NumSmithItems - 1) { + smithItems[NumSmithItems - 1].clear(); } else { - for (; !SmithItems[idx + 1].isEmpty(); idx++) { - SmithItems[idx] = std::move(SmithItems[idx + 1]); + for (; !smithItems[idx + 1].isEmpty(); idx++) { + smithItems[idx] = std::move(smithItems[idx + 1]); } - SmithItems[idx].clear(); + smithItems[idx].clear(); } CalcPlrInv(*MyPlayer, true); } -void SmithBuyEnter() +void StoreSession::SmithBuyEnter() { - if (CurrentTextLine == BackButtonLine()) { + if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Smith); - CurrentTextLine = 12; + currentTextLine = 12; return; } - OldTextLine = CurrentTextLine; - OldScrollPos = ScrollPos; - OldActiveStore = TalkID::SmithBuy; + oldTextLine = currentTextLine; + oldScrollPos = scrollPos; + oldActiveStore = TalkID::SmithBuy; - int idx = ScrollPos + ((CurrentTextLine - PreviousScrollPos) / 4); - if (!PlayerCanAfford(SmithItems[idx]._iIvalue)) { + int idx = scrollPos + ((currentTextLine - previousScrollPos) / 4); + if (!CanPlayerAfford(smithItems[idx]._iIvalue)) { StartStore(TalkID::NoMoney); return; } - if (!StoreAutoPlace(SmithItems[idx], false)) { + if (!GiveItemToPlayer(smithItems[idx], false)) { StartStore(TalkID::NoRoom); return; } - TempItem = SmithItems[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 = OldScrollPos + ((OldTextLine - PreviousScrollPos) / 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(); - PremiumItemCount--; + premiumItems[xx].clear(); + premiumItemCount--; SpawnPremium(*MyPlayer); } -void SmithPremiumBuyEnter() +void StoreSession::SmithPremiumBuyEnter() { - if (CurrentTextLine == BackButtonLine()) { + if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Smith); - CurrentTextLine = 14; + currentTextLine = 14; return; } - OldActiveStore = TalkID::SmithPremiumBuy; - OldTextLine = CurrentTextLine; - OldScrollPos = ScrollPos; + oldActiveStore = TalkID::SmithPremiumBuy; + oldTextLine = currentTextLine; + oldScrollPos = scrollPos; - int xx = ScrollPos + ((CurrentTextLine - PreviousScrollPos) / 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; } - TempItem = 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 = OldScrollPos + ((OldTextLine - PreviousScrollPos) / 4); - if (PlayerItemIndexes[idx] >= 0) - myPlayer.RemoveInvItem(PlayerItemIndexes[idx]); + int idx = oldScrollPos + ((oldTextLine - previousScrollPos) / 4); + if (playerItemIndexes[idx] >= 0) + MyPlayer->RemoveInvItem(playerItemIndexes[idx]); else - 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]; + 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 (CurrentTextLine == BackButtonLine()) { + if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Smith); - CurrentTextLine = 16; + currentTextLine = 16; return; } - OldTextLine = CurrentTextLine; - OldActiveStore = TalkID::SmithSell; - OldScrollPos = ScrollPos; + oldTextLine = currentTextLine; + oldActiveStore = TalkID::SmithSell; + oldScrollPos = scrollPos; - int idx = ScrollPos + ((CurrentTextLine - PreviousScrollPos) / 4); + int idx = scrollPos + ((currentTextLine - previousScrollPos) / 4); - if (!StoreGoldFit(PlayerItems[idx])) { + if (!StoreGoldFit(playerItems[idx])) { StartStore(TalkID::NoRoom); return; } - TempItem = PlayerItems[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 = OldScrollPos + ((OldTextLine - PreviousScrollPos) / 4); - PlayerItems[idx]._iDurability = PlayerItems[idx]._iMaxDur; + int idx = oldScrollPos + ((oldTextLine - previousScrollPos) / 4); + playerItems[idx]._iDurability = playerItems[idx]._iMaxDur; - int8_t i = PlayerItemIndexes[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 (CurrentTextLine == BackButtonLine()) { + if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Smith); - CurrentTextLine = 18; + currentTextLine = 18; return; } - OldActiveStore = TalkID::SmithRepair; - OldTextLine = CurrentTextLine; - OldScrollPos = ScrollPos; + oldActiveStore = TalkID::SmithRepair; + oldTextLine = currentTextLine; + oldScrollPos = scrollPos; - int idx = ScrollPos + ((CurrentTextLine - PreviousScrollPos) / 4); + int idx = scrollPos + ((currentTextLine - previousScrollPos) / 4); - if (!PlayerCanAfford(PlayerItems[idx]._iIvalue)) { + if (!CanPlayerAfford(playerItems[idx]._iIvalue)) { StartStore(TalkID::NoMoney); return; } - TempItem = PlayerItems[idx]; + tempItem = playerItems[idx]; StartStore(TalkID::Confirm); } -void WitchEnter() +void StoreSession::WitchEnter() { - switch (CurrentTextLine) { + switch (currentTextLine) { case 12: - OldTextLine = 12; - TownerId = TOWN_WITCH; - OldActiveStore = 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: - ActiveStore = 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 = OldScrollPos + ((OldTextLine - PreviousScrollPos) / 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) { - WitchItems[WITCH_ITEMS - 1].clear(); + if (idx == NumWitchItems - 1) { + witchItems[NumWitchItems - 1].clear(); } else { - for (; !WitchItems[idx + 1].isEmpty(); idx++) { - WitchItems[idx] = std::move(WitchItems[idx + 1]); + for (; !witchItems[idx + 1].isEmpty(); idx++) { + witchItems[idx] = std::move(witchItems[idx + 1]); } - WitchItems[idx].clear(); + witchItems[idx].clear(); } } CalcPlrInv(*MyPlayer, true); } -void WitchBuyEnter() +void StoreSession::WitchBuyEnter() { - if (CurrentTextLine == BackButtonLine()) { + if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Witch); - CurrentTextLine = 14; + currentTextLine = 14; return; } - OldTextLine = CurrentTextLine; - OldScrollPos = ScrollPos; - OldActiveStore = TalkID::WitchBuy; + oldTextLine = currentTextLine; + oldScrollPos = scrollPos; + oldActiveStore = TalkID::WitchBuy; - int idx = ScrollPos + ((CurrentTextLine - PreviousScrollPos) / 4); + int idx = scrollPos + ((currentTextLine - previousScrollPos) / 4); - if (!PlayerCanAfford(WitchItems[idx]._iIvalue)) { + if (!CanPlayerAfford(witchItems[idx]._iIvalue)) { StartStore(TalkID::NoMoney); return; } - if (!StoreAutoPlace(WitchItems[idx], false)) { + if (!GiveItemToPlayer(witchItems[idx], false)) { StartStore(TalkID::NoRoom); return; } - TempItem = WitchItems[idx]; + tempItem = witchItems[idx]; StartStore(TalkID::Confirm); } -void WitchSellEnter() +void StoreSession::WitchSellEnter() { - if (CurrentTextLine == BackButtonLine()) { + if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Witch); - CurrentTextLine = 16; + currentTextLine = 16; return; } - OldTextLine = CurrentTextLine; - OldActiveStore = TalkID::WitchSell; - OldScrollPos = ScrollPos; + oldTextLine = currentTextLine; + oldActiveStore = TalkID::WitchSell; + oldScrollPos = scrollPos; - int idx = ScrollPos + ((CurrentTextLine - PreviousScrollPos) / 4); + int idx = scrollPos + ((currentTextLine - previousScrollPos) / 4); - if (!StoreGoldFit(PlayerItems[idx])) { + if (!StoreGoldFit(playerItems[idx])) { StartStore(TalkID::NoRoom); return; } - TempItem = PlayerItems[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 = OldScrollPos + ((OldTextLine - PreviousScrollPos) / 4); - PlayerItems[idx]._iCharges = PlayerItems[idx]._iMaxCharges; + int idx = oldScrollPos + ((oldTextLine - previousScrollPos) / 4); + playerItems[idx]._iCharges = playerItems[idx]._iMaxCharges; - Player &myPlayer = *MyPlayer; - - int8_t i = PlayerItemIndexes[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 (CurrentTextLine == BackButtonLine()) { + if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Witch); - CurrentTextLine = 18; + currentTextLine = 18; return; } - OldActiveStore = TalkID::WitchRecharge; - OldTextLine = CurrentTextLine; - OldScrollPos = ScrollPos; + oldActiveStore = TalkID::WitchRecharge; + oldTextLine = currentTextLine; + oldScrollPos = scrollPos; - int idx = ScrollPos + ((CurrentTextLine - PreviousScrollPos) / 4); + int idx = scrollPos + ((currentTextLine - previousScrollPos) / 4); - if (!PlayerCanAfford(PlayerItems[idx]._iIvalue)) { + if (!CanPlayerAfford(playerItems[idx]._iIvalue)) { StartStore(TalkID::NoMoney); return; } - TempItem = PlayerItems[idx]; + tempItem = playerItems[idx]; StartStore(TalkID::Confirm); } -void BoyEnter() +void StoreSession::BoyEnter() { - if (!BoyItem.isEmpty() && CurrentTextLine == 18) { - if (!PlayerCanAfford(50)) { - OldActiveStore = TalkID::Boy; - OldTextLine = 18; - OldScrollPos = ScrollPos; + 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 ((CurrentTextLine != 8 && !BoyItem.isEmpty()) || (CurrentTextLine != 12 && BoyItem.isEmpty())) { - ActiveStore = TalkID::None; + if ((currentTextLine != 8 && !boyItem.isEmpty()) || (currentTextLine != 12 && boyItem.isEmpty())) { + activeStore = TalkID::None; return; } - TownerId = TOWN_PEGBOY; - OldActiveStore = TalkID::Boy; - OldTextLine = CurrentTextLine; + 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(); - OldActiveStore = TalkID::Boy; + GiveItemToPlayer(item, true); + boyItem.clear(); + oldActiveStore = TalkID::Boy; CalcPlrInv(*MyPlayer, true); - OldTextLine = 12; + oldTextLine = 12; } /** * @brief Purchases an item from the healer. */ -void HealerBuyItem(Item &item) +void StoreSession::HealerBuyItem(Item &item) { - int idx = OldScrollPos + ((OldTextLine - PreviousScrollPos) / 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 = OldScrollPos + ((OldTextLine - PreviousScrollPos) / 4); + idx = oldScrollPos + ((oldTextLine - previousScrollPos) / 4); if (idx == 19) { - HealerItems[19].clear(); + healerItems[19].clear(); } else { - for (; !HealerItems[idx + 1].isEmpty(); idx++) { - HealerItems[idx] = std::move(HealerItems[idx + 1]); + for (; !healerItems[idx + 1].isEmpty(); idx++) { + healerItems[idx] = std::move(healerItems[idx + 1]); } - HealerItems[idx].clear(); + healerItems[idx].clear(); } CalcPlrInv(*MyPlayer, true); } -void BoyBuyEnter() +void StoreSession::BoyBuyEnter() { - if (CurrentTextLine != 10) { - ActiveStore = TalkID::None; + if (currentTextLine != 10) { + activeStore = TalkID::None; return; } - OldActiveStore = TalkID::BoyBuy; - OldScrollPos = ScrollPos; - OldTextLine = 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; } - TempItem = BoyItem; - TempItem._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 = PlayerItemIndexes[((OldTextLine - PreviousScrollPos) / 4) + OldScrollPos]; + 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 (CurrentTextLine == 18) { - switch (OldActiveStore) { + if (currentTextLine == 18) { + switch (oldActiveStore) { case TalkID::SmithBuy: SmithBuyItem(item); break; @@ -1849,117 +1671,115 @@ void ConfirmEnter(Item &item) } } - StartStore(OldActiveStore); + StartStore(oldActiveStore); - if (CurrentTextLine == BackButtonLine()) + if (currentTextLine == BackButtonLine()) return; - CurrentTextLine = OldTextLine; - ScrollPos = std::min(OldScrollPos, NumTextLines); + currentTextLine = oldTextLine; + scrollPos = std::min(oldScrollPos, numTextLines); - while (CurrentTextLine != -1 && !TextLine[CurrentTextLine].isSelectable()) { - CurrentTextLine--; + while (currentTextLine != -1 && !textLine[currentTextLine].isSelectable()) { + currentTextLine--; } } -void HealerEnter() +void StoreSession::HealerEnter() { - switch (CurrentTextLine) { + switch (currentTextLine) { case 12: - OldTextLine = 12; - TownerId = TOWN_HEALER; - OldActiveStore = TalkID::Healer; + oldTextLine = 12; + oldActiveStore = TalkID::Healer; StartStore(TalkID::Gossip); break; case 14: StartStore(TalkID::HealerBuy); break; case 18: - ActiveStore = TalkID::None; + activeStore = TalkID::None; break; } } -void HealerBuyEnter() +void StoreSession::HealerBuyEnter() { - if (CurrentTextLine == BackButtonLine()) { + if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Healer); - CurrentTextLine = 14; + currentTextLine = 14; return; } - OldTextLine = CurrentTextLine; - OldScrollPos = ScrollPos; - OldActiveStore = TalkID::HealerBuy; + oldTextLine = currentTextLine; + oldScrollPos = scrollPos; + oldActiveStore = TalkID::HealerBuy; - int idx = ScrollPos + ((CurrentTextLine - PreviousScrollPos) / 4); + int idx = scrollPos + ((currentTextLine - previousScrollPos) / 4); - if (!PlayerCanAfford(HealerItems[idx]._iIvalue)) { + if (!CanPlayerAfford(healerItems[idx]._iIvalue)) { StartStore(TalkID::NoMoney); return; } - if (!StoreAutoPlace(HealerItems[idx], false)) { + if (!GiveItemToPlayer(healerItems[idx], false)) { StartStore(TalkID::NoRoom); return; } - TempItem = HealerItems[idx]; + tempItem = healerItems[idx]; StartStore(TalkID::Confirm); } -void StorytellerEnter() +void StoreSession::StorytellerEnter() { - switch (CurrentTextLine) { + switch (currentTextLine) { case 12: - OldTextLine = 12; - TownerId = TOWN_STORY; - OldActiveStore = TalkID::Storyteller; + oldTextLine = 12; + oldActiveStore = TalkID::Storyteller; StartStore(TalkID::Gossip); break; case 14: StartStore(TalkID::StorytellerIdentify); break; case 18: - ActiveStore = TalkID::None; + activeStore = TalkID::None; break; } } -void StorytellerIdentifyEnter() +void StoreSession::StorytellerIdentifyEnter() { - if (CurrentTextLine == BackButtonLine()) { + if (currentTextLine == BackButtonLine()) { StartStore(TalkID::Storyteller); - CurrentTextLine = 14; + currentTextLine = 14; return; } - OldActiveStore = TalkID::StorytellerIdentify; - OldTextLine = CurrentTextLine; - OldScrollPos = ScrollPos; + oldActiveStore = TalkID::StorytellerIdentify; + oldTextLine = currentTextLine; + oldScrollPos = scrollPos; - int idx = ScrollPos + ((CurrentTextLine - PreviousScrollPos) / 4); + int idx = scrollPos + ((currentTextLine - previousScrollPos) / 4); - if (!PlayerCanAfford(PlayerItems[idx]._iIvalue)) { + if (!CanPlayerAfford(playerItems[idx]._iIvalue)) { StartStore(TalkID::NoMoney); return; } - TempItem = PlayerItems[idx]; + tempItem = playerItems[idx]; StartStore(TalkID::Confirm); } -void TalkEnter() +void StoreSession::TalkEnter() { - if (CurrentTextLine == BackButtonLine()) { - StartStore(OldActiveStore); - CurrentTextLine = OldTextLine; + if (currentTextLine == BackButtonLine()) { + StartStore(oldActiveStore); + currentTextLine = oldTextLine; return; } int sn = 0; for (auto &quest : Quests) { - if (quest._qactive == QUEST_ACTIVE && QuestDialogTable[TownerId][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 (CurrentTextLine == sn - 2) { - Towner *target = GetTowner(TownerId); + 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[TownerId][quest._qidx] != TEXT_NONE && quest._qlog) { - if (sn == CurrentTextLine) { - InitQTextMsg(QuestDialogTable[TownerId][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 (CurrentTextLine) { + switch (currentTextLine) { case 12: - OldTextLine = 12; - TownerId = TOWN_TAVERN; - OldActiveStore = TalkID::Tavern; + oldTextLine = 12; + oldActiveStore = TalkID::Tavern; StartStore(TalkID::Gossip); break; case 18: - ActiveStore = TalkID::None; + activeStore = TalkID::None; break; } } -void BarmaidEnter() +void StoreSession::BarmaidEnter() { - switch (CurrentTextLine) { + switch (currentTextLine) { case 12: - OldTextLine = 12; - TownerId = TOWN_BMAID; - OldActiveStore = TalkID::Barmaid; + oldTextLine = 12; + oldActiveStore = TalkID::Barmaid; StartStore(TalkID::Gossip); break; case 14: - ActiveStore = TalkID::None; + activeStore = TalkID::None; IsStashOpen = true; Stash.RefreshItemStatFlags(); invflag = true; @@ -2023,27 +1841,26 @@ void BarmaidEnter() } break; case 18: - ActiveStore = TalkID::None; + activeStore = TalkID::None; break; } } -void DrunkEnter() +void StoreSession::DrunkEnter() { - switch (CurrentTextLine) { + switch (currentTextLine) { case 12: - OldTextLine = 12; - TownerId = TOWN_DRUNK; - OldActiveStore = TalkID::Drunk; + oldTextLine = 12; + oldActiveStore = TalkID::Drunk; StartStore(TalkID::Gossip); break; case 18: - ActiveStore = 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 = &PlayerItems[CurrentItemIndex]; - PlayerItems[CurrentItemIndex] = *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; - PlayerItemIndexes[CurrentItemIndex] = i; - CurrentItemIndex++; -} - -void InitStores() +void StoreSession::InitStores() { - ClearSText(0, STORE_LINES); - ActiveStore = TalkID::None; - IsTextFullSize = false; - HasScrollbar = false; - PremiumItemCount = 0; - PremiumItemLevel = 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(); - BoyItemLevel = 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(); } - ActiveStore = TalkID::None; - for (STextStruct &entry : TextLine) { + 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 (!IsTextFullSize) { + if (!isTextFullSize) { sx += 320; } - const int sy = uiPosition.y + PaddingTop + TextLine[line].y + TextLine[line]._syoff; + const int sy = uiPosition.y + PaddingTop + textLine[line].y + textLine[line]._syoff; - int width = IsTextFullSize ? 575 : 255; - if (HasScrollbar && 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 (CurrentTextLine == 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 (!IsTextFullSize) { + 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() { - CurrentTextLine = -1; - IsTextFullSize = 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++) { - 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; + 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; !SmithItems[i].isEmpty(); i++) { + for (int i = 0; !smithItems[i].isEmpty(); i++) { hasAnyItems = true; break; } if (hasAnyItems) - StartSmithBuy(); + SetupTownerBuyScreen(s); else { - ActiveStore = TalkID::SmithBuy; - OldTextLine = 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 (CurrentItemIndex > 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(TempItem); + 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 (CurrentItemIndex > 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(TempItem); + 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; } - CurrentTextLine = -1; - for (int i = 0; i < STORE_LINES; i++) { - if (TextLine[i].isSelectable()) { - CurrentTextLine = i; + currentTextLine = -1; + for (int i = 0; i < NumStoreLines; i++) { + if (textLine[i].isSelectable()) { + currentTextLine = i; break; } } - ActiveStore = s; + activeStore = s; } -void DrawSText(const Surface &out) +void StoreSession::DrawSText(const Surface &out) { - if (!IsTextFullSize) - DrawSTextBack(out); + if (!isTextFullSize) + DrawTextUI(out); else DrawQTextBack(out); - if (HasScrollbar) { - switch (ActiveStore) { + if (hasScrollbar) { + switch (activeStore) { case TalkID::SmithBuy: - ScrollSmithBuy(ScrollPos); + SetupTownerItemList(TalkID::SmithBuy, scrollPos); break; case TalkID::SmithSell: case TalkID::SmithRepair: case TalkID::WitchSell: case TalkID::WitchRecharge: case TalkID::StorytellerIdentify: - ScrollSmithSell(ScrollPos); + SetupTownerItemList(activeStore, scrollPos, false); break; case TalkID::WitchBuy: - ScrollWitchBuy(ScrollPos); + SetupTownerItemList(activeStore, scrollPos); break; case TalkID::HealerBuy: - ScrollHealerBuy(ScrollPos); + SetupTownerItemList(activeStore, scrollPos); break; case TalkID::SmithPremiumBuy: - ScrollSmithPremiumBuy(ScrollPos); + 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 (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); + 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 (HasScrollbar) - 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 (ActiveStore) { + 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: - ActiveStore = TalkID::None; + activeStore = TalkID::None; break; case TalkID::Gossip: - StartStore(OldActiveStore); - CurrentTextLine = OldTextLine; + StartStore(oldActiveStore); + currentTextLine = oldTextLine; break; case TalkID::SmithBuy: StartStore(TalkID::Smith); - CurrentTextLine = 12; + currentTextLine = 12; break; case TalkID::SmithPremiumBuy: StartStore(TalkID::Smith); - CurrentTextLine = 14; + currentTextLine = 14; break; case TalkID::SmithSell: StartStore(TalkID::Smith); - CurrentTextLine = 16; + currentTextLine = 16; break; case TalkID::SmithRepair: StartStore(TalkID::Smith); - CurrentTextLine = 18; + currentTextLine = 18; break; case TalkID::WitchBuy: StartStore(TalkID::Witch); - CurrentTextLine = 14; + currentTextLine = 14; break; case TalkID::WitchSell: StartStore(TalkID::Witch); - CurrentTextLine = 16; + currentTextLine = 16; break; case TalkID::WitchRecharge: StartStore(TalkID::Witch); - CurrentTextLine = 18; + currentTextLine = 18; break; case TalkID::HealerBuy: StartStore(TalkID::Healer); - CurrentTextLine = 14; + currentTextLine = 14; break; case TalkID::StorytellerIdentify: StartStore(TalkID::Storyteller); - CurrentTextLine = 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(OldActiveStore); - CurrentTextLine = OldTextLine; - ScrollPos = OldScrollPos; + StartStore(oldActiveStore); + currentTextLine = oldTextLine; + scrollPos = oldScrollPos; break; case TalkID::None: break; } } -void StoreUp() +void StoreSession::StoreUp() { PlaySFX(SfxID::MenuMove); - if (CurrentTextLine == -1) { + if (currentTextLine == -1) { return; } - if (HasScrollbar) { - if (CurrentTextLine == PreviousScrollPos) { - if (ScrollPos != 0) - ScrollPos--; + if (hasScrollbar) { + if (currentTextLine == previousScrollPos) { + if (scrollPos != 0) + scrollPos--; return; } - CurrentTextLine--; - while (!TextLine[CurrentTextLine].isSelectable()) { - if (CurrentTextLine == 0) - CurrentTextLine = STORE_LINES - 1; + currentTextLine--; + while (!textLine[currentTextLine].isSelectable()) { + if (currentTextLine == 0) + currentTextLine = NumStoreLines - 1; else - CurrentTextLine--; + currentTextLine--; } return; } - if (CurrentTextLine == 0) - CurrentTextLine = STORE_LINES - 1; + if (currentTextLine == 0) + currentTextLine = NumStoreLines - 1; else - CurrentTextLine--; + currentTextLine--; - while (!TextLine[CurrentTextLine].isSelectable()) { - if (CurrentTextLine == 0) - CurrentTextLine = STORE_LINES - 1; + while (!textLine[currentTextLine].isSelectable()) { + if (currentTextLine == 0) + currentTextLine = NumStoreLines - 1; else - CurrentTextLine--; + currentTextLine--; } } -void StoreDown() +void StoreSession::StoreDown() { PlaySFX(SfxID::MenuMove); - if (CurrentTextLine == -1) { + if (currentTextLine == -1) { return; } - if (HasScrollbar) { - if (CurrentTextLine == NextScrollPos) { - if (ScrollPos < NumTextLines) - ScrollPos++; + if (hasScrollbar) { + if (currentTextLine == nextScrollPos) { + if (scrollPos < numTextLines) + scrollPos++; return; } - CurrentTextLine++; - while (!TextLine[CurrentTextLine].isSelectable()) { - if (CurrentTextLine == STORE_LINES - 1) - CurrentTextLine = 0; + currentTextLine++; + while (!textLine[currentTextLine].isSelectable()) { + if (currentTextLine == NumStoreLines - 1) + currentTextLine = 0; else - CurrentTextLine++; + currentTextLine++; } return; } - if (CurrentTextLine == STORE_LINES - 1) - CurrentTextLine = 0; + if (currentTextLine == NumStoreLines - 1) + currentTextLine = 0; else - CurrentTextLine++; + currentTextLine++; - while (!TextLine[CurrentTextLine].isSelectable()) { - if (CurrentTextLine == STORE_LINES - 1) - CurrentTextLine = 0; + while (!textLine[currentTextLine].isSelectable()) { + if (currentTextLine == NumStoreLines - 1) + currentTextLine = 0; else - CurrentTextLine++; + currentTextLine++; } } -void StorePrior() +void StoreSession::StorePrior() { PlaySFX(SfxID::MenuMove); - if (CurrentTextLine != -1 && HasScrollbar) { - if (CurrentTextLine == PreviousScrollPos) { - ScrollPos = std::max(ScrollPos - 4, 0); + if (currentTextLine != -1 && hasScrollbar) { + if (currentTextLine == previousScrollPos) { + scrollPos = std::max(scrollPos - 4, 0); } else { - CurrentTextLine = PreviousScrollPos; + currentTextLine = previousScrollPos; } } } -void StoreNext() +void StoreSession::StoreNext() { PlaySFX(SfxID::MenuMove); - if (CurrentTextLine != -1 && HasScrollbar) { - if (CurrentTextLine == NextScrollPos) { - if (ScrollPos < NumTextLines) - ScrollPos += 4; - if (ScrollPos > NumTextLines) - ScrollPos = NumTextLines; + if (currentTextLine != -1 && hasScrollbar) { + if (currentTextLine == nextScrollPos) { + if (scrollPos < numTextLines) + scrollPos += 4; + if (scrollPos > numTextLines) + scrollPos = numTextLines; } else { - CurrentTextLine = NextScrollPos; + 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 (ActiveStore) { + switch (activeStore) { case TalkID::Smith: SmithEnter(); break; @@ -2644,12 +2441,12 @@ void StoreEnter() break; case TalkID::NoMoney: case TalkID::NoRoom: - StartStore(OldActiveStore); - CurrentTextLine = OldTextLine; - ScrollPos = OldScrollPos; + StartStore(oldActiveStore); + currentTextLine = oldTextLine; + scrollPos = oldScrollPos; break; case TalkID::Confirm: - ConfirmEnter(TempItem); + 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 (!IsTextFullSize) { + if (!isTextFullSize) { if (!windowRect.contains(MousePosition)) { - while (ActiveStore != TalkID::None) + while (activeStore != TalkID::None) StoreESC(); } } else { if (!windowRectFull.contains(MousePosition)) { - while (ActiveStore != TalkID::None) + while (activeStore != TalkID::None) StoreESC(); } } @@ -2711,26 +2508,26 @@ void CheckStoreBtn() qtextflag = false; if (leveltype == DTYPE_TOWN) stream_stop(); - } else if (CurrentTextLine != -1) { + } else if (currentTextLine != -1) { const int relativeY = MousePosition.y - (uiPosition.y + PaddingTop); - if (HasScrollbar && 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 (CountdownScrollUp <= 0) { + if (countdownScrollUp <= 0) { StoreUp(); - CountdownScrollUp = 10; + countdownScrollUp = 10; } else { - CountdownScrollUp--; + countdownScrollUp--; } } if (y == 20) { - if (CountdownScrollDown <= 0) { + if (countdownScrollDown <= 0) { StoreDown(); - CountdownScrollDown = 10; + countdownScrollDown = 10; } else { - CountdownScrollDown--; + 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 - && TextLine[y - 1].hasText() && !TextLine[y].hasText() - && relativeY < TextLine[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 (HasScrollbar && y <= 20 && !TextLine[y].isSelectable()) { - if (TextLine[y - 2].isSelectable()) { + if (hasScrollbar && y <= 20 && !textLine[y].isSelectable()) { + if (textLine[y - 2].isSelectable()) { y -= 2; - } else if (TextLine[y - 1].isSelectable()) { + } else if (textLine[y - 1].isSelectable()) { y--; } } - if (TextLine[y].isSelectable() || (HasScrollbar && y == BackButtonLine())) { - CurrentTextLine = 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() { - CountdownScrollUp = -1; - CountdownScrollDown = -1; + activeStore = TalkID::None; } } // namespace devilution diff --git a/Source/stores.h b/Source/stores.h index cea33714bbca..753718134536 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 ActiveStore; - -/** Current index into PlayerItemIndexes/PlayerItems */ -extern DVL_API_FOR_TEST int CurrentItemIndex; -/** Map of inventory items being presented in the store */ -extern int8_t PlayerItemIndexes[48]; -/** Copies of the players items as presented in the store */ -extern DVL_API_FOR_TEST Item PlayerItems[48]; - -/** Items sold by Griswold */ -extern Item SmithItems[SMITH_ITEMS]; -/** Number of premium items for sale by Griswold */ -extern int PremiumItemCount; -/** Base level of current premium items sold by Griswold */ -extern int PremiumItemLevel; -/** Premium items sold by Griswold */ -extern Item PremiumItems[SMITH_PREMIUM_ITEMS]; - -/** Items sold by Pepin */ -extern Item HealerItems[20]; - -/** Items sold by Adria */ -extern Item WitchItems[WITCH_ITEMS]; - -/** Current level of the item sold by Wirt */ -extern int BoyItemLevel; -/** 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 b89786a185ea..5468d078c013 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 9ec9fca6c0a5..44fe4e33aac9 100644 --- a/Source/track.cpp +++ b/Source/track.cpp @@ -66,7 +66,7 @@ void RepeatMouseAction() if (sgbMouseDown == CLICK_NONE && ControllerActionHeld == GameActionType_NONE) return; - if (ActiveStore != 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 a725eef9f42a..f1c98aac768f 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 PremiumItemCount -R 32 PremiumItemLevel +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 0e4d60e29e9a..3403457d3fa0 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 = &PlayerItems[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; - CurrentItemIndex = 0; - AddStoreHoldRepair(item, 0); - EXPECT_EQ(1, CurrentItemIndex); + currentItemIndex = 0; + AddPlayerItemToRepairList(item, 0); + EXPECT_EQ(1, currentItemIndex); EXPECT_EQ(95 * (item->_iMaxDur - i) / 2, item->_ivalue); } item->_iDurability = 59; - CurrentItemIndex = 0; + currentItemIndex = 0; item->_ivalue = 500; item->_iIvalue = 30; // To cheap to repair - AddStoreHoldRepair(item, 0); - EXPECT_EQ(0, CurrentItemIndex); + 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 = &PlayerItems[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; - CurrentItemIndex = 0; - AddStoreHoldRepair(item, 0); - EXPECT_EQ(1, CurrentItemIndex); + currentItemIndex = 0; + AddPlayerItemToRepairList(item, 0); + EXPECT_EQ(1, currentItemIndex); EXPECT_EQ(50 * (item->_iMaxDur - i), item->_ivalue); } item->_iDurability = 19; - CurrentItemIndex = 0; + currentItemIndex = 0; item->_ivalue = 10; // less than 1 per dur item->_iIvalue = item->_ivalue; - AddStoreHoldRepair(item, 0); - EXPECT_EQ(1, CurrentItemIndex); + AddPlayerItemToRepairList(item, 0); + EXPECT_EQ(1, currentItemIndex); EXPECT_EQ(1, item->_ivalue); EXPECT_EQ(1, item->_iIvalue); }