mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-03 08:07:07 -04:00
Merge pull request #2321 from Nexhas28/nexhas28-fix-firered-nickname-mon-icon-scale
This commit is contained in:
@@ -2100,7 +2100,10 @@ local function start_post_catch_flow(catchRes)
|
||||
gender = gender,
|
||||
personality = personality,
|
||||
seed = ename,
|
||||
title = "YOUR POKEMON'S NICKNAME?",
|
||||
-- pret naming_screen.c:1712 DrawMonTextEntryBox: gSpeciesNames[mon]
|
||||
-- + gText_PkmnsNickname. The hand-written "YOUR POKEMON'S NICKNAME?"
|
||||
-- was 141px wide and spilled over the frame's right edge.
|
||||
title = Naming.monTitle(Pokemon.name(sp)),
|
||||
onDone = function(nick)
|
||||
if nick and nick ~= "" and nick ~= ename then
|
||||
if mon then mon.nickname = nick end
|
||||
|
||||
@@ -261,7 +261,7 @@ Natives.ALLOW = {
|
||||
end)
|
||||
local sname = (Pokemon.name and Pokemon.name(species)) or "POKéMON"
|
||||
adapters.openNaming({
|
||||
title = sname .. "'s nickname?",
|
||||
title = require("src.ui.game3.naming").monTitle(sname),
|
||||
template = "NICKNAME",
|
||||
maxLen = 10, -- pret POKEMON_NAME_LENGTH
|
||||
species = species,
|
||||
|
||||
+29
-6
@@ -25,6 +25,15 @@ Naming.TEMPLATE = {
|
||||
NICKNAME = "NICKNAME",
|
||||
}
|
||||
|
||||
-- pret gText_PkmnsNickname ("'s nickname?"), prepended with gSpeciesNames[mon]
|
||||
-- by DrawMonTextEntryBox — pokefirered/src/naming_screen.c:1712. Used by both
|
||||
-- mon naming templates (CAUGHT_MON and NICKNAME).
|
||||
function Naming.monTitle(speciesName)
|
||||
local s = tostring(speciesName or "")
|
||||
if s == "" then s = "POKéMON" end
|
||||
return s .. "'s nickname?"
|
||||
end
|
||||
|
||||
-- pret sKeyboardChars + sPageColumnXPos (cursor). Letters drawn via ROW_TEXT CLEARs.
|
||||
local PAGES = {
|
||||
{
|
||||
@@ -90,9 +99,19 @@ local SIDE = { "PAGE", "BACK", "OK" }
|
||||
-- Values below are on-screen top-left blit positions (px).
|
||||
local L = {
|
||||
titleX = 73, titleY = 33,
|
||||
-- WIN_TEXT_ENTRY_BOX = {tilemapLeft 9, tilemapTop 4, width 16} → screen
|
||||
-- x 72..200; the title prints at (1,1) inside it, so it has 127px before the
|
||||
-- GBA's per-window clip (CopyGlyphToWindow) would truncate it.
|
||||
titleMaxW = 127,
|
||||
-- Player/rival icon CreateSprite(56,37); 16×32 → TL (48,21)
|
||||
iconCX = 56, iconCY = 37,
|
||||
iconW = 16, iconH = 32,
|
||||
-- Mon icon CreateMonIcon(species, SpriteCallbackDummy, 56, 40) is a 32×32
|
||||
-- sprite (Versions.MON_ICON_W/H) drawn unscaled, centred on the frame baked
|
||||
-- into bg.png — pokefirered/src/naming_screen.c:1422. Reusing the 16×32
|
||||
-- player box above would letterbox it to half size.
|
||||
monIconCX = 56, monIconCY = 40,
|
||||
monIconW = 32, monIconH = 32,
|
||||
charY = 49,
|
||||
-- Underscore CreateSprite(base+3,60) 8×8 → TL (base-1, 56)
|
||||
underscoreBaseY = 56,
|
||||
@@ -310,13 +329,15 @@ local function drawPlayerIcon(st)
|
||||
if entry and entry.image then
|
||||
local iw = entry.w or entry.image:getWidth()
|
||||
local ih = entry.h or (entry.quads and entry.h) or entry.image:getHeight()
|
||||
local sc = math.min(L.iconW / iw, L.iconH / ih)
|
||||
local q = entry.quads and (entry.quads[1] or entry.quads[0])
|
||||
local cx, cy = L.iconCX, 40 -- pokefirered/src/naming_screen.c:1422
|
||||
-- A mon icon is 32×32 and fills its frame 1:1; the frontPic fallback
|
||||
-- is 64×64 and shrinks into the same box.
|
||||
local sc = math.min(L.monIconW / iw, L.monIconH / ih)
|
||||
-- pret passes SpriteCallbackDummy, so the icon shows its frame 0.
|
||||
local q = entry.quads and entry.quads[0]
|
||||
if q then
|
||||
love.graphics.draw(entry.image, q, cx, cy, 0, sc, sc, iw / 2, ih / 2)
|
||||
love.graphics.draw(entry.image, q, L.monIconCX, L.monIconCY, 0, sc, sc, iw / 2, ih / 2)
|
||||
else
|
||||
love.graphics.draw(entry.image, cx, cy, 0, sc, sc, iw / 2, ih / 2)
|
||||
love.graphics.draw(entry.image, L.monIconCX, L.monIconCY, 0, sc, sc, iw / 2, ih / 2)
|
||||
end
|
||||
return
|
||||
end
|
||||
@@ -705,7 +726,9 @@ function Naming.draw()
|
||||
|
||||
-- 5) Title + icon + typed name (above KB)
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
drawText(st.title, L.titleX, L.titleY)
|
||||
-- Clamp to the text-entry window so an over-long title cannot spill over the
|
||||
-- frame (pret blits glyphs into the window buffer and clips there).
|
||||
drawText(st.title, L.titleX, L.titleY, { maxWidth = L.titleMaxW })
|
||||
|
||||
drawPlayerIcon(st)
|
||||
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
-- #2319: the Gen 3 nickname screen drew the mon's 32x32 menu icon at half
|
||||
-- size, because the mon branch reused the player OW sprite's 16x32 box.
|
||||
-- pret pokefirered/src/naming_screen.c:1422 CreateMonIcon(species,
|
||||
-- SpriteCallbackDummy, 56, 40, ...) draws it unscaled, showing frame 0.
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.harness")
|
||||
local check, eq = T.check, T.eq
|
||||
love = love or require("tests.love_stub")
|
||||
|
||||
local function fakeImage(w, h)
|
||||
return {
|
||||
w = w, h = h,
|
||||
getWidth = function() return w end,
|
||||
getHeight = function() return h end,
|
||||
getDimensions = function() return w, h end,
|
||||
}
|
||||
end
|
||||
|
||||
-- 32x32 icon frames stacked two high; 64x64 front pic fallback.
|
||||
local ICON_IMG = fakeImage(32, 64)
|
||||
local PIC_IMG = fakeImage(64, 64)
|
||||
local QUAD0, QUAD1 = { frame = 0 }, { frame = 1 }
|
||||
|
||||
local monIcon, monFrontPic
|
||||
package.loaded["src.core.game3.pokemon"] = {
|
||||
icon = function() return monIcon end,
|
||||
frontPic = function() return monFrontPic end,
|
||||
}
|
||||
package.loaded["src.ui.game3.stack"] = {
|
||||
push = function() end, pop = function() end,
|
||||
busy = function() return false end, drawOrder = function() return {} end,
|
||||
}
|
||||
|
||||
local Versions = require("src.import.gba.versions")
|
||||
local Naming = require("src.ui.game3.naming")
|
||||
|
||||
eq(Naming.L.monIconW, Versions.MON_ICON_W,
|
||||
"mon box width matches the extracted icon width")
|
||||
eq(Naming.L.monIconH, Versions.MON_ICON_H,
|
||||
"mon box height matches the extracted icon height")
|
||||
|
||||
-- Normalise both draw signatures into one record:
|
||||
-- draw(img, quad, x, y, rot, sx, sy, ox, oy) and draw(img, x, y, rot, sx, sy, ox, oy)
|
||||
local draws = {}
|
||||
local realDraw = love.graphics.draw
|
||||
love.graphics.draw = function(a, b, c, d, e, f, g, h, i)
|
||||
local rec
|
||||
if type(b) == "table" then
|
||||
rec = { img = a, quad = b, x = c, y = d, rot = e, sx = f, sy = g, ox = h, oy = i }
|
||||
else
|
||||
rec = { img = a, x = b, y = c, rot = d, sx = e, sy = f, ox = g, oy = h }
|
||||
end
|
||||
draws[#draws + 1] = rec
|
||||
end
|
||||
|
||||
local function render(opts)
|
||||
draws = {}
|
||||
Naming.open(opts)
|
||||
local ok, err = pcall(Naming.draw)
|
||||
Naming.dismiss()
|
||||
return draws, ok, err
|
||||
end
|
||||
|
||||
local function drawsOf(ds, img)
|
||||
local out = {}
|
||||
for _, d in ipairs(ds) do
|
||||
if d.img == img then out[#out + 1] = d end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
-- The mon icon: 1:1 on the (56, 40) frame centre, static frame 0.
|
||||
monIcon = { image = ICON_IMG, w = 32, h = 32, sheetH = 64, frames = 2,
|
||||
quads = { [0] = QUAD0, [1] = QUAD1 } }
|
||||
monFrontPic = nil
|
||||
local ds, ok, err = render({ template = "NICKNAME", species = 1, maxLen = 10 })
|
||||
check(ok, "nickname draw runs headless: " .. tostring(err))
|
||||
local icon = drawsOf(ds, ICON_IMG)
|
||||
eq(#icon, 1, "the mon icon is drawn once")
|
||||
local d = icon[1] or {}
|
||||
eq(d.quad, QUAD0, "static frame 0, not the 1px-shifted frame 1")
|
||||
eq(d.x, 56, "icon centre x = 56 (naming_screen.c:1422)")
|
||||
eq(d.y, 40, "icon centre y = 40 (naming_screen.c:1422)")
|
||||
eq(d.rot, 0, "no rotation")
|
||||
eq(d.sx, 1, "32x32 icon drawn 1:1, not the player box's 0.5")
|
||||
eq(d.sy, 1, "uniform scale")
|
||||
eq(d.ox, 16, "origin x is the frame centre, so the draw covers the frame")
|
||||
eq(d.oy, 16, "origin y is the frame centre")
|
||||
|
||||
-- frontPic fallback: 64x64 shrunk into the same 32x32 box (was 0.25).
|
||||
monIcon = nil
|
||||
monFrontPic = { image = PIC_IMG, w = 64, h = 64 }
|
||||
local ds2 = render({ template = "CAUGHT_MON", species = 4 })
|
||||
local pic = drawsOf(ds2, PIC_IMG)
|
||||
eq(#pic, 1, "the front-pic fallback is drawn once")
|
||||
eq(pic[1] and pic[1].x, 56, "fallback keeps the mon centre x")
|
||||
eq(pic[1] and pic[1].y, 40, "fallback keeps the mon centre y")
|
||||
eq(pic[1] and pic[1].sx, 0.5, "64x64 fallback fills the 32x32 mon box")
|
||||
eq(pic[1] and pic[1].sy, 0.5, "uniform fallback scale")
|
||||
eq(pic[1] and pic[1].ox, 32, "fallback origin is the pic centre")
|
||||
|
||||
-- Player/rival slot: still the 16x32 OW box.
|
||||
monIcon, monFrontPic = nil, nil
|
||||
local portrait = fakeImage(64, 64)
|
||||
local ds3 = render({ template = "PLAYER", icon = portrait })
|
||||
local p = drawsOf(ds3, portrait)
|
||||
eq(#p, 1, "the player-slot portrait is drawn once")
|
||||
eq(p[1] and p[1].x, 56, "player slot keeps its OW centre x")
|
||||
eq(p[1] and p[1].y, 37, "player slot keeps its OW centre y")
|
||||
eq(p[1] and p[1].sx, 0.25, "player OW box math is unchanged (16/64)")
|
||||
eq(p[1] and p[1].sy, 0.25, "player OW box scale is uniform")
|
||||
eq(p[1] and p[1].ox, 32, "player OW origin is the portrait centre")
|
||||
|
||||
-- Guard: the mon icon is never letterboxed below 1:1 again.
|
||||
for _, dl in ipairs({ ds, ds2 }) do
|
||||
for _, e in ipairs(drawsOf(dl, ICON_IMG)) do
|
||||
check((e.sx or 0) >= 1, "mon icon scale is never below 1:1")
|
||||
end
|
||||
end
|
||||
|
||||
love.graphics.draw = realDraw
|
||||
T.finish()
|
||||
@@ -0,0 +1,132 @@
|
||||
-- #2319 (second report): the nickname screen's title ran past the right edge of
|
||||
-- the text box. The catch flow hand-wrote "YOUR POKEMON'S NICKNAME?" (141px in a
|
||||
-- 127px window), where pret composes gSpeciesNames[mon] + gText_PkmnsNickname:
|
||||
-- pokefirered/src/naming_screen.c:1712 DrawMonTextEntryBox
|
||||
-- pokefirered/src/strings.c:772 gText_PkmnsNickname = "'s nickname?"
|
||||
-- sWindowTemplates[WIN_TEXT_ENTRY_BOX] = {tilemapLeft 9, width 16} → screen
|
||||
-- x 72..200, text printed at (1,1) → x 73, so the budget is 127px.
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.harness")
|
||||
local check, eq = T.check, T.eq
|
||||
love = love or require("tests.love_stub")
|
||||
|
||||
-- --- exact GBA advance widths, from the extracted FRLG latin font ------------
|
||||
local function loadTable(path, pattern)
|
||||
local f = io.open(path, "r")
|
||||
if not f then return nil end
|
||||
local src = f:read("*a")
|
||||
f:close()
|
||||
local t = {}
|
||||
for a, b in src:gmatch(pattern) do t[tonumber(a)] = tonumber(b) end
|
||||
return t
|
||||
end
|
||||
|
||||
local widths = loadTable("src/import/gba/chrome/fonts/latin_widths.lua", "%[(%d+)%]%s*=%s*(%d+)")
|
||||
check(widths ~= nil and next(widths) ~= nil, "latin glyph widths are loadable")
|
||||
|
||||
-- TextIR.CHARMAP: glyph id -> character, inverted to character -> glyph id.
|
||||
local charmap = require("src.core.game3.scripting.text_ir").CHARMAP
|
||||
local glyphOf = {}
|
||||
for gid, ch in pairs(charmap) do
|
||||
if type(gid) == "number" and type(ch) == "string" and #ch == 1 then
|
||||
glyphOf[ch] = gid
|
||||
end
|
||||
end
|
||||
check(next(glyphOf) ~= nil, "TextIR.CHARMAP is invertible to character -> glyph id")
|
||||
|
||||
local function px(s)
|
||||
local total = 0
|
||||
for i = 1, #s do
|
||||
local gid = glyphOf[s:sub(i, i)]
|
||||
if gid then total = total + (widths[gid] or 0) end
|
||||
end
|
||||
return total
|
||||
end
|
||||
|
||||
local LEFT, BUDGET = 73, 127 -- text-entry window starts at x=73, 127px of room
|
||||
local function endsAt(s) return LEFT + px(s) end
|
||||
|
||||
-- --- the reported string genuinely overflowed -------------------------------
|
||||
local reported = "YOUR POKEMON'S NICKNAME?"
|
||||
check(px(reported) > BUDGET,
|
||||
("the old hardcoded title (%dpx) really did overflow the %dpx window"):format(px(reported), BUDGET))
|
||||
check(endsAt(reported) > 200,
|
||||
"the old title ran past the window's right edge (x=200)")
|
||||
|
||||
-- --- Naming.monTitle mirrors pret -------------------------------------------
|
||||
local Naming = require("src.ui.game3.naming")
|
||||
eq(type(Naming.monTitle), "function", "Naming.monTitle exists")
|
||||
eq(Naming.monTitle("CHARMANDER"), "CHARMANDER's nickname?",
|
||||
"mon title is gSpeciesNames[mon] + gText_PkmnsNickname")
|
||||
eq(Naming.monTitle("BULBASAUR"), "BULBASAUR's nickname?", "title tracks the species")
|
||||
check(Naming.monTitle("CHARMANDER") ~= Naming.monTitle("SQUIRTLE"),
|
||||
"title is species-derived, not a fixed caption")
|
||||
eq(Naming.monTitle(nil), "POKéMON's nickname?", "a missing name falls back safely")
|
||||
|
||||
-- --- every real species title fits the window -------------------------------
|
||||
-- Longest real species names (both 10 characters) plus the widest 10-character
|
||||
-- name the font can produce: a strict upper bound over all caps names.
|
||||
for _, name in ipairs({ "CHARMANDER", "FORRETRESS", "MASQUERAIN", "BUTTERFREE" }) do
|
||||
local title = Naming.monTitle(name)
|
||||
check(endsAt(title) <= 200,
|
||||
("%q ends at x=%d, inside the box (<=200)"):format(title, endsAt(title)))
|
||||
end
|
||||
|
||||
local widest = {}
|
||||
for ch in pairs(glyphOf) do
|
||||
if ch:match("^[A-Z]$") then widest[#widest + 1] = ch end
|
||||
end
|
||||
table.sort(widest, function(a, b)
|
||||
local wa, wb = widths[glyphOf[a]] or 0, widths[glyphOf[b]] or 0
|
||||
if wa == wb then return a < b end
|
||||
return wa > wb
|
||||
end)
|
||||
local worstName = table.concat({ widest[1], widest[2], widest[3], widest[4], widest[5],
|
||||
widest[6], widest[7], widest[8], widest[9], widest[10] })
|
||||
local worstTitle = Naming.monTitle(worstName)
|
||||
check(endsAt(worstTitle) <= 200,
|
||||
("worst-case 10-char name %q ends at x=%d, still inside the box"):format(worstTitle, endsAt(worstTitle)))
|
||||
|
||||
-- --- the screen clamps the title to the window ------------------------------
|
||||
-- pret blits glyphs into the window buffer (CopyGlyphToWindow), which clips at
|
||||
-- the window edge; the port must pass that window as maxWidth rather than the
|
||||
-- generic 240 default, which would let text run over the frame art.
|
||||
eq(Naming.L.titleMaxW, BUDGET, "L.titleMaxW is the text-entry window's usable width")
|
||||
eq(Naming.L.titleX, LEFT, "the title still starts at the window's left edge")
|
||||
|
||||
local FrlgFont = require("src.ui.game3.frlg_font")
|
||||
local realDraw = FrlgFont.draw
|
||||
local seen = {}
|
||||
FrlgFont.draw = function(text, x, y, opts)
|
||||
seen[#seen + 1] = { text = text, x = x, y = y, maxWidth = opts and opts.maxWidth }
|
||||
return 0
|
||||
end
|
||||
|
||||
package.loaded["src.core.game3.pokemon"] = {
|
||||
icon = function() return nil end,
|
||||
frontPic = function() return nil end,
|
||||
}
|
||||
package.loaded["src.ui.game3.stack"] = {
|
||||
push = function() end, pop = function() end,
|
||||
busy = function() return false end, drawOrder = function() return {} end,
|
||||
}
|
||||
|
||||
local title = Naming.monTitle("BUTTERFREE")
|
||||
Naming.open({ template = "CAUGHT_MON", species = 12, maxLen = 10, title = title })
|
||||
local ok, err = pcall(Naming.draw)
|
||||
Naming.dismiss()
|
||||
FrlgFont.draw = realDraw
|
||||
check(ok, "nickname draw runs headless: " .. tostring(err))
|
||||
|
||||
local drawn
|
||||
for _, s in ipairs(seen) do
|
||||
if s.text == title then drawn = s end
|
||||
end
|
||||
check(drawn ~= nil, "the title is drawn as its own run")
|
||||
eq(drawn and drawn.x, LEFT, "title drawn at the window's left edge")
|
||||
eq(drawn and drawn.y, 33, "title drawn at the template's y")
|
||||
eq(drawn and drawn.maxWidth, BUDGET,
|
||||
"title is clamped to the window, not the 240px default that overran the frame")
|
||||
|
||||
T.finish()
|
||||
@@ -124,7 +124,7 @@ for _, key in ipairs(expectedKeys) do
|
||||
end
|
||||
|
||||
-- Check Naming screen layout coordinates match pokefirered
|
||||
Naming.open({ template = "NICKNAME", species = 1, maxLen = 10, title = "YOUR POKEMON'S NICKNAME?" })
|
||||
Naming.open({ template = "NICKNAME", species = 1, maxLen = 10, title = Naming.monTitle("BULBASAUR") })
|
||||
check(Naming.isOpen(), "Naming screen is open")
|
||||
|
||||
-- Verify species icon positioning at (56, 40)
|
||||
|
||||
Reference in New Issue
Block a user