mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-09 01:59:15 -04:00
222 lines
8.8 KiB
Lua
222 lines
8.8 KiB
Lua
-- The wall radios (engine/pokegear/pokegear.asm PlayRadio). A house radio's
|
|
-- bg event runs `jumpstd Radio1Script` -- `setval MAPRADIO_*` then `special
|
|
-- MapRadio` (engine/events/std_scripts.asm, engine/events/specials.asm) --
|
|
-- and PlayRadio owns the joypad from there: it resolves the MAPRADIO_* index
|
|
-- through PlayRadioStationPointers, prints the station's name in quotes in
|
|
-- the caller's text box, waits 100 frames, then runs one PlayRadioShow frame
|
|
-- per loop until A or B. The show is the SAME state machine the Pokegear's
|
|
-- radio card runs (src/ui/gen2/Pokegear.lua's Radio), and the exit music is
|
|
-- the same ExitPokegearRadio_HandleMusic: the channel song the show started
|
|
-- stays playing as the map music.
|
|
--
|
|
-- Pushed by src/script/gen2/Specials.lua H.MapRadio while the script VM is
|
|
-- parked on the special; `onDone` resumes it, and `closetext` follows.
|
|
|
|
local Chrome = require("src.ui.gen2.Chrome")
|
|
local Buena = require("src.core.gen2.Buena")
|
|
local Nests = require("src.core.gen2.Nests")
|
|
local Pokegear = require("src.ui.gen2.Pokegear")
|
|
local Runtime = require("src.mods.Runtime")
|
|
|
|
local MapRadio = {}
|
|
MapRadio.__index = MapRadio
|
|
-- The map stays on screen under the text box, exactly as the cart leaves it.
|
|
MapRadio.isOpaque = false
|
|
|
|
-- PlayRadioStationPointers, in MAPRADIO_* order (constants/
|
|
-- radio_constants.asm). Index 0, LoadStation_PokemonChannel, resolves by
|
|
-- region and time of day below rather than to a fixed station.
|
|
local STATIONS = {
|
|
[1] = "OAKS_POKEMON_TALK",
|
|
[2] = "POKEDEX_SHOW",
|
|
[3] = "POKEMON_MUSIC",
|
|
[4] = "LUCKY_CHANNEL",
|
|
[5] = "UNOWN_RADIO",
|
|
[6] = "PLACES_AND_PEOPLE",
|
|
[7] = "LETS_ALL_SING",
|
|
[8] = "ROCKET_RADIO",
|
|
}
|
|
|
|
local POKEGEAR_ONLY_STATIONS = { "POKE_FLUTE_RADIO", "EVOLUTION_RADIO" }
|
|
|
|
-- The same eight as the `radio_channels` registry sees them (src/mods/
|
|
-- Schemas.lua), one of the Gen 2-only six: Red has no radio, so the name is
|
|
-- gated under Gen 1 and routed to data.gen2RadioChannels under Gen 2. Id =
|
|
-- the station the dial lands on, which is the LoadStation_* id
|
|
-- src/ui/gen2/Pokegear.lua's show state machine is keyed by; `channel` is its
|
|
-- MAPRADIO_* position, the byte a wall radio's `setval` passes the special.
|
|
-- Position 0 is deliberately unregistered: it is not a station, it resolves by
|
|
-- region and time of day in resolveStation below.
|
|
--
|
|
-- src/mods/Builtins.lua seeds these engine-owned, so a mod's register of
|
|
-- ROCKET_RADIO collides and has to say override.
|
|
function MapRadio.registerInto(registry, data, owner)
|
|
local count = 0
|
|
for channel, station in pairs(STATIONS) do
|
|
registry:register(station, { channel = channel,
|
|
name = Pokegear.STATION_NAMES[station] }, owner)
|
|
count = count + 1
|
|
end
|
|
-- Two Pokegear-only signals have no MAPRADIO_* index. They still belong in
|
|
-- the same registry because the tuner resolves their display names by id;
|
|
-- leaving `channel` absent keeps wall-radio lookup unambiguous.
|
|
for _, station in ipairs(POKEGEAR_ONLY_STATIONS) do
|
|
registry:register(station, { name = Pokegear.STATION_NAMES[station] }, owner)
|
|
count = count + 1
|
|
end
|
|
local metadata = Buena.metadata(data)
|
|
if metadata then
|
|
registry:register("BUENAS_PASSWORD", { name = metadata.stationName }, owner)
|
|
count = count + 1
|
|
end
|
|
return count
|
|
end
|
|
|
|
-- The merged record for a dial position, or nil. Read through data rather
|
|
-- than through STATIONS so a registered station is on the dial for real; the
|
|
-- module's own table is the fallback for a boot with no loader.
|
|
function MapRadio.channelRecord(data, channel)
|
|
local rows = data and data.gen2RadioChannels
|
|
if type(rows) == "table" then
|
|
for station, record in pairs(rows) do
|
|
if type(record) == "table" and record.channel == channel then
|
|
return record, station
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
local station = STATIONS[channel]
|
|
if not station then return nil end
|
|
return { channel = channel, name = Pokegear.STATION_NAMES[station] }, station
|
|
end
|
|
|
|
-- The player's landmark, the way Game2:currentLandmark reads it: the map
|
|
-- header's landmark byte resolved through the `landmarks` registry
|
|
-- (src/core/gen2/Nests.lua). IsInJohto and the Rocket takeover both branch
|
|
-- on it.
|
|
local function landmarkOf(game)
|
|
local world = game and game.world
|
|
local map = world and world.map and world.map.def
|
|
return Nests.landmarkId(game and game.data, map and map.landmark)
|
|
end
|
|
|
|
-- opts: channel (the MAPRADIO_* index setval left in wScriptVar), onDone(),
|
|
-- and for tests: save, currentLandmark, radioData, radioRng.
|
|
function MapRadio.new(game, opts)
|
|
opts = opts or {}
|
|
local self = setmetatable({}, MapRadio)
|
|
self.game = game
|
|
self.onDone = opts.onDone
|
|
-- The Pokegear owns the radio data assembly (Pokegear:radioData) and the
|
|
-- region/time reads; a gear instance that is never drawn keeps one copy of
|
|
-- that wiring.
|
|
self.gear = Pokegear.new(game, {
|
|
save = opts.save or (game and game.save),
|
|
currentLandmark = opts.currentLandmark or landmarkOf(game),
|
|
radioData = opts.radioData,
|
|
radioRng = opts.radioRng,
|
|
})
|
|
self.station, self.stationName = self:resolveStation(opts.channel or 0)
|
|
self.radio = Pokegear.Radio.new({
|
|
data = self.gear:radioData(), rng = opts.radioRng,
|
|
})
|
|
self.radio:tune(self.station)
|
|
-- radio.channel, a Gen 2 invention: Gen 1 has no radio, so there is no name
|
|
-- to share. Raised on the tune rather than per show line, because the
|
|
-- station is what a mod reasons about -- the show under it is the same state
|
|
-- machine the Pokegear's radio card runs and reports its song through
|
|
-- music.started like everything else.
|
|
--
|
|
-- station the LoadStation_* id the dial landed on
|
|
-- channel the MAPRADIO_* index the script's setval passed in; 0 is the
|
|
-- Pokemon Channel position, which resolves by region and hour
|
|
-- name the station's display name, the one quoted in the text box
|
|
-- source "map_radio" -- the wall radio, as against the Pokegear card
|
|
if Runtime.wants("radio.channel") then
|
|
Runtime.emit("radio.channel", {
|
|
station = self.station, channel = opts.channel or 0,
|
|
name = self:name(),
|
|
source = "map_radio",
|
|
})
|
|
end
|
|
-- .PlayStation parks ENTER_MAP_MUSIC before the show's own
|
|
-- RadioMusicRestartDE lands the channel song, so backing out before the
|
|
-- show starts still restores the map theme.
|
|
self.radioMusicPlaying = "enterMap"
|
|
self.radioSong = nil
|
|
-- `ld c, 100 / call DelayFrames`: the name sits alone in the box before
|
|
-- the show's first line, and no button is read during the delay.
|
|
self.hold = 100
|
|
return self
|
|
end
|
|
|
|
-- LoadStation_PokemonChannel: in Johto the morning airs the Pokedex Show and
|
|
-- the rest of the day Oak's Pokemon Talk; from Kanto the same dial position
|
|
-- carries Places & People. Answers the station and, when the record carries
|
|
-- one, its own display name.
|
|
function MapRadio:resolveStation(channel)
|
|
if channel ~= 0 then
|
|
local record, station = MapRadio.channelRecord(
|
|
self.game and self.game.data, channel)
|
|
if station then return station, record and record.name end
|
|
return "OAKS_POKEMON_TALK", nil
|
|
end
|
|
if self.gear:region() ~= "johto" then return "PLACES_AND_PEOPLE" end
|
|
if (self.gear:timeOfDayIndex() or 0) == 0 then return "POKEDEX_SHOW" end
|
|
return "OAKS_POKEMON_TALK"
|
|
end
|
|
|
|
-- The name quoted in the text box. A loaded game resolves it through the
|
|
-- same registry as the Pokegear; STATION_NAMES is only the loader-free fallback.
|
|
function MapRadio:name()
|
|
if self.stationName then return self.stationName end
|
|
return self.gear and self.gear:stationName(self.station)
|
|
or Pokegear.STATION_NAMES[self.station]
|
|
end
|
|
|
|
function MapRadio:close()
|
|
Pokegear.exitRadioMusic(self.game, self.radioMusicPlaying)
|
|
self.radioMusicPlaying = nil
|
|
local stack = self.game and self.game.stack
|
|
if stack then stack:pop() end
|
|
if self.onDone then self.onDone() end
|
|
end
|
|
|
|
function MapRadio:update(_dt)
|
|
if self.hold > 0 then
|
|
self.hold = self.hold - 1
|
|
return
|
|
end
|
|
local input = self.game and self.game.input
|
|
if input and (input:wasPressed("a") or input:wasPressed("b")) then
|
|
self:close()
|
|
return
|
|
end
|
|
self.radio:step()
|
|
local song = self.radio.music
|
|
if song and song ~= self.radioSong then
|
|
self.radioSong = song
|
|
self.radioMusicPlaying = Pokegear.radioPlayingValue(song)
|
|
local data = self.game and self.game.data
|
|
if data then pcall(require("src.core.Music").play, data, song) end
|
|
end
|
|
end
|
|
|
|
function MapRadio:draw()
|
|
-- PlayRadio's own frame: Textbox at (0,12), 4 rows by 18, with the station
|
|
-- name quoted at (2,14) until the show's first line scrolls in.
|
|
Chrome.textbox(0, 12, 18, 4)
|
|
local radio = self.radio
|
|
if radio.top == "" and radio.bottom == "" then
|
|
local name = self:name() or ""
|
|
Chrome.print("“" .. name .. "”", 1, 14)
|
|
return
|
|
end
|
|
if radio.top ~= "" then Chrome.print(radio.top, 1, 14) end
|
|
if radio.bottom ~= "" then Chrome.print(radio.bottom, 1, 16) end
|
|
end
|
|
|
|
MapRadio.STATIONS = STATIONS
|
|
|
|
return MapRadio
|