diff --git a/src/core/Game2.lua b/src/core/Game2.lua index 22f6ea14..33dc2f40 100644 --- a/src/core/Game2.lua +++ b/src/core/Game2.lua @@ -550,7 +550,7 @@ function Game2:learnMoveOn(mon, moveId, onDone) end if ok then -- data/text/common_3.asm:119 - return self:say(("%s learned\n%s!"):format(name, moveName), + return self:say(Strings("%s learned\n%s!", name, moveName), function() finish(true) end, TextBox.soundOpts(self, "Sfx_DexFanfare5079")) end @@ -558,15 +558,14 @@ function Game2:learnMoveOn(mon, moveId, onDone) local askForget, pickMove, askStop -- DidNotLearnMoveText, then `ld b, 0` (learn.asm:110-113). local function decline() - self:say(("%s\ndid not learn\v%s."):format(name, moveName), + self:say(Strings("%s\ndid not learn\v%s.", name, moveName), function() finish(false) end) end -- ForgetMove's AskForgetMoveText + YesNoBox (learn.asm:123-127). askForget = function() self.stack:push(TextBox.new(self, - ("%s is\ntrying to learn\v%s.\fBut %s\ncan't learn more\vthan four moves." - .. "\fDelete an older\nmove to make room\vfor %s?") - :format(name, moveName, name, moveName), + Strings("%s is\ntrying to learn\v%s.\fBut %s\ncan't learn more\vthan four moves.\fDelete an older\nmove to make room\vfor %s?", + name, moveName, name, moveName), nil, { choice = function(yes) if yes then return pickMove() end return askStop() @@ -575,7 +574,7 @@ function Game2:learnMoveOn(mon, moveId, onDone) -- StopLearningMoveText, whose NO is `jp c, .loop` (learn.asm:104-108). askStop = function() self.stack:push(TextBox.new(self, - ("Stop learning\n%s?"):format(moveName), nil, + Strings("Stop learning\n%s?", moveName), nil, { choice = function(yes) if yes then return decline() end return askForget() @@ -598,7 +597,7 @@ function Game2:learnMoveOn(mon, moveId, onDone) -- MoveCantForgetHMText, then `jr .loop` (learn.asm:183-197): the -- question stays up and the list comes back over it. if old and HM_MOVES[old.id] then - return self:say("HM moves can't be\nforgotten now.", pushList) + return self:say(Strings("HM moves can't be\nforgotten now."), pushList) end self.stack:pop() -- the question the list stood on local oldDef = (self.data.moves or {})[old and old.id] @@ -608,9 +607,8 @@ function Game2:learnMoveOn(mon, moveId, onDone) -- pokemon.move_learned is raised here too. ModRuntime.emit("pokemon.move_learned", { mon = mon, moveId = moveId }) -- engine/pokemon/learn.asm:225-229, data/text/common_3.asm:165-173 - self:say(("1, 2 and…" .. TextBox.PAUSE .. " Poof!" .. TextBox.PAUSE - .. "\f%s forgot\n%s.\fAnd…\f%s learned\n%s!") - :format(name, oldName, name, moveName), + self:say(Strings("1, 2 and…\1 Poof!\1\f%s forgot\n%s.\fAnd…\f%s learned\n%s!", + name, oldName, name, moveName), function() finish(true) end, TextBox.soundOpts(self, "Sfx_DexFanfare5079", { pauseSounds = { "Sfx_SwitchPokemon" } })) @@ -620,7 +618,7 @@ function Game2:learnMoveOn(mon, moveId, onDone) -- MoveAskForgetText, a `done` text: the box stays while the list stands on -- it (learn.asm:136-137). pickMove = function() - self.stack:push(TextBox.new(self, "Which move should\nbe forgotten?", nil, + self.stack:push(TextBox.new(self, Strings("Which move should\nbe forgotten?"), nil, { stay = { onShown = pushList } })) end askForget() @@ -655,13 +653,13 @@ function Game2:useFieldItem(itemId) if id == moveId then allowed = true end end if not allowed then - self:say(("%s can't learn %s!"):format( + self:say(Strings("%s can't learn %s!", require("src.battle.gen2.Mon").displayName(mon), moveName)) return end for _, move in ipairs(mon.moves or {}) do if move.id == moveId then - self:say(("%s already knows %s!"):format( + self:say(Strings("%s already knows %s!", require("src.battle.gen2.Mon").displayName(mon), moveName)) return end diff --git a/src/core/gen2/ItemEffects.lua b/src/core/gen2/ItemEffects.lua index b0b8a8a6..9ef47adc 100644 --- a/src/core/gen2/ItemEffects.lua +++ b/src/core/gen2/ItemEffects.lua @@ -16,6 +16,7 @@ local Happiness = require("src.core.gen2.Happiness") local Mon = require("src.battle.gen2.Mon") +local Strings = require("src.core.Strings") local ItemEffects = {} @@ -72,26 +73,33 @@ local BITTER = { } -- _ItemWontHaveEffectText / _ItemCantUseOnEggText (data/text/common_3.asm). -ItemEffects.TEXT_NO_EFFECT = "It won't have any\neffect." -ItemEffects.TEXT_CANT_USE_ON_EGG = "That can't be used\non an EGG." +-- Strings.source keeps these in the catalog harvest even though they are +-- declared here and only formatted/looked up at each use site below (#186, +-- #245): every use site was a bare table read or a direct :format() call +-- until now, which meant a mod's translation catalog never had a chance to +-- apply -- every language showed this same English text. +ItemEffects.TEXT_NO_EFFECT = Strings.source("It won't have any\neffect.") +ItemEffects.TEXT_CANT_USE_ON_EGG = Strings.source("That can't be used\non an EGG.") -- _ItemCantUseOnMonText (data/text/common_3.asm:1265). -ItemEffects.TEXT_CANT_USE_ON_MON = "That can't be used\non this #MON." +ItemEffects.TEXT_CANT_USE_ON_MON = Strings.source("That can't be used\non this #MON.") -- _PPRestoredText (data/text/common_3.asm). -ItemEffects.TEXT_PP_RESTORED = "PP was restored." +ItemEffects.TEXT_PP_RESTORED = Strings.source("PP was restored.") -- _PPIsMaxedOutText / _PPsIncreasedText (data/text/common_3.asm). -ItemEffects.TEXT_PP_MAXED = "%s's PP\nis maxed out." -ItemEffects.TEXT_PP_INCREASED = "%s's PP\nincreased." +ItemEffects.TEXT_PP_MAXED = Strings.source("%s's PP\nis maxed out.") +ItemEffects.TEXT_PP_INCREASED = Strings.source("%s's PP\nincreased.") -- PrintPartyMenuActionText's .MenuActionTexts (engine/pokemon/party_menu.asm), -- keyed by the class GetItemHealingAction resolves. Each is the two rows the --- cart prints: the nickname line, then the fixed line. +-- cart prints: the nickname line, then the fixed line. Strings.source for +-- the same reason as the constants above: healStatus() below formats these +-- directly at each use, so without it a mod's catalog never sees them. local STATUS_TEXT = { - psn = "%s's\ncured of poison.", - par = "%s's\nrid of paralysis.", - brn = "%s's\nburn was healed.", - frz = "%s\nwas defrosted.", - slp = "%s\nwoke up.", - all = "%s's\nhealth returned.", + psn = Strings.source("%s's\ncured of poison."), + par = Strings.source("%s's\nrid of paralysis."), + brn = Strings.source("%s's\nburn was healed."), + frz = Strings.source("%s\nwas defrosted."), + slp = Strings.source("%s\nwoke up."), + all = Strings.source("%s's\nhealth returned."), } -- The port's party records spell status several ways (the battle writes the @@ -145,7 +153,7 @@ local function restoreHp(itemId, mon) local amount = ItemEffects.HEAL_HP[itemId] local maxHp = maxHpOf(mon) if fainted(mon) or (mon.hp or 0) >= maxHp then - return { used = false, text = ItemEffects.TEXT_NO_EFFECT } + return { used = false, text = Strings(ItemEffects.TEXT_NO_EFFECT) } end local healed = math.min(maxHp, (mon.hp or 0) + amount) local gained = healed - (mon.hp or 0) @@ -157,7 +165,7 @@ local function restoreHp(itemId, mon) used = true, -- data/text/common_1.asm:30 -- home/text.asm:772 - text = ("%s\nrecovered %dHP!"):format(monName(mon), gained), + text = Strings("%s\nrecovered %dHP!", monName(mon), gained), } end @@ -181,23 +189,23 @@ end -- confusion arm reads wPlayerSubStatus3, which does not exist out of battle. local function healStatus(itemId, mon, class, data) if fainted(mon) then - return { used = false, text = ItemEffects.TEXT_NO_EFFECT } + return { used = false, text = Strings(ItemEffects.TEXT_NO_EFFECT) } end local have = ItemEffects.healClassOf(mon.status, data) if not have or (class ~= "all" and have ~= class) then - return { used = false, text = ItemEffects.TEXT_NO_EFFECT } + return { used = false, text = Strings(ItemEffects.TEXT_NO_EFFECT) } end clearStatus(mon) bitterHappiness(itemId, mon) local shape = STATUS_TEXT[class == "all" and "all" or have] - return { used = true, text = shape:format(monName(mon)) } + return { used = true, text = Strings(shape, monName(mon)) } end -- RevivePokemon: only a fainted mon accepts; REVIVE stands it up at half max -- HP (ReviveHalfHP's `srl d / rr e`), the other two at full. local function revive(itemId, mon) if not fainted(mon) then - return { used = false, text = ItemEffects.TEXT_NO_EFFECT } + return { used = false, text = Strings(ItemEffects.TEXT_NO_EFFECT) } end local maxHp = maxHpOf(mon) mon.hp = (ItemEffects.REVIVE[itemId] == "half") @@ -206,7 +214,7 @@ local function revive(itemId, mon) bitterHappiness(itemId, mon) return { used = true, - text = ("%s\nis revitalized."):format(monName(mon)), + text = Strings("%s\nis revitalized.", monName(mon)), } end @@ -218,7 +226,7 @@ end -- exactly the new level, for the caller to offer. local function rareCandy(mon, data) if (mon.level or 0) >= Mon.MAX_LEVEL then - return { used = false, text = ItemEffects.TEXT_NO_EFFECT } + return { used = false, text = Strings(ItemEffects.TEXT_NO_EFFECT) } end local def = data and data.pokemon and data.pokemon[mon.species] -- through Mon.growthFor, so a growth_rates record a mod registered is the @@ -245,14 +253,18 @@ local function rareCandy(mon, data) learned = learned, -- data/text/common_1.asm:86 sfx = "Sfx_DexFanfare5079", - text = ("%s grew to\nlevel %d!"):format(monName(mon), newLevel), + text = Strings("%s grew to\nlevel %d!", monName(mon), newLevel), } end --- engine/items/item_effects.asm:1216 StatStrings. +-- engine/items/item_effects.asm:1216 StatStrings. Strings.source per entry, +-- the same pattern as the identical stat-name tables in MoveEffects.lua/ +-- TrainerAI.lua/gen2/Effects.lua/ContestMenu.lua/SummaryMenu.lua, so a mod's +-- catalog harvest finds these independently of any other stat-name call site. local VITAMIN_LABEL = { - hp = "HEALTH", attack = "ATTACK", defense = "DEFENSE", - speed = "SPEED", special = "SPECIAL", + hp = Strings.source("HEALTH"), attack = Strings.source("ATTACK"), + defense = Strings.source("DEFENSE"), speed = Strings.source("SPEED"), + special = Strings.source("SPECIAL"), } -- engine/items/item_effects.asm:1149 VitaminEffect. @@ -261,7 +273,7 @@ local function vitamin(itemId, mon, data) mon.statExp = mon.statExp or Mon.newStatExp() local cur = mon.statExp[stat] or 0 if cur >= 25600 then - return { used = false, text = ItemEffects.TEXT_NO_EFFECT } + return { used = false, text = Strings(ItemEffects.TEXT_NO_EFFECT) } end mon.statExp[stat] = math.min(Mon.MAX_STAT_EXP, cur + 2560) local def = data and data.pokemon and data.pokemon[mon.species] @@ -272,7 +284,7 @@ local function vitamin(itemId, mon, data) Happiness.change(mon, "USEDITEM") return { used = true, - text = ("%s's\n%s rose."):format(monName(mon), VITAMIN_LABEL[stat]), + text = Strings("%s's\n%s rose.", monName(mon), Strings(VITAMIN_LABEL[stat])), } end @@ -392,15 +404,15 @@ end -- The one-call families (everything but PP, which needs a move pick first). -- Returns { used, text, learned?, level? }. function ItemEffects.useOnMon(itemId, mon, data) - if not mon then return { used = false, text = ItemEffects.TEXT_NO_EFFECT } end + if not mon then return { used = false, text = Strings(ItemEffects.TEXT_NO_EFFECT) } end if mon.isEgg then - return { used = false, text = ItemEffects.TEXT_CANT_USE_ON_EGG } + return { used = false, text = Strings(ItemEffects.TEXT_CANT_USE_ON_EGG) } end local record = ItemEffects.recordFor(itemId, data) -- The PP family has its own entry point; reaching it here is the same -- "nothing happens" the unported items get. if not record or not record.use or record.action == "pp" then - return { used = false, text = ItemEffects.TEXT_NO_EFFECT } + return { used = false, text = Strings(ItemEffects.TEXT_NO_EFFECT) } end return record.use({ item = itemId, mon = mon, data = data }) end @@ -423,13 +435,13 @@ end -- the ELIXER family (Elixer_RestorePPofAllMoves) walks every slot and counts -- -- one restored move is enough for the item to be spent. function ItemEffects.usePpItem(itemId, mon, slot, data) - if not mon then return { used = false, text = ItemEffects.TEXT_NO_EFFECT } end + if not mon then return { used = false, text = Strings(ItemEffects.TEXT_NO_EFFECT) } end if mon.isEgg then - return { used = false, text = ItemEffects.TEXT_CANT_USE_ON_EGG } + return { used = false, text = Strings(ItemEffects.TEXT_CANT_USE_ON_EGG) } end local record = ItemEffects.recordFor(itemId, data) if not record or not record.use or record.action ~= "pp" then - return { used = false, text = ItemEffects.TEXT_NO_EFFECT } + return { used = false, text = Strings(ItemEffects.TEXT_NO_EFFECT) } end return record.use({ item = itemId, mon = mon, data = data, slot = slot }) end @@ -478,9 +490,9 @@ for itemId, row in pairs(ItemEffects.RESTORE_PP) do any = restoreMove(moves[ctx.slot], row.amount) end if not any then - return { used = false, text = ItemEffects.TEXT_NO_EFFECT } + return { used = false, text = Strings(ItemEffects.TEXT_NO_EFFECT) } end - return { used = true, text = ItemEffects.TEXT_PP_RESTORED } + return { used = true, text = Strings(ItemEffects.TEXT_PP_RESTORED) } end) end @@ -488,24 +500,24 @@ end record("PP_UP", "pp", function(ctx) local move = (ctx.mon.moves or {})[ctx.slot] if type(move) ~= "table" or not move.id then - return { used = false, text = ItemEffects.TEXT_NO_EFFECT } + return { used = false, text = Strings(ItemEffects.TEXT_NO_EFFECT) } end local row = ((ctx.data and ctx.data.moves) or {})[move.id] local name = (row and row.name) or move.id -- constants/pokemon_data_constants.asm:216 PP_UP_MASK. if move.id == "SKETCH" or (move.ppUps or 0) >= 3 then - return { used = false, text = ItemEffects.TEXT_PP_MAXED:format(name) } + return { used = false, text = Strings(ItemEffects.TEXT_PP_MAXED, name) } end local base = (row and row.pp) or move.maxPp if not base then - return { used = false, text = ItemEffects.TEXT_PP_MAXED:format(name) } + return { used = false, text = Strings(ItemEffects.TEXT_PP_MAXED, name) } end -- engine/items/item_effects.asm:2736 ComputeMaxPP. local bonus = math.min(math.floor(base / 5), 7) move.ppUps = (move.ppUps or 0) + 1 move.maxPp = base + move.ppUps * bonus move.pp = (move.pp or 0) + bonus - return { used = true, text = ItemEffects.TEXT_PP_INCREASED:format(name) } + return { used = true, text = Strings(ItemEffects.TEXT_PP_INCREASED, name) } end) for itemId in pairs(ItemEffects.VITAMIN) do @@ -522,12 +534,12 @@ for _, itemId in ipairs({ "SUN_STONE", "MOON_STONE", "FIRE_STONE", "THUNDERSTONE", "WATER_STONE", "LEAF_STONE" }) do record(itemId, "stone", function(ctx) if ctx.mon.item == "EVERSTONE" then - return { used = false, text = ItemEffects.TEXT_NO_EFFECT } + return { used = false, text = Strings(ItemEffects.TEXT_NO_EFFECT) } end local Evolution = require("src.core.gen2.Evolution") local entry = Evolution.checkMon(ctx.data, ctx.mon, { force = true, item = ctx.item }) - if not entry then return { used = false, text = ItemEffects.TEXT_NO_EFFECT } end + if not entry then return { used = false, text = Strings(ItemEffects.TEXT_NO_EFFECT) } end return { used = true, evolution = entry } end) end diff --git a/src/ui/gen2/BattleState.lua b/src/ui/gen2/BattleState.lua index 328abbf5..9df01039 100644 --- a/src/ui/gen2/BattleState.lua +++ b/src/ui/gen2/BattleState.lua @@ -3860,7 +3860,7 @@ function BattleState:useItem(itemId) if not ok then -- _ItemWontHaveEffectText's own `line` break, the same one -- ItemEffects.TEXT_NO_EFFECT carries (data/text/common_3.asm). - self.message = ItemEffects.TEXT_NO_EFFECT + self.message = Strings(ItemEffects.TEXT_NO_EFFECT) self.messageTimer = MESSAGE_FRAMES self.phase = "resolving" return @@ -3965,7 +3965,7 @@ function BattleState:cureBattleConfusion(itemId) local mon = self.battle.player local state = mon and self.battle:volatile(mon) if not (state and state.confuseCount) then - self.message = oneLine(ItemEffects.TEXT_NO_EFFECT) + self.message = oneLine(Strings(ItemEffects.TEXT_NO_EFFECT)) self.messageTimer = MESSAGE_FRAMES self.phase = "resolving" return diff --git a/src/ui/gen2/BattleTowerMenu.lua b/src/ui/gen2/BattleTowerMenu.lua index b47c0178..a121c249 100644 --- a/src/ui/gen2/BattleTowerMenu.lua +++ b/src/ui/gen2/BattleTowerMenu.lua @@ -76,7 +76,7 @@ function BattleTowerMenu.new(game, opts) -- `ld a, $1 / ld [wcd4f], a` (../pokecrystal/mobile/mobile_46.asm:1152-1153) self.cursor = 1 self.phase = "pick" - self.message = PICK_TEXT + self.message = Strings(PICK_TEXT) return self end @@ -116,17 +116,17 @@ function BattleTowerMenu:confirm() if not row then -- ../pokecrystal/mobile/mobile_46.asm:1291-1303 `.asm_118a3c` self.phase = "quit" - self.message = QUIT_TEXT + self.message = Strings(QUIT_TEXT) self.yes = true return end if BattleTower.levelCheck(self.party, row.group) then - return self:refuse(TOPS_TEXT) + return self:refuse(Strings(TOPS_TEXT)) end local uber = BattleTower.ubersCheck(self.party, row.group) if uber then local name = (self.monName and self.monName(uber)) or uber - return self:refuse(string.format(UBER_TEXT, name)) + return self:refuse(Strings(UBER_TEXT, name)) end self:finish(row.group) end @@ -147,7 +147,7 @@ function BattleTowerMenu:updatePick() elseif input:wasPressed("b") then self:playSfx("Sfx_ReadText2") self.phase = "quit" - self.message = QUIT_TEXT + self.message = Strings(QUIT_TEXT) self.yes = true end end @@ -165,12 +165,12 @@ function BattleTowerMenu:updateQuit() self:playSfx("Sfx_ReadText2") if self.yes then return self:finish(nil) end self.phase = "pick" - self.message = PICK_TEXT + self.message = Strings(PICK_TEXT) self.cursor = 1 elseif input:wasPressed("b") then self:playSfx("Sfx_ReadText2") self.phase = "pick" - self.message = PICK_TEXT + self.message = Strings(PICK_TEXT) self.cursor = 1 end end @@ -181,7 +181,7 @@ function BattleTowerMenu:update(_dt) self.wait = (self.wait or 0) - 1 if self.wait > 0 then return end self.phase = "pick" - self.message = PICK_TEXT + self.message = Strings(PICK_TEXT) self.cursor = 1 return end @@ -198,15 +198,15 @@ function BattleTowerMenu:drawPanel() end if self.phase == "quit" then Chrome.box(YN_X, YN_Y, YN_W, YN_H) - Chrome.print(YES_LABEL, YN_TEXT_X, YES_Y) - Chrome.print(NO_LABEL, YN_TEXT_X, NO_Y) + Chrome.print(Strings(YES_LABEL), YN_TEXT_X, YES_Y) + Chrome.print(Strings(NO_LABEL), YN_TEXT_X, NO_Y) Chrome.cursor(YN_TEXT_X - 1, self.yes and YES_Y or NO_Y) love.graphics.setColor(1, 1, 1, 1) return end Chrome.box(PICK_X, PICK_Y, PICK_W, PICK_H) local row = self.rows[self.cursor] - Chrome.print(row and BattleTowerMenu.levelLabel(row.group) or CANCEL_LABEL, + Chrome.print(row and BattleTowerMenu.levelLabel(row.group) or Strings(CANCEL_LABEL), ROW_X, ROW_Y) Chrome.print(UP_ARROW, ARROW_X, UP_Y) Chrome.print(DOWN_ARROW, ARROW_X, DOWN_Y) diff --git a/src/ui/gen2/PartyMenu.lua b/src/ui/gen2/PartyMenu.lua index d7180f27..025a9ff3 100644 --- a/src/ui/gen2/PartyMenu.lua +++ b/src/ui/gen2/PartyMenu.lua @@ -396,7 +396,7 @@ function PartyMenu:finishSoftboiled() local before, after = FieldMoves.softboiledTransfer(user, target, self.softboiledCost or 0) if not before then - self:showItemResult(slot, { text = ItemEffects.TEXT_CANT_USE_ON_MON }) + self:showItemResult(slot, { text = Strings(ItemEffects.TEXT_CANT_USE_ON_MON) }) return end self.softboiledFrom, self.softboiledCost = nil, nil