Expand Ruby and Sapphire's placeholders from the script cache

The runtime expanded Ruby and Sapphire's {EVIL_TEAM}, {GOOD_LEADER},
{VERSION}, {RIVAL} and {KUN} (and the other version-dependent names)
from text/placeholders.lua, the values the extractor reads from the US
cart, so a translation never reached them: the team leaders stayed MAXIE
and ARCHIE, the rival MAY or BRENDAN, the version RUBY or SAPPHIRE, and
the Japanese honorific empty. Each value is a cart string the script
cache also holds under its pokeruby label (gExpandedPlaceholder_Maxie,
_May, _Ruby, _Kun...); the placeholders now read it from there, with the
extract's value as the fallback, the way Emerald's do.

The labels are defined once, in text_placeholders_extract.lua
(rsSymbols, the evil and good teams swapped by edition), and both the
Ruby/Sapphire import and the message box read them from there.
This commit is contained in:
thibautbus
2026-10-05 16:14:42 +02:00
parent 070a55da91
commit b5988d4654
4 changed files with 98 additions and 18 deletions
+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
@@ -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
@@ -0,0 +1,67 @@
-- 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")
if failed > 0 then
print(failed .. " check(s) failed")
os.exit(1)
end
print("all checks passed")