mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-22 13:36:14 +02:00
c0f4315cd8
CLOSES #1484, CLOSES #1486, CLOSES #1513, CLOSES #1517, CLOSES #1556, CLOSES #1564, CLOSES #1567
198 lines
7.4 KiB
Lua
198 lines
7.4 KiB
Lua
-- #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
|