mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-22 13:36:14 +02:00
c23f82efdd
CLOSES #1663, CLOSES #1664, CLOSES #1665, CLOSES #1666, CLOSES #1667, CLOSES #1668, CLOSES #1670, CLOSES #1672
172 lines
6.8 KiB
Lua
172 lines
6.8 KiB
Lua
-- BIDE on the real Gold battle screen: the PP counter in the move list, and
|
|
-- the lock that keeps a storing mon on the move it started (#1664, #1665).
|
|
--
|
|
-- POKEPORT_GAME=gold POKEPORT_DRIVER=tests/drivers/gold_bide_lock_bug1664_1665.lua \
|
|
-- POKEPORT_SHOT_DIR=/tmp/gold-bide love .
|
|
--
|
|
-- data/moves/effects.asm:795-800 puts `storeenergy` ahead of `doturn`, and
|
|
-- BattleCommand_DoTurn masks SUBSTATUS_BIDE out of the PP spend
|
|
-- (engine/battle/effect_commands.asm:977-979), so the whole three-turn Bide
|
|
-- costs the one PP the opening turn paid. The shots are the point: the
|
|
-- number beside BIDE in the move list must read the same on 01, 02 and 03.
|
|
--
|
|
-- The lock is ParsePlayerAction's own arm (engine/battle/core.asm:569-576),
|
|
-- which sits INSIDE the FIGHT branch and skips MoveSelectionScreen only -- so
|
|
-- the 2x2 menu still opens, a switch is still legal, and using an item runs
|
|
-- .reset_bide (:627-629) and CANCELS the store. Block 2 submits TACKLE in
|
|
-- the middle of a Bide on purpose: the engine has to answer with the Bide.
|
|
--
|
|
-- NOT covered here, and deliberately: src/ui/gen2/BattleState.lua still draws
|
|
-- the move list on a storing turn. The cart jumps past it. That half is a
|
|
-- screen change and is called out in the fix report rather than faked.
|
|
local U = require("tests.drivers.util")
|
|
|
|
local Mon = require("src.battle.gen2.Mon")
|
|
|
|
local function battleScreen(game)
|
|
for _ = 1, 900 do
|
|
local top = game.stack:top()
|
|
if top and top.battle then return top end
|
|
U.wait(1)
|
|
end
|
|
error("battle screen never came up")
|
|
end
|
|
|
|
local function drain(game, screen, frames)
|
|
for _ = 1, (frames or 400) do
|
|
if screen.phase == "menu" and #screen.queue == 0 and not screen.anim then
|
|
return true
|
|
end
|
|
U.tap(game, "a")
|
|
U.wait(3)
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function giveMoves(mon, game, moves)
|
|
mon.moves = {}
|
|
for i, id in ipairs(moves) do
|
|
local def = assert(game.data.moves[id], id .. " is not in moves.lua")
|
|
mon.moves[i] = { id = id, pp = def.pp, maxPp = def.pp }
|
|
end
|
|
return mon
|
|
end
|
|
|
|
local function newWild(game, species, level, moves)
|
|
local mon = Mon.new(game.data, species, level)
|
|
if moves then giveMoves(mon, game, moves) end
|
|
return mon
|
|
end
|
|
|
|
return function(game)
|
|
local out = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/gold-bide"
|
|
U.wait(45)
|
|
local world = game.world
|
|
assert(world and world.map, "gold world did not boot")
|
|
local failures = {}
|
|
local function check(ok, what)
|
|
print((ok and "[ok] " or "[FAIL] ") .. what)
|
|
if not ok then failures[#failures + 1] = what end
|
|
end
|
|
local function pp(mon, slot) return mon.moves[slot].pp end
|
|
|
|
-- ------------------------------------------------------------------ 1
|
|
-- One Bide, start to finish, with the move list open between turns. The
|
|
-- foe is a SPLASH-only RATTATA so nothing interrupts the store.
|
|
local player = Mon.new(game.data, "SNORLAX", 40)
|
|
giveMoves(player, game, { "BIDE", "TACKLE" })
|
|
game.save.party = { player }
|
|
game.save.inventory = { POTION = 5 }
|
|
local foe = newWild(game, "RATTATA", 20, { "TACKLE" })
|
|
assert(world:startBattle({ wild = foe }), "startBattle failed")
|
|
local screen = battleScreen(game)
|
|
drain(game, screen, 200)
|
|
local opening = pp(player, 1)
|
|
print(("[driver] BIDE opens at %d PP"):format(opening))
|
|
|
|
screen:submit({ kind = "move", move = "BIDE" })
|
|
drain(game, screen, 400)
|
|
U.shot(game, out .. "/01-selected.png")
|
|
local afterSelect = pp(player, 1)
|
|
print(("[driver] after the SELECTION turn: %d PP"):format(afterSelect))
|
|
check(afterSelect == opening - 1,
|
|
"the opening turn pays its one PP through doturn")
|
|
check(screen.battle:forcedMove(player) == "BIDE",
|
|
"and ParsePlayerAction's bide arm holds the FIGHT choice")
|
|
|
|
-- The middle of the store, with TACKLE deliberately submitted: the cart
|
|
-- never re-reads the move list, so wCurPlayerMove is still the BIDE.
|
|
local tackleBefore = pp(player, 2)
|
|
screen:submit({ kind = "move", move = "TACKLE" })
|
|
drain(game, screen, 400)
|
|
U.shot(game, out .. "/02-storing.png")
|
|
print(("[driver] after a STORING turn: BIDE %d PP, TACKLE %d PP")
|
|
:format(pp(player, 1), pp(player, 2)))
|
|
check(pp(player, 1) == afterSelect, "a storing turn spends no PP at all")
|
|
check(pp(player, 2) == tackleBefore,
|
|
"and the move the menu offered was never run")
|
|
|
|
-- The release. Whatever the roll, the store is two or three turns
|
|
-- (UnleashEnergy's `BattleRandom / and 1 / inc a / inc a`,
|
|
-- move_effects/bide.asm:88-92), so keep pressing FIGHT until it lets go.
|
|
local foeBefore = foe.hp
|
|
for _ = 1, 3 do
|
|
if not screen.battle:forcedMove(player) then break end
|
|
screen:submit({ kind = "move", move = "BIDE" })
|
|
drain(game, screen, 400)
|
|
end
|
|
U.shot(game, out .. "/03-unleashed.png")
|
|
print(("[driver] after the RELEASE: %d PP, foe %d -> %d HP")
|
|
:format(pp(player, 1), foeBefore, foe.hp))
|
|
check(pp(player, 1) == afterSelect,
|
|
"the whole Bide cost exactly one PP, the way the cart charges it")
|
|
check(screen.battle:forcedMove(player) == nil, "and the lock is released")
|
|
for _ = 1, 400 do
|
|
if not game.stack:top() or not game.stack:top().battle then break end
|
|
U.tap(game, "a")
|
|
U.wait(3)
|
|
end
|
|
U.wait(60)
|
|
|
|
-- ------------------------------------------------------------------ 2
|
|
-- .reset_bide: a nonzero wBattlePlayerAction that is not
|
|
-- BATTLEPLAYERACTION_SWITCH clears SUBSTATUS_BIDE (core.asm:572-573,
|
|
-- :627-629), so opening the PACK mid-store throws the stored damage away.
|
|
local bider = Mon.new(game.data, "SNORLAX", 40)
|
|
giveMoves(bider, game, { "BIDE", "TACKLE" })
|
|
bider.hp = math.max(1, bider.hp - 30)
|
|
game.save.party = { bider }
|
|
game.save.inventory = { POTION = 5 }
|
|
local foe2 = newWild(game, "RATTATA", 20, { "TACKLE" })
|
|
assert(world:startBattle({ wild = foe2 }), "startBattle failed")
|
|
screen = battleScreen(game)
|
|
drain(game, screen, 200)
|
|
screen:submit({ kind = "move", move = "BIDE" })
|
|
drain(game, screen, 400)
|
|
check(screen.battle:forcedMove(bider) == "BIDE", "the second Bide is up")
|
|
screen:useItem("POTION")
|
|
drain(game, screen, 400)
|
|
U.shot(game, out .. "/04-item-cancelled.png")
|
|
print("[driver] after the PACK: forcedMove="
|
|
.. tostring(screen.battle:forcedMove(bider)))
|
|
check(screen.battle:forcedMove(bider) == nil,
|
|
"using an item cancels the Bide, as .reset_bide does")
|
|
check(screen.battle:volatile(bider).bideStored == nil,
|
|
"and the damage it had banked goes with it")
|
|
for _ = 1, 400 do
|
|
if not game.stack:top() or not game.stack:top().battle then break end
|
|
U.tap(game, "a")
|
|
U.wait(3)
|
|
end
|
|
|
|
print("[driver] NOTE: the move list is still drawn on a storing turn; the "
|
|
.. "cart's bide arm skips MoveSelectionScreen and that half lives in "
|
|
.. "src/ui/gen2/BattleState.lua")
|
|
if #failures > 0 then
|
|
for _, what in ipairs(failures) do print("[FAIL] " .. what) end
|
|
error(#failures .. " bide checks failed")
|
|
end
|
|
print("[driver] all bide checks passed")
|
|
print("[driver] shots in " .. out)
|
|
end
|