From c9d30dca14a553f8fc5ba318ac74d7094f7da295 Mon Sep 17 00:00:00 2001 From: Eric Robinson Date: Thu, 26 Sep 2024 22:54:06 -0400 Subject: [PATCH] Progress update --- Source/items.cpp | 24 +++++--- Source/stores.cpp | 136 ++++++++++++++++++++++++++++++---------------- Source/stores.h | 12 ++-- 3 files changed, 115 insertions(+), 57 deletions(-) diff --git a/Source/items.cpp b/Source/items.cpp index b5c89afb554..ff1805e9f2f 100644 --- a/Source/items.cpp +++ b/Source/items.cpp @@ -4380,13 +4380,15 @@ void SpawnSmith(int lvl) constexpr int PinnedItemCount = 0; int maxValue = 140000; - int maxItems = 19; + int maxItems = NumSmithBasicItems; + if (gbIsHellfire) { maxValue = 200000; - maxItems = 24; + maxItems = NumSmithBasicItemsHf; } int iCnt = RandomIntBetween(10, maxItems); + for (int i = 0; i < iCnt; i++) { Item &newItem = Blacksmith.basicItems[i]; @@ -4401,7 +4403,10 @@ void SpawnSmith(int lvl) newItem._iCreateInfo = lvl | CF_SMITH; newItem._iIdentified = true; } - for (int i = iCnt; i < NumSmithItemsHf; i++) + + Blacksmith.basicItemCount = iCnt; + + for (int i = iCnt; i < NumSmithBasicItems; i++) Blacksmith.basicItems[i].clear(); SortVendor(Blacksmith.basicItems, PinnedItemCount); @@ -4449,10 +4454,10 @@ void SpawnWitch(int lvl) int bookCount = 0; const int pinnedBookCount = gbIsHellfire ? RandomIntLessThan(MaxPinnedBookCount) : 0; - const int itemCount = RandomIntBetween(10, gbIsHellfire ? 24 : 17); + const int itemCount = RandomIntBetween(10, gbIsHellfire ? NumWitchItemsHf : NumWitchItems); const int maxValue = gbIsHellfire ? 200000 : 140000; - for (int i = 0; i < NumWitchItems; i++) { + for (int i = 0; i < itemCount; i++) { Item &item = Witch.items[i]; item = {}; @@ -4503,6 +4508,7 @@ void SpawnWitch(int lvl) item._iCreateInfo = lvl | CF_WITCH; item._iIdentified = true; } + Witch.itemCount = itemCount; SortVendor(Witch.items, PinnedItemCount); } @@ -4616,6 +4622,9 @@ void SpawnBoy(int lvl) || Boy.items[0]._iMinDex > dexterity || Boy.items[0]._iIvalue < ivalue) && count < 250)); + + Boy.itemCount = 1; // FIXME: Magic number + Boy.items[0]._iCreateInfo = lvl | CF_BOY; Boy.items[0]._iIdentified = true; Boy.itemLevel = lvl / 2; @@ -4625,9 +4634,9 @@ void SpawnHealer(int lvl) { constexpr size_t PinnedItemCount = 2; constexpr std::array<_item_indexes, PinnedItemCount + 1> PinnedItemTypes = { IDI_HEAL, IDI_FULLHEAL, IDI_RESURRECT }; - const auto itemCount = static_cast(RandomIntBetween(10, gbIsHellfire ? 19 : 17)); + const auto itemCount = static_cast(RandomIntBetween(10, gbIsHellfire ? NumHealerItemsHf : NumHealerItems)); - for (size_t i = 0; i < sizeof(Healer.items) / sizeof(Healer.items[0]); ++i) { + for (size_t i = 0; i < itemCount; i++) { Item &item = Healer.items[i]; item = {}; @@ -4651,6 +4660,7 @@ void SpawnHealer(int lvl) item._iCreateInfo = lvl | CF_HEALER; item._iIdentified = true; } + Healer.itemCount = itemCount; SortVendor(Healer.items, PinnedItemCount); } diff --git a/Source/stores.cpp b/Source/stores.cpp index 4f3eff3e01a..ae322d4388d 100644 --- a/Source/stores.cpp +++ b/Source/stores.cpp @@ -97,7 +97,7 @@ 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 +bool hasItemList; // Does the current panel have a scrollbar int oldScrollPos; // Remember last scroll position int scrollPos; // Scroll position int nextScrollPos; // Next scroll position @@ -205,6 +205,17 @@ constexpr int SmallTextHeight = 12; constexpr int LargeLineHeight = SmallLineHeight + 1; constexpr int LargeTextHeight = 18; +std::unordered_map<_talker_id, TownerStore *> townerStores; + +void InitializeTownerStores() +{ + townerStores[TOWN_SMITH] = &Blacksmith; + townerStores[TOWN_HEALER] = &Healer; + townerStores[TOWN_WITCH] = &Witch; + townerStores[TOWN_PEGBOY] = &Boy; + townerStores[TOWN_STORY] = &Storyteller; +} + /** * The line index with the Back / Leave button. * This is a special button that is always the last line. @@ -214,7 +225,7 @@ constexpr int LargeTextHeight = 18; int BackButtonLine() { if (IsSmallFontTall()) { - return hasScrollbar ? 21 : 20; + return hasItemList ? 21 : 20; } return 22; } @@ -255,8 +266,33 @@ void DrawTextUI(const Surface &out) DrawHalfTransparentRectTo(out, uiPosition.x + 347, uiPosition.y + 28, 265, 297); } +// FIXME: Put in anonymous namespace +int GetItemCount(TalkID talkId) +{ + TownerStore *towner = townerStores[townerId]; + + if (towner != nullptr) { + switch (talkId) { + case TalkID::SmithBuy: + return towner->basicItemCount; + case TalkID::SmithPremiumBuy: + case TalkID::HealerBuy: + case TalkID::WitchBuy: + case TalkID::BoyBuy: + return towner->itemCount; + } + } + + return playerItems.size(); +} + void DrawScrollbar(const Surface &out, int y1, int y2) { + int itemCount = GetItemCount(activeStore); + + if (itemCount <= BuyLineSpace) + return; + const Point uiPosition = GetUIRectangle().position; int yd1 = y1 * 12 + 44 + uiPosition.y; int yd2 = y2 * 12 + 44 + uiPosition.y; @@ -277,10 +313,8 @@ void DrawScrollbar(const Surface &out, int y1, int y2) yd3 = oldTextLine; else yd3 = currentTextLine; - if (currentItemIndex > 1) - yd3 = 1000 * (scrollPos + ((yd3 - previousScrollPos) / 4)) / (currentItemIndex - 1) * (y2 * 12 - y1 * 12 - 24) / 1000; - else - yd3 = 0; + + yd3 = 1000 * (scrollPos + ((yd3 - previousScrollPos) / 4)) / (itemCount - 1) * ((y2 * 12) - (y1 * 12) - 24) / 1000; ClxDraw(out, { uiPosition.x + 601, (y1 + 1) * 12 + 44 + uiPosition.y + yd3 }, (*pSTextSlidCels)[12]); } @@ -405,7 +439,7 @@ bool GiveItemToPlayer(Item &item, bool persistItem) void SetupErrorScreen(TalkID talkId) { StartStore(oldActiveStore); - hasScrollbar = false; + hasItemList = false; std::string_view text; switch (talkId) { @@ -426,7 +460,7 @@ void SetupErrorScreen(TalkID talkId) void SetupConfirmScreen(Item &item) { StartStore(oldActiveStore); - hasScrollbar = false; + hasItemList = false; ClearTextLines(5, 23); UiFlags itemColor = item.getTextColorWithStatCheck(); @@ -472,7 +506,7 @@ void SetupGossipScreen() int la; isTextFullSize = false; - hasScrollbar = false; + hasItemList = false; SetLineText(0, 2, fmt::format(fmt::runtime(_("Talk to {:s}")), "" /*towner name*/), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); SetLineAsDivider(5); @@ -618,7 +652,7 @@ void SetupTownerMenuScreen() RestoreResource(); isTextFullSize = false; - hasScrollbar = false; + hasItemList = false; const TownerLine &lines = TownerLines[townerId]; @@ -859,6 +893,7 @@ void SetupTownerItemList(TalkID talkId, int idx, bool selling = true) break; case TalkID::BoyBuy: SetupItemList(talkId, Boy.items, idx, selling); + break; case TalkID::SmithSell: case TalkID::SmithRepair: case TalkID::WitchSell: @@ -871,11 +906,11 @@ void SetupTownerItemList(TalkID talkId, int idx, bool selling = true) } } -void SetBuyScreenHeader() +void SetBuyScreenHeader(TalkID talkId) { const UiFlags flags = UiFlags::ColorWhitegold; - if (currentItemIndex > 1) + if (GetItemCount(talkId) > 1) SetLineText(20, 1, _("I have these items for sale:"), flags, false); else SetLineText(20, 1, _("I have this item for sale:"), flags, false); @@ -902,14 +937,11 @@ static void UpdateItemStatFlag(Item &item) { if (!item.isEmpty()) { item._iStatFlag = MyPlayer->CanUseItem(item); - currentItemIndex++; } } void UpdateItemStatFlags(TalkID talkId) { - currentItemIndex = 0; // FIXME: Remove this?? - switch (talkId) { case TalkID::SmithBuy: for (Item &item : Blacksmith.basicItems) @@ -951,29 +983,28 @@ void SetupTownerBuyScreen(TalkID talkId) isTextFullSize = true; scrollPos = 0; renderGold = true; - - if (talkId == TalkID::WitchBuy) { - numTextLines = 20; - } + int itemCount = GetItemCount(talkId); SetLineAsDivider(BuySellMenuDividerLine); SetupTownerItemList(talkId, scrollPos); UpdateItemStatFlags(talkId); - hasScrollbar = currentItemIndex > BuyLineSpace ? true : false; - numTextLines = std::max(currentItemIndex - BuyLineSpace, 0); - SetBuyScreenHeader(); + hasItemList = itemCount > BuyLineSpace ? true : false; + numTextLines = std::max(itemCount - BuyLineSpace, 0); + SetBuyScreenHeader(talkId); AddItemListBackButton(talkId, true); } void SetupTownerSellScreen(TalkID talkId) { isTextFullSize = true; - hasScrollbar = true; - FilterPlayerItemsForAction(talkId); + int itemCount = GetItemCount(talkId); + + hasItemList = itemCount > BuyLineSpace ? true : false; + if (playerItems.empty()) { - hasScrollbar = false; + hasItemList = false; renderGold = true; SetLineText(20, 1, _("You have nothing I want."), UiFlags::ColorWhitegold, false); SetLineAsDivider(BuySellMenuDividerLine); @@ -988,9 +1019,10 @@ void SetupTownerSellScreen(TalkID talkId) SetLineAsDivider(BuySellMenuDividerLine); SetupTownerItemList(talkId, scrollPos, false); - AddItemListBackButton(talkId); + AddItemListBackButton(talkId, /*selectable=*/true); } +// FIXME: should take TalkID as argument??? void SetupTownerRepairScreen() { isTextFullSize = true; @@ -998,9 +1030,13 @@ void SetupTownerRepairScreen() // Filter the items and calculate repair costs FilterPlayerItemsForAction(TalkID::SmithRepair); + int itemCount = GetItemCount(TalkID::SmithRepair); + + hasItemList = itemCount > BuyLineSpace ? true : false; + // If no items are left after filtering, show a "nothing to repair" message if (playerItems.empty()) { - hasScrollbar = false; + hasItemList = false; renderGold = true; SetLineText(20, 1, _("You have nothing to repair."), UiFlags::ColorWhitegold, false); SetLineAsDivider(BuySellMenuDividerLine); @@ -1009,7 +1045,6 @@ void SetupTownerRepairScreen() } // Display the filtered list in the store UI - hasScrollbar = true; scrollPos = 0; numTextLines = static_cast(playerItems.size()); @@ -1019,7 +1054,7 @@ void SetupTownerRepairScreen() // Pass the filtered items to SetupTownerItemList for rendering SetupTownerItemList(TalkID::SmithRepair, scrollPos, false); - AddItemListBackButton(TalkID::SmithRepair); + AddItemListBackButton(TalkID::SmithRepair, /*selectable=*/true); } void SetupTownerRechargeScreen() @@ -1029,9 +1064,13 @@ void SetupTownerRechargeScreen() // Filter the items and calculate recharge costs FilterPlayerItemsForAction(TalkID::WitchRecharge); + int itemCount = GetItemCount(TalkID::WitchRecharge); + + hasItemList = itemCount > BuyLineSpace ? true : false; + // If no items are left after filtering, show a "nothing to recharge" message if (playerItems.empty()) { - hasScrollbar = false; + hasItemList = false; renderGold = true; SetLineText(20, 1, _("You have nothing to recharge."), UiFlags::ColorWhitegold, false); SetLineAsDivider(3); @@ -1040,7 +1079,6 @@ void SetupTownerRechargeScreen() } // Display the filtered list in the store UI - hasScrollbar = true; scrollPos = 0; numTextLines = static_cast(playerItems.size()); @@ -1060,9 +1098,13 @@ void SetupTownerIdentifyScreen() // Filter the items to include only those that can be identified FilterPlayerItemsForAction(TalkID::StorytellerIdentify); + int itemCount = GetItemCount(TalkID::StorytellerIdentify); + + hasItemList = itemCount > BuyLineSpace ? true : false; + // If no items are left after filtering, show a "nothing to identify" message if (playerItems.empty()) { - hasScrollbar = false; + hasItemList = false; renderGold = true; SetLineText(20, 1, _("You have nothing to identify."), UiFlags::ColorWhitegold, false); SetLineAsDivider(3); @@ -1071,7 +1113,6 @@ void SetupTownerIdentifyScreen() } // Display the filtered list in the store UI - hasScrollbar = true; scrollPos = 0; numTextLines = static_cast(playerItems.size()); @@ -1087,7 +1128,7 @@ void SetupTownerIdentifyScreen() void SetupTownerIdentifyResultScreen(Item &item) { StartStore(oldActiveStore); - hasScrollbar = false; + hasItemList = false; ClearTextLines(5, 23); UiFlags itemColor = item.getTextColorWithStatCheck(); @@ -1935,16 +1976,17 @@ void DrawSelector(const Surface &out, const Rectangle &rect, std::string_view te void InitStores() { + int numSmithItems = gbIsHellfire ? NumSmithItemsHf : NumSmithItems; ClearTextLines(0, NumStoreLines); activeStore = TalkID::None; isTextFullSize = false; - hasScrollbar = false; + hasItemList = false; Blacksmith.itemCount = 0; Blacksmith.itemLevel = 1; // Resize the vectors to their intended sizes Blacksmith.basicItems.resize(NumSmithBasicItems); - Blacksmith.items.resize(NumSmithItems); + Blacksmith.items.resize(numSmithItems); Healer.items.resize(NumHealerItems); Witch.items.resize(NumWitchItems); Boy.items.resize(NumBoyItems); @@ -1957,6 +1999,8 @@ void InitStores() item.clear(); Boy.itemLevel = 0; + + InitializeTownerStores(); } void SetupTownStores() @@ -2001,7 +2045,7 @@ void PrintSString(const Surface &out, int margin, int line, std::string_view tex const int sy = uiPosition.y + PaddingTop + textLine[line].y + textLine[line]._syoff; int width = isTextFullSize ? 575 : 255; - if (hasScrollbar && line >= 4 && line <= 20) { + if (hasItemList && line >= 4 && line <= 20) { width -= 9; // Space for the selector } width -= margin * 2; @@ -2210,7 +2254,7 @@ void DrawSText(const Surface &out) else DrawQTextBack(out); - if (hasScrollbar) { + if (hasItemList) { switch (activeStore) { case TalkID::SmithBuy: SetupTownerItemList(TalkID::SmithBuy, scrollPos); @@ -2249,7 +2293,7 @@ void DrawSText(const Surface &out) PrintSString(out, 28, 1, fmt::format(fmt::runtime(_("Your gold: {:s}")), FormatInteger(GetTotalPlayerGold())).c_str(), UiFlags::ColorWhitegold | UiFlags::AlignRight); } - if (hasScrollbar) + if (hasItemList) DrawScrollbar(out, 4, 20); } @@ -2336,7 +2380,7 @@ void StoreUp() return; } - if (hasScrollbar) { + if (hasItemList) { if (currentTextLine == previousScrollPos) { if (scrollPos != 0) scrollPos--; @@ -2373,7 +2417,7 @@ void StoreDown() return; } - if (hasScrollbar) { + if (hasItemList) { if (currentTextLine == nextScrollPos) { if (scrollPos < numTextLines) scrollPos++; @@ -2406,7 +2450,7 @@ void StoreDown() void StorePrior() { PlaySFX(SfxID::MenuMove); - if (currentTextLine != -1 && hasScrollbar) { + if (currentTextLine != -1 && hasItemList) { if (currentTextLine == previousScrollPos) { scrollPos = std::max(scrollPos - 4, 0); } else { @@ -2418,7 +2462,7 @@ void StorePrior() void StoreNext() { PlaySFX(SfxID::MenuMove); - if (currentTextLine != -1 && hasScrollbar) { + if (currentTextLine != -1 && hasItemList) { if (currentTextLine == nextScrollPos) { if (scrollPos < numTextLines) scrollPos += 4; @@ -2554,7 +2598,7 @@ void CheckStoreButton() } else if (currentTextLine != -1) { const int relativeY = MousePosition.y - (uiPosition.y + PaddingTop); - if (hasScrollbar && MousePosition.x > 600 + uiPosition.x) { + if (hasItemList && MousePosition.x > 600 + uiPosition.x) { // Scroll bar is always measured in terms of the small line height. int y = relativeY / SmallLineHeight; if (y == 4) { @@ -2588,14 +2632,14 @@ void CheckStoreButton() if (y >= 5) { if (y >= BackButtonLine() + 1) y = BackButtonLine(); - if (hasScrollbar && y <= 20 && !textLine[y].isSelectable()) { + if (hasItemList && y <= 20 && !textLine[y].isSelectable()) { if (textLine[y - 2].isSelectable()) { y -= 2; } else if (textLine[y - 1].isSelectable()) { y--; } } - if (textLine[y].isSelectable() || (hasScrollbar && y == BackButtonLine())) { + if (textLine[y].isSelectable() || (hasItemList && y == BackButtonLine())) { currentTextLine = y; StoreEnter(); } diff --git a/Source/stores.h b/Source/stores.h index 10482e53ac0..13ce8b6f9d5 100644 --- a/Source/stores.h +++ b/Source/stores.h @@ -22,11 +22,14 @@ namespace devilution { /** @brief Number of player items that display in stores (Inventory slots and belt slots) */ const int NumPlayerItems = (NUM_XY_SLOTS - (SLOTXY_EQUIPPED_LAST + 1)); -constexpr int NumSmithBasicItems = 25; +constexpr int NumSmithBasicItems = 19; +constexpr int NumSmithBasicItemsHf = 24; constexpr int NumSmithItems = 6; constexpr int NumSmithItemsHf = 15; -constexpr int NumHealerItems = 20; -constexpr int NumWitchItems = 25; +constexpr int NumHealerItems = 17; +constexpr int NumHealerItemsHf = 19; +constexpr int NumWitchItems = 17; +constexpr int NumWitchItemsHf = 24; constexpr int NumBoyItems = 1; constexpr int NumStoreLines = 104; @@ -91,9 +94,10 @@ class TownerStore { // Store(); public: std::vector basicItems; // Used for the blacksmith store that only displays non-magical items + int basicItemCount; std::vector items; - uint8_t itemLevel; int itemCount; + uint8_t itemLevel; //_talker_id townerId; TalkID buyAction;