-- scripts/SilphCo7F.asm:206-252 -- POKEPORT_DRIVER=tests/drivers/silph7f_rival_exit_bug2242_test.lua \ -- POKEPORT_IDENTITY=bug2242 POKEPORT_TOUCH=0 POKEPORT_VERSION=red \ -- POKEPORT_SHOT_DIR=/tmp/shots love . (BUG2242_TILE=2 runs the upper tile) return function(game) local U = dofile("tests/drivers/util.lua") local DIR = os.getenv("POKEPORT_SHOT_DIR") or os.getenv("SHOT_DIR") or "/tmp/shots" local Pokemon = require("src.pokemon.Pokemon") local Flags = require("src.script.Flags") local TextBox = require("src.render.TextBox") local mapScripts = require("data.scripts.init") local MAP = "SILPH_CO_7F" local TILE_Y = tonumber(os.getenv("BUG2242_TILE") or "3") if TILE_Y ~= 2 and TILE_Y ~= 3 then TILE_Y = 3 end local TRIG_X = 3 local WANT = (TILE_Y == 2) and { rx = 3, ry = 3, dirs = { "right", "right" }, cells = { "(4,3)", "(5,3)" }, shotCell = "(4,3)" } or { rx = 3, ry = 4, dirs = { "left", "up", "up", "right", "right", "right", "down" }, cells = { "(2,4)", "(2,3)", "(2,2)", "(3,2)", "(4,2)", "(5,2)", "(5,3)" }, shotCell = "(4,2)" } local failures = 0 local function check(label, ok) U.log(ok and "PASS" or "FAIL", label) if not ok then failures = failures + 1 end return ok end local function finish() U.log(("%s: %d failure(s)"):format(failures == 0 and "DONE" or "DONE WITH FAILURES", failures)) love.event.quit(failures == 0 and 0 or 1) while true do coroutine.yield() end end local tank = Pokemon.new(game.data, "MEWTWO", 100) tank.moves = { { id = "PSYCHIC_M", pp = 99 }, { id = "THUNDERBOLT", pp = 99 }, { id = "ICE_BEAM", pp = 99 }, { id = "RECOVER", pp = 99 }, } game.save.party = { tank } game.save.player = game.save.player or {} game.save.player.name = game.save.player.name or "RED" game.save.player.rival = game.save.player.rival or "BLUE" game.save.defeatedTrainers = {} game.save.objectToggles = game.save.objectToggles or {} game.save.objectToggles[MAP] = nil Flags.set(game.save, "EVENT_CHOSE_SQUIRTLE") Flags.clear(game.save, "EVENT_BEAT_SILPH_CO_RIVAL") U.teleport(game, MAP, TRIG_X + 1, TILE_Y, "left") U.wait(10) local ow = game.overworld local t = game.data.text for _, key in ipairs({ "_SilphCo7FRivalText", "_SilphCo7FRivalWaitedHereText", "_SilphCo7FRivalDefeatedText", "_SilphCo7FRivalGoodLuckToYouText" }) do check(key .. " resolves to a string", type(t[key]) == "string" and t[key] ~= "") end local defeated = t._SilphCo7FRivalDefeatedText or "" local needle = defeated:match("Oh ho") and "Oh ho" or "BOSS" local spawn for _, o in ipairs(game.data.maps[MAP].objects or {}) do if o.name == "SILPHCO7F_RIVAL" then spawn = o end end check("SILPHCO7F_RIVAL has an object_event", spawn ~= nil) check("it spawns on (3,7) as in data/maps/objects/SilphCo7F.asm", spawn ~= nil and spawn.x == 3 and spawn.y == 7) check("SILPH_CO_7F (5,4) is the desk, not floor", not ow.map:isWalkableCell(5, 4)) local Music = require("src.core.Music") local realPlay = Music.play Music.play = function() end local rows, probeFacing do local probe = { runner = { isRunning = function() return false end, run = function(_, r) rows = r end }, player = { facing = "left" }, npcByIndex = function() return { def = { name = "X" } } end, } local script = mapScripts.get(MAP) check("SILPH_CO_7F has an onStep hook", script ~= nil and script.onStep ~= nil) if script and script.onStep then check(("onStep fires on the ambush tile (%d,%d)"):format(TRIG_X, TILE_Y), script.onStep(game, probe, TRIG_X, TILE_Y) == true) end probeFacing = probe.player.facing end Music.play = realPlay local moveTo, walk, arm, battle, mapDefeat for i, r in ipairs(rows or {}) do if r[1] == "move_npc_to" then moveTo = r end if r[1] == "walk_npc" then walk = r end if r[1] == "save_end_battle_text" then arm = i end if r[1] == "rival_battle" then battle = i end if r[1] == "show_text" and r[2] == "_SilphCo7FRivalDefeatedText" then mapDefeat = i end end check("the scene carries move/walk rows", moveTo ~= nil and walk ~= nil) check("player is turned down at the rival", probeFacing == "down") check("_SilphCo7FRivalDefeatedText is armed right before rival_battle", arm ~= nil and battle ~= nil and arm == battle - 1 and rows[arm][2] == "_SilphCo7FRivalDefeatedText") check("the loss line is not printed on the map afterwards", mapDefeat == nil) if moveTo and walk then U.log(("plan: rival to (%d,%d), exit %s"):format( moveTo[3], moveTo[4], table.concat(walk[3], ", "))) check(("rival parks on (%d,%d)"):format(WANT.rx, WANT.ry), moveTo[3] == WANT.rx and moveTo[4] == WANT.ry) local same = #walk[3] == #WANT.dirs for i = 1, #WANT.dirs do if walk[3][i] ~= WANT.dirs[i] then same = false end end check("exit list is " .. table.concat(WANT.dirs, ", "), same) local D = { up = { 0, -1 }, down = { 0, 1 }, left = { -1, 0 }, right = { 1, 0 } } local x, y, clean = moveTo[3], moveTo[4], true for i, d in ipairs(walk[3]) do x, y = x + D[d][1], y + D[d][2] if not ow.map:isWalkableCell(x, y) then clean = false U.log((" exit step %d (%s) walks into solid ground at (%d,%d)") :format(i, d, x, y)) end if x == TRIG_X and y == TILE_Y then clean = false U.log((" exit step %d (%s) walks through the player on (%d,%d)") :format(i, d, x, y)) end end check("every exit cell is walkable and none is the player's", clean) check("the exit ends on the (5,3) teleporter", x == 5 and y == 3) end U.hold(game, "left", 24) local function boxText() local top = game.stack:top() if getmetatable(top) ~= TextBox then return "" end local parts = {} for _, page in ipairs(top.pages or {}) do if type(page) == "table" then for _, line in ipairs(page) do parts[#parts + 1] = tostring(line) end end end return table.concat(parts, " ") end local function rivalNpc() for _, n in ipairs(game.overworld and game.overworld.npcs or {}) do if n.def and n.def.name == "SILPHCO7F_RIVAL" then return n end end end local waited = t._SilphCo7FRivalWaitedHereText or "" local waitedNeedle = waited:match("waited") and "waited" or "here" local parked for _ = 1, 600 do local r = rivalNpc() local s = boxText() if r and #ow.scriptMoves == 0 and not r.moving and s:find(waitedNeedle, 1, true) then parked = r break end if s ~= "" then U.tap(game, "a") end U.wait(2) end check("the rival showed up and stopped walking", parked ~= nil) if parked then U.log(("rival parked on (%d,%d) facing %s; player on (%d,%d) facing %s") :format(parked.cellX, parked.cellY, tostring(parked.facing), ow.player.cellX, ow.player.cellY, tostring(ow.player.facing))) check(("he is on (%d,%d) live"):format(WANT.rx, WANT.ry), parked.cellX == WANT.rx and parked.cellY == WANT.ry) end local sawBattle, defeatInBattle, moneyAfter, shotDefeat = false, false, false, false for f = 1, 4000 do local top = game.stack:top() if top and top.phase then if not sawBattle then U.log("battle started at loop " .. f) end sawBattle = true local cur = top.current local text = cur and type(cur.text) == "string" and cur.text or "" if text:find(needle, 1, true) then defeatInBattle = true if not shotDefeat then shotDefeat = true U.log("in-battle loss line: " .. text:gsub("\n", " / ")) check("the loss line opens with the rival's tag", text:sub(1, #game.save.player.rival + 2) == game.save.player.rival .. ": ") U.wait(45) if U.shot(game, DIR .. "/2241_01_defeat_text_in_battle.png") then U.log("captured", DIR .. "/2241_01_defeat_text_in_battle.png") end end elseif defeatInBattle and text:find("for winning", 1, true) then moneyAfter = true end if top.phase == "menu" then top.menuIndex = 1 elseif top.phase == "moveSelect" then top.moveIndex = 1 end U.tap(game, "a") if f > 2400 and top.onFinish then U.log("force-finishing a stalled battle") top.onFinish("win") if game.stack:top() == top then game.stack:pop() end end elseif sawBattle then break elseif top ~= ow then U.tap(game, "a") end U.wait(2) end check("the battle ran and ended", sawBattle) check("the loss line printed inside the battle state", defeatInBattle) check("the money line followed it", moneyAfter) local mapBoxes = {} local sawLuck = false local luck = t._SilphCo7FRivalGoodLuckToYouText or "" local luckNeedle = luck:match("moving on up") and "moving on up" or "luck" for _ = 1, 600 do local s = boxText() if s ~= "" and mapBoxes[#mapBoxes] ~= s then mapBoxes[#mapBoxes + 1] = s end if s:find(luckNeedle, 1, true) then sawLuck = true break end if game.stack:top() ~= ow then U.tap(game, "a") end U.wait(3) end check("GoodLuckToYou is on screen after the battle", sawLuck) check("EVENT_BEAT_SILPH_CO_RIVAL is set", game.save.flags.EVENT_BEAT_SILPH_CO_RIVAL == true) local leaked = false for _, s in ipairs(mapBoxes) do if s:find(needle, 1, true) then leaked = true end end check("no map box carried the loss line", not leaked) local visited, last = {}, nil local function sample() local r = rivalNpc() if not r then return false end local function note(x, y) local key = ("(%d,%d)"):format(x, y) if key ~= last then visited[#visited + 1] = key last = key end end if r.moving and r.targetX then note(r.targetX, r.targetY) else note(r.cellX, r.cellY) end return true end sample() local shotWalk = false local gone = false U.tap(game, "a") for _ = 1, 1200 do if game.stack:top() ~= ow and getmetatable(game.stack:top()) == TextBox then U.tap(game, "a") end if not sample() then gone = true break end if not shotWalk and last == WANT.shotCell then shotWalk = true U.wait(4) if U.shot(game, DIR .. "/2242_02_walk_around_player.png") then U.log("captured", DIR .. "/2242_02_walk_around_player.png") end sample() end U.wait(1) end U.log("rival visited: " .. table.concat(visited, " ")) check("the rival was hidden at the end of the walk", gone) local start = ("(%d,%d)"):format(WANT.rx, WANT.ry) check("the walk starts from his parked cell", visited[1] == start) local path = {} for i = 2, #visited do path[#path + 1] = visited[i] end check("visited cells are " .. table.concat(WANT.cells, " "), table.concat(path, " ") == table.concat(WANT.cells, " ")) check("he ends on (5,3)", visited[#visited] == "(5,3)") if TILE_Y == 3 then check("he arrives on (5,3) from (5,2)", visited[#visited - 1] == "(5,2)") local bad = false for _, c in ipairs(visited) do if c == "(4,3)" or c == "(5,4)" then bad = true end end check("he never steps on (4,3) or the (5,4) desk", not bad) end check("the rival is gone from the map", rivalNpc() == nil) finish() end