mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-18 11:44:42 +02:00
326 lines
11 KiB
Lua
326 lines
11 KiB
Lua
-- The Pokemon Center PC's whose-PC menu (engine/events/pokecenter_pc.asm
|
|
-- PokemonCenterPC). Every Pokecenter reaches it the same way: the PC is a
|
|
-- COLL_PC tile, the A press runs PCScript (engine/events/std_scripts.asm) and
|
|
-- its `special PokemonCenterPC` opens this screen through World:openPc.
|
|
--
|
|
-- .ChooseWhichPCListToUse picks the row list:
|
|
--
|
|
-- PCPC_BEFORE_POKEDEX BILL's PC / <PLAYER>'s PC / TURN OFF
|
|
-- PCPC_BEFORE_HOF + PROF.OAK's PC (CheckReceivedDex)
|
|
-- PCPC_POSTGAME + HALL OF FAME (wHallOfFameCount > 0)
|
|
--
|
|
-- BILL's PC opens the storage system (src/ui/gen2/PcMenu.lua, _BillsPC's own
|
|
-- five rows), <PLAYER>'s PC the item PC (src/ui/gen2/ItemPcMenu.lua,
|
|
-- PLAYERSPC_NORMAL), PROF.OAK's PC the #DEX rating (ProfOaksPC,
|
|
-- engine/events/prof_oaks_pc.asm) and HALL OF FAME the roster viewer
|
|
-- (src/ui/gen2/HallOfFame.lua "view" mode, _HallOfFamePC).
|
|
|
|
local Chrome = require("src.ui.gen2.Chrome")
|
|
local Screens = require("src.ui.Screens")
|
|
local Sound = require("src.core.Sound")
|
|
local Specials = require("src.script.gen2.Specials")
|
|
local Strings = require("src.core.Strings")
|
|
|
|
local CenterPcMenu = {}
|
|
CenterPcMenu.__index = CenterPcMenu
|
|
CenterPcMenu.isOpaque = true
|
|
|
|
-- ENGINE_POKEDEX (constants/engine_flags.asm, index 11): the flag
|
|
-- CheckReceivedDex reads and Mr.Pokemon's `setflag ENGINE_POKEDEX` writes.
|
|
-- The port lands plain ENGINE_* ids on save.engineFlags
|
|
-- (World:setEngineFlag), so the same store answers here.
|
|
local ENGINE_POKEDEX = 11
|
|
|
|
function CenterPcMenu:wantsFillScale() return true end
|
|
function CenterPcMenu:drawsWidescreen() return true end
|
|
|
|
-- A multi-page body: `para` (a blank line in the transcription) is a
|
|
-- screenful of its own, two lines to a page.
|
|
local function pagesOf(body)
|
|
local pages = {}
|
|
for chunk in (tostring(body) .. "\n\n"):gmatch("(.-)\n\n") do
|
|
local lines = {}
|
|
for line in (chunk .. "\n"):gmatch("(.-)\n") do
|
|
if line ~= "" then lines[#lines + 1] = line end
|
|
end
|
|
if #lines > 0 then pages[#pages + 1] = lines end
|
|
end
|
|
return pages
|
|
end
|
|
|
|
-- opts: save, events, items (items.lua), onClose()
|
|
function CenterPcMenu.new(game, opts)
|
|
opts = opts or {}
|
|
local self = setmetatable({}, CenterPcMenu)
|
|
self.game = game
|
|
self.save = opts.save or (game and game.save)
|
|
self.items = opts.items or (game and game.data and game.data.items)
|
|
self.data = game and game.data
|
|
self.events = opts.events
|
|
self.onClose = opts.onClose
|
|
self.index = 1
|
|
self.message = nil
|
|
self.confirm = nil
|
|
self.closed = false
|
|
self:buildEntries()
|
|
local party = self.save and self.save.party
|
|
if not (party and #party > 0) then
|
|
-- PC_CheckPartyForPokemon: SFX_CHOOSE_PC_OPTION, the refusal, and the PC
|
|
-- never boots (`ret c` before PC_PlayBootSound).
|
|
self:playSfx("Sfx_ChoosePcOption")
|
|
self:say({ { "Bzzzzt! You must", "have a #MON to", "use this!" } },
|
|
function() self:close() end)
|
|
else
|
|
-- PC_PlayBootSound + _PokecenterPCTurnOnText.
|
|
self:playSfx("Sfx_BootPc")
|
|
self:say({ { "{PLAYER} turned on", "the PC." } })
|
|
end
|
|
return self
|
|
end
|
|
|
|
function CenterPcMenu:playSfx(name)
|
|
local data = self.data
|
|
local sfx = data and data.audio and data.audio.sfx
|
|
if sfx and sfx[Sound.resolve(data, name)] then
|
|
Sound.play(data, name)
|
|
end
|
|
end
|
|
|
|
function CenterPcMenu:playerName()
|
|
local player = self.save and self.save.player
|
|
return (player and player.name) or "GOLD"
|
|
end
|
|
|
|
-- .WhichPC, gated the way .ChooseWhichPCListToUse gates it. TURN OFF is
|
|
-- always last; the extra rows slot in above it in PCPCITEM_* order.
|
|
function CenterPcMenu:buildEntries()
|
|
local save = self.save
|
|
local hasDex = save and save.engineFlags
|
|
and save.engineFlags[ENGINE_POKEDEX] == true
|
|
local hofCount = (save and save.hallOfFame and save.hallOfFame.count) or 0
|
|
local entries = {
|
|
{ id = "bills", label = "BILL's PC" },
|
|
{ id = "players", label = self:playerName() .. "'s PC" },
|
|
}
|
|
if hasDex then
|
|
entries[#entries + 1] = { id = "oaks", label = "PROF.OAK's PC" }
|
|
if hofCount > 0 then
|
|
entries[#entries + 1] = { id = "hof", label = "HALL OF FAME" }
|
|
end
|
|
end
|
|
entries[#entries + 1] = { id = "turnoff", label = "TURN OFF" }
|
|
self.entries = entries
|
|
end
|
|
|
|
-- pages is a list of pages, each a list of lines; a page may carry `sfx`,
|
|
-- played the moment it comes up (FindOakRating hands PlaySFX its fanfare
|
|
-- right before the rating text prints).
|
|
function CenterPcMenu:say(pages, onDone)
|
|
self.message = { pages = pages, page = 1, onDone = onDone }
|
|
local first = pages[1]
|
|
if first and first.sfx then self:playSfx(first.sfx) end
|
|
end
|
|
|
|
function CenterPcMenu:close()
|
|
if self.closed then return end
|
|
self.closed = true
|
|
if self.onClose then self.onClose() end
|
|
end
|
|
|
|
-- .shutdown: PC_PlayShutdownSound, then the menu is gone. The party refusal
|
|
-- never reaches this -- the PC never booted.
|
|
function CenterPcMenu:shutdown()
|
|
self:playSfx("Sfx_ShutDownPc")
|
|
self:close()
|
|
end
|
|
|
|
-- ProfOaksPC's `.shutdown`: _OakPCText4 either way, then back to the menu
|
|
-- loop (`jr nc, .loop` in PokemonCenterPC -- the OaksPC row answers nc).
|
|
function CenterPcMenu:oakClosed()
|
|
self:say({ { "The link to PROF.", "OAK's PC closed." } })
|
|
end
|
|
|
|
-- ProfOaksPCBoot, inside a screen rather than a script: the counts, the
|
|
-- rating pick and the texts come from src/script/gen2/Specials.lua so the two
|
|
-- callers cannot drift apart.
|
|
function CenterPcMenu:oakRate()
|
|
local seen, caught = Specials.dexCounts(self.save)
|
|
local rating = Specials.findOakRating(caught)
|
|
local pages = pagesOf(Strings(Specials.OAK_PC_TEXT.completion))
|
|
for _, page in ipairs(pagesOf(
|
|
Strings(Specials.OAK_PC_TEXT.counts, seen, caught))) do
|
|
pages[#pages + 1] = page
|
|
end
|
|
local ratingPages = pagesOf(Strings(rating.text))
|
|
if ratingPages[1] then ratingPages[1].sfx = rating.sfx end
|
|
for _, page in ipairs(ratingPages) do pages[#pages + 1] = page end
|
|
self:say(pages, function() self:oakClosed() end)
|
|
end
|
|
|
|
function CenterPcMenu:choose()
|
|
local entry = self.entries[self.index]
|
|
if not entry then return end
|
|
local game = self.game
|
|
if entry.id == "turnoff" then
|
|
-- TurnOffPC: PokecenterPCOaksClosedText, then carry into .shutdown.
|
|
self:say({ { "\xe2\x80\xa6", "Link closed\xe2\x80\xa6" } },
|
|
function() self:shutdown() end)
|
|
return
|
|
end
|
|
-- PC_PlayChoosePCSound opens all four of the other rows.
|
|
self:playSfx("Sfx_ChoosePcOption")
|
|
if entry.id == "bills" then
|
|
self:say({ { "BILL's PC", "accessed." },
|
|
{ "#MON Storage", "System opened." } }, function()
|
|
if not (game and game.stack) then return end
|
|
Screens.push(game, "Gen2PcMenu", {
|
|
save = self.save,
|
|
bills = true,
|
|
onClose = function() game.stack:pop() end,
|
|
})
|
|
end)
|
|
elseif entry.id == "players" then
|
|
self:say({ { "Accessed own PC." },
|
|
{ "Item Storage", "System opened." } }, function()
|
|
if not (game and game.stack) then return end
|
|
Screens.push(game, "Gen2ItemPcMenu", {
|
|
save = self.save,
|
|
items = self.items,
|
|
onClose = function() game.stack:pop() end,
|
|
})
|
|
end)
|
|
elseif entry.id == "oaks" then
|
|
self:say({ { "PROF.OAK's PC", "accessed." },
|
|
{ "#DEX Rating", "System opened." } }, function()
|
|
-- _OakPCText1's yes/no; NO is the same `.shutdown` as a finished rating.
|
|
self.confirm = {
|
|
prompt = { "Want to get your", "#DEX rated?" },
|
|
choice = 1,
|
|
onYes = function() self:oakRate() end,
|
|
onNo = function() self:oakClosed() end,
|
|
}
|
|
end)
|
|
elseif entry.id == "hof" then
|
|
if not (game and game.stack) then return end
|
|
-- HallOfFamePC: FadeToMenu, _HallOfFamePC, CloseSubmenu.
|
|
Screens.push(game, "Gen2HallOfFame", {
|
|
save = self.save,
|
|
mode = "view",
|
|
onDone = function() game.stack:pop() end,
|
|
})
|
|
end
|
|
end
|
|
|
|
function CenterPcMenu:update(_dt)
|
|
local input = self.game and self.game.input
|
|
if not input then return end
|
|
|
|
if self.message then
|
|
if input:wasPressed("a") or input:wasPressed("b") then
|
|
local m = self.message
|
|
if m.page < #m.pages then
|
|
m.page = m.page + 1
|
|
local page = m.pages[m.page]
|
|
if page and page.sfx then self:playSfx(page.sfx) end
|
|
return
|
|
end
|
|
self.message = nil
|
|
if m.onDone then m.onDone() end
|
|
end
|
|
return
|
|
end
|
|
|
|
if self.confirm then
|
|
local c = self.confirm
|
|
if input:wasPressed("up") or input:wasPressed("down") then
|
|
c.choice = c.choice == 1 and 2 or 1
|
|
elseif input:wasPressed("b") then
|
|
-- YesNoBox's B is NO.
|
|
self.confirm = nil
|
|
if c.onNo then c.onNo() end
|
|
elseif input:wasPressed("a") then
|
|
self.confirm = nil
|
|
if c.choice == 1 then
|
|
if c.onYes then c.onYes() end
|
|
elseif c.onNo then
|
|
c.onNo()
|
|
end
|
|
end
|
|
return
|
|
end
|
|
|
|
if input:wasPressed("up") then
|
|
self.index = self.index > 1 and self.index - 1 or #self.entries
|
|
elseif input:wasPressed("down") then
|
|
self.index = self.index < #self.entries and self.index + 1 or 1
|
|
elseif input:wasPressed("a") then
|
|
self:choose()
|
|
elseif input:wasPressed("b") then
|
|
-- DoNthMenu's carry lands straight in .shutdown, no text.
|
|
self:shutdown()
|
|
end
|
|
end
|
|
|
|
function CenterPcMenu:drawBottomLines(lines)
|
|
Chrome.box(0, 12, 20, 6)
|
|
if not lines then return end
|
|
local name = self:playerName()
|
|
local startY = #lines >= 3 and 13 or 14
|
|
for i, line in ipairs(lines) do
|
|
Chrome.print((line:gsub("{PLAYER}", name)), 1, startY + (i - 1) * 2)
|
|
end
|
|
end
|
|
|
|
function CenterPcMenu:drawPanel()
|
|
Chrome.clear()
|
|
|
|
if self.message then
|
|
self:drawBottomLines(self.message.pages[self.message.page])
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
return
|
|
end
|
|
|
|
-- _PokecenterPCWhoseText stays up under the menu
|
|
-- (PC_DisplayTextWaitMenu leaves it there); the menu window is drawn on
|
|
-- top of it, the way the cart's windows stack.
|
|
self:drawBottomLines({ "Access whose PC?" })
|
|
-- .TopMenu is menu_coords 0, 0, 15, 12.
|
|
Chrome.box(0, 0, 16, math.max(12, #self.entries * 2 + 2))
|
|
for i, entry in ipairs(self.entries) do
|
|
local ty = i * 2
|
|
if i == self.index then Chrome.cursor(1, ty) end
|
|
Chrome.print(entry.label, 2, ty)
|
|
end
|
|
|
|
if self.confirm then
|
|
self:drawBottomLines(self.confirm.prompt)
|
|
Chrome.box(14, 7, 6, 5)
|
|
Chrome.print("YES", 16, 8)
|
|
Chrome.print("NO", 16, 10)
|
|
Chrome.cursor(15, self.confirm.choice == 1 and 8 or 10)
|
|
end
|
|
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
end
|
|
|
|
function CenterPcMenu:draw()
|
|
self:drawPanel()
|
|
end
|
|
|
|
function CenterPcMenu:drawWidescreen(winW, winH)
|
|
local G = love.graphics
|
|
G.setColor(1, 1, 1, 1)
|
|
G.rectangle("fill", 0, 0, winW, winH)
|
|
local scale = Chrome.fitScale(winW, winH)
|
|
G.push()
|
|
G.translate(math.floor((winW - 160 * scale) / 2),
|
|
math.floor((winH - 144 * scale) / 2))
|
|
G.scale(scale, scale)
|
|
self:drawPanel()
|
|
G.pop()
|
|
end
|
|
|
|
CenterPcMenu.ENGINE_POKEDEX = ENGINE_POKEDEX
|
|
|
|
return CenterPcMenu
|