mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-29 07:00:55 -04:00
353 lines
14 KiB
Lua
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
|