mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-16 00:02:23 +02:00
squassshheee
CLOSES #128 CLOSES #114 CLOSES #113 CLOSES #111 CLOSES #110 CLOSES #109 CLOSES #107 CLOSES #106 CLOSES #105 CLOSES #90 CLOSES #103
This commit is contained in:
@@ -64,6 +64,13 @@ return function(game)
|
||||
check(warped, "YES warps to REDS_HOUSE_2F")
|
||||
check(ow.player.cellX == 3 and ow.player.cellY == 6,
|
||||
"landed on the bedroom spawn (3,6)")
|
||||
check(ow.lastOutdoor and ow.lastOutdoor.id == "PALLET_TOWN",
|
||||
"PC home warp retargets lastOutdoor to Pallet (#103)")
|
||||
local lead = game.save.party and game.save.party[1]
|
||||
if lead and lead.stats then
|
||||
check(lead.hp == lead.stats.hp and lead.status == nil,
|
||||
"PC home warp heals the party")
|
||||
end
|
||||
|
||||
U.log(pass and "RESULT: ALL PASS" or "RESULT: SEE FAILURES ABOVE")
|
||||
end
|
||||
|
||||
+2
-15
@@ -4165,21 +4165,8 @@ function ops.fieldMove(s, where)
|
||||
say(("fieldMove %s: nobody in the party knows it"):format(move))
|
||||
return false
|
||||
end
|
||||
-- The knower must also be CONSCIOUS: the party submenu hides field
|
||||
-- moves on a fainted mon (vanilla -- partyKnows requires hp > 0), so
|
||||
-- the menu walk below finds only STATS/SWITCH and fails. Route 23's
|
||||
-- mandatory surf strip is where this bit: WARTORTLE arrived dead from
|
||||
-- the Viridian Gym attrition, "no SURF entry" x3, the water was never
|
||||
-- crossed, and the whole league skip-cascaded with all eight badges
|
||||
-- in the bag. Heal, fail this attempt loudly, and let the caller's
|
||||
-- retry run the step again with a live user.
|
||||
local knower = (G.save.party or {})[slot]
|
||||
if knower and (knower.hp or 0) <= 0 then
|
||||
say(("fieldMove %s: its only user has fainted -- visiting a centre "
|
||||
.. "before retrying"):format(move))
|
||||
visitPokeCenter(where)
|
||||
return false
|
||||
end
|
||||
-- Field moves work from fainted users in Gen 1 (PartyMenu + partyKnows
|
||||
-- no longer gate on hp > 0). No Poké Center detour needed here.
|
||||
-- CUT acts on the tile the player is FACING (OverworldState:tryCut), and
|
||||
-- a `goto` leaves us facing whatever direction we last stepped. Without
|
||||
-- turning to the tree first the move fires into empty ground, the game
|
||||
|
||||
+6
-1
@@ -127,7 +127,12 @@ stub.sound = {
|
||||
|
||||
stub.keyboard = { isDown = function() return false end }
|
||||
|
||||
stub.mouse = { getPosition = function() return 0, 0 end }
|
||||
stub.mouse = {
|
||||
getPosition = function() return 0, 0 end,
|
||||
isCursorSupported = function() return false end,
|
||||
getSystemCursor = function(name) return name end,
|
||||
setCursor = function() end,
|
||||
}
|
||||
|
||||
stub.timer = { getTime = function() return 0 end }
|
||||
|
||||
|
||||
@@ -320,6 +320,9 @@ check(vanillaSave.player.map == "REDS_HOUSE_2F" and vanillaSave.player.x == 3
|
||||
"the seeded boot config reproduces the NewGameWarp bedroom spawn")
|
||||
check(vanillaSave.player.name == "RED" and vanillaSave.player.rival == "BLUE"
|
||||
and vanillaSave.money == 3000, "the seeded boot config reproduces the Red start")
|
||||
-- issue #109: vanilla New Game puts 1 Potion in the player's item PC
|
||||
check(vanillaSave.pcItems and vanillaSave.pcItems.POTION == 1,
|
||||
"new game seeds 1 Potion in the player's PC")
|
||||
-- the heal point is deliberately NOT the spawn: wLastBlackoutMap is
|
||||
-- zero-filled at new game and PALLET_TOWN is map 0
|
||||
check(vanillaSave.lastHeal.map == "PALLET_TOWN" and vanillaSave.lastHeal.x == 5
|
||||
@@ -329,6 +332,8 @@ local bareSave = SaveData.newGame()
|
||||
check(bareSave.player.map == "REDS_HOUSE_2F" and bareSave.money == 3000
|
||||
and bareSave.lastHeal.map == "PALLET_TOWN",
|
||||
"newGame without a boot config is unchanged")
|
||||
check(bareSave.pcItems and bareSave.pcItems.POTION == 1,
|
||||
"newGame without a boot config still seeds the PC Potion")
|
||||
|
||||
local bootData, bootLoader = withMod("total_conversion", [[
|
||||
return function(mod)
|
||||
|
||||
@@ -618,6 +618,33 @@ check(PaletteFX.pal({ palettes = nil }, "ROUTE") == gbc.palettes.ROUTE,
|
||||
"RED++ still has ROUTE (aliased from VIRIDIAN)")
|
||||
check(PaletteFX.effectiveColors(gbc.palettes.MEWMON) == gbc.palettes.MEWMON,
|
||||
"RED++ passes zone colors through like GBC")
|
||||
-- issue #128: RED++'s gbc pack is Red-derived; Blue must keep ROM LOGO1
|
||||
-- (and the Blue-only SLOTS* rows) so the title ribbon is blue, not red
|
||||
do
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
local prevVer = GameVersion.get()
|
||||
local blueLogo1 = {
|
||||
{ 255, 239, 255 }, { 247, 247, 140 }, { 173, 0, 33 }, { 115, 156, 239 },
|
||||
}
|
||||
local blueSlots2 = {
|
||||
{ 255, 239, 255 }, { 255, 255, 140 }, { 132, 156, 239 }, { 25, 16, 16 },
|
||||
}
|
||||
local rom = { palettes = { palettes = {
|
||||
LOGO1 = blueLogo1, SLOTS2 = blueSlots2, ROUTE = { { 1, 2, 3 }, { 0, 0, 0 },
|
||||
{ 0, 0, 0 }, { 0, 0, 0 } },
|
||||
} } }
|
||||
GameVersion.set("blue")
|
||||
check(PaletteFX.pal(rom, "LOGO1") == blueLogo1,
|
||||
"Blue + RED++ prefers ROM LOGO1 over the Red gbc pack")
|
||||
check(PaletteFX.pal(rom, "SLOTS2") == blueSlots2,
|
||||
"Blue + RED++ prefers ROM SLOTS2 over the Red gbc pack")
|
||||
check(PaletteFX.pal(rom, "ROUTE") == gbc.palettes.ROUTE,
|
||||
"Blue + RED++ still uses the gbc pack for shared names")
|
||||
GameVersion.set("red")
|
||||
check(PaletteFX.pal(rom, "LOGO1") == gbc.palettes.LOGO1,
|
||||
"Red + RED++ keeps the gbc-pack LOGO1 (title stays red)")
|
||||
GameVersion.set(prevVer)
|
||||
end
|
||||
PaletteFX.setMode(prevMode)
|
||||
|
||||
-- ------- the transitions registry
|
||||
|
||||
@@ -536,6 +536,32 @@ do
|
||||
"newGame starts an empty modData")
|
||||
check(plain.meta.format == Version.saveFormat and #plain.meta.mods == 0,
|
||||
"newGame stamps a vanilla meta")
|
||||
check(plain.pcItems and plain.pcItems.POTION == 1,
|
||||
"unhooked newGame seeds 1 Potion in pcItems (issue #109)")
|
||||
end
|
||||
|
||||
-- issue #109: loading a pre-fix save that never had pcItems must not
|
||||
-- invent a free Potion (player may already have withdrawn/tossed it, or
|
||||
-- the empty PC is intentional). Seeding is New Game only.
|
||||
do
|
||||
local files = {}
|
||||
love.filesystem = memfs(files)
|
||||
local legacy = {
|
||||
meta = { format = Version.saveFormat, mods = {} },
|
||||
player = { map = "PALLET_TOWN", x = 5, y = 6, facing = "down",
|
||||
name = "RED", rival = "BLUE", id = 1 },
|
||||
flags = {}, inventory = {}, party = {}, box = {}, money = 3000,
|
||||
defeatedTrainers = {}, pokedex = { seen = {}, owned = {} },
|
||||
lastHeal = { map = "PALLET_TOWN", x = 5, y = 6 },
|
||||
options = SaveData.defaultOptions(),
|
||||
}
|
||||
check(legacy.pcItems == nil, "fixture omits pcItems on purpose")
|
||||
check(SaveData.save(legacy), "legacy save without pcItems writes")
|
||||
local loaded = SaveData.load()
|
||||
check(loaded ~= nil, "legacy save loads")
|
||||
check(loaded.pcItems == nil or loaded.pcItems.POTION == nil,
|
||||
"load does not invent a PC Potion for existing saves")
|
||||
love.filesystem = realFS
|
||||
end
|
||||
|
||||
-- a throwing mod migration is skipped, never fatal: an uncaught error here
|
||||
|
||||
@@ -475,6 +475,38 @@ check(title.version and title.version.path
|
||||
== "assets/generated/title/red_version.png",
|
||||
"the shipped version ribbon loads")
|
||||
|
||||
-- issue #128: title SGB zones must resolve Blue's LOGO1 (blue ribbon),
|
||||
-- not Red's, when the ROM pack carries Blue SuperPalettes
|
||||
do
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
local PaletteFX = require("src.render.PaletteFX")
|
||||
local prevVer, prevMode = GameVersion.get(), PaletteFX.mode
|
||||
local blueLogo1 = {
|
||||
{ 255, 239, 255 }, { 247, 247, 140 }, { 173, 0, 33 }, { 115, 156, 239 },
|
||||
}
|
||||
local mewmon = {
|
||||
{ 255, 239, 255 }, { 247, 181, 140 }, { 132, 115, 156 }, { 25, 16, 16 },
|
||||
}
|
||||
local logo2 = {
|
||||
{ 255, 239, 255 }, { 247, 247, 140 }, { 148, 148, 197 }, { 58, 58, 132 },
|
||||
}
|
||||
local game = { data = { palettes = { palettes = {
|
||||
LOGO1 = blueLogo1, LOGO2 = logo2, MEWMON = mewmon,
|
||||
} } } }
|
||||
PaletteFX.setMode("redpp")
|
||||
GameVersion.set("blue")
|
||||
local zones = TitleState.sgbPalettes(title, game)
|
||||
check(zones and zones[2] and zones[2].colors == blueLogo1,
|
||||
"Blue title ribbon zone uses ROM LOGO1 under RED++")
|
||||
GameVersion.set("red")
|
||||
zones = TitleState.sgbPalettes(title, game)
|
||||
local gbcLogo1 = PaletteFX.gbcPack().palettes.LOGO1
|
||||
check(zones and zones[2] and zones[2].colors == gbcLogo1,
|
||||
"Red title under RED++ keeps gbc-pack LOGO1 even if ROM has Blue's")
|
||||
GameVersion.set(prevVer)
|
||||
PaletteFX.setMode(prevMode)
|
||||
end
|
||||
|
||||
local OakSpeech = require("src.ui.OakSpeech")
|
||||
local ogame = { data = {
|
||||
field = { oakSpeech = { music = "X_Song", demoSpecies = "PIKACHU" } },
|
||||
|
||||
+13
-2
@@ -281,11 +281,21 @@ end
|
||||
-- the digit only), plus the LIFT_KEY gate.
|
||||
-- ===================================================================
|
||||
do
|
||||
-- without the key: text-only, no floor menu
|
||||
local gated = openElevator("ROCKET_HIDEOUT_ELEVATOR", {})
|
||||
local hideoutSnap = snapshotWarps("ROCKET_HIDEOUT_ELEVATOR")
|
||||
-- ROM car defaults to B1F; entering from another floor without the key
|
||||
-- must still seed a walk-out back to that floor (#90 / #105).
|
||||
eq(hideoutSnap[1].destMap, "ROCKET_HIDEOUT_B1F",
|
||||
"Rocket Hideout ROM car warps still default to B1F")
|
||||
|
||||
-- without the key: text-only, no floor menu, but exit is seeded
|
||||
local gated, _, _, gatedOw =
|
||||
openElevator("ROCKET_HIDEOUT_ELEVATOR", {}, "ROCKET_HIDEOUT_B2F")
|
||||
check(gated ~= nil, "Rocket Hideout without LIFT_KEY still pushes something")
|
||||
check(gated ~= nil and getmetatable(gated) ~= ListMenu,
|
||||
"Rocket Hideout without LIFT_KEY does not open the floor menu")
|
||||
eq(gatedOw.map.def.warps[1].destMap, "ROCKET_HIDEOUT_B2F",
|
||||
"Rocket Hideout without LIFT_KEY seeds exit warps to the entry floor")
|
||||
restoreWarps("ROCKET_HIDEOUT_ELEVATOR", hideoutSnap)
|
||||
|
||||
-- with the key: full B1F/B2F/B4F menu, same as the other elevators
|
||||
local menu, warpCalls, stack, ow = openElevator("ROCKET_HIDEOUT_ELEVATOR", { LIFT_KEY = 1 })
|
||||
@@ -324,6 +334,7 @@ do
|
||||
eq(warpCalls[1].y, chosen.value.y, "Rocket Hideout walk-out lands on the chosen floor y")
|
||||
end
|
||||
end
|
||||
restoreWarps("ROCKET_HIDEOUT_ELEVATOR", hideoutSnap)
|
||||
end
|
||||
|
||||
Sound.play = origSoundPlay
|
||||
|
||||
@@ -69,6 +69,12 @@ check(runScript(mapScripts.talkScript("OAKS_LAB", "TEXT_OAKSLAB_OAK1")),
|
||||
eq(Game.save.inventory.OAKS_PARCEL, nil, "parcel delivered")
|
||||
check(Flags.get(Game.save, "EVENT_OAK_GOT_PARCEL"), "delivery flag set")
|
||||
check(Flags.get(Game.save, "EVENT_GOT_POKEDEX"), "Pokedex flag set")
|
||||
-- OaksLab.asm OakGivesPokedex: HideObject TOGGLE_POKEDEX_1/2 (#106)
|
||||
local labToggles = Game.save.objectToggles and Game.save.objectToggles.OAKS_LAB
|
||||
check(labToggles and labToggles.OAKSLAB_POKEDEX1 == false,
|
||||
"POKEDEX1 hidden after receiving Pokédex")
|
||||
check(labToggles and labToggles.OAKSLAB_POKEDEX2 == false,
|
||||
"POKEDEX2 hidden after receiving Pokédex")
|
||||
eq(Game.save.inventory.POKE_BALL, nil, "still no POKe BALLs at the pokedex beat")
|
||||
|
||||
-- talking to Oak again before beating the Route 22 rival should fall
|
||||
@@ -89,4 +95,16 @@ check(runScript(mapScripts.talkScript("OAKS_LAB", "TEXT_OAKSLAB_OAK1")),
|
||||
"Oak talk (post-grant) script completes")
|
||||
eq(Game.save.inventory.POKE_BALL, 5, "POKe Ball count unchanged on a second talk")
|
||||
|
||||
-- === 5) onEnter re-hides table Pokédex for pre-#106 saves ===
|
||||
Game.save.objectToggles = { OAKS_LAB = {
|
||||
OAKSLAB_POKEDEX1 = true, OAKSLAB_POKEDEX2 = true,
|
||||
} }
|
||||
local oaksLab = mapScripts.get("OAKS_LAB")
|
||||
check(oaksLab and type(oaksLab.onEnter) == "function", "OAKS_LAB onEnter present")
|
||||
oaksLab.onEnter(Game, nil)
|
||||
local repaired = Game.save.objectToggles.OAKS_LAB
|
||||
check(repaired.OAKSLAB_POKEDEX1 == false
|
||||
and repaired.OAKSLAB_POKEDEX2 == false,
|
||||
"onEnter hides both Pokédex table sprites when EVENT_GOT_POKEDEX is set")
|
||||
|
||||
S.finish()
|
||||
|
||||
@@ -408,6 +408,55 @@ check(strFlash ~= page2 and strFlash ~= ow and strFlash.pages == nil,
|
||||
for _ = 1, 10 do frame({}) end
|
||||
eq(Game.stack:top(), ow, "back on the map after the blink")
|
||||
|
||||
-- ===========================================================================
|
||||
-- #107: fainted Pokémon can still use field moves (party submenu + partyKnows)
|
||||
-- ===========================================================================
|
||||
local fainted = mkMon("PIDGEOTTO", "FLY", "CUT", "STRENGTH", "SURF")
|
||||
fainted.hp = 0
|
||||
Game.save.party = { fainted }
|
||||
Game.save.inventory = {
|
||||
THUNDERBADGE = true, CASCADEBADGE = true,
|
||||
RAINBOWBADGE = true, SOULBADGE = true,
|
||||
}
|
||||
ow = pushOW("PALLET_TOWN", 4, 13, "down")
|
||||
check(ow:partyKnows("SURF") == fainted,
|
||||
"partyKnows finds SURF on a fainted mon")
|
||||
check(ow:partyKnows("CUT") == fainted,
|
||||
"partyKnows finds CUT on a fainted mon")
|
||||
local pmFaint = PartyMenu.new(Game)
|
||||
Game.stack:push(pmFaint)
|
||||
frame({ "a" })
|
||||
local actsFaint = submenuActions(pmFaint)
|
||||
-- FLY needs OVERWORLD tileset (Pallet); CUT/SURF/STRENGTH need badges only
|
||||
check(actsFaint.fly and actsFaint.cut and actsFaint.surf and actsFaint.strength,
|
||||
"fainted mon still lists FLY/CUT/SURF/STRENGTH in the party submenu")
|
||||
popToOW()
|
||||
|
||||
-- STRENGTH activation from a fainted user (name text + strengthActive)
|
||||
ow = pushOW("SEAFOAM_ISLANDS_1F", 17, 10, "right")
|
||||
clearCaptured()
|
||||
local pmFaintStr = PartyMenu.new(Game)
|
||||
-- Seafoam is not OVERWORLD, so FLY is omitted: STATS, SWITCH, CUT, STRENGTH, SURF
|
||||
selectSubItem(pmFaintStr, 4)
|
||||
eq(Game.overworld.strengthActive, true,
|
||||
"fainted mon can activate STRENGTH from the party menu")
|
||||
check(sawText("used") and sawText("STRENGTH"),
|
||||
"fainted STRENGTH still prints _UsedStrengthText")
|
||||
drainText()
|
||||
|
||||
-- SURF mount from a fainted user when facing water
|
||||
Game.save.party = { fainted }
|
||||
Game.save.inventory.SOULBADGE = true
|
||||
ow = pushOW("PALLET_TOWN", 4, 13, "down")
|
||||
ow.player.surfing = false
|
||||
eq(ow:useSurfFieldMove(), "ok", "useSurfFieldMove ok with only a fainted SURF mon")
|
||||
clearCaptured()
|
||||
-- submenu order: STATS, SWITCH, FLY, CUT, STRENGTH, SURF (move order on mon)
|
||||
local pmFaintSurf = PartyMenu.new(Game)
|
||||
selectSubItem(pmFaintSurf, 6)
|
||||
eq(ow.player.surfing, true, "fainted mon can SURF from the party menu")
|
||||
check(not onStack(pmFaintSurf), "party menu closes after fainted SURF")
|
||||
|
||||
-- restore the spied constructor so later dofile'd suites are unaffected
|
||||
TextBox.new = realTextBoxNew
|
||||
popAll()
|
||||
|
||||
+37
-3
@@ -88,8 +88,12 @@ local SaveData = require("src.core.SaveData")
|
||||
local stack2 = newStack()
|
||||
local game2 = { data = Data, input = fakeInput, stack = stack2,
|
||||
save = SaveData.newGame() }
|
||||
game2.save.party = { { species = "PIKACHU", level = 81 } }
|
||||
-- battered party: record_hall_of_fame must heal before the autosave (#103)
|
||||
game2.save.party = { { species = "PIKACHU", level = 81, hp = 1,
|
||||
stats = { hp = 100 }, status = "PAR",
|
||||
moves = { { id = "THUNDERBOLT", pp = 0 } } } }
|
||||
game2.save.player.map = "HALL_OF_FAME"
|
||||
game2.save.lastOutdoor = { id = "INDIGO_PLATEAU", x = 9, y = 5 }
|
||||
local wrote = false
|
||||
function game2:writeSave() wrote = true; SaveData.save(self.save) end
|
||||
|
||||
@@ -129,9 +133,39 @@ local saved = savedRaw and SaveData.decode(savedRaw) or nil
|
||||
check(saved ~= nil, "save.lua written and decodable")
|
||||
eq(saved and saved.lastHeal and saved.lastHeal.map, "PALLET_TOWN",
|
||||
"wLastBlackoutMap := PALLET_TOWN before the save")
|
||||
eq(saved and saved.player and saved.player.map, "HALL_OF_FAME",
|
||||
"save keeps the player in the HALL_OF_FAME room")
|
||||
eq(saved and saved.player and saved.player.map, "REDS_HOUSE_2F",
|
||||
"CONTINUE lands in the bedroom (post-game home, #103)")
|
||||
eq(saved and saved.player and saved.player.x, 3, "bedroom spawn x")
|
||||
eq(saved and saved.player and saved.player.y, 6, "bedroom spawn y")
|
||||
eq(saved and saved.lastOutdoor and saved.lastOutdoor.id, "PALLET_TOWN",
|
||||
"LAST_MAP exits aim at Pallet Town, not Indigo")
|
||||
check(saved and saved.postGameHomeOk, "postGameHomeOk marks the relocate done")
|
||||
eq(saved and #(saved.hallOfFame or {}), 1, "hall of fame team persisted")
|
||||
local healed = saved and saved.party and saved.party[1]
|
||||
local thunderPp = Data.moves and Data.moves.THUNDERBOLT and Data.moves.THUNDERBOLT.pp
|
||||
check(healed and healed.hp == healed.stats.hp and healed.status == nil
|
||||
and healed.moves[1].pp == thunderPp,
|
||||
"party is fully healed in the post-credits save")
|
||||
|
||||
-- house exit mats resolve to Pallet's door, not Indigo
|
||||
local Warp = require("src.world.Warp")
|
||||
local houseExit = { destMap = "LAST_MAP", destWarp = 1, x = 2, y = 7 }
|
||||
local destMap, dx, dy = Warp.destination(Data, houseExit, saved.lastOutdoor)
|
||||
eq(destMap, "PALLET_TOWN", "Red's house LAST_MAP -> PALLET_TOWN")
|
||||
eq(dx, 5, "Pallet door x")
|
||||
eq(dy, 5, "Pallet door y")
|
||||
|
||||
-- stuck-save rescue: HoF + hallOfFame + no postGameHomeOk -> bedroom
|
||||
local stuck = SaveData.newGame()
|
||||
stuck.player.map = "HALL_OF_FAME"
|
||||
stuck.player.x, stuck.player.y = 4, 2
|
||||
stuck.lastOutdoor = { id = "INDIGO_PLATEAU", x = 9, y = 5 }
|
||||
stuck.hallOfFame = { { { species = "PIKACHU", level = 81 } } }
|
||||
check(SaveData.needsPostGameRescue(stuck), "pre-fix softlock save needs rescue")
|
||||
SaveData.applyPostGameHome(stuck, Data.field.boot)
|
||||
check(not SaveData.needsPostGameRescue(stuck), "rescue is one-shot")
|
||||
eq(stuck.player.map, "REDS_HOUSE_2F", "rescue warps home")
|
||||
eq(stuck.lastOutdoor.id, "PALLET_TOWN", "rescue retargets LAST_MAP")
|
||||
|
||||
-- `jp Init`: everything popped, boot sequence pushed (intro -> title)
|
||||
eq(#stack2.states, 1, "soft reset leaves exactly the boot state")
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
-- Parity: Rocket Hideout B4F Lift Key drop (#90 / #105).
|
||||
--
|
||||
-- Oracle: scripts/RocketHideoutB4F.asm RocketHideoutB4FRocket3AfterBattleText
|
||||
-- (PrintText + CheckAndSetEvent EVENT_ROCKET_DROPPED_LIFT_KEY / ShowObject
|
||||
-- TOGGLE_ROCKET_HIDEOUT_B4F_ITEM_5). The LIFT_KEY object_event starts
|
||||
-- hidden; talking to Rocket3 after his defeat reveals it.
|
||||
--
|
||||
-- Self-contained: `luajit tests/parity_lift_key.lua`; also dofile'd 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 lift key")
|
||||
local check, eq = S.check, S.eq
|
||||
|
||||
local mapScripts = require("data.scripts.init")
|
||||
local OverworldState = require("src.world.OverworldController")
|
||||
|
||||
local liftKeyObj
|
||||
for _, o in ipairs(Data.maps.ROCKET_HIDEOUT_B4F.objects) do
|
||||
if o.name == "ROCKETHIDEOUTB4F_LIFT_KEY" then liftKeyObj = o break end
|
||||
end
|
||||
check(liftKeyObj and liftKeyObj.hidden == true,
|
||||
"LIFT_KEY object_event is extracted as hidden")
|
||||
|
||||
local script = mapScripts.talkScript("ROCKET_HIDEOUT_B4F",
|
||||
"TEXT_ROCKETHIDEOUTB4F_ROCKET3")
|
||||
check(type(script) == "function",
|
||||
"Rocket3 after-battle drop is a hand-ported talk handler")
|
||||
|
||||
-- Capture TextBox payloads without driving the typewriter / font path.
|
||||
local realTB = package.loaded["src.render.TextBox"]
|
||||
package.loaded["src.render.TextBox"] = {
|
||||
new = function(game, text, done) return { text = text, onDone = done } end,
|
||||
}
|
||||
|
||||
-- Drive the defeated-talk path headless: show after text, then ShowObject.
|
||||
do
|
||||
local pushed, engaged
|
||||
local save = {
|
||||
flags = {},
|
||||
inventory = {},
|
||||
defeatedTrainers = { ROCKET_HIDEOUT_B4F_obj_4 = true },
|
||||
objectToggles = {},
|
||||
}
|
||||
local game = {
|
||||
data = Data,
|
||||
save = save,
|
||||
stack = {
|
||||
push = function(_, box) pushed = box end,
|
||||
},
|
||||
}
|
||||
local ow = {
|
||||
map = { id = "ROCKET_HIDEOUT_B4F", def = Data.maps.ROCKET_HIDEOUT_B4F },
|
||||
npcs = {},
|
||||
entities = {},
|
||||
trainerDefeated = function(_, npc)
|
||||
return save.defeatedTrainers[npc.id] == true
|
||||
end,
|
||||
engageTrainer = function() engaged = true end,
|
||||
}
|
||||
local npc = {
|
||||
id = "ROCKET_HIDEOUT_B4F_obj_4",
|
||||
def = { name = "ROCKETHIDEOUTB4F_ROCKET3", index = 4,
|
||||
text = "TEXT_ROCKETHIDEOUTB4F_ROCKET3",
|
||||
trainerClass = "OPP_ROCKET", trainerParty = 18 },
|
||||
}
|
||||
local doneCalled = false
|
||||
script(game, ow, npc, function() doneCalled = true end)
|
||||
|
||||
check(not engaged, "defeated Rocket3 talk does not re-engage battle")
|
||||
check(pushed ~= nil, "defeated Rocket3 talk pushes the after-battle text")
|
||||
eq(pushed and pushed.text, Data.text._RocketHideoutB4FRocket3AfterBattleText,
|
||||
"after-battle text is the dropped-key line")
|
||||
check(not save.flags.EVENT_ROCKET_DROPPED_LIFT_KEY,
|
||||
"drop flag unset until the text box closes")
|
||||
check(not OverworldState.objectVisible(save, "ROCKET_HIDEOUT_B4F", liftKeyObj),
|
||||
"LIFT_KEY still hidden before the after-battle text finishes")
|
||||
|
||||
-- dismiss the box: CheckAndSetEvent + ShowObject
|
||||
pushed.onDone()
|
||||
check(doneCalled, "talk done() runs after ShowObject")
|
||||
check(save.flags.EVENT_ROCKET_DROPPED_LIFT_KEY == true,
|
||||
"EVENT_ROCKET_DROPPED_LIFT_KEY set on first after-battle talk")
|
||||
check(save.objectToggles.ROCKET_HIDEOUT_B4F
|
||||
and save.objectToggles.ROCKET_HIDEOUT_B4F.ROCKETHIDEOUTB4F_LIFT_KEY == true,
|
||||
"ShowObject toggles ROCKETHIDEOUTB4F_LIFT_KEY visible")
|
||||
check(OverworldState.objectVisible(save, "ROCKET_HIDEOUT_B4F", liftKeyObj),
|
||||
"LIFT_KEY objectVisible after ShowObject")
|
||||
|
||||
-- second talk: reprint only, no double-toggle churn
|
||||
pushed, doneCalled = nil, false
|
||||
local togglesBefore = save.objectToggles.ROCKET_HIDEOUT_B4F.ROCKETHIDEOUTB4F_LIFT_KEY
|
||||
script(game, ow, npc, function() doneCalled = true end)
|
||||
check(pushed ~= nil, "second after-battle talk still shows text")
|
||||
pushed.onDone()
|
||||
check(doneCalled, "second talk done() runs")
|
||||
eq(save.objectToggles.ROCKET_HIDEOUT_B4F.ROCKETHIDEOUTB4F_LIFT_KEY, togglesBefore,
|
||||
"second talk does not re-ShowObject")
|
||||
end
|
||||
|
||||
-- Undefeated path still engages the trainer (TalkToTrainer).
|
||||
do
|
||||
local engaged = false
|
||||
local save = { flags = {}, inventory = {}, defeatedTrainers = {} }
|
||||
local game = {
|
||||
data = Data, save = save,
|
||||
stack = { push = function() end },
|
||||
}
|
||||
local ow = {
|
||||
map = { id = "ROCKET_HIDEOUT_B4F" },
|
||||
trainerDefeated = function() return false end,
|
||||
engageTrainer = function(_, npc, done)
|
||||
engaged = true
|
||||
if done then done() end
|
||||
end,
|
||||
}
|
||||
local npc = {
|
||||
id = "ROCKET_HIDEOUT_B4F_obj_4",
|
||||
def = { name = "ROCKETHIDEOUTB4F_ROCKET3", index = 4 },
|
||||
}
|
||||
script(game, ow, npc, function() end)
|
||||
check(engaged, "undefeated Rocket3 talk engages the trainer battle")
|
||||
end
|
||||
|
||||
package.loaded["src.render.TextBox"] = realTB
|
||||
S.finish()
|
||||
@@ -0,0 +1,100 @@
|
||||
-- Parity test: Mom heal rest (#113) and Silph Co. 9F nurse fade.
|
||||
--
|
||||
-- pokered RedsHouse1FMomText / RedsHouse1FMomHealScript:
|
||||
-- pre-starter -> WakeUpText; else YouShouldRest -> GBFadeOutToWhite ->
|
||||
-- HealParty -> MUSIC_PKMN_HEALED (wait) -> GBFadeInFromWhite ->
|
||||
-- LookingGreatText.
|
||||
-- SilphCo9FNurseText: heal then white fade / Delay3 / fade in (no jingle).
|
||||
--
|
||||
-- Self-contained; run via `luajit tests/parity_mom_heal.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 mom heal")
|
||||
local check, eq = S.check, S.eq
|
||||
|
||||
local reds = dofile("data/scripts/reds_house.lua")
|
||||
local silph = dofile("data/scripts/flavor/silph_co_9f.lua")
|
||||
|
||||
local function cmds(rows)
|
||||
local out = {}
|
||||
for _, row in ipairs(rows) do out[#out + 1] = row[1] end
|
||||
return out
|
||||
end
|
||||
|
||||
local function find(rows, verb, arg)
|
||||
for i, row in ipairs(rows) do
|
||||
if row[1] == verb and (arg == nil or row[2] == arg) then
|
||||
return i, row
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- --- Mom: pre-starter branch + heal rest sequence
|
||||
do
|
||||
local rows = reds.talk.TEXT_REDSHOUSE1F_MOM
|
||||
check(rows ~= nil, "Mom talk script exists")
|
||||
|
||||
local iFlag = find(rows, "check_flag", "EVENT_GOT_STARTER")
|
||||
check(iFlag, "Mom checks EVENT_GOT_STARTER")
|
||||
local iWake = find(rows, "show_text", "_RedsHouse1FMomWakeUpText")
|
||||
check(iWake and iWake > iFlag, "WakeUpText follows the starter check")
|
||||
local iRest = find(rows, "show_text", "_RedsHouse1FMomYouShouldRestText")
|
||||
check(iRest and iRest > iWake, "heal path is after the wake-up branch")
|
||||
|
||||
local iFadeOut = find(rows, "fade", "out")
|
||||
local iHeal = find(rows, "heal_party")
|
||||
local iJingle = find(rows, "play_once", "Music_PkmnHealed")
|
||||
local iFadeIn = find(rows, "fade", "in")
|
||||
local iGreat = find(rows, "show_text", "_RedsHouse1FMomLookingGreatText")
|
||||
check(iFadeOut and rows[iFadeOut][3] == "white",
|
||||
"Mom fades out to white after rest text")
|
||||
check(iHeal and iHeal > iFadeOut, "heal_party runs under the white fade")
|
||||
check(iJingle and iJingle > iHeal,
|
||||
"Music_PkmnHealed plays after heal_party")
|
||||
check(iFadeIn and rows[iFadeIn][3] == "white" and iFadeIn > iJingle,
|
||||
"Mom fades in from white after the jingle")
|
||||
check(iGreat and iGreat > iFadeIn,
|
||||
"LookingGreatText follows the fade-in")
|
||||
|
||||
-- jump_if_true must land on the heal path (rest text), not wake-up
|
||||
local jumpRow
|
||||
for _, row in ipairs(rows) do
|
||||
if row[1] == "jump_if_true" then jumpRow = row break end
|
||||
end
|
||||
check(jumpRow, "Mom has jump_if_true after starter check")
|
||||
eq(rows[jumpRow[2]][2], "_RedsHouse1FMomYouShouldRestText",
|
||||
"EVENT_GOT_STARTER jumps to the heal rest text")
|
||||
end
|
||||
|
||||
-- --- Silph Co. 9F nurse: white fade, no heal jingle
|
||||
do
|
||||
local rows = silph.SILPH_CO_9F.talk.TEXT_SILPHCO9F_NURSE
|
||||
check(rows ~= nil, "Silph nurse talk script exists")
|
||||
|
||||
local iHeal = find(rows, "heal_party")
|
||||
local iFadeOut = find(rows, "fade", "out")
|
||||
local iWait = find(rows, "wait")
|
||||
local iFadeIn = find(rows, "fade", "in")
|
||||
check(iHeal, "Silph nurse heals the party")
|
||||
check(iFadeOut and rows[iFadeOut][3] == "white" and iFadeOut > iHeal,
|
||||
"Silph nurse fades out to white after heal (pokered order)")
|
||||
check(iWait and rows[iWait][2] == 3 and iWait > iFadeOut,
|
||||
"Silph nurse Delay3 between fades")
|
||||
check(iFadeIn and rows[iFadeIn][3] == "white" and iFadeIn > iWait,
|
||||
"Silph nurse fades in from white")
|
||||
check(not find(rows, "play_once", "Music_PkmnHealed"),
|
||||
"Silph nurse does not play Music_PkmnHealed")
|
||||
|
||||
local sequence = table.concat(cmds(rows), ",")
|
||||
check(sequence:find("heal_party,fade,wait,fade,show_text", 1, true),
|
||||
"Silph heal rest sequence is heal → fade out → wait → fade in → text")
|
||||
end
|
||||
|
||||
-- --- play_once is a blocking command (Mom / captain wait loops)
|
||||
do
|
||||
local Commands = require("src.script.Commands")
|
||||
check(Commands.meta.play_once and Commands.meta.play_once.blocking,
|
||||
"play_once is marked blocking so heal jingles wait to finish")
|
||||
end
|
||||
|
||||
S.finish()
|
||||
@@ -0,0 +1,117 @@
|
||||
-- Parity: Oak's lab starter-ball Pokédex preview (#110).
|
||||
-- pret StarterDex (engine/events/starter_dex.asm) temporarily sets the
|
||||
-- owned bits so ShowPokedexData prints the full entry before the player
|
||||
-- has caught anything. Also: English R/B prints only the kind string
|
||||
-- (no " POKéMON" suffix — that clipped "LIZARD" to "LIZARD POKé").
|
||||
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 starter dex")
|
||||
local check, eq = S.check, S.eq
|
||||
|
||||
local Font = require("src.render.Font")
|
||||
Font.load(Data)
|
||||
|
||||
local DexEntryMenu = require("src.ui.DexEntryMenu")
|
||||
local SaveData = require("src.core.SaveData")
|
||||
local mapScripts = require("data.scripts.init")
|
||||
|
||||
local function fakeGame()
|
||||
return {
|
||||
data = Data,
|
||||
save = SaveData.newGame(),
|
||||
input = { wasPressed = function() return false end },
|
||||
stack = { pop = function() end },
|
||||
}
|
||||
end
|
||||
|
||||
local function drawCapture(menu)
|
||||
local drawn = {}
|
||||
local saved = Font.draw
|
||||
Font.draw = function(text, x, y)
|
||||
drawn[#drawn + 1] = { text = tostring(text), x = x, y = y }
|
||||
return Font.width(text)
|
||||
end
|
||||
menu:draw()
|
||||
Font.draw = saved
|
||||
return drawn
|
||||
end
|
||||
|
||||
local function findText(drawn, needle)
|
||||
for _, d in ipairs(drawn) do
|
||||
if d.text == needle or d.text:find(needle, 1, true) then return d end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- === 1) unowned entry without forceOwned stays "Data unknown." ===
|
||||
do
|
||||
local game = fakeGame()
|
||||
game.save.pokedex = { seen = {}, owned = {} }
|
||||
local menu = DexEntryMenu.new(game, "CHARMANDER")
|
||||
local drawn = drawCapture(menu)
|
||||
check(findText(drawn, "Data unknown."),
|
||||
"unowned Charmander shows Data unknown without forceOwned")
|
||||
check(not findText(drawn, "Obviously prefers"),
|
||||
"unowned Charmander hides description without forceOwned")
|
||||
check(not findText(drawn, "HT "),
|
||||
"unowned Charmander hides height without forceOwned")
|
||||
end
|
||||
|
||||
-- === 2) forceOwned shows full entry without mutating save ===
|
||||
do
|
||||
local game = fakeGame()
|
||||
game.save.pokedex = { seen = {}, owned = {} }
|
||||
local menu = DexEntryMenu.new(game, { species = "CHARMANDER", forceOwned = true })
|
||||
check(menu.forceOwned, "forceOwned flag sticks on the menu")
|
||||
local drawn = drawCapture(menu)
|
||||
check(findText(drawn, "Obviously prefers"),
|
||||
"forceOwned Charmander shows dex description")
|
||||
check(findText(drawn, "HT "),
|
||||
"forceOwned Charmander shows height")
|
||||
check(not findText(drawn, "Data unknown."),
|
||||
"forceOwned Charmander does not show Data unknown")
|
||||
check(not game.save.pokedex.owned.CHARMANDER,
|
||||
"forceOwned preview does not mark Charmander owned")
|
||||
end
|
||||
|
||||
-- === 3) kind is the bare English string (no POKéMON suffix) ===
|
||||
do
|
||||
local game = fakeGame()
|
||||
game.save.pokedex = { seen = {}, owned = { CHARMANDER = true } }
|
||||
local menu = DexEntryMenu.new(game, "CHARMANDER")
|
||||
local drawn = drawCapture(menu)
|
||||
local kind = findText(drawn, "LIZARD")
|
||||
check(kind and kind.text == "LIZARD",
|
||||
"kind draws as LIZARD only (English R/B PlaceString)")
|
||||
check(not findText(drawn, "POKéMON"),
|
||||
"kind line does not append POKéMON")
|
||||
check(kind.x + Font.width(kind.text) <= 160,
|
||||
"LIZARD kind fits on-screen (no clip)")
|
||||
end
|
||||
|
||||
-- === 4) Oak's lab starter scripts request forceOwned ===
|
||||
do
|
||||
local balls = {
|
||||
"TEXT_OAKSLAB_CHARMANDER_POKE_BALL",
|
||||
"TEXT_OAKSLAB_SQUIRTLE_POKE_BALL",
|
||||
"TEXT_OAKSLAB_BULBASAUR_POKE_BALL",
|
||||
}
|
||||
for _, textId in ipairs(balls) do
|
||||
local script = mapScripts.talkScript("OAKS_LAB", textId)
|
||||
local found
|
||||
for _, row in ipairs(script) do
|
||||
if row[1] == "push_screen" and row[2] == "DexEntryMenu" then
|
||||
found = row[3]
|
||||
break
|
||||
end
|
||||
end
|
||||
check(type(found) == "table" and found.forceOwned == true
|
||||
and type(found.species) == "string",
|
||||
textId .. " pushes DexEntryMenu with forceOwned")
|
||||
end
|
||||
end
|
||||
|
||||
S.finish()
|
||||
@@ -0,0 +1,138 @@
|
||||
-- Parity test: Pokémon Tower 2F rival leaves after defeat (#111).
|
||||
--
|
||||
-- pokered PokemonTower2FDefeatedRivalScript (scripts/PokemonTower2F.asm):
|
||||
-- after EVENT_BEAT_POKEMON_TOWER_RIVAL, walk RightThenDown or
|
||||
-- DownThenRight (EVENT_POKEMON_TOWER_RIVAL_ON_LEFT when the player is on
|
||||
-- (15,5)), then HideObject TOGGLE_POKEMON_TOWER_2F_RIVAL. The port used
|
||||
-- to end after DefeatedText, so he stayed and HowsYourDex could re-fire.
|
||||
--
|
||||
-- Self-contained; run via `luajit tests/parity_tower_rival.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 tower rival")
|
||||
local check, eq = S.check, S.eq
|
||||
|
||||
package.loaded["src.render.TextBox"] = {
|
||||
new = function(_, text, done) return { text = text, done = done } end,
|
||||
}
|
||||
package.loaded["src.core.Music"] = {
|
||||
play = function() end,
|
||||
playOnce = function() return true end,
|
||||
stop = function() end,
|
||||
}
|
||||
|
||||
local story = dofile("data/scripts/story.lua")
|
||||
local tower = story.POKEMON_TOWER_2F
|
||||
check(tower ~= nil, "POKEMON_TOWER_2F map script exists")
|
||||
check(type(tower.rivalScript) == "function", "rivalScript helper exported")
|
||||
check(type(tower.onStep) == "function", "onStep ambush exists")
|
||||
check(type(tower.onEnter) == "function", "onEnter hide repair exists")
|
||||
|
||||
local RIGHT_THEN_DOWN =
|
||||
{ "right", "down", "down", "right", "down", "down", "right", "right" }
|
||||
local DOWN_THEN_RIGHT =
|
||||
{ "down", "down", "right", "right", "right", "right", "down", "down" }
|
||||
|
||||
local function dirsEqual(a, b)
|
||||
if type(a) ~= "table" or #a ~= #b then return false end
|
||||
for i = 1, #b do if a[i] ~= b[i] then return false end end
|
||||
return true
|
||||
end
|
||||
|
||||
local function findWalk(rows)
|
||||
for _, r in ipairs(rows) do
|
||||
if r[1] == "walk_npc" then return r end
|
||||
end
|
||||
end
|
||||
|
||||
-- (1) both approach tiles pick the pokered exit movement
|
||||
do
|
||||
local below = tower.rivalScript(14)
|
||||
local left = tower.rivalScript(15)
|
||||
local wBelow, wLeft = findWalk(below), findWalk(left)
|
||||
check(wBelow ~= nil, "player-below script has walk_npc")
|
||||
check(wLeft ~= nil, "rival-on-left script has walk_npc")
|
||||
eq(wBelow[2], 1, "walk targets object index 1 (rival)")
|
||||
check(dirsEqual(wBelow[3], RIGHT_THEN_DOWN),
|
||||
"player at x=14 uses RightThenDownMovement")
|
||||
check(dirsEqual(wLeft[3], DOWN_THEN_RIGHT),
|
||||
"player at x=15 uses DownThenRightMovement")
|
||||
end
|
||||
|
||||
-- (2) win path: flag, defeat text, walk, hide -- in order
|
||||
do
|
||||
local rows = tower.rivalScript(14)
|
||||
local preds = {
|
||||
{ "set_flag EVENT_BEAT_POKEMON_TOWER_RIVAL",
|
||||
function(r) return r[1] == "set_flag"
|
||||
and r[2] == "EVENT_BEAT_POKEMON_TOWER_RIVAL" end },
|
||||
{ "show_text DefeatedText",
|
||||
function(r) return r[1] == "show_text"
|
||||
and r[2] == "_PokemonTower2FRivalDefeatedText" end },
|
||||
{ "walk_npc exit",
|
||||
function(r) return r[1] == "walk_npc" end },
|
||||
{ "hide_object POKEMONTOWER2F_RIVAL",
|
||||
function(r) return r[1] == "hide_object"
|
||||
and r[2] == "POKEMON_TOWER_2F"
|
||||
and r[3] == "POKEMONTOWER2F_RIVAL" end },
|
||||
}
|
||||
local pi = 1
|
||||
for _, r in ipairs(rows) do
|
||||
if pi <= #preds and preds[pi][2](r) then pi = pi + 1 end
|
||||
end
|
||||
for i = 1, #preds do
|
||||
check(i < pi, "rival script has, in order: " .. preds[i][1])
|
||||
end
|
||||
end
|
||||
|
||||
-- (3) onStep: only the encounter coords, and not after the beat flag
|
||||
do
|
||||
local ran
|
||||
local game = { save = { flags = {} }, data = {} }
|
||||
local ow = {
|
||||
runner = {
|
||||
isRunning = function() return false end,
|
||||
run = function(_, rows) ran = rows end,
|
||||
},
|
||||
player = { facing = "down" },
|
||||
npcByIndex = function() return { def = { name = "POKEMONTOWER2F_RIVAL" } } end,
|
||||
}
|
||||
check(tower.onStep(game, ow, 15, 5), "onStep fires on (15,5)")
|
||||
check(ran ~= nil and findWalk(ran) ~= nil, "onStep runs exit-walk script")
|
||||
check(dirsEqual(findWalk(ran)[3], DOWN_THEN_RIGHT),
|
||||
"onStep (15,5) picks DownThenRight")
|
||||
eq(ow.player.facing, "left", "onStep (15,5) faces the rival")
|
||||
|
||||
ran = nil
|
||||
check(tower.onStep(game, ow, 14, 6), "onStep fires on (14,6)")
|
||||
check(dirsEqual(findWalk(ran)[3], RIGHT_THEN_DOWN),
|
||||
"onStep (14,6) picks RightThenDown")
|
||||
|
||||
check(not tower.onStep(game, ow, 14, 5), "onStep ignores rival's own tile")
|
||||
game.save.flags.EVENT_BEAT_POKEMON_TOWER_RIVAL = true
|
||||
check(not tower.onStep(game, ow, 15, 5), "beaten: onStep is inert")
|
||||
end
|
||||
|
||||
-- (4) onEnter hides a stuck rival once the beat flag is set
|
||||
do
|
||||
local Commands = require("src.script.Commands")
|
||||
local hidden = {}
|
||||
local realHide = Commands.hide_object
|
||||
Commands.hide_object = function(_, mapId, name)
|
||||
hidden[#hidden + 1] = { mapId, name }
|
||||
end
|
||||
|
||||
tower.onEnter({ save = { flags = {} } }, {})
|
||||
eq(#hidden, 0, "onEnter: no hide while unbeaten")
|
||||
|
||||
tower.onEnter({
|
||||
save = { flags = { EVENT_BEAT_POKEMON_TOWER_RIVAL = true } },
|
||||
}, {})
|
||||
eq(#hidden, 1, "onEnter: hide once beaten")
|
||||
eq(hidden[1][1], "POKEMON_TOWER_2F", "onEnter hide map")
|
||||
eq(hidden[1][2], "POKEMONTOWER2F_RIVAL", "onEnter hide object")
|
||||
|
||||
Commands.hide_object = realHide
|
||||
end
|
||||
|
||||
S.finish()
|
||||
@@ -0,0 +1,39 @@
|
||||
-- #114: launcher must restore the arrow cursor when leaving for boot.
|
||||
-- Self-contained: `luajit tests/rom_importer_cursor_test.lua`; also dofile'd
|
||||
-- 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 S = require("tests.harness").suite("rom importer cursor")
|
||||
local eq = S.eq
|
||||
|
||||
local currentCursor = "arrow"
|
||||
love.mouse.isCursorSupported = function() return true end
|
||||
love.mouse.getSystemCursor = function(name) return name end
|
||||
love.mouse.setCursor = function(c) currentCursor = c or "arrow" end
|
||||
|
||||
local RomImporter = require("src.import.RomImporter")
|
||||
|
||||
local booted = nil
|
||||
local ri = setmetatable({
|
||||
android = false,
|
||||
workState = nil,
|
||||
ready = { red = true, blue = false },
|
||||
onComplete = function(version) booted = version end,
|
||||
}, RomImporter)
|
||||
|
||||
-- Simulate leaving Play while the hand cursor is still active (hover).
|
||||
currentCursor = "hand"
|
||||
ri:play("red")
|
||||
eq(booted, "red", "play boots the chosen version")
|
||||
eq(currentCursor, "arrow", "play restores the arrow cursor before boot")
|
||||
|
||||
-- Android / unsupported cursors must not error.
|
||||
booted = nil
|
||||
currentCursor = "hand"
|
||||
ri.android = true
|
||||
ri:play("red")
|
||||
eq(booted, "red", "android play still boots")
|
||||
eq(currentCursor, "hand", "android play leaves the cursor alone")
|
||||
|
||||
S.finish()
|
||||
@@ -2672,6 +2672,9 @@ end
|
||||
-- ---------------------------------------------- input hold regressions
|
||||
runSuites({ "tests/input_hold_test.lua" })
|
||||
|
||||
-- ---------------------------------------------- launcher cursor (#114)
|
||||
runSuites({ "tests/rom_importer_cursor_test.lua" })
|
||||
|
||||
-- ---------------------------------------------- parity workstream tests
|
||||
-- Each tests/parity_*.lua is a self-contained file (own bootstrap + check,
|
||||
-- error()s if any assertion fails). Globbed, so dropping a new parity
|
||||
|
||||
Reference in New Issue
Block a user