mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
173 lines
6.6 KiB
Lua
173 lines
6.6 KiB
Lua
-- pokered engine/overworld/movement.asm:406-414
|
|
-- scripts/SSAnneCaptainsRoom.asm:5-10,45-68
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local T = require("tests.harness")
|
|
local check, eq = T.check, T.eq
|
|
love = love or require("tests.love_stub")
|
|
|
|
local OW = require("src.world.OverworldController")
|
|
local Commands = require("src.script.Commands")
|
|
local GameVersion = require("src.core.GameVersion")
|
|
local story = require("data.scripts.story")
|
|
|
|
local player = { cellX = 3, cellY = 5 }
|
|
local function newNpc()
|
|
return { facing = "up",
|
|
facePlayer = function(self) self.facing = "down" end }
|
|
end
|
|
|
|
local ow = setmetatable({ player = player }, { __index = OW })
|
|
local npc = newNpc()
|
|
ow.noNpcFacePlayer = true
|
|
ow:makeNpcFacePlayer(npc)
|
|
eq(npc.facing, "up", "the no-face flag leaves the NPC's facing alone")
|
|
|
|
ow.noNpcFacePlayer = nil
|
|
ow:makeNpcFacePlayer(npc)
|
|
eq(npc.facing, "down", "with the flag clear the NPC turns to the player")
|
|
|
|
ow.noNpcFacePlayer = true
|
|
ow:makeNpcFacePlayer(nil)
|
|
|
|
local ctx = { overworld = ow }
|
|
Commands.no_npc_face_player(ctx, true)
|
|
eq(ow.noNpcFacePlayer, true, "no_npc_face_player true sets the flag")
|
|
Commands.no_npc_face_player(ctx, false)
|
|
eq(ow.noNpcFacePlayer, nil, "no_npc_face_player false clears the flag")
|
|
Commands.no_npc_face_player({}, true)
|
|
|
|
local room = story.SS_ANNE_CAPTAINS_ROOM
|
|
check(type(room.onEnter) == "function", "SS_ANNE_CAPTAINS_ROOM has a map script")
|
|
|
|
local saved = GameVersion.get()
|
|
local function entered(version, flags)
|
|
GameVersion.set(version)
|
|
local state = {}
|
|
room.onEnter({ save = { flags = flags } }, state)
|
|
return state.noNpcFacePlayer
|
|
end
|
|
|
|
eq(entered("yellow", {}), true, "yellow sets the bit before HM01")
|
|
eq(entered("yellow", { EVENT_RUBBED_CAPTAINS_BACK = true }), true,
|
|
"yellow gates on EVENT_GOT_HM01, not the rub")
|
|
eq(entered("yellow", { EVENT_GOT_HM01 = true }), nil, "yellow clears it after HM01")
|
|
eq(entered("red", {}), true, "red sets the bit before the rub")
|
|
eq(entered("red", { EVENT_RUBBED_CAPTAINS_BACK = true }), nil,
|
|
"red gates on EVENT_RUBBED_CAPTAINS_BACK")
|
|
GameVersion.set(saved)
|
|
|
|
local rows = room.talk.TEXT_SSANNECAPTAINSROOM_CAPTAIN
|
|
local rubAt, optsAt, clearAt, rubbedAt, giveAt, rearmAt, gotAt, finalClearAt
|
|
for i, row in ipairs(rows) do
|
|
check(row[1] ~= "play_once",
|
|
"no bare play_once row survives -- the jingle rides the box")
|
|
if row[1] == "show_text"
|
|
and row[2] == "_SSAnneCaptainsRoomRubCaptainsBackText" then rubAt = i end
|
|
if row[1] == "text_opts" then optsAt = i end
|
|
if row[1] == "no_npc_face_player" then
|
|
if row[2] == true then rearmAt = i
|
|
elseif clearAt then finalClearAt = i
|
|
else clearAt = i end
|
|
end
|
|
if row[1] == "set_flag" and row[2] == "EVENT_RUBBED_CAPTAINS_BACK" then
|
|
rubbedAt = i
|
|
end
|
|
if row[1] == "set_flag" and row[2] == "EVENT_GOT_HM01" then gotAt = i end
|
|
if row[1] == "give_item" then giveAt = i end
|
|
end
|
|
check(rubAt, "the rub text is still shown")
|
|
eq(optsAt, rubAt - 1, "a text_opts row arms the rub box")
|
|
check(rubbedAt and rubbedAt > rubAt, "EVENT_RUBBED_CAPTAINS_BACK is set after the rub")
|
|
check(clearAt, "a no_npc_face_player row clears the bit")
|
|
eq(rows[clearAt][2], false, "that row clears rather than sets")
|
|
eq(clearAt, rubbedAt + 1, "the rub tail clears the bit before the gift (SSAnneCaptainsRoom.asm:64-66)")
|
|
check(finalClearAt and gotAt and finalClearAt == gotAt + 1,
|
|
"the success path clears it again after EVENT_GOT_HM01 (:31-33)")
|
|
if require("src.core.GameVersion").isYellow() then
|
|
eq(rearmAt, nil, "Yellow never re-arms the bit: a full bag still turns the captain")
|
|
else
|
|
eq(rearmAt, giveAt - 1, "Red re-arms the bit right before GiveItem so a full bag halts with him back-turned (pokered :34-37)")
|
|
end
|
|
|
|
local auto = rows[optsAt][2].auto
|
|
eq(auto.wait, false, "the rub box never waits for A (bare text terminator)")
|
|
eq(auto.delay, 0, "WaitForSoundToFinish has no trailing Delay3")
|
|
check(type(auto.sound) == "function", "the rub box starts the jingle itself")
|
|
|
|
local Music = require("src.core.Music")
|
|
local playedSong
|
|
local realPlayOnce, realOnePlaying = Music.playOnce, Music.oneShotPlaying
|
|
local playing = true
|
|
Music.playOnce = function(_, song) playedSong = song; return true end
|
|
Music.oneShotPlaying = function() return playing end
|
|
local src = auto.sound()
|
|
eq(playedSong, "Music_PkmnHealed", "the rub box plays MUSIC_PKMN_HEALED")
|
|
check(src and src.isPlaying(src), "the box holds while the jingle plays")
|
|
eq(src.getDuration(src), 10, "the source reports a duration past the 180-frame ceiling")
|
|
playing = false
|
|
eq(src.isPlaying(src), false, "and lets go once the jingle ends")
|
|
Music.playOnce = function() return false end
|
|
eq(auto.sound(), nil, "a jingle that never started drops the hold")
|
|
Music.playOnce, Music.oneShotPlaying = realPlayOnce, realOnePlaying
|
|
|
|
for i, row in ipairs(rows) do
|
|
if row[1] == "jump" or row[1]:sub(1, 8) == "jump_if_" then
|
|
local target = row[#row]
|
|
check(target == "end" or (type(target) == "number" and target >= 1 and target <= #rows),
|
|
string.format("row %d jumps to a real row (%s)", i, tostring(target)))
|
|
end
|
|
end
|
|
eq(rows[2][2], #rows, "the already-healed branch jumps to the last row")
|
|
eq(rows[#rows - 1][2], "end", "the healed path halts with the reserved end target")
|
|
eq(#require("src.script.ScriptRunner").validate(rows), 0, "the captain rows pass validate")
|
|
|
|
local function setUpvalue(fn, name, value)
|
|
local i = 1
|
|
while true do
|
|
local n = debug.getupvalue(fn, i)
|
|
if not n then return false end
|
|
if n == name then
|
|
debug.setupvalue(fn, i, value)
|
|
return true
|
|
end
|
|
i = i + 1
|
|
end
|
|
end
|
|
|
|
local talkDone
|
|
local function talkTo(state, who)
|
|
talkDone = nil
|
|
state:showMapText("TEXT_X", who, function() end)
|
|
return talkDone
|
|
end
|
|
check(setUpvalue(OW.showMapText, "mapScripts", {
|
|
talkScript = function() return function(_, _, _, onDone) talkDone = onDone end end,
|
|
talkSource = function() end,
|
|
}), "showMapText reads the script registry through an upvalue")
|
|
local room2 = setmetatable({ player = player, map = { id = 1, def = { label = "X" } } },
|
|
{ __index = OW })
|
|
|
|
room2.noNpcFacePlayer = true
|
|
local captain = newNpc()
|
|
local done = talkTo(room2, captain)
|
|
eq(captain.facing, "up", "the captain keeps his back turned at talk time")
|
|
room2.noNpcFacePlayer = nil
|
|
done()
|
|
eq(captain.facing, "down", "and turns once the conversation ends with the bit cleared")
|
|
|
|
room2.noNpcFacePlayer = nil
|
|
local other = newNpc()
|
|
done = talkTo(room2, other)
|
|
eq(other.facing, "down", "an ordinary NPC faces the player at talk time")
|
|
other.facing = "left"
|
|
done()
|
|
eq(other.facing, "left", "and is not re-faced when the conversation ends")
|
|
|
|
room2.noNpcFacePlayer = true
|
|
done = talkTo(room2, nil)
|
|
done()
|
|
|
|
T.finish("ss_anne_captain_face_bug2189")
|