mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-15 07:41:21 +02:00
This commit is contained in:
@@ -191,12 +191,12 @@ return function(game)
|
||||
return cond()
|
||||
end
|
||||
|
||||
-- Jump the flash clock to just under FLASH_FRAMES (220) once the shot is
|
||||
-- Jump the flash clock to just under FLASH_FRAMES (368) once the shot is
|
||||
-- taken. EvolutionState:update only compares self.t against that constant,
|
||||
-- so this ends the animation early without touching the palette logic.
|
||||
local function skipToEnd()
|
||||
local st = evoTop()
|
||||
if st then st.t = 214 end
|
||||
if st then st.t = 362 end
|
||||
end
|
||||
|
||||
local function startEvo(species, into)
|
||||
@@ -212,7 +212,7 @@ return function(game)
|
||||
|
||||
-- ---- case 1: the reported case, SGB, mid-flash --------------------------
|
||||
local weedle = startEvo("WEEDLE", "KAKUNA")
|
||||
U.wait(24) -- into the flash, far short of FLASH_FRAMES = 220
|
||||
U.wait(24) -- into the flash, far short of FLASH_FRAMES = 368
|
||||
U.shot(game, DIR .. "/evo279_1_flash_sgb.png")
|
||||
local c1 = probe("flash/SGB", {
|
||||
monYellow1 = YELLOW[2], monYellow2 = YELLOW[3],
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
-- LINK_STATE_TRADING) skip that poll and cannot be cancelled.
|
||||
--
|
||||
-- Case 1 (level path, cancelable): open EvolutionState directly, wait past
|
||||
-- the 80-frame pre-animLoop delay (still well under FLASH_FRAMES=220),
|
||||
-- the 80-frame pre-animLoop delay (still well under FLASH_FRAMES=368),
|
||||
-- press B, and assert the mon stays CATERPIE with the "stopped evolving"
|
||||
-- text on screen.
|
||||
-- Case 1b: after cancel, checkParty with no level-ups must not re-offer;
|
||||
@@ -81,7 +81,7 @@ return function(game)
|
||||
Evolution.evolve(game, mon, "METAPOD", function() done1 = true end)
|
||||
|
||||
if not waitFor(evoTop, 300) then error("EvolutionState never opened (case1)") end
|
||||
U.wait(100) -- past the 80-frame pre-animLoop delay, still under 220
|
||||
U.wait(100) -- past the 80-frame pre-animLoop delay, still under 368
|
||||
U.log("case1 flash", "t=", top().t, "species=", mon.species)
|
||||
U.shot(game, DIR .. "/evo213_1_evolving.png")
|
||||
|
||||
@@ -133,8 +133,8 @@ return function(game)
|
||||
local done2 = false
|
||||
Evolution.evolve(game, mon2, "METAPOD", function() done2 = true end)
|
||||
if not waitFor(evoTop, 300) then error("EvolutionState never opened (case2)") end
|
||||
-- let the full flash run (FLASH_FRAMES=220) without pressing B
|
||||
waitFor(function() return not evoTop() end, 400)
|
||||
-- let the full flash run (FLASH_FRAMES=368) without pressing B
|
||||
waitFor(function() return not evoTop() end, 500)
|
||||
if not waitFor(function() return findText("evolved into") ~= nil end, 120) then
|
||||
error("Congratulations text not shown (case2)")
|
||||
end
|
||||
|
||||
@@ -89,8 +89,8 @@ return function(game)
|
||||
Evolution.evolve(game, mon, "GYARADOS", function() done1 = true end)
|
||||
|
||||
if not waitFor(evoTop, 300) then error("EvolutionState never opened (case1)") end
|
||||
-- let the full flash run (FLASH_FRAMES=220) with no input, then apply
|
||||
waitFor(function() return not evoTop() end, 400)
|
||||
-- let the full flash run (FLASH_FRAMES=368) with no input, then apply
|
||||
waitFor(function() return not evoTop() end, 500)
|
||||
if not waitFor(function() return findText("evolved into") ~= nil end, 120) then
|
||||
error("Congratulations text not shown (case1)")
|
||||
end
|
||||
@@ -124,7 +124,7 @@ return function(game)
|
||||
local done2 = false
|
||||
Evolution.evolve(game, mon2, "GYARADOS", function() done2 = true end)
|
||||
if not waitFor(evoTop, 300) then error("EvolutionState never opened (case2)") end
|
||||
waitFor(function() return not evoTop() end, 400)
|
||||
waitFor(function() return not evoTop() end, 500)
|
||||
if not waitFor(function() return findText("evolved into") ~= nil end, 120) then
|
||||
error("Congratulations text not shown (case2)")
|
||||
end
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
-- Driver: a trainer who walked up to the player must be back on her spawn
|
||||
-- cell the next time her map loads, connection crossings included (#1028).
|
||||
-- .loadNewMap calls LoadMapHeader for a seam crossing exactly as a warp does,
|
||||
-- and .loadSpriteData zeroes the sprite state data and re-seeds every
|
||||
-- SPRITESTATEDATA2_MAPY/MAPX from the map header's object data
|
||||
-- (pokered home/overworld.asm), so only the save-side defeat flag survives.
|
||||
--
|
||||
-- Route 3's Lass (data/maps/objects/Route3.asm object_event 23, 4,
|
||||
-- SPRITE_COOLTRAINER_F, STAY, LEFT, ..., OPP_LASS) has sight range 4, so
|
||||
-- standing above the third Bug Catcher at (19,4) is inside her line and she
|
||||
-- walks dist-1 = 3 cells west to (20,4) -- the reporter's exact setup.
|
||||
--
|
||||
-- POKEPORT_DRIVER=tests/drivers/trainer_reset_bug1028_test.lua \
|
||||
-- POKEPORT_IDENTITY=bug1028 POKEPORT_TOUCH=0 POKEPORT_VERSION=red love .
|
||||
return function(game)
|
||||
local U = dofile("tests/drivers/util.lua")
|
||||
local Pokemon = require("src.pokemon.Pokemon")
|
||||
local Zoom = require("src.render.Zoom")
|
||||
local DIR = os.getenv("SHOT_DIR") or "/tmp/shots"
|
||||
|
||||
local MAP = "ROUTE_3"
|
||||
local TARGET = 6
|
||||
local SPAWN = { x = 23, y = 4 }
|
||||
local STAND = { x = 19, y = 4 }
|
||||
|
||||
local pass = true
|
||||
local function check(label, ok)
|
||||
if not ok then pass = false end
|
||||
U.log(ok and "PASS" or "FAIL", label)
|
||||
return ok
|
||||
end
|
||||
|
||||
local function lass()
|
||||
local ow = game.overworld
|
||||
for _, n in ipairs(ow and ow.npcs or {}) do
|
||||
if n.def and n.def.index == TARGET then return n end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function at(n) return n and (n.cellX .. "," .. n.cellY) or "absent" end
|
||||
|
||||
-- hold a direction until cond() or the budget runs out, then let the
|
||||
-- half-finished step land
|
||||
local function holdUntil(btn, cond, budget)
|
||||
local first = true
|
||||
for _ = 1, budget or 900 do
|
||||
if cond() then break end
|
||||
if first then table.insert(game.input.pressQueue, btn); first = false end
|
||||
game.input.state[btn] = true
|
||||
coroutine.yield()
|
||||
end
|
||||
game.input.state[btn] = false
|
||||
for _ = 1, 40 do
|
||||
local ow = game.overworld
|
||||
if ow and not ow.player.moving then break end
|
||||
coroutine.yield()
|
||||
end
|
||||
U.wait(4)
|
||||
return cond()
|
||||
end
|
||||
|
||||
local function walkTo(btn, axis, want, budget)
|
||||
return holdUntil(btn, function()
|
||||
local p = game.overworld and game.overworld.player
|
||||
return p and p[axis] == want and not p.moving
|
||||
end, budget)
|
||||
end
|
||||
|
||||
local function onMap(id)
|
||||
return game.overworld and game.overworld.map and game.overworld.map.id == id
|
||||
end
|
||||
|
||||
-- mash A until the battle stack unwinds back to the overworld
|
||||
local function mashToOverworld(budget)
|
||||
for _ = 1, budget or 4000 do
|
||||
if game.stack:top() == game.overworld and not game.overworld.engaging then
|
||||
return true
|
||||
end
|
||||
U.tap(game, "a")
|
||||
U.wait(3)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- absolute zoom: reset first so every shot frames the same amount of world
|
||||
local function survey(n)
|
||||
Zoom.reset()
|
||||
U.wait(2)
|
||||
for _ = 1, n do game:zoomStep(-1) end
|
||||
U.wait(30)
|
||||
end
|
||||
|
||||
-- back up to the sighting cell so the before/after shots share a camera
|
||||
local function returnToStand()
|
||||
walkTo("right", "cellX", 11)
|
||||
walkTo("up", "cellY", 4)
|
||||
walkTo("right", "cellX", STAND.x)
|
||||
end
|
||||
|
||||
game.save.party = {
|
||||
Pokemon.new(game.data, "CHARIZARD", 80),
|
||||
Pokemon.new(game.data, "SNORLAX", 80),
|
||||
}
|
||||
game.save.player.name = "MATT"
|
||||
|
||||
U.teleport(game, MAP, STAND.x, STAND.y, "right")
|
||||
U.wait(20)
|
||||
|
||||
-- only the target Lass may engage: the reporter has already cleared the
|
||||
-- Bug Catchers and Youngsters ahead of her
|
||||
local ow = game.overworld
|
||||
for _, n in ipairs(ow.npcs) do
|
||||
if n.def.trainerClass and n.def.index ~= TARGET then
|
||||
game.save.defeatedTrainers[n.id] = true
|
||||
end
|
||||
end
|
||||
|
||||
local l = lass()
|
||||
check("Lass starts on her spawn cell " .. SPAWN.x .. "," .. SPAWN.y,
|
||||
l ~= nil and l.cellX == SPAWN.x and l.cellY == SPAWN.y)
|
||||
U.log("spawn:", at(l))
|
||||
|
||||
-- she sights the player and walks up, then the battle runs
|
||||
for _ = 1, 600 do
|
||||
if ow.engaging then break end
|
||||
coroutine.yield()
|
||||
end
|
||||
check("the Lass sighted the player at " .. STAND.x .. "," .. STAND.y, ow.engaging == true)
|
||||
check("the battle ran to completion", mashToOverworld())
|
||||
U.wait(30)
|
||||
|
||||
l = lass()
|
||||
local movedX, movedY = l and l.cellX, l and l.cellY
|
||||
U.log("after the walk-up she stands at", at(l))
|
||||
check("she is off her spawn cell after walking up",
|
||||
l ~= nil and not (movedX == SPAWN.x and movedY == SPAWN.y))
|
||||
check("she is recorded as defeated", game.save.defeatedTrainers[l.id] == true)
|
||||
|
||||
survey(4)
|
||||
U.shot(game, DIR .. "/bug1028_1_walked_up.png")
|
||||
Zoom.reset()
|
||||
U.wait(5)
|
||||
|
||||
-- west along row 4, down the single gap at column 11, then west on row 9
|
||||
-- to the Pewter City seam
|
||||
walkTo("left", "cellX", 11)
|
||||
walkTo("down", "cellY", 9)
|
||||
check("reached the descent column", game.overworld.player.cellX == 11
|
||||
and game.overworld.player.cellY == 9)
|
||||
|
||||
-- crossing x=0 westward is the seam: crossConnection, not a warp
|
||||
holdUntil("left", function() return onMap("PEWTER_CITY") end, 1200)
|
||||
check("crossed the seam into Pewter City", onMap("PEWTER_CITY"))
|
||||
|
||||
survey(12)
|
||||
U.shot(game, DIR .. "/bug1028_2_pewter_survey.png")
|
||||
Zoom.reset()
|
||||
U.wait(5)
|
||||
|
||||
-- and back east into Route 3, the map load that must re-seed her
|
||||
holdUntil("right", function() return onMap(MAP) end, 1200)
|
||||
check("crossed back into Route 3", onMap(MAP))
|
||||
U.wait(30)
|
||||
|
||||
l = lass()
|
||||
U.log("after the return crossing she stands at", at(l))
|
||||
check("she is back on her spawn cell " .. SPAWN.x .. "," .. SPAWN.y,
|
||||
l ~= nil and l.cellX == SPAWN.x and l.cellY == SPAWN.y)
|
||||
check("she is still recorded as defeated",
|
||||
l ~= nil and game.save.defeatedTrainers[l.id] == true)
|
||||
|
||||
-- same cell, same zoom as shot 1: she should now be three cells further
|
||||
-- east, and being defeated she must not re-engage on the way back in
|
||||
returnToStand()
|
||||
check("walked back to the sighting cell",
|
||||
game.overworld.player.cellX == STAND.x
|
||||
and game.overworld.player.cellY == STAND.y)
|
||||
check("a defeated trainer does not re-engage", game.overworld.engaging ~= true)
|
||||
|
||||
survey(4)
|
||||
U.shot(game, DIR .. "/bug1028_3_reset.png")
|
||||
|
||||
Zoom.reset()
|
||||
U.log(pass and "RESULT PASS" or "RESULT FAIL")
|
||||
love.event.quit(pass and 0 or 1)
|
||||
while true do coroutine.yield() end
|
||||
end
|
||||
@@ -20,7 +20,7 @@ local PartyMenu = require("src.ui.PartyMenu")
|
||||
|
||||
-- minimal stack/input doubles matching the StateStack and Input surfaces,
|
||||
-- plus an overworld stub: PALLET_TOWN's OVERWORLD tileset passes
|
||||
-- CheckIfInOutsideMap, and the badges cover the list-time HM gates
|
||||
-- CheckIfInOutsideMap
|
||||
local function newGame(moves, inventory)
|
||||
local game = {
|
||||
data = { pokemon = { LAPRAS = { name = "LAPRAS" },
|
||||
@@ -87,14 +87,15 @@ openSubmenu(pm2)
|
||||
same(actions(pm2.subItems), { "stats", "switch" },
|
||||
"no field moves: the submenu is just STATS/SWITCH")
|
||||
|
||||
-- the badge gates still filter the list: the same Lapras without the
|
||||
-- badges keeps its moves but shows none of them
|
||||
-- GetMonFieldMoves is badge-blind: the same Lapras without the badges
|
||||
-- still lists both HMs, and .outOfBattleMovePointers refuses on
|
||||
-- selection instead (#1022)
|
||||
local noBadges = newGame(
|
||||
{ { id = "STRENGTH", pp = 15 }, { id = "SURF", pp = 15 } })
|
||||
local pm3 = PartyMenu.new(noBadges, {})
|
||||
noBadges.stack:push(pm3)
|
||||
openSubmenu(pm3)
|
||||
same(actions(pm3.subItems), { "stats", "switch" },
|
||||
"ungated badges keep the HM moves out of the submenu")
|
||||
same(actions(pm3.subItems), { "strength", "surf", "stats", "switch" },
|
||||
"the HM moves are listed without the badges (#1022)")
|
||||
|
||||
T.finish("party_fieldmove_order_bug792")
|
||||
|
||||
@@ -16,6 +16,7 @@ local T = require("tests.modkit")
|
||||
local boxes = {}
|
||||
package.loaded["src.render.TextBox"] = {
|
||||
new = function(_, s, done) return { text = s, onDone = done } end,
|
||||
soundOpts = function() return {} end,
|
||||
}
|
||||
package.loaded["src.core.Sound"] = { play = function() end }
|
||||
package.loaded["src.inventory.Bag"] = {
|
||||
|
||||
@@ -187,6 +187,13 @@ local function runOne(seed)
|
||||
local function drive(side)
|
||||
local bt = side.bt
|
||||
if bt.result then return end
|
||||
local top = side.game.stack:top()
|
||||
if top and top.forceSwitch and top.party then
|
||||
for i, mon in ipairs(top.party) do
|
||||
if mon.hp > 0 then top.index = i break end
|
||||
end
|
||||
return
|
||||
end
|
||||
if bt.phase ~= "menu" then side.menuFrames = 0 end
|
||||
if bt.phase == "moveSelect" then
|
||||
local usable = {}
|
||||
|
||||
@@ -181,6 +181,13 @@ local function playMatch(hostEntry, guestEntry, rnd, label)
|
||||
local function drive(side)
|
||||
local bt = side.bt
|
||||
if bt.result then return end
|
||||
local top = side.game.stack:top()
|
||||
if top and top.forceSwitch and top.party then
|
||||
for i, mon in ipairs(top.party) do
|
||||
if mon.hp > 0 then top.index = i break end
|
||||
end
|
||||
return
|
||||
end
|
||||
if bt.phase ~= "menu" then side.menuFrames = 0 end
|
||||
if bt.phase == "moveSelect" then
|
||||
local usable = {}
|
||||
|
||||
@@ -604,7 +604,7 @@ do
|
||||
local game = uiGame({ mon })
|
||||
Bag.add(game.save, "RARE_CANDY", 1)
|
||||
game.stack:push(BagMenu.new(game))
|
||||
for _ = 1, 600 do
|
||||
for _ = 1, 800 do
|
||||
local top = game.stack:top()
|
||||
if not top then break end
|
||||
pressed = { a = true }
|
||||
|
||||
@@ -584,7 +584,9 @@ check(not fpm.submenu and forced == fgame.save.party[1],
|
||||
-- ------- issues #320/#385: the STRENGTH texts print over the party menu
|
||||
do
|
||||
local owStub = { strengthActive = false,
|
||||
map = { def = { tileset = "OVERWORLD" } }, dark = false }
|
||||
map = { def = { tileset = "OVERWORLD" } }, dark = false,
|
||||
partyKnows = function(self, id) return self.knows == id end,
|
||||
knows = "STRENGTH" }
|
||||
local sgame = partyGame()
|
||||
sgame.overworld = owStub
|
||||
sgame.data.text = {} -- the strength texts fall back to Strings sources
|
||||
|
||||
@@ -49,6 +49,14 @@ function Link.prepare(data)
|
||||
return Input
|
||||
end
|
||||
|
||||
local function steerReplacement(game)
|
||||
local top = game.stack:top()
|
||||
if not (top and top.forceSwitch and top.party) then return end
|
||||
for i, mon in ipairs(top.party) do
|
||||
if mon.hp > 0 then top.index = i return end
|
||||
end
|
||||
end
|
||||
|
||||
-- run a full lockstep battle over a loopback pair, mashing A on both
|
||||
-- sides, and report whether any turn's hashes disagreed
|
||||
function Link.lockstep(gameA, gameB, opts)
|
||||
@@ -80,7 +88,9 @@ function Link.lockstep(gameA, gameB, opts)
|
||||
while (resA == nil or resB == nil) and guard < limit do
|
||||
guard = guard + 1
|
||||
Input.pressed = { a = true }
|
||||
steerReplacement(gameA)
|
||||
gameA.stack:update(1 / 60)
|
||||
steerReplacement(gameB)
|
||||
gameB.stack:update(1 / 60)
|
||||
end
|
||||
|
||||
|
||||
+11
-4
@@ -315,12 +315,19 @@ do
|
||||
local Renderer = require("src.render.Renderer")
|
||||
local SaveData = require("src.core.SaveData")
|
||||
local OW = require("src.world.OverworldController")
|
||||
-- the reward text is a CHAIN of boxes split at each gym script's sound
|
||||
-- command, so walk it: read a box's pages, close it, let its onDone push
|
||||
-- the next one
|
||||
local function stackedDialogue()
|
||||
local top = Game.stack:top()
|
||||
if not (top and top.pages) then return "" end
|
||||
local parts = {}
|
||||
for _, page in ipairs(top.pages) do
|
||||
parts[#parts + 1] = table.concat(page, "\n")
|
||||
local top = Game.stack:top()
|
||||
while top and top.pages do
|
||||
for _, page in ipairs(top.pages) do
|
||||
parts[#parts + 1] = table.concat(page, "\n")
|
||||
end
|
||||
Game.stack:pop()
|
||||
if top.onDone then top.onDone() end
|
||||
top = Game.stack:top()
|
||||
end
|
||||
return table.concat(parts, "\n")
|
||||
end
|
||||
|
||||
+10
-8
@@ -176,7 +176,8 @@ check(not onStack(pmSurf), "party menu closes after a successful SURF")
|
||||
check(sawText("got on"), "_SurfingGotOnText shown on a successful SURF")
|
||||
|
||||
-- ===========================================================================
|
||||
-- I: list-time badge filter, CUT/SURF/STRENGTH absent without the badge
|
||||
-- I: GetMonFieldMoves is badge-blind -- CUT/SURF/STRENGTH are listed with or
|
||||
-- without the badge, and .outOfBattleMovePointers refuses on selection (#1022)
|
||||
-- ===========================================================================
|
||||
Game.save.party = { mkMon("SQUIRTLE", "CUT", "SURF", "STRENGTH") }
|
||||
Game.save.inventory = {}
|
||||
@@ -185,8 +186,8 @@ local pmNoBadge = PartyMenu.new(Game)
|
||||
Game.stack:push(pmNoBadge)
|
||||
frame({ "a" })
|
||||
local actsOff = submenuActions(pmNoBadge)
|
||||
check(not actsOff.cut and not actsOff.surf and not actsOff.strength,
|
||||
"no CUT/SURF/STRENGTH submenu entries without the required badges")
|
||||
check(actsOff.cut and actsOff.surf and actsOff.strength,
|
||||
"CUT/SURF/STRENGTH submenu entries listed without the badges (#1022)")
|
||||
popToOW()
|
||||
Game.save.inventory = { CASCADEBADGE = true, SOULBADGE = true, RAINBOWBADGE = true }
|
||||
local pmBadge = PartyMenu.new(Game)
|
||||
@@ -194,7 +195,7 @@ Game.stack:push(pmBadge)
|
||||
frame({ "a" })
|
||||
local actsOn = submenuActions(pmBadge)
|
||||
check(actsOn.cut and actsOn.surf and actsOn.strength,
|
||||
"CUT/SURF/STRENGTH submenu entries appear once the badges are held")
|
||||
"CUT/SURF/STRENGTH submenu entries still there once the badges are held")
|
||||
|
||||
-- ===========================================================================
|
||||
-- I: CUT from the party menu. The Cerulean tree BLOCK (50, at block 9,14)
|
||||
@@ -471,8 +472,9 @@ local pmLobby = PartyMenu.new(Game)
|
||||
Game.stack:push(pmLobby)
|
||||
frame({ "a" })
|
||||
local actsLobby = submenuActions(pmLobby)
|
||||
check(not actsLobby.fly, "FLY omitted inside Indigo Plateau lobby")
|
||||
check(not actsLobby.escape, "TELEPORT omitted inside Indigo Plateau lobby")
|
||||
check(actsLobby.fly, "FLY still listed inside Indigo Plateau lobby (#1022)")
|
||||
check(actsLobby.escape,
|
||||
"TELEPORT still listed inside Indigo Plateau lobby (#1022)")
|
||||
popToOW()
|
||||
|
||||
-- restore fainted field-move mon for the STRENGTH/SURF cases below
|
||||
@@ -486,8 +488,8 @@ Game.save.inventory = {
|
||||
ow = pushOW("SEAFOAM_ISLANDS_1F", 17, 10, "right")
|
||||
clearCaptured()
|
||||
local pmFaintStr = PartyMenu.new(Game)
|
||||
-- Seafoam is not OVERWORLD, so FLY is omitted: CUT, STRENGTH, SURF, STATS, SWITCH
|
||||
selectSubItem(pmFaintStr, 2)
|
||||
-- move order on the mon: FLY, CUT, STRENGTH, SURF, then STATS, SWITCH
|
||||
selectSubItem(pmFaintStr, 3)
|
||||
eq(Game.overworld.strengthActive, true,
|
||||
"fainted mon can activate STRENGTH from the party menu")
|
||||
check(sawText("used") and sawText("STRENGTH"),
|
||||
|
||||
@@ -72,13 +72,19 @@ Game.input = Input; Input:init()
|
||||
Game.renderer = Renderer; Renderer:init()
|
||||
Game.stack = StateStack; StateStack:init()
|
||||
|
||||
-- concatenates the pages of the TextBox checkVictoryRewards pushed
|
||||
-- concatenates the pages of the box CHAIN checkVictoryRewards pushed: the
|
||||
-- reward text splits into a box per gym-script sound command, so close each
|
||||
-- one and let its onDone push the next
|
||||
local function stackedDialogue()
|
||||
local top = Game.stack:top()
|
||||
if not (top and top.pages) then return "" end
|
||||
local parts = {}
|
||||
for _, page in ipairs(top.pages) do
|
||||
parts[#parts + 1] = table.concat(page, "\n")
|
||||
local top = Game.stack:top()
|
||||
while top and top.pages do
|
||||
for _, page in ipairs(top.pages) do
|
||||
parts[#parts + 1] = table.concat(page, "\n")
|
||||
end
|
||||
Game.stack:pop()
|
||||
if top.onDone then top.onDone() end
|
||||
top = Game.stack:top()
|
||||
end
|
||||
return table.concat(parts, "\n")
|
||||
end
|
||||
|
||||
@@ -178,6 +178,7 @@ check(#moves == 0, "the parked Pikachu of the confused beat stays put")
|
||||
-- BillsHouseScript0 skips the whole entry beat for a statused starter
|
||||
-- (CheckPikachuStatusCondition, scripts/BillsHouse.asm:45-46)
|
||||
ow.pikachuBillsScene = nil
|
||||
yellowGame.save.pikachuMapScriptActive = nil
|
||||
moves = {}
|
||||
yellowGame.save.party = { { species = "PIKACHU", hp = 12, status = "PAR" } }
|
||||
PikachuFollower.onBillsHouseEnter(yellowGame, ow)
|
||||
|
||||
@@ -42,7 +42,7 @@ local ow = {
|
||||
|
||||
Follower.onFanClubEntered({ save = save, data = Data }, ow)
|
||||
check(ow.pikachuFanClubScene, "Fan Club disables normal Pikachu following")
|
||||
check(ow.pikachuMapScriptActive, "Fan Club sets the map-script flag")
|
||||
check(save.pikachuMapScriptActive, "Fan Club sets the map-script flag")
|
||||
eq(ow.player.facing, "down", "Fan Club resets the player direction")
|
||||
eq(moves[1] and moves[1][1], "up", "Fan Club starts with slide-up displacement")
|
||||
eq(moves[1] and moves[1][2], 1, "Fan Club slide-up spans one tile")
|
||||
|
||||
+2
-2
@@ -2562,8 +2562,8 @@ do
|
||||
end
|
||||
eq(fanfares, 1, "one caught fanfare per capture")
|
||||
eq(tinks, 3, "three wobble tinks on a $43 capture")
|
||||
check(fanfareAt and caughtAt and fanfareAt < caughtAt,
|
||||
"Caught_Mon sounds with the caught text, not after its dismissal")
|
||||
check(fanfareAt and caughtAt and caughtAt < fanfareAt,
|
||||
"Caught_Mon sounds once the caught text is out, before its prompt")
|
||||
eq(cb4.result, "caught", "the capture resolved the battle")
|
||||
-- the nickname AskName that follows clears it (ClearSprites), so the
|
||||
-- assertion is sampled while the caught text is up
|
||||
|
||||
Reference in New Issue
Block a user