Merge pull request #2724 from thibautbus/feat/rs-translation-runtime

This commit is contained in:
bryanthaboi
2026-10-06 20:00:47 -04:00
25 changed files with 536 additions and 79 deletions
+1 -1
View File
@@ -231,7 +231,7 @@ function M.commit(token, words)
elseif token.type == 6 then writeBard(token, words)
elseif token.type == 10 then token.session.gabbyAndTyData.quote[0] = words[1]
elseif token.type == 9 then
out.stringVar2 = Easy.rawWord(words[1]) .. " " .. Easy.rawWord(words[2])
out.stringVar2 = Easy.word(words[1]) .. " " .. Easy.word(words[2])
out.input = require("src.core.game3.rs.dewford_trend").trySetTrendyPhrase(words, token.session) and 1 or 0
elseif token.type ~= 13 then writeTv(token, words) end
if token.type == 0 then out.input = M.passphrase(words) and 1 or 0 end
+1 -1
View File
@@ -118,7 +118,7 @@ function M.word(id)
local E = require("src.core.game3.easy_chat_text")
local group = E.group(math.floor(value / 512) % 128)
if group then
for _, word in ipairs(group.words) do if word.id == value then return word.text end end
for _, word in ipairs(group.words) do if word.id == value then return E.word(value) end end
end
return romText("gOtherText_ThreeQuestions")
end, id)
@@ -79,7 +79,12 @@ M.BY_NAME.GabbyAndTyGetLastQuote = function(ctx, adapters)
local session, g = R.session(), gabby()
local quote = n(g.quote[0]) % 65536
if quote == 65535 then return false, 0 end
stringVar(ctx, adapters, 1, session, Q.encode(session, Playback.word(quote)))
local word = Playback.word(quote)
local bytes = Q.encode(session, word)
if Playback.displayBytes(session, bytes) ~= word then
bytes = Q.encode(session, require("src.core.game3.easy_chat_text").rawWord(quote))
end
stringVar(ctx, adapters, 1, session, bytes)
g.quote[0] = 65535
return false, 1
end
+2 -14
View File
@@ -17,19 +17,7 @@ return function(V)
}
end
local stems = {
UNKNOWN = "Empty", KUN_MALE = "Kun", KUN_FEMALE = "Chan",
RIVAL_MALE = "May", RIVAL_FEMALE = "Brendan",
VERSION = V.GAME == "sapphire" and "Sapphire" or "Ruby",
AQUA = "Aqua", MAGMA = "Magma", ARCHIE = "Archie", MAXIE = "Maxie",
KYOGRE = "Kyogre", GROUDON = "Groudon",
}
-- pokeruby/src/string_util.c:476
local evil, good = { "Magma", "Maxie", "Groudon" }, { "Aqua", "Archie", "Kyogre" }
if V.GAME == "sapphire" then evil, good = good, evil end
for i, name in ipairs({ "TEAM", "LEADER", "LEGENDARY" }) do
stems["EVIL_" .. name], stems["GOOD_" .. name] = evil[i], good[i]
end
V.TEXT_PLACEHOLDERS = {}
for name, stem in pairs(stems) do V.TEXT_PLACEHOLDERS[name] = V.sym("gExpandedPlaceholder_" .. stem) end
local Placeholders = require("src.import.gba.text_placeholders_extract")
for name, label in pairs(Placeholders.rsSymbols(V.GAME)) do V.TEXT_PLACEHOLDERS[name] = V.sym(label) end
end
+15 -2
View File
@@ -153,6 +153,12 @@ function PokedexChromeExtract.extractEntries(rom, cache, root)
end
end
local syms = Versions.SYMS
local function symbolAt(ptr)
if not (syms and syms.namesAt and ptr and ptr >= 0x08000000 and ptr < 0x0A000000) then return nil end
return syms.namesAt(ptr - 0x08000000)[1]
end
local entries = {}
-- src/data/pokemon/pokedex_entries.h:3
for nat = 0, natDexCount do
@@ -177,6 +183,8 @@ function PokedexChromeExtract.extractEntries(rom, cache, root)
weight = weight,
description = desc1,
description2 = desc2,
descriptionLabel = symbolAt(descPtr1),
descriptionLabel2 = E.desc2 and symbolAt(descPtr2) or nil,
pokemonScale = pokemonScale,
pokemonOffset = pokemonOffset,
trainerScale = trainerScale,
@@ -195,8 +203,12 @@ function PokedexChromeExtract.extractEntries(rom, cache, root)
table.sort(ids)
for _, id in ipairs(ids) do
local e = entries[id]
local labels = ""
for _, field in ipairs({ "descriptionLabel", "descriptionLabel2" }) do
if e[field] then labels = labels .. string.format(", %s = \"%s\"", field, escape_lua(e[field])) end
end
lines[#lines + 1] = string.format(
" [%d] = { category = \"%s\", height = %d, weight = %d, description = \"%s\", description2 = \"%s\", pokemonScale = %d, pokemonOffset = %d, trainerScale = %d, trainerOffset = %d },",
" [%d] = { category = \"%s\", height = %d, weight = %d, description = \"%s\", description2 = \"%s\", pokemonScale = %d, pokemonOffset = %d, trainerScale = %d, trainerOffset = %d%s },",
id,
escape_lua(e.category),
e.height or 0,
@@ -206,7 +218,8 @@ function PokedexChromeExtract.extractEntries(rom, cache, root)
e.pokemonScale or 256,
e.pokemonOffset or 0,
e.trainerScale or 256,
e.trainerOffset or 0
e.trainerOffset or 0,
labels
)
end
lines[#lines + 1] = "}"
@@ -23,6 +23,11 @@ function PokedexEntriesExtract.ready(cache, cacheRoot)
for _, f in ipairs(PokedexEntriesExtract.files()) do
if not cache:exists(root .. f) then return false end
end
local okL, L = pcall(function() return require("src.import.gba.layouts.registry").active() end)
if okL and type(L) == "table" and L.id == "rs" then
local body = cache.read and cache:read(root .. "entries.lua")
return body ~= nil and body:find("descriptionLabel", 1, true) ~= nil
end
return true
end
+3
View File
@@ -25,6 +25,9 @@ function M.text(c, off)
end
error("RS text is unterminated")
end
function M.label(c, off)
return off and c.S.namesAt(off)[1] or nil
end
function M.pairs(c, name, signed)
local off, out = c:off(name), {}
for i = 0, c.S.size(name) / 2 - 1 do
+1 -1
View File
@@ -19,7 +19,7 @@ local PACKS = {
{"naming", {version = 3}}, {"rse/menus"}, {"rse/common_ui"}, {"rse/bag"},
{"rse/berry_tag"}, {"rse/party"}, {"rse/summary"}, {"rse/mail"}, {"rse/easy_chat"},
{"rse/trendy_phrase"}, {"rse/easy_chat_editor", {editorVersion = 2}}, {"rse/rs_egg_hatch"},
{"pokemon/storage", {version = 5}}, {"rse/pokedex"}, {"rse/pokedex_detail"}, {"rse/diploma"},
{"pokemon/storage", {version = 5}}, {"rse/pokedex", {packVersion = 1}}, {"rse/pokedex_detail"}, {"rse/diploma"},
{"rse/trainer_card"}, {"rse/trade"}, {"rse/pokenav"}, {"rse/pokenav_detail"},
{"rse/pokenav_shell"}, {"rse/pokenav_condition"}, {"rse/pokenav_ribbons"}, {"rs/assets"},
}
+18 -6
View File
@@ -1,6 +1,6 @@
local K = require("src.import.gba.rse.boot_gfx")
local A = require("src.import.gba.rs.assets")
local M = {SUB = "rse/pokedex"}
local M = {SUB = "rse/pokedex", packVersion = 1}
local N = "pokedex.o:"
local maps = {{"list", "gUnknown_08E96738"}, {"list_underlay", "gUnknown_08E9C6DC"},
{"start_menu_main", "gPokedexStartMenuMain_Tilemap"}, {"start_menu_search", "gPokedexStartMenuSearchResults_Tilemap"},
@@ -18,18 +18,24 @@ local function bytes(c, name, stride)
for i = 0, c.S.count(name, stride or 1) - 1 do out[i + 1] = stride == 2 and c:u16(off + i * 2) or c:u8(off + i) end
return out
end
-- pokeruby/src/pokedex.c:1116
local function ownLabel(c, off)
local label = A.label(c, off)
return label and label:match("^DexText_") and label or nil
end
local function options(c, name)
local out, off = {}, c:off(N .. name)
for i = 0, c.S.count(N .. name, 8) - 1 do
local a, b = c:ptr(off + i * 8), c:ptr(off + i * 8 + 4)
if not b then break end
out[#out + 1] = {description = a and A.text(c, a) or "", title = A.text(c, b)}
out[#out + 1] = {description = a and A.text(c, a) or "", title = A.text(c, b),
descriptionKey = ownLabel(c, a), titleKey = ownLabel(c, b)}
end
return out
end
function M.run(rom, cache, opts)
local c = A.context(rom, cache, opts, M.SUB)
local man = {screen = "pokedex", coverage = "native_list_detail_search_backgrounds_and_search_tables", gfx = {}, maps = {}, layers = {}, palettes = {}}
local man = {screen = "pokedex", packVersion = M.packVersion, coverage = "native_list_detail_search_backgrounds_and_search_tables", gfx = {}, maps = {}, layers = {}, palettes = {}}
-- pokeruby/src/pokedex.c:1967
local pals = {hoenn = c:palAt(c:off("gPokedexMenu_Pal"), 96), national = c:pal(N .. "sNationalPokedexPalette", 96),
searchResults = c:pal(N .. "sPokedexSearchPalette", 96), search = c:pal("gPokedexMenuSearch_Pal", 64)}
@@ -54,12 +60,14 @@ function M.run(rom, cache, opts)
local top = c:off(N .. "sSearchMenuTopBarItems")
for i = 0, c.S.count(N .. "sSearchMenuTopBarItems", 8) - 1 do
local o = top + i * 8
search.topBar[i + 1] = {description = A.text(c, assert(c:ptr(o))), x = c:u8(o + 4), y = c:u8(o + 5), width = c:u8(o + 6)}
search.topBar[i + 1] = {description = A.text(c, assert(c:ptr(o))), descriptionKey = A.label(c, c:ptr(o)),
x = c:u8(o + 4), y = c:u8(o + 5), width = c:u8(o + 6)}
end
local io = c:off(N .. "sSearchMenuItems")
for i = 0, c.S.count(N .. "sSearchMenuItems", 12) - 1 do
local o = io + i * 12
search.items[i + 1] = {description = A.text(c, assert(c:ptr(o))), titleX = c:u8(o + 4), titleY = c:u8(o + 5), titleWidth = c:u8(o + 6),
search.items[i + 1] = {description = A.text(c, assert(c:ptr(o))), descriptionKey = A.label(c, c:ptr(o)),
titleX = c:u8(o + 4), titleY = c:u8(o + 5), titleWidth = c:u8(o + 6),
selX = c:u8(o + 7), selY = c:u8(o + 8), selWidth = c:u8(o + 9)}
end
for _, k in ipairs({"SearchNatDex", "ShiftNatDex", "SearchHoennDex", "ShiftHoennDex"}) do
@@ -74,5 +82,9 @@ function M.run(rom, cache, opts)
c.files[#c.files + 1] = "orders.lua"
return A.finish(c, man)
end
function M.ready(cache, root) return A.ready(M.SUB, cache, root) end
function M.ready(cache, root)
if not A.ready(M.SUB, cache, root) then return false end
local body = cache:read((root or "data/generated/gba") .. "/" .. M.SUB .. "/manifest.lua")
return body:find("packVersion = " .. M.packVersion, 1, true) ~= nil
end
return M
@@ -20,6 +20,30 @@ TextPlaceholders.SYMBOLS = {
GROUDON = "gText_ExpandedPlaceholder_Groudon",
}
-- pokeruby/src/string_util.c:476
function TextPlaceholders.rsSymbols(game)
local stems = {
UNKNOWN = "Empty", KUN_MALE = "Kun", KUN_FEMALE = "Chan",
RIVAL_MALE = "May", RIVAL_FEMALE = "Brendan",
VERSION = game == "sapphire" and "Sapphire" or "Ruby",
AQUA = "Aqua", MAGMA = "Magma", ARCHIE = "Archie", MAXIE = "Maxie",
KYOGRE = "Kyogre", GROUDON = "Groudon",
}
local evil, good = { "Magma", "Maxie", "Groudon" }, { "Aqua", "Archie", "Kyogre" }
if game == "sapphire" then evil, good = good, evil end
for i, name in ipairs({ "TEAM", "LEADER", "LEGENDARY" }) do
stems["EVIL_" .. name], stems["GOOD_" .. name] = evil[i], good[i]
end
local out = {}
for name, stem in pairs(stems) do out[name] = "gExpandedPlaceholder_" .. stem end
return out
end
function TextPlaceholders.symbolsFor(game)
if game == "ruby" or game == "sapphire" then return TextPlaceholders.rsSymbols(game) end
return TextPlaceholders.SYMBOLS
end
-- pokeemerald/src/string_util.c:448
TextPlaceholders.BY_GENDER = {
KUN = { male = "KUN_MALE", female = "KUN_FEMALE" },
+5 -4
View File
@@ -42,17 +42,18 @@ end
local placeholderCache = {}
-- pokeemerald/src/strings.c:6
local function cartPlaceholders(extracted)
local function cartPlaceholders(extracted, game)
local Extract = require("src.import.gba.text_placeholders_extract")
local RomText = require("src.core.game3.rom_text")
local symbols = Extract.symbolsFor(game)
local function value(name)
local label = Extract.SYMBOLS[name]
local label = symbols[name]
if label and RomText.has(label) then return RomText.plain(label) end
return extracted[name]
end
local out, byGender = {}, {}
for name, v in pairs(extracted) do out[name] = v end
for name in pairs(Extract.SYMBOLS) do out[name] = value(name) end
for name in pairs(symbols) do out[name] = value(name) end
for name, pair in pairs(extracted.byGender or {}) do byGender[name] = pair end
for name, pair in pairs(Extract.BY_GENDER) do
byGender[name] = { male = out[pair.male], female = out[pair.female] }
@@ -94,7 +95,7 @@ TextIR.setContextProvider(function(kind, dialect, ctx)
local okC, CacheFs = pcall(require, "src.import.CacheFs")
local t = okC and CacheFs.loadActive(dialect.placeholders) or nil
if type(t) == "table" then
placeholderCache[id] = { bundle = bundle, values = cartPlaceholders(t) }
placeholderCache[id] = { bundle = bundle, values = cartPlaceholders(t, id) }
return placeholderCache[id].values
end
return nil
+2
View File
@@ -137,6 +137,7 @@ local function action_texts(list)
PartyMenu._actionTexts = { list = list, texts = texts }
return texts
end
PartyMenu._actionTextsFor = action_texts
local FR_INSETS = { msgX = 2, msgY = 2, actX = 9, actY = 2, cursorX = 1 }
local function textInsets()
@@ -944,6 +945,7 @@ function PartyMenu.show(sessionParty, moveOverlay, opts)
end
opts = opts or {}
PartyMenu._order = nil
PartyMenu._rsPrompt = nil
if opts.mode == "battle_switch" or opts.mode == "battle_faint" or (opts.mode == "use" and opts.battleOrder) then
local party0 = sessionParty or (opts.session and opts.session.party)
local ov0 = moveOverlay or (opts.session and (opts.session.move_overlay or opts.session.moveOverlay))
+12 -6
View File
@@ -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)
+8
View File
@@ -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)
+13 -3
View File
@@ -6,6 +6,15 @@ local Cursor = require("src.ui.game3.rs.menu_cursor")
local Pal = require("src.core.game3.pal_fade")
local Fx = require("src.core.game3.gba_fx")
local Easy = require("src.core.game3.easy_chat_text")
local words, groups = {}, {}
local function wordText(id)
if words[id] == nil then words[id] = Easy.word(id) end
return words[id]
end
local function groupText(group)
if groups[group] == nil then groups[group] = Easy.groupName(group) end
return groups[group]
end
local Policy = require("src.ui.game3.rs.easy_chat_editor_policy")
local Frames = require("src.ui.game3.rs.easy_chat_editor_frames")
local C = {isMenu = true, open = false}
@@ -33,6 +42,7 @@ function C.manifest(kind)
end
function C.show(opts)
opts = opts or {}
words, groups = {}, {}
assert(opts.token and opts.type ~= 9, "native RS editor requires its save contract; type9 uses its separate host")
C._man, C._session, C._opts = C.manifest(opts.type), assert(opts.session), opts
C._before, C.words = copyWords(opts.words), copyWords(opts.words)
@@ -347,7 +357,7 @@ local function phraseWords()
local line = {}
for col = 0, C._man.columns - 1 do
local i = row * C._man.columns + col + 1
if i <= C._man.wordCount and C.words[i] ~= Policy.EMPTY then line[#line + 1] = Easy.rawWord(C.words[i]) .. " " end
if i <= C._man.wordCount and C.words[i] ~= Policy.EMPTY then line[#line + 1] = wordText(C.words[i]) .. " " end
end
local pen = C._man.wordPens[row * C._man.columns + 1]
if pen then text(table.concat(line), pen.x, pen.y, "phrase") end
@@ -360,7 +370,7 @@ local function phraseWords()
local image = assert(Kit.image(C._man.blankWord))
love.graphics.setColor(1, 1, 1, 1)
Fx.draw(function() love.graphics.draw(image, x, y) end, C._pal:fx(0))
else text(Easy.rawWord(C.words[i]), x, y, "phrase") end
else text(wordText(C.words[i]), x, y, "phrase") end
end
end
local function pickerText()
@@ -382,7 +392,7 @@ local function pickerText()
local word = (view == "groups" and C.groups or C._list)[row * 2 + col + 1]
if word then
local x = (view == "groups" and 16 or 48) + col * 88
text(view == "groups" and word.name or word.text, x, 88 + (row - state.top) * 16 + offset, "picker")
text(view == "groups" and groupText(word) or wordText(word.id), x, 88 + (row - state.top) * 16 + offset, "picker")
end
end
end
+13 -3
View File
@@ -6,6 +6,15 @@ local Cursor = require("src.ui.game3.rs.menu_cursor")
local Pal = require("src.core.game3.pal_fade")
local Fx = require("src.core.game3.gba_fx")
local Easy = require("src.core.game3.easy_chat_text")
local words, groups = {}, {}
local function wordText(id)
if words[id] == nil then words[id] = Easy.word(id) end
return words[id]
end
local function groupText(group)
if groups[group] == nil then groups[group] = Easy.groupName(group) end
return groups[group]
end
local Policy = require("src.ui.game3.rs.mail_composer_policy")
local Frames = require("src.ui.game3.rs.mail_composer_frames")
local C = {isMenu = true, open = false}
@@ -23,6 +32,7 @@ function C.manifest()
end
function C.write(record, opts)
opts = opts or {}
words, groups = {}, {}
C._man, C._record, C._session, C._opts = C.manifest(), assert(record), assert(opts.session), opts
C._before, C.words = copyWords(record.words), copyWords(record.words)
C.groups = Policy.groups(C._session, opts.gates)
@@ -259,7 +269,7 @@ local function phraseWords()
local parts = {}
for col = 0, 1 do
local i = row * 2 + col + 1
if i <= 9 and C.words[i] ~= Policy.EMPTY then parts[#parts + 1] = Easy.rawWord(C.words[i]) .. " " end
if i <= 9 and C.words[i] ~= Policy.EMPTY then parts[#parts + 1] = wordText(C.words[i]) .. " " end
end
text(table.concat(parts), 40, row * 16, "phrase")
end
@@ -271,7 +281,7 @@ local function phraseWords()
local image = assert(Kit.image(C._man.blankWord))
love.graphics.setColor(1, 1, 1, 1)
Fx.draw(function() love.graphics.draw(image, x, y) end, C._pal:fx(0))
else text(Easy.rawWord(C.words[i]), x, y, "phrase") end
else text(wordText(C.words[i]), x, y, "phrase") end
end
end
local function pickerText()
@@ -293,7 +303,7 @@ local function pickerText()
local word = (view == "groups" and C.groups or C._list)[row * 2 + col + 1]
if word then
local x = (view == "groups" and 16 or 48) + col * 88
text(view == "groups" and word.name or word.text, x, 88 + (row - state.top) * 16 + offset, "picker")
text(view == "groups" and groupText(word) or wordText(word.id), x, 88 + (row - state.top) * 16 + offset, "picker")
end
end
end
+1 -1
View File
@@ -43,7 +43,7 @@ function Reader.content(mail, man)
local parts = {}
for i = 1, line.words do
local id = tonumber(mail.words and mail.words[word]) or 65535
parts[#parts + 1] = Easy.rawWord(id)
parts[#parts + 1] = Easy.word(id)
if i < line.words and id ~= 65535 then parts[#parts + 1] = " " end
word = word + 1
end
+12 -3
View File
@@ -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
+33 -9
View File
@@ -12,12 +12,23 @@ local function menuData()
return assert(require("src.ui.game3.rse.scene_kit").manifest(Data.manifest).party)
end
-- pokeruby/src/pokemon_menu.c:126
Data.actionLabels = {"OtherText_Summary", "OtherText_Switch2", "OtherText_Item", "gOtherText_CancelNoTerminator",
"OtherText_Give2", "OtherText_Take2", "OtherText_Take", "OtherText_Mail", "OtherText_Read2",
"gOtherText_CancelNoTerminator"}
function Data.actionText(action, fields)
local RomText = require("src.core.game3.rom_text")
local nativeId = Data.actionIds[action]
if nativeId then return assert(menuData().cursorOptions[nativeId + 1]) end
if Data.extraLabels[action] then return require("src.core.game3.rom_text").plain(Data.extraLabels[action]) end
if nativeId then
local label = Data.actionLabels[nativeId + 1]
if RomText.has(label) then return RomText.plain(label) end
return assert(menuData().cursorOptions[nativeId + 1])
end
if Data.extraLabels[action] then return RomText.plain(Data.extraLabels[action]) end
assert(fields and fields.index[action] ~= nil, "unknown native RS party action: " .. tostring(action))
return action
local move = fields.moves and fields.moves[fields.index[action] + 1]
return move and require("src.core.game3.pokemon").moveName(move) or action
end
function Data.buildActions(mon, party, fields)
@@ -152,11 +163,23 @@ function Data.drawDescription(desc, x, y)
love.graphics.draw(image, quad, x, y)
end
local function prompt(id, width)
local function promptText(id, name)
local RomText = require("src.core.game3.rom_text")
local key = RomText.key("PartyMenuPromptTexts", id)
if RomText.has(key) then return RomText.plain(key, {stringVars = {name or ""}}) end
return assert(require("src.ui.game3.rse.scene_kit").manifest("rse/party").prompts[id + 1])
end
local function prompt(ui, anchor, id, width)
local memo = ui._rsPrompt
if not (memo and memo.anchor == anchor and memo.id == id) then
local mon = id == 5 and ui._party and ui._party[ui.cursor]
memo = {anchor = anchor, id = id, text = promptText(id, mon and require("src.core.game3.pokemon").displayName(mon))}
ui._rsPrompt = memo
end
local Chrome = require("src.ui.game3.chrome")
Chrome.stdFrame(1, 17, width, 2)
require("src.ui.game3.frlg_font").draw(assert(require("src.ui.game3.rse.scene_kit").manifest("rse/party").prompts[id + 1]), 8, 136,
require("src.ui.game3.rs.party_chrome").textOptions("menu"))
require("src.ui.game3.frlg_font").draw(memo.text, 8, 136, require("src.ui.game3.rs.party_chrome").textOptions("menu"))
end
function Data.drawActions(ui, item)
@@ -166,9 +189,10 @@ function Data.drawActions(ui, item)
local left = item and (30 - (width + 1)) or 19
local top = item and (20 - (count * 2 + 2)) or (18 - count * 2)
local Font = require("src.ui.game3.frlg_font")
prompt(item and ui._submenuKind ~= "MAIL" and 13 or 5, item and (ui._submenuKind == "MAIL" and 18 or 21) or 17)
prompt(ui, actions, item and ui._submenuKind ~= "MAIL" and 13 or 5, item and (ui._submenuKind == "MAIL" and 18 or 21) or 17)
require("src.ui.game3.chrome").stdFrame(left + 1, top + 1, width - 1, count * 2)
for i, action in ipairs(actions) do Font.draw(Data.actionText(action, ui._fieldMoveData), (left + 1) * 8, (top + 1) * 8 + (i - 1) * 16,
local texts = ui._actionTextsFor(actions)
for i in ipairs(actions) do Font.draw(texts[i], (left + 1) * 8, (top + 1) * 8 + (i - 1) * 16,
require("src.ui.game3.rs.party_chrome").textOptions("menu")) end
local cursor = item and ui.itemActionCursor or ui.actionCursor
require("src.ui.game3.rs.menu_cursor").draw((left + 1) * 8, (top + 1) * 8 + (cursor - 1) * 16, (width - 1) * 8)
@@ -180,7 +204,7 @@ function Data.drawPrompt(ui)
elseif ui.mode == "give" then id = 4
elseif ui.mode == "move_tutor" then id = 20
elseif ui.mode == "use" then id = require("src.core.game3.items_data").isTm(ui._item) and 2 or 3 end
prompt(id, 22)
prompt(ui, ui.mode, id, 22)
end
function Data.yesNoLabels()
+7 -1
View File
@@ -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)
+5 -1
View File
@@ -50,6 +50,7 @@ local function detailRows(open) S._detailRowsOpen = open end
function S.openMenu(party, index, opts)
opts = opts or {}
S._man, S._party, S._opts = S.manifest(), party or {}, opts
S._contestDescriptions = {}
S._cursor = index or 1
S._playerState = opts.playerState or opts.session
or require("src.core.game3.runtime").getSession()
@@ -500,7 +501,10 @@ local function moves()
local amount = value == 255 and 0 or math.floor(value / 10)
for i = 0, 7 do frame(S._man.hearts, row == 1 and (i < amount and 1 or 0) or (i < amount and 3 or 4), 48 + i % 4 * 8, 120 + (row - 1) * 16 + math.floor(i / 4) * 8) end
end
print(effect.description, 88, 120, 255)
local descriptions = S._contestDescriptions or {}
S._contestDescriptions = descriptions
descriptions[effect] = descriptions[effect] or SummaryData.contestEffectDescription(effect)
print(descriptions[effect], 88, 120, 255)
else
local def = Pokemon.battleMove(id)
text("gOtherText_Power2", 8, 120); text("gOtherText_Accuracy2", 8, 136)
+9 -2
View File
@@ -18,6 +18,12 @@ local function copiedCard(card)
out.stars = math.max(0, math.min(4, math.floor(tonumber(out.stars) or 0)))
return out
end
local function profilePhrase(card)
local Easy, words = require("src.core.game3.easy_chat_text"), card.easyChatProfile or {}
local out = {}
for row = 0, 1 do out[row + 1] = Easy.word(words[row * 2 + 1] or 65535) .. " " .. Easy.word(words[row * 2 + 2] or 65535) end
return out
end
function T.show(opts)
opts = opts or {}
local rt = package.loaded["src.core.game3.runtime"]
@@ -25,6 +31,7 @@ function T.show(opts)
T._clockSession = opts.clockSession or (rt and rt.getSession and rt.getSession()) or T._session
T._link = opts.linkCard == true or (T._session.dex == nil and T._session.stars ~= nil)
T._card = T._link and copiedCard(T._session) or Policy.generate(T._session)
T._phrase = T._link and profilePhrase(T._card) or nil
T._man, T._onClose = T.manifest(), opts.onClose
T.open, T.side, T._phase, T._setup = true, "front", "setup", 0
T._pal, T._stepper = Pal.new(), Kit.stepper()
@@ -143,8 +150,8 @@ function T.texts(card, side, colon)
local time = T._link and c or T._session
put("time", tostring(tonumber(time.playTimeHours) or 0) .. (colon and " : " or " ") .. string.format("%02d", tonumber(time.playTimeMinutes) or 0), 80, 96, "values", false, 48)
if T._link then
local Easy, words = require("src.core.game3.easy_chat_text"), c.easyChatProfile or {}
for row = 0, 1 do put("phrase" .. row, Easy.rawWord(words[row * 2 + 1] or 65535) .. " " .. Easy.rawWord(words[row * 2 + 2] or 65535), 16, 112 + row * 16) end
local phrase = T._phrase or profilePhrase(c)
for row = 0, 1 do put("phrase" .. row, phrase[row + 1], 16, 112 + row * 16) end
end
else
put("name", c.name .. s.nameSuffix, 224, 16, "values", true)
+13 -3
View File
@@ -6,6 +6,15 @@ local Cursor = require("src.ui.game3.rs.menu_cursor")
local Pal = require("src.core.game3.pal_fade")
local Fx = require("src.core.game3.gba_fx")
local Easy = require("src.core.game3.easy_chat_text")
local words, groups = {}, {}
local function wordText(id)
if words[id] == nil then words[id] = Easy.word(id) end
return words[id]
end
local function groupText(group)
if groups[group] == nil then groups[group] = Easy.groupName(group) end
return groups[group]
end
local Policy = require("src.ui.game3.rs.trendy_phrase_policy")
local Frames = require("src.ui.game3.rs.trendy_phrase_frames")
local C = {isMenu = true, open = false}
@@ -27,6 +36,7 @@ function C.manifest()
end
function C.show(opts)
opts = opts or {}
words, groups = {}, {}
C._man, C._session, C._opts = C.manifest(), assert(opts.session), opts
C._before, C.words = copyWords(opts.words), copyWords(opts.words)
C.groups = Policy.groups(C._session, opts.gates)
@@ -264,7 +274,7 @@ local function triangle(x, y)
end
local function phraseWords()
if C._confirm and C._confirm.kind == "save" then
text(Easy.rawWord(C.words[1]) .. " " .. Easy.rawWord(C.words[2]) .. " ", 48, 24, "phrase")
text(wordText(C.words[1]) .. " " .. wordText(C.words[2]) .. " ", 48, 24, "phrase")
return
end
for i = 1, 2 do
@@ -273,7 +283,7 @@ local function phraseWords()
local image = assert(Kit.image(C._man.blankWord))
love.graphics.setColor(1, 1, 1, 1)
Fx.draw(function() love.graphics.draw(image, x, y) end, C._pal:fx(0))
else text(Easy.rawWord(C.words[i]), x, y, "phrase") end
else text(wordText(C.words[i]), x, y, "phrase") end
end
end
local function pickerText()
@@ -295,7 +305,7 @@ local function pickerText()
local word = (view == "groups" and C.groups or C._list)[row * 2 + col + 1]
if word then
local x = (view == "groups" and 16 or 48) + col * 88
text(view == "groups" and word.name or word.text, x, 88 + (row - state.top) * 16 + offset, "picker")
text(view == "groups" and groupText(word) or wordText(word.id), x, 88 + (row - state.top) * 16 + offset, "picker")
end
end
end
+47 -17
View File
@@ -12,8 +12,32 @@ local RsPolicy = require("src.ui.game3.rs.pokedex_policy")
local function nativeRs() return Gfx.manifest().assetLayout == "rs" end
local rsTextNames = {gText_CryOf = "CryOf", gText_SizeComparedTo = "SizeComparedTo", gText_SelectorArrow = "RightPointingTriangle",
gText_SearchingPleaseWait = "Searching", gText_SearchCompleted = "SearchComplete", gText_NoMatchingPkmnWereFound = "NoMatching"}
local function cached(label, copy)
if label and RomText.has(label) then return RomText.plain(label) end
return copy
end
local RS_LABELS = {RightPointingTriangle = "DexText_RightPointingTriangle"}
local function rsString(key)
local copy = assert(Gfx.manifest().strings[key], "native RS dex text " .. tostring(key))
local text = cached(RS_LABELS[key] or ("gDexText_" .. key), nil)
if text == nil then return copy end
return (copy:match("^\252\019.") or "") .. text
end
-- pokeruby/src/pokedex.c:4228
local function categorySuffix(unknown)
local i, marks = 1, 0
while i <= #unknown do
local c = unknown:sub(i, i)
if c == "?" then marks, i = marks + 1, i + 1
elseif c == " " then i = i + 1
elseif unknown:sub(i, i + 2) == "?" then marks, i = marks + 1, i + 3
else break end
end
if marks == 0 or i > #unknown then return "" end
return (unknown:sub(i - 1, i - 1) == " " and " " or "") .. unknown:sub(i)
end
local function dexText(key)
return nativeRs() and assert(Gfx.manifest().strings[assert(rsTextNames[key], "native RS dex text alias")]) or RomText.plain(key)
return nativeRs() and rsString(assert(rsTextNames[key], "native RS dex text alias")) or RomText.plain(key)
end
local Pokedex = {}
@@ -910,20 +934,26 @@ end
local function monInfo(s, nat, nationalNumber, owned, newEntry)
local e = entryFor(nat)
if nativeRs() then
local strings, out = Gfx.manifest().strings, {}
local out = {}
if newEntry then
local t = strings.RegisterComplete
local t = rsString("RegisterComplete")
out[#out + 1] = {text = t, x = 16 + math.floor((208 - FrlgFont.measure(t)) / 2), y = 0}
end
local num = nationalNumber and nat or (Pokedex.hoennNumber(nat) or nat)
out[#out + 1] = {text = string.format("%03d", num), x = 104, y = 24}
out[#out + 1] = {text = pokemon().name(Pokedex.speciesOf(nat)) or Gfx.manifest().tenDashes, x = 128, y = 24}
local category = owned and ((e.category or "") .. " " .. strings.UnknownPoke:match("[^? ]+.*$")) or strings.UnknownPoke
local cx = 88 + (owned and (FrlgFont.measure(strings.UnknownPoke) - FrlgFont.measure(category)) or 0)
local unknown = rsString("UnknownPoke")
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 strings.UnknownHeight, x = 128, y = 56}
out[#out + 1] = {text = owned and RsPolicy.weightText(e.weight or 0) or strings.UnknownWeight, x = 128, y = 72}
local desc = s.descriptionPage == 1 and e.description2 or e.description
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 desc
if s.descriptionPage == 1 then
desc = cached(e.descriptionLabel2, e.description2)
else
desc = cached(e.descriptionLabel, e.description)
end
out[#out + 1] = {text = owned and (desc or "") or "", x = 16, y = 104}
return out
end
@@ -1535,7 +1565,8 @@ function tasks.loadSize(s)
s.state = 3
elseif st == 3 then
local name = s.session and (s.session.name or s.session.playerName) or ""
local t = dexText("gText_SizeComparedTo") .. tostring(name)
local label = dexText("gText_SizeComparedTo")
local t = label:find("^の") and tostring(name) .. label or label .. tostring(name)
local w = FrlgFont.measure(t) or 0
s.sizeText = { text = t, x = nativeRs() and (24 + 96 - math.floor(w / 2)) or (w < 240 and math.floor((240 - w) / 2) or 0), y = nativeRs() and 120 or 121 }
s.state = 4
@@ -1593,10 +1624,7 @@ function tasks.switchFromSize(s)
end
end
local function cartText(key, fallback)
if key and RomText.has(key) then return RomText.plain(key) end
return fallback
end
local cartText = cached
-- pokeemerald/src/pokedex.c:1330
local TYPE_OPTION = { "gText_DexSearchTypeNone" }
@@ -1632,11 +1660,13 @@ local ITEM_DESCRIPTIONS = { "gText_ListByFirstLetter", "gText_ListByBodyColor",
"gText_ListByType", "gText_SelectPokedexListingMode", "gText_SelectPokedexMode", "gText_ExecuteSearchSwitch" }
local function topBarDescription(i)
return cartText(TOPBAR_DESCRIPTIONS[i + 1], Gfx.manifest().search.topBar[i + 1].description)
local row = Gfx.manifest().search.topBar[i + 1]
return cartText(row.descriptionKey or TOPBAR_DESCRIPTIONS[i + 1], row.description)
end
local function itemDescription(i)
return cartText(ITEM_DESCRIPTIONS[i + 1], Gfx.manifest().search.items[i + 1].description)
local row = Gfx.manifest().search.items[i + 1]
return cartText(row.descriptionKey or ITEM_DESCRIPTIONS[i + 1], row.description)
end
-- pokeemerald/src/pokedex.c:1437
@@ -1657,8 +1687,8 @@ local function searchOptionTexts(which)
local out = {}
for i, t in ipairs(list) do
out[i] = {
title = cartText(keys.titles[i], t.title),
description = cartText(keys.descriptions and keys.descriptions[i], t.description),
title = cartText(t.titleKey or keys.titles[i], t.title),
description = cartText(t.descriptionKey or (keys.descriptions and keys.descriptions[i]), t.description),
}
end
return out
@@ -0,0 +1,280 @@
-- Ruby and Sapphire read the cart strings a translation mod overrides from
-- the script cache, where the mod's text overrides land, as Emerald does
-- (game3_emerald_pack_text_test.lua); the extract's or the pack's copy stays
-- the fallback.
package.path = "./?.lua;./?/init.lua;" .. package.path
local function ir(s) return { { t = "text", s = s }, { t = "eos" } } end
local BUNDLE = { text = {} }
package.loaded["src.core.game3.scripting.space"] = { ensureBundle = function() return BUNDLE end }
local TextIR = require("src.core.game3.scripting.text_ir")
local failed = 0
local function check(cond, msg)
if cond then
print("[ok] " .. msg)
else
failed = failed + 1
print("[FAIL] " .. msg)
end
end
-- Expanded placeholders (pokeruby/src/string_util.c:476): the teams, their
-- leaders and legendaries by edition, the version, the rival and the
-- honorific.
local Extract = require("src.import.gba.text_placeholders_extract")
local ruby, sapphire = Extract.rsSymbols("ruby"), Extract.rsSymbols("sapphire")
check(ruby.EVIL_TEAM == "gExpandedPlaceholder_Magma" and sapphire.EVIL_TEAM == "gExpandedPlaceholder_Aqua",
"the evil team is Magma in Ruby and Aqua in Sapphire")
check(ruby.GOOD_LEADER == "gExpandedPlaceholder_Archie" and sapphire.GOOD_LEADER == "gExpandedPlaceholder_Maxie",
"the good team's leader follows the edition")
check(ruby.VERSION == "gExpandedPlaceholder_Ruby" and sapphire.VERSION == "gExpandedPlaceholder_Sapphire",
"the version names the edition")
check(Extract.symbolsFor("emerald") == Extract.SYMBOLS, "Emerald keeps its own labels")
local Message = require("src.ui.game3.message")
BUNDLE.text = {
gExpandedPlaceholder_Maxie = ir("MAX"), gExpandedPlaceholder_Archie = ir("ARTHUR"),
gExpandedPlaceholder_Ruby = ir("RUBIS"), gExpandedPlaceholder_May = ir("FLORA"),
gExpandedPlaceholder_Kun = ir("くん"),
}
local placeholders = Message.cartPlaceholders({
EVIL_TEAM = "MAGMA", EVIL_LEADER = "MAXIE", GOOD_LEADER = "ARCHIE", VERSION = "RUBY",
RIVAL_MALE = "MAY", RIVAL_FEMALE = "BRENDAN", KUN_MALE = "", KUN_FEMALE = "",
byGender = { RIVAL = { male = "MAY", female = "BRENDAN" }, KUN = { male = "", female = "" } },
}, "ruby")
check(placeholders.EVIL_LEADER == "MAX" and placeholders.GOOD_LEADER == "ARTHUR",
"the leaders' names read the cache by the edition's labels")
check(placeholders.VERSION == "RUBIS", "the version's name reads the cache")
check(placeholders.EVIL_TEAM == "MAGMA", "a placeholder missing from the cache keeps the copy")
check(placeholders.byGender.RIVAL.male == "FLORA", "the rival's name reads the cache")
local line = { { t = "player" }, { t = "ph", code = 5, name = "KUN" }, { t = "text", s = ": " },
{ t = "ph", code = 10, name = "EVIL_LEADER" }, { t = "text", s = " / " },
{ t = "ph", code = 6, name = "RIVAL" }, { t = "eos" } }
check(TextIR.toPlain(line, { dialect = "rs", playerName = "RED", playerGender = 0, placeholders = placeholders })
== "REDくん: MAX / FLORA", "a Ruby line expands the honorific, the leader and the rival from the cache")
BUNDLE.text = { gExpandedPlaceholder_Archie = ir("ARTHUR"), gExpandedPlaceholder_Maxie = ir("MAX") }
local sapphireLine = Message.cartPlaceholders({ EVIL_LEADER = "ARCHIE", byGender = {} }, "sapphire")
check(sapphireLine.EVIL_LEADER == "ARTHUR", "Sapphire's evil leader is Archie's row")
-- The native Pokédex (src/ui/game3/rse/pokedex.lua): the entry's pages by
-- their own labels, its labels and the search screen by the labels the
-- packs name next to their copies.
local MANIFEST = {
assetLayout = "rs",
strings = { UnknownPoke = " ????? POKéMON", CryOf = "\252\019\002CRY OF", RegisterComplete = "REGISTERED",
UnknownHeight = "\252\019\012??'??\"", UnknownWeight = "????.? lbs.", SizeComparedTo = "SIZE COMPARED TO ",
Searching = "Searching...", SearchComplete = "Search completed.", NoMatching = "No matching POKéMON.",
RightPointingTriangle = ">" },
search = {
topBar = { { description = "Search for POKéMON.", descriptionKey = "DexText_SearchForPoke" } },
items = { { description = "List by the first letter.", descriptionKey = "DexText_ListByABC" } },
names = { { title = "ABC", titleKey = "DexText_ABC", description = "" } },
colors = {}, types = {}, orders = {}, modes = {},
},
}
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 { [277] = { category = "WOOD GECKO", height = 5, weight = 50, description = "Ruby page one.",
description2 = "Ruby page two.", descriptionLabel = "DexDescription_Treecko_1",
descriptionLabel2 = "DexDescription_Treecko_2" } }
end
return { nationalToRegional = {} }
end }, { __index = function() return function() end end })
package.loaded["src.core.game3.pokemon"] = { speciesFromNational = function() return 277 end,
name = function() return "ARCKO" end }
local Pokedex = require("src.ui.game3.rse.pokedex")
BUNDLE.text = {
DexDescription_Treecko_2 = ir("Page deux."), gDexText_UnknownPoke = ir(" ????? POKéMON"),
gDexText_CryOf = ir("CRI DE"), DexText_SearchForPoke = ir("Chercher des POKéMON."), DexText_ABC = ir("ABC FR"),
}
local function texts(rows)
local out = {}
for _, row in ipairs(rows) do out[#out + 1] = row.text end
return table.concat(out, "|")
end
local page1 = texts(Pokedex.monInfo({ descriptionPage = 0 }, 252, true, true, false))
local page2 = texts(Pokedex.monInfo({ descriptionPage = 1 }, 252, true, true, false))
check(page1:find("Ruby page one.", 1, true) ~= nil, "a page missing from the cache keeps the pack's copy")
check(page2:find("Page deux.", 1, true) ~= nil, "the second page reads the cache by its own label")
check(page1:find("WOOD GECKO POKéMON", 1, true) ~= nil, "the category keeps the cart's POKéMON after it")
check(Pokedex.topBarDescription(0) == "Chercher des POKéMON.", "the search top bar reads its own label")
check(Pokedex.itemDescription(0) == "List by the first letter.", "a search line missing from the cache keeps the copy")
check(Pokedex.searchOptionTexts(Pokedex.SEARCH.NAME)[1].title == "ABC FR", "a search option reads its own label")
-- pokeruby/src/pokedex.c:4228: only the English cart follows the category
-- with the word after its unknown-category string's question marks.
local function infoRow(owned, y)
for _, row in ipairs(Pokedex.monInfo({ descriptionPage = 0 }, 252, true, owned, false)) do
if row.y == y then return row.text end
end
end
local function category(unknown)
BUNDLE.text.gDexText_UnknownPoke = ir(unknown)
return infoRow(true, 40)
end
check(category(" ????? POKéMON") == "WOOD GECKO POKéMON", "the English cart follows the category with POKéMON")
check(category("?????") == "WOOD GECKO", "the French and German carts print the category alone")
check(category("POKéMON ?????") == "WOOD GECKO", "so does the Italian cart, whose word comes first")
check(category("?????ポケモン") == "WOOD GECKOポケモン", "the Japanese word follows the category without a space")
BUNDLE.text.gDexText_UnknownHeight = ir("???,? m")
check(infoRow(false, 56) == "\252\019\012???,? m", "a label keeps its copy's CLEAR_TO in front of the cache's text")
local Registry = require("src.import.gba.layouts.registry")
local active = Registry.active
Registry.active = function() return { id = "rs" } end
local Entries = require("src.import.gba.pokedex_entries_extract")
local function entriesCache(body) return { exists = function() return true end, read = function() return body end } end
check(not Entries.ready(entriesCache("return { [277] = { description = \"x\" } }")),
"an R/S cache without the description labels extracts its entries again")
check(Entries.ready(entriesCache("return { [277] = { descriptionLabel = \"DexDescription_Treecko_1\" } }")),
"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")
-- Easy Chat: what the player reads prints each word translated
-- (EasyChatText.word); what the scripts compare keeps the cart's word.
package.loaded["src.core.game3.easy_chat_text"] = {
-- 1513 is 513's cart word under another id, translated differently
rawWord = function(id) return "RAW" .. id % 1000 end, word = function(id) return "MOT" .. id end,
group = function() return { words = { { id = 513, text = "RAW513" } } } end,
}
package.loaded["src.core.game3.rs.dewford_trend"] = { editorValid = function() return true end,
trySetTrendyPhrase = function() return true end }
local Contracts = require("src.core.game3.rs.easy_chat_contracts")
local trend = Contracts.commit({ type = 9, wordCount = 2, before = { 0xFFFF, 0xFFFF }, session = {} }, { 513, 514 })
check(trend.stringVar2 == "MOT513 MOT514", "the trendy phrase the script prints is translated")
check(trend.result == 1, "a new trend counts as a change")
local same = Contracts.commit({ type = 9, wordCount = 2, before = { 1513, 514 }, session = {} }, { 513, 514 })
check(same.result == 0, "the change is measured on the cart's words, not their translation")
check(require("src.core.game3.rs.tv_playback").word(513) == "MOT513", "the TV prints a word translated")
-- Gabby and Ty's last quote is printed from the cart's bytes: a translated
-- word the cart's charset cannot hold keeps the cart's word.
local Routes = require("src.core.game3.scripting.natives_rs_tv_routes")
local Rse, Tv = require("src.core.game3.rse.init"), require("src.core.game3.rse.tv")
local session, gabbyData = { version = "ruby" }, { quote = {} }
local rseSession, tvState = Rse.session, Tv.state
Rse.session = function() return session end
Tv.state = function() return { gabbyAndTyData = gabbyData } end
local function lastQuote()
gabbyData.quote[0] = 513
local ctx = {}
Routes.BY_NAME.GabbyAndTyGetLastQuote(ctx, nil)
return ctx.stringVars[1]
end
check(lastQuote() == "MOT513", "Gabby and Ty quote a word translated")
local EASY = package.loaded["src.core.game3.easy_chat_text"]
local word = EASY.word
EASY.word = function() return "ともだち" end
check(lastQuote() == "RAW513", "a word the cart cannot print keeps the cart's word")
EASY.word, Rse.session, Tv.state = word, rseSession, tvState
-- The party menu's actions (pokeruby/src/pokemon_menu.c:126) read the
-- script cache by their pret labels, and a field move prints its move's name.
MANIFESTS["rse/menus"] = { party = { cursorOptions = { "SUMMARY", "SWITCH", "ITEM", "CANCEL", "GIVE", "TAKE",
"TAKE", "MAIL", "READ", "CANCEL", "CUT" } } }
package.loaded["src.core.game3.pokemon"].moveName = function(move) return move == 15 and "COUPE" or nil end
local PartyData = require("src.ui.game3.rs.party_menu_data")
BUNDLE.text = { OtherText_Summary = ir("RESUME"), OtherText_Take = ir("PRENDRE") }
check(PartyData.actionText("SUMMARY") == "RESUME", "a party action reads the cache by its label")
check(PartyData.actionText("TAKE_MAIL") == "PRENDRE", "the mail's TAKE reads its own label")
check(PartyData.actionText("SWITCH") == "SWITCH", "an action missing from the cache keeps the pack's copy")
check(PartyData.actionText("CUT", { index = { CUT = 0 }, moves = { 15 } }) == "COUPE", "a field move prints its move's name")
MANIFESTS["rse/party"] = { prompts = setmetatable({}, { __index = function() return "PROMPT" end }) }
local drawn, resolved = {}, 0
local font, chrome, cursor, partyChrome = package.loaded["src.ui.game3.frlg_font"], package.loaded["src.ui.game3.chrome"],
package.loaded["src.ui.game3.rs.menu_cursor"], package.loaded["src.ui.game3.rs.party_chrome"]
package.loaded["src.ui.game3.frlg_font"] = { draw = function(text) drawn[#drawn + 1] = text end }
package.loaded["src.ui.game3.chrome"] = { stdFrame = function() end }
package.loaded["src.ui.game3.rs.menu_cursor"] = { draw = function() end }
package.loaded["src.ui.game3.rs.party_chrome"] = { textOptions = function() return {} end }
local menu = { ACTIONS = { "SUMMARY", "CANCEL" }, actionCursor = 1,
_actionTextsFor = function(list) resolved = resolved + 1 return { "RESUME", "RETOUR" } end }
PartyData.drawActions(menu, false)
check(drawn[2] == "RESUME" and drawn[3] == "RETOUR" and resolved == 1, "the action menu draws the texts resolved for its list")
local displayName = package.loaded["src.core.game3.pokemon"].displayName
package.loaded["src.core.game3.pokemon"].displayName = function(mon) return mon.nickname end
BUNDLE.text = { ["PartyMenuPromptTexts[5]"] = { { t = "text", s = "Que faire avec " }, { t = "strvar", n = 1 },
{ t = "text", s = "?" }, { t = "eos" } } }
drawn, menu.ACTIONS, menu._party, menu.cursor = {}, { "SUMMARY", "CANCEL" }, { { nickname = "POUSSIFEU" } }, 1
PartyData.drawActions(menu, false)
check(drawn[1] == "Que faire avec POUSSIFEU?", "the action prompt reads the cache and names the POKeMON")
BUNDLE.text = {}
drawn = {}
PartyData.drawActions(menu, false)
check(drawn[1] == "Que faire avec POUSSIFEU?", "the prompt resolved for this menu is drawn again without a lookup")
BUNDLE.text = { ["PartyMenuPromptTexts[5]"] = { { t = "text", s = "Que faire avec " }, { t = "strvar", n = 1 },
{ t = "text", s = "?" }, { t = "eos" } } }
drawn, menu.ACTIONS, menu._party = {}, { "SUMMARY", "CANCEL" }, { { nickname = "GOBOU" } }
PartyData.drawActions(menu, false)
check(drawn[1] == "Que faire avec GOBOU?", "a new action menu names its own POKeMON")
package.loaded["src.core.game3.pokemon"].displayName = displayName
package.loaded["src.ui.game3.frlg_font"], package.loaded["src.ui.game3.chrome"] = font, chrome
package.loaded["src.ui.game3.rs.menu_cursor"], package.loaded["src.ui.game3.rs.party_chrome"] = cursor, partyChrome
if failed > 0 then
print(failed .. " check(s) failed")
os.exit(1)
end
print("all checks passed")