-- The player-name menu (pokegold engine/menus/intro_menu.asm NamePlayer, -- data/player_names.asm NameMenuHeader). -- -- Transcribed from the header rather than laid out by eye, because almost -- every coordinate here comes out of one: -- -- menu_coords 0, 0, 10, TEXTBOX_Y - 1 the box: (0,0) to (10,11) -- STATICMENU_CURSOR | STATICMENU_PLACE_TITLE | STATICMENU_DISABLE_B -- 5 items, default option 1, title indent 2, title "NAME" -- -- GetMenuTextStartCoord turns those flags into the label origin: the border -- costs a row and a column, STATICMENU_CURSOR costs another column, and the -- absence of STATICMENU_NO_TOP_SPACING costs another row -- so the labels -- start at (2,2) and step TWO rows each (PlaceVerticalMenuItems adds -- 2 * SCREEN_WIDTH per item). The cursor sits one column left of the labels, -- at column 1. The title is placed from MenuBoxCoord2Tile plus the indent, -- which lands it ON the box's top border at (2,0). -- -- The other half of the screen is the pic. NamePlayer opens by calling -- MovePlayerPicRight, which walks the 7x7 CAL frontpic OakSpeech left at -- hlcoord 6,4 one tile per frame until it sits at hlcoord 13,4 -- so the menu -- is not on its own, it slides in beside the player. Picking a preset runs -- MovePlayerPicLeft to walk it back before returning to the speech. -- -- STATICMENU_DISABLE_B is why B does nothing here: there is no way out of -- this menu except choosing a name. local Chrome = require("src.ui.gen2.Chrome") local Font = require("src.render.Font") local GbcPalette = require("src.render.GbcPalette") local Screens = require("src.ui.Screens") local NamePick = {} NamePick.__index = NamePick NamePick.isOpaque = true -- data/player_names.asm PlayerNameArray, Gold's half of the IF. local PRESETS = { "GOLD", "HIRO", "TAYLOR", "KARL" } -- menu_coords 0, 0, 10, TEXTBOX_Y - 1 (TEXTBOX_Y = 12). local BOX_X1, BOX_Y1, BOX_X2, BOX_Y2 = 0, 0, 10, 11 -- GetMenuTextStartCoord's answer for this header's flags. local TEXT_X, TEXT_Y = 2, 2 local CURSOR_X = TEXT_X - 1 local TITLE, TITLE_X, TITLE_Y = "NAME", 2, 0 -- Intro_PrepTrainerPic puts the 7x7 pic at hlcoord 6,4; MovePlayerPic walks -- it to 13,4 one tile per frame. local PIC_X_LEFT, PIC_X_RIGHT, PIC_Y = 6, 13, 4 local PIC_TILES = 7 function NamePick:wantsFillScale() return true end function NamePick:drawsWidescreen() return true end -- opts: onDone(name), font, pic (the CAL frontpic already loaded by the Oak -- speech), picColors, presets function NamePick.new(game, opts) opts = opts or {} local self = setmetatable({}, NamePick) self.game = game self.onDone = opts.onDone self.items = { "NEW NAME" } for _, name in ipairs(opts.presets or PRESETS) do self.items[#self.items + 1] = name end -- `db 1 ; default option`: the cursor starts on NEW NAME, not on a preset. self.cursor = 1 self.pic = opts.pic self.picColors = opts.picColors self.fontOk = false local font = opts.font if font then self.fontOk = pcall(Font.load, { font = font }) end -- The approach walk, and then the walk back once a preset is taken. picX -- is the pic's live tile column rather than a step count: MovePlayerPic -- walks it one tile per frame and both directions end where they end, so -- the column IS the state. self.picX = PIC_X_LEFT self.slide = "in" return self end function NamePick:choose(name) if self.onDone then self.onDone(name) end end -- NEW NAME opens Gold's own keyboard (src/ui/gen2/NamingScreen.lua), not -- Gen 1's: the two differ in grid size, the case switch, and where DEL/END sit. function NamePick:openNaming() local data = self.game and self.game.data or {} local sprites = data.gen2Sprites local chris = sprites and sprites.SPRITE_CHRIS local Palettes = require("src.world.gen2.Palettes") Screens.push(self.game, "Gen2NamingScreen", { type = "player", menuGfx = data.gen2MenuGfx, iconPath = chris and chris.image or nil, -- Chris is PAL_OW_RED; the naming screen is lit like day. iconColors = data.gen2Palettes and Palettes.spritePalette(data.gen2Palettes, "DAY", chris) or nil, onDone = function(name) -- An empty name keeps the default, the way ending entry with nothing -- typed leaves wPlayerName at its preset. self.game.stack:pop() -- the naming screen if name and #name > 0 then self:choose(name) else self:choose(self.items[2] or "GOLD") end end, }) end function NamePick:update(_dt) -- MovePlayerPic is a blocking DelayFrame loop on the cart: nothing reads -- the joypad until the pic has finished walking. if self.slide == "in" then if self.picX < PIC_X_RIGHT then self.picX = self.picX + 1 return end self.slide = nil return elseif self.slide == "out" then if self.picX > PIC_X_LEFT then self.picX = self.picX - 1 return end self.slide = nil self:choose(self.pendingName) return end local input = self.game.input if not input then return end if input:wasPressed("up") then self.cursor = self.cursor > 1 and self.cursor - 1 or #self.items elseif input:wasPressed("down") then self.cursor = self.cursor < #self.items and self.cursor + 1 or 1 elseif input:wasPressed("a") or input:wasPressed("start") then -- `ld a, [wMenuCursorY]; dec a; jr z, .NewName`: item 1 is NEW NAME and -- everything else is a preset. B is deliberately not handled -- -- STATICMENU_DISABLE_B. if self.cursor == 1 then if self.fontOk then self:openNaming() else self:choose(self.items[2] or "GOLD") end else -- A preset returns through MovePlayerPicLeft, so the pic walks back -- before the speech resumes. self.pendingName = self.items[self.cursor] self.slide = "out" end end end function NamePick:drawPic() if not self.pic then return end local G = love.graphics local w, h = self.pic:getDimensions() -- PlaceGraphic lays a 7x7 block from the coordinate; a pic smaller than -- that sits in the block's bottom-left, as PadFrontpic leaves it. local x = self.picX * 8 local y = PIC_Y * 8 + (PIC_TILES - h / 8) * 8 local function body() G.setColor(1, 1, 1, 1) G.draw(self.pic, x, y) end if self.picColors and GbcPalette.available() then GbcPalette.with(self.picColors, body) else body() end G.setColor(1, 1, 1, 1) end function NamePick:drawPanel() local G = love.graphics Chrome.clear() self:drawPic() -- The menu is only up once the pic has finished walking over: MenuBox is -- drawn by ShowPlayerNamingChoices, which NamePlayer calls after the walk. if self.slide == "in" then return end if not self.fontOk then G.setColor(0, 0, 0, 1) for index, label in ipairs(self.items) do local prefix = (index == self.cursor) and "> " or " " G.print(prefix .. label, TEXT_X * 8, (TEXT_Y + (index - 1) * 2) * 8) end G.setColor(1, 1, 1, 1) return end -- MenuBox: GetMenuBoxDims then `dec b / dec c`, so the interior is one less -- than the span in each direction. Chrome.textbox(BOX_X1, BOX_Y1, BOX_X2 - BOX_X1 - 1, BOX_Y2 - BOX_Y1 - 1) -- PlaceString REPLACES the tilemap cells it lands on, and the title lands -- on the box's top border -- so the border tiles under it have to go, or -- the letters sit on a line the cart does not draw there. G.setColor(1, 1, 1, 1) G.rectangle("fill", TITLE_X * 8, TITLE_Y * 8, #TITLE * 8, 8) Chrome.print(TITLE, TITLE_X, TITLE_Y) for index, label in ipairs(self.items) do Chrome.print(label, TEXT_X, TEXT_Y + (index - 1) * 2) end Chrome.cursor(CURSOR_X, TEXT_Y + (self.cursor - 1) * 2) G.setColor(1, 1, 1, 1) end function NamePick:draw() self:drawPanel() end function NamePick: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 NamePick.PRESETS = PRESETS return NamePick