Merge pull request #1399 from AverageConsumer/codex/mod-battle-special-intents

This commit is contained in:
bryanthaboi
2026-08-16 12:09:03 -04:00
committed by GitHub
4 changed files with 95 additions and 12 deletions
+8 -2
View File
@@ -287,10 +287,16 @@ The shared Red, Blue, Yellow, and Gold intents are:
- `{ kind = "move", slot = 1..4 }` - `{ kind = "move", slot = 1..4 }`
- `{ kind = "back" }` while the move menu is active - `{ kind = "back" }` while the move menu is active
Red, Blue, and Yellow also expose their generation-specific choices:
- `{ kind = "safari", action = "ball" }` (`bait`, `rock`, and `run` are the
other accepted actions)
- `{ kind = "mimic", index = 1 }` using an entry's snapshot `index`
Menu choices and moves use the same engine methods as the native controls; Menu choices and moves use the same engine methods as the native controls;
`party` and `item` open the native screens rather than exposing or duplicating `party` and `item` open the native screens rather than exposing or duplicating
their mutable logic. Tutorial, link, Safari, forced, stale, and covered battle their mutable logic. Tutorial, link, forced, stale, and covered battle states
states refuse these core intents. Use `mod.input` for ordinary text advance. refuse core intents. Use `mod.input` for ordinary text advance.
## Rendering pipelines ## Rendering pipelines
+9 -2
View File
@@ -218,13 +218,20 @@ function BattleAPI:submit(intent)
return nil, "stale battle context" return nil, "stale battle context"
end end
local kind = battle:battleKind() local kind = battle:battleKind()
if kind == "oldman" or kind == "link" or kind == "safari" then if kind == "oldman" or kind == "link" then
return nil, "battle kind is not controllable" return nil, "battle kind is not controllable"
end end
if top ~= battle then return nil, "battle menu is covered" end if top ~= battle then return nil, "battle menu is covered" end
local ok, err local ok, err
if intent.kind == "menu" then if intent.kind == "safari" then
if kind ~= "safari" then return nil, "safari menu is not active" end
ok, err = battle:chooseSafari(intent.action)
elseif kind == "safari" then
return nil, "battle kind is not controllable"
elseif intent.kind == "mimic" then
ok, err = battle:chooseMimic(intent.index)
elseif intent.kind == "menu" then
if battle.phase ~= "menu" then return nil, "battle menu is not active" end if battle.phase ~= "menu" then return nil, "battle menu is not active" end
if not MENU_CHOICES[intent.choice] then if not MENU_CHOICES[intent.choice] then
return nil, "unknown battle menu choice" return nil, "unknown battle menu choice"
+34 -7
View File
@@ -2019,6 +2019,38 @@ function BattleState:cancelMove()
return true return true
end end
local SAFARI_ACTION_INDEX = { ball = 1, bait = 2, rock = 3, run = 4 }
function BattleState:chooseSafari(action)
if self.phase ~= "menu" or not self.safari then
return nil, "safari menu is not active"
end
if self.safari.balls <= 0 then return nil, "no safari balls remain" end
local index = SAFARI_ACTION_INDEX[action]
if not index then return nil, "invalid safari action" end
self.menuIndex = index
self:safariAction(action)
return true
end
function BattleState:chooseMimic(index)
if self.phase ~= "mimicSelect" then
return nil, "mimic menu is not active"
end
if type(index) ~= "number" or index % 1 ~= 0 then
return nil, "invalid mimic slot"
end
local pick = self.mimicMoves and self.mimicMoves[index]
local ctx = self.mimicCtx
if not pick or not ctx then return nil, "invalid mimic slot" end
self.mimicIndex = index
self.mimicMoves, self.mimicCtx = nil, nil
self.phase = "messages"
self.nextInsert = 0 -- the copy's anim + text go to the queue head
self:applyMimic(ctx.user, ctx.target, ctx.moveInst, pick.slot)
return true
end
function BattleState:swapMoves(i, j) function BattleState:swapMoves(i, j)
if i == j then return end if i == j then return end
local moves = self.player.curMoves local moves = self.player.curMoves
@@ -2140,7 +2172,7 @@ function BattleState:update(dt)
self.menuIndex = row * 2 + col + 1 self.menuIndex = row * 2 + col + 1
if input:wasPressed("a") then if input:wasPressed("a") then
require("src.core.Sound").play(self.data, "Press_AB") require("src.core.Sound").play(self.data, "Press_AB")
self:safariAction(({ "ball", "bait", "rock", "run" })[self.menuIndex]) self:chooseSafari(({ "ball", "bait", "rock", "run" })[self.menuIndex])
end end
return return
end end
@@ -2248,12 +2280,7 @@ function BattleState:update(dt)
self.mimicIndex = self.mimicIndex < #moves and self.mimicIndex + 1 or 1 self.mimicIndex = self.mimicIndex < #moves and self.mimicIndex + 1 or 1
elseif input:wasPressed("a") then elseif input:wasPressed("a") then
require("src.core.Sound").play(self.data, "Press_AB") require("src.core.Sound").play(self.data, "Press_AB")
local pick = moves[self.mimicIndex] self:chooseMimic(self.mimicIndex)
local ctx = self.mimicCtx
self.mimicMoves, self.mimicCtx = nil, nil
self.phase = "messages"
self.nextInsert = 0 -- the copy's anim + text go to the queue head
self:applyMimic(ctx.user, ctx.target, ctx.moveInst, pick.slot)
end end
return return
end end
+44 -1
View File
@@ -48,7 +48,7 @@ local battle = {
enemy = { mon = { species = "TESTMON", level = 4, hp = 12, enemy = { mon = { species = "TESTMON", level = 4, hp = 12,
stats = { hp = 12 }, moves = {} }, curTypes = { "NORMAL" }, stages = {} }, stats = { hp = 12 }, moves = {} }, curTypes = { "NORMAL" }, stages = {} },
} }
function battle:battleKind() return "wild" end function battle:battleKind() return self.kind or "wild" end
function battle:effectRecord() return { accuracyChecked = true } end function battle:effectRecord() return { accuracyChecked = true } end
function battle:visibleText() return { "Wild TESTMON appeared!" } end function battle:visibleText() return { "Wild TESTMON appeared!" } end
function battle:menuLockedAction() return nil end function battle:menuLockedAction() return nil end
@@ -63,6 +63,14 @@ function battle:chooseMove(slot)
return true return true
end end
function battle:cancelMove() self.phase = "menu" return true end function battle:cancelMove() self.phase = "menu" return true end
function battle:chooseSafari(action)
self.chosenSafari, self.phase = action, "messages"
return true
end
function battle:chooseMimic(slot)
self.chosenMimic, self.phase = slot, "messages"
return true
end
function battle:catchChance(ball) function battle:catchChance(ball)
return require("src.battle.Catching").chance(ball, self.enemy.mon, return require("src.battle.Catching").chance(ball, self.enemy.mon,
game.data.pokemon[self.enemy.mon.species]) game.data.pokemon[self.enemy.mon.species])
@@ -123,6 +131,18 @@ check(api:submit({ id = 3, revision = back.revision, kind = "back" }),
"Gen 1 accepts move-menu back") "Gen 1 accepts move-menu back")
eq(battle.phase, "menu", "Gen 1 back restores the command menu") eq(battle.phase, "menu", "Gen 1 back restores the command menu")
battle.kind, battle.safari = "safari", { balls = 30 }
local safari = api:snapshot()
check(api:submit({ id = 4, revision = safari.revision,
kind = "safari", action = "rock" }), "Gen 1 accepts a Safari action")
eq(battle.chosenSafari, "rock", "Gen 1 uses the semantic Safari path")
battle.kind, battle.safari = "wild", nil
battle.phase, battle.mimicMoves = "mimicSelect", { { slot = 1 } }
local mimic = api:snapshot()
check(api:submit({ id = 5, revision = mimic.revision,
kind = "mimic", index = 1 }), "Gen 1 accepts a Mimic choice")
eq(battle.chosenMimic, 1, "Gen 1 uses the semantic Mimic path")
local player2 = { species = "CHIKORITA", level = 5, hp = 20, local player2 = { species = "CHIKORITA", level = 5, hp = 20,
maxHp = 21, moves = { { id = "TACKLE", pp = 35, maxPp = 35 } } } maxHp = 21, moves = { { id = "TACKLE", pp = 35, maxPp = 35 } } }
local enemy2 = { species = "RATTATA", level = 3, hp = 12, maxHp = 12, local enemy2 = { species = "RATTATA", level = 3, hp = 12, maxHp = 12,
@@ -228,6 +248,29 @@ do
eq(real.phase, "menu", "native Gen 1 move-menu back still works") eq(real.phase, "menu", "native Gen 1 move-menu back still works")
end end
do
local state = setmetatable({ phase = "menu", safari = { balls = 30 },
menuIndex = 1 }, { __index = Gen1BattleState })
function state:safariAction(action) self.safariChoice = action end
local ok, err = state:chooseSafari("missing")
check(not ok and err == "invalid safari action",
"native Safari rejects an unknown action")
check(state:chooseSafari("rock"), "native Safari choice is accepted")
eq(state.menuIndex, 3, "native Safari cursor follows the semantic choice")
eq(state.safariChoice, "rock", "native Safari action uses the shared path")
state.phase = "mimicSelect"
state.mimicMoves = { { slot = 4 } }
state.mimicCtx = { user = {}, target = {}, moveInst = {} }
function state:applyMimic(_, _, _, slot) self.mimicSlot = slot end
ok, err = state:chooseMimic(2)
check(not ok and err == "invalid mimic slot",
"native Mimic rejects an unknown choice")
check(state:chooseMimic(1), "native Mimic choice is accepted")
eq(state.phase, "messages", "native Mimic choice resumes battle messages")
eq(state.mimicSlot, 4, "native Mimic choice copies the selected move slot")
end
local Loader = require("src.mods.Loader") local Loader = require("src.mods.Loader")
local fs = { read = function() end, getInfo = function() end, local fs = { read = function() end, getInfo = function() end,
getDirectoryItems = function() return {} end } getDirectoryItems = function() return {} end }