mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 00:10:56 +02:00
123 lines
5.1 KiB
Lua
123 lines
5.1 KiB
Lua
-- Driver: the foe's party ball row between a KO and the next send-out (#283).
|
|
-- pokered ReplaceFaintedEnemyMon (engine/battle/core.asm:892-896) callfars
|
|
-- DrawEnemyPokeballs before it falls into EnemySendOut; the port never did.
|
|
-- POKEPORT_DRIVER=tests/drivers/enemy_balls_bug283_test.lua \
|
|
-- POKEPORT_IDENTITY=bug283 POKEPORT_TOUCH=0 POKEPORT_VERSION=red \
|
|
-- SHOT_DIR=/tmp/shots love . (never under POKEPORT_SPEED: audio-timed)
|
|
return function(game)
|
|
local U = dofile("tests/drivers/util.lua")
|
|
local DIR = os.getenv("SHOT_DIR") or "/tmp/shots"
|
|
local Pokemon = require("src.pokemon.Pokemon")
|
|
local BattleState = require("src.battle.BattleState")
|
|
|
|
local function check(label, ok)
|
|
U.log(ok and "PASS" or "FAIL", label)
|
|
return ok
|
|
end
|
|
|
|
-- roster 1 is RATTATA 11 / EKANS 11 in both versions: two slots, so the row
|
|
-- has a KO'd ball, a live one and four empties to tell apart.
|
|
local OPP, ROSTER = "OPP_YOUNGSTER", 1
|
|
|
|
-- ---- preconditions ------------------------------------------------------
|
|
-- Each of these fails silently as "no ball row on screen", same as the bug.
|
|
|
|
check("trainer class " .. OPP .. " is in the data",
|
|
game.data.trainers ~= nil and game.data.trainers[OPP] ~= nil)
|
|
|
|
-- drawBallRow bails out silently when the sheet will not load, and the
|
|
-- underline comes off the $73/$74/$76/$78 HUD glyph pages
|
|
local ballSheet = love.filesystem.getInfo("assets/generated/battle/balls.png")
|
|
check("assets/generated/battle/balls.png is in the cache", ballSheet ~= nil)
|
|
local okImg = pcall(love.graphics.newImage, "assets/generated/battle/balls.png")
|
|
check("the ball sheet actually decodes", okImg)
|
|
check("BattleState:drawBallRow exists",
|
|
type(BattleState.drawBallRow) == "function")
|
|
local HudTiles = require("src.render.HudTiles")
|
|
check("HudTiles.tile is available for the underline",
|
|
type(HudTiles.tile) == "function")
|
|
|
|
-- SHIFT keeps the row up through the whole YES/NO prompt (showEnemyBalls is
|
|
-- not cleared until the send-out act); SET only flashes it for 16 frames.
|
|
game.save.options = game.save.options or {}
|
|
game.save.options.battleStyle = "shift"
|
|
game.save.player.name = "bryan"
|
|
-- two party slots: SHIFT only offers the switch when wPartyCount > 1
|
|
game.save.party = {
|
|
Pokemon.new(game.data, "MEWTWO", 70),
|
|
Pokemon.new(game.data, "CHARIZARD", 50),
|
|
}
|
|
check("a one-shot lead is in the party", game.save.party[1] ~= nil)
|
|
|
|
U.teleport(game, "PALLET_TOWN", 10, 8, "down")
|
|
U.wait(20)
|
|
local ow = game.overworld
|
|
check("overworld is up to push the battle from", ow ~= nil)
|
|
|
|
local ok, battle = pcall(BattleState.newTrainer, game, OPP, ROSTER)
|
|
check("trainer battle constructed", ok and battle ~= nil)
|
|
if not ok then
|
|
U.log("could not start", OPP, "->", tostring(battle))
|
|
while true do coroutine.yield() end
|
|
end
|
|
check("the foe has at least two mons to swap between", #battle.enemyParty >= 2)
|
|
battle.onFinish = function() end
|
|
ow:pushBattle(battle)
|
|
|
|
-- tap A until cond(), polling every frame so a one-frame window is caught
|
|
local function tapUntil(cond, taps, gap)
|
|
for _ = 1, (taps or 60) do
|
|
if cond() then return true end
|
|
U.tap(game, "a")
|
|
for _ = 1, (gap or 6) do
|
|
if cond() then return true end
|
|
U.wait(1)
|
|
end
|
|
end
|
|
return cond()
|
|
end
|
|
|
|
check("reached the FIGHT/PKMN/ITEM/RUN menu",
|
|
tapUntil(function() return battle.phase == "menu" end, 60))
|
|
|
|
-- FIGHT, then the first move: L70 MEWTWO one-shots a L11 RATTATA, so the KO
|
|
-- is the real sequence and not a poked hp value.
|
|
U.tap(game, "a")
|
|
U.wait(10)
|
|
U.tap(game, "a")
|
|
U.wait(10)
|
|
|
|
-- Stop the instant showEnemyBalls goes up: that flag is raised exactly where
|
|
-- ReplaceFaintedEnemyMon calls DrawEnemyPokeballs.
|
|
local reached = tapUntil(function() return battle.showEnemyBalls == true end,
|
|
90, 4)
|
|
check("the foe's first mon was KO'd and the ball row went up (#283)", reached)
|
|
check("the KO'd slot really is fainted",
|
|
battle.enemyParty[1] ~= nil and battle.enemyParty[1].hp <= 0)
|
|
check("a live reserve is still in the row",
|
|
battle.enemyParty[2] ~= nil and battle.enemyParty[2].hp > 0)
|
|
|
|
if reached then
|
|
check("row screenshot reached disk",
|
|
U.shot(game, DIR .. "/bug283_balls_after_ko.png"))
|
|
U.log("captured", DIR .. "/bug283_balls_after_ko.png")
|
|
-- a few more presses put the SHIFT prompt on top of the still-visible row
|
|
tapUntil(function() return battle.showEnemyBalls ~= true end, 3, 20)
|
|
check("row screenshot during the SHIFT prompt reached disk",
|
|
U.shot(game, DIR .. "/bug283_balls_with_prompt.png"))
|
|
U.log("captured", DIR .. "/bug283_balls_with_prompt.png")
|
|
U.log("showEnemyBalls is still up at hand-off:",
|
|
tostring(battle.showEnemyBalls))
|
|
end
|
|
|
|
-- ---- hand off -----------------------------------------------------------
|
|
U.log("The foe's first mon is KO'd and we are paused before the send-out.")
|
|
U.log("Top left should carry six small Poke Balls running right to left from")
|
|
U.log("(64,16) on a HUD rule: one crossed out, one solid, four empty.")
|
|
U.log("#283 was that corner empty. Press A, the row clears on the send-out.")
|
|
|
|
while true do
|
|
coroutine.yield()
|
|
end
|
|
end
|