mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
efd81d8e34
love-nx emits gamepad+raw on one press; NamingScreen saw a+b and always erased. Skip raw when isGamepad(); align NX Y→a/X→b; prefer A if both. Co-authored-by: Cursor <cursoragent@cursor.com>
201 lines
6.6 KiB
Lua
201 lines
6.6 KiB
Lua
-- Gen 1 letter-grid naming screen (engine/menus/naming_screen.asm).
|
||
-- Full gen-1 glyph grid (data/text/alphabets.asm): five 9-cell rows
|
||
-- ending in ED, plus a case-switch row. A picks a letter, B deletes,
|
||
-- SELECT flips case, START or the ED cell confirms. If opts.presets is
|
||
-- given, a "NEW NAME" + presets menu is shown first
|
||
-- (engine/menus/main_menu.asm name lists).
|
||
-- Pops itself from the stack, then calls opts.onDone(name).
|
||
--
|
||
-- ui.naming.grid may replace either page; keep an "ED" cell and a
|
||
-- single-cell case-switch row so confirm / case-flip keep working.
|
||
|
||
local Font = require("src.render.Font")
|
||
local Runtime = require("src.mods.Runtime")
|
||
local Sound = require("src.core.Sound")
|
||
local Theme = require("src.ui.Theme")
|
||
local Strings = require("src.core.Strings")
|
||
|
||
local NamingScreen = {}
|
||
NamingScreen.__index = NamingScreen
|
||
NamingScreen.isOpaque = true
|
||
|
||
-- SGB: generic whole-screen palette (SET_PAL_GENERIC)
|
||
function NamingScreen:sgbPalettes(game)
|
||
return require("src.render.PaletteFX").wholeNamed(game.data, "MEWMON")
|
||
end
|
||
|
||
-- both letter pages (wAlphabetCase, data/text/alphabets.asm): row 6 is
|
||
-- the case-switch cell, labelled with the page it flips to
|
||
local GRID_UPPER = {
|
||
{ "A", "B", "C", "D", "E", "F", "G", "H", "I" },
|
||
{ "J", "K", "L", "M", "N", "O", "P", "Q", "R" },
|
||
{ "S", "T", "U", "V", "W", "X", "Y", "Z", " " },
|
||
{ "×", "(", ")", ":", ";", "[", "]", "<PK>", "<MN>" },
|
||
{ "-", "?", "!", "♂", "♀", "/", ".", ",", "ED" },
|
||
{ "lower case" },
|
||
}
|
||
local GRID_LOWER = {
|
||
{ "a", "b", "c", "d", "e", "f", "g", "h", "i" },
|
||
{ "j", "k", "l", "m", "n", "o", "p", "q", "r" },
|
||
{ "s", "t", "u", "v", "w", "x", "y", "z", " " },
|
||
{ "×", "(", ")", ":", ";", "[", "]", "<PK>", "<MN>" },
|
||
{ "-", "?", "!", "♂", "♀", "/", ".", ",", "ED" },
|
||
{ "UPPER CASE" },
|
||
}
|
||
|
||
-- locate the ED confirm cell and the case-switch row on a (possibly
|
||
-- modded) grid; falls back to vanilla coordinates
|
||
local function findMeta(grid)
|
||
local caseRow, edRow, edCol = #grid, 5, 9
|
||
for r, row in ipairs(grid) do
|
||
if #row == 1 and (row[1] == "lower case" or row[1] == "UPPER CASE"
|
||
or row[1] == "lower" or row[1] == "UPPER") then
|
||
caseRow = r
|
||
end
|
||
for c, cell in ipairs(row) do
|
||
if cell == "ED" then edRow, edCol = r, c end
|
||
end
|
||
end
|
||
return caseRow, edRow, edCol
|
||
end
|
||
|
||
local function sameGrid(grid) return grid end
|
||
|
||
function NamingScreen.new(game, opts)
|
||
opts = opts or {}
|
||
local self = setmetatable({}, NamingScreen)
|
||
self.game = game
|
||
self.title = opts.title or Strings("YOUR NAME?")
|
||
self.presets = opts.presets
|
||
self.maxLen = opts.maxLen or 7
|
||
self.default = opts.default
|
||
self.onDone = opts.onDone
|
||
self.glyphs = {} -- typed glyphs; multi-byte cells (<PK>, ♂, ×) count as 1
|
||
self.row, self.col = 1, 1
|
||
self.lower = false
|
||
return self
|
||
end
|
||
|
||
function NamingScreen:enter()
|
||
if self.presets and #self.presets > 0 then
|
||
local Menu = require("src.ui.Menu")
|
||
local items = { { label = Strings("NEW NAME") } }
|
||
for _, preset in ipairs(self.presets) do
|
||
table.insert(items, {
|
||
label = preset,
|
||
onSelect = function()
|
||
-- the menu already popped itself; pop the naming screen too
|
||
self.game.stack:pop()
|
||
if self.onDone then self.onDone(preset) end
|
||
end,
|
||
})
|
||
end
|
||
self.game.stack:push(Menu.new(self.game, items, {
|
||
tx = 4, ty = 0, tw = 12, th = #items * 2 + 2, cancelable = false,
|
||
}))
|
||
end
|
||
end
|
||
|
||
function NamingScreen:confirm()
|
||
local name = table.concat(self.glyphs)
|
||
if name == "" then
|
||
name = (self.presets and self.presets[1]) or self.default or "A"
|
||
end
|
||
Sound.play(self.game.data, "Press_AB")
|
||
self.game.stack:pop()
|
||
if self.onDone then self.onDone(name) end
|
||
end
|
||
|
||
function NamingScreen:grid()
|
||
local base = self.lower and GRID_LOWER or GRID_UPPER
|
||
if not Runtime.wantsHook("ui.naming.grid") then return base end
|
||
local hooked = Runtime.call("ui.naming.grid", sameGrid, base, {
|
||
lower = self.lower and true or false,
|
||
title = self.title,
|
||
maxLen = self.maxLen,
|
||
game = self.game,
|
||
})
|
||
if type(hooked) ~= "table" or #hooked == 0 then return base end
|
||
return hooked
|
||
end
|
||
|
||
-- Gen 1 jumps the cursor to ED once the name is full.
|
||
function NamingScreen:jumpToEnd()
|
||
local _, edRow, edCol = findMeta(self:grid())
|
||
self.row, self.col = edRow, edCol
|
||
end
|
||
|
||
function NamingScreen:update(dt)
|
||
local GRID = self:grid()
|
||
local caseRow, edRow, edCol = findMeta(GRID)
|
||
local input = self.game.input
|
||
if input:wasPressed("start") then
|
||
self:confirm()
|
||
return
|
||
end
|
||
if input:wasPressed("select") then -- SELECT also flips the case page
|
||
self.lower = not self.lower
|
||
return
|
||
end
|
||
if input:wasPressed("up") then
|
||
-- wrapping up from the top row lands on the case-switch cell
|
||
self.row = self.row > 1 and self.row - 1 or caseRow
|
||
self.col = math.min(self.col, #GRID[self.row])
|
||
elseif input:wasPressed("down") then
|
||
self.row = self.row < #GRID and self.row + 1 or 1
|
||
self.col = math.min(self.col, #GRID[self.row])
|
||
elseif input:wasPressed("left") then
|
||
-- no horizontal movement on the case-switch row
|
||
if self.row ~= caseRow then
|
||
self.col = self.col > 1 and self.col - 1 or #GRID[self.row]
|
||
end
|
||
elseif input:wasPressed("right") then
|
||
if self.row ~= caseRow then
|
||
self.col = self.col < #GRID[self.row] and self.col + 1 or 1
|
||
end
|
||
else
|
||
-- Prefer A over B when both edges fire in one frame (love-nx dual
|
||
-- gamepad+raw path historically set both; erase must not win).
|
||
local pressedA = input:wasPressed("a")
|
||
local pressedB = input:wasPressed("b")
|
||
if pressedA and pressedB then pressedB = false end
|
||
if pressedB then
|
||
table.remove(self.glyphs)
|
||
elseif pressedA then
|
||
if self.row == edRow and self.col == edCol then
|
||
self:confirm()
|
||
return
|
||
end
|
||
if self.row == caseRow then
|
||
self.lower = not self.lower
|
||
return
|
||
end
|
||
if #self.glyphs < self.maxLen then
|
||
Sound.play(self.game.data, "Press_AB")
|
||
table.insert(self.glyphs, GRID[self.row][self.col])
|
||
if #self.glyphs >= self.maxLen then self:jumpToEnd() end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
function NamingScreen:draw()
|
||
love.graphics.setColor(1, 1, 1, 1)
|
||
love.graphics.rectangle("fill", 0, 0, 160, 144)
|
||
love.graphics.setColor(0, 0, 0, 1)
|
||
Font.draw(self.title, 8, 8)
|
||
-- typed name with dashes for the empty slots
|
||
for i = 1, self.maxLen do
|
||
Font.draw(self.glyphs[i] or "-", 56 + (i - 1) * 8, 24)
|
||
end
|
||
for r, row in ipairs(self:grid()) do
|
||
for c, cell in ipairs(row) do
|
||
Font.draw(Strings(cell), c * 16, 32 + r * 16)
|
||
end
|
||
end
|
||
Font.drawCode(Theme.cursor, self.col * 16 - 8, 32 + self.row * 16)
|
||
love.graphics.setColor(1, 1, 1, 1)
|
||
end
|
||
|
||
return NamingScreen
|