diff --git a/docs/modding.md b/docs/modding.md index 4856b6c1..824b3a3a 100644 --- a/docs/modding.md +++ b/docs/modding.md @@ -226,6 +226,8 @@ exposes `headbutt`, `whirlpool`, `waterfall`, `sweet_scent`, and the contextual `squirtbottle` key item. Fishing rows include the owned rods that are valid choices. The list is empty while the world is busy, and omits an action whenever its item, move, badge, terrain, or engine state forbids it. +The optional second return is `"world is busy"` during transient input locks +or `"no overworld"` before a playable world exists. Call `mod.world:useFieldAction(id, opts)` to perform a listed action through the active game's own field-item path. Fishing accepts `{ rod = "OLD_ROD" }` diff --git a/src/world/WorldAPI.lua b/src/world/WorldAPI.lua index 4f4200e9..286e5dd6 100644 --- a/src/world/WorldAPI.lua +++ b/src/world/WorldAPI.lua @@ -95,8 +95,10 @@ end -- are listed; callers receive copied labels and never inspect world internals. function WorldAPI:availableFieldActions() local game, ow, out = self.game, self:overworld(), {} - if not (game and game.save and ow and ow.map and ow.player) - or not acceptsMenuInput(game, ow) then return out end + if not (game and game.save and ow and ow.map and ow.player) then + return out, NO_OVERWORLD + end + if not acceptsMenuInput(game, ow) then return out, "world is busy" end local save, inventory = game.save, game.save.inventory or {} local items = game.data and game.data.items or {} diff --git a/src/world/gen2/WorldAPI.lua b/src/world/gen2/WorldAPI.lua index 9c204cf4..b5f1dbd9 100644 --- a/src/world/gen2/WorldAPI.lua +++ b/src/world/gen2/WorldAPI.lua @@ -78,8 +78,10 @@ end -- collision and fishing rules. function WorldAPI:availableFieldActions() local world, game, out = self:overworld(), self.game, {} - if not (world and game and game.save and world.map and world.player) - or not world:acceptsMenuInput() then return out end + if not (world and game and game.save and world.map and world.player) then + return out, NO_OVERWORLD + end + if not world:acceptsMenuInput() then return out, "world is busy" end local inventory = game.save.inventory or {} if (inventory.BICYCLE or 0) > 0 then diff --git a/tests/modkit/cases/world_field_items.lua b/tests/modkit/cases/world_field_items.lua index 095c333a..0e90146e 100644 --- a/tests/modkit/cases/world_field_items.lua +++ b/tests/modkit/cases/world_field_items.lua @@ -34,6 +34,9 @@ function redGame.stack:top() return self.states[#self.states] end local RedAPI = require("src.world.WorldAPI") local red = RedAPI.new(redGame, "fixture") +local unavailable, reason = RedAPI.new({}, "fixture"):availableFieldActions() +T.eq(#unavailable, 0, "Red lists no actions without an overworld") +T.eq(reason, "no overworld", "Red reports a missing overworld") local RedWorld = require("src.world.OverworldController") T.check(type(RedWorld.useBicycle) == "function" and type(RedWorld.useFishingRod) == "function" @@ -59,7 +62,9 @@ T.check(not ok and err == "fishing rod unavailable", T.eq(redWorld.rodUsed, used, "a rejected Red rod changes nothing") redWorld.player.moving = true -T.eq(#red:availableFieldActions(), 0, "Red hides actions while moving") +actions, err = red:availableFieldActions() +T.eq(#actions, 0, "Red hides actions while moving") +T.eq(err, "world is busy", "Red distinguishes a busy world from no actions") ok, err = red:useFieldAction("bicycle") T.check(not ok and err == "world is busy", "Red refuses a stale action while busy") @@ -142,6 +147,9 @@ goldWorld.fieldContext = function(_, mon) return { local GoldAPI = require("src.world.gen2.WorldAPI") local gold = GoldAPI.new(goldGame, "fixture") +unavailable, reason = GoldAPI.new({}, "fixture"):availableFieldActions() +T.eq(#unavailable, 0, "Gold lists no actions without an overworld") +T.eq(reason, "no overworld", "Gold reports a missing overworld") actions = gold:availableFieldActions() byId = {} for _, action in ipairs(actions) do byId[action.id] = action end @@ -170,4 +178,9 @@ T.check(gold:useFieldAction("squirtbottle"), T.eq(goldWorld.itemUsed, "SQUIRTBOTTLE", "Gold delegates the SquirtBottle to its field-item path") +goldWorld.acceptsMenuInput = function() return false end +actions, err = gold:availableFieldActions() +T.eq(#actions, 0, "Gold hides actions while busy") +T.eq(err, "world is busy", "Gold distinguishes a busy world from no actions") + T.finish()