mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-17 11:11:10 +02:00
feat(mods): add battle menu auxiliary action
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
-- Shared settled ordinary-player-decision predicate. Checkpoint capture and
|
||||
-- the public auxiliary action deliberately use this one engine-owned rule so
|
||||
-- a tool cannot open at a phase that it could not subsequently checkpoint.
|
||||
-- It exposes no controller; callers receive only the result/reason.
|
||||
|
||||
local BattleSafety = {}
|
||||
|
||||
local BATTLE_BUSY_FIELDS = {
|
||||
"current", "afterQueue", "nextInsert", "pendingHit", "waitingUI",
|
||||
"waitingSound", "waitFrames", "draining", "animPlaying", "growIn",
|
||||
"introSlide", "ghostReveal", "mimicCtx", "mimicMoves", "result",
|
||||
}
|
||||
|
||||
local function nonempty(value)
|
||||
return type(value) == "table" and next(value) ~= nil
|
||||
end
|
||||
|
||||
local function running(runner)
|
||||
return runner and runner.isRunning and runner:isRunning()
|
||||
end
|
||||
|
||||
local function scriptsBusy(overworld)
|
||||
return running(overworld and overworld.runner)
|
||||
or nonempty(overworld and overworld.parallelRunners)
|
||||
or nonempty(overworld and overworld.pendingScripts)
|
||||
or nonempty(overworld and overworld.parallelQueue)
|
||||
or nonempty(overworld and overworld.scriptMoves)
|
||||
end
|
||||
|
||||
function BattleSafety.inspect(game, battle)
|
||||
if type(game) ~= "table" or type(game.save) ~= "table"
|
||||
or type(game.save.version) ~= "string" then
|
||||
return nil, "not_in_playthrough", "A checkpoint requires an identified active playthrough."
|
||||
end
|
||||
if type(battle) ~= "table" then
|
||||
return nil, "not_battle", "No battle is active."
|
||||
end
|
||||
if battle.kind == "link" then
|
||||
return nil, "link_battle_unsupported", "Network battles cannot be checkpointed."
|
||||
end
|
||||
if battle.safari or battle.ghost or battle.scopeReveal or battle.demo or battle.noCatch then
|
||||
return nil, "battle_variant_unsupported",
|
||||
"This battle variant does not have a checkpoint contract."
|
||||
end
|
||||
if battle.kind ~= "wild" and battle.kind ~= "trainer" then
|
||||
return nil, "battle_variant_unsupported",
|
||||
"This battle kind does not have a checkpoint contract."
|
||||
end
|
||||
local expectedOrigin = battle.kind == "wild" and "wild_encounter"
|
||||
or "trainer_encounter"
|
||||
if type(battle.checkpointOrigin) ~= "table"
|
||||
or battle.checkpointOrigin.kind ~= expectedOrigin then
|
||||
return nil, "battle_origin_unsupported",
|
||||
"The battle completion path cannot be reconstructed safely."
|
||||
end
|
||||
if scriptsBusy(game.overworld) then
|
||||
return nil, "script_busy", "A suspended or queued script cannot be checkpointed."
|
||||
end
|
||||
if battle.phase ~= "menu" or nonempty(battle.queue) then
|
||||
return nil, "battle_phase_busy",
|
||||
"Wait for the player command menu before creating a checkpoint."
|
||||
end
|
||||
for _, field in ipairs(BATTLE_BUSY_FIELDS) do
|
||||
if battle[field] ~= nil and battle[field] ~= false then
|
||||
return nil, "battle_phase_busy", "Wait for the current battle action to finish."
|
||||
end
|
||||
end
|
||||
if not battle.player or not battle.enemy or not battle.player.mon
|
||||
or battle.player.mon.hp <= 0
|
||||
or (battle.menuLockedAction and battle:menuLockedAction(battle.player)) then
|
||||
return nil, "battle_phase_busy",
|
||||
"Wait for an ordinary player decision before creating a checkpoint."
|
||||
end
|
||||
for _, battler in ipairs({ battle.player, battle.enemy }) do
|
||||
if not battler.mon or battler.shownHP ~= battler.mon.hp
|
||||
or battler.shownStatus ~= battler.mon.status
|
||||
or battler.drainFloor ~= nil or battler.drainHold ~= nil
|
||||
or battler.faintQueued then
|
||||
return nil, "battle_phase_busy",
|
||||
"Wait for battle status and HP presentation to settle."
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
return BattleSafety
|
||||
@@ -21,6 +21,7 @@ local MoveEffects = require("src.battle.MoveEffects")
|
||||
local Party = require("src.pokemon.Party")
|
||||
local Pokemon = require("src.pokemon.Pokemon")
|
||||
local Runtime = require("src.mods.Runtime")
|
||||
local BattleSafety = require("src.battle.BattleSafety")
|
||||
local Screens = require("src.ui.Screens")
|
||||
local Status = require("src.battle.Status")
|
||||
local Timing = require("src.core.Timing")
|
||||
@@ -1942,6 +1943,17 @@ function BattleState:update(dt)
|
||||
self:resolveTurn(locked)
|
||||
return
|
||||
end
|
||||
-- START has no vanilla action at the settled ordinary player-decision
|
||||
-- boundary. A tool mod may claim this semantic auxiliary action through
|
||||
-- the public hook, receiving only game plus a data-only kind. The shared
|
||||
-- safety predicate keeps every unsupported/forced/animated phase inert.
|
||||
if input:wasPressed("start") and Runtime.wantsHook("battle.menu_auxiliary") then
|
||||
local safe = BattleSafety.inspect(self.game, self)
|
||||
if safe and Runtime.call("battle.menu_auxiliary", function() return false end,
|
||||
self.game, { kind = self.kind }) == true then
|
||||
return
|
||||
end
|
||||
end
|
||||
local col = (self.menuIndex - 1) % 2
|
||||
local row = math.floor((self.menuIndex - 1) / 2)
|
||||
if input:wasPressed("left") then
|
||||
|
||||
+5
-56
@@ -7,6 +7,7 @@ local Version = require("src.core.Version")
|
||||
local BattleState = require("src.battle.BattleState")
|
||||
local BattleCheckpoint = require("src.core.BattleCheckpoint")
|
||||
local ModRuntime = require("src.mods.Runtime")
|
||||
local BattleSafety = require("src.battle.BattleSafety")
|
||||
|
||||
local Checkpoint = {}
|
||||
|
||||
@@ -36,61 +37,9 @@ local function scriptsBusy(ow)
|
||||
or nonempty(ow.scriptMoves)
|
||||
end
|
||||
|
||||
local BATTLE_BUSY_FIELDS = {
|
||||
"current", "afterQueue", "nextInsert", "pendingHit", "waitingUI",
|
||||
"waitingSound", "waitFrames", "draining", "animPlaying", "growIn",
|
||||
"introSlide", "ghostReveal", "mimicCtx", "mimicMoves", "result",
|
||||
}
|
||||
|
||||
local function inspectBattle(ow, battle)
|
||||
if battle.kind == "link" then
|
||||
return refusal("battle", "link_battle_unsupported",
|
||||
"Network battles cannot be checkpointed.")
|
||||
end
|
||||
if battle.safari or battle.ghost or battle.scopeReveal or battle.demo
|
||||
or battle.noCatch then
|
||||
return refusal("battle", "battle_variant_unsupported",
|
||||
"This battle variant does not have a checkpoint contract.")
|
||||
end
|
||||
if battle.kind ~= "wild" and battle.kind ~= "trainer" then
|
||||
return refusal("battle", "battle_variant_unsupported",
|
||||
"This battle kind does not have a checkpoint contract.")
|
||||
end
|
||||
local origin = battle.checkpointOrigin
|
||||
local expectedOrigin = battle.kind == "wild" and "wild_encounter"
|
||||
or "trainer_encounter"
|
||||
if type(origin) ~= "table" or origin.kind ~= expectedOrigin then
|
||||
return refusal("battle", "battle_origin_unsupported",
|
||||
"The battle completion path cannot be reconstructed safely.")
|
||||
end
|
||||
if scriptsBusy(ow) then
|
||||
return refusal("battle", "script_busy",
|
||||
"A suspended or queued script cannot be checkpointed.")
|
||||
end
|
||||
if battle.phase ~= "menu" or nonempty(battle.queue) then
|
||||
return refusal("battle", "battle_phase_busy",
|
||||
"Wait for the player command menu before creating a checkpoint.")
|
||||
end
|
||||
for _, field in ipairs(BATTLE_BUSY_FIELDS) do
|
||||
if battle[field] ~= nil and battle[field] ~= false then
|
||||
return refusal("battle", "battle_phase_busy",
|
||||
"Wait for the current battle action to finish.")
|
||||
end
|
||||
end
|
||||
if not battle.player or not battle.enemy or battle.player.mon.hp <= 0
|
||||
or (battle.menuLockedAction and battle:menuLockedAction(battle.player)) then
|
||||
return refusal("battle", "battle_phase_busy",
|
||||
"Wait for an ordinary player decision before creating a checkpoint.")
|
||||
end
|
||||
for _, battler in ipairs({ battle.player, battle.enemy }) do
|
||||
if battler.shownHP ~= battler.mon.hp
|
||||
or battler.shownStatus ~= battler.mon.status
|
||||
or battler.drainFloor ~= nil or battler.drainHold ~= nil
|
||||
or battler.faintQueued then
|
||||
return refusal("battle", "battle_phase_busy",
|
||||
"Wait for battle status and HP presentation to settle.")
|
||||
end
|
||||
end
|
||||
local function inspectBattle(game, battle)
|
||||
local allowed, reason, message = BattleSafety.inspect(game, battle)
|
||||
if not allowed then return refusal("battle", reason, message) end
|
||||
return { canCapture = true, canRestore = true, kind = "battle" }
|
||||
end
|
||||
|
||||
@@ -109,7 +58,7 @@ function Checkpoint.inspect(game)
|
||||
end
|
||||
local top = game.stack and game.stack.top and game.stack:top()
|
||||
if getmetatable(top) == BattleState then
|
||||
return inspectBattle(ow, top)
|
||||
return inspectBattle(game, top)
|
||||
end
|
||||
if top ~= ow then
|
||||
return refusal("overworld", "screen_busy",
|
||||
|
||||
Reference in New Issue
Block a user