mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
228 lines
9.3 KiB
Lua
228 lines
9.3 KiB
Lua
-- Parity: Team Rocket leaves Silph Co, and the president keeps talking (#392).
|
|
--
|
|
-- Oracle: scripts/SilphCo11F.asm SilphCo11FTeamRocketLeavesScript walks
|
|
-- .HideToggleableObjectIDs (TOGGLE_SILPH_CO_2F_2..11F_3 plus the Saffron
|
|
-- street set, which M.SAFFRON_CITY handles) through predef HideObject just
|
|
-- before SetEvent EVENT_BEAT_SILPH_CO_GIOVANNI. The ordinals come from
|
|
-- constants/toggle_constants.asm and name the objects listed per floor in
|
|
-- data/maps/toggleable_objects.asm, so the item balls, the rescued 2F/10F
|
|
-- workers and the 7F rival keep their sprites. HideObject writes
|
|
-- wMissableObjectFlags, which the port keeps in save.objectToggles.
|
|
--
|
|
-- Oracle: scripts/SilphCo11F.asm SilphCo11FSilphPresidentText branches on
|
|
-- EVENT_GOT_MASTER_BALL alone -- nz jumps to .got_item, which prints
|
|
-- _SilphCo11FSilphPresidentMasterBallDescriptionText -- so every later talk
|
|
-- prints something.
|
|
--
|
|
-- Self-contained: `luajit tests/parity_silph_rockets.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 silph rockets")
|
|
local check, eq = S.check, S.eq
|
|
|
|
local MapScripts = require("src.script.MapScripts")
|
|
require("data.scripts.init")
|
|
local Commands = require("src.script.Commands")
|
|
local ScriptRunner = require("src.script.ScriptRunner")
|
|
local OverworldState = require("src.world.OverworldController")
|
|
|
|
-- .HideToggleableObjectIDs, floor by floor, in the order the asm lists them
|
|
local HIDDEN = {
|
|
{ "SILPH_CO_2F", { "SILPHCO2F_SCIENTIST1", "SILPHCO2F_SCIENTIST2",
|
|
"SILPHCO2F_ROCKET1", "SILPHCO2F_ROCKET2" } },
|
|
{ "SILPH_CO_3F", { "SILPHCO3F_ROCKET", "SILPHCO3F_SCIENTIST" } },
|
|
{ "SILPH_CO_4F", { "SILPHCO4F_ROCKET1", "SILPHCO4F_SCIENTIST",
|
|
"SILPHCO4F_ROCKET2" } },
|
|
{ "SILPH_CO_5F", { "SILPHCO5F_ROCKET1", "SILPHCO5F_SCIENTIST",
|
|
"SILPHCO5F_ROCKER", "SILPHCO5F_ROCKET2" } },
|
|
{ "SILPH_CO_6F", { "SILPHCO6F_ROCKET1", "SILPHCO6F_SCIENTIST",
|
|
"SILPHCO6F_ROCKET2" } },
|
|
{ "SILPH_CO_7F", { "SILPHCO7F_ROCKET1", "SILPHCO7F_SCIENTIST",
|
|
"SILPHCO7F_ROCKET2", "SILPHCO7F_ROCKET3" } },
|
|
{ "SILPH_CO_8F", { "SILPHCO8F_ROCKET1", "SILPHCO8F_SCIENTIST",
|
|
"SILPHCO8F_ROCKET2" } },
|
|
{ "SILPH_CO_9F", { "SILPHCO9F_ROCKET1", "SILPHCO9F_SCIENTIST",
|
|
"SILPHCO9F_ROCKET2" } },
|
|
{ "SILPH_CO_10F", { "SILPHCO10F_ROCKET", "SILPHCO10F_SCIENTIST" } },
|
|
{ "SILPH_CO_11F", { "SILPHCO11F_GIOVANNI", "SILPHCO11F_ROCKET1",
|
|
"SILPHCO11F_ROCKET2" } },
|
|
}
|
|
|
|
-- toggleable objects the hide list deliberately skips
|
|
local KEPT = {
|
|
{ "SILPH_CO_2F", "SILPHCO2F_SILPH_WORKER_F" },
|
|
{ "SILPH_CO_3F", "SILPHCO3F_HYPER_POTION" },
|
|
{ "SILPH_CO_5F", "SILPHCO5F_CARD_KEY" },
|
|
{ "SILPH_CO_7F", "SILPHCO7F_RIVAL" },
|
|
{ "SILPH_CO_7F", "SILPHCO7F_TM_SWORDS_DANCE" },
|
|
{ "SILPH_CO_10F", "SILPHCO10F_SILPH_WORKER_F" },
|
|
{ "SILPH_CO_11F", "SILPHCO11F_BEAUTY" },
|
|
}
|
|
|
|
local function objOf(mapId, name)
|
|
for _, o in ipairs(Data.maps[mapId].objects) do
|
|
if o.name == name then return o end
|
|
end
|
|
end
|
|
|
|
local function newSave(beat)
|
|
return {
|
|
flags = beat and { EVENT_BEAT_SILPH_CO_GIOVANNI = true } or {},
|
|
inventory = {}, objectToggles = {}, itemsTaken = {},
|
|
defeatedTrainers = {},
|
|
}
|
|
end
|
|
|
|
-- every name in both lists has to be a real object_event, or the toggle
|
|
-- write lands on a key nothing reads
|
|
for _, floor in ipairs(HIDDEN) do
|
|
for _, name in ipairs(floor[2]) do
|
|
check(objOf(floor[1], name) ~= nil,
|
|
name .. " is an object_event on " .. floor[1])
|
|
end
|
|
end
|
|
for _, kept in ipairs(KEPT) do
|
|
check(objOf(kept[1], kept[2]) ~= nil,
|
|
kept[2] .. " is an object_event on " .. kept[1])
|
|
end
|
|
|
|
-- ---------------------------------------------------------------- hide pass
|
|
-- 11F's onEnter is the whole pass: it runs on the post-battle callback's
|
|
-- floor and repairs saves that beat Giovanni before the list existed
|
|
local function enter(mapId, save)
|
|
local view = MapScripts.get(mapId)
|
|
check(view and type(view.onEnter) == "function",
|
|
mapId .. " has an onEnter hook")
|
|
if view and view.onEnter then view.onEnter({ save = save }, nil) end
|
|
end
|
|
|
|
do
|
|
local save = newSave(true)
|
|
enter("SILPH_CO_11F", save)
|
|
local hidden = 0
|
|
for _, floor in ipairs(HIDDEN) do
|
|
for _, name in ipairs(floor[2]) do
|
|
eq(save.objectToggles[floor[1]] and save.objectToggles[floor[1]][name],
|
|
false, name .. " hidden by the 11F pass")
|
|
check(not OverworldState.objectVisible(save, floor[1],
|
|
objOf(floor[1], name)),
|
|
name .. " no longer spawns")
|
|
hidden = hidden + 1
|
|
end
|
|
end
|
|
eq(hidden, 31, "the asm hides 31 Silph objects on 2F-11F")
|
|
|
|
for _, kept in ipairs(KEPT) do
|
|
eq(save.objectToggles[kept[1]] and save.objectToggles[kept[1]][kept[2]],
|
|
nil, kept[2] .. " is not in .HideToggleableObjectIDs")
|
|
check(OverworldState.objectVisible(save, kept[1], objOf(kept[1], kept[2])),
|
|
kept[2] .. " still spawns")
|
|
end
|
|
end
|
|
|
|
-- each floor repairs itself on entry and touches no other floor, so a save
|
|
-- that walks back in one elevator ride at a time still clears
|
|
for _, floor in ipairs(HIDDEN) do
|
|
local save = newSave(true)
|
|
enter(floor[1], save)
|
|
for _, name in ipairs(floor[2]) do
|
|
eq(save.objectToggles[floor[1]][name], false,
|
|
name .. " hidden on entering " .. floor[1])
|
|
end
|
|
if floor[1] ~= "SILPH_CO_11F" then
|
|
local others = 0
|
|
for mapId in pairs(save.objectToggles) do
|
|
if mapId ~= floor[1] then others = others + 1 end
|
|
end
|
|
eq(others, 0, floor[1] .. " onEnter writes only its own floor")
|
|
end
|
|
end
|
|
|
|
-- before the win nothing is hidden: the grunts are still battleable
|
|
for _, floor in ipairs(HIDDEN) do
|
|
local save = newSave(false)
|
|
enter(floor[1], save)
|
|
eq(next(save.objectToggles), nil,
|
|
floor[1] .. " hides nothing while the event is unset")
|
|
for _, name in ipairs(floor[2]) do
|
|
check(OverworldState.objectVisible(save, floor[1], objOf(floor[1], name)),
|
|
name .. " still spawns before Giovanni falls")
|
|
end
|
|
end
|
|
|
|
-- ---------------------------------------------------------------- president
|
|
local rows = MapScripts.get("SILPH_CO_11F").talk.TEXT_SILPHCO11F_SILPH_PRESIDENT
|
|
check(type(rows) == "table", "the president has a hand-ported talk script")
|
|
|
|
-- the shipped bug: the last row jumped past the end of the script, which
|
|
-- validate reports and every branch walked into
|
|
local problems = ScriptRunner.validate(rows)
|
|
eq(#problems, 0, "president script validates: " .. table.concat(problems, "; "))
|
|
|
|
for _, row in ipairs(rows) do
|
|
check(row[2] ~= "EVENT_BEAT_SILPH_CO_GIOVANNI",
|
|
"no Giovanni gate: the teleport pads reach him without the trigger")
|
|
end
|
|
|
|
for _, key in ipairs({ "_SilphCo11FSilphPresidentText",
|
|
"_SilphCo11FSilphPresidentReceivedMasterBallText",
|
|
"_SilphCo11FSilphPresidentMasterBallDescriptionText" }) do
|
|
local body = Data.text[key]
|
|
check(type(body) == "string" and body ~= "", key .. " is extracted")
|
|
end
|
|
|
|
-- run the rows through the real interpreter with only the leaf commands
|
|
-- stubbed, so the branch arithmetic is what is under test. Commands is a
|
|
-- shared module singleton read by every later suite dofile'd into this
|
|
-- same process (tests/run_tests.lua), so the three stubs must be restored
|
|
-- before this file falls off the end -- an unrestored give_item stub
|
|
-- silently swallows any later test's item grants, matching the save/
|
|
-- restore parity_gift_atomicity.lua already does for show_text.
|
|
local origFacePlayer, origShowText, origGiveItem =
|
|
Commands.face_player, Commands.show_text, Commands.give_item
|
|
local shown, given
|
|
Commands.face_player = function() end
|
|
Commands.show_text = function(_, key) shown[#shown + 1] = key end
|
|
Commands.give_item = function(_, item, count) given = { item, count } end
|
|
|
|
local function talk(save)
|
|
shown, given = {}, nil
|
|
local runner = ScriptRunner.new({ data = Data, save = save }, nil)
|
|
runner:exec(rows, runner:makeContext({}))
|
|
return shown, given
|
|
end
|
|
|
|
do
|
|
local save = newSave(true)
|
|
local first = talk(save)
|
|
eq(first[1], "_SilphCo11FSilphPresidentText", "first talk thanks the player")
|
|
eq(given and given[1], "MASTER_BALL", "first talk hands over a MASTER BALL")
|
|
eq(given and given[2], 1, "one ball, as in lb bc, MASTER_BALL, 1")
|
|
eq(first[2], "_SilphCo11FSilphPresidentReceivedMasterBallText",
|
|
"the got-item line prints after the give (sound_get_key_item rides it)")
|
|
eq(#first, 2, "the description is not printed on the same talk")
|
|
eq(save.flags.EVENT_GOT_MASTER_BALL, true, "SetEvent EVENT_GOT_MASTER_BALL")
|
|
|
|
local second, secondGive = talk(save)
|
|
eq(second[1], "_SilphCo11FSilphPresidentMasterBallDescriptionText",
|
|
"second talk describes the ball instead of going silent")
|
|
eq(#second, 1, "and prints nothing else")
|
|
eq(secondGive, nil, "a second ball is not handed out")
|
|
end
|
|
|
|
-- the ball does not depend on Giovanni's coordinate trigger having fired
|
|
do
|
|
local fresh = talk(newSave(false))
|
|
eq(fresh[1], "_SilphCo11FSilphPresidentText",
|
|
"an unbeaten-Giovanni save still gets the thank-you and the ball")
|
|
end
|
|
|
|
Commands.face_player, Commands.show_text, Commands.give_item =
|
|
origFacePlayer, origShowText, origGiveItem
|
|
|
|
S.finish()
|