diff --git a/src/core/game3/battle/adapter.lua b/src/core/game3/battle/adapter.lua index 16557dde..1564a25b 100644 --- a/src/core/game3/battle/adapter.lua +++ b/src/core/game3/battle/adapter.lua @@ -494,8 +494,8 @@ function Adapter.new(battleState, sayFn) if id and id > 0 then if ABILITY_BY_ID[id] then return ABILITY_BY_ID[id] end local ok, Pokemon = pcall(require, "src.core.game3.pokemon") - if ok and Pokemon and Pokemon.abilityName then - local n = Pokemon.abilityName(id) + if ok and Pokemon and Pokemon.romAbilityName then + local n = Pokemon.romAbilityName(id) if n and n ~= "" and not n:match("^ABILITY") then return (tostring(n):upper():gsub("%s+", "_")) end diff --git a/src/core/game3/battle/facility_arena.lua b/src/core/game3/battle/facility_arena.lua index 422d370f..44d2c8f2 100644 --- a/src/core/game3/battle/facility_arena.lua +++ b/src/core/game3/battle/facility_arena.lua @@ -228,7 +228,8 @@ local function fill(st, extra) end function Fac:refText(st, which, extra) - local ir = Data.arena().refereeStrings[which + 1] + local RomText = require("src.core.game3.rom_text") + local ir = RomText.irOr(RomText.key("gRefereeStringsTable", which), Data.arena().refereeStrings[which + 1]) return Data.battleText(ir, fill(st, extra)) end @@ -373,11 +374,32 @@ function Fac:runStep(st, step) return true end +-- pokeemerald/src/battle_arena.c:412 +local JUDGMENT_TEXTS = { + { Fac.WIN.PLAYER_NAME, "gText_PlayerMon1Name", "playerMon1Name" }, + { Fac.WIN.VS, "gText_Vs", "vs" }, + { Fac.WIN.OPPONENT_NAME, "gText_OpponentMon1Name", "opponentMon1Name" }, + { Fac.WIN.MIND, "gText_Mind", "mind" }, + { Fac.WIN.SKILL, "gText_Skill", "skill" }, + { Fac.WIN.BODY, "gText_Body", "body" }, + { Fac.WIN.JUDGMENT_TITLE, "gText_Judgment", "judgment" }, +} + +local function judgmentTexts() + local RomText = require("src.core.game3.rom_text") + local text, out = Data.arena().text, {} + for i, row in ipairs(JUDGMENT_TEXTS) do + out[i] = { win = row[1], ir = RomText.irOr(row[2], text[row[3]]) } + end + return out +end + -- pokeemerald/src/battle_arena.c:395 function Fac:judgeStep(st, step) local j = self.window if step.state == "open" then - self.window = { fade = 0, target = 8, icons = {}, line = true, buffs = { buff1 = "0", buff2 = "0" } } + self.window = { fade = 0, target = 8, icons = {}, line = true, buffs = { buff1 = "0", buff2 = "0" }, + texts = judgmentTexts() } self.fadeWait = true return false elseif step.state == "row" then @@ -519,16 +541,9 @@ function Fac:draw(st) Chrome.userFrame(Chrome._frameType or 0, 6, 1, 18, 12) local W, I = A.windows, A.textInfo local fl = fill(st, j.buffs) - local function put(win, ir) - windowText(W[win + 1], I[win + 1], fontText(ir, fl)) + for _, t in ipairs(j.texts) do + windowText(W[t.win + 1], I[t.win + 1], fontText(t.ir, fl)) end - put(Fac.WIN.PLAYER_NAME, A.text.playerMon1Name) - put(Fac.WIN.VS, A.text.vs) - put(Fac.WIN.OPPONENT_NAME, A.text.opponentMon1Name) - put(Fac.WIN.MIND, A.text.mind) - put(Fac.WIN.SKILL, A.text.skill) - put(Fac.WIN.BODY, A.text.body) - put(Fac.WIN.JUDGMENT_TITLE, A.text.judgment) if j.line then local img = self:iconImage(Fac.ANIM.LINE) for i = 0, 7 do love.graphics.draw(img, 64 + i * 16 - 8, 84 - 8) end diff --git a/src/core/game3/pokemon.lua b/src/core/game3/pokemon.lua index b265086f..d9da4323 100644 --- a/src/core/game3/pokemon.lua +++ b/src/core/game3/pokemon.lua @@ -4,6 +4,7 @@ local PokemonExtract = require("src.import.gba.pokemon_extract") local Versions = require("src.import.gba.versions") local ModRuntime = require("src.mods.Runtime") local CacheBlob = require("src.import.CacheBlob") +local Strings = require("src.core.Strings") local Pokemon = {} @@ -331,7 +332,7 @@ function Pokemon.abilityName(abilityId) if not abilityId or abilityId < 1 then return "-------" end if not Pokemon._abilityNames then Pokemon.install(Pokemon._cache) end local n = Pokemon._abilityNames and Pokemon._abilityNames[abilityId] - if n and n ~= "" then return n end + if n and n ~= "" then return Strings(n) end error("no ROM ability name for ability " .. abilityId, 2) end diff --git a/src/core/game3/profiles/emerald/ui.lua b/src/core/game3/profiles/emerald/ui.lua index fd9eb6b6..9475c252 100644 --- a/src/core/game3/profiles/emerald/ui.lua +++ b/src/core/game3/profiles/emerald/ui.lua @@ -52,6 +52,13 @@ return { -- pokeemerald/src/menu_specialized.c:1503 levelUpStats = { "gText_MaxHP", "gText_Attack", "gText_Defense", "gText_SpAtk", "gText_SpDef", "gText_Speed" }, }, + -- pokeemerald/src/data/party_menu.h:658 + cursorOptionTexts = { + "gText_Summary5", "gText_Switch2", "gText_Cancel2", "gText_Item", "gMenuText_Give", "gText_Take", + "gText_Mail", "gText_Take2", "gText_Read2", "gText_Cancel2", "gText_Shift", "gText_SendOut", + "gText_Enter", "gText_NoEntry", "gText_Store", "gText_Register", "gText_Trade4", "gText_Trade4", + "gMenuText_Toss", + }, -- pokeemerald/src/party_menu.c:2101 buttons = { cancel = "gText_Cancel", confirm = "gMenuText_Confirm" }, -- pokeemerald/src/party_menu.c:2557 diff --git a/src/core/game3/rom_text.lua b/src/core/game3/rom_text.lua index 52f7ac48..1b5ae8f8 100644 --- a/src/core/game3/rom_text.lua +++ b/src/core/game3/rom_text.lua @@ -56,6 +56,16 @@ function RomText.has(key) return cachedText(b, key) ~= nil end +function RomText.irOr(key, fallback) + if key ~= nil and RomText.has(key) then return RomText.ir(key) end + return fallback +end + +function RomText.refIr(ref) + if type(ref) ~= "table" then return nil end + return RomText.irOr(ref.name, RomText.irOr(ref.key, ref.ir)) +end + local SOURCE_FORMS = {} for _, named in ipairs({ false, true }) do for _, nl in ipairs({ "\n", "\\n" }) do diff --git a/src/core/game3/rse/frontier/apprentice.lua b/src/core/game3/rse/frontier/apprentice.lua index 723fba25..535bdbdf 100644 --- a/src/core/game3/rse/frontier/apprentice.lua +++ b/src/core/game3/rse/frontier/apprentice.lua @@ -593,7 +593,7 @@ function Apprentice.message(sess, which) local e = map[which] if not e then return nil end local ref = e[1][id] and e[1][id][e[2]] - return ref and ref.ir + return ref and require("src.core.game3.rom_text").refIr(ref) end -- pokeemerald/src/apprentice.c:564 diff --git a/src/core/game3/rse/frontier/dome.lua b/src/core/game3/rse/frontier/dome.lua index 096e5000..787dc5ae 100644 --- a/src/core/game3/rse/frontier/dome.lua +++ b/src/core/game3/rse/frontier/dome.lua @@ -617,10 +617,16 @@ local function setStr(ctx, adapters, n, v) Util.setStringVar(ctx, adapters, n, v) end +local function tableText(list, name, i) + local RomText = require("src.core.game3.rom_text") + return RomText.irOr(RomText.key(name, i - 1), list[i]) +end +Dome.tableText = tableText + -- pokeemerald/src/battle_dome.c:2564 function Dome.roundText(sess) local f = Util.frontier(sess) - return Data.dome().text.rounds[(tonumber(f.curChallengeBattleNum) or 0) + 1] + return tableText(Data.dome().text.rounds, "gRoundsStringTable", (tonumber(f.curChallengeBattleNum) or 0) + 1) end -- pokeemerald/src/battle_dome.c:3028 @@ -1033,7 +1039,8 @@ function Dome.trainerCard(sess, tid) card.species[i] = facilityMon(sess, f.domeMonIds[tid + 1][i]).species end end - card.potential = M.text.potential[(trainerId == D.TRAINER_FRONTIER_BRAIN) and Dome.TRAINERS_COUNT + 1 or tid + 1] + card.potential = tableText(M.text.potential, "sBattleDomePotentialTexts", + (trainerId == D.TRAINER_FRONTIER_BRAIN) and Dome.TRAINERS_COUNT + 1 or tid + 1) local pts = {} for k = 0, Dome.NUM_MOVE_POINT_TYPES - 1 do pts[k] = 0 end for i = 0, D.PARTY_SIZE - 1 do @@ -1061,8 +1068,8 @@ function Dome.trainerCard(sess, tid) style = style + 1 end card.style = style - card.styleText = M.text.styles[style + 1] - card.statText = M.text.stats[Dome.statTextId(sess, tid) + 1] + card.styleText = tableText(M.text.styles, "sBattleDomeOpponentStyleTexts", style + 1) + card.statText = tableText(M.text.stats, "sBattleDomeOpponentStatsTexts", Dome.statTextId(sess, tid) + 1) return card end diff --git a/src/core/game3/scripting/natives_frontier.lua b/src/core/game3/scripting/natives_frontier.lua index 6a1d5d82..7e9b1b91 100644 --- a/src/core/game3/scripting/natives_frontier.lua +++ b/src/core/game3/scripting/natives_frontier.lua @@ -1,6 +1,7 @@ local Rse = require("src.core.game3.rse.init") local D = require("src.core.game3.rse.frontier.trainers") local Util = require("src.core.game3.rse.frontier.util") +local RomText = require("src.core.game3.rom_text") local NativesFrontier = {} @@ -307,7 +308,7 @@ function NativesFrontier.ssTidalMultichoice(ctx, adapters) local FrlgFont = require("src.ui.game3.frlg_font") for i, id in ipairs(sel) do local ref = D.manifest().ssTidalDestinations[id + 1] - labels[i] = TextIR.toPlain(ref.ir, {}) + labels[i] = TextIR.toPlain(RomText.refIr(ref), {}) local w = FrlgFont.measure(labels[i]) if w > widest then widest = w end end diff --git a/src/core/game3/summary_data.lua b/src/core/game3/summary_data.lua index 6bb5e3cf..8e0e7082 100644 --- a/src/core/game3/summary_data.lua +++ b/src/core/game3/summary_data.lua @@ -416,6 +416,16 @@ function SummaryData.abilityDescription(abilityId, abilityName) return Strings(d.ABILITIES["ABILITY_"]) end +-- pokeemerald/src/data/contest_text_tables.h:220 +function SummaryData.contestEffectDescription(effect) + local text = type(effect) == "table" and effect.description or effect + return Strings(type(text) == "string" and text or "") +end + +function SummaryData.contestCategoryName(name) + return Strings(name or "") +end + function SummaryData.moveDescription(moveId, moveName) local d = get_descriptions() moveName = rom_name("romMoveName", moveId, moveName) diff --git a/src/ui/game3/map_name_popup.lua b/src/ui/game3/map_name_popup.lua index 7319106b..25648907 100644 --- a/src/ui/game3/map_name_popup.lua +++ b/src/ui/game3/map_name_popup.lua @@ -155,7 +155,7 @@ local function rsePrint() local idx = pyramid.location(sess) == pyramid.LOCATION.TOP and #headers or (tonumber(pyramid.frontier(sess).curChallengeBattleNum) or 0) + 1 local ref = headers[idx] - if ref then name = require("src.core.game3.scripting.text_ir").toPlain(ref.ir) end + if ref then name = require("src.core.game3.scripting.text_ir").toPlain(RomText.refIr(ref)) end elseif sec == rseConstants():id("region_map_sections", "MAPSEC_SECRET_BASE") then -- pokeemerald/src/secret_base.c:735 local sb = R.system("secretBase") diff --git a/src/ui/game3/message.lua b/src/ui/game3/message.lua index 7405c8c7..c1b24df5 100644 --- a/src/ui/game3/message.lua +++ b/src/ui/game3/message.lua @@ -41,6 +41,27 @@ end local placeholderCache = {} +-- pokeemerald/src/strings.c:6 +local function cartPlaceholders(extracted) + local Extract = require("src.import.gba.text_placeholders_extract") + local RomText = require("src.core.game3.rom_text") + local function value(name) + local label = Extract.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, 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] } + end + out.byGender = byGender + return out +end +Message.cartPlaceholders = cartPlaceholders + -- pokeemerald/src/string_util.c:456 TextIR.setContextProvider(function(kind, dialect, ctx) if kind == "gender" then @@ -66,13 +87,15 @@ TextIR.setContextProvider(function(kind, dialect, ctx) local GameVersion = require("src.core.GameVersion") local s = liveSession() local id = (s and s.version) or GameVersion.get() or "" + local Sp = package.loaded["src.core.game3.scripting.space"] + local bundle = Sp and Sp.bundle local hit = placeholderCache[id] - if hit then return hit end + if hit and hit.bundle == bundle then return hit.values end 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] = t - return t + placeholderCache[id] = { bundle = bundle, values = cartPlaceholders(t) } + return placeholderCache[id].values end return nil end) diff --git a/src/ui/game3/party_menu.lua b/src/ui/game3/party_menu.lua index a4157498..83cc27a7 100644 --- a/src/ui/game3/party_menu.lua +++ b/src/ui/game3/party_menu.lua @@ -90,7 +90,7 @@ local function gameFieldMoves() index[label] = j - 1 byMove[move] = label end - return { labels = labels, base = base, index = index, byMove = byMove } + return { labels = labels, base = base, index = index, byMove = byMove, moves = moves, texts = p.cursorOptionTexts } end local function fieldMoveIndex() @@ -111,14 +111,32 @@ local function cursor_option_text(act) local p = partyUi() if p and p.actionText then return p.actionText(act, g) end if g then + -- pokeemerald/src/data/party_menu.h:658 local fm = g.index[act] - if fm then return g.labels[g.base + fm + 1] end - return g.labels[(assert(CURSOR_OPTION[act], act)) + 1] + if fm then + local move = g.moves[fm + 1] + return move and Pokemon.moveName(move) or g.labels[g.base + fm + 1] + end + local i = assert(CURSOR_OPTION[act], act) + local key = g.texts and g.texts[i + 1] + if key and RomText.has(key) then return RomText.plain(key) end + return g.labels[i + 1] end local fm = FIELD_MOVE_INDEX[act] if fm then return RomText.at("sCursorOptions", CURSOR_OPTION_FIELD_MOVES + fm) end return RomText.at("sCursorOptions", (assert(CURSOR_OPTION[act], act))) end +PartyMenu._cursorOptionText = cursor_option_text + +-- pokeemerald/src/party_menu.c:2557 +local function action_texts(list) + local cached = PartyMenu._actionTexts + if cached and cached.list == list then return cached.texts end + local texts = {} + for i, act in ipairs(list) do texts[i] = cursor_option_text(act) end + PartyMenu._actionTexts = { list = list, texts = texts } + return texts +end local FR_INSETS = { msgX = 2, msgY = 2, actX = 9, actY = 2, cursorX = 1 } local function textInsets() @@ -2859,12 +2877,13 @@ function PartyMenu.draw() local popX = 22 local popY = 19 - popH Window.stdFrame(Window.template(popX, popY, popW, popH)) - for i, act in ipairs(PartyMenu.ITEM_ACTIONS) do + local texts = action_texts(PartyMenu.ITEM_ACTIONS) + for i in ipairs(PartyMenu.ITEM_ACTIONS) do local rowY = (popY * 8) + (i - 1) * 16 + ins.actY if i == PartyMenu.itemActionCursor then Window.cursorPx(popX * 8 + ins.cursorX, rowY) end - FrlgFont.draw(cursor_option_text(act), popX * 8 + ins.actX, rowY, { colors = FrlgFont.COLOR.NORMAL }) + FrlgFont.draw(texts[i], popX * 8 + ins.actX, rowY, { colors = FrlgFont.COLOR.NORMAL }) end elseif PartyMenu.mode == "action" and p and p.drawActions then p.drawActions(PartyMenu, false) @@ -2878,6 +2897,7 @@ function PartyMenu.draw() local popX = 19 local popY = 19 - popH Window.stdFrame(Window.template(popX, popY, popW, popH)) + local texts = action_texts(PartyMenu.ACTIONS) for i, act in ipairs(PartyMenu.ACTIONS) do local rowY = (popY * 8) + (i - 1) * 16 + ins.actY if i == PartyMenu.actionCursor then @@ -2885,7 +2905,7 @@ function PartyMenu.draw() end local isFm = PartyMenu._fieldMoveNames and PartyMenu._fieldMoveNames[act] local col = isFm and (FrlgFont.COLOR.BLUE or FrlgFont.COLOR.MALE_NPC) or FrlgFont.COLOR.NORMAL - FrlgFont.draw(cursor_option_text(act), popX * 8 + ins.actX, rowY, { colors = col }) + FrlgFont.draw(texts[i], popX * 8 + ins.actX, rowY, { colors = col }) end else if p and p.drawPrompt then diff --git a/src/ui/game3/rse/contest.lua b/src/ui/game3/rse/contest.lua index 30c03099..b451b80c 100644 --- a/src/ui/game3/rse/contest.lua +++ b/src/ui/game3/rse/contest.lua @@ -730,7 +730,10 @@ function UI:printContestMoveDescription(move) self:fillBox(0, TILE_EMPTY_JAM_HEART, 0x15, 0x20, MAX_CONTEST_MOVE_HEARTS, 0x01, 0x11) self:fillBox(0, TILE_FILLED_JAM_HEART, 0x15, 0x20, hearts, 0x01, 0x11) local key = "gContestEffectDescriptionPointers[" .. (cm.effect or 0) .. "]" - local desc = UI.has(key) and UI.plain(key) or "" + local desc = "" + if UI.has(key) then + desc = require("src.core.game3.summary_data").contestEffectDescription(UI.plain(key)) + end self.win[WIN_MOVE_DESCRIPTION] = { text = desc, x = 0, y = 1, fg = 15, shadow = 8 } self.win[WIN_SLASH] = { text = UI.plain("gText_Slash"), x = 0, y = 1, fg = 15, shadow = 8 } end diff --git a/src/ui/game3/rse/dome_tourney.lua b/src/ui/game3/rse/dome_tourney.lua index f7c92916..7ec1bfc5 100644 --- a/src/ui/game3/rse/dome_tourney.lua +++ b/src/ui/game3/rse/dome_tourney.lua @@ -280,7 +280,7 @@ local function drawTree() drawLines() love.graphics.draw(treeImage(), 0, 0) local W = M().treeWindows - local title = textOf(M().text.battleTourney) + local title = st.treeTitle local tw = W[3] drawSpaced(title, tw.left * 8 + centerX(title, 0x70, "short", 2), tw.top * 8 + 1, colors(COLOR.DYNAMIC_5, COLOR.DYNAMIC_4), "short", 2) @@ -425,12 +425,12 @@ local function drawMatchCard(card, ox, oy) local W = M().cardWindows local c = colors(COLOR.DYNAMIC_5, COLOR.DYNAMIC_4) local d = card.data - local wt = textOf(M().text.wins[d.win.id + 1], { d.win.var1, d.win.var2 }) + local wt = textOf(d.winIr, { d.win.var1, d.win.var2 }) winText(W[9], wt, 0, 0, c, "normal", 0, ox, oy) local left, right = d.sides[1], d.sides[2] winText(W[7], left.name, centerX(left.name, 0x40, "short", 2), 2, c, "short", 2, ox, oy) winText(W[8], right.name, centerX(right.name, 0x40, "short", 2), 2, c, "short", 2, ox, oy) - local mn = textOf(M().text.matchNumbers[d.matchNo + 1]) + local mn = textOf(d.matchNoIr) winText(W[6], mn, centerX(mn, 0xA0, "short", 0), 2, c, "short", 0, ox, oy) drawPic(picFor(left.trainerId), ox + 48, oy + 88, left.lost) drawPic(picFor(right.trainerId), ox + 192, oy + 88, right.lost) @@ -443,7 +443,17 @@ end local function makeCard(kind, id) if kind == "trainer" then return { kind = kind, id = id, data = trainerCardData(id) } end - return { kind = kind, id = id, data = matchCardData(id) } + local data = matchCardData(id) + local Dome = require("src.core.game3.rse.frontier.dome") + -- pokeemerald/src/battle_dome.c:4931, :4977 + data.winIr = Dome.tableText(M().text.wins, "sBattleDomeWinTexts", data.win.id + 1) + data.matchNoIr = Dome.tableText(M().text.matchNumbers, "sBattleDomeMatchNumberTexts", data.matchNo + 1) + return { kind = kind, id = id, data = data } +end + +-- pokeemerald/src/battle_dome.c:5415 +local function treeTitle() + return textOf(require("src.core.game3.rom_text").irOr("gText_BattleTourney", M().text.battleTourney)) end local function drawCard(card, ox, oy) @@ -541,6 +551,7 @@ function UI.showTree(opts) st.cursor = opts.cursor or 0 st.frame, st.bg2y, st.bg3y = 0, 0, 11 buildTree(st.mode) + st.treeTitle = treeTitle() st.phase = "fadein" fadeIn() push() @@ -596,6 +607,7 @@ local function backToTree() st.mode = rt.mode st.bg2y, st.bg3y = 0, 11 buildTree(st.mode) + st.treeTitle = st.treeTitle or treeTitle() st.phase = "fadein" fadeIn() end diff --git a/src/ui/game3/rse/factory_select.lua b/src/ui/game3/rse/factory_select.lua index 903ea3b0..aac3bb32 100644 --- a/src/ui/game3/rse/factory_select.lua +++ b/src/ui/game3/rse/factory_select.lua @@ -1,6 +1,7 @@ local Stack = require("src.ui.game3.stack") local FrlgFont = require("src.ui.game3.frlg_font") local RomText = require("src.core.game3.rom_text") +local Strings = require("src.core.Strings") local Kit = require("src.ui.game3.rse.scene_kit") local Gfx = require("src.ui.game3.rse.pokeblock_gfx") local PalFade = require("src.core.game3.pal_fade") @@ -162,7 +163,7 @@ function Common.categoryText(species) entriesPack = Gfx.loadLua("data/generated/gba/pokemon/pokedex/entries.lua") or {} end local e = entriesPack[tonumber(species) or 0] or {} - return (e.category or "") .. " " .. RomText.plain("gText_Pokemon") + return Strings(e.category or "") .. " " .. RomText.plain("gText_Pokemon") end function Common.speciesName(species) diff --git a/src/ui/game3/rse/frontier_pass.lua b/src/ui/game3/rse/frontier_pass.lua index 3c1b963f..42a4ccb8 100644 --- a/src/ui/game3/rse/frontier_pass.lua +++ b/src/ui/game3/rse/frontier_pass.lua @@ -149,12 +149,32 @@ local function updateHighlight(cur, prev) local _ = g end +-- pokeemerald/src/frontier_pass.c:530 +local function description(i) + return RomText.irOr(RomText.key("sPassAreaDescriptions", i), man().descriptions[i + 1]) +end + +-- pokeemerald/src/frontier_pass.c:556 +local LANDMARK_TEXTS = { + { "gText_BattleTower3", "gText_BattleTowerDesc" }, + { "gText_BattleDome2", "gText_BattleDomeDesc" }, + { "gText_BattlePalace2", "gText_BattlePalaceDesc" }, + { "gText_BattleArena2", "gText_BattleArenaDesc" }, + { "gText_BattleFactory2", "gText_BattleFactoryDesc" }, + { "gText_BattlePike2", "gText_BattlePikeDesc" }, + { "gText_BattlePyramid2", "gText_BattlePyramidDesc" }, +} + +local function landmarkText(i, field) + local labels = LANDMARK_TEXTS[i + 1] + return RomText.irOr(labels and labels[field == "name" and 1 or 2], man().landmarks[i + 1][field]) +end + local function describe(area) - local d = man().descriptions if area == A.RECORD and not st.hasRecord then - st.desc = textOf(d[A.NOTHING + 1]) + st.desc = textOf(description(A.NOTHING)) elseif area ~= A.NOTHING then - st.desc = textOf(d[area + 1]) + st.desc = textOf(description(area)) else st.desc = nil end @@ -456,6 +476,10 @@ function Map.show() Map.pal:beginFade(PalFade.ALL, 0, 16, 0, PalFade.WHITE) Map.image = nil Map.head = Map.headPosition() + Map.landmarks = {} + for i = 0, NUM_FACILITIES - 1 do + Map.landmarks[i] = { name = textOf(landmarkText(i, "name")), description = textOf(landmarkText(i, "description")) } + end end -- pokeemerald/src/frontier_pass.c:1582 @@ -544,9 +568,9 @@ function Map.draw() love.graphics.draw(Map.image, 0, 0) local lm = man().landmarks for i = 0, NUM_FACILITIES - 1 do - drawText(textOf(lm[i + 1].name), 160 + 4, 8 + i * 16 + 1, colors(i == Map.pos and 2 or 1), "narrow") + drawText(Map.landmarks[i].name, 160 + 4, 8 + i * 16 + 1, colors(i == Map.pos and 2 or 1), "narrow") end - drawText(textOf(lm[Map.pos + 1].description), 16 + 4, 128, colors(0)) + drawText(Map.landmarks[Map.pos].description, 16 + 4, 128, colors(0)) local cur = spriteFrame("cursor", 4, 16, 16, p.cursor) love.graphics.draw(cur, 155 + 8, Map.cursorY - 8, 0, -1, 1) local mark = lm[Map.pos + 1] diff --git a/src/ui/game3/rse/frontier_records.lua b/src/ui/game3/rse/frontier_records.lua index 4f4214a1..4d1e2304 100644 --- a/src/ui/game3/rse/frontier_records.lua +++ b/src/ui/game3/rse/frontier_records.lua @@ -338,12 +338,12 @@ function Records.buildRankingHall(sess, hallId, lvlMode) local win = newWindow(HALL_TPL) local man = D().manifest() local pair = man.recordsChallengeTexts[hallId + 1] - local facilityName = plainIr(pair[1].ir) - put(win, plainIr(pair[2].ir, { facilityName }), 0, 1) - local lvl = plainIr(man.levelModeText[lvlMode + 1].ir) + local facilityName = plainIr(RomText.refIr(pair[1])) + put(win, plainIr(RomText.refIr(pair[2]), { facilityName }), 0, 1) + local lvl = plainIr(RomText.refIr(man.levelModeText[lvlMode + 1])) put(win, lvl, (240 - 32) - width(lvl), 1) local rows = U().rankingHall(sess, hallId, lvlMode) - local recordText = man.hallFacilityToRecordsText[hallId + 1].ir + local recordText = RomText.refIr(man.hallFacilityToRecordsText[hallId + 1]) local link = hallId == U().RANKING_HALL.TOWER_LINK for i, r in ipairs(rows) do local pos = i - 1 diff --git a/src/ui/game3/rse/mapsec.lua b/src/ui/game3/rse/mapsec.lua index adee424c..c91ac79c 100644 --- a/src/ui/game3/rse/mapsec.lua +++ b/src/ui/game3/rse/mapsec.lua @@ -1,4 +1,5 @@ local MapSections = require("src.import.gba.rse.map_sections_extract") +local Strings = require("src.core.Strings") local Mapsec = {} @@ -45,7 +46,7 @@ end -- pokeemerald/src/region_map.c:1568 function Mapsec.name(sec) local e = Mapsec.entry(sec) - return e and e.name or "" + return e and e.name and Strings(e.name) or "" end -- pokeemerald/src/map_name_popup.c:403 diff --git a/src/ui/game3/rse/move_relearner.lua b/src/ui/game3/rse/move_relearner.lua index 1e2a838a..80e0c9f0 100644 --- a/src/ui/game3/rse/move_relearner.lua +++ b/src/ui/game3/rse/move_relearner.lua @@ -399,8 +399,11 @@ local function draw_contest(moveId) right(RomText.plain("gText_MoveRelearnerJam"), 92, 41) if not moveId then return end local cm, eff, c = contest_row(moveId) - if cm then print_at(c.categories and c.categories[cm.category] or "", 4, 25) end - if eff then print_lines(eff.description, 0, 65, NARROW) end + if cm then + local category = c.categories and c.categories[cm.category] + print_at(SummaryData.contestCategoryName(category), 4, 25) + end + if eff then print_lines(SummaryData.contestEffectDescription(eff), 0, 65, NARROW) end draw_hearts(eff) end diff --git a/src/ui/game3/rse/pokedex.lua b/src/ui/game3/rse/pokedex.lua index 1806aa1e..7c9e2a1c 100644 --- a/src/ui/game3/rse/pokedex.lua +++ b/src/ui/game3/rse/pokedex.lua @@ -1,4 +1,5 @@ local Kit = require("src.ui.game3.rse.scene_kit") +local Strings = require("src.core.Strings") local FrlgFont = require("src.ui.game3.frlg_font") local RomText = require("src.core.game3.rom_text") local Pal = require("src.core.game3.pal_fade") @@ -935,7 +936,12 @@ local function monInfo(s, nat, nationalNumber, owned, newEntry) out[#out + 1] = { text = RomText.plain("gText_NumberClear01") .. string.format("%03d", num), x = 0x60, y = 0x19 } local sp = Pokedex.speciesOf(nat) out[#out + 1] = { text = sp ~= 0 and pokemon().name(sp) or Gfx.manifest().tenDashes, x = 0x84, y = 0x19 } - local category = owned and ((e.category or "") .. " " .. RomText.plain("gText_Pokemon")) or RomText.plain("gText_5MarksPokemon") + local category + if owned then + category = Strings(e.category or "") .. " " .. RomText.plain("gText_Pokemon") + else + category = RomText.plain("gText_5MarksPokemon") + end out[#out + 1] = { text = category, x = 0x64, y = 0x29 } out[#out + 1] = { text = RomText.plain("gText_HTHeight"), x = 0x60, y = 0x39 } out[#out + 1] = { text = RomText.plain("gText_WTWeight"), x = 0x60, y = 0x49 } @@ -946,7 +952,7 @@ local function monInfo(s, nat, nationalNumber, owned, newEntry) out[#out + 1] = { text = RomText.plain("gText_UnkHeight"), x = 0x81, y = 0x39 } out[#out + 1] = { text = RomText.plain("gText_UnkWeight"), x = 0x81, y = 0x49 } end - local desc = owned and (e.description or "") or "" + local desc = owned and Strings(e.description or "") or "" local w = 0 for line in (desc .. "\n"):gmatch("(.-)\n") do w = math.max(w, FrlgFont.measure(line) or 0) end out[#out + 1] = { text = desc, x = w < 240 and math.floor((240 - w) / 2) or 0, y = 95 } @@ -1587,8 +1593,54 @@ function tasks.switchFromSize(s) end end +local function cartText(key, fallback) + if key and RomText.has(key) then return RomText.plain(key) end + return fallback +end + +-- pokeemerald/src/pokedex.c:1330 +local TYPE_OPTION = { "gText_DexSearchTypeNone" } +for _, t in ipairs({ 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17 }) do + TYPE_OPTION[#TYPE_OPTION + 1] = RomText.key("gTypeNames", t) +end +local SEARCH_OPTION_TEXTS = { + [SEARCH.NAME] = { titles = { "gText_DexSearchDontSpecify", "gText_DexSearchAlphaABC", "gText_DexSearchAlphaDEF", + "gText_DexSearchAlphaGHI", "gText_DexSearchAlphaJKL", "gText_DexSearchAlphaMNO", "gText_DexSearchAlphaPQR", + "gText_DexSearchAlphaSTU", "gText_DexSearchAlphaVWX", "gText_DexSearchAlphaYZ" } }, + [SEARCH.COLOR] = { titles = { "gText_DexSearchDontSpecify", "gText_DexSearchColorRed", "gText_DexSearchColorBlue", + "gText_DexSearchColorYellow", "gText_DexSearchColorGreen", "gText_DexSearchColorBlack", "gText_DexSearchColorBrown", + "gText_DexSearchColorPurple", "gText_DexSearchColorGray", "gText_DexSearchColorWhite", "gText_DexSearchColorPink" } }, + [SEARCH.TYPE_LEFT] = { titles = TYPE_OPTION }, + [SEARCH.TYPE_RIGHT] = { titles = TYPE_OPTION }, + [SEARCH.ORDER] = { + titles = { "gText_DexSortNumericalTitle", "gText_DexSortAtoZTitle", "gText_DexSortHeaviestTitle", + "gText_DexSortLightestTitle", "gText_DexSortTallestTitle", "gText_DexSortSmallestTitle" }, + descriptions = { "gText_DexSortNumericalDescription", "gText_DexSortAtoZDescription", + "gText_DexSortHeaviestDescription", "gText_DexSortLightestDescription", "gText_DexSortTallestDescription", + "gText_DexSortSmallestDescription" }, + }, + [SEARCH.MODE] = { + titles = { "gText_DexHoennTitle", "gText_DexNatTitle" }, + descriptions = { "gText_DexHoennDescription", "gText_DexNatDescription" }, + }, +} +-- pokeemerald/src/pokedex.c:1017 +local TOPBAR_DESCRIPTIONS = { "gText_SearchForPkmnBasedOnParameters", "gText_SwitchPokedexListings", + "gText_ReturnToPokedex" } +-- pokeemerald/src/pokedex.c:1042 +local ITEM_DESCRIPTIONS = { "gText_ListByFirstLetter", "gText_ListByBodyColor", "gText_ListByType", + "gText_ListByType", "gText_SelectPokedexListingMode", "gText_SelectPokedexMode", "gText_ExecuteSearchSwitch" } + +local function topBarDescription(i) + return cartText(TOPBAR_DESCRIPTIONS[i + 1], Gfx.manifest().search.topBar[i + 1].description) +end + +local function itemDescription(i) + return cartText(ITEM_DESCRIPTIONS[i + 1], Gfx.manifest().search.items[i + 1].description) +end + -- pokeemerald/src/pokedex.c:1437 -local function searchOptionTexts(which) +local function searchOptionList(which) local sm = Gfx.manifest().search if which == SEARCH.NAME then return sm.names end if which == SEARCH.COLOR then return sm.colors end @@ -1598,6 +1650,23 @@ local function searchOptionTexts(which) return {} end +local function searchOptionTexts(which) + local list = searchOptionList(which) + local keys = SEARCH_OPTION_TEXTS[which] + if not keys then return list end + local out = {} + for i, t in ipairs(list) do + out[i] = { + title = cartText(keys.titles[i], t.title), + description = cartText(keys.descriptions and keys.descriptions[i], t.description), + } + end + return out +end +Pokedex.searchOptionTexts = searchOptionTexts +Pokedex.topBarDescription = topBarDescription +Pokedex.itemDescription = itemDescription + local function searchSel(q, which) local c = q.cursor[which] or 0 local o = q.scroll[which] or 0 @@ -1766,7 +1835,7 @@ function tasks.loadSearch(s) elseif st == 1 then local q = s.searchState setDefaultSearchModeAndOrder(s, q) - setSearchMessage(q, Gfx.manifest().search.topBar[TOPBAR.SEARCH + 1].description) + setSearchMessage(q, topBarDescription(TOPBAR.SEARCH)) s.searchArrows = { { x = 184, y = 4, down = false, data2 = 0, invisible = true }, { x = 184, y = 108, down = true, data2 = 0, invisible = true }, @@ -1784,7 +1853,7 @@ function tasks.loadSearch(s) s.state = 0 s.fn = "searchTopBar" s.searchState.phase = "topbar" - setSearchMessage(s.searchState, Gfx.manifest().search.topBar[s.searchState.topBar + 1].description) + setSearchMessage(s.searchState, topBarDescription(s.searchState.topBar)) refreshSearch(s) end end @@ -1794,7 +1863,6 @@ end function tasks.searchTopBar(s, inp) local new = inp.new or {} local q = s.searchState - local sm = Gfx.manifest().search if new.b then se("SE_PC_OFF") s.fn = "exitSearch" @@ -1815,20 +1883,20 @@ function tasks.searchTopBar(s, inp) return end q.phase = "menu" - setSearchMessage(q, sm.items[q.menuItem + 1].description) + setSearchMessage(q, itemDescription(q.menuItem)) refreshSearch(s) return end if new.left and q.topBar > TOPBAR.SEARCH then se("SE_DEX_PAGE") q.topBar = q.topBar - 1 - setSearchMessage(q, sm.topBar[q.topBar + 1].description) + setSearchMessage(q, topBarDescription(q.topBar)) refreshSearch(s) end if new.right and q.topBar < TOPBAR.CANCEL then se("SE_DEX_PAGE") q.topBar = q.topBar + 1 - setSearchMessage(q, sm.topBar[q.topBar + 1].description) + setSearchMessage(q, topBarDescription(q.topBar)) refreshSearch(s) end end @@ -1850,7 +1918,7 @@ function tasks.searchMenu(s, inp) setDefaultSearchModeAndOrder(s, q) q.phase = "topbar" s.fn = "searchTopBar" - setSearchMessage(q, sm.topBar[q.topBar + 1].description) + setSearchMessage(q, topBarDescription(q.topBar)) refreshSearch(s) return end @@ -1896,7 +1964,7 @@ function tasks.searchMenu(s, inp) if nxt ~= 0xFF then se(sound) q.menuItem = nxt - setSearchMessage(q, sm.items[q.menuItem + 1].description) + setSearchMessage(q, itemDescription(q.menuItem)) refreshSearch(s) end end @@ -1947,7 +2015,7 @@ function tasks.searchDone(s, inp) else s.fn = "searchMenu" q.phase = "menu" - setSearchMessage(q, Gfx.manifest().search.items[q.menuItem + 1].description) + setSearchMessage(q, itemDescription(q.menuItem)) refreshSearch(s) se("SE_BALL") end @@ -1959,8 +2027,7 @@ function tasks.searchParam(s, inp) local new, rep = inp.new or {}, inp.rep or {} local q = s.searchState local mi = q.menuItem - local texts = searchOptionTexts(mi) - local maxOption = #texts - 1 + local maxOption = #searchOptionList(mi) - 1 if new.a or new.b then se(new.a and "SE_PIN" or "SE_BALL") if new.b then @@ -1969,7 +2036,7 @@ function tasks.searchParam(s, inp) end q.paramBox = false q.phase = "menu" - setSearchMessage(q, Gfx.manifest().search.items[mi + 1].description) + setSearchMessage(q, itemDescription(mi)) refreshSearch(s) s.fn = "searchMenu" return @@ -1994,7 +2061,7 @@ function tasks.searchParam(s, inp) end if moved then se("SE_SELECT") - local t = texts[searchSel(q, mi) + 1] + local t = searchOptionTexts(mi)[searchSel(q, mi) + 1] setSearchMessage(q, t and t.description or "") refreshSearch(s) end @@ -2138,8 +2205,7 @@ function Pokedex.frame(s, inp) end if s.searchArrows and s.fn == "searchParam" then local q = s.searchState - local texts = searchOptionTexts(q.menuItem) - local last = #texts - 1 + local last = #searchOptionList(q.menuItem) - 1 for _, a in ipairs(s.searchArrows) do local off = q.scroll[q.menuItem] or 0 if a.down then diff --git a/src/ui/game3/rse/pyramid_bag.lua b/src/ui/game3/rse/pyramid_bag.lua index 6f59c6ac..160ae726 100644 --- a/src/ui/game3/rse/pyramid_bag.lua +++ b/src/ui/game3/rse/pyramid_bag.lua @@ -100,11 +100,18 @@ local function itemName(id) end -- pokeemerald/src/battle_pyramid_bag.c:379 +-- pokeemerald/src/battle_pyramid_bag.c:687 +function PBag.returnTo(location) + local ret = Py().manifest().bagReturnTo[(PBag.LOCATION[location] or 0) + 1] + return ret and require("src.core.game3.scripting.text_ir").toPlain(RomText.refIr(ret), {}) or "" +end + function PBag.show(opts) opts = opts or {} for k in pairs(st) do st[k] = nil end st.session = opts.session st.location = opts.location or "field" + st.returnTo = PBag.returnTo(st.location) st.onClose = opts.onClose st.onUse = opts.onUse st.onGive = opts.onGive @@ -465,9 +472,7 @@ local function description() return RomText.plain("gText_MoveVar1Where", { stringVars = { itemName(items[st.swapFrom + 1]) } }) end if item then return ItemsData.description(item) or "" end - local ret = Py().manifest().bagReturnTo[(PBag.LOCATION[st.location] or 0) + 1] - local TextIR = require("src.core.game3.scripting.text_ir") - return RomText.plain("gText_ReturnToVar1", { stringVars = { ret and TextIR.toPlain(ret.ir, {}) or "" } }) + return RomText.plain("gText_ReturnToVar1", { stringVars = { st.returnTo or "" } }) end PBag.description = description diff --git a/src/ui/game3/rse/region_map.lua b/src/ui/game3/rse/region_map.lua index d93cc41d..8c9aedec 100644 --- a/src/ui/game3/rse/region_map.lua +++ b/src/ui/game3/rse/region_map.lua @@ -353,14 +353,20 @@ local function newState(opts) end RegionMap.newState = newState +-- pokeemerald/src/region_map.c:349 +local MULTI_NAME_TABLES = { "sEverGrandeCityNames" } + -- pokeemerald/src/region_map.c:1760 function RegionMap.updateFlyText(s) s.flyText = nil if s.mapSecType > TYPE.NONE then - for _, m in ipairs(RegionMap.manifest().multiNameFlyDestinations) do + for i, m in ipairs(RegionMap.manifest().multiNameFlyDestinations) do if s.mapSecId == m.mapSecId then if flagSet(s, m.flag) then - s.flyText = { tall = true, name = s.mapSecName, sub = m.names[s.posWithinMapSec + 1] or "" } + local sub = m.names[s.posWithinMapSec + 1] or "" + local key = MULTI_NAME_TABLES[i] and RomText.key(MULTI_NAME_TABLES[i], s.posWithinMapSec) + if key and RomText.has(key) then sub = RomText.plain(key) end + s.flyText = { tall = true, name = s.mapSecName, sub = sub } end break end diff --git a/src/ui/game3/rse/starter_choose.lua b/src/ui/game3/rse/starter_choose.lua index cfad4ca0..e6d93125 100644 --- a/src/ui/game3/rse/starter_choose.lua +++ b/src/ui/game3/rse/starter_choose.lua @@ -1,5 +1,6 @@ local Kit = require("src.ui.game3.rse.scene_kit") local RomText = require("src.core.game3.rom_text") +local Strings = require("src.core.Strings") local FrlgFont = require("src.ui.game3.frlg_font") local Pal = require("src.core.game3.pal_fade") local Trig = require("src.core.game3.trig") @@ -126,7 +127,7 @@ local function categoryText(species, policy) local nat = Pokemon.national and Pokemon.national(species) or species local row = dex and dex[nat] -- pokeemerald/src/international_string_util.c:86 - local category = (row and row.category) or "" + local category = Strings((row and row.category) or "") if policy then return policy.categoryText(category, RomText.plain(policy.categoryKey)) end return category .. " " .. RomText.plain("gText_Pokemon") end diff --git a/src/ui/game3/rse/summary_menu.lua b/src/ui/game3/rse/summary_menu.lua index b9133318..7ec92bab 100644 --- a/src/ui/game3/rse/summary_menu.lua +++ b/src/ui/game3/rse/summary_menu.lua @@ -5,6 +5,7 @@ local RomText = require("src.core.game3.rom_text") local Pokemon = require("src.core.game3.pokemon") local ItemsData = require("src.core.game3.items_data") local SummaryData = require("src.core.game3.summary_data") +local Strings = require("src.core.Strings") local RseSummary = {} @@ -416,7 +417,7 @@ local function drawInfo(m, Sm, mon, egg) dynamic = { [0] = RomText.ir("sMemoNatureTextColor"), [1] = RomText.ir("sMemoMiscTextColor"), [2] = RomText.at("gNatureNamePointers", natureId), [3] = tostring(metLevel == 0 and 5 or metLevel), - [4] = secName or "", [5] = RomText.ir("gText_EmptyString5"), + [4] = secName and Strings(secName) or "", [5] = RomText.ir("gText_EmptyString5"), }, }) end @@ -545,7 +546,7 @@ local function drawMoves(m, Sm, mon, contest, detail) drawTile(m, "hearts", i < appeal and 1 or 0, tx * 8, ty * 8) drawTile(m, "hearts", i < jam and 3 or 4, tx * 8, (ty + 2) * 8) end - put(m, dw, eff.description or "", 6, 1, 0) + put(m, dw, SummaryData.contestEffectDescription(eff), 6, 1, 0) end else local pa = win(m, W.POWER_ACC) diff --git a/src/ui/game3/rse/trainer_hill_records.lua b/src/ui/game3/rse/trainer_hill_records.lua index 95ffe7a1..b15a93f4 100644 --- a/src/ui/game3/rse/trainer_hill_records.lua +++ b/src/ui/game3/rse/trainer_hill_records.lua @@ -32,7 +32,7 @@ function Records.lines(sess) local y = 18 local modes = Hl.manifest().modeStrings for i = 1, Hl.NUM_MODES do - out[#out + 1] = { text = TextIR.toPlain(modes[i].ir, {}), x = 0, y = y } + out[#out + 1] = { text = TextIR.toPlain(RomText.refIr(modes[i]), {}), x = 0, y = y } y = y + 15 local m, s, f = Hl.timeParts(sess.trainerHillTimes[i]) local vars = { string.format("%2d", m), string.format("%2d", s), string.format("%02d", f) } diff --git a/src/ui/game3/summary_menu.lua b/src/ui/game3/summary_menu.lua index 1cc2ded6..83746751 100644 --- a/src/ui/game3/summary_menu.lua +++ b/src/ui/game3/summary_menu.lua @@ -905,23 +905,27 @@ local function draw_page_skills(mon) -- Party stores ability as numeric id (e.g. 65 = OVERGROW); resolve to name. local abilityId = tonumber(mon.abilityId) or tonumber(mon.ability) local ability = mon.abilityName + local abilityNameTranslated = false if type(mon.ability) == "string" and mon.ability ~= "" and not tonumber(mon.ability) then ability = mon.ability end if (not ability or ability == "") and abilityId and abilityId > 0 then ability = Pokemon.abilityName(abilityId) + abilityNameTranslated = true end if not ability or ability == "" then local aid = Pokemon.abilityId and Pokemon.abilityId(Pokemon.speciesOf(mon), mon.personality or 0) if aid and aid > 0 then abilityId = aid ability = Pokemon.abilityName(aid) + abilityNameTranslated = true end end ability = ability or "—" local ax, ay = cxy("abilityName", 74, 129) - -- No registry renames abilities; a translation reaches the name through Strings(). - draw_text(Strings(tostring(ability)), ax, ay, 80, "NORMAL") + local abilityText = tostring(ability) + if not abilityNameTranslated then abilityText = Strings(abilityText) end + draw_text(abilityText, ax, ay, 80, "NORMAL") local desc = SummaryData.abilityDescription(abilityId, tostring(ability)) local ad = coords().abilityDesc or { x = 10, y = 143, w = 232 } draw_text(desc, ad.x or 10, ad.y or 143, ad.w or 232, "NORMAL") diff --git a/tests/engine/game3_emerald_pack_text_test.lua b/tests/engine/game3_emerald_pack_text_test.lua new file mode 100644 index 00000000..6a3fc309 --- /dev/null +++ b/tests/engine/game3_emerald_pack_text_test.lua @@ -0,0 +1,209 @@ +-- Emerald screens that keep their own copy of a cart string read the script +-- cache's text for it when the cache holds it, the way script messages do, +-- so a mod's text overrides reach them; 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 RomText = require("src.core.game3.rom_text") +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 +local function plain(x) return x and TextIR.toPlain(x, {}) or nil end + +-- RomText.irOr / RomText.refIr +BUNDLE.text = { gText_RecordsLv50 = ir("NIVEAU 50"), ["g3:08abcdef"] = ir("PAR ADRESSE") } +check(plain(RomText.irOr("gText_RecordsLv50", ir("LV. 50"))) == "NIVEAU 50", "irOr reads the cached text") +check(plain(RomText.irOr("gText_Missing", ir("LV. 50"))) == "LV. 50", "irOr falls back on the copy") +check(plain(RomText.irOr(nil, ir("LV. 50"))) == "LV. 50", "irOr without a key keeps the copy") +check(plain(RomText.refIr({ name = "gText_RecordsLv50", key = "g3:08000000", ir = ir("LV. 50") })) == "NIVEAU 50", + "refIr reads a reference by its symbol") +check(plain(RomText.refIr({ name = "sUnnamed", key = "g3:08abcdef", ir = ir("BY ADDRESS") })) == "PAR ADRESSE", + "refIr reads a reference by its address") +check(plain(RomText.refIr({ name = "sUnnamed", key = "g3:08000000", ir = ir("COPY") })) == "COPY", + "refIr falls back on the reference's copy") +check(RomText.refIr(nil) == nil, "refIr of no reference is nil") + +-- Battle Dome tables, keyed by the table's label (extract_scripts.lua). +BUNDLE.text = { ["sBattleDomePotentialTexts[1]"] = ir("Fort potentiel") } +local Dome = require("src.core.game3.rse.frontier.dome") +local potential = { ir("Best candidate"), ir("Strong potential") } +check(plain(Dome.tableText(potential, "sBattleDomePotentialTexts", 2)) == "Fort potentiel", + "a Dome table string reads the cached text by its label") +check(plain(Dome.tableText(potential, "sBattleDomePotentialTexts", 1)) == "Best candidate", + "a Dome table string missing from the cache keeps the copy") + +-- Apprentice messages are pack references. +local Apprentice = require("src.core.game3.rse.frontier.apprentice") +BUNDLE.text = { gText_ApprenticeWhichMon = ir("Lequel?") } +Apprentice.manifest = function() + return { whichMon = { { { name = "gText_ApprenticeWhichMon", key = "g3:08000001", ir = ir("Which one?") }, + { name = "gText_ApprenticeThanks", key = "g3:08000002", ir = ir("Thanks!") } } } } +end +Apprentice.player = function() return { id = 0 } end +check(plain(Apprentice.message({}, Apprentice.MSG.WHICH_MON)) == "Lequel?", "an Apprentice line reads the cached text") +check(plain(Apprentice.message({}, Apprentice.MSG.THANKS_MON)) == "Thanks!", + "an Apprentice line missing from the cache keeps the copy") + +-- The Battle Pyramid bag's "Return to ..." line (pokeemerald/src/battle_pyramid_bag.c:687). +local Pyramid = require("src.core.game3.rse.frontier.pyramid") +local PyramidBag = require("src.ui.game3.rse.pyramid_bag") +Pyramid.bagCursor = { scroll = 0, cursor = 0 } +Pyramid.bagLists = function() return {}, {} end +Pyramid.manifest = function() + return { bagReturnTo = { { name = "gText_TheField", key = "g3:08000003", ir = ir("the field") } } } +end +PyramidBag._st.count, PyramidBag._st.location = 1, "field" +BUNDLE.text = { gText_ReturnToVar1 = { { t = "text", s = "Retourner " }, { t = "strvar", n = 1 }, { t = "eos" } }, + gText_TheField = ir("au jeu") } +PyramidBag._st.returnTo = PyramidBag.returnTo("field") +check(PyramidBag.description() == "Retourner au jeu", "the bag's return line names the place from the cache") +BUNDLE.text.gText_TheField = nil +check(PyramidBag.description() == "Retourner au jeu", "the open bag keeps the place it read") +PyramidBag._st.returnTo = PyramidBag.returnTo("field") +check(PyramidBag.description() == "Retourner the field", "a place missing from the cache keeps the copy") + +-- Ever Grande City's fly destinations (pokeemerald/src/region_map.c:343). +local RegionMap = require("src.ui.game3.rse.region_map") +RegionMap.manifest = function() + return { multiNameFlyDestinations = { { names = { "POKéMON LEAGUE", "POKéMON CENTER" }, mapSecId = 5, flag = 2100 } } } +end +local function flySub() + local s = { mapSecType = RegionMap.TYPE.CITY_CANFLY, mapSecId = 5, posWithinMapSec = 1, mapSecName = "EVER GRANDE", + session = { flags = { [2100] = true } } } + RegionMap.updateFlyText(s) + return s.flyText.sub +end +BUNDLE.text = { ["sEverGrandeCityNames[1]"] = ir("CENTRE POKéMON") } +check(flySub() == "CENTRE POKéMON", "a fly destination reads its name from the cache") +BUNDLE.text = {} +check(flySub() == "POKéMON CENTER", "a fly destination missing from the cache keeps the copy") + +-- The Pokédex search screen (pokeemerald/src/pokedex.c:1017, 1042, 1330). +local Gfx = require("src.ui.game3.rse.pokedex_gfx") +Gfx.manifest = function() + return { search = { + topBar = { { description = "Search for POKéMON based on selected parameters." } }, + items = { { description = "List by the first letter in the name." }, {}, {}, {}, {}, {}, + { description = "Execute search/switch." } }, + orders = { { title = "NUMERICAL", description = "Pokédex listing by number." }, + { title = "A TO Z", description = "Alphabetical order." } }, + types = { { title = "NONE" }, { title = "NORMAL" }, { title = "FIGHT" } }, + } } +end +local Pokedex = require("src.ui.game3.rse.pokedex") +BUNDLE.text = { + gText_SearchForPkmnBasedOnParameters = ir("Chercher des POKéMON."), + gText_ListByFirstLetter = ir("Classer par initiale."), + gText_ExecuteSearchSwitch = ir("Lancer la recherche."), + gText_DexSortNumericalTitle = ir("NUMERIQUE"), + gText_DexSortNumericalDescription = ir("Classement par numéro."), + ["gTypeNames[0]"] = ir("NORMAL"), ["gTypeNames[1]"] = ir("COMBAT"), +} +local orders = Pokedex.searchOptionTexts(Pokedex.SEARCH.ORDER) +check(orders[1].title == "NUMERIQUE" and orders[1].description == "Classement par numéro.", + "a search option reads its title and description from the cart") +check(orders[2].title == "A TO Z" and orders[2].description == "Alphabetical order.", + "a search option missing from the cache keeps the copy") +local types = Pokedex.searchOptionTexts(Pokedex.SEARCH.TYPE_LEFT) +check(types[1].title == "NONE" and types[3].title == "COMBAT", "type options read gTypeNames by type id") +check(Pokedex.topBarDescription(0) == "Chercher des POKéMON.", "a top bar description reads the cart's text") +check(Pokedex.itemDescription(0) == "Classer par initiale.", "a search item description reads the cart's text") +check(Pokedex.itemDescription(Pokedex.SEARCH.OK) == "Lancer la recherche.", "the OK item's description reads the cart's text") + +-- The party menu's actions (pokeemerald/src/data/party_menu.h:658). +require("tests.game3_cache").stubSpeciesNames() +local Profile = require("src.core.game3.profile") +local Pokemon = require("src.core.game3.pokemon") +local Kit = require("src.ui.game3.rse.scene_kit") +local PartyMenu = require("src.ui.game3.party_menu") +Profile.forSession = function() + return { ui = { party = { manifest = "rse/menus", cursorOptionTexts = { "gText_Summary5", "gText_Switch2", + "gText_Cancel2" } } } } +end +Kit.manifest = function() + return { party = { cursorOptions = { "SUMMARY", "SWITCH", "CANCEL", "CUT" }, fieldMoves = { 15 } } } +end +Pokemon.moveName = function(move) return move == 15 and "COUPE" or nil end +BUNDLE.text = { gText_Summary5 = ir("RESUME"), gText_Switch2 = ir("ORDRE") } +check(PartyMenu._cursorOptionText("SUMMARY") == "RESUME", "a party action reads the cart's text") +check(PartyMenu._cursorOptionText("SWITCH") == "ORDRE", "a second party action reads the cart's text") +check(PartyMenu._cursorOptionText("CANCEL") == "CANCEL", "a party action missing from the cache keeps the label") +check(PartyMenu._cursorOptionText("CUT") == "COUPE", "a field move prints the move's name") + +-- Expanded placeholders (pokeemerald/src/strings.c:6): the rival's name and +-- Japanese くん/ちゃん come from the cache, the extract's copies otherwise. +local Message = require("src.ui.game3.message") +local extracted = { + RIVAL_MALE = "MAY", RIVAL_FEMALE = "BRENDAN", KUN_MALE = "", KUN_FEMALE = "", VERSION = "EMERALD", + byGender = { RIVAL = { male = "MAY", female = "BRENDAN" }, KUN = { male = "", female = "" } }, +} +BUNDLE.text = { gText_ExpandedPlaceholder_May = ir("FLORA"), gText_ExpandedPlaceholder_Kun = ir("くん") } +local placeholders = Message.cartPlaceholders(extracted) +check(placeholders.byGender.RIVAL.male == "FLORA", "the rival's name reads the cache") +check(placeholders.byGender.RIVAL.female == "BRENDAN", "a rival name missing from the cache keeps the copy") +check(placeholders.byGender.KUN.male == "くん", "the honorific reads the cache") +check(placeholders.VERSION == "EMERALD", "a placeholder missing from the cache keeps the copy") +BUNDLE.text.gText_ExpandedPlaceholder_May = ir("AURA") +check(placeholders.byGender.RIVAL.male == "FLORA", "the values are resolved once, not on every read") +local line = { { t = "player" }, { t = "ph", code = 5, name = "KUN" }, { t = "text", s = " / " }, + { t = "ph", code = 6, name = "RIVAL" }, { t = "eos" } } +BUNDLE.text = { gText_ExpandedPlaceholder_May = ir("FLORA"), gText_ExpandedPlaceholder_Kun = ir("くん"), + gText_ExpandedPlaceholder_Brendan = ir("BRICE"), gText_ExpandedPlaceholder_Chan = ir("ちゃん") } +placeholders = Message.cartPlaceholders(extracted) +check(TextIR.toPlain(line, { dialect = "rse", playerName = "RED", playerGender = 0, placeholders = placeholders }) + == "REDくん / FLORA", "a boy's line expands the honorific and May's name from the cache") +check(TextIR.toPlain(line, { dialect = "rse", playerName = "RED", playerGender = 1, placeholders = placeholders }) + == "REDちゃん / BRICE", "a girl's line expands the honorific and Brendan's name from the cache") + +-- The context provider builds them once per script cache. +local Space = package.loaded["src.core.game3.scripting.space"] +Space.bundle = BUNDLE +package.loaded["src.import.CacheFs"] = { loadActive = function() return extracted end } +local first = TextIR.placeholdersFor({ dialect = "rse" }) +check(first ~= nil and TextIR.placeholdersFor({ dialect = "rse" }) == first, + "the provider builds the placeholders once per script cache") +check(first.byGender.RIVAL.female == "BRICE", "the provider's placeholders read the cache") +BUNDLE = { text = { gText_ExpandedPlaceholder_Brendan = ir("BRUNO") } } +Space.bundle = BUNDLE +check(TextIR.placeholdersFor({ dialect = "rse" }).byGender.RIVAL.female == "BRUNO", + "a new script cache rebuilds them") +Space.bundle = nil + +-- The Battle Arena's judgment window reads its titles once, when it opens +-- (pokeemerald/src/battle_arena.c:412). +local Data = require("src.core.game3.rse.frontier.f2_data") +Data.arena = function() + return { text = { playerMon1Name = ir("A"), vs = ir("VS"), opponentMon1Name = ir("B"), mind = ir("MIND"), + skill = ir("SKILL"), body = ir("BODY"), judgment = ir("JUDGMENT") } } +end +local Arena = require("src.core.game3.battle.facility_arena") +local arena = Arena.new() +BUNDLE.text = { gText_Mind = ir("ESPRIT") } +arena:judgeStep({}, { state = "open" }) +local titles = {} +for _, row in ipairs(arena.window.texts) do titles[row.win] = plain(row.ir) end +check(titles[Arena.WIN.MIND] == "ESPRIT", "the judgment window reads a title from the cache when it opens") +check(titles[Arena.WIN.BODY] == "BODY", "a judgment title missing from the cache keeps the copy") +BUNDLE.text = { gText_Mind = ir("AUTRE") } +titles = {} +for _, row in ipairs(arena.window.texts) do titles[row.win] = plain(row.ir) end +check(titles[Arena.WIN.MIND] == "ESPRIT", "the open window keeps the titles it read") + +if failed > 0 then + print(failed .. " check(s) failed") + os.exit(1) +end +print("all checks passed") diff --git a/tests/engine/game3_emerald_translation_runtime_test.lua b/tests/engine/game3_emerald_translation_runtime_test.lua new file mode 100644 index 00000000..11775b06 --- /dev/null +++ b/tests/engine/game3_emerald_translation_runtime_test.lua @@ -0,0 +1,201 @@ +package.path = "./?.lua;./?/init.lua;" .. package.path + +local Strings = require("src.core.Strings") +local Mapsec = require("src.ui.game3.rse.mapsec") +local RomText = require("src.core.game3.rom_text") +local FrlgFont = require("src.ui.game3.frlg_font") +local Pokemon = require("src.core.game3.pokemon") +local BattleText = require("src.core.game3.battle.battle_text") +local SummaryData = require("src.core.game3.summary_data") + +Pokemon._abilityNames = { [9] = "STATIC" } +Pokemon._romAbilityNames = { [9] = "STATIC" } + +Mapsec.readLua = function(rel) + assert(rel == "pokemon/pokedex/entries.lua") + return { + [252] = { + category = "Wood Gecko", + description = "A small gecko.", + height = 5, + weight = 50, + }, + } +end +RomText.plain = function(key) return key end +FrlgFont.measure = function(text) return #text end +Pokemon.name = function() return "TREECKO" end + +local Pokedex = require("src.ui.game3.rse.pokedex") +Pokedex.speciesOf = function() return 252 end +Pokedex.hoennNumber = function() return 1 end + +local function info() + return Pokedex.monInfo({}, 252, false, true, false) +end + +-- pokeemerald/src/starter_choose.c:579 and battle_factory_screen.c:2007 print +-- the same CopyMonCategoryText string as the Pokédex. +local Kit = require("src.ui.game3.rse.scene_kit") +Kit.loadLua = function(rel) + if rel == "data/generated/gba/region_map/map_sections.lua" then + return { sections = { [0] = { name = "LITTLEROOT TOWN" } } } + end + -- the Pokédex reads its layout from the graphics manifest (Emerald's, not + -- Ruby/Sapphire's native one) + if rel == "data/generated/gba/rse/pokedex/manifest.lua" then return {} end + assert(rel == "data/generated/gba/pokemon/dex.lua", rel) + return { [252] = { category = "Wood Gecko" } } +end +Pokemon.national = function(species) return species end +Pokemon._names = { [252] = "TREECKO" } -- keeps the starter label from reinstalling the packs +local StarterChoose = require("src.ui.game3.rse.starter_choose") +StarterChoose.species = function() return 252 end +local function starterCategory() + local view = setmetatable({ man = {}, selection = 1 }, StarterChoose) + view:_createLabel() + return view.label.category +end + +local PokeblockGfx = require("src.ui.game3.rse.pokeblock_gfx") +PokeblockGfx.loadLua = function(rel) + assert(rel == "data/generated/gba/pokemon/pokedex/entries.lua", rel) + return { [252] = { category = "Wood Gecko" } } +end +local FactoryCommon = require("src.ui.game3.rse.factory_select").Common + +-- pokeemerald/src/contest.c:3237 +local ContestUI = require("src.ui.game3.rse.contest") +ContestUI.has = function(key) return key == "gContestEffectDescriptionPointers[1]" end +ContestUI.plain = function(key) + if key == "gContestEffectDescriptionPointers[1]" then return "Startles the audience." end + return key +end +local function contestMoveDescription() + local view = setmetatable({ + c = { data = { moves = { [33] = { category = 0, effect = 1 } }, effects = { [1] = { appeal = 20, jam = 0 } } } }, + win = {}, + fillBox = function() end, + fillBoxInc = function() end, + }, { __index = ContestUI }) + view:printContestMoveDescription(33) + return view.win[10].text +end + +-- pokeemerald/src/region_map.c:1568 +Mapsec.pack = function() + return { count = 1, sections = { [0] = { name = "LITTLEROOT TOWN" } } } +end + +-- pokeemerald/src/pokemon_summary_screen.c:3116: the trainer memo's met +-- location is a map section name. +local RseSummary = require("src.ui.game3.rse.summary_menu") +local PalText = require("src.ui.game3.rse.pal_text") +local drawInfo +for i = 1, 60 do + local name, value = debug.getupvalue(RseSummary.draw, i) + if not name then break end + if name == "drawInfo" then drawInfo = value; break end +end +assert(drawInfo, "RSE summary info page drawer") +RomText.ir = function() return {} end +RomText.at = function() return {} end +PalText.width = function() return 0 end +Pokemon.types = function() return {} end +Pokemon.speciesOf = function() return 252 end +SummaryData.nature = function() return 0 end +local function metLocation() + local shown + local draw = PalText.draw + PalText.draw = function(_, _, _, ctx) + if ctx and ctx.dynamic then shown = ctx.dynamic[4] end + end + local w = { left = 0, top = 0, paletteNum = 0 } + local m = { palette = {}, textColors = {}, windows = {}, pageWindows = { info = { w, w, w, w } } } + local describe = SummaryData.abilityDescription + SummaryData.abilityDescription = function() return "" end + drawInfo(m, { _playerState = { name = "MAY", trainerId = 1 } }, + { species = 252, ability = 9, otName = "MAY", otId = 1, metLevel = 5, metLocation = 0 }, false) + PalText.draw, SummaryData.abilityDescription = draw, describe + return shown +end + +-- A modded ability past the built-in id table resolves to a key from its name. +local Adapter = require("src.core.game3.battle.adapter") +Pokemon._abilityNames[200] = "NEW SKILL" +Pokemon._romAbilityNames[200] = "NEW SKILL" +local function adapterAbilityKey() + return Adapter.new({}):abilityOf({ ability = 200 }) +end + +Strings.load({}) +assert(Pokemon.abilityName(9) == "STATIC", + "an ability name keeps its English ROM value without translations") +assert(SummaryData.contestCategoryName("Cool") == "Cool", + "a contest category keeps its English source without translations") +assert(SummaryData.contestEffectDescription({ description = "Startles the audience." }) + == "Startles the audience.", + "a contest effect description keeps its English source without translations") +local english = info() +assert(english[3].text == "Wood Gecko gText_Pokemon", + "the category keeps its English source when no translation is loaded") +assert(english[#english].text == "A small gecko.", + "the description keeps its English source when no translation is loaded") +assert(starterCategory() == "Wood Gecko gText_Pokemon", + "the starter label keeps its English category without translations") +assert(FactoryCommon.categoryText(252) == "Wood Gecko gText_Pokemon", + "the Battle Factory keeps its English category without translations") +assert(contestMoveDescription() == "Startles the audience.", + "the contest move window keeps its English effect text without translations") +assert(Mapsec.name(0) == "LITTLEROOT TOWN", + "a map section keeps its English name without translations") +assert(Mapsec.name(99) == "", "an unknown map section has no name") +assert(metLocation() == "LITTLEROOT TOWN", + "the RSE summary keeps the English met location without translations") +assert(adapterAbilityKey() == "NEW_SKILL", "the battle adapter keys an unlisted ability by its ROM name") + +Strings.load({ strings = { + STATIC = "STATIQUE", + Cool = "Sang-froid", + ["Startles the audience."] = "Surprend le public.", + ["Wood Gecko"] = "Gecko des bois", + ["A small gecko."] = "Un petit gecko.", + ["LITTLEROOT TOWN"] = "BOURG-EN-VOL", + ["NEW SKILL"] = "NOUVEAU TALENT", +} }) +assert(Pokemon.abilityName(9) == "STATIQUE", + "the shared ability-name getter translates the displayed name") +assert(Pokemon.romAbilityName(9) == "STATIC", + "the original ROM name stays available for stable ID lookups") +assert(BattleText.RESOLVE_RSE[0x17]({ lastAbility = 9 }) == "STATIQUE", + "the RSE battle ability placeholder uses the translated shared getter") +assert(SummaryData.contestCategoryName("Cool") == "Sang-froid", + "the move relearner translates the contest category source") +assert(SummaryData.contestEffectDescription({ description = "Startles the audience." }) + == "Surprend le public.", + "the RSE summary and move relearner translate the contest effect source") +local french = info() +assert(french[3].text == "Gecko des bois gText_Pokemon", + "the RSE Pokédex translates the source category at presentation time") +assert(french[#french].text == "Un petit gecko.", + "the RSE Pokédex translates the source description at presentation time") +assert(starterCategory() == "Gecko des bois gText_Pokemon", + "the starter label translates the category like the Pokédex") +assert(FactoryCommon.categoryText(252) == "Gecko des bois gText_Pokemon", + "the Battle Factory translates the category like the Pokédex") +assert(contestMoveDescription() == "Surprend le public.", + "the contest move window translates the effect text like the summary") +assert(Mapsec.name(0) == "BOURG-EN-VOL", + "the Emerald map section name goes through the registry") +assert(metLocation() == "BOURG-EN-VOL", + "the RSE summary translates the met location like the region map") +assert(Pokemon.abilityName(200) == "NOUVEAU TALENT", "the display name is translated") +assert(adapterAbilityKey() == "NEW_SKILL", + "the battle adapter keeps keying on the ROM name when the display name is translated") + +Strings.load({}) +local reloaded = info() +assert(reloaded[#reloaded].text == "A small gecko.", + "reloading without a catalog restores the English description") + +print("game3_emerald_translation_runtime_test: PASS") diff --git a/tests/engine/game3_summary_layout_s3_test.lua b/tests/engine/game3_summary_layout_s3_test.lua index 449fc9a4..1bcaf43e 100644 --- a/tests/engine/game3_summary_layout_s3_test.lua +++ b/tests/engine/game3_summary_layout_s3_test.lua @@ -50,12 +50,13 @@ SummaryChrome.manifest = function() return MANIFEST end local SESSION = nil package.loaded["src.core.game3.runtime"] = { getSession = function() return SESSION end } local SummaryData = require("src.core.game3.summary_data") +local Strings = require("src.core.Strings") SummaryData.moveDescription = function() return "" end SummaryData.abilityDescription = function() return "" end local realMemo = SummaryData.formatTrainerMemo SummaryData.formatTrainerMemo = function() return {} end SummaryData.expProgress = function() return { totalExp = 0, expNeeded = 0, progressPercent = 0 } end -Pokemon.abilityName = function() return "STATIC" end +Pokemon.abilityName = function() return Strings("STATIC") end Pokemon.abilityId = function() return 9 end Pokemon.monFrontPic = function() calls[#calls + 1] = { name = "pic", args = {} } return nil end Pokemon.monIcon = function() calls[#calls + 1] = { name = "icon", args = {} } return nil end @@ -215,6 +216,29 @@ local def = text_at("7") eq(def and def.x, 210 + 27 - 6, "a one-digit stat right-aligns too") SummaryMenu.close() +print("[test] 5b. translated ability names are looked up once") +Strings.load({ strings = { STATIC = "STATIQUE", STATIQUE = "NOM ENCORE TRADUIT" } }) +local numericAbility = mon() +numericAbility.ability = 9 +SummaryMenu.openMenu({ numericAbility }, 1, { page = SummaryMenu.PAGE_SKILLS }) +draw() +check(text_at("STATIQUE") ~= nil, + "the numeric ability getter's translated name is not translated a second time") +check(text_at("NOM ENCORE TRADUIT") == nil, + "a chained catalog entry does not change the numeric ability label") +SummaryMenu.close() + +local externalAbility = mon() +externalAbility.ability = "STATIC" +SummaryMenu.openMenu({ externalAbility }, 1, { page = SummaryMenu.PAGE_SKILLS }) +draw() +check(text_at("STATIQUE") ~= nil, + "a raw external ability label is translated once at the summary consumer") +check(text_at("NOM ENCORE TRADUIT") == nil, + "a raw external ability label also avoids chained translation") +SummaryMenu.close() +Strings.load({}) + local function text_xy(x, y) for _, t in ipairs(texts) do if t.x == x and t.y == y then return t end end return nil diff --git a/tests/game3_summary_description_test.lua b/tests/game3_summary_description_test.lua index f1a6d0cb..6b4b7f16 100644 --- a/tests/game3_summary_description_test.lua +++ b/tests/game3_summary_description_test.lua @@ -30,6 +30,15 @@ check(SummaryData.moveDescription(71, "VOL-VIE"):find("absorbs half", 1, true) ~ "a renamed move still finds its description") check(SummaryData.abilityDescription(9, "STATIK") == "Paralyzes on contact.", "a renamed ability still finds its description") +check(Pokemon.abilityName(9) == "STATIK", + "an ability name falls back to the current displayed source") + +Strings.load({ strings = { STATIK = "STATIQUE" } }) +check(Pokemon.abilityName(9) == "STATIQUE", + "the shared ability-name getter translates the current displayed name") +check(Pokemon.romAbilityName(9) == "STATIC", + "the ROM ability name remains stable for ID-based lookups") +Strings.load({}) Strings.load({ strings = { ["Paralyzes on contact."] = "Paralyse au contact." } }) check(SummaryData.abilityDescription(9, "STATIK") == "Paralyse au contact.",