diff --git a/docs/modding.md b/docs/modding.md index 3fd5bc51..6c73a042 100644 --- a/docs/modding.md +++ b/docs/modding.md @@ -300,5 +300,10 @@ update and input ownership, so a mod can mirror a native menu on another display without reimplementing it. The default is `true`. Treat the wrapper as a pure predicate: the renderer may ask it more than once per frame. +Scrollable list states expose `state.kind` for use with this hook. Generic +lists fall back to their title; PC lists use stable, localization-independent +identifiers: `pc_box_withdraw`, `pc_box_deposit`, `pc_box_release`, +`pc_box_change`, `pc_item_withdraw`, `pc_item_deposit`, and `pc_item_toss`. + Developer mode also arms the mod loader's dev tripwire, which flags mods that reach outside their permission set. diff --git a/src/ui/BoxMenu.lua b/src/ui/BoxMenu.lua index 332e90ef..7fed06b7 100644 --- a/src/ui/BoxMenu.lua +++ b/src/ui/BoxMenu.lua @@ -67,6 +67,7 @@ local function withdraw(game) game.stack:push(ListMenu.new(game, Strings("BOX %d (WITHDRAW)", game.save.currentBox), items, { noSound = true, -- PCMainMenu holds BIT_NO_MENU_BUTTON_SOUND (#570) + kind = "pc_box_withdraw", onChoose = function(item, list) local mon = box[item.value] if not mon then return end @@ -114,6 +115,7 @@ local function deposit(game) end game.stack:push(ListMenu.new(game, "PARTY (DEPOSIT)", items, { noSound = true, -- PCMainMenu holds BIT_NO_MENU_BUTTON_SOUND (#570) + kind = "pc_box_deposit", onChoose = function(item, list) local mon = game.save.party[item.value] if not mon then return end @@ -160,6 +162,7 @@ local function release(game) game.stack:push(ListMenu.new(game, Strings("BOX %d (RELEASE)", game.save.currentBox), items, { noSound = true, -- PCMainMenu holds BIT_NO_MENU_BUTTON_SOUND (#570) + kind = "pc_box_release", onChoose = function(_, list) local mon = box[list.index] if not mon then return end @@ -193,6 +196,7 @@ local function changeBox(game) end game.stack:push(ListMenu.new(game, "CHANGE BOX", items, { noSound = true, -- PCMainMenu holds BIT_NO_MENU_BUTTON_SOUND (#570) + kind = "pc_box_change", onChoose = function(item, list) -- the original asks BEFORE switching ("When you change a #MON -- BOX, data will be saved. OK?"); declining aborts the change diff --git a/src/ui/ListMenu.lua b/src/ui/ListMenu.lua index 5c1f0eaa..b3856a0c 100644 --- a/src/ui/ListMenu.lua +++ b/src/ui/ListMenu.lua @@ -51,6 +51,7 @@ function ListMenu.new(game, title, items, opts) local self = setmetatable({}, ListMenu) self.game = game self.title = title + self.kind = opts.kind or title self.items = items self.index = 1 self.scroll = 0 diff --git a/src/ui/PlayerPC.lua b/src/ui/PlayerPC.lua index 6fdb6b90..09b9e60b 100644 --- a/src/ui/PlayerPC.lua +++ b/src/ui/PlayerPC.lua @@ -72,6 +72,7 @@ end local function withdraw(game) local pc = game.save.pcItems game.stack:push(ListMenu.new(game, "WITHDRAW ITEM", buildItems(game, pc), { + kind = "pc_item_withdraw", messageBox = true, noSound = true, -- PlayerPCMenu holds BIT_NO_MENU_BUTTON_SOUND (#570) onChoose = function(item, list) @@ -110,6 +111,7 @@ local function deposit(game) if not Bag.isBadge(id) then depositable[id] = count end end game.stack:push(ListMenu.new(game, "DEPOSIT ITEM", buildItems(game, depositable), { + kind = "pc_item_deposit", messageBox = true, noSound = true, -- PlayerPCMenu holds BIT_NO_MENU_BUTTON_SOUND (#570) onChoose = function(item, list) @@ -131,6 +133,7 @@ end local function toss(game) local pc = game.save.pcItems game.stack:push(ListMenu.new(game, "TOSS ITEM", buildItems(game, pc), { + kind = "pc_item_toss", messageBox = true, noSound = true, -- PlayerPCMenu holds BIT_NO_MENU_BUTTON_SOUND (#570) onChoose = function(item, list) diff --git a/tests/engine/pc_list_kinds.lua b/tests/engine/pc_list_kinds.lua new file mode 100644 index 00000000..ad0238c3 --- /dev/null +++ b/tests/engine/pc_list_kinds.lua @@ -0,0 +1,48 @@ +-- Stable ListMenu identities for screen.render_visible and companion UIs. +package.path = "./?.lua;./?/init.lua;" .. package.path + +local T = require("tests.modkit") +local Data = T.fixtures.load() + +local SaveData = require("src.core.SaveData") +local BoxMenu = require("src.ui.BoxMenu") +local ListMenu = require("src.ui.ListMenu") +local PlayerPC = require("src.ui.PlayerPC") +local Boxes = require("src.pokemon.Boxes") + +local pushed +local game = { + data = Data, + save = SaveData.newGame(), + stack = { push = function(_, state) pushed = state end }, +} + +local species = T.fixtures.ids.species[1] +Boxes.ensure(game.save)[1][1] = { species = species, level = 5 } +game.save.party[1] = { species = species, level = 5 } +game.save.party[2] = { species = species, level = 6 } +game.save.pcItems = { FIX_POTION = 2 } +game.save.inventory.FIX_POTION = 2 + +local generic = ListMenu.new(game, "VISIBLE TITLE", {}, {}) +T.eq(generic.kind, "VISIBLE TITLE", "generic lists fall back to their title") +local explicit = ListMenu.new(game, "Localized title", {}, { kind = "stable_id" }) +T.eq(explicit.kind, "stable_id", "explicit list kind is preserved") + +local box = BoxMenu.new(game) +for i, kind in ipairs({ "pc_box_withdraw", "pc_box_deposit", + "pc_box_release", "pc_box_change" }) do + pushed = nil + box.items[i].onSelect() + T.eq(pushed and pushed.kind, kind, kind .. " is stable") +end + +local items = PlayerPC.new(game) +for i, kind in ipairs({ "pc_item_withdraw", "pc_item_deposit", + "pc_item_toss" }) do + pushed = nil + items.items[i].onSelect() + T.eq(pushed and pushed.kind, kind, kind .. " is stable") +end + +T.finish("pc_list_kinds")