Fix Save Editor inventory edits leaving C/D-pad buttons desynced (#7008)

The Save Editor's Inventory tab writes gSaveContext.inventory.items[]
directly, but a C/D-pad item button mirrors the slot it points at
(buttonItems[i] == items[cButtonSlots[i-1]]). That derived value was
never refreshed, so e.g. emptying a bottle assigned to a button left the
button showing its old contents -- a desync the game's equip/inventory
consistency then trips on at the next age swap or bottle update.

Re-sync any button pointing at an edited slot after the write. Equipment
assigned to a button stores an equipment-page index in cButtonSlots
(>= inventory size), so it never matches an inventory slot and is left
untouched.
Includes a default-on "Keep C/D-pad buttons in sync" checkbox
(mirroring "Restrict to valid items") so inventory edits can intentionally
leave a slot desynced from its button for RBA practice/debugging.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
David Racine
2026-08-03 09:36:29 -04:00
committed by GitHub
parent 985590246f
commit 44f680a0b9
@@ -522,6 +522,19 @@ void DrawBGSItemFlag(uint8_t itemID) {
ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1));
}
// Re-sync any C/D-pad button that mirrors an edited inventory slot (buttonItems[i] == items[cButtonSlots[i-1]]),
// so a raw item edit doesn't leave the button showing stale contents.
static void SyncButtonItemsForSlot(uint8_t slot) {
for (size_t i = 1; i < ARRAY_COUNT(gSaveContext.equips.buttonItems); i++) {
if (gSaveContext.equips.cButtonSlots[i - 1] == slot) {
gSaveContext.equips.buttonItems[i] = gSaveContext.inventory.items[slot];
if (gPlayState != nullptr) {
Interface_LoadItemIcon1(gPlayState, static_cast<u16>(i));
}
}
}
}
void DrawInventoryTab() {
static bool restrictToValid = true;
@@ -529,6 +542,11 @@ void DrawInventoryTab() {
"Restrict to valid items", &restrictToValid,
checkboxOptionsBase.Tooltip("Restricts items and ammo to only what is possible to legally acquire in-game"));
static bool syncButtons = true;
Checkbox("Keep C/D-pad buttons in sync", &syncButtons,
checkboxOptionsBase.Tooltip("Refresh a C or D-pad button when its inventory slot is edited. Disable to "
"leave a slot and its button out of sync (e.g. to set up RBA)."));
for (int y = 0; y < 4; y++) {
for (int x = 0; x < 6; x++) {
static_assert(5 + 3 * 6 < sizeof(gSaveContext.inventory.items) / sizeof(gSaveContext.inventory.items[0]));
@@ -579,8 +597,12 @@ void DrawInventoryTab() {
PushStyleButton(Colors::DarkGray);
if (ImGui::Button("##itemNonePicker",
ImVec2(IMAGE_SIZE, IMAGE_SIZE) + ImGui::GetStyle().FramePadding * 2)) {
if (selectedIndex != SLOT_NONE)
if (selectedIndex != SLOT_NONE) {
gSaveContext.inventory.items[selectedIndex] = ITEM_NONE;
if (syncButtons) {
SyncButtonItemsForSlot(selectedIndex);
}
}
ImGui::CloseCurrentPopup();
}
PopStyleButton();
@@ -619,6 +641,9 @@ void DrawInventoryTab() {
PopStyleButton();
if (ret) {
gSaveContext.inventory.items[selectedIndex] = slotEntry.id;
if (syncButtons) {
SyncButtonItemsForSlot(selectedIndex);
}
ImGui::CloseCurrentPopup();
}
UIWidgets::Tooltip(SohUtils::GetItemName(slotEntry.id).c_str());