mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-25 23:11:15 +02:00
230 lines
8.0 KiB
Lua
230 lines
8.0 KiB
Lua
-- Gold's intro menu (engine/menus/main_menu.asm MainMenu).
|
|
--
|
|
-- Which entries appear depends on whether a save exists
|
|
-- (MainMenu_GetWhichMenu reads wSaveFileExists):
|
|
-- no save -> NEW GAME, OPTION
|
|
-- save -> CONTINUE, NEW GAME, OPTION
|
|
-- MYSTERY GIFT is a third case on a CGB with an unlocked SRAM counter; it
|
|
-- needs the link cable, so it is not offered here.
|
|
--
|
|
-- With a save present the menu also shows the clock box
|
|
-- (MainMenu_PrintCurrentTimeAndDay): a 4x13 textbox at (0,12) with the day of
|
|
-- the week and the current time. Its .PlaceTime calls UpdateTime before it
|
|
-- reads hHours, so it prints the GAME clock -- the RTC through the save's own
|
|
-- wStartHour / wStartMinute base -- and not the raw RTC. That is the same
|
|
-- read the overworld makes (World:hour), so the time on this screen and the
|
|
-- light outside the door always agree.
|
|
--
|
|
-- Choosing CONTINUE shows the save panel (DisplaySaveInfoOnContinue) and waits
|
|
-- for A to confirm or B to back out (ConfirmContinue).
|
|
|
|
local Chrome = require("src.ui.gen2.Chrome")
|
|
local Clock = require("src.core.gen2.Clock")
|
|
local Logger = require("src.core.Logger")
|
|
local Music = require("src.core.Music")
|
|
local Runtime = require("src.mods.Runtime")
|
|
local Save = require("src.core.gen2.Save")
|
|
|
|
local MainMenu = {}
|
|
MainMenu.__index = MainMenu
|
|
MainMenu.isOpaque = true
|
|
|
|
-- MainMenu_PrintCurrentTimeAndDay's PrintDayOfWeek strings. Clock.DAY_NAMES
|
|
-- / Clock.weekdayName is the single translated home for this table (see
|
|
-- InitClock.lua's DAYS), so this screen's clock box cannot drift from the
|
|
-- Pokegear's own.
|
|
local DAYS = Clock.DAY_NAMES
|
|
|
|
-- MUSIC_MAIN_MENU; resolved by name so a cache without it just stays quiet.
|
|
local MENU_MUSIC = "Music_MainMenu"
|
|
|
|
function MainMenu:wantsFillScale() return true end
|
|
function MainMenu:drawsWidescreen() return true end
|
|
|
|
-- opts: onNewGame(), onContinue(save), onOption(), hasSave (override for
|
|
-- tests), save (a pre-loaded save table, so the menu does not read the disk
|
|
-- twice), clock ({ hour, minute, weekday }) to pin the clock box.
|
|
function MainMenu.new(game, opts)
|
|
opts = opts or {}
|
|
local self = setmetatable({}, MainMenu)
|
|
self.game = game
|
|
self.onNewGame = opts.onNewGame
|
|
self.onContinue = opts.onContinue
|
|
self.onOption = opts.onOption
|
|
self.onExit = opts.onExit
|
|
self.clock = opts.clock
|
|
|
|
self.save = opts.save
|
|
if self.save == nil and opts.hasSave ~= false then
|
|
local loaded = Save.load()
|
|
self.save = loaded
|
|
end
|
|
self.hasSave = opts.hasSave
|
|
if self.hasSave == nil then self.hasSave = self.save ~= nil end
|
|
|
|
self.phase = "menu" -- menu | confirm
|
|
self:buildList()
|
|
return self
|
|
end
|
|
|
|
-- ui.title_menu.items identity: an unhooked build hands its own list back.
|
|
local function sameItems(_, items) return items end
|
|
|
|
function MainMenu:buildList()
|
|
local items = {}
|
|
if self.hasSave then
|
|
items[#items + 1] = { label = "CONTINUE", value = "continue" }
|
|
end
|
|
items[#items + 1] = { label = "NEW GAME", value = "new" }
|
|
items[#items + 1] = { label = "OPTION", value = "option" }
|
|
-- Not on the cart: a cartridge is left by switching the console off, and
|
|
-- there is no console here. Mirrors the Gen 1 port's title menu
|
|
-- (src/ui/TitleState.lua), which adds the same row for the same reason.
|
|
items[#items + 1] = { label = "EXIT GAME", value = "exit" }
|
|
-- The same hook name and the same (game, items) payload the Gen 1 title
|
|
-- menu raises (src/ui/TitleState.lua:openMenu), so one mod's title rows
|
|
-- serve both games; only the row shape differs, because Chrome.List reads
|
|
-- { label, value } where the Gen 1 Menu reads { label, onSelect }. A hook
|
|
-- that answers with anything but a table is degraded to the vanilla list
|
|
-- rather than leaving the player with no way into the game.
|
|
local hooked = Runtime.call("ui.title_menu.items", sameItems, self.game, items)
|
|
if type(hooked) == "table" then
|
|
items = hooked
|
|
else
|
|
Logger.error("ui.title_menu.items returned %s; keeping the vanilla items",
|
|
type(hooked))
|
|
end
|
|
-- MenuHeader's "db 1 ; default option": the first entry, so CONTINUE when
|
|
-- there is a save and NEW GAME when there is not.
|
|
self.list = Chrome.List.new({
|
|
items = items,
|
|
x = 2, y = 2, spacing = 2,
|
|
wrap = true,
|
|
index = 1,
|
|
onChoose = function(value) self:choose(value) end,
|
|
})
|
|
end
|
|
|
|
function MainMenu:choose(value)
|
|
if value == "continue" then
|
|
self.phase = "confirm"
|
|
self.confirmDelay = 20 -- ld c, 20 / DelayFrames before input is read
|
|
elseif value == "new" then
|
|
if self.onNewGame then self.onNewGame() end
|
|
elseif value == "option" then
|
|
if self.onOption then self.onOption() end
|
|
elseif value == "exit" then
|
|
if self.onExit then
|
|
self.onExit()
|
|
elseif love.event and love.event.quit then
|
|
love.event.quit()
|
|
end
|
|
end
|
|
end
|
|
|
|
function MainMenu:enter()
|
|
local data = self.game and self.game.data
|
|
local audio = data and data.audio
|
|
if audio and audio.runtime and audio.songs and audio.songs[MENU_MUSIC] then
|
|
Music.play(data, MENU_MUSIC)
|
|
end
|
|
end
|
|
|
|
function MainMenu:update(_dt)
|
|
local input = self.game and self.game.input
|
|
if not input then return end
|
|
if self.phase == "confirm" then
|
|
if self.confirmDelay and self.confirmDelay > 0 then
|
|
self.confirmDelay = self.confirmDelay - 1
|
|
return
|
|
end
|
|
if input:wasPressed("a") then
|
|
if self.onContinue then self.onContinue(self.save) end
|
|
elseif input:wasPressed("b") then
|
|
self.phase = "menu"
|
|
end
|
|
return
|
|
end
|
|
self.list:update(input)
|
|
end
|
|
|
|
-- .PlaceTime's three reads: GetWeekday, hHours and hMinutes, all of them after
|
|
-- UpdateTime. `weekday` is returned 1-based for the DAYS table above, which is
|
|
-- os.date's wday numbering and NOT wCurDay's (Clock.weekday counts SUNDAY 0).
|
|
-- opts.clock still pins the box outright for a driver's screenshots.
|
|
function MainMenu:clockParts()
|
|
if self.clock then
|
|
return self.clock.hour or 0, self.clock.minute or 0,
|
|
self.clock.weekday or 1
|
|
end
|
|
local save = self.save
|
|
return Clock.hour(save), Clock.minute(save), Clock.weekday(save) + 1
|
|
end
|
|
|
|
function MainMenu:drawClockBox()
|
|
-- Textbox at (0,12) with 4 interior rows and 13 interior columns.
|
|
Chrome.textbox(0, 12, 13, 4)
|
|
local hour, minute, weekday = self:clockParts()
|
|
Chrome.print(Clock.weekdayName(weekday) or "DAY", 1, 14)
|
|
-- PrintHour prints 1-12 with no leading zero, then ':' then two zero-padded
|
|
-- minutes; the AM/PM half is drawn by PrintHour itself.
|
|
local display = hour % 12
|
|
if display == 0 then display = 12 end
|
|
local half = hour < 12 and "AM" or "PM"
|
|
Chrome.print(("%s:%s %s"):format(
|
|
Chrome.number(display, 2), Chrome.number(minute, 2, true), half), 4, 16)
|
|
end
|
|
|
|
function MainMenu:drawSavePanel()
|
|
local summary = Save.summary(self.save)
|
|
-- DisplaySaveInfoOnContinue: a box down the right side listing the trainer.
|
|
Chrome.textbox(4, 0, 14, 9)
|
|
if not summary then
|
|
Chrome.print("NO SAVE FILE", 5, 2)
|
|
return
|
|
end
|
|
Chrome.print("PLAYER " .. summary.name, 5, 2)
|
|
Chrome.print("BADGES", 5, 4)
|
|
Chrome.printRight(tostring(summary.badges), 17, 4)
|
|
Chrome.print("POKéDEX", 5, 6)
|
|
Chrome.printRight(tostring(summary.caught), 17, 6)
|
|
Chrome.print("TIME", 5, 8)
|
|
Chrome.printRight(("%d:%s"):format(
|
|
summary.hours, Chrome.number(summary.minutes, 2, true)), 17, 8)
|
|
end
|
|
|
|
function MainMenu:drawPanel()
|
|
Chrome.clear()
|
|
if self.phase == "confirm" then
|
|
self:drawSavePanel()
|
|
return
|
|
end
|
|
-- MenuHeader: menu_coords 0, 0, 14, 7 -- a box from (0,0) to (14,7), which
|
|
-- is exactly two rows per entry plus the border. The extra EXIT GAME row
|
|
-- grows it the way AutomaticGetMenuBottomCoord would.
|
|
Chrome.box(0, 0, 15,
|
|
math.min(#self.list.items * 2 + 2, Chrome.SCREEN_H))
|
|
self.list:draw()
|
|
if self.hasSave then self:drawClockBox() end
|
|
end
|
|
|
|
function MainMenu:draw()
|
|
self:drawPanel()
|
|
end
|
|
|
|
function MainMenu: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(Chrome.fitOrigin(winW, winH, scale))
|
|
G.scale(scale, scale)
|
|
self:drawPanel()
|
|
G.pop()
|
|
end
|
|
|
|
MainMenu.DAYS = DAYS
|
|
|
|
return MainMenu
|