Skip to content

Commit 0ecc3ed

Browse files
strichyuripourre
andauthored
[QOL] The buy and sell functionality of NPC stores is now visually driven to align with the stash and Diablo 2 style of trading (#8434)
- Add repair buttons (including “Repair All”) with proper alignment, tooltips, and cursor hit areas - Improve controller support: - Fix grid snapping and cursor positioning across panels - Allow pressing/releasing UI buttons - Fix item selling and movement between panels (inventory, belt, store) - Prevent invalid interactions: - Disable tab/repair navigation while holding items - Hide unavailable options for non-Smith vendors - Fix UI issues: - Item misalignment and snapping - Floating gold cost display - Level-up button overlapping store panel - Fix vendor-specific issues: - Correct tooltip for non-Smith vendors - Clean up Adria dialog options - Fix stability issues: - Resolve segfault when selling items - Refactor and cleanup: - Unify store sell logic - Remove unused and stale code - Improve IsPlayerInStore() logic - General bug fixes and UI interaction improvements --------- Co-authored-by: Yuri Pourre <yuripourre@users.noreply.github.com>
1 parent 9f9489b commit 0ecc3ed

21 files changed

+1675
-132
lines changed

CMake/Assets.cmake

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,13 @@ set(devilutionx_assets
6262
data/monstertags.clx
6363
data/panel8buc.clx
6464
data/panel8bucp.clx
65+
data/repairAllBtn.clx
66+
data/repairSingleBtn.clx
6567
data/resistance.clx
6668
data/stash.clx
6769
data/stashnavbtns.clx
70+
data/store.clx
71+
data/tabBtnUp.clx
6872
data/talkbutton.clx
6973
data/xpbar.clx
7074
fonts/12-00.clx

Source/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ set(libdevilutionx_SRCS
150150
qol/itemlabels.cpp
151151
qol/monhealthbar.cpp
152152
qol/stash.cpp
153+
qol/visual_store.cpp
153154
qol/xpbar.cpp
154155

155156
quests/validation.cpp

Source/control/control_infobox.cpp

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "levels/trigs.h"
77
#include "panels/partypanel.hpp"
88
#include "qol/stash.h"
9+
#include "qol/visual_store.h"
910
#include "qol/xpbar.h"
1011
#include "towners.h"
1112
#include "utils/algorithm/container.hpp"
@@ -174,6 +175,29 @@ Rectangle GetFloatingInfoRect(const int lineHeight, const int textSpacing)
174175
}
175176
}
176177

178+
// 5) Visual Store (Rect position)
179+
if (pcursstoreitem != -1) {
180+
const VisualStorePage &page = VisualStore.pages[VisualStore.currentPage];
181+
std::span<Item> allItems = GetVisualStoreItems();
182+
for (const auto &vsItem : page.items) {
183+
if (vsItem.index != pcursstoreitem)
184+
continue;
185+
186+
const Item &item = allItems[vsItem.index];
187+
Point itemPosition = GetVisualStoreSlotCoord(vsItem.position);
188+
const Size itemGridSize = GetInventorySize(item);
189+
190+
itemPosition.y += itemGridSize.height * (VisualStoreGridHeight + 1) - 1; // Align position to bottom left of the item graphic
191+
itemPosition.x += itemGridSize.width * VisualStoreGridWidth / 2; // Align position to center of the item graphic
192+
itemPosition.x -= maxW / 2; // Align position to the center of the floating item info box
193+
194+
return { { itemPosition.x, itemPosition.y }, { maxW, totalH } };
195+
}
196+
}
197+
if (pcursstorebtn != -1) {
198+
return { GetVisualBtnCoord(pcursstorebtn).position, { maxW, totalH } };
199+
}
200+
177201
return { { 0, 0 }, { 0, 0 } };
178202
}
179203

@@ -201,6 +225,11 @@ int GetHoverSpriteHeight()
201225
auto &it = Stash.stashList[pcursstashitem];
202226
return GetInventorySize(it).height * (InventorySlotSizeInPixels.height + 1);
203227
}
228+
if (pcursstoreitem != -1) {
229+
std::span<Item> allItems = GetVisualStoreItems();
230+
auto &it = allItems[pcursstoreitem];
231+
return GetInventorySize(it).height * (INV_SLOT_SIZE_PX + 1);
232+
}
204233
return InventorySlotSizeInPixels.height;
205234
}
206235

@@ -356,7 +385,7 @@ void CheckPanelInfo()
356385
void DrawInfoBox(const Surface &out)
357386
{
358387
DrawPanelBox(out, MakeSdlRect(InfoBoxRect.position.x, InfoBoxRect.position.y + PanelPaddingHeight, InfoBoxRect.size.width, InfoBoxRect.size.height), GetMainPanel().position + Displacement { InfoBoxRect.position.x, InfoBoxRect.position.y });
359-
if (!MainPanelFlag && !trigflag && pcursinvitem == -1 && pcursstashitem == StashStruct::EmptyCell && !SpellSelectFlag && pcurs != CURSOR_HOURGLASS) {
388+
if (!MainPanelFlag && !trigflag && pcursinvitem == -1 && pcursstashitem == StashStruct::EmptyCell && pcursstoreitem == -1 && pcursstorebtn == -1 && !SpellSelectFlag && pcurs != CURSOR_HOURGLASS) {
360389
InfoString = StringOrView {};
361390
InfoColor = UiFlags::ColorWhite;
362391
}
@@ -413,7 +442,7 @@ void DrawInfoBox(const Surface &out)
413442

414443
void DrawFloatingInfoBox(const Surface &out)
415444
{
416-
if (pcursinvitem == -1 && pcursstashitem == StashStruct::EmptyCell) {
445+
if (pcursinvitem == -1 && pcursstashitem == StashStruct::EmptyCell && pcursstoreitem == -1 && pcursstorebtn == -1) {
417446
FloatingInfoString = StringOrView {};
418447
InfoColor = UiFlags::ColorWhite;
419448
}

Source/control/control_panel.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "panels/spell_list.hpp"
2424
#include "pfile.h"
2525
#include "qol/stash.h"
26+
#include "qol/visual_store.h"
2627
#include "stores.h"
2728
#include "utils/sdl_compat.h"
2829

@@ -59,7 +60,7 @@ const Rectangle &GetRightPanel()
5960
}
6061
bool IsLeftPanelOpen()
6162
{
62-
return CharFlag || QuestLogIsOpen || IsStashOpen;
63+
return CharFlag || QuestLogIsOpen || IsStashOpen || IsVisualStoreOpen;
6364
}
6465
bool IsRightPanelOpen()
6566
{
@@ -223,7 +224,7 @@ bool IsLevelUpButtonVisible()
223224
if (ControlMode == ControlTypes::VirtualGamepad) {
224225
return false;
225226
}
226-
if (IsPlayerInStore() || IsStashOpen) {
227+
if (IsPlayerInStore() || IsStashOpen || IsVisualStoreOpen) {
227228
return false;
228229
}
229230
if (QuestLogIsOpen && GetLeftPanel().contains(GetMainPanel().position + Displacement { 0, -74 })) {
@@ -300,6 +301,7 @@ void OpenCharPanel()
300301
QuestLogIsOpen = false;
301302
CloseGoldWithdraw();
302303
CloseStash();
304+
CloseVisualStore();
303305
CharFlag = true;
304306
}
305307

@@ -565,6 +567,7 @@ void CheckMainPanelButtonUp()
565567
CloseCharPanel();
566568
CloseGoldWithdraw();
567569
CloseStash();
570+
CloseVisualStore();
568571
if (!QuestLogIsOpen)
569572
StartQuestlog();
570573
else
@@ -593,9 +596,10 @@ void CheckMainPanelButtonUp()
593596
break;
594597
case PanelButtonInventory:
595598
SpellbookFlag = false;
599+
invflag = !invflag;
596600
CloseGoldWithdraw();
597601
CloseStash();
598-
invflag = !invflag;
602+
CloseVisualStore();
599603
CloseGoldDrop();
600604
break;
601605
case PanelButtonSpellbook:

Source/controls/game_controls.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,10 @@ bool GetGameAction(const SDL_Event &event, ControllerButtonEvent ctrlEvent, Game
181181
if ((!VirtualGamepadState.primaryActionButton.isHeld && ControllerActionHeld == GameActionType_PRIMARY_ACTION)
182182
|| (!VirtualGamepadState.secondaryActionButton.isHeld && ControllerActionHeld == GameActionType_SECONDARY_ACTION)
183183
|| (!VirtualGamepadState.spellActionButton.isHeld && ControllerActionHeld == GameActionType_CAST_SPELL)) {
184+
// Handle button release for visual store buttons
185+
if (ControllerActionHeld == GameActionType_PRIMARY_ACTION) {
186+
PerformPrimaryActionRelease();
187+
}
184188
ControllerActionHeld = GameActionType_NONE;
185189
LastPlayerAction = PlayerActionType::None;
186190
}
@@ -399,6 +403,13 @@ bool HandleControllerButtonEvent(const SDL_Event &event, const ControllerButtonE
399403
if (ctrlEvent.up && !PadmapperActionNameTriggeredByButtonEvent(ctrlEvent).empty()) {
400404
// Button press may have brought up a menu;
401405
// don't confuse release of that button with intent to interact with the menu
406+
407+
// Handle visual store button release for physical gamepad
408+
std::string_view actionName = PadmapperActionNameTriggeredByButtonEvent(ctrlEvent);
409+
if (actionName == "PrimaryAction") {
410+
PerformPrimaryActionRelease();
411+
}
412+
402413
PadmapperRelease(ctrlEvent.button, /*invokeAction=*/true);
403414
return true;
404415
} else if (GetGameAction(event, ctrlEvent, &action)) {

0 commit comments

Comments
 (0)