diff --git a/src/core/game3/pokedex_units.lua b/src/core/game3/pokedex_units.lua new file mode 100644 index 00000000..d49f59a7 --- /dev/null +++ b/src/core/game3/pokedex_units.lua @@ -0,0 +1,34 @@ +local Strings = require("src.core.Strings") + +local Units = {} + +function Units.metric(species) + local Pokemon = require("src.core.game3.pokemon") + if Pokemon._dex == nil then return nil end + local ok, entry = pcall(Pokemon.dexEntry, species) + if ok and type(entry) == "table" and entry.heightM and entry.weightKg then return entry end + return nil +end + +local function decimal(value, pad) + local text = (("%.1f"):format(value or 0):gsub("%.", ",")) + return string.rep(pad or " ", math.max(0, 5 - #text)) .. text +end + +function Units.height(entry, pad) + return decimal(entry.heightM, pad) .. " " .. Strings("m") +end + +function Units.weight(entry, pad) + return decimal(entry.weightKg, pad) .. " " .. Strings("kg") +end + +function Units.unknownHeight() + return "???,? " .. Strings("m") +end + +function Units.unknownWeight() + return "???,? " .. Strings("kg") +end + +return Units diff --git a/src/mods/Schemas.lua b/src/mods/Schemas.lua index 626a6740..a1befda5 100644 --- a/src/mods/Schemas.lua +++ b/src/mods/Schemas.lua @@ -1222,7 +1222,8 @@ local function monRecord(base, id) local entry = national and t.dex and t.dex[national] if entry then record.dexEntry = { kind = entry.category or "", height = entry.height or 0, - weight = entry.weight or 0 } + weight = entry.weight or 0, heightM = entry.heightM, + weightKg = entry.weightKg } end return record end @@ -1346,6 +1347,8 @@ local function writeMon(target, t, num, value) category = value.dexEntry.kind or old.category, height = value.dexEntry.height or old.height, weight = value.dexEntry.weight or old.weight, + heightM = value.dexEntry.heightM or old.heightM, + weightKg = value.dexEntry.weightKg or old.weightKg, } end local sprites = sideTable(target, "spriteOverrides") @@ -1847,7 +1850,8 @@ R.pokemon = { item = f.opt(f.id("items")), param = f.opt(f.int(0)) }), dexEntry = f.opt(f.rec{ kind = f.str, height = f.int(0), - weight = f.int(0) }), + weight = f.int(0), heightM = f.opt(f.num), + weightKg = f.opt(f.num) }), spriteFront = f.path, spriteBack = f.path, trueColor = f.opt(f.bool), }, diff --git a/src/ui/game3/pokedex.lua b/src/ui/game3/pokedex.lua index 62e4ae1a..779036ed 100644 --- a/src/ui/game3/pokedex.lua +++ b/src/ui/game3/pokedex.lua @@ -239,6 +239,7 @@ function Pokedex.showRegistration(speciesId, opts) opts = opts or {} Pokedex.open = true Pokedex._regSpecies = tonumber(speciesId) or 1 + Pokedex._unitTexts = nil Pokedex.selectedSpecies = Pokedex._regSpecies Pokedex._session = opts.session Pokedex._dex = (opts.session and opts.session.dex) or Dex.new() @@ -269,6 +270,7 @@ end -- pokedex_screen.c function Pokedex.resetScreenState() + Pokedex._unitTexts = nil Pokedex.screen = "mode_select" Pokedex.subScreenPrev = "mode_select" Pokedex.modeCursor = 2 @@ -856,6 +858,20 @@ function Pokedex.controlInfoForDataPage(screen) return RomText.plain("gText_Cry"), RomText.plain("gText_NextDataCancel") end +local function unitTexts(sp, caught) + local memo = Pokedex._unitTexts + if memo and memo.sp == sp and memo.caught == caught then return memo end + local Units = require("src.core.game3.pokedex_units") + local metric = Units.metric(sp) + memo = { sp = sp, caught = caught } + if metric then + memo.height = caught and Units.height(metric) or Units.unknownHeight() + memo.weight = caught and Units.weight(metric) or Units.unknownWeight() + end + Pokedex._unitTexts = memo + return memo +end + --- 3. Detailed Data Entry Screen (Page 1: Specs & Flavor Text, Page 2: Size Chart & Area Map) local function draw_data_screen() local dex = Pokedex._dex @@ -903,13 +919,14 @@ local function draw_data_screen() -- Line 3 (y = 60): HT in FONT_SMALL at x=16; Height value at x=46 FrlgFont.draw(RomText.plain("gText_HT"), 16, 60, { small = true, colors = upperColors }) - local htStr = isCaught and (entry.heightFormatted or " ??'??\"") or " ??'??\"" + local units = unitTexts(sp, isCaught) + local htStr = units.height or (isCaught and (entry.heightFormatted or " ??'??\"") or " ??'??\"") FrlgFont.draw(htStr, 46, 60, { small = true, colors = upperColors }) -- Line 4 (y = 72): WT in FONT_SMALL at x=16; Weight value at x=46 FrlgFont.draw(RomText.plain("gText_WT"), 16, 72, { small = true, colors = upperColors }) -- src/pokedex_screen.c:2834 - local wtStr = isCaught and entry.weightFormatted or ("????.? " .. RomText.plain("gText_Lbs")) + local wtStr = units.weight or (isCaught and entry.weightFormatted or ("????.? " .. RomText.plain("gText_Lbs"))) FrlgFont.draw(wtStr, 46, 72, { small = true, colors = upperColors }) -- Footprint (16x16) at screen x=104, y=64 (window x=88, y=40) diff --git a/src/ui/game3/rse/pokedex.lua b/src/ui/game3/rse/pokedex.lua index 8a067251..a986fab8 100644 --- a/src/ui/game3/rse/pokedex.lua +++ b/src/ui/game3/rse/pokedex.lua @@ -1,5 +1,6 @@ local Kit = require("src.ui.game3.rse.scene_kit") local Strings = require("src.core.Strings") +local Units = require("src.core.game3.pokedex_units") local FrlgFont = require("src.ui.game3.frlg_font") local RomText = require("src.core.game3.rom_text") local Pal = require("src.core.game3.pal_fade") @@ -946,8 +947,9 @@ local function monInfo(s, nat, nationalNumber, owned, newEntry) local category = owned and (Strings(e.category or "") .. categorySuffix(unknown)) or unknown local cx = 88 + (owned and (FrlgFont.measure(unknown) - FrlgFont.measure(category)) or 0) out[#out + 1] = {text = category, x = cx, y = 40} - out[#out + 1] = {text = owned and Pokedex.heightText(e.height or 0) or rsString("UnknownHeight"), x = 128, y = 56} - out[#out + 1] = {text = owned and RsPolicy.weightText(e.weight or 0) or rsString("UnknownWeight"), x = 128, y = 72} + local metric = owned and Units.metric(Pokedex.speciesOf(nat)) + out[#out + 1] = {text = metric and Units.height(metric, " ") or owned and Pokedex.heightText(e.height or 0) or rsString("UnknownHeight"), x = 128, y = 56} + out[#out + 1] = {text = metric and Units.weight(metric, " ") or owned and RsPolicy.weightText(e.weight or 0) or rsString("UnknownWeight"), x = 128, y = 72} local desc if s.descriptionPage == 1 then desc = cached(e.descriptionLabel2, e.description2) @@ -975,7 +977,11 @@ local function monInfo(s, nat, nationalNumber, owned, newEntry) out[#out + 1] = { text = category, x = 0x64, y = 0x29 } out[#out + 1] = { text = RomText.plain("gText_HTHeight"), x = 0x60, y = 0x39 } out[#out + 1] = { text = RomText.plain("gText_WTWeight"), x = 0x60, y = 0x49 } - if owned then + local metric = owned and Units.metric(sp) + if metric then + out[#out + 1] = { text = Units.height(metric, "{UNK_SPACER}"), x = 0x81, y = 0x39 } + out[#out + 1] = { text = Units.weight(metric, "{UNK_SPACER}"), x = 0x81, y = 0x49 } + elseif owned then out[#out + 1] = { text = Pokedex.heightText(e.height or 0), x = 0x81, y = 0x39 } out[#out + 1] = { text = Pokedex.weightText(e.weight or 0), x = 0x81, y = 0x49 } else diff --git a/tests/engine/game3_pokedex_metric_units_test.lua b/tests/engine/game3_pokedex_metric_units_test.lua new file mode 100644 index 00000000..555805fc --- /dev/null +++ b/tests/engine/game3_pokedex_metric_units_test.lua @@ -0,0 +1,65 @@ +#!/usr/bin/env luajit +package.path = "./?.lua;./?/init.lua;" .. package.path +_G.love = _G.love or require("tests.love_stub") + +local T = require("tests.harness") +local check = T.check + +T.suite("game3 pokedex metric units") + +local Schemas = require("src.mods.Schemas") +local target = { _names = { [1] = "BULBASAUR" }, _dex = { [1] = { category = "SEED", height = 7, weight = 69 } } } +local patched = { dexEntry = { kind = "SEED", height = 7, weight = 69, heightM = 0.7, weightKg = 6.9 } } +Schemas.gen3View.monWrite(target, { ops = { BULBASAUR = true }, get = function() return patched end }) +local row = target._dex[1] +check(row.heightM == 0.7 and row.weightKg == 6.9 and row.height == 7 and row.category == "SEED", + "a patch's metric values reach the dex table beside the cart's") + +local DEX = { [252] = { category = "WOOD GECKO", height = 5, weight = 50, heightM = 0.5, weightKg = 5 }, + [253] = { category = "WOOD GECKO", height = 9, weight = 216 } } +package.loaded["src.core.game3.pokemon"] = { _dex = DEX, + dexEntry = function(species) return DEX[species] end, + name = function() return "TREECKO" end, + speciesFromNational = function(nat) return nat end, +} + +local Units = require("src.core.game3.pokedex_units") +check(Units.metric(252) == DEX[252] and Units.metric(253) == nil, "only a species with metric values is metric") +check(Units.height(DEX[252]) == " 0,5 m" and Units.weight(DEX[252]) == " 5,0 kg", + "a metric height and weight print with one decimal and a comma") +check(Units.weight({ weightKg = 950 }) == "950,0 kg", "a three-digit weight fills the field") +check(Units.unknownHeight() == "???,? m" and Units.unknownWeight() == "???,? kg", "the unknown masks") + +local MANIFEST = { assetLayout = "emerald", tenDashes = "----------", + strings = { UnknownPoke = "?????POKéMON", UnknownHeight = "??'??\"", UnknownWeight = "????.? lbs." } } +package.loaded["src.ui.game3.rse.pokedex_gfx"] = setmetatable({ manifest = function() return MANIFEST end }, + { __index = function() return function() end end }) +package.loaded["src.ui.game3.rse.mapsec"] = setmetatable({ readLua = function(path) + if path:find("entries", 1, true) then return DEX end + return { nationalToRegional = {} } +end }, { __index = function() return function() end end }) +local BUNDLE = { text = {} } +package.loaded["src.core.game3.scripting.space"] = { ensureBundle = function() return BUNDLE end } +local Pokedex = require("src.ui.game3.rse.pokedex") +local function rows(nat, owned) + local out = {} + for _, r in ipairs(Pokedex.monInfo({ descriptionPage = 0 }, nat, true, owned, false)) do out[#out + 1] = r.text end + return table.concat(out, "|") +end +MANIFEST.assetLayout = "rs" +check(rows(252, true):find("| 0,5 m| 5,0 kg|", 1, true) ~= nil, + "a caught Ruby/Sapphire entry prints its metric values, two spaces per missing digit") +check(rows(253, true):find("lbs.", 1, true) ~= nil, "a Ruby/Sapphire entry without metric values keeps the US ones") +check(rows(252, false):find("????.? lbs.", 1, true) ~= nil, "a seen Ruby/Sapphire entry keeps the cart's unknown rows") + +local function ir(text) return { { t = "text", s = text }, { t = "eos" } } end +BUNDLE.text = { gText_Pokemon = ir("POKéMON"), gText_5MarksPokemon = ir("?????POKéMON"), gText_NumberClear01 = ir("No. "), + gText_HTHeight = ir("HT"), gText_WTWeight = ir("WT"), + gText_UnkHeight = ir("???,? m"), gText_UnkWeight = ir("???,? kg") } +MANIFEST.assetLayout = "emerald" +check(rows(252, true):find("{UNK_SPACER}{UNK_SPACER}0,5 m|{UNK_SPACER}{UNK_SPACER}5,0 kg", 1, true) ~= nil, + "a caught Emerald entry prints its metric values aligned with the digit spacer") +check(rows(253, true):find("lbs.", 1, true) ~= nil, "an Emerald entry without metric values keeps the US ones") +check(rows(252, false):find("???,? m|???,? kg", 1, true) ~= nil, "a seen Emerald entry prints the cart's unknown rows") + +T.finish("game3 pokedex metric units")