diff --git a/src/ui/game3/rs/contest_painting.lua b/src/ui/game3/rs/contest_painting.lua index c0be9d62..34618ce9 100644 --- a/src/ui/game3/rs/contest_painting.lua +++ b/src/ui/game3/rs/contest_painting.lua @@ -1,5 +1,6 @@ local Base = require("src.ui.game3.rse.contest_painting") local IR = require("src.core.game3.scripting.text_ir") +local RomText = require("src.core.game3.rom_text") local Kit = require("src.ui.game3.rse.scene_kit") local Font = require("src.ui.game3.frlg_font") local Painting = {ID = Base.ID, SUB = Base.SUB} @@ -11,9 +12,9 @@ local function manifest() return man end -local function render(bytes) +local function render(ir) local out = {} - for _, seg in ipairs(IR.decode(bytes, {dialect = "rs"})) do + for _, seg in ipairs(ir) do if seg.t == "ext" then out[#out + 1] = string.char(252, seg.cmd) for _, arg in ipairs(seg.args or {}) do out[#out + 1] = string.char(arg) end @@ -21,7 +22,10 @@ local function render(bytes) end return table.concat(out) end -local function text(man, key) return render(assert(man.textBytes[key], "native painting caption missing: " .. key)) end +local function text(man, key) + local bytes = assert(man.textBytes[key], "native painting caption missing: " .. key) + return render(RomText.irOr(key, IR.decode(bytes, {dialect = "rs"}))) +end local function name(value, limit) if type(value) == "table" then local bytes = {} @@ -30,7 +34,7 @@ local function name(value, limit) bytes[#bytes + 1] = value[i] end bytes[#bytes + 1] = 255 - return render(bytes) + return render(IR.decode(bytes, {dialect = "rs"})) end if type(value) ~= "string" then return "" end if not limit then return value end @@ -48,9 +52,11 @@ function Painting.caption(saveIdx, winner, artist, man) local nickname = name(winner.monName or winner.nickname, man.captionLayout.nicknameBytes) local category = tonumber(winner.contestCategory) or 0 if saveIdx < man.captionLayout.museumStart then + -- pokeruby/src/contest_painting.c:234 + local possessive, first, second = text(man, man.hallPossessive), name(winner.trainerName), nickname + if possessive:find("^[ ,]") then first, second = nickname, name(winner.trainerName) end return text(man, assert(man.rankNames[category], "native painting category")) .. text(man, man.hallCaption) - .. name(winner.trainerName) .. string.char(unpack(man.captionLayout.hallLatinControl)) - .. text(man, man.hallPossessive) .. nickname + .. first .. string.char(unpack(man.captionLayout.hallLatinControl)) .. possessive .. second end local pair = assert(man.captionParts[category], "native museum caption pair") return text(man, pair.prefix) .. nickname .. text(man, pair.suffix) diff --git a/src/ui/game3/rs/decoration.lua b/src/ui/game3/rs/decoration.lua index ebcb86dd..4407424d 100644 --- a/src/ui/game3/rs/decoration.lua +++ b/src/ui/game3/rs/decoration.lua @@ -27,9 +27,16 @@ local aliases = { UI.TEXT_ALIASES = {gText_ApostropheSBase = "gOtherText_PlayersBase"} local cats = {gText_Desk = 0, gText_Chair = 1, gText_Plant = 2, gText_Ornament = 3, gText_Mat = 4, gText_Poster = 5, gText_Doll = 6, gText_Cushion = 7} +local resolved = {} local function nativeText(key, vars) if cats[key] ~= nil then return Inv.categoryName(cats[key]) end local native, m = aliases[key] or key, Decor.manifest() + if not vars and resolved[native] then return resolved[native] end + if Text.has(native) then + local value = Text.plain(native, vars and {stringVars = vars} or nil) + if not vars then resolved[native] = value end + return value + end if m.textBytes and m.textBytes[native] then return IR.toPlain(IR.decode(m.textBytes[native], {dialect = "rs"}), {stringVars = vars or {}}) end @@ -116,6 +123,7 @@ function UI.draw(d, host) end local function options(opts) opts = opts or {} + resolved = {} opts.text, opts.draw = nativeText, UI.draw opts.recordSecretBaseVisit = false opts.avatarGfx = function(s, female) diff --git a/src/ui/game3/rs/move_relearner.lua b/src/ui/game3/rs/move_relearner.lua index 174b7bab..5abc762c 100644 --- a/src/ui/game3/rs/move_relearner.lua +++ b/src/ui/game3/rs/move_relearner.lua @@ -3,6 +3,7 @@ local Kit = require("src.ui.game3.rse.scene_kit") local Font = require("src.ui.game3.frlg_font") local Pokemon = require("src.core.game3.pokemon") local IR = require("src.core.game3.scripting.text_ir") +local RomText = require("src.core.game3.rom_text") local bit = require("bit") local UI = {} local keys = {gText_TeachWhichMoveToPkmn = "gOtherText_TeachWhichMove", @@ -13,13 +14,20 @@ local function manifest() assert(m.assetLayout == "rs", "native RS move tutor pack required") return m end +local resolved = {} +local function cartIr(native) + if not resolved[native] then + resolved[native] = RomText.irOr(native, + IR.decode(assert(manifest().textBytes[native], "native tutor text " .. native), {dialect = "rs"})) + end + return resolved[native] +end local function text(key, ctx) - local m, native = manifest(), keys[key] or key - return IR.toPlain(IR.decode(assert(m.textBytes[native], "native tutor text " .. native), {dialect = "rs"}), ctx or {}) + return IR.toPlain(cartIr(keys[key] or key), ctx or {}) end local function pages(key, vars) local out = {} - local value = IR.toAscii(IR.decode(assert(manifest().textBytes[key]), {dialect = "rs"}), {stringVars = vars}) + local value = IR.toAscii(cartIr(key), {stringVars = vars}) for page in (value .. "\\p"):gmatch("(.-)\\p") do if page ~= "" then out[#out + 1] = page end end return out end @@ -164,6 +172,7 @@ function UI.draw(st) end function UI.show(mon, opts) manifest(); opts = opts or {} + resolved = {} opts.maxShown, opts.text, opts.draw, opts.startLearn = 3, text, UI.draw, startLearn return Base.show(mon, opts) end diff --git a/src/ui/game3/rs/shop_menu.lua b/src/ui/game3/rs/shop_menu.lua index 55e59b8c..5c7e8877 100644 --- a/src/ui/game3/rs/shop_menu.lua +++ b/src/ui/game3/rs/shop_menu.lua @@ -5,6 +5,7 @@ local Chrome = require("src.ui.game3.chrome") local Cursor = require("src.ui.game3.rs.menu_cursor") local Window = require("src.ui.game3.window") local TextIR = require("src.core.game3.scripting.text_ir") +local RomText = require("src.core.game3.rom_text") local Items = require("src.core.game3.items_data") local UI = {CAMERA_OFFSET = {x = -3, y = -3}} UI.WIN = {money = Window.template(1, 1, 12, 2), qty = Window.template(1, 11, 12, 2), @@ -15,10 +16,14 @@ local function manifest() assert(man.assetLayout == "rs" and man.shopVersion == 2, "native RS shop schema required") return man end +local resolved = {} function UI.ir(key) + if resolved[key] then return resolved[key] end local man = manifest() local native = assert(man.textAliases[key] or (man.textBytes[key] and key), "native RS shop text policy missing: " .. key) - return TextIR.decode(assert(man.textBytes[native], "native RS shop text bytes missing: " .. native), {dialect = "rs"}) + resolved[key] = RomText.irOr(native, + TextIR.decode(assert(man.textBytes[native], "native RS shop text bytes missing: " .. native), {dialect = "rs"})) + return resolved[key] end function UI.plain(key, ctx) return TextIR.toPlain(UI.ir(key), ctx or {}) end function UI.box(key, ctx) return UI.plain(key, ctx) end @@ -176,6 +181,7 @@ function UI.drawSell(sale) end function UI.show(shop, opts) opts = opts or {}; opts.nativePolicy = UI + resolved = {} manifest() shop._rsShopFrame = 0 return Base.show(shop, opts) diff --git a/tests/engine/game3_rs_translation_runtime_test.lua b/tests/engine/game3_rs_translation_runtime_test.lua index 7cc2bd02..c9a08427 100644 --- a/tests/engine/game3_rs_translation_runtime_test.lua +++ b/tests/engine/game3_rs_translation_runtime_test.lua @@ -134,6 +134,65 @@ check(Entries.ready(entriesCache("return { [277] = { descriptionLabel = \"DexDes "an R/S cache with the labels keeps its entries") Registry.active = active +-- Native screens that keep a cart string in their pack under its pret label +-- read the script cache's text under that label first. +local COPY = { 0xBD, 0xC9, 0xCA, 0xD3, 0xFF } -- "COPY" +local Kit = require("src.ui.game3.rse.scene_kit") +local MANIFESTS = { + ["items/shop"] = { assetLayout = "rs", shopVersion = 2, textAliases = { gText_HowMayIServeYou = "gOtherText_HowMayIServe" }, + textBytes = { gOtherText_HowMayIServe = COPY, gOtherText_Unnamed = COPY } }, +} +Kit.manifest = function(sub) + if MANIFESTS[sub] then return MANIFESTS[sub] end + return { assetLayout = "rs", textBytes = { gOtherText_TeachWhichMove = COPY, gOtherText_Unnamed = COPY } } +end +local Shop = require("src.ui.game3.rs.shop_menu") +BUNDLE.text = { gOtherText_HowMayIServe = ir("Que puis-je faire pour vous ?"), + gOtherText_TeachWhichMove = ir("Quelle capacité apprendre ?") } +check(Shop.plain("gText_HowMayIServeYou") == "Que puis-je faire pour vous ?", "the shop reads the cache by its label") +check(Shop.plain("gOtherText_Unnamed") == "COPY", "a shop line missing from the cache keeps the pack's copy") + +-- The move relearner, the decoration menus and the museum's painting +-- captions, through the text function each screen hands its shared base. +package.loaded["src.ui.game3.rse.move_relearner"] = { SUB = "tutor", show = function(_, opts) return opts end } +local tutorText = require("src.ui.game3.rs.move_relearner").show({}, {}).text +check(tutorText("gText_TeachWhichMoveToPkmn") == "Quelle capacité apprendre ?", "the move relearner reads the cache by its label") +check(tutorText("gOtherText_Unnamed") == "COPY", "a move relearner line missing from the cache keeps the pack's copy") +BUNDLE.text.gOtherText_TeachWhichMove = ir("Autre texte") +check(tutorText("gText_TeachWhichMoveToPkmn") == "Quelle capacité apprendre ?", "a screen resolves its text once while it is open") +tutorText = require("src.ui.game3.rs.move_relearner").show({}, {}).text +check(tutorText("gText_TeachWhichMoveToPkmn") == "Autre texte", "and again when it opens") + +package.loaded["src.ui.game3.rse.decoration"] = { open = function(opts) return opts end } +package.loaded["src.core.game3.rse.decoration"] = { manifest = function() + return { textBytes = { gSecretBaseText_DecorReturned = COPY, gSecretBaseText_NoDecor = COPY }, strings = {} } +end } +package.loaded["src.core.game3.rse.decoration_inventory"] = { categoryName = function() return "" end } +local decorText = require("src.ui.game3.rs.decoration").open().text +BUNDLE.text = { gSecretBaseText_DecorReturned = ir("La décoration est retournée au PC.") } +check(decorText("gText_DecorationReturnedToPC") == "La décoration est retournée au PC.", "the decoration menus read the cache by their labels") +check(decorText("gText_NoDecorationHere") == "COPY", "a decoration line missing from the cache keeps the pack's copy") + +package.loaded["src.ui.game3.rse.contest_painting"] = { ID = "contest_painting", SUB = "contest_painting" } +local Painting = require("src.ui.game3.rs.contest_painting") +local PAINT = { captionLayout = { museumStart = 5, nicknameBytes = 10 }, + captionParts = { [0] = { prefix = "gContestPaintingCool1", suffix = "gContestPaintingCool2" } }, + textBytes = { gContestPaintingCool1 = COPY, gContestPaintingCool2 = COPY } } +BUNDLE.text = { gContestPaintingCool1 = ir("Le POKéMON ") } +check(Painting.caption(5, { nickname = "ZIGZATON", contestCategory = 0 }, nil, PAINT) == "Le POKéMON ZIGZATONCOPY", + "a painting's caption reads the cache by its labels, the pack's copy where the cache has none") +-- pokeruby/src/contest_painting.c:234: the European carts name the POKéMON +-- first ("ZIGZATON von MAY"), the US and Japanese ones the trainer. +PAINT.rankNames = { [0] = "gContestRankNormal" } +PAINT.hallCaption, PAINT.hallPossessive = "gContestText_ContestWinner", "gOtherText_Unknown1" +PAINT.captionLayout.hallLatinControl = {} +PAINT.textBytes.gContestRankNormal, PAINT.textBytes.gContestText_ContestWinner, PAINT.textBytes.gOtherText_Unknown1 = COPY, COPY, COPY +local winner = { nickname = "ZIGZATON", trainerName = "MAY", contestCategory = 0 } +BUNDLE.text = { gContestRankNormal = ir("NORMAL "), gContestText_ContestWinner = ir("WINNER "), gOtherText_Unknown1 = ir("'s ") } +check(Painting.caption(0, winner, nil, PAINT) == "NORMAL WINNER MAY's ZIGZATON", "the US hall caption names the trainer first") +BUNDLE.text.gOtherText_Unknown1 = ir(" von ") +check(Painting.caption(0, winner, nil, PAINT) == "NORMAL WINNER ZIGZATON von MAY", "a European hall caption names the POKéMON first") + if failed > 0 then print(failed .. " check(s) failed") os.exit(1)