mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-22 21:46:51 +02:00
Fix Gen 1/2 battle, menu, PC and audio parity gaps; wire luacheck into CI
CLOSES #1484, CLOSES #1486, CLOSES #1513, CLOSES #1517, CLOSES #1556, CLOSES #1564, CLOSES #1567
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
-- #1513: exp credit has to be per THIS appearance of the enemy's active mon.
|
||||
-- ResetBattleParticipants falls through into AddBattleParticipant
|
||||
-- (engine/battle/core.asm:3033 and :3037), and AI_Switch farcalls it right
|
||||
-- after EnemySwitch (engine/battle/ai/items.asm:697), so the moment the rival
|
||||
-- rotates, the only mon still credited is the one the player has on the field.
|
||||
--
|
||||
-- POKEPORT_GAME=gold POKEPORT_TOUCH=0 POKEPORT_IDENTITY=gold-dev \
|
||||
-- POKEPORT_DRIVER=tests/drivers/gold_exp_participants_bug1513.lua love .
|
||||
--
|
||||
-- What to look for, in order:
|
||||
-- 00 CYNDAQUIL is out against JOEY's GEODUDE.
|
||||
-- 01 TOTODILE has been switched in through the real party menu, so BOTH
|
||||
-- party mons have now "met" GEODUDE.
|
||||
-- 02 "JOEY withdrew GEODUDE!" -- the rotation the ticket is about.
|
||||
-- 03 the exp lines for the KO of the mon that came IN (PIDGEY). Only
|
||||
-- TOTODILE may be named here. A "CYNDAQUIL gained N EXP. Points!" line
|
||||
-- in this shot is the bug.
|
||||
-- 04 the party screen: CYNDAQUIL's level and EXP bar are pixel-identical to
|
||||
-- shot 01's, TOTODILE's have moved.
|
||||
-- 05 GEODUDE comes back out and is KO'd. CYNDAQUIL still gains nothing --
|
||||
-- that half is correct cart behavior (ram/wram.asm:775-776, "All bits
|
||||
-- cleared if the enemy faints") and must NOT be "fixed".
|
||||
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 runToPhase(game, screen, phase, frames)
|
||||
for _ = 1, (frames or 600) do
|
||||
if screen.phase == phase then return true end
|
||||
if screen.battle.over then return false end
|
||||
U.tap(game, "a")
|
||||
U.wait(3)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Page the queue out, shooting the first box that carries `needle`.
|
||||
local function pageUntilMenu(game, screen, out, needles)
|
||||
local shot = {}
|
||||
for _ = 1, 1200 do
|
||||
local message = screen.message
|
||||
if message then
|
||||
for needle, path in pairs(needles) do
|
||||
if not shot[needle] and message:find(needle, 1, true) then
|
||||
U.shot(game, out .. "/" .. path)
|
||||
shot[needle] = message
|
||||
end
|
||||
end
|
||||
end
|
||||
if screen.phase == "menu" or screen.phase == "done"
|
||||
or screen.battle.over then
|
||||
break
|
||||
end
|
||||
U.tap(game, "a")
|
||||
U.wait(3)
|
||||
end
|
||||
return shot
|
||||
end
|
||||
|
||||
return function(game)
|
||||
local out = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/gold-bug1513"
|
||||
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 lead = Mon.new(game.data, "CYNDAQUIL", 20)
|
||||
lead.moves = { { id = "TACKLE", pp = 35, maxPp = 35 } }
|
||||
local bench = Mon.new(game.data, "TOTODILE", 20)
|
||||
bench.moves = { { id = "TACKLE", pp = 35, maxPp = 35 } }
|
||||
game.save.party = { lead, bench }
|
||||
|
||||
local foe1 = Mon.new(game.data, "GEODUDE", 8)
|
||||
foe1.moves = { { id = "TACKLE", pp = 35, maxPp = 35 } }
|
||||
local foe2 = Mon.new(game.data, "PIDGEY", 8)
|
||||
foe2.moves = { { id = "TACKLE", pp = 35, maxPp = 35 } }
|
||||
-- attributes[6] is the low byte of TRNATTR_AI_ITEM_SWITCH: OFTEN
|
||||
-- (src/battle/gen2/Ai.lua:1307). The real Rival1 class is SWITCH_SOMETIMES
|
||||
-- (pokegold:data/trainers/attributes.asm), the same path at a lower roll.
|
||||
assert(world:startBattle({ trainer = { class = "YOUNGSTER", name = "JOEY",
|
||||
party = { foe1, foe2 }, attributes = { 0, 0, 0, 0, 0, 0x01, 0 } } }),
|
||||
"trainer startBattle failed")
|
||||
local screen = battleScreen(game)
|
||||
assert(runToPhase(game, screen, "menu", 400), "never reached the menu")
|
||||
U.shot(game, out .. "/00-lead-out.png")
|
||||
|
||||
------------------------------------------------- the player's own switch
|
||||
-- Through the real party menu, so the human sees the same path they used.
|
||||
screen:chooseMenu("party")
|
||||
for _ = 1, 240 do
|
||||
if screen.battle.player == bench then break end
|
||||
local top = game.stack:top()
|
||||
if top ~= screen then
|
||||
U.tap(game, "down")
|
||||
U.wait(3)
|
||||
U.tap(game, "a")
|
||||
U.wait(6)
|
||||
U.tap(game, "a")
|
||||
U.wait(6)
|
||||
else
|
||||
U.wait(2)
|
||||
end
|
||||
end
|
||||
check(screen.battle.player == bench, "TOTODILE was switched in")
|
||||
check(screen.battle.participants[1] and screen.battle.participants[2],
|
||||
"both party mons are credited against GEODUDE")
|
||||
pageUntilMenu(game, screen, out, {})
|
||||
U.shot(game, out .. "/01-switched.png")
|
||||
print(("[driver] before the rotation: cyndaquil %d totodile %d")
|
||||
:format(lead.experience, bench.experience))
|
||||
local leadExp = lead.experience
|
||||
local benchExp = bench.experience
|
||||
|
||||
------------------------------------------------------- the AI's rotation
|
||||
-- Perish Song at one turn left is CheckAbleToSwitch's maximum score, the
|
||||
-- cheapest way to make the AI commit to the rotation on demand.
|
||||
screen.battle:volatile(screen.battle.enemy).perish = 1
|
||||
screen:submit({ kind = "move", move = "TACKLE" })
|
||||
local seen = pageUntilMenu(game, screen, out, {
|
||||
["withdrew"] = "02-rival-withdrew.png",
|
||||
["gained"] = "03-exp-lines.png",
|
||||
})
|
||||
check(seen["withdrew"] ~= nil, "JOEY withdrew GEODUDE")
|
||||
check(screen.battle.enemy == foe2, "and sent PIDGEY out")
|
||||
check(screen.battle.participants[1] == nil,
|
||||
"CYNDAQUIL lost its credit when the rival rotated")
|
||||
check(screen.battle.participants[2] == true,
|
||||
"and TOTODILE, the mon on the field, kept it")
|
||||
|
||||
------------------------------------------------------------ the KO payout
|
||||
if not screen.battle.over and (foe2.hp or 0) > 0 then
|
||||
if screen.phase ~= "menu" then
|
||||
runToPhase(game, screen, "menu", 400)
|
||||
end
|
||||
foe2.hp = 1
|
||||
screen:submit({ kind = "move", move = "TACKLE" })
|
||||
local paid = pageUntilMenu(game, screen, out, {
|
||||
["gained"] = "03-exp-lines.png",
|
||||
})
|
||||
if paid["gained"] then print("[driver] exp box: " .. paid["gained"]) end
|
||||
end
|
||||
print(("[driver] after the PIDGEY KO: cyndaquil %d totodile %d")
|
||||
:format(lead.experience, bench.experience))
|
||||
check(lead.experience == leadExp,
|
||||
"CYNDAQUIL earned NOTHING from the mon it never faced")
|
||||
check(bench.experience > benchExp, "TOTODILE was paid for it")
|
||||
|
||||
if screen.phase == "menu" then
|
||||
screen:chooseMenu("party")
|
||||
U.wait(20)
|
||||
U.shot(game, out .. "/04-party-exp.png")
|
||||
U.tap(game, "b")
|
||||
U.wait(10)
|
||||
if game.stack:top() ~= screen then
|
||||
U.tap(game, "b")
|
||||
U.wait(10)
|
||||
end
|
||||
end
|
||||
|
||||
------------------------------------------- GEODUDE comes back and faints
|
||||
-- Correct cart behavior: no residual credit for a previous appearance.
|
||||
leadExp = lead.experience
|
||||
benchExp = bench.experience
|
||||
if not screen.battle.over then
|
||||
runToPhase(game, screen, "menu", 600)
|
||||
if screen.phase == "menu" and screen.battle.enemy == foe1 then
|
||||
foe1.hp = 1
|
||||
screen:submit({ kind = "move", move = "TACKLE" })
|
||||
pageUntilMenu(game, screen, out, { ["gained"] = "05-geodude-ko.png" })
|
||||
print(("[driver] after the GEODUDE KO: cyndaquil %d totodile %d")
|
||||
:format(lead.experience, bench.experience))
|
||||
check(lead.experience == leadExp,
|
||||
"CYNDAQUIL still earns nothing when GEODUDE comes back (correct)")
|
||||
check(bench.experience > benchExp, "and TOTODILE takes the whole share")
|
||||
else
|
||||
print("[driver] GEODUDE never came back out; second half skipped")
|
||||
end
|
||||
end
|
||||
|
||||
if #failures > 0 then
|
||||
error(#failures .. " exp participant checks failed")
|
||||
end
|
||||
print("[driver] #1513 fixed; shots in " .. out)
|
||||
end
|
||||
Reference in New Issue
Block a user