mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 00:10:56 +02:00
210 lines
8.7 KiB
Lua
210 lines
8.7 KiB
Lua
-- Driver: Fighting Dojo prize balls, #853 (dex page first) and #854 (the
|
|
-- question stays on screen under YES/NO). pokered scripts/FightingDojo.asm
|
|
-- runs `ld a, HITMONLEE / call DisplayPokedex` before .Text, and .Text is a
|
|
-- text_end string printed with PrintText immediately followed by YesNoChoice.
|
|
-- No POKEPORT_SPEED here: the dex page and the YES/NO pop are what is judged.
|
|
-- SHOT_DIR=/tmp/shots POKEPORT_DRIVER=tests/drivers/dojo_balls_bug853_test.lua POKEPORT_IDENTITY=bug853 POKEPORT_TOUCH=0 POKEPORT_VERSION=red love .
|
|
return function(game)
|
|
local U = dofile("tests/drivers/util.lua")
|
|
local DIR = os.getenv("SHOT_DIR") or "/tmp/shots"
|
|
local TextBox = require("src.render.TextBox")
|
|
local ChoiceBox = require("src.ui.ChoiceBox")
|
|
local DexEntryMenu = require("src.ui.DexEntryMenu")
|
|
local MapScripts = require("src.script.MapScripts")
|
|
local Screens = require("src.ui.Screens")
|
|
local OW = require("src.world.OverworldController")
|
|
local Pokemon = require("src.pokemon.Pokemon")
|
|
|
|
-- pokered data/maps/objects/FightingDojo.asm: the two SPRITE_POKE_BALL
|
|
-- objects sit at (4, 1) HITMONLEE and (5, 1) HITMONCHAN, on the north wall
|
|
-- under the posters. The only approach is from the mat below them.
|
|
local MAP = "FIGHTING_DOJO"
|
|
local LEE = { name = "FIGHTINGDOJO_HITMONLEE_POKE_BALL", x = 4, y = 1 }
|
|
local CHAN = { name = "FIGHTINGDOJO_HITMONCHAN_POKE_BALL", x = 5, y = 1 }
|
|
local START = { x = 4, y = 4 } -- walk up from here to (4, 2), facing LEE
|
|
|
|
local failures = {}
|
|
local function check(cond, msg)
|
|
if cond then U.log("PASS", msg) else
|
|
failures[#failures + 1] = msg
|
|
U.log("FAIL", msg)
|
|
end
|
|
return cond
|
|
end
|
|
|
|
local function topIs(mt) return getmetatable(game.stack:top()) == mt end
|
|
local function under()
|
|
return game.stack.states[#game.stack.states - 1]
|
|
end
|
|
|
|
local function npcByName(ow, name)
|
|
for _, n in ipairs(ow.npcs or {}) do
|
|
if n.def and n.def.name == name then return n end
|
|
end
|
|
end
|
|
|
|
local function pageText()
|
|
local top = game.stack:top()
|
|
if getmetatable(top) ~= TextBox then return "" end
|
|
local page = top.pages and top.pages[top.pageIndex]
|
|
return page and table.concat(page, "\n") or ""
|
|
end
|
|
|
|
local function waitFor(cond, cap)
|
|
for _ = 1, (cap or 200) do
|
|
if cond() then return true end
|
|
U.wait(2)
|
|
end
|
|
return cond()
|
|
end
|
|
|
|
local function mashUntil(cond, cap)
|
|
for _ = 1, (cap or 100) do
|
|
if cond() then return true end
|
|
U.tap(game, "a")
|
|
U.wait(2)
|
|
end
|
|
return cond()
|
|
end
|
|
|
|
-- fresh dojo with the master already beaten and neither prize taken
|
|
local function seed(x, y, facing)
|
|
while game.stack:top() do game.stack:pop() end
|
|
game.save.flags = {
|
|
EVENT_BEAT_KARATE_MASTER = true,
|
|
EVENT_BEAT_FIGHTING_DOJO_TRAINER_0 = true,
|
|
EVENT_BEAT_FIGHTING_DOJO_TRAINER_1 = true,
|
|
EVENT_BEAT_FIGHTING_DOJO_TRAINER_2 = true,
|
|
EVENT_BEAT_FIGHTING_DOJO_TRAINER_3 = true,
|
|
}
|
|
game.save.defeatedTrainers = { FIGHTING_DOJO_obj_1 = true }
|
|
game.save.objectToggles = {}
|
|
game.save.player.name = game.save.player.name or "RED"
|
|
-- one mon so give_pokemon has a party to append to, and room for a prize
|
|
game.save.party = { Pokemon.new(game.data, "CHARIZARD", 50) }
|
|
game.stack:push(OW, MAP, x, y, facing or "up")
|
|
U.wait(10)
|
|
return game.stack:top()
|
|
end
|
|
|
|
local ow = seed(START.x, START.y, "up")
|
|
|
|
------------------------------------------------------------------
|
|
-- machine-checkable half: seed, objects, script rows, text, screen id
|
|
------------------------------------------------------------------
|
|
check(game.save.flags.EVENT_BEAT_KARATE_MASTER == true,
|
|
"EVENT_BEAT_KARATE_MASTER is set (the balls answer at all)")
|
|
check(not game.save.flags.EVENT_GOT_HITMONLEE
|
|
and not game.save.flags.EVENT_GOT_HITMONCHAN,
|
|
"neither prize taken yet (no 'greedy' refusal path)")
|
|
|
|
local leeBall, chanBall = npcByName(ow, LEE.name), npcByName(ow, CHAN.name)
|
|
check(leeBall ~= nil, "HITMONLEE ball object loaded")
|
|
check(chanBall ~= nil, "HITMONCHAN ball object loaded")
|
|
check(leeBall and leeBall.cellX == LEE.x and leeBall.cellY == LEE.y,
|
|
("HITMONLEE ball sits at the asm cell (%d, %d)"):format(LEE.x, LEE.y))
|
|
check(chanBall and chanBall.cellX == CHAN.x and chanBall.cellY == CHAN.y,
|
|
("HITMONCHAN ball sits at the asm cell (%d, %d)"):format(CHAN.x, CHAN.y))
|
|
|
|
check(type(MapScripts.talkScript(MAP, "TEXT_FIGHTINGDOJO_HITMONLEE_POKE_BALL"))
|
|
== "function",
|
|
"TEXT_FIGHTINGDOJO_HITMONLEE_POKE_BALL has a hand-ported talk script")
|
|
check(type(MapScripts.talkScript(MAP, "TEXT_FIGHTINGDOJO_HITMONCHAN_POKE_BALL"))
|
|
== "function",
|
|
"TEXT_FIGHTINGDOJO_HITMONCHAN_POKE_BALL has a hand-ported talk script")
|
|
|
|
-- the ask() string is the extracted descriptor, not the "You want X?" stub
|
|
local leeText = game.data.text._FightingDojoHitmonleePokeBallText
|
|
local chanText = game.data.text._FightingDojoHitmonchanPokeBallText
|
|
check(type(leeText) == "string" and leeText ~= "",
|
|
"_FightingDojoHitmonleePokeBallText resolves")
|
|
check(type(chanText) == "string" and chanText ~= "",
|
|
"_FightingDojoHitmonchanPokeBallText resolves")
|
|
if type(leeText) == "string" then
|
|
U.log("lee prompt reads:", (leeText:gsub("\n", " / ")))
|
|
end
|
|
local dexOk = pcall(Screens.get, game, "DexEntryMenu")
|
|
check(dexOk, "DexEntryMenu resolves through the Screens registry")
|
|
|
|
------------------------------------------------------------------
|
|
-- rehearsal on the HITMONCHAN ball, answered NO so nothing is consumed
|
|
------------------------------------------------------------------
|
|
if chanBall then
|
|
ow:talkTo(chanBall)
|
|
check(waitFor(function() return topIs(DexEntryMenu) end, 60),
|
|
"#853: the ball opens the HITMONCHAN dex page before any question")
|
|
U.shot(game, DIR .. "/dojo_balls_1_dex.png")
|
|
U.tap(game, "b")
|
|
check(waitFor(function() return topIs(TextBox) end, 60),
|
|
"#853: closing the dex page leads into the offer text")
|
|
mashUntil(function() return topIs(ChoiceBox) end, 60)
|
|
check(topIs(ChoiceBox), "#854: the YES/NO menu opens on the offer")
|
|
check(getmetatable(under()) == TextBox,
|
|
"#854: the question box is still on the stack under the YES/NO menu")
|
|
U.shot(game, DIR .. "/dojo_balls_2_choice.png")
|
|
U.tap(game, "b") -- B answers NO; the prize stays unclaimed
|
|
waitFor(function() return game.stack:top() == ow end, 120)
|
|
check(not game.save.flags.EVENT_GOT_HITMONCHAN,
|
|
"answering NO leaves the HITMONCHAN prize unclaimed")
|
|
check(#game.save.party == 1, "answering NO adds nothing to the party")
|
|
end
|
|
|
|
------------------------------------------------------------------
|
|
-- hand-off: walk to the HITMONLEE ball and open it for real
|
|
------------------------------------------------------------------
|
|
ow = seed(START.x, START.y, "up")
|
|
for _ = 1, 12 do
|
|
if ow.player.cellY <= LEE.y + 1 then break end
|
|
U.hold(game, "up", 16)
|
|
U.wait(4)
|
|
end
|
|
|
|
local function facingTheBall()
|
|
local cur = game.overworld
|
|
local ball = cur and npcByName(cur, LEE.name)
|
|
if not ball then return false end
|
|
local fx, fy = cur.player:facingCell()
|
|
return cur:npcAtCell(fx, fy) == ball
|
|
end
|
|
|
|
if not facingTheBall() then
|
|
-- a map edit or a mod moved the ball: stand on any free walkable
|
|
-- neighbour instead. {dx, dy, facing} is the offset from the ball to
|
|
-- the stand cell plus the direction that looks back at it.
|
|
local sides = {
|
|
{ 0, 1, "up" }, { 1, 0, "left" }, { -1, 0, "right" }, { 0, -1, "down" },
|
|
}
|
|
for _, s in ipairs(sides) do
|
|
local cx, cy = LEE.x + s[1], LEE.y + s[2]
|
|
if ow.map:isWalkableCell(cx, cy) and not ow:npcAtCell(cx, cy) then
|
|
U.log("walk up stopped short; standing on", cx, cy, "facing", s[3])
|
|
ow = seed(cx, cy, s[3])
|
|
break
|
|
end
|
|
end
|
|
end
|
|
check(facingTheBall(), "player is standing against the HITMONLEE ball")
|
|
|
|
U.tap(game, "a")
|
|
check(waitFor(function() return topIs(DexEntryMenu) end, 60),
|
|
"#853: pressing A opens the HITMONLEE dex page")
|
|
U.shot(game, DIR .. "/dojo_balls_3_handoff.png")
|
|
|
|
if #failures == 0 then
|
|
U.log("all checks passed")
|
|
else
|
|
U.log(("%d check(s) failed:"):format(#failures), table.concat(failures, "; "))
|
|
end
|
|
|
|
U.log("On screen now: the HITMONLEE dex page the ball opened, name and")
|
|
U.log("sprite only, since the mon is seen but not owned yet. Press B: the")
|
|
U.log("offer types out, and the YES/NO menu should appear above it with the")
|
|
U.log("question still readable -- the old bug swapped the text away for a")
|
|
U.log("bare YES/NO over the overworld. Answer YES to take HITMONLEE; the")
|
|
U.log("HITMONCHAN ball beside it stays put and gives the greedy refusal.")
|
|
|
|
while true do
|
|
coroutine.yield()
|
|
end
|
|
end
|