diff --git a/src/core/game3/battle/battle_text.lua b/src/core/game3/battle/battle_text.lua index f050fe45..6d37c9f5 100644 --- a/src/core/game3/battle/battle_text.lua +++ b/src/core/game3/battle/battle_text.lua @@ -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) diff --git a/src/core/game3/battle/effects/secondary.lua b/src/core/game3/battle/effects/secondary.lua index 2deb8889..e0e72b03 100644 --- a/src/core/game3/battle/effects/secondary.lua +++ b/src/core/game3/battle/effects/secondary.lua @@ -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 diff --git a/src/core/game3/battle/held_items.lua b/src/core/game3/battle/held_items.lua index 51073e71..bb0230dd 100644 --- a/src/core/game3/battle/held_items.lua +++ b/src/core/game3/battle/held_items.lua @@ -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, }) diff --git a/src/core/game3/battle/state.lua b/src/core/game3/battle/state.lua index 6524b8f5..d8031959 100644 --- a/src/core/game3/battle/state.lua +++ b/src/core/game3/battle/state.lua @@ -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) diff --git a/src/core/game3/rse/secret_base.lua b/src/core/game3/rse/secret_base.lua index e2931b08..73dfbdcd 100644 --- a/src/core/game3/rse/secret_base.lua +++ b/src/core/game3/rse/secret_base.lua @@ -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 diff --git a/src/ui/game3/naming.lua b/src/ui/game3/naming.lua index 67d9bec9..38924459 100644 --- a/src/ui/game3/naming.lua +++ b/src/ui/game3/naming.lua @@ -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). diff --git a/tests/engine/game3_european_mon_prefix_test.lua b/tests/engine/game3_european_mon_prefix_test.lua new file mode 100644 index 00000000..60faa070 --- /dev/null +++ b/tests/engine/game3_european_mon_prefix_test.lua @@ -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") diff --git a/tests/engine/game3_european_name_placeholder_test.lua b/tests/engine/game3_european_name_placeholder_test.lua new file mode 100644 index 00000000..0971b06d --- /dev/null +++ b/tests/engine/game3_european_name_placeholder_test.lua @@ -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") diff --git a/tests/engine/game3_european_stat_change_test.lua b/tests/engine/game3_european_stat_change_test.lua new file mode 100644 index 00000000..64aefd6e --- /dev/null +++ b/tests/engine/game3_european_stat_change_test.lua @@ -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")