Files
gen1recomp/tests/parity_rocket3_sight_bug1814.lua
T
2026-08-27 07:11:43 -04:00

86 lines
3.8 KiB
Lua

-- Parity: Rocket3 in the Hideout B4F engages on sight, and the LIFT KEY
-- still drops after a sight-triggered battle (#1814).
--
-- RocketHideout4TrainerHeader2 is `trainer EVENT_..., 1, ...`
-- (scripts/RocketHideoutB4F.asm:95-96) and the `trainer` macro stores arg 2
-- as `db \2 << 4` (macros/scripts/maps.asm:107-127), so CheckSpriteCanSeePlayer
-- (engine/overworld/trainer_sight.asm:257-262) engages him from one tile away
-- through SCRIPT_ROCKETHIDEOUTB4F_DEFAULT (asm:42, home/trainers.asm:129).
-- The port used to skip any trainer with a hand-ported talk script; only an
-- explicit `noSight` opt-out may do that now. His LIFT KEY lives in
-- Rocket3AfterBattleText (asm:189-199), i.e. the next talk, so a sight battle
-- does not lose it.
--
-- Self-contained; run via `luajit tests/parity_rocket3_sight_bug1814.lua`.
package.path = "./?.lua;./?/init.lua;" .. package.path
if not _G.love then _G.love = require("tests.love_stub") end
local S = require("tests.harness").suite("parity rocket3 sight")
local check, eq = S.check, S.eq
local MAP = "ROCKET_HIDEOUT_B4F"
local TEXT = "TEXT_ROCKETHIDEOUTB4F_ROCKET3"
-- the sight range the engine reads
local headers = dofile("data/generated/trainer_headers.lua")
local h = headers.RocketHideoutB4F and headers.RocketHideoutB4F[4]
check(h ~= nil, "RocketHideoutB4F header 4 is extracted")
eq(h and h.range, 1, "Rocket3's view range is one tile")
local maps = dofile("data/generated/maps.lua")
local rocket3
for _, o in ipairs(maps[MAP].objects or {}) do
if o.text == TEXT then rocket3 = o end
end
check(rocket3 ~= nil, "Rocket3 is object " .. TEXT)
eq(rocket3 and rocket3.index, 4, "he is object index 4, the header's row")
check(rocket3 and rocket3.trainerClass ~= nil, "and he is a trainer sprite")
eq(rocket3 and rocket3.movement, "STAY", "he holds his tile")
eq(rocket3 and rocket3.range, "DOWN", "facing down, so the sight line is below him")
eq(rocket3 and rocket3.x, 11, "standing at x 11")
eq(rocket3 and rocket3.y, 2, "standing at y 2")
-- the gate the fix uses: having a talk script is NOT an opt-out; only an
-- explicit noSight set is
local story3 = dofile("data/scripts/story3.lua")
local hideout = story3[MAP]
check(hideout ~= nil and hideout.talk[TEXT] ~= nil,
"Rocket3 still has his hand-ported talk script")
check(hideout.noSight == nil or hideout.noSight[TEXT] ~= true,
"and he is not opted out of CheckFightingMapTrainers")
-- the LIFT KEY half: after the battle -- however it started -- the next talk
-- reveals the ball
-- put the real module back at the bottom: the tier dofiles every parity file
-- into one process, so a stub left behind here breaks every later suite
local realTextBox = package.loaded["src.render.TextBox"]
package.loaded["src.render.TextBox"] = {
new = function(_, text, done) return { text = text, done = done } end,
soundOpts = function() return nil end,
substitute = function(_, t) return t end,
}
local pushed
local game = {
data = { text = { _RocketHideoutB4FRocket3AfterBattleText = "Oh no!" } },
save = { flags = {} },
stack = { push = function(_, box) pushed = box end },
}
local ow = {
map = { id = MAP, def = { objects = {} } },
npcs = {}, entities = {},
trainerDefeated = function() return true end,
}
local finished = false
hideout.talk[TEXT](game, ow, { def = rocket3 }, function() finished = true end)
check(pushed ~= nil, "talking after the win prints the after-battle text")
if pushed and pushed.done then pushed.done() end
check(game.save.flags.EVENT_ROCKET_DROPPED_LIFT_KEY == true,
"EVENT_ROCKET_DROPPED_LIFT_KEY is set")
local toggles = game.save.objectToggles and game.save.objectToggles[MAP]
check(toggles and toggles.ROCKETHIDEOUTB4F_LIFT_KEY == true,
"the LIFT KEY ball is shown")
check(finished, "and the talk hands control back")
package.loaded["src.render.TextBox"] = realTextBox
S.finish()