mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
more buggies CLOSES #960, CLOSES #961, CLOSES #968, CLOSES #995, CLOSES #1006, CLOSES #1009, CLOSES #1013, CLOSES #1021, CLOSES #1031, CLOSES #1044, CLOSES #1049, CLOSES #1050, CLOSES #1045,,
This commit is contained in:
@@ -248,7 +248,7 @@ return function(game)
|
||||
|
||||
-- ---- case 2: B-cancel settles on the OLD form's colours -----------------
|
||||
local karp = startEvo("MAGIKARP", "GYARADOS")
|
||||
U.wait(24)
|
||||
U.wait(100) -- past the 80-frame pre-animLoop delay before the poll starts
|
||||
U.hold(game, "b", 20) -- evolution.asm Evolution_CheckForCancel
|
||||
if not waitFor(function() return findText("stopped evolving") ~= nil end, 240) then
|
||||
check(false, "holding B prints \"stopped evolving\"")
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
-- Driver: cancel an evolution with the B button (#213).
|
||||
--
|
||||
-- pokered engine/pokemon/evos_moves.asm polls hJoyHeld during the pic
|
||||
-- flash: holding B aborts the evolution (the mon keeps its species and
|
||||
-- pokered engine/movie/evolution.asm polls hJoy5 during the pic flash: a
|
||||
-- fresh B press aborts the evolution (the mon keeps its species and
|
||||
-- _StoppedEvolvingText prints). Trade evolutions (wLinkState ==
|
||||
-- LINK_STATE_TRADING) skip that poll and cannot be cancelled.
|
||||
--
|
||||
-- Case 1 (level path, cancelable): open EvolutionState directly, wait a
|
||||
-- few frames into the flash (t well under FLASH_FRAMES=220), hold B, and
|
||||
-- assert the mon stays CATERPIE with "stopped evolving" text on screen.
|
||||
-- Case 1 (level path, cancelable): open EvolutionState directly, wait past
|
||||
-- the 80-frame pre-animLoop delay (still well under FLASH_FRAMES=220),
|
||||
-- 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;
|
||||
-- a subsequent level-up set must offer again (EvolveAfterBattle parity).
|
||||
-- Case 2 (control): let the flash run to completion with no input and
|
||||
@@ -80,11 +81,11 @@ 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(20) -- into the flash, well under FLASH_FRAMES=220
|
||||
U.wait(100) -- past the 80-frame pre-animLoop delay, still under 220
|
||||
U.log("case1 flash", "t=", top().t, "species=", mon.species)
|
||||
U.shot(game, DIR .. "/evo213_1_evolving.png")
|
||||
|
||||
U.hold(game, "b", 20) -- Gen1 hJoyHeld B-cancel
|
||||
U.hold(game, "b", 20) -- Gen1 hJoy5 B-cancel
|
||||
|
||||
-- the flash aborts: EvolutionState is no longer the top (the stopped
|
||||
-- text overlays it and then pops it)
|
||||
@@ -118,7 +119,7 @@ return function(game)
|
||||
if not waitFor(evoTop, 300) then
|
||||
error("EvolutionState never opened after level-up re-offer")
|
||||
end
|
||||
U.wait(20)
|
||||
U.wait(100) -- past the same 80-frame pre-animLoop delay
|
||||
U.hold(game, "b", 20) -- cancel so case 2 stays independent
|
||||
if not waitFor(function() return not evoTop() end, 240) then
|
||||
error("level-up re-offer did not abort on B")
|
||||
|
||||
@@ -0,0 +1,484 @@
|
||||
-- Ear check for the PC, bump, door/stairs and battle menu SFX (#960, #961, #1044, #1045).
|
||||
-- POKEPORT_DRIVER=tests/drivers/menu_sfx_bug960_bug961_bug1044_bug1045_test.lua POKEPORT_IDENTITY=sfx960 POKEPORT_TOUCH=0 POKEPORT_VERSION=red SHOT_DIR=/tmp/shots love .
|
||||
return function(game)
|
||||
local U = dofile("tests/drivers/util.lua")
|
||||
local DIR = os.getenv("SHOT_DIR") or "/tmp/shots"
|
||||
local BattleState = require("src.battle.BattleState")
|
||||
local Boxes = require("src.pokemon.Boxes")
|
||||
local Menu = require("src.ui.Menu")
|
||||
local Pokemon = require("src.pokemon.Pokemon")
|
||||
local Sound = require("src.core.Sound")
|
||||
local Strings = require("src.core.Strings")
|
||||
local Timing = require("src.core.Timing")
|
||||
|
||||
local FADE = Timing.WARP_FADE_OUT
|
||||
|
||||
local pass, fail = 0, 0
|
||||
local function check(label, ok)
|
||||
if ok then pass = pass + 1 else fail = fail + 1 end
|
||||
U.log(ok and "PASS" or "FAIL", label)
|
||||
return ok
|
||||
end
|
||||
|
||||
-- every cue this run makes, forwarded so playback is untouched; the frame
|
||||
local cues = {}
|
||||
local realPlay = Sound.play
|
||||
Sound.play = function(data, name)
|
||||
cues[#cues + 1] = { name = name, frame = U.frame() }
|
||||
return realPlay(data, name)
|
||||
end
|
||||
local function since(mark)
|
||||
local names = {}
|
||||
for i = mark + 1, #cues do names[#names + 1] = cues[i].name end
|
||||
return #names > 0 and table.concat(names, ", ") or "nothing"
|
||||
end
|
||||
local function heard(mark, want)
|
||||
for i = mark + 1, #cues do
|
||||
if cues[i].name == want then return cues[i] end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
local function countOf(mark, want)
|
||||
local n = 0
|
||||
for i = mark + 1, #cues do if cues[i].name == want then n = n + 1 end end
|
||||
return n
|
||||
end
|
||||
|
||||
-- ---- what the ear cannot check -----------------------------------------
|
||||
local opts = game.save.options or {}
|
||||
local sfxVol, musicVol = opts.sfxVol or 0, opts.musicVol or 0
|
||||
if sfxVol == 0 then
|
||||
U.log("FAIL SFX volume is 0. Every line below is about a sound that is or")
|
||||
U.log(" is not there, and at 0 none of them are. Set SFX to 7 in OPTION")
|
||||
U.log(" and start over, or this run proves nothing at all.")
|
||||
end
|
||||
check(("sfx volume %d/7, music volume %d/7"):format(sfxVol, musicVol),
|
||||
sfxVol > 0)
|
||||
|
||||
-- an unresolved key is silent in exactly the way these bugs were
|
||||
local sfx = (game.data.audio or {}).sfx or {}
|
||||
for _, key in ipairs({ "Turn_On_PC", "Turn_Off_PC", "Enter_PC", "Collision",
|
||||
"Save", "Go_Inside", "Go_Outside", "Press_AB" }) do
|
||||
check("sfx " .. key .. " is in the generated audio", sfx[key] ~= nil)
|
||||
end
|
||||
|
||||
-- positions come from data/events/hidden_events.asm and data/maps/objects/*.asm
|
||||
local extras = game.data.field.hiddenExtras or {}
|
||||
local pcTiles = extras.pcTiles or {}
|
||||
local bedroomPC = (pcTiles.REDS_HOUSE_2F or {})[1]
|
||||
local centerPC = (pcTiles.VIRIDIAN_POKECENTER or {})[1]
|
||||
check("REDS_HOUSE_2F carries the OpenRedsPC tile", bedroomPC ~= nil)
|
||||
check("VIRIDIAN_POKECENTER carries a PC tile", centerPC ~= nil)
|
||||
|
||||
local function warpTo(mapId, destMap)
|
||||
for _, w in ipairs((game.data.maps[mapId] or {}).warps or {}) do
|
||||
if w.destMap == destMap then return w end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
local houseDoor = warpTo("PALLET_TOWN", "REDS_HOUSE_1F")
|
||||
local houseExit = warpTo("REDS_HOUSE_1F", "LAST_MAP")
|
||||
local houseStairs = warpTo("REDS_HOUSE_1F", "REDS_HOUSE_2F")
|
||||
local centerDoor = warpTo("VIRIDIAN_CITY", "VIRIDIAN_POKECENTER")
|
||||
check("PALLET_TOWN has the door into REDS_HOUSE_1F", houseDoor ~= nil)
|
||||
check("REDS_HOUSE_1F has an exit mat and the stairs up",
|
||||
houseExit ~= nil and houseStairs ~= nil)
|
||||
check("VIRIDIAN_CITY has the door into its POKéMON CENTER", centerDoor ~= nil)
|
||||
|
||||
-- ---- helpers ------------------------------------------------------------
|
||||
local DIRS = { up = { 0, -1 }, down = { 0, 1 },
|
||||
left = { -1, 0 }, right = { 1, 0 } }
|
||||
local ORDER = { "down", "up", "left", "right" }
|
||||
|
||||
local function pressStep(dir)
|
||||
table.insert(game.input.pressQueue, dir)
|
||||
game.input.state[dir] = true
|
||||
U.wait(1)
|
||||
end
|
||||
|
||||
local function stand(mapId, x, y, facing)
|
||||
U.teleport(game, mapId, x, y, facing)
|
||||
U.wait(15)
|
||||
return game.overworld
|
||||
end
|
||||
|
||||
-- the cell you stand on to face (cx, cy) from `dir`, i.e. one step back
|
||||
local function cellBehind(cx, cy, dir)
|
||||
local d = DIRS[dir]
|
||||
return cx - d[1], cy - d[2]
|
||||
end
|
||||
|
||||
-- first walkable free neighbour of (cx, cy), plus the facing that looks at
|
||||
local function approach(ow, cx, cy)
|
||||
for _, dir in ipairs(ORDER) do
|
||||
local sx, sy = cellBehind(cx, cy, dir)
|
||||
if ow.map:isWalkableCell(sx, sy) and not ow:npcAtCell(sx, sy) then
|
||||
return sx, sy, dir
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- any free walkable cell on the map with a solid one next to it, so the
|
||||
local function findWall(ow)
|
||||
local map = ow.map
|
||||
for cy = 0, map.heightCells - 1 do
|
||||
for cx = 0, map.widthCells - 1 do
|
||||
if map:isWalkableCell(cx, cy) and not map:warpAtCell(cx, cy)
|
||||
and not ow:npcAtCell(cx, cy) then
|
||||
for _, d in ipairs(ORDER) do
|
||||
local dd = DIRS[d]
|
||||
if not map:isWalkableCell(cx + dd[1], cy + dd[2]) then
|
||||
return cx, cy, d
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function npcNamed(ow, name)
|
||||
for _, n in ipairs(ow.npcs or {}) do
|
||||
if n.def and n.def.name == name then return n end
|
||||
end
|
||||
end
|
||||
|
||||
-- PlayMapChangeSound (home/overworld.asm:690) plays before GBFadeOutToBlack,
|
||||
local function takeDoor(dir, want, label)
|
||||
local mark = #cues
|
||||
local from = game.overworld.map.id
|
||||
local cue, switched
|
||||
for _ = 1, 300 do
|
||||
if cue or switched then
|
||||
game.input.state[dir] = false
|
||||
U.wait(1)
|
||||
else
|
||||
pressStep(dir)
|
||||
end
|
||||
cue = cue or heard(mark, want)
|
||||
local ow = game.overworld
|
||||
if not switched and ow and ow.map.id ~= from then switched = U.frame() end
|
||||
if cue and switched then break end
|
||||
end
|
||||
game.input.state[dir] = false
|
||||
U.wait(50) -- PlayerStepOutFromDoor walks off the mat before anything else
|
||||
if not (cue and switched) then
|
||||
check(("%s: %s played and the map changed"):format(label, want), false)
|
||||
U.log(" cues on the way through:", since(mark))
|
||||
return
|
||||
end
|
||||
check(("%s: %s fired %d frames before the map switched (the fade is %d)")
|
||||
:format(label, want, switched - cue.frame, FADE),
|
||||
switched - cue.frame >= FADE - 8)
|
||||
end
|
||||
|
||||
-- CollisionCheckOnLand (home/overworld.asm): a sprite in the way takes the
|
||||
local function bumpInto(dir, label)
|
||||
local mark = #cues
|
||||
for _ = 1, 10 do pressStep(dir) end
|
||||
game.input.state[dir] = false
|
||||
U.wait(24) -- past the 16-frame bumpCooldown, so the next bump is its own
|
||||
check(label .. " rings Collision", heard(mark, "Collision") ~= nil)
|
||||
end
|
||||
|
||||
local function topIs(class)
|
||||
local top = game.stack:top()
|
||||
return top ~= nil and getmetatable(top) == class
|
||||
end
|
||||
local function rowIndex(menu, label)
|
||||
for i, item in ipairs(menu.items or {}) do
|
||||
if item.label == label then return i end
|
||||
end
|
||||
end
|
||||
-- rows come and go with save state and the ui.pc.items hook, so pick each
|
||||
local function choose(menu, label)
|
||||
local i = rowIndex(menu, label)
|
||||
if not i then
|
||||
check("the menu has a " .. label .. " row", false)
|
||||
return false
|
||||
end
|
||||
menu.index = i
|
||||
menu:clampScroll()
|
||||
U.wait(2)
|
||||
U.tap(game, "a")
|
||||
U.wait(26)
|
||||
return true
|
||||
end
|
||||
local function mash(cond, tries)
|
||||
for _ = 1, tries or 40 do
|
||||
if cond() then return true end
|
||||
U.tap(game, "a")
|
||||
U.wait(6)
|
||||
end
|
||||
return cond()
|
||||
end
|
||||
|
||||
game.save.party = { Pokemon.new(game.data, "BULBASAUR", 20) }
|
||||
game.save.party[1].moves = {
|
||||
{ id = "TACKLE", pp = 35, maxPP = 35 },
|
||||
{ id = "VINE_WHIP", pp = 10, maxPP = 10 },
|
||||
}
|
||||
Boxes.ensure(game.save)
|
||||
game.save.currentBox = 1
|
||||
|
||||
-- ---- the door into the house, from outside (#961) -----------------------
|
||||
if houseDoor then
|
||||
local sx, sy = cellBehind(houseDoor.x, houseDoor.y, "up")
|
||||
stand("PALLET_TOWN", sx, sy, "up")
|
||||
takeDoor("up", "Go_Inside", "walking into RED's house")
|
||||
end
|
||||
|
||||
-- walking into MOM, and into a wall (#960); teleport is only a fallback
|
||||
local ow = game.overworld
|
||||
if ow.map.id ~= "REDS_HOUSE_1F" and houseExit then
|
||||
ow = stand("REDS_HOUSE_1F", houseExit.x, houseExit.y, "up")
|
||||
end
|
||||
local mom = npcNamed(ow, "REDSHOUSE1F_MOM")
|
||||
check("MOM is loaded on REDS_HOUSE_1F", mom ~= nil)
|
||||
if mom then
|
||||
local sx, sy, dir = approach(ow, mom.cellX, mom.cellY)
|
||||
if sx then
|
||||
ow = stand("REDS_HOUSE_1F", sx, sy, dir)
|
||||
-- the teleport rebuilt the npc list, so pin her on the state we bump
|
||||
local pinned = npcNamed(ow, "REDSHOUSE1F_MOM")
|
||||
if pinned then pinned.frozen = true end
|
||||
U.shot(game, DIR .. "/bug960_mom.png")
|
||||
bumpInto(dir, "walking into MOM")
|
||||
else
|
||||
check("MOM has a free cell to be walked into from", false)
|
||||
end
|
||||
-- the control: a wall bump was audible before #960 too, so silence here
|
||||
local wx, wy, wdir = findWall(game.overworld)
|
||||
if wdir then
|
||||
ow = stand("REDS_HOUSE_1F", wx, wy, wdir)
|
||||
bumpInto(wdir, "walking into the wall to the " .. wdir)
|
||||
else
|
||||
check("REDS_HOUSE_1F has a wall to bump into", false)
|
||||
end
|
||||
end
|
||||
|
||||
-- ---- the stairs up (#961) -----------------------------------------------
|
||||
if houseStairs then
|
||||
local sx, sy = cellBehind(houseStairs.x, houseStairs.y, "up")
|
||||
stand("REDS_HOUSE_1F", sx, sy, "up")
|
||||
-- the destination decides the cue here, not the tile underfoot, so an
|
||||
takeDoor("up", "Go_Inside", "taking the stairs up")
|
||||
end
|
||||
|
||||
-- ---- the bedroom PC (#960) ---------------------------------------------
|
||||
if bedroomPC then
|
||||
-- OpenRedsPC's hidden_event is gated on SPRITE_FACING_UP, so the cell
|
||||
local sx, sy = cellBehind(bedroomPC.x, bedroomPC.y, "up")
|
||||
ow = stand("REDS_HOUSE_2F", sx, sy, "up")
|
||||
check("the cell below the bedroom PC is walkable",
|
||||
ow.map:isWalkableCell(sx, sy))
|
||||
local mark = #cues
|
||||
U.tap(game, "a")
|
||||
U.wait(26)
|
||||
check("A on the bedroom PC opens a menu", topIs(Menu))
|
||||
check("...and turns it on with Turn_On_PC", heard(mark, "Turn_On_PC") ~= nil)
|
||||
U.shot(game, DIR .. "/bug960_bedroom_pc.png")
|
||||
if topIs(Menu) then
|
||||
mark = #cues
|
||||
choose(game.stack:top(), Strings("LOG OFF"))
|
||||
check("LOG OFF on the bedroom PC rings Turn_Off_PC (#960)",
|
||||
heard(mark, "Turn_Off_PC") ~= nil)
|
||||
U.log(" cues:", since(mark))
|
||||
check("...and the PC closed", game.stack:top() == game.overworld)
|
||||
end
|
||||
-- B out of the same menu is ExitPlayerPC's other entry and rings it too
|
||||
U.tap(game, "a")
|
||||
U.wait(26)
|
||||
if topIs(Menu) then
|
||||
local mark2 = #cues
|
||||
U.tap(game, "b")
|
||||
U.wait(26)
|
||||
check("backing out of the bedroom PC with B rings it as well",
|
||||
heard(mark2, "Turn_Off_PC") ~= nil)
|
||||
end
|
||||
end
|
||||
|
||||
-- ---- back out of the house, to the street (#961) ------------------------
|
||||
if houseExit then
|
||||
local sx, sy = cellBehind(houseExit.x, houseExit.y, "down")
|
||||
ow = stand("REDS_HOUSE_1F", sx, sy, "down")
|
||||
if not ow.map:isWalkableCell(sx, sy) then
|
||||
U.log(("(%d, %d) is blocked; using the other half of the mat")
|
||||
:format(sx, sy))
|
||||
ow = stand("REDS_HOUSE_1F", sx + 1, sy, "down")
|
||||
end
|
||||
takeDoor("down", "Go_Outside", "stepping out onto the street")
|
||||
end
|
||||
|
||||
-- ---- into the POKéMON CENTER, for the PC main menu -----------------------
|
||||
if centerDoor then
|
||||
local sx, sy = cellBehind(centerDoor.x, centerDoor.y, "up")
|
||||
stand("VIRIDIAN_CITY", sx, sy, "up")
|
||||
takeDoor("up", "Go_Inside", "walking into the POKéMON CENTER")
|
||||
end
|
||||
|
||||
-- ---- the PC main menu: Enter_PC, and the silence under it (#960) --------
|
||||
if centerPC then
|
||||
local sx, sy = cellBehind(centerPC.x, centerPC.y, "up")
|
||||
ow = stand("VIRIDIAN_POKECENTER", sx, sy, "up")
|
||||
for _, n in ipairs(ow.npcs or {}) do n.frozen = true end -- the GENTLEMAN walks
|
||||
local mark = #cues
|
||||
U.tap(game, "a")
|
||||
U.wait(26)
|
||||
check("A on the Center PC opens the PC main menu", topIs(Menu))
|
||||
check("...with Turn_On_PC", heard(mark, "Turn_On_PC") ~= nil)
|
||||
|
||||
local mine = (game.save.player.name or "RED") .. "'s PC"
|
||||
if topIs(Menu) then
|
||||
mark = #cues
|
||||
choose(game.stack:top(), mine)
|
||||
check(mine .. " rings Enter_PC (#960)", heard(mark, "Enter_PC") ~= nil)
|
||||
check("...and the item PC opened", topIs(Menu))
|
||||
-- BIT_USING_GENERIC_PC: reached this way, ExitPlayerPC is silent and
|
||||
mark = #cues
|
||||
U.tap(game, "b")
|
||||
U.wait(26)
|
||||
check("backing out of it again is silent, as the ROM is",
|
||||
heard(mark, "Turn_Off_PC") == nil)
|
||||
U.log(" cues:", since(mark))
|
||||
end
|
||||
|
||||
-- ---- CHANGE BOX (#1044) ----------------------------------------------
|
||||
-- the box PC reads SOMEONE'S PC until EVENT_MET_BILL (pokemon_pc.asm)
|
||||
local flags = game.save.flags or {}
|
||||
local boxPC = (flags.EVENT_MET_BILL or flags.EVENT_GOT_SS_TICKET)
|
||||
and "BILL'S PC" or Strings("SOMEONE'S PC")
|
||||
if topIs(Menu) then
|
||||
mark = #cues
|
||||
choose(game.stack:top(), boxPC)
|
||||
check(boxPC .. " rings Enter_PC too", heard(mark, "Enter_PC") ~= nil)
|
||||
check("...and the box menu opened", topIs(Menu))
|
||||
end
|
||||
if topIs(Menu) and rowIndex(game.stack:top(), Strings("CHANGE BOX")) then
|
||||
choose(game.stack:top(), Strings("CHANGE BOX"))
|
||||
U.shot(game, DIR .. "/bug1044_change_box.png")
|
||||
local before = game.save.currentBox
|
||||
U.tap(game, "down") -- BOX 1 is the current one, so move off it
|
||||
U.wait(8)
|
||||
mark = #cues
|
||||
-- A picks the box, then the "data will be saved" prompt and its YES
|
||||
U.tap(game, "a")
|
||||
U.wait(20)
|
||||
mash(function() return game.save.currentBox ~= before end, 40)
|
||||
U.wait(60) -- the 15-frame answer hold, then the write
|
||||
check(("CHANGE BOX switched box %s -> %s")
|
||||
:format(tostring(before), tostring(game.save.currentBox)),
|
||||
game.save.currentBox ~= before)
|
||||
check("...and rang the SAVE jingle (#1044)", heard(mark, "Save") ~= nil)
|
||||
U.log(" cues:", since(mark))
|
||||
end
|
||||
|
||||
-- ---- LOG OFF from the main menu --------------------------------------
|
||||
for _ = 1, 6 do
|
||||
if game.stack:top() == game.overworld then break end
|
||||
U.tap(game, "b")
|
||||
U.wait(20)
|
||||
end
|
||||
U.tap(game, "a")
|
||||
U.wait(26)
|
||||
if topIs(Menu) and rowIndex(game.stack:top(), Strings("LOG OFF")) then
|
||||
local mark2 = #cues
|
||||
choose(game.stack:top(), Strings("LOG OFF"))
|
||||
check("LOG OFF on the PC main menu rings Turn_Off_PC",
|
||||
heard(mark2, "Turn_Off_PC") ~= nil)
|
||||
end
|
||||
for _ = 1, 8 do
|
||||
if game.stack:top() == game.overworld then break end
|
||||
U.tap(game, "b")
|
||||
U.wait(20)
|
||||
end
|
||||
end
|
||||
|
||||
-- the battle menu click (#1045) already landed in HEAD 12c2677; confirmation only
|
||||
ow = stand("ROUTE_1", 5, 5, "down")
|
||||
if not ow.map:isWalkableCell(5, 5) then
|
||||
local fx, fy
|
||||
for cy = 0, ow.map.heightCells - 1 do
|
||||
for cx = 0, ow.map.widthCells - 1 do
|
||||
if ow.map:isWalkableCell(cx, cy) then fx, fy = cx, cy break end
|
||||
end
|
||||
if fx then break end
|
||||
end
|
||||
if fx then ow = stand("ROUTE_1", fx, fy, "down") end
|
||||
end
|
||||
local battle = BattleState.newWild(game, "SNORLAX", 50)
|
||||
battle.onFinish = function(result) ow:afterBattle(result, battle) end
|
||||
-- SPLASH so the foe's turn can never end the run under the menu presses
|
||||
battle.enemy.mon.moves = { { id = "SPLASH", pp = 40, maxPP = 40 } }
|
||||
battle.enemy.curMoves = battle.enemy.mon.moves
|
||||
ow:pushBattle(battle)
|
||||
U.wait(220) -- the send-out intro runs before the menu is reachable
|
||||
mash(function() return battle.phase == "menu" end, 60)
|
||||
check("the wild SNORLAX battle reached its FIGHT menu",
|
||||
battle.phase == "menu")
|
||||
|
||||
-- one press, then long enough for the click to be its own sound
|
||||
local function click(btn, label, want)
|
||||
local mark = #cues
|
||||
U.tap(game, btn)
|
||||
U.wait(28)
|
||||
local n = countOf(mark, "Press_AB")
|
||||
check(("%s: %s gave %d click%s, expected %d")
|
||||
:format(label, btn:upper(), n, n == 1 and "" or "s", want),
|
||||
n == want)
|
||||
end
|
||||
if battle.phase == "menu" then
|
||||
click("a", "A on FIGHT (#1045a)", 1)
|
||||
check("...and the move list opened", battle.phase == "moveSelect")
|
||||
U.shot(game, DIR .. "/bug1045_move_list.png")
|
||||
click("b", "B out of the move list (#1045c)", 1)
|
||||
check("...and the FIGHT menu came back", battle.phase == "menu")
|
||||
click("a", "A on FIGHT again", 1)
|
||||
click("a", "A on a move (#1045b)", 1)
|
||||
mash(function() return battle.phase == "menu" end, 120)
|
||||
if battle.phase == "menu" then
|
||||
battle.menuIndex = 4 -- RUN shares the FIGHT/PKMN/ITEM call site
|
||||
U.wait(4)
|
||||
click("a", "A on RUN", 1)
|
||||
end
|
||||
end
|
||||
U.log(("machine checks: %d passed, %d failed"):format(pass, fail))
|
||||
|
||||
-- ---- over to you --------------------------------------------------------
|
||||
if centerPC then
|
||||
local sx, sy = cellBehind(centerPC.x, centerPC.y, "up")
|
||||
ow = stand("VIRIDIAN_POKECENTER", sx, sy, "up")
|
||||
for _, n in ipairs(ow.npcs or {}) do n.frozen = true end
|
||||
end
|
||||
U.log("Everything above has been pressed once already; you are parked at the")
|
||||
U.log("Viridian POKéMON CENTER PC to do it again by ear.")
|
||||
U.log("A opens the PC main menu. RED's PC clicks in on the two-note ENTER PC")
|
||||
U.log("chirp and LOG OFF closes with the descending power-down. B out of RED's")
|
||||
U.log("PC is silent on purpose; a power-down there is the near miss, not a pass.")
|
||||
U.log("SOMEONE'S PC, CHANGE BOX, any other box, YES: the SAVE jingle rings")
|
||||
U.log("after the box has changed, the same jingle the SAVE menu plays. A")
|
||||
U.log("jingle before the switch, or none at all, is #1044 back.")
|
||||
U.log("The bedroom PC upstairs at home is the other half of #960: it beeps on,")
|
||||
U.log("and LOG OFF or B rings the power-down there. That one is not silent.")
|
||||
U.log("The COOLTRAINER at (4,3) and the NURSE at (3,1) are pinned; walk into")
|
||||
U.log("either and it thuds like a wall. Walking into a wall is the control --")
|
||||
U.log("it always thudded, so a silent wall means the device, not the fix.")
|
||||
U.log(("Walk out over the exit mat at the bottom of the room: the door sound")
|
||||
.. (" starts as the screen begins to darken, %d frames ahead of")
|
||||
:format(FADE))
|
||||
U.log("Viridian City. One that lands on the new map, or after it, is #961.")
|
||||
U.log("In any battle, A on FIGHT/PKMN/ITEM/RUN, A on a move and B out of the")
|
||||
U.log("move list all click. Silence on any of the three is #1045.")
|
||||
U.log("Shots: " .. DIR .. "/bug960_*.png, bug1044_*.png, bug1045_*.png")
|
||||
|
||||
-- keeps naming cues with their frame after the hand-off, so a door sound
|
||||
local reported = #cues
|
||||
while true do
|
||||
if #cues > reported then
|
||||
for i = reported + 1, #cues do
|
||||
U.log("cue", cues[i].name, "frame", cues[i].frame)
|
||||
end
|
||||
reported = #cues
|
||||
end
|
||||
coroutine.yield()
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,410 @@
|
||||
-- Pikachu stays in its ball until the rival fight (#1009) and clears (4,3) for the rival (#1021); the Route 15 leg only instruments #920.
|
||||
-- pokeyellow scripts/OaksLab.asm, OaksLab_2.asm.
|
||||
return function(game)
|
||||
local U = dofile("tests/drivers/util.lua")
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
local MapScripts = require("src.script.MapScripts")
|
||||
local Commands = require("src.script.Commands")
|
||||
local PF = require("src.world.PikachuFollower")
|
||||
local Pokemon = require("src.pokemon.Pokemon")
|
||||
local Bag = require("src.inventory.Bag")
|
||||
local BattleState = require("src.battle.BattleState")
|
||||
|
||||
local SHOT_DIR = os.getenv("SHOT_DIR") or "/tmp/shots"
|
||||
local LAB = "OAKS_LAB"
|
||||
local RIVAL = 1 -- OAKSLAB_RIVAL, object index 1
|
||||
local GIFT = { x = 5, y = 3 } -- where OaksLabRLE_PlayerWalksToOak ends
|
||||
|
||||
local function check(label, ok)
|
||||
U.log(ok and "PASS" or "FAIL", label)
|
||||
return ok
|
||||
end
|
||||
local function idle()
|
||||
while true do coroutine.yield() end
|
||||
end
|
||||
local function ow() return game.overworld end
|
||||
local function follower() return PF.current(game.overworld) end
|
||||
local function ctx()
|
||||
return { game = game, save = game.save, overworld = game.overworld }
|
||||
end
|
||||
local function where(npc)
|
||||
if not npc then return "gone" end
|
||||
return "(" .. npc.cellX .. "," .. npc.cellY .. ") facing "
|
||||
.. tostring(npc.facing)
|
||||
end
|
||||
|
||||
-- one player step; false when refused, so a caller can route around a map edit
|
||||
local function stepOnce(dir)
|
||||
local p = ow().player
|
||||
local x0, y0 = p.cellX, p.cellY
|
||||
for _ = 1, 60 do
|
||||
table.insert(game.input.pressQueue, dir)
|
||||
game.input.state[dir] = true
|
||||
coroutine.yield()
|
||||
p = ow().player
|
||||
if p.cellX ~= x0 or p.cellY ~= y0 then break end
|
||||
end
|
||||
game.input.state[dir] = false
|
||||
for _ = 1, 40 do
|
||||
if not ow().player.moving then break end
|
||||
U.wait(1)
|
||||
end
|
||||
U.wait(3)
|
||||
p = ow().player
|
||||
return p.cellX ~= x0 or p.cellY ~= y0
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------- checks
|
||||
if not check("running the Yellow cache (POKEPORT_VERSION=yellow)",
|
||||
GameVersion.isYellow()) then
|
||||
U.log("Red and Blue have no follower and no Yellow lab script, so every")
|
||||
U.log("line below would fail for the wrong reason.")
|
||||
idle()
|
||||
end
|
||||
check("SPRITE_PIKACHU resolves in the sprite table",
|
||||
game.data.sprites ~= nil and game.data.sprites.SPRITE_PIKACHU ~= nil)
|
||||
check("PikachuFollower.oaksLabMakeWay exists",
|
||||
type(PF.oaksLabMakeWay) == "function")
|
||||
check("the pikachu_make_way verb exists",
|
||||
type(Commands.pikachu_make_way) == "function")
|
||||
check("and it blocks the runner while the walk plays",
|
||||
Commands.meta.pikachu_make_way ~= nil
|
||||
and Commands.meta.pikachu_make_way.blocking == true)
|
||||
|
||||
local lab = require("data.scripts.oaks_lab_yellow")
|
||||
local oakRows = lab.talk.TEXT_OAKSLAB_OAK1
|
||||
local function rowIndex(pred)
|
||||
for i, r in ipairs(oakRows) do
|
||||
if pred(r) then return i end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
local iGramps = rowIndex(function(r)
|
||||
return r[1] == "show_text" and r[2] == "_OaksLabRivalGrampsText"
|
||||
end)
|
||||
local iMakeWay = rowIndex(function(r) return r[1] == "pikachu_make_way" end)
|
||||
local iShow = rowIndex(function(r)
|
||||
return r[1] == "show_object" and r[3] == "OAKSLAB_RIVAL"
|
||||
end)
|
||||
check("Oak's parcel branch has a make-way row", iMakeWay ~= nil)
|
||||
-- the callfar sits after the GRAMPS text and before ShowObject; a row on
|
||||
check("it sits between the GRAMPS text and the rival's ShowObject",
|
||||
iGramps ~= nil and iMakeWay ~= nil and iShow ~= nil
|
||||
and iGramps < iMakeWay and iMakeWay < iShow)
|
||||
|
||||
local sfx = game.save.options and game.save.options.sfxVol
|
||||
if sfx == 0 then
|
||||
U.log("sfxVol is 0. Pikachu's cry as it bursts out of the ball will be")
|
||||
U.log("silent and a mute run reads exactly like a missing cry -- turn")
|
||||
U.log("the sound back up before judging the escape scene.")
|
||||
end
|
||||
|
||||
-- leg 1: in the ball. Level 30 only so the lab battle ends fast
|
||||
game.save.flags = game.save.flags or {}
|
||||
local flags = game.save.flags
|
||||
flags.EVENT_GOT_STARTER = true
|
||||
flags.EVENT_CHOSE_PIKACHU = true
|
||||
flags.EVENT_FOLLOWED_OAK_INTO_LAB = true
|
||||
flags.EVENT_FOLLOWED_OAK_INTO_LAB_2 = true
|
||||
flags.EVENT_BATTLED_RIVAL_IN_OAKS_LAB = nil
|
||||
flags.EVENT_OAK_ASKED_TO_CHOOSE_MON = true
|
||||
flags.EVENT_GOT_POKEDEX = nil
|
||||
flags.EVENT_OAK_GOT_PARCEL = nil
|
||||
game.save.party = { Pokemon.new(game.data, "PIKACHU", 30) }
|
||||
game.save.player.name = "bryan"
|
||||
game.save.onBike = false
|
||||
game.save.pikachuInBall = true
|
||||
|
||||
U.teleport(game, LAB, GIFT.x, GIFT.y, "up")
|
||||
U.wait(10)
|
||||
Commands.show_object(ctx(), LAB, "OAKSLAB_OAK1")
|
||||
U.wait(5)
|
||||
|
||||
-- asked after the teleport: the registry only fills on first require
|
||||
check("the Yellow lab module is the one bound to the map",
|
||||
MapScripts.talkScript(LAB, "TEXT_OAKSLAB_OAK1") == oakRows)
|
||||
|
||||
check("straight after the gift there is no follower on the map",
|
||||
follower() == nil)
|
||||
|
||||
-- save compat: nil pikachuInBall falls back to the rival-fight flag, not false
|
||||
game.save.pikachuInBall = nil
|
||||
U.wait(10)
|
||||
check("a pre-#1009 save before the rival fight still has none",
|
||||
follower() == nil)
|
||||
flags.EVENT_BATTLED_RIVAL_IN_OAKS_LAB = true
|
||||
U.wait(20)
|
||||
check("a pre-#1009 save past the rival fight keeps its follower",
|
||||
follower() ~= nil)
|
||||
flags.EVENT_BATTLED_RIVAL_IN_OAKS_LAB = nil
|
||||
game.save.pikachuInBall = true
|
||||
U.wait(20)
|
||||
check("and the in-ball byte puts it away again", follower() == nil)
|
||||
|
||||
U.shot(game, SHOT_DIR .. "/bug1009_in_ball.png")
|
||||
U.log("captured", SHOT_DIR .. "/bug1009_in_ball.png",
|
||||
"- player alone at the gift spot")
|
||||
|
||||
-- --------------------------------------------- leg 2: out of the ball
|
||||
Commands.show_object(ctx(), LAB, "OAKSLAB_RIVAL")
|
||||
U.wait(5)
|
||||
|
||||
-- OaksLabRivalChallengesPlayerScript fires from y >= 6 with the starter held
|
||||
local walkedClean = true
|
||||
for _ = 1, 10 do
|
||||
if ow().player.cellY >= 6 then break end
|
||||
if follower() then walkedClean = false end
|
||||
if not stepOnce("down") then
|
||||
if not stepOnce("left") then break end
|
||||
end
|
||||
end
|
||||
check("crossed the lab to the door row with no follower behind",
|
||||
walkedClean and follower() == nil)
|
||||
check("reached the row the rival challenges from", ow().player.cellY >= 6)
|
||||
|
||||
-- the escape only spawns the companion once the overworld is back on top
|
||||
local sawBattle, escapedAt, spawnCell = false, nil, nil
|
||||
for _ = 1, 2500 do
|
||||
local o = game.overworld
|
||||
local top = game.stack:top()
|
||||
if top ~= o then
|
||||
if getmetatable(top) == BattleState then sawBattle = true end
|
||||
U.tap(game, "a")
|
||||
U.wait(3)
|
||||
else
|
||||
local npc = follower()
|
||||
if npc and not escapedAt then
|
||||
escapedAt = U.frame()
|
||||
spawnCell = { x = npc.cellX, y = npc.cellY, facing = npc.facing }
|
||||
U.shot(game, SHOT_DIR .. "/bug1009_escaped.png")
|
||||
end
|
||||
if escapedAt and not o.runner:isRunning() and #o.scriptMoves == 0 then
|
||||
break
|
||||
end
|
||||
U.wait(2)
|
||||
end
|
||||
end
|
||||
|
||||
check("the rival battle actually ran", sawBattle)
|
||||
check("the escape scene put a follower on the map", escapedAt ~= nil)
|
||||
check("EVENT_BATTLED_RIVAL_IN_OAKS_LAB is set",
|
||||
flags.EVENT_BATTLED_RIVAL_IN_OAKS_LAB == true)
|
||||
check("the ball is open (save.pikachuInBall false, not nil)",
|
||||
game.save.pikachuInBall == false)
|
||||
if spawnCell then
|
||||
local p = ow().player
|
||||
U.log("it appeared at", spawnCell.x, spawnCell.y, "with the player at",
|
||||
p.cellX, p.cellY, "facing", p.facing)
|
||||
-- OaksLabPikachuEscapesPokeballScript faces the player up and uses spawn
|
||||
check("it burst out on the cell behind the player, not beside him",
|
||||
spawnCell.x == p.cellX and spawnCell.y == p.cellY + 1)
|
||||
U.log("captured", SHOT_DIR .. "/bug1009_escaped.png")
|
||||
end
|
||||
|
||||
-- leg 3: Oak's .DeliverParcelText needs parcel held, no balls, no Pokedex
|
||||
flags.EVENT_BATTLED_RIVAL_IN_OAKS_LAB = true
|
||||
flags.EVENT_PALLET_AFTER_GETTING_POKEBALLS = nil
|
||||
flags.EVENT_BEAT_ROUTE22_RIVAL_1ST_BATTLE = nil
|
||||
flags.EVENT_GOT_POKEDEX = nil
|
||||
game.save.pikachuInBall = false
|
||||
game.save.inventory = game.save.inventory or {}
|
||||
game.save.inventory.POKE_BALL = nil
|
||||
if game.save.pokedex then game.save.pokedex.owned = {} end
|
||||
Bag.add(game.save, "OAKS_PARCEL", 1, game.data)
|
||||
|
||||
-- the SPRITE_FACING_LEFT case of TryApplyPikachuMovementData
|
||||
U.teleport(game, LAB, 4, 3, "right")
|
||||
U.wait(10)
|
||||
Commands.show_object(ctx(), LAB, "OAKSLAB_OAK1")
|
||||
-- he walked out after the lab battle; ShowObject brings him back mid-scene
|
||||
Commands.hide_object(ctx(), LAB, "OAKSLAB_RIVAL")
|
||||
U.wait(5)
|
||||
check("the follower is back for the parcel scene", follower() ~= nil)
|
||||
stepOnce("right")
|
||||
-- Oak stands on (5,2) and blocks, so this only turns the player up
|
||||
U.hold(game, "up", 10)
|
||||
U.wait(10)
|
||||
|
||||
local p = ow().player
|
||||
local pika = follower()
|
||||
U.log("player at", p.cellX, p.cellY, "facing", p.facing,
|
||||
"| Pikachu at", where(pika))
|
||||
check("the player is below Oak on row 3", p.cellY == 3 and p.facing == "up")
|
||||
local onRivalCell = pika ~= nil and pika.cellX == 4 and pika.cellY == 3
|
||||
check("Pikachu is standing on the rival's landing cell (4,3)", onRivalCell)
|
||||
if not onRivalCell then
|
||||
U.log("Without it on (4,3) the movement data does not apply and the")
|
||||
U.log("scene below proves nothing about #1021.")
|
||||
end
|
||||
U.shot(game, SHOT_DIR .. "/bug1021_before.png")
|
||||
|
||||
-- the rival's own walk sits in the same scriptMoves list, so ask only
|
||||
local function stillWalking(o, npc)
|
||||
if npc.moving then return true end
|
||||
for _, mv in ipairs(o.scriptMoves) do
|
||||
if mv.entity == npc then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- end pose snapshotted at walk stop; the idle roll turns it later (Func_fc803)
|
||||
U.tap(game, "a")
|
||||
local leftCell, rivalSeen, rivalArrived, midShot = nil, nil, nil, false
|
||||
local settled
|
||||
local trace, last = {}, nil
|
||||
for f = 1, 1200 do
|
||||
local o = game.overworld
|
||||
local top = game.stack:top()
|
||||
local npc = follower()
|
||||
if npc then
|
||||
local cell = npc.cellX .. "," .. npc.cellY
|
||||
if cell ~= last then
|
||||
last = cell
|
||||
trace[#trace + 1] = "f" .. f .. " " .. cell
|
||||
end
|
||||
if not leftCell and not (npc.cellX == 4 and npc.cellY == 3) then
|
||||
leftCell = f
|
||||
end
|
||||
if leftCell and not settled and not stillWalking(o, npc) then
|
||||
settled = { x = npc.cellX, y = npc.cellY, facing = npc.facing }
|
||||
end
|
||||
end
|
||||
local rival = o:npcByIndex(RIVAL)
|
||||
if rival and not rivalSeen then rivalSeen = f end
|
||||
if rival and not rivalArrived and rival.cellX == 4 and rival.cellY == 3
|
||||
and not rival.moving then
|
||||
rivalArrived = f
|
||||
end
|
||||
if leftCell and rival and rival.moving and not midShot then
|
||||
midShot = true
|
||||
U.shot(game, SHOT_DIR .. "/bug1021_stepped_aside.png")
|
||||
end
|
||||
if rivalArrived and settled then break end
|
||||
if top ~= o then U.tap(game, "a") end
|
||||
U.wait(2)
|
||||
end
|
||||
|
||||
U.log("Pikachu trace:", table.concat(trace, " | "))
|
||||
check("Pikachu moved off (4,3)", leftCell ~= nil)
|
||||
check("the rival came in and reached (4,3)", rivalArrived ~= nil)
|
||||
if leftCell and rivalSeen then
|
||||
-- the callfar runs before ShowObject: the cell is clear before the rival
|
||||
check("it started clearing the cell before the rival appeared",
|
||||
leftCell <= rivalSeen)
|
||||
end
|
||||
if settled then
|
||||
U.log("the walk ended with Pikachu at", settled.x, settled.y,
|
||||
"facing", settled.facing)
|
||||
end
|
||||
-- OaksLabPikachuMovementData2: STEP_DOWN, STEP_RIGHT, LOOK_UP
|
||||
check("it ended one below the player looking up",
|
||||
settled ~= nil and settled.x == 5 and settled.y == 4
|
||||
and settled.facing == "up")
|
||||
U.shot(game, SHOT_DIR .. "/bug1021_rival_in_place.png")
|
||||
|
||||
-- leg 4: nothing is fixed for #920; this only records ow.npcs and trail.ledgeHop
|
||||
local eastMap = game.data.maps.FUCHSIA_CITY
|
||||
and game.data.maps.FUCHSIA_CITY.connections
|
||||
and game.data.maps.FUCHSIA_CITY.connections.east
|
||||
eastMap = eastMap and eastMap.map or "ROUTE_15"
|
||||
U.log("Fuchsia's east connection is", eastMap)
|
||||
|
||||
U.teleport(game, "FUCHSIA_CITY", 10, 12, "down")
|
||||
U.wait(10)
|
||||
local city = ow().map
|
||||
local row = nil
|
||||
for y = 0, city.heightCells - 1 do
|
||||
if city:isWalkableCell(city.widthCells - 1, y)
|
||||
and city:isWalkableCell(city.widthCells - 2, y) then
|
||||
row = row or y
|
||||
end
|
||||
end
|
||||
if row then
|
||||
U.log("crossing the east seam on row", row)
|
||||
U.teleport(game, "FUCHSIA_CITY", city.widthCells - 2, row, "right")
|
||||
U.wait(10)
|
||||
for _ = 1, 6 do
|
||||
if ow().map.id == eastMap then break end
|
||||
if not stepOnce("right") then break end
|
||||
end
|
||||
end
|
||||
-- the seam row is blocked (gate rebuild, mod): drop in on the route itself
|
||||
local function dropOnRoute()
|
||||
U.teleport(game, eastMap, 4, 8, "right")
|
||||
U.wait(10)
|
||||
local m = ow().map
|
||||
if m:isWalkableCell(4, 8) then return end
|
||||
for y = 0, m.heightCells - 1 do
|
||||
for x = 0, 9 do
|
||||
if m:isWalkableCell(x, y) then
|
||||
U.teleport(game, eastMap, x, y, "right")
|
||||
U.wait(10)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if ow().map.id ~= eastMap then
|
||||
U.log("could not walk the seam; dropping straight on to", eastMap)
|
||||
dropOnRoute()
|
||||
end
|
||||
check("standing on " .. eastMap, ow().map.id == eastMap)
|
||||
|
||||
local worstGap, lostAt, steps = 0, nil, 0
|
||||
local back = { x = ow().player.cellX, y = ow().player.cellY }
|
||||
for i = 1, 10 do
|
||||
if not stepOnce("right") then
|
||||
if not stepOnce("down") then break end
|
||||
end
|
||||
local o = game.overworld
|
||||
if o.map.id ~= eastMap then
|
||||
-- the route's gate is a warp, and an arrival respawn is not the stall
|
||||
U.log("step", i, "walked into", o.map.id, "- stepping back out")
|
||||
U.teleport(game, eastMap, back.x, back.y, "left")
|
||||
U.wait(10)
|
||||
break
|
||||
end
|
||||
steps = i
|
||||
back.x, back.y = o.player.cellX, o.player.cellY
|
||||
local npc = follower()
|
||||
local p2 = o.player
|
||||
local hop = o.pikachuTrail and o.pikachuTrail.ledgeHop
|
||||
if npc then
|
||||
local gap = math.abs(npc.cellX - p2.cellX)
|
||||
+ math.abs(npc.cellY - p2.cellY)
|
||||
if gap > worstGap then worstGap = gap end
|
||||
U.log("step", i, "player", p2.cellX, p2.cellY, "| Pikachu", where(npc),
|
||||
"| gap", gap, "| ledgeHop", tostring(hop))
|
||||
else
|
||||
lostAt = lostAt or i
|
||||
U.log("step", i, "player", p2.cellX, p2.cellY,
|
||||
"| Pikachu is not in ow.npcs at all | ledgeHop", tostring(hop))
|
||||
end
|
||||
end
|
||||
U.log("walked", steps, "steps east on", eastMap)
|
||||
check("the follower stayed in ow.npcs the whole way", lostAt == nil)
|
||||
check("it never fell more than two cells behind", worstGap <= 2)
|
||||
if lostAt then
|
||||
U.log("it dropped out of ow.npcs on step", lostAt,
|
||||
"- that is the shape #920 would take")
|
||||
end
|
||||
-- an arrival parks the follower under the player (#863), so walk one step
|
||||
stepOnce("left")
|
||||
U.wait(20)
|
||||
U.shot(game, SHOT_DIR .. "/bug920_east_of_fuchsia.png")
|
||||
|
||||
U.log("Six shots, in story order. bug1009_in_ball: the player alone in the")
|
||||
U.log("lab with the starter already in the party. bug1009_escaped: the cry,")
|
||||
U.log("then Pikachu on the cell behind him. bug1021_stepped_aside: it walks")
|
||||
U.log("down and right on the GRAMPS text while the rival is still off the")
|
||||
U.log("map, ending below the player looking up. The near miss to watch for")
|
||||
U.log("is the rival arriving first and Pikachu shuffling around him after,")
|
||||
U.log("or the walk playing under the box instead of after it.")
|
||||
U.log("Talk to Pikachu here for the control case: it answers with a bubble")
|
||||
U.log("and a cry, which is the same sprite and the same audio path.")
|
||||
U.log("#920 is unfixed. The pad is yours on Route 15; the step log above is")
|
||||
U.log("what the triage wants from a stall -- whether it is still in ow.npcs")
|
||||
U.log("and whether trail.ledgeHop stayed set after a ledge.")
|
||||
|
||||
idle()
|
||||
end
|
||||
Reference in New Issue
Block a user