Merge pull request #655 from spiritsnails/fix/warden-yesno-645

This commit is contained in:
bryanthaboi
2026-08-02 08:26:05 -04:00
committed by GitHub
3 changed files with 166 additions and 19 deletions
+25 -11
View File
@@ -674,16 +674,18 @@ M.SAFARI_ZONE_SECRET_HOUSE = {
M.WARDENS_HOUSE = {
talk = {
TEXT_WARDENSHOUSE_WARDEN = {
-- Labelled rather than hand-numbered: the branches here have been
-- re-pointed twice now (#535, #645), and every insert used to mean
-- renumbering three jumps that had no way of announcing they were stale.
{ "face_player" }, -- 1
{ "check_flag", "EVENT_GOT_HM04" }, -- 2
-- #535: previously jumped to the same silent-end target as the
-- give-then-thank fallthrough (row 13), so the warden said nothing
-- on every visit after the trade. pokered's .got_item branch
-- (scripts/WardensHouse.asm) instead prints HM04ExplanationText,
-- so route here to the new row 17 that does the same.
{ "jump_if_true", 17 }, -- 3
-- give-then-thank fallthrough, so the warden said nothing on every
-- visit after the trade. pokered's .got_item branch
-- (scripts/WardensHouse.asm) instead prints HM04ExplanationText.
{ "jump_if_true", "got_hm04" }, -- 3
{ "check_item", "GOLD_TEETH" }, -- 4
{ "jump_if_false", 15 }, -- 5
{ "jump_if_false", "no_teeth" }, -- 5
{ "show_text", "_WardensHouseWardenGaveTheGoldTeethText" }, -- 6
{ "take_item", "GOLD_TEETH", 1 }, -- 7
{ "set_flag", "EVENT_GAVE_GOLD_TEETH" }, -- 8
@@ -692,15 +694,27 @@ M.WARDENS_HOUSE = {
{ "give_item", "HM_STRENGTH", 1, false }, -- 10
{ "show_text", "_WardensHouseWardenReceivedHM04Text" }, -- 11
{ "set_flag", "EVENT_GOT_HM04" }, -- 12
{ "jump", 18 }, -- 13 (already got it this convo; jp .done)
{ "jump", 18 }, -- 14 (unused)
{ "show_text", "_WardensHouseWardenGibberish1Text" }, -- 15
{ "jump", 18 }, -- 16 (#535: skip the new explanation row below)
{ "jump", "end" }, -- 13 (jp .done)
-- #645: WardensHouseWardenText prints Gibberish1, then YesNoChoice,
-- and the warden answers the same gibberish either way -- Gibberish2
-- on yes, Gibberish3 on no (scripts/WardensHouse.asm). The port
-- printed the question and walked off before the answer.
{ "label", "no_teeth" }, -- 14
{ "ask", "_WardensHouseWardenGibberish1Text" }, -- 15
{ "jump_if_true", "gibberish_yes" }, -- 16
{ "show_text", "_WardensHouseWardenGibberish3Text" }, -- 17
{ "jump", "end" }, -- 18
{ "label", "gibberish_yes" }, -- 19
{ "show_text", "_WardensHouseWardenGibberish2Text" }, -- 20
{ "jump", "end" }, -- 21
-- #535: pokered .got_item branch (scripts/WardensHouse.asm) --
-- printed on every subsequent talk once EVENT_GOT_HM04 is set.
-- Text is _WardensHouseWardenHM04ExplanationText (text/WardensHouse.asm):
-- HM04 teaches Strength, and hints at the Safari Zone secret house.
{ "show_text", "_WardensHouseWardenHM04ExplanationText" }, -- 17
{ "label", "got_hm04" }, -- 22
{ "show_text", "_WardensHouseWardenHM04ExplanationText" }, -- 23
},
},
}
@@ -0,0 +1,103 @@
-- Driver: #645 Warden's House YES/NO.
--
-- WardensHouseWardenText prints Gibberish1, calls YesNoChoice, and answers
-- with Gibberish2 on yes / Gibberish3 on no (scripts/WardensHouse.asm). The
-- port printed the question and ended the script, so the box just closed.
--
-- Runs the conversation twice against the live script: once answering YES,
-- once walking the cursor down to NO, shooting the YES/NO box and each reply.
-- POKEPORT_DRIVER=tests/drivers/wardens_house_yesno_bug645_test.lua \
-- POKEPORT_SPEED=2 lovec .
return function(game)
local U = dofile("tests/drivers/util.lua")
local DIR = os.getenv("SHOT_DIR") or "/tmp/shots"
-- record what the script actually prints, so the log is checkable on its
-- own and does not depend on reading the shots
local Commands = require("src.script.Commands")
local shown = {}
local origShow = Commands.show_text
-- forward every argument: the 4th is extraOpts, which is how Commands.ask
-- passes its `choice` callback -- drop it and the YES/NO box never appears
Commands.show_text = function(ctx, textId, ...)
shown[#shown + 1] = textId
U.log("text:", textId)
return origShow(ctx, textId, ...)
end
local ChoiceBox = require("src.ui.ChoiceBox")
local function choiceUp()
local top = game.stack:top()
return top and getmetatable(top) == ChoiceBox and top or nil
end
-- mash A until the YES/NO box is up, then hand it back
local function talkUntilChoice()
U.tap(game, "a")
for _ = 1, 600 do
local box = choiceUp()
if box then return box end
U.tap(game, "a")
U.wait(3)
end
return nil
end
local function talkDone()
for _ = 1, 600 do
if game.stack:top() == game.overworld then return true end
U.tap(game, "a")
U.wait(3)
end
return false
end
-- the warden stands at (2,3); face him from the tile below
U.teleport(game, "WARDENS_HOUSE", 2, 4, "up")
-- Lead-in: this is meant to be WATCHED, not just logged. At SPEED=2 the
-- driver runs two iterations per rendered frame, so a wait of N is N/120
-- seconds on screen -- long enough here to find the window before anything
-- happens. Every beat below is held the same way.
U.log("watch now: talking to the WARDEN in 5 seconds")
U.wait(600)
U.shot(game, DIR .. "/warden_0_before.png")
-- ---------------------------------------------------------------- YES
shown = {}
local box = talkUntilChoice()
if not box then
U.log("FAIL no YES/NO box appeared after the gibberish line")
else
U.log("YES/NO box is up, cursor on", box.index == 1 and "YES" or "NO")
U.wait(360) -- hold the box on screen for 3s
U.shot(game, DIR .. "/warden_1_yesno_box.png")
U.tap(game, "a") -- take the default, YES
U.wait(360)
U.shot(game, DIR .. "/warden_2_yes_reply.png")
end
talkDone()
U.log("YES branch printed:", table.concat(shown, ","))
U.log("now the same conversation again, answering NO")
U.wait(360)
-- ----------------------------------------------------------------- NO
shown = {}
box = talkUntilChoice()
if not box then
U.log("FAIL no YES/NO box on the second talk")
else
U.wait(240)
U.tap(game, "down") -- walk the cursor onto NO, on screen
U.wait(360)
U.log("cursor now on", box.index == 1 and "YES" or "NO")
U.shot(game, DIR .. "/warden_3_yesno_on_no.png")
U.tap(game, "a")
U.wait(360)
U.shot(game, DIR .. "/warden_4_no_reply.png")
end
talkDone()
U.log("NO branch printed:", table.concat(shown, ","))
U.wait(360)
Commands.show_text = origShow
end
+38 -8
View File
@@ -36,13 +36,22 @@ local script = story.WARDENS_HOUSE.talk.TEXT_WARDENSHOUSE_WARDEN
-- instrument show_text the way parity_gift_atomicity.lua does, to record
-- exactly which text ids actually printed
local shown = {}
-- forward EVERY argument: the 4th is extraOpts, which is how Commands.ask
-- hands down its `choice` callback. A wrapper that stops at `subs` silently
-- turns every ask in the script back into a plain show_text -- no YES/NO box,
-- and ctx.lastCheck left holding whatever the previous check_* put there.
local origShow = Commands.show_text
Commands.show_text = function(ctx, textId, subs)
Commands.show_text = function(ctx, textId, ...)
shown[#shown + 1] = textId
return origShow(ctx, textId, subs)
return origShow(ctx, textId, ...)
end
local function runScript()
-- `button` drives the whole conversation: both A and B page a text box, and
-- on the YES/NO box A takes the cursor's default (YES) while B snaps to NO
-- and answers false (ChoiceBox:update, .choseSecondMenuItem). So holding A
-- runs the yes branch and holding B runs the no branch, with no reaching
-- into the choice box from the test.
local function runScript(button)
shown = {}
StateStack:init()
local ow = { map = { id = "WARDENS_HOUSE", def = { label = "WardensHouse" } },
@@ -53,7 +62,7 @@ local function runScript()
local guard = 0
while r:isRunning() and guard < 3000 do
guard = guard + 1
Input.pressed = { a = true }
Input.pressed = { [button or "a"] = true }
StateStack:update(1 / 60)
r:update()
end
@@ -85,13 +94,34 @@ check(runScript(), "a third talk completes")
eq(table.concat(shown, ","), "_WardensHouseWardenHM04ExplanationText",
"the explanation text repeats on every later talk, not just the first")
-- === 3) unrelated path is unchanged: no GOLD TEETH, no flag yet ===
-- === 3) no GOLD TEETH yet: the gibberish question, then a YES/NO, then the
-- warden's answer -- Gibberish2 on yes, Gibberish3 on no (#645).
-- The port used to stop dead after the question. ===
Game.save = SaveData.newGame()
check(runScript(), "empty-handed talk completes")
eq(table.concat(shown, ","), "_WardensHouseWardenGibberish1Text",
"without the GOLD TEETH the Warden's gibberish line is unchanged")
check(runScript("a"), "empty-handed talk completes on yes")
eq(table.concat(shown, ","),
"_WardensHouseWardenGibberish1Text,_WardensHouseWardenGibberish2Text",
"answering YES gets the warden's reply, not silence (#645)")
check(not Flags.get(Game.save, "EVENT_GOT_HM04"), "no HM04 yet")
Game.save = SaveData.newGame()
check(runScript("b"), "empty-handed talk completes on no")
eq(table.concat(shown, ","),
"_WardensHouseWardenGibberish1Text,_WardensHouseWardenGibberish3Text",
"and answering NO gets the other reply (#645)")
-- the question is asked, not just printed: `ask` is what puts the YES/NO box
-- up, so a future edit that downgrades it back to show_text fails here
local askRow
for _, row in ipairs(script) do
if row[2] == "_WardensHouseWardenGibberish1Text" then askRow = row[1] end
end
eq(askRow, "ask", "the gibberish line is asked with a YES/NO, not just shown")
-- neither answer touches the teeth trade
check(not Flags.get(Game.save, "EVENT_GAVE_GOLD_TEETH"),
"and neither answer hands over teeth the player does not have")
Commands.show_text = origShow
S.finish()