mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
239 lines
9.3 KiB
Lua
239 lines
9.3 KiB
Lua
-- The Yellow Viridian catch tutorial has to FAIL its throw (#636).
|
|
-- pokeyellow scripts/ViridianCity.asm:168 (ViridianCityCheckWaitingOldMan)
|
|
-- starts the demo from (19,9); ItemUseBall's .oldManBattle branch reads
|
|
-- EVENT_INITIAL_CATCH_TRAINING and stores anim data $63, so the ball shakes
|
|
-- three times and breaks open, and only then does the losing-my-touch line
|
|
-- make sense. No POKEPORT_SPEED here: the shakes and the ball sounds run on
|
|
-- the real-time audio clock and fast-forward pulls them apart.
|
|
-- POKEPORT_DRIVER=tests/drivers/oldman_yellow_bug636_test.lua \
|
|
-- POKEPORT_VERSION=yellow POKEPORT_IDENTITY=bug636 SHOT_DIR=/tmp/shots love .
|
|
return function(game)
|
|
local U = dofile("tests/drivers/util.lua")
|
|
local mapScripts = require("data.scripts.init")
|
|
local GameVersion = require("src.core.GameVersion")
|
|
local BattleState = require("src.battle.BattleState")
|
|
local TextBox = require("src.render.TextBox")
|
|
local Pokemon = require("src.pokemon.Pokemon")
|
|
|
|
local MAP = "VIRIDIAN_CITY"
|
|
local OLD_MAN2 = "VIRIDIANCITY_OLD_MAN2"
|
|
local TEXT = "TEXT_VIRIDIANCITY_OLD_MAN2"
|
|
local SHOT_DIR = os.getenv("SHOT_DIR") or "/tmp/shots"
|
|
-- ../pokeyellow/data/maps/objects/ViridianCity.asm:37 puts OLD_MAN2 on
|
|
-- (18,9); the trigger cell is the gap east of him, (19,9). Each candidate
|
|
-- is {stand x, stand y, direction that walks into (19,9)}.
|
|
local TRIGGER = { x = 19, y = 9 }
|
|
local APPROACHES = {
|
|
{ 19, 10, "up" }, { 19, 8, "down" }, { 20, 9, "left" },
|
|
}
|
|
|
|
local fails = 0
|
|
local function check(label, ok)
|
|
if not ok then fails = fails + 1 end
|
|
U.log(ok and "PASS" or "FAIL", label)
|
|
return ok
|
|
end
|
|
|
|
local function flat(s) return (tostring(s):gsub("[\n\v\f]", " / ")) end
|
|
|
|
-- ---- what the eye cannot see -------------------------------------------
|
|
check("booted on Yellow (the fail branch is Yellow-only)",
|
|
GameVersion.get() == "yellow")
|
|
|
|
local hooks = mapScripts.get(MAP)
|
|
check(MAP .. " has a map-script table", type(hooks) == "table")
|
|
check("the (19,9) step hook survived the base-script merge",
|
|
type(hooks) == "table" and type(hooks.onStep) == "function")
|
|
check(TEXT .. " has a talk handler",
|
|
type(hooks) == "table" and type(hooks.talk) == "table"
|
|
and type(hooks.talk[TEXT]) == "function")
|
|
|
|
-- a species constant that no longer resolves builds a nameless battler and
|
|
-- looks exactly like the demo never starting
|
|
local om = game.data.field.oldManBattle
|
|
check("field.oldManBattle exists", type(om) == "table")
|
|
check("it is Yellow's RATTATA, not Red's WEEDLE",
|
|
type(om) == "table" and om.species == "RATTATA")
|
|
check("that species resolves in the pokemon table",
|
|
type(om) == "table" and game.data.pokemon[om.species] ~= nil)
|
|
|
|
-- the flag itself, off a throwaway battle that is never pushed
|
|
local probe = BattleState.newWild(game, (om and om.species) or "RATTATA",
|
|
(om and om.level) or 5)
|
|
probe:makeOldManDemo(nil, true)
|
|
check("makeOldManDemo(name, true) sets demoFails", probe.demoFails == true)
|
|
probe:makeOldManDemo(nil, false)
|
|
check("and without it the demo still catches, for Red and the reruns",
|
|
probe.demoFails == false)
|
|
|
|
local miss = game.data.text._ItemUseBallText04
|
|
check("_ItemUseBallText04 (three shakes, then free) resolves",
|
|
type(miss) == "string" and miss ~= "")
|
|
local touch = game.data.text._ViridianCityOldManLosingMyTouchText
|
|
check("_ViridianCityOldManLosingMyTouchText resolves",
|
|
type(touch) == "string" and touch ~= "")
|
|
check("it really is the losing-my-touch line",
|
|
type(touch) == "string" and touch:find("losing", 1, true) ~= nil)
|
|
if type(miss) == "string" then U.log("breakout line reads:", flat(miss)) end
|
|
if type(touch) == "string" then U.log("old man then says:", flat(touch)) end
|
|
|
|
local sfx = (game.save.options or {}).sfxVol
|
|
if sfx == 0 then
|
|
U.log("FAIL sfx volume is 0, so the ball wobbles and the break will be silent")
|
|
else
|
|
U.log("sfx volume is", tostring(sfx), "-- the ball should be audible")
|
|
end
|
|
|
|
-- ---- put the tutorial back to its untouched state -----------------------
|
|
game.save.party = {
|
|
Pokemon.new(game.data, "PIKACHU", 6),
|
|
}
|
|
game.save.flags = game.save.flags or {}
|
|
game.save.flags.EVENT_GOT_POKEDEX = true
|
|
game.save.flags.EVENT_COMPLETED_CATCH_TRAINING = nil
|
|
game.save.objectToggles = {}
|
|
local ownedBefore = 0
|
|
for _ in pairs((game.save.pokedex and game.save.pokedex.owned) or {}) do
|
|
ownedBefore = ownedBefore + 1
|
|
end
|
|
local partyBefore = #game.save.party
|
|
|
|
local function npcNamed(name)
|
|
local ow = game.overworld
|
|
for _, n in ipairs((ow and ow.npcs) or {}) do
|
|
if n.def and n.def.name == name then return n end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-- walk into (19,9) from the first free neighbour, so a later map edit that
|
|
-- walls off the south approach degrades instead of pressing into a fence
|
|
local stand
|
|
U.teleport(game, MAP, APPROACHES[1][1], APPROACHES[1][2], "up")
|
|
U.wait(20)
|
|
local ow = game.overworld
|
|
check("the tutorial old man is on the map", npcNamed(OLD_MAN2) ~= nil)
|
|
for _, a in ipairs(APPROACHES) do
|
|
if ow and ow.map:isWalkableCell(a[1], a[2]) and not ow:npcAtCell(a[1], a[2]) then
|
|
stand = a
|
|
break
|
|
end
|
|
end
|
|
check(("a free cell next to (%d,%d) to walk in from")
|
|
:format(TRIGGER.x, TRIGGER.y), stand ~= nil)
|
|
stand = stand or APPROACHES[1]
|
|
if stand ~= APPROACHES[1] then
|
|
U.log(("south approach blocked, walking in from (%d,%d)"):format(stand[1], stand[2]))
|
|
U.teleport(game, MAP, stand[1], stand[2], stand[3])
|
|
U.wait(15)
|
|
end
|
|
|
|
-- ---- step on the trigger and let the demo run ---------------------------
|
|
local function topIsBattle()
|
|
local top = game.stack:top()
|
|
return (top and top.demo and top.demoName) and top or nil
|
|
end
|
|
|
|
local battle
|
|
for _ = 1, 40 do
|
|
U.hold(game, stand[3], 6)
|
|
U.wait(4)
|
|
battle = topIsBattle()
|
|
if battle then break end
|
|
-- the apology speech comes first; page through it to reach the battle
|
|
if getmetatable(game.stack:top()) == TextBox then
|
|
U.tap(game, "a")
|
|
U.wait(6)
|
|
end
|
|
end
|
|
if not battle then
|
|
for _ = 1, 200 do
|
|
battle = topIsBattle()
|
|
if battle then break end
|
|
if getmetatable(game.stack:top()) == TextBox then U.tap(game, "a") end
|
|
U.wait(6)
|
|
end
|
|
end
|
|
check("stepping onto (19,9) opened the catch demo", battle ~= nil)
|
|
check("the demo is the failing one (#636)",
|
|
battle ~= nil and battle.demoFails == true)
|
|
if battle then
|
|
U.log("demo opponent is", tostring(battle.enemy and battle.enemy.name))
|
|
end
|
|
|
|
-- Advance only pages that have finished typing (msgPrompt / msgWaiting are
|
|
-- the same holds the player would clear by hand), so the toss animation and
|
|
-- its sounds play at their own speed.
|
|
local sawMiss, sawCaught, shot = false, false, false
|
|
if battle then
|
|
for _ = 1, 3000 do
|
|
if game.stack:top() ~= battle then break end
|
|
local cur = battle.current
|
|
local line = cur and cur.text
|
|
if type(line) == "string" then
|
|
if miss and line == miss then sawMiss = true end
|
|
if line:find("caught", 1, true) then sawCaught = true end
|
|
end
|
|
if battle.msgPrompt and sawMiss and not shot then
|
|
shot = U.shot(game, SHOT_DIR .. "/bug636_ball_broke_open.png")
|
|
if shot then U.log("captured", SHOT_DIR .. "/bug636_ball_broke_open.png") end
|
|
end
|
|
if battle.msgPrompt or battle.msgWaiting then
|
|
U.tap(game, "a")
|
|
end
|
|
U.wait(2)
|
|
end
|
|
end
|
|
check("the ball broke open and printed the three-shake line", sawMiss)
|
|
check("nothing was caught", not sawCaught)
|
|
|
|
-- ---- the line that only follows a failed throw --------------------------
|
|
local box
|
|
for _ = 1, 400 do
|
|
local top = game.stack:top()
|
|
if getmetatable(top) == TextBox then box = top break end
|
|
U.wait(4)
|
|
end
|
|
check("the old man speaks again after the battle", box ~= nil)
|
|
local shown = {}
|
|
for _, page in ipairs((box and box.pages) or {}) do
|
|
for _, l in ipairs(page) do shown[#shown + 1] = l end
|
|
end
|
|
local joined = table.concat(shown, " / ")
|
|
if box then U.log("his box reads:", joined) end
|
|
check("he says he must be losing his touch",
|
|
joined:find("losing", 1, true) ~= nil)
|
|
if box then
|
|
if U.shot(game, SHOT_DIR .. "/bug636_losing_my_touch.png") then
|
|
U.log("captured", SHOT_DIR .. "/bug636_losing_my_touch.png")
|
|
end
|
|
end
|
|
|
|
local ownedAfter = 0
|
|
for _ in pairs((game.save.pokedex and game.save.pokedex.owned) or {}) do
|
|
ownedAfter = ownedAfter + 1
|
|
end
|
|
check("the demo added nothing to the party", #game.save.party == partyBefore)
|
|
check("and nothing to the dex's owned list", ownedAfter == ownedBefore)
|
|
|
|
-- ---- rewind so it can be watched again ----------------------------------
|
|
game.save.flags.EVENT_COMPLETED_CATCH_TRAINING = nil
|
|
game.save.objectToggles = {}
|
|
U.teleport(game, MAP, stand[1], stand[2], stand[3])
|
|
U.wait(15)
|
|
|
|
if fails > 0 then
|
|
U.log(fails, "check(s) above say FAIL; the screen is not worth watching yet.")
|
|
end
|
|
U.log(("ran the tutorial once and rewound it: you are on (%d,%d), so")
|
|
:format(stand[1], stand[2]))
|
|
U.log("holding " .. stand[3] .. " into (19,9) starts the demo again. The ball")
|
|
U.log("should wobble three times and pop open, then he blames his touch. The")
|
|
U.log("near miss to watch for is three wobbles, the caught jingle, and the")
|
|
U.log("same line anyway.")
|
|
|
|
while true do
|
|
coroutine.yield()
|
|
end
|
|
end
|