Files
gen1recomp/tests/drivers/em_tv_shows.lua
T
bryanthaboi 690f0cd9a1 TOUCHDOWN
2026-09-27 16:07:31 -04:00

353 lines
14 KiB
Lua

local U = require("tests.drivers.util")
local S = require("tests.drivers.em_story_util")
local L = require("tests.drivers.em_battle_loop")
return function(game)
local d = S.new("em_tv_shows", "/tmp/em_tv_shows")
local check, note = d.check, d.note
local function shot(name) d.shot(game, name) end
local function finish() return d.finish() end
for _ = 1, 900 do
if game.phase == "boot" and game.boot then break end
U.wait(1)
end
if not check(game.boot ~= nil, "boot reached") then return finish() end
local SaveData = require("src.core.SaveData")
local Schema = require("src.core.game3.save_schema_firered")
local raw = love.filesystem.read("saves/emerald/slot1.lua")
if not check(type(raw) == "string", "identity has the post-Hall-of-Fame save") then return finish() end
local session = Schema.fromSaveTable(SaveData.decode(raw))
require("src.core.game3.options").bind(session, game.options)
game:adoptSave(session, true)
game.sessionStartedAt = os.time()
game:_enterField(session, "continue")
U.wait(30)
S.settle(game)
local Runtime = require("src.core.game3.runtime")
local Map = require("src.core.game3.map")
local Player = require("src.core.game3.player")
local Message = require("src.ui.game3.message")
local Field = require("src.core.game3.field")
local Collision = require("src.core.game3.collision")
local Battle = require("src.core.game3.battle")
local PartyMenu = require("src.ui.game3.party_menu")
local Naming = require("src.ui.game3.naming")
local Rtc = require("src.core.game3.rtc")
local MB = require("src.core.game3.mb")
local Tv = require("src.core.game3.rse.tv")
local NativesTv = require("src.core.game3.scripting.natives_tv")
local Rse = require("src.core.game3.rse.init")
local C = S.C()
session = Runtime.getSession()
check(session and session.version == "emerald" and S.flag("FLAG_SYS_GAME_CLEAR"), "post-game Emerald session ("
.. tostring(session and session.name) .. ")")
check(S.flag("FLAG_SYS_TV_START"), "FLAG_SYS_TV_START set by the story")
Tv.state(session)
session.gabbyAndTyData.onAir = false
Tv.clearPokeNews(session)
for i = 0, Tv.TV_SHOWS_COUNT - 1 do Tv.deleteShow(session.tvShows, i) end
session.repelSteps = 0
local function teleport(mapId, x, y, facing)
S.settle(game)
local ok, err = pcall(function() Map.load(nil, game, mapId, { x = x, y = y, facing = facing }) end)
if not ok then note("Map.load " .. mapId .. ": " .. tostring(err)) end
local s = Runtime.getSession()
if s then s.x, s.y, s.facing = x, y, facing end
Player.cellX, Player.cellY = x, y
Player.px, Player.py = x * 16, y * 16
Player.targetX, Player.targetY = x, y
Player.facing = facing
U.wait(20)
local ax, ay = Player.cellX, Player.cellY
S.settle(game)
if os.getenv("EM_TV_DEBUG") then
note(string.format("teleport %s want %d,%d after wait %d,%d after settle %d,%d", mapId, x, y, ax, ay,
Player.cellX, Player.cellY))
end
return ok and S.mapNow() == mapId
end
local function findBehavior(name)
local g3 = Runtime._game
local def = g3 and g3.data and g3.data.maps and g3.data.maps[S.mapNow()]
local layout = def and def.midLayout
local want = MB.id(name)
local out = {}
for y = 0, (layout and layout.height or 0) - 1 do
for x = 0, (layout and layout.width or 0) - 1 do
if Collision.behavior(x, y) == want then out[#out + 1] = { x, y } end
end
end
return out
end
local function tvMetatile()
local tv = findBehavior("TELEVISION")[1]
if not tv then return nil, nil end
local o = Field.metatileOverrideAt(S.mapNow(), tv[1], tv[2])
return o and o.metatile, tv
end
local function typeName(text)
local st = Naming._state
for _ = 1, 20 do
if st.name == "" then break end
U.tap(game, "b")
U.wait(3)
end
for ch in text:gmatch("[%z\1-\127\194-\244][\128-\191]*") do
local pi, r, c
for p, page in ipairs(st.pages or {}) do
for ri, row in ipairs(page.rows) do
for ci, cell in ipairs(row) do
if cell == ch and not pi then pi, r, c = p, ri, ci end
end
end
end
if not pi then return false end
for _ = 1, 6 do
if st.page == pi and st.swapT == nil then break end
if st.swapT == nil then U.tap(game, "select") end
U.wait(4)
end
for _ = 1, 8 do
if st.row == r then break end
U.tap(game, st.row < r and "down" or "up")
U.wait(3)
end
for _ = 1, 12 do
if st.col == c then break end
U.tap(game, st.col < c and "right" or "left")
U.wait(3)
end
U.tap(game, "a")
U.wait(3)
end
return st.name == text
end
local function watchTV(label)
local _, tv = tvMetatile()
local pages = {}
if not tv then return pages end
local ok = S.adjacentTo(game, tv[1], tv[2])
if not ok then note("could not reach the TV at " .. tv[1] .. "," .. tv[2]) return pages end
NativesTv.lastMessage, NativesTv.lastText = nil, nil
local last
U.tap(game, "a")
for i = 1, 6000 do
if Message.isOpen() then
if Message._pages ~= last and Message._pages then
last = Message._pages
pages[#pages + 1] = table.concat(Message._pages, " ")
if #pages == 1 then
U.wait(30)
shot(label)
end
end
if Message.isTyping and Message.isTyping() then Message.skipReveal() end
if i % 4 == 0 then U.tap(game, "a") else U.wait(1) end
elseif S.busy() then
U.wait(1)
else
U.wait(3)
if not S.busy() then break end
end
end
if #pages == 0 then
note(string.format("watchTV %s: no message; player %d,%d facing %s, TV %d,%d, last scripts %s", label, Player.cellX,
Player.cellY, tostring(Player.facing), tv[1], tv[2], table.concat(d.started, ",", math.max(1, #d.started - 3))))
end
return pages
end
local function nextDay(days)
Rtc.advance((days or 1) * 24 * 60)
local m, x, y, f = S.mapNow(), Player.cellX, Player.cellY, Player.facing
teleport(m, x, y, f)
end
-- pokeemerald/data/maps/SlateportCity_NameRatersHouse/scripts.inc:4
local lead = session.party[1]
local oldNick = Tv.nickname(lead)
check(teleport("EM_SLATEPORT_CITY_NAME_RATERS_HOUSE", 4, 6, "up"), "at the Name Rater's house")
local rater = S.objectByScript("SlateportCity_NameRatersHouse_EventScript_NameRater")
if not check(rater ~= nil, "Name Rater is in the house") then return finish() end
S.talkTo(game, rater)
local typed, namingSeen = false, false
S.settle(game, {
limit = 8000,
onIdleUi = function()
if Naming.isOpen() and Naming._state then
if not namingSeen then
namingSeen = true
U.wait(20)
typed = typeName("TVSTAR")
U.wait(10)
shot("01_name_rater_naming")
U.tap(game, "start")
U.wait(6)
U.tap(game, "a")
U.wait(20)
else
U.wait(1)
end
return true
end
if PartyMenu.isOpen and PartyMenu.isOpen() then
PartyMenu.cursor = 1
U.wait(6)
U.tap(game, "a")
U.wait(10)
return true
end
return false
end,
})
check(namingSeen and typed, "naming screen typed TVSTAR")
check(lead.nickname == "TVSTAR", "lead mon renamed (" .. tostring(lead.nickname) .. ", was " .. tostring(oldNick) .. ")")
local slot
for i = 0, Tv.NUM_NORMAL_TVSHOW_SLOTS - 1 do
if session.tvShows[i].kind == Tv.TVSHOW_NAME_RATER_SHOW then slot = i end
end
local show = slot and session.tvShows[slot]
check(show ~= nil and show.active == true, "TryPutNameRaterShowOnTheAir put the Name Rater show on the air (slot "
.. tostring(slot) .. ")")
check(show and show.pokemonName == "TVSTAR" and show.trainerName == session.name, "show stores TVSTAR and the player")
-- pokeemerald/src/tv.c:826
check(teleport("EM_LITTLEROOT_TOWN_BRENDANS_HOUSE_2F", 4, 3, "up"), "in the bedroom (TV at 4,1)")
do
local cells = findBehavior("TELEVISION")
local parts = {}
for _, c in ipairs(cells) do
local o = Field.metatileOverrideAt(S.mapNow(), c[1], c[2])
parts[#parts + 1] = c[1] .. "," .. c[2] .. "=" .. tostring(o and o.metatile)
end
note("TV cells " .. table.concat(parts, " ") .. " player " .. Player.cellX .. "," .. Player.cellY)
end
local tvOn = select(1, tvMetatile())
check(tvOn == C:require("metatile_labels", "METATILE_Building_TV_On"), "UpdateTVScreensOnMap lights the TV (metatile "
.. tostring(tvOn) .. ")")
check(not S.flag("FLAG_SYS_TV_WATCH"), "FLAG_SYS_TV_WATCH cleared while a show is on the air")
shot("02_tv_on")
local pages = watchTV("03_name_rater_page1")
local all = table.concat(pages, " | ")
check(#pages >= 4, "Name Rater show printed " .. #pages .. " pages")
check(all:find("TVSTAR", 1, true) ~= nil, "pages name TVSTAR")
check(all:find("NAME RATER", 1, true) ~= nil or all:find("Name Rater", 1, true) ~= nil, "pages are the NAME RATER show")
note("name rater pages: " .. all:gsub("\n", " "):sub(1, 400))
check(show and show.active == false, "TVShowDone takes it off the air")
check(S.flag("FLAG_SYS_TV_WATCH"), "EventScript_TurnOffTV sets FLAG_SYS_TV_WATCH")
check(select(1, tvMetatile()) == C:require("metatile_labels", "METATILE_Building_TV_Off"), "TurnOffTVScreen")
shot("04_after_name_rater")
-- pokeemerald/src/battle_main.c:5126
local realRandom = Tv.random
Tv.random = function() return 0 end
local caught = { playerMon1Species = tonumber(lead.species), caughtMonSpecies = S.species("SPECIES_ZIGZAGOON"),
caughtMonNick = "ZIGGY", catchAttempts = { [3] = 1 }, lastUsedItem = S.item("ITEM_POKE_BALL"),
lastOpponentSpecies = S.species("SPECIES_ZIGZAGOON") }
local _, handled = Rse.call("tv", "onBattleEnd", "TryPutPokemonTodayOnAir", nil, caught, Tv.B_OUTCOME_CAUGHT, {})
Tv.random = realRandom
check(handled, "battle-end TV hook reached through the rse system table")
local outbreak, today
for i = 0, Tv.LAST_TVSHOW_IDX - 1 do
local s = session.tvShows[i]
if s.kind == Tv.TVSHOW_MASS_OUTBREAK then outbreak = s end
if s.kind == Tv.TVSHOW_POKEMON_TODAY_CAUGHT then today = s end
end
check(outbreak ~= nil and outbreak.daysBeforeOutbreak == 1, "post-game catch rolled a mass outbreak show (1 day out)")
check(outbreak and outbreak.species == S.species("SPECIES_SEEDOT"), "Seedot outbreak from sPokeOutbreakSpeciesList")
check(today ~= nil and today.active == false and today.nickname == "ZIGGY", "Pokemon Today waits for record mixing")
check(session.pokeNews[0].kind ~= 0 and session.pokeNews[0].dayCountdown == 4, "PokeNews queued 4 days out")
nextDay(0)
pages = watchTV("05_tv_day0")
all = table.concat(pages, " | ")
check(all:find("program", 1, true) ~= nil, "day 0: nothing on air -> 'might like this program' (" .. all:gsub("\n", " ") .. ")")
nextDay(1)
check(outbreak.daysBeforeOutbreak == 0, "UpdateTVShowsPerDay counted the outbreak down")
pages = watchTV("06_outbreak_news")
all = table.concat(pages, " | ")
note("outbreak pages: " .. all:gsub("\n", " "))
check(all:find("SEEDOT", 1, true) ~= nil and all:find("ROUTE 102", 1, true) ~= nil, "TV reports SEEDOT swarming ROUTE 102")
local ob = session.outbreak
check(ob and ob.map == "EM_ROUTE102" and ob.species == S.species("SPECIES_SEEDOT") and ob.probability == 50,
"StartMassOutbreak arms the encounter hook")
check(session.outbreakDaysLeft == 2, "outbreak lasts 2 days")
-- pokeemerald/src/wild_encounter.c:481
check(teleport("EM_ROUTE102", 20, 10, "down"), "on Route 102")
local grass = findBehavior("TALL_GRASS")
local spot
for _, g in ipairs(grass) do
for _, g2 in ipairs(grass) do
if not spot and math.abs(g[1] - g2[1]) + math.abs(g[2] - g2[2]) == 1 then spot = { g, g2 } end
end
end
check(spot ~= nil, "found a pair of grass cells (" .. #grass .. " grass cells)")
local seen = {}
local outbreakMon
if spot then
S.goTo(game, spot[1])
for _ = 1, 300 do
if outbreakMon or #seen >= 14 then break end
local P = Player
local target = (P.cellX == spot[1][1] and P.cellY == spot[1][2]) and spot[2] or spot[1]
local dir = target[1] > P.cellX and "right" or target[1] < P.cellX and "left" or target[2] > P.cellY and "down" or "up"
S.step(game, dir)
for _ = 1, 120 do
if Battle.isActive() then break end
U.wait(1)
end
if Battle.isActive() then
for _ = 1, 400 do
if Battle._st and Battle._st.enemy and Battle._st.enemy.mon then break end
U.wait(1)
end
local st = Battle._st
local mon = st and ((st.enemy and st.enemy.mon) or (st.battlers and st.battlers[1] and st.battlers[1].mon))
local moves = mon and mon.moves or {}
seen[#seen + 1] = string.format("%s lv%s [%s]", tostring(mon and mon.species), tostring(mon and mon.level),
table.concat(moves, ","))
if mon and tonumber(mon.species) == S.species("SPECIES_SEEDOT") and tonumber(mon.level) == 3
and moves[1] == S.move("MOVE_BIDE") and moves[2] == S.move("MOVE_HARDEN") and moves[3] == S.move("MOVE_LEECH_SEED") then
outbreakMon = mon
for _ = 1, 1500 do
if Battle._phase == "command" then break end
if Message.isOpen() then U.tap(game, "a") end
U.wait(2)
end
U.wait(20)
shot("07_outbreak_seedot")
end
L.run(game, {})
S.settle(game, { limit = 2000 })
end
end
end
note("encounters: " .. table.concat(seen, "; "))
check(outbreakMon ~= nil, "Route 102 grass spawns the outbreak Seedot lv3 with BIDE/HARDEN/LEECH SEED")
nextDay(1)
check(session.outbreakDaysLeft == 1, "one outbreak day left")
nextDay(1)
check(session.outbreakPokemonSpecies == 0 and session.outbreak == nil, "EndMassOutbreak after two days")
check(teleport("EM_LITTLEROOT_TOWN_BRENDANS_HOUSE_2F", 4, 3, "up"), "back in the bedroom")
pages = watchTV("08_pokenews")
all = table.concat(pages, " | ")
note("pokenews pages: " .. all:gsub("\n", " "))
check(#pages >= 1 and NativesTv.lastText and tostring(NativesTv.lastText.group):find("^sPokeNewsTextGroup") ~= nil,
"PokeNews airs once its countdown is under 3 days (" .. tostring(NativesTv.lastText and NativesTv.lastText.group) .. ")")
finish()
end