Merge pull request #1439 from thibautbus/fix/stat-rise-message-translation

This commit is contained in:
bryanthaboi
2026-08-16 20:43:54 -04:00
committed by GitHub
4 changed files with 165 additions and 5 deletions
+10 -1
View File
@@ -33,6 +33,15 @@ end
local HEAL_AMOUNT = { POTION = 20, SUPER_POTION = 50, HYPER_POTION = 200 }
local X_STAT = { X_ATTACK = "attack", X_DEFEND = "defense", X_SPEED = "speed" }
-- Strings.source, not Strings: harvested at require time so the catalog
-- generator can see the literal, same pattern as MoveEffects.lua's
-- STAT_LABEL (#811) -- Strings(stat:upper()) alone is a dynamic argument
-- the harvester can't discover.
local STAT_LABEL = {
attack = Strings.source("ATTACK"), defense = Strings.source("DEFENSE"),
speed = Strings.source("SPEED"),
}
-- The trainer's ai_classes record from the merged registry; the direct
-- require covers battles built without a loader. A trainer record's
-- aiClass field picks a record other than its own id.
@@ -124,7 +133,7 @@ function TrainerAI.useItem(battle, item)
elseif X_STAT[item] then
local stat = X_STAT[item]
enemy.stages[stat] = math.min(6, (enemy.stages[stat] or 0) + 1)
table.insert(msgs, Strings("%s's\n%s rose!", displayName(enemy), stat:upper()))
table.insert(msgs, Strings("%s's\n%s rose!", displayName(enemy), Strings(STAT_LABEL[stat])))
elseif item == "GUARD_SPEC" then
enemy.mist = true
table.insert(msgs, Strings("%s's\nprotected against\nstat changes!", displayName(enemy)))
+2 -2
View File
@@ -2429,7 +2429,7 @@ Battle.MOVE_EFFECTS.EFFECT_LIGHT_SCREEN = function(self, attacker)
if (side.lightScreen or 0) > 0 then return fail(self) end
side.lightScreen = Battle.SCREEN_TURNS
self:emit({ kind = "message",
text = self:monName(attacker) .. "'s SPCL.DEF rose!" })
text = Strings("%s's SPCL.DEF rose!", self:monName(attacker)) })
end
Battle.MOVE_EFFECTS.EFFECT_REFLECT = function(self, attacker)
@@ -2437,7 +2437,7 @@ Battle.MOVE_EFFECTS.EFFECT_REFLECT = function(self, attacker)
if (side.reflect or 0) > 0 then return fail(self) end
side.reflect = Battle.SCREEN_TURNS
self:emit({ kind = "message",
text = self:monName(attacker) .. "'s DEFENSE rose!" })
text = Strings("%s's DEFENSE rose!", self:monName(attacker)) })
end
-- engine/battle/move_effects/safeguard.asm:1
+12 -2
View File
@@ -59,6 +59,16 @@ local STONES = {
LEAF_STONE = true, MOON_STONE = true,
}
-- Strings.source, not Strings: harvested at require time so the catalog
-- generator can see the literal, same pattern as MoveEffects.lua's
-- STAT_LABEL (#811) -- Strings(stat:upper()) alone is a dynamic argument
-- the harvester can't discover.
local STAT_LABEL = {
hp = Strings.source("HP"), attack = Strings.source("ATTACK"),
defense = Strings.source("DEFENSE"), speed = Strings.source("SPEED"),
special = Strings.source("SPECIAL"), accuracy = Strings.source("ACCURACY"),
}
-- vitamins: stat-exp boosters (ItemUseVitamin)
local VITAMINS = { HP_UP = "hp", PROTEIN = "attack", IRON = "defense",
CARBOS = "speed", CALCIUM = "special" }
@@ -294,7 +304,7 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
"Nothing happened!") }
end
b.stages[stat] = cur + 1
return "consumed", { Strings("%s's\n%s rose!", b.name, stat:upper()) }
return "consumed", { Strings("%s's\n%s rose!", b.name, Strings(STAT_LABEL[stat])) }
end
-- ItemUseDireHit/ItemUseGuardSpec always set the bit and consume
-- the item, even when it is already active
@@ -493,7 +503,7 @@ function ItemEffects.use(data, save, itemId, target, battle, moveIndex, ow)
-- Spanish ROM puts the stat before the name), so the extracted line
-- cannot be filled positionally; the engine wording stands
return "consumed", { Strings("%s's %s\nrose!", monName(data, target),
vitaminStat == "hp" and "HP" or vitaminStat:upper()) }
Strings(STAT_LABEL[vitaminStat])) }
end
-- PP UP boosts the move the player picked (ItemUsePPUp's move menu)
@@ -0,0 +1,141 @@
-- The stat name substituted into X-item/vitamin "rose!" messages, and
-- Gold's whole Light Screen / Reflect "rose!" messages, must reach a
-- translation catalog, not just the surrounding sentence template (RBY:
-- src/inventory/ItemEffects.lua, src/battle/TrainerAI.lua; Gold:
-- src/battle/gen2/Battle.lua). With no catalog loaded the message stays
-- English (the existing baseline); with one loaded that translates the
-- relevant word(s), the substitution must change too -- that is the
-- actual bug this suite guards against, which passing/failing sentences
-- alone (as other suites already check) cannot tell apart from text that
-- never reached Strings() at all.
package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.modkit")
local Data = T.fixtures.fresh()
local Pokemon = require("src.pokemon.Pokemon")
local SaveData = require("src.core.SaveData")
local ItemEffects = require("src.inventory.ItemEffects")
local TrainerAI = require("src.battle.TrainerAI")
local Strings = require("src.core.Strings")
local function withCatalog(catalog, fn)
Strings.load({ strings = catalog })
local ok, err = pcall(fn)
Strings.load(nil)
if not ok then error(err, 0) end
end
-- ------------------------------------------------------- player X-item
local save = SaveData.newGame()
local player = { name = "FIXMON", stages = {} }
local xBattle = { player = player, kind = "wild" }
local _, baseline = ItemEffects.use(Data, save, "X_ATTACK", nil, xBattle)
T.check(baseline[1]:find("ATTACK", 1, true) ~= nil,
"X ATTACK's rose! message names the stat in English with no catalog")
withCatalog({ ATTACK = "ATTAQUE" }, function()
player.stages.attack = nil
local _, msgs = ItemEffects.use(Data, save, "X_ATTACK", nil, xBattle)
T.check(msgs[1]:find("ATTAQUE", 1, true) ~= nil,
"a catalog translating ATTACK reaches the X ATTACK rose! message")
T.check(msgs[1]:find("ATTACK", 1, true) == nil,
"...and the untranslated English stat name is gone")
end)
-- --------------------------------------------------------- player vitamin
local target = Pokemon.new(Data, "FIXMON_A", 10)
withCatalog({ DEFENSE = "DEFENSE_FR" }, function()
local _, msgs = ItemEffects.use(Data, save, "IRON", target)
T.check(msgs[1]:find("DEFENSE_FR", 1, true) ~= nil,
"a catalog translating DEFENSE reaches the IRON (vitamin) rose! message")
end)
local hpTarget = Pokemon.new(Data, "FIXMON_A", 10)
withCatalog({ HP = "PV" }, function()
local _, msgs = ItemEffects.use(Data, save, "HP_UP", hpTarget)
T.check(msgs[1]:find("PV", 1, true) ~= nil,
"a catalog translating HP reaches the HP UP rose! message")
end)
-- ------------------------------------------------------- AI trainer X-item
local enemy = { name = "FOE", stages = {} }
local aiBattle = { enemy = enemy, trainer = { name = "TRAINER" }, data = Data }
withCatalog({ SPEED = "VITESSE" }, function()
local msgs = TrainerAI.useItem(aiBattle, "X_SPEED")
T.check(msgs[2]:find("VITESSE", 1, true) ~= nil,
"a catalog translating SPEED reaches the AI trainer's X SPEED rose! message")
end)
-- ------------------------------------------------------- Gold: Light Screen / Reflect
local Gen2Battle = require("src.battle.gen2.Battle")
local Gen2Mon = require("src.battle.gen2.Mon")
local GEN2_DATA = {
pokemon = {
MACHOP = {
id = "MACHOP", index = 66, name = "MACHOP",
baseStats = { hp = 70, attack = 80, defense = 50, speed = 35,
specialAttack = 35, specialDefense = 35 },
types = { "NORMAL", "NORMAL" }, catchRate = 180, baseExp = 75,
growthRate = "GROWTH_MEDIUM_FAST", genderRatio = 63,
levelMoves = { { level = 1, move = "TACKLE" } }, evolutions = {},
},
},
moves = {
TACKLE = { id = "TACKLE", name = "TACKLE", power = 35, type = "NORMAL",
accuracy = 95, pp = 35, effect = "EFFECT_NORMAL_HIT" },
},
type_chart = { types = { NORMAL = { id = "NORMAL", index = 0,
category = "physical" } }, matchups = {} },
items = {},
}
local perfectDvs = { attack = 15, defense = 15, speed = 15, special = 15 }
perfectDvs.hp = Gen2Mon.hpDV(perfectDvs)
local function newGen2Battle()
local player = Gen2Mon.new(GEN2_DATA, "MACHOP", 15, { dvs = perfectDvs })
player.moves = { { id = "TACKLE", pp = 35, maxPp = 35 } }
local wild = Gen2Mon.new(GEN2_DATA, "MACHOP", 15, { dvs = perfectDvs })
wild.moves = { { id = "TACKLE", pp = 35, maxPp = 35 } }
return Gen2Battle.new({ data = GEN2_DATA, party = { player }, wild = wild })
end
withCatalog({ ["%s's SPCL.DEF rose!"] = "%s voit sa DEF.SPÉ augmenter !" },
function()
local lsBattle = newGen2Battle()
Gen2Battle.MOVE_EFFECTS.EFFECT_LIGHT_SCREEN(lsBattle, lsBattle.player)
local events = lsBattle:takeEvents()
local found = false
for _, event in ipairs(events) do
if event.kind == "message"
and event.text:find("DEF.SPÉ augmenter", 1, true) then
found = true
end
end
T.check(found,
"a catalog translating Light Screen's rose! message reaches it")
end)
withCatalog({ ["%s's DEFENSE rose!"] = "%s voit sa DEFENSE augmenter !" },
function()
local refBattle = newGen2Battle()
Gen2Battle.MOVE_EFFECTS.EFFECT_REFLECT(refBattle, refBattle.player)
local events = refBattle:takeEvents()
local found = false
for _, event in ipairs(events) do
if event.kind == "message"
and event.text:find("DEFENSE augmenter", 1, true) then
found = true
end
end
T.check(found,
"a catalog translating Reflect's rose! message reaches it")
end)
T.finish("stat rise message translation")