-- Manual check that the Silph Co 2F worker begs before handing over TM36 (#393). -- pokered scripts/SilphCo2F.asm prints .PleaseTakeThisText (text/SilphCo2F.asm:1, -- "Eeek!" / "No! Stop! Help!" ... "please take this!") before GiveItem; the port -- had no such string in the cache, so the talk opened on "got TM36!". Re-import -- first (CACHE_FORMAT bump). No POKEPORT_SPEED here: fast-forward -- desynchronizes the item jingle from the box it belongs to. -- SHOT_DIR=/tmp/shots POKEPORT_DRIVER=tests/drivers/silph_tm36_bug393_test.lua POKEPORT_IDENTITY=bug393 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 Bag = require("src.inventory.Bag") local mapScripts = require("data.scripts.init") -- pokered data/maps/objects/SilphCo2F.asm: SILPHCO2F_SILPH_WORKER_F stands at -- (10, 1) facing UP with row 0 walled off, so she is talked to from below. local MAP = "SILPH_CO_2F" local TEXT = "TEXT_SILPHCO2F_SILPH_WORKER_F" local WORKER = "SILPHCO2F_SILPH_WORKER_F" local PRE = "SilphCo2FSilphWorkerFPleaseTakeThisText" local STAND = { x = 10, y = 2, facing = "up" } local function check(label, ok) U.log(ok and "PASS" or "FAIL", label) return ok end -- the whole bug was a missing cache string, so say which half is wrong when -- the boxes come out empty local pre = game.data.text[PRE] check(PRE .. " is in the text cache", type(pre) == "string" and pre ~= "") if type(pre) == "string" then check("it is the scared line", pre:find("Eeek!", 1, true) ~= nil and pre:find("please take this!", 1, true) ~= nil) U.log("pre text reads:", (pre:gsub("[\n\011\012]", " / "))) end check(MAP .. "/" .. TEXT .. " runs a hand-ported script", type(mapScripts.talkScript(MAP, TEXT)) == "function") check("TM_SELFDESTRUCT is a known item", game.data.items.TM_SELFDESTRUCT ~= nil) -- a save that already has the TM takes the explanation-only branch if game.save.flags.EVENT_GOT_TM36 then game.save.flags.EVENT_GOT_TM36 = nil Bag.remove(game.save, "TM_SELFDESTRUCT", 1) U.log("EVENT_GOT_TM36 was already set; cleared it to replay the gift") end local vol = game.save.options and game.save.options.sfxVol if (vol or 0) == 0 then U.log("sfxVol is 0: the item jingle will be silent, raise it in OPTION first") else U.log("sfxVol", tostring(vol), "-- one Get_Item1 jingle, after the pleading") end U.teleport(game, MAP, STAND.x, STAND.y, STAND.facing) U.wait(10) local function workerIn(ow) for _, n in ipairs(ow.npcs or {}) do if n.def and n.def.name == WORKER then return n end end return nil end -- re-reads game.overworld: the fallback below teleports again, which rebuilds -- the state and its npc list local function facingTheWorker() local ow = game.overworld local her = ow and workerIn(ow) if not her then return false end local fx, fy = ow.player:facingCell() return ow:npcAtCell(fx, fy) == her end local ow = game.overworld local her = workerIn(ow) check("the worker is loaded on " .. MAP, her ~= nil) if her and not facingTheWorker() then -- a map edit or a mod moved her: stand on any free walkable neighbour. -- {dx, dy, facing} is the offset from her cell plus the way back at her. local sides = { { 0, 1, "up" }, { 0, -1, "down" }, { 1, 0, "left" }, { -1, 0, "right" }, } for _, s in ipairs(sides) do local cx, cy = her.cellX + s[1], her.cellY + s[2] if ow.map:isWalkableCell(cx, cy) and not ow:npcAtCell(cx, cy) then U.log(("(%d, %d) is blocked, standing on"):format(STAND.x, STAND.y), cx, cy, "facing", s[3]) U.teleport(game, MAP, cx, cy, s[3]) U.wait(10) ow = game.overworld break end end end check("the player is facing her", facingTheWorker()) local function boxText() local top = game.stack:top() if getmetatable(top) ~= TextBox then return nil end local out = {} for _, page in ipairs(top.pages or {}) do for _, line in ipairs(page) do out[#out + 1] = line end end return table.concat(out, " / ") end local function hasTm() return (game.save.inventory or {}).TM_SELFDESTRUCT ~= nil end U.tap(game, "a") U.wait(30) local first = boxText() check("A on her opens a text box", first ~= nil) if first then U.log("box 1 reads:", first) end check("the first box is the scared line, not the TM", first ~= nil and first:find("Eeek!", 1, true) ~= nil) check("and the TM is still hers while it is up", not hasTm()) U.shot(game, DIR .. "/bug393_1_scared.png") -- read the rest of the conversation the way a player does. The pleading runs -- seven lines before GiveItem, and the gift box and the SELFDESTRUCT warning -- follow it, so turn pages until the talk actually ends instead of counting -- them out -- the bound is only there so a stuck box cannot hang the driver. -- boxText reads the whole box, so several A presses walk one string: log a -- box the first time it is seen, not once per page turn local i, last = 1, first for _ = 1, 40 do U.tap(game, "a") U.wait(40) local t = boxText() if not t then break end if t ~= last then i, last = i + 1, t U.log(("box %d reads:"):format(i), t) if t:find("TM36", 1, true) then U.shot(game, DIR .. "/bug393_2_tm36.png") end end end check("TM36 ended up in the bag", hasTm()) check("EVENT_GOT_TM36 is set", game.save.flags.EVENT_GOT_TM36 ~= nil) U.log("bag holds", tostring(Bag.slots(game.save)), "item kinds now") -- hand it back unclaimed so the pleading can be watched and heard live game.save.flags.EVENT_GOT_TM36 = nil Bag.remove(game.save, "TM_SELFDESTRUCT", 1) while getmetatable(game.stack:top()) == TextBox do U.tap(game, "a") U.wait(20) end U.teleport(game, MAP, STAND.x, STAND.y, STAND.facing) U.wait(10) U.log("the gift was rolled back; the pad is yours, press A on her") U.log("She should panic first: \"Eeek!/No! Stop! Help!\", then work out you") U.log("are not a Rocket and offer the TM, and only then the jingle plays and") U.log("the box says got TM36!, followed by the SELFDESTRUCT warning.") U.log("Press A on her once more after that: straight to the warning, no panic.") while true do coroutine.yield() end end