mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 00:10:56 +02:00
188 lines
7.7 KiB
Lua
188 lines
7.7 KiB
Lua
-- Parity (#552, a regression of #105): the Rocket Hideout B4F grunt drops
|
|
-- the LIFT KEY on Yellow too, and the ball he drops reaches the bag.
|
|
--
|
|
-- Oracle: pokeyellow/scripts/RocketHideoutB4F.asm. Yellow replaces
|
|
-- Rocket1/Rocket2 with Jessie & James and leaves one grunt, spelled
|
|
-- ROCKETHIDEOUTB4F_ROCKET / TEXT_ROCKETHIDEOUTB4F_ROCKET where Red and
|
|
-- Blue say ROCKET3, so the hand-ported Red/Blue key never matched and the
|
|
-- talk fell through to the generic trainer path: battle, no drop, no key,
|
|
-- and the B1F elevator stays locked for good. Yellow also moves the drop
|
|
-- off the follow-up talk -- RocketHideoutB4FRocketEndBattleText is
|
|
-- text_promptbutton + text_asm, so its SetEvent EVENT_ROCKET_DROPPED_LIFT_KEY
|
|
-- and ShowObject TOGGLE_ROCKET_HIDEOUT_B4F_ITEM_5 run the moment the
|
|
-- battle ends, not on the next conversation.
|
|
--
|
|
-- The Red/Blue half stays in tests/parity_lift_key.lua; this one owns the
|
|
-- Yellow spelling and the pickup that #105 was originally about.
|
|
--
|
|
-- Self-contained: `luajit tests/parity_lift_key_yellow_bug552.lua`; also
|
|
-- globbed by tests/run_tests.lua.
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
if not _G.love then _G.love = require("tests.love_stub") end
|
|
local Data = require("src.core.Data")
|
|
if not (Data.maps and Data.maps.PALLET_TOWN) then Data:load() end
|
|
|
|
local S = require("tests.harness").suite("parity Yellow lift key (#552)")
|
|
local check, eq = S.check, S.eq
|
|
|
|
local mapScripts = require("data.scripts.init")
|
|
local OverworldState = require("src.world.OverworldController")
|
|
local SaveData = require("src.core.SaveData")
|
|
local TextBox = require("src.render.TextBox")
|
|
require("src.render.Font").load(Data)
|
|
|
|
local MAP = "ROCKET_HIDEOUT_B4F"
|
|
local BALL = "ROCKETHIDEOUTB4F_LIFT_KEY"
|
|
|
|
local ballObj
|
|
for _, o in ipairs(Data.maps[MAP].objects) do
|
|
if o.name == BALL then ballObj = o end
|
|
end
|
|
check(ballObj ~= nil, "the LIFT KEY ball is an object_event on B4F")
|
|
check(ballObj and ballObj.hidden == true, "it starts hidden")
|
|
eq(ballObj and ballObj.item, "LIFT_KEY", "and it carries the LIFT KEY")
|
|
|
|
-- Yellow's object_event names the same grunt trainer party the Red/Blue
|
|
-- Rocket3 does (OPP_ROCKET, 18), so only the text id differs
|
|
local script = mapScripts.talkScript(MAP, "TEXT_ROCKETHIDEOUTB4F_ROCKET")
|
|
check(type(script) == "function",
|
|
"TEXT_ROCKETHIDEOUTB4F_ROCKET resolves to a hand-ported handler (#552)")
|
|
check(type(mapScripts.talkScript(MAP, "TEXT_ROCKETHIDEOUTB4F_ROCKET3")) == "function",
|
|
"the Red/Blue ROCKET3 handler is still registered alongside it")
|
|
|
|
-- ------------------------------------------------------------------ setup
|
|
local function newWorld(defeated)
|
|
local save = SaveData.newGame()
|
|
save.objectToggles = {}
|
|
save.itemsTaken = {}
|
|
save.defeatedTrainers = {}
|
|
local pushed = {}
|
|
local game = {
|
|
data = Data,
|
|
save = save,
|
|
stack = { push = function(_, state) pushed[#pushed + 1] = state end },
|
|
}
|
|
local npc = {
|
|
id = MAP .. "_obj_4",
|
|
def = { name = "ROCKETHIDEOUTB4F_ROCKET", index = 4,
|
|
text = "TEXT_ROCKETHIDEOUTB4F_ROCKET",
|
|
trainerClass = "OPP_ROCKET", trainerParty = 18 },
|
|
}
|
|
if defeated then save.defeatedTrainers[npc.id] = true end
|
|
local ow = {
|
|
map = { id = MAP, def = Data.maps[MAP] },
|
|
npcs = {}, entities = {},
|
|
engagements = 0,
|
|
trainerDefeated = function(_, who)
|
|
return save.defeatedTrainers[who.id] == true
|
|
end,
|
|
}
|
|
-- engageTrainer writes the win before it runs its callback, which is
|
|
-- where the end-battle text's SetEvent + ShowObject belong
|
|
ow.engageTrainer = function(self, who, after)
|
|
self.engagements = self.engagements + 1
|
|
save.defeatedTrainers[who.id] = true
|
|
if after then after() end
|
|
end
|
|
return game, ow, npc, save, pushed
|
|
end
|
|
|
|
-- ------------------------------------------- winning the battle drops it
|
|
do
|
|
local game, ow, npc, save, pushed = newWorld(false)
|
|
local done = false
|
|
check(not OverworldState.objectVisible(save, MAP, ballObj),
|
|
"the ball is not on the floor before the fight")
|
|
script(game, ow, npc, function() done = true end)
|
|
|
|
eq(ow.engagements, 1, "an undefeated grunt still starts his battle")
|
|
check(save.flags.EVENT_ROCKET_DROPPED_LIFT_KEY == true,
|
|
"EVENT_ROCKET_DROPPED_LIFT_KEY is set the moment the battle ends")
|
|
eq(save.objectToggles[MAP] and save.objectToggles[MAP][BALL], true,
|
|
"ShowObject reveals ROCKETHIDEOUTB4F_LIFT_KEY (TOGGLE_..._ITEM_5)")
|
|
check(OverworldState.objectVisible(save, MAP, ballObj),
|
|
"the LIFT KEY ball is on the floor without a second conversation")
|
|
check(done, "the talk finishes and hands input back")
|
|
eq(#pushed, 0, "the end-battle text is the battle's, not a second box")
|
|
end
|
|
|
|
-- --------------------------------------------- losing it drops nothing
|
|
do
|
|
local game, ow, npc, save = newWorld(false)
|
|
ow.engageTrainer = function(self, _, after)
|
|
self.engagements = self.engagements + 1
|
|
if after then after() end -- blacked out: no win recorded
|
|
end
|
|
script(game, ow, npc, function() end)
|
|
check(not save.flags.EVENT_ROCKET_DROPPED_LIFT_KEY,
|
|
"a lost battle drops no key")
|
|
check(not OverworldState.objectVisible(save, MAP, ballObj),
|
|
"and the ball stays hidden")
|
|
end
|
|
|
|
-- ------------------------------------- later talks only reprint the line
|
|
do
|
|
local game, ow, npc, save, pushed = newWorld(true)
|
|
save.flags.EVENT_ROCKET_DROPPED_LIFT_KEY = true
|
|
save.objectToggles[MAP] = { [BALL] = true }
|
|
local done = false
|
|
script(game, ow, npc, function() done = true end)
|
|
eq(ow.engagements, 0, "a beaten grunt does not re-engage")
|
|
eq(#pushed, 1, "he says his after-battle line")
|
|
eq(pushed[1] and getmetatable(pushed[1]), TextBox, "in a text box")
|
|
if pushed[1] and pushed[1].onDone then pushed[1].onDone() end
|
|
check(done, "closing that box hands input back to the overworld")
|
|
eq(save.objectToggles[MAP][BALL], true, "the ball is not re-toggled")
|
|
end
|
|
|
|
-- ------------------------------------------ the ball itself, #105's half
|
|
-- talkTo's item-ball branch is what turns the revealed object into a bag
|
|
-- entry; without it the fix above only puts a sprite on the floor. Same
|
|
-- `Game` upvalue rewiring tests/parity_D.lua uses for the Safari counters.
|
|
do
|
|
local game, _, _, save, pushed = newWorld(true)
|
|
save.flags.EVENT_ROCKET_DROPPED_LIFT_KEY = true
|
|
save.objectToggles[MAP] = { [BALL] = true }
|
|
|
|
local function bind(fn, upvalue, value)
|
|
local i = 1
|
|
while true do
|
|
local name = debug.getupvalue(fn, i)
|
|
if not name then return false end
|
|
if name == upvalue then debug.setupvalue(fn, i, value); return true end
|
|
i = i + 1
|
|
end
|
|
end
|
|
check(bind(OverworldState.talkTo, "Game", game), "talkTo binds the Game upvalue")
|
|
check(bind(OverworldState.talkTo, "mapScripts", mapScripts),
|
|
"talkTo binds the map-script registry")
|
|
|
|
local ball = {
|
|
id = MAP .. "_obj_" .. ballObj.index,
|
|
def = ballObj,
|
|
cellX = ballObj.x, cellY = ballObj.y,
|
|
}
|
|
local world = setmetatable({
|
|
map = { id = MAP, def = Data.maps[MAP] },
|
|
npcs = { ball }, entities = { ball },
|
|
}, { __index = OverworldState })
|
|
|
|
world:talkTo(ball)
|
|
eq(save.inventory.LIFT_KEY, 1, "picking the ball up puts the LIFT KEY in the bag (#105)")
|
|
eq(save.itemsTaken[ball.id], true, "the ball is marked taken")
|
|
eq(#world.npcs, 0, "and it leaves the floor")
|
|
check(not OverworldState.objectVisible(save, MAP, ballObj),
|
|
"a taken ball does not respawn on the next map load")
|
|
eq(#pushed, 1, "one box: the found-item line")
|
|
local lines = {}
|
|
for _, page in ipairs(pushed[1] and pushed[1].pages or {}) do
|
|
for _, line in ipairs(page) do lines[#lines + 1] = line end
|
|
end
|
|
check(table.concat(lines, " "):find("LIFT KEY", 1, true) ~= nil,
|
|
"the box names the LIFT KEY")
|
|
eq(Data.items.LIFT_KEY and Data.items.LIFT_KEY.keyItem, true,
|
|
"LIFT KEY is a key item, so it cannot be tossed away again")
|
|
end
|
|
|
|
S.finish()
|