Merge pull request #2680 from thibautbus/fix/game3-name-order-in-cart-text

Join names to translated cart text the way the European carts do
This commit is contained in:
bryanthaboi
2026-10-05 11:33:59 -04:00
committed by GitHub
9 changed files with 197 additions and 9 deletions
+11 -1
View File
@@ -39,11 +39,21 @@ local function monName(ref)
return require("src.core.game3.battle.state").displayName(ref)
end
-- A foe's name with the cart's wild/foe word. The US and Japanese rows go
-- before the name ("Wild ", "やせいの "); the French, Italian and Spanish
-- carts write theirs to follow it (" sauvage", " salvaje", " selvatico") and
-- append it (pret pokeemerald multi-language, src/battle_message.c:4042,
-- :4649). The German row has the same shape (" (Wild)").
function BattleText.withMonPrefix(prefix, name)
if prefix:sub(1, 1) == " " then return name .. prefix end
return prefix .. name
end
-- src/battle_message.c:1807 HANDLE_NICKNAME_STRING_CASE
local function withPrefix(fill, ref)
if isPlayer(ref) then return monName(ref) end
local prefix = fill.trainer and "sText_FoePkmnPrefix" or "sText_WildPkmnPrefix"
return RomText.plain(prefix) .. monName(ref)
return BattleText.withMonPrefix(RomText.plain(prefix), monName(ref))
end
local function moveName(fill, move)
+16 -2
View File
@@ -32,12 +32,26 @@ function Secondary.statName(stat)
return RomText.at("gStatNamesTable", Secondary.STAT_ID[stat])
end
-- A two-stage stat change: the US and Japanese "sharply"/"harshly" rows end
-- with a space before the plain change ("sharply rose!", "ぐーんと あがった!");
-- the French, Italian and Spanish ones carry the whole change ("monte
-- beaucoup!"), and their code skips the plain string that follows (pret
-- pokeemerald multi-language, src/battle_message.c:4617). The German rows have
-- the same shape ("steigt stark!").
function Secondary.sharpChange(sharpId, plainId)
local sharp = RomText.plain(sharpId)
if sharp:sub(-1) == " " or sharp:sub(-3) == "\227\128\128" then
return sharp .. RomText.plain(plainId)
end
return sharp
end
-- src/battle_script_commands.c:6758
local function stat_text(ad, battler, stat, delta, isUser)
local change
if delta >= 2 then change = RomText.plain("STRINGID_STATSHARPLY") .. RomText.plain("STRINGID_STATROSE")
if delta >= 2 then change = Secondary.sharpChange("STRINGID_STATSHARPLY", "STRINGID_STATROSE")
elseif delta >= 1 then change = RomText.plain("STRINGID_STATROSE")
elseif delta <= -2 then change = RomText.plain("STRINGID_STATHARSHLY") .. RomText.plain("STRINGID_STATFELL")
elseif delta <= -2 then change = Secondary.sharpChange("STRINGID_STATHARSHLY", "STRINGID_STATFELL")
else change = RomText.plain("STRINGID_STATFELL") end
local id
if delta > 0 then
+1 -1
View File
@@ -143,7 +143,7 @@ local function stat_up(ad, b, item, stat, delta)
b.stages[stat] = math.min(6, (b.stages[stat] or 0) + delta)
ad:playAnim("general", "STATS_CHANGE", b, b, Secondary.statAnimArg(stat, delta))
local change = RomText.plain("STRINGID_STATROSE")
if delta >= 2 then change = RomText.plain("STRINGID_STATSHARPLY") .. change end
if delta >= 2 then change = Secondary.sharpChange("STRINGID_STATSHARPLY", "STRINGID_STATROSE") end
say_id(ad, "STRINGID_USINGITEMSTATOFPKMNROSE", {
lastItem = item, buff1 = Secondary.statName(stat), scrActive = b, buff2 = change,
})
+2 -1
View File
@@ -466,7 +466,8 @@ function State.prefixedName(st, battler, name)
local RomText = require("src.core.game3.rom_text")
local prefix = (st ~= nil and not st.wild) and "sText_FoePkmnPrefix" or "sText_WildPkmnPrefix"
local ok, pre = pcall(RomText.plain, prefix)
return (ok and pre or (st ~= nil and not st.wild and "Foe " or "Wild ")) .. name
pre = ok and pre or (st ~= nil and not st.wild and "Foe " or "Wild ")
return require("src.core.game3.battle.battle_text").withMonPrefix(pre, name)
end
function State.isFainted(battler)
+10 -3
View File
@@ -596,11 +596,18 @@ function SB.ownedByAnotherPlayer(sess)
return SB.base(sess, 0).secretBaseId ~= SB._curId
end
-- pokeemerald/src/secret_base.c:728
-- pokeemerald/src/secret_base.c:728; the European carts put the owner in the
-- row's STR_VAR_1 ("BASE DE {STR_VAR_1}", pret pokeemerald multi-language,
-- src/secret_base.c:732), the US and Japanese ones append the row to it.
function SB.name(idx, sess)
local b = SB.base(sess, idx)
local ok, suffix = pcall(Rse.text, "gText_ApostropheSBase")
return tostring(b.trainerName or "") .. (ok and suffix or "'s BASE")
local owner = tostring(b.trainerName or "")
local ok, base = pcall(function()
return require("src.core.game3.rom_text").plain("gText_ApostropheSBase", { stringVars = { "\1" } })
end)
if not ok then return owner .. "'s BASE" end
if base:find("\1", 1, true) then return (base:gsub("\1", function() return owner end)) end
return owner .. base
end
-- pokeemerald/src/secret_base.c:735
+12 -1
View File
@@ -56,13 +56,24 @@ Naming.TEMPLATE = {
-- pokeemerald/include/naming_screen.h:7
Naming.TEMPLATE_ORDER = { "PLAYER", "BOX", "CAUGHT_MON", "NICKNAME", "WALDA" }
-- A name and a cart string the US code appends after it. The French, Italian
-- and Spanish carts put the name in the string's STR_VAR_1 instead, wherever
-- the row places it (pret pokeemerald multi-language, src/naming_screen.c:1746);
-- a row without one keeps the US order.
function Naming.nameInto(key, name)
name = tostring(name or "")
local text = RomText.plain(key, { stringVars = { "\1" } })
if text:find("\1", 1, true) then return (text:gsub("\1", function() return name end)) end
return name .. text
end
-- pokeemerald/src/naming_screen.c:1715
function Naming.monTitle(speciesName)
if Versions.active() == "ruby" or Versions.active() == "sapphire" then
return RomText.plain("OtherText_PokeName", {stringVars = {[1] = tostring(speciesName or "")}})
end
-- pokeemerald/src/text.c:972
return tostring(speciesName or "") .. RomText.plain("gText_PkmnsNickname", { stringVars = {} })
return Naming.nameInto("gText_PkmnsNickname", speciesName)
end
-- pret sKeyboardChars + sPageColumnXPos (cursor).
@@ -0,0 +1,47 @@
-- The wild/foe word joined to a foe's name: the US and Japanese rows go before
-- it, the European carts write theirs to follow it and append it (pret
-- pokeemerald multi-language, src/battle_message.c:4042, :4649).
package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
local eq = T.eq
local RomText = require("src.core.game3.rom_text")
local BattleText = require("src.core.game3.battle.battle_text")
local State = require("src.core.game3.battle.state")
local saved = {}
local function set(key, ir)
if saved[key] == nil then saved[key] = RomText.overrides[key] or false end
RomText.overrides[key] = ir
end
local function text(s) return { { t = "text", s = s }, { t = "eos" } } end
-- pokeemerald multi-language src/battle_message.c:4042
eq(BattleText.withMonPrefix("Wild ", "ZIGZAGOON"), "Wild ZIGZAGOON", "US: the wild word comes first")
eq(BattleText.withMonPrefix("やせいの ", "ジグザグマ"), "やせいの ジグザグマ", "Japanese: the wild word comes first")
eq(BattleText.withMonPrefix(" sauvage", "ZIGZATON"), "ZIGZATON sauvage", "French: the wild word follows the name")
eq(BattleText.withMonPrefix(" (Wild)", "ZIGZACHS"), "ZIGZACHS (Wild)", "German: the wild word follows the name")
set("sText_WildPkmnPrefix", text(" sauvage"))
set("sText_FoePkmnPrefix", text(" ennemi"))
set("sText_TestAttackerUses", { { t = "bph", code = 0x0F }, { t = "text", s = " utilise" }, { t = "eos" } })
eq(BattleText.get("sText_TestAttackerUses", { atk = { name = "ZIGZATON", side = "opponent" } }),
"ZIGZATON sauvage utilise", "B_ATK_NAME_WITH_PREFIX puts the French wild word after the name")
eq(BattleText.get("sText_TestAttackerUses", { atk = { name = "ZIGZATON", side = "opponent" }, trainer = true }),
"ZIGZATON ennemi utilise", "and the foe word against a trainer")
eq(BattleText.get("sText_TestAttackerUses", { atk = { name = "POUSSIFEU", side = "player" } }),
"POUSSIFEU utilise", "the player's own POKéMON has no prefix")
-- src/battle_message.c:4649 B_BUFF_MON_NICK_WITH_PREFIX
eq(State.prefixedName({ wild = true }, { side = "opponent" }, "ZIGZATON"), "ZIGZATON sauvage",
"a buffered name takes the same order")
eq(State.prefixedName({ wild = false }, { side = "opponent" }, "ZIGZATON"), "ZIGZATON ennemi",
"a buffered foe name too")
set("sText_WildPkmnPrefix", text("Wild "))
eq(State.prefixedName({ wild = true }, { side = "opponent" }, "ZIGZAGOON"), "Wild ZIGZAGOON",
"the US row still goes first")
for key, ir in pairs(saved) do RomText.overrides[key] = ir or nil end
T.finish("game3_european_mon_prefix_test")
@@ -0,0 +1,50 @@
-- A name joined to a cart string the US code appends after it: the European
-- carts put it in the string's STR_VAR_1 (pret pokeemerald multi-language,
-- src/naming_screen.c:1746, src/secret_base.c:732).
package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
local eq = T.eq
local RomText = require("src.core.game3.rom_text")
local Naming = require("src.ui.game3.naming")
local SB = require("src.core.game3.rse.secret_base")
local saved = {}
local function set(key, ir)
if saved[key] == nil then saved[key] = RomText.overrides[key] or false end
RomText.overrides[key] = ir
end
local function text(s) return { { t = "text", s = s }, { t = "eos" } } end
-- src/naming_screen.c:1746
set("gText_PkmnsNickname", text("'s nickname?"))
eq(Naming.monTitle("BULBASAUR"), "BULBASAUR's nickname?", "FireRed's US row has no placeholder: the name goes first")
set("gText_PkmnsNickname", { { t = "strvar", n = 1 }, { t = "text", s = "'s nickname?" }, { t = "eos" } })
eq(Naming.monTitle("MUDKIP"), "MUDKIP's nickname?", "Emerald's US row prints the same")
set("gText_PkmnsNickname", { { t = "text", s = "Surnom de " }, { t = "strvar", n = 1 }, { t = "text", s = "?" }, { t = "eos" } })
eq(Naming.monTitle("POUSSIFEU"), "Surnom de POUSSIFEU?", "French: the species fills the row's STR_VAR_1")
eq(Naming.monTitle(nil), "Surnom de ?", "a missing name leaves the placeholder empty")
set("gText_PkmnsNickname", text(" の ニックネームは?"))
eq(Naming.monTitle("フシギダネ"), "フシギダネ の ニックネームは?", "FireRed's Japanese row has no placeholder either")
-- a mod's catalog entry for the English source: the name still lands in it
local Strings = require("src.core.Strings")
set("gText_PkmnsNickname", { { t = "strvar", n = 1 }, { t = "text", s = "'s nickname?" }, { t = "eos" } })
Strings.load({ strings = { ["%s's nickname?"] = "Surnom de %s?" } })
eq(Naming.monTitle("MUDKIP"), "Surnom de MUDKIP?", "a catalog translation takes the name in its %s")
Strings.load(nil)
-- src/secret_base.c:732
local sess = { secretBases = { { trainerName = "SACHA" } } }
set("gText_ApostropheSBase", text("'s BASE"))
eq(SB.name(0, sess), "SACHA's BASE", "US: the row follows the owner")
set("gText_ApostropheSBase", text(" きち"))
eq(SB.name(0, sess), "SACHA きち", "Japanese: the same")
set("gText_ApostropheSBase", { { t = "text", s = "BASE DE " }, { t = "strvar", n = 1 }, { t = "eos" } })
eq(SB.name(0, sess), "BASE DE SACHA", "French: the owner fills the row's STR_VAR_1")
for key, ir in pairs(saved) do RomText.overrides[key] = ir or nil end
T.finish("game3_european_name_placeholder_test")
@@ -0,0 +1,48 @@
-- A two-stage stat change: the European "sharply"/"harshly" rows carry the
-- whole change, and the carts skip the plain string after them (pret
-- pokeemerald multi-language, src/battle_message.c:4617).
package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
local eq = T.eq
local RomText = require("src.core.game3.rom_text")
local Secondary = require("src.core.game3.battle.effects.secondary")
local saved = {}
local function set(key, ir)
if saved[key] == nil then saved[key] = RomText.overrides[key] or false end
RomText.overrides[key] = ir
end
local function text(s) return { { t = "text", s = s }, { t = "eos" } } end
-- src/battle_message.c:4617
set("STRINGID_STATSHARPLY", text("sharply "))
set("STRINGID_STATROSE", text("rose!"))
eq(Secondary.sharpChange("STRINGID_STATSHARPLY", "STRINGID_STATROSE"), "sharply rose!",
"US: the sharply row runs on into the plain change")
set("STRINGID_STATSHARPLY", text("ぐーんと "))
set("STRINGID_STATROSE", text("あがった!"))
eq(Secondary.sharpChange("STRINGID_STATSHARPLY", "STRINGID_STATROSE"), "ぐーんと あがった!",
"Japanese: the same")
set("STRINGID_STATSHARPLY", text("monte beaucoup!"))
set("STRINGID_STATROSE", text("augmente!"))
eq(Secondary.sharpChange("STRINGID_STATSHARPLY", "STRINGID_STATROSE"), "monte beaucoup!",
"French: the sharply row is the whole change")
set("STRINGID_STATHARSHLY", text("harshly "))
set("STRINGID_STATFELL", text("fell!"))
eq(Secondary.sharpChange("STRINGID_STATHARSHLY", "STRINGID_STATFELL"), "harshly fell!",
"US: the harshly row runs on into the plain fall")
set("STRINGID_STATHARSHLY", text("baisse beaucoup!"))
set("STRINGID_STATFELL", text("baisse!"))
eq(Secondary.sharpChange("STRINGID_STATHARSHLY", "STRINGID_STATFELL"), "baisse beaucoup!",
"French: the harshly row is the whole fall")
set("STRINGID_STATSHARPLY", text("subió mucho"))
set("STRINGID_STATROSE", text("subió"))
eq(Secondary.sharpChange("STRINGID_STATSHARPLY", "STRINGID_STATROSE"), "subió mucho",
"Spanish: a row without an exclamation mark is the whole change too")
for key, ir in pairs(saved) do RomText.overrides[key] = ir or nil end
T.finish("game3_european_stat_change_test")