mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
247 lines
9.5 KiB
Lua
247 lines
9.5 KiB
Lua
-- Gen 2's battle engine and battle screen must look up authored text at the
|
|
-- point it is emitted/drawn. Names stay runtime arguments; the catalog keys
|
|
-- are stable English templates that modkit can harvest.
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local T = require("tests.harness")
|
|
|
|
love = require("tests.love_stub")
|
|
require("src.core.Logger").warn = function() end
|
|
|
|
local Strings = require("src.core.Strings")
|
|
local Effects = require("src.battle.gen2.Effects")
|
|
local Battle = require("src.battle.gen2.Battle")
|
|
local BattleState = require("src.ui.gen2.BattleState")
|
|
local Chrome = require("src.ui.gen2.Chrome")
|
|
local GbcPalette = require("src.render.GbcPalette")
|
|
|
|
Strings.load({ strings = {
|
|
["%s must recharge!"] = "%s doit recharger!",
|
|
["%s's %s sharply rose!"] = "%s: %s monte beaucoup!",
|
|
["%s's %s was disabled!"] = "%s: %s neutralisee!",
|
|
["%s TRANSFORMED into %s!"] = "%s copie %s!",
|
|
["%s used %s!"] = "%s emploie %s!",
|
|
["It started to rain!"] = "La pluie commence!",
|
|
["%s fell asleep!"] = "%s s'endort!",
|
|
["%s was poisoned!"] = "%s est empoisonne!",
|
|
["%s was badly poisoned!"] = "%s est gravement empoisonne!",
|
|
["%s is paralyzed! It may be unable to move!"] = "%s est paralyse!",
|
|
["%s was burned!"] = "%s est brule!",
|
|
["%s was frozen solid!"] = "%s est gele!",
|
|
["%s is hurt by poison!"] = "%s souffre du poison!",
|
|
["ATTACK"] = "ATTAQUE",
|
|
["FIGHT"] = "COMBAT",
|
|
["<PK><MN>"] = "EQUIPE",
|
|
["PACK"] = "SAC",
|
|
["RUN"] = "FUITE",
|
|
["YES"] = "OUI",
|
|
["NO"] = "NON",
|
|
["PSN"] = "PSN-CATALOGUE",
|
|
-- Registry-authored display values and already formatted messages must not
|
|
-- be fed through the source catalog a second time.
|
|
["PSN-FR"] = "MAUVAISE-SECONDE-TRADUCTION",
|
|
["OVERRIDE"] = "OVR-FR",
|
|
["CUSTOM"] = "NOUVEAU-FR",
|
|
["TYPE LOCALISE"] = "MAUVAIS-TYPE-RETRADUIT",
|
|
["%s is already out."] = "%s SORT DEJA.",
|
|
["PIKA SORT DEJA."] = "MAUVAISE-SECONDE-TRADUCTION",
|
|
["There's no will to battle!"] = "AUCUNE VOLONTE!",
|
|
["AUCUNE VOLONTE!"] = "MAUVAISE-SECONDE-TRADUCTION",
|
|
["PARKBALL\xc3\x97%02d"] = "BALLS:%02d",
|
|
} })
|
|
|
|
local function battleStub(data)
|
|
local player = { nickname = "DITTO", species = "DITTO", hp = 20,
|
|
maxHp = 20, stats = {}, moves = {}, volatile = {} }
|
|
local enemy = { nickname = "CIBLE", species = "RAW_SPECIES", hp = 20,
|
|
maxHp = 20, stats = {}, moves = {}, volatile = {} }
|
|
local battle = setmetatable({
|
|
data = data or {}, events = {}, player = player, enemy = enemy,
|
|
party = { player }, enemyParty = { enemy },
|
|
screens = { player = {}, enemy = {} },
|
|
stages = { player = Battle.newStages(), enemy = Battle.newStages() },
|
|
random = function() return 0 end,
|
|
}, { __index = Battle })
|
|
return battle, player, enemy
|
|
end
|
|
|
|
do
|
|
local mon = { nickname = "PIKACHU", volatile = { recharge = true } }
|
|
local battle = setmetatable({ data = {}, events = {}, player = mon },
|
|
{ __index = Battle })
|
|
T.check(not battle:canAct(mon), "recharge still spends the turn")
|
|
T.eq(battle.events[1] and battle.events[1].text,
|
|
"PIKACHU doit recharger!",
|
|
"a translated battle template receives the runtime battler name")
|
|
end
|
|
|
|
T.eq(Effects.stageMessage("PIKACHU", "attack", 2),
|
|
"PIKACHU: ATTAQUE monte beaucoup!",
|
|
"stat labels and the complete stage template are both translated")
|
|
|
|
do
|
|
local data = {
|
|
moves = { RAW_MOVE = { name = "JOLI MOUV" } },
|
|
items = { RAW_ITEM = { name = "BEL OBJET" } },
|
|
pokemon = {
|
|
DITTO = { name = "METAMORPH", types = { "NORMAL" } },
|
|
RAW_SPECIES = { name = "BELLE ESPECE", types = { "NORMAL" } },
|
|
},
|
|
}
|
|
local battle, player, enemy = battleStub(data)
|
|
enemy.moves = { { id = "RAW_MOVE", pp = 5 } }
|
|
enemy.volatile.lastMove = "RAW_MOVE"
|
|
Battle.MOVE_EFFECTS.EFFECT_DISABLE(battle, player, enemy)
|
|
T.eq(battle.events[#battle.events].text, "CIBLE: JOLI MOUV neutralisee!",
|
|
"a multi-argument template receives the move's display name, not its id")
|
|
|
|
battle.events = {}
|
|
Battle.MOVE_EFFECTS.EFFECT_TRANSFORM(battle, player, enemy)
|
|
T.eq(battle.events[1].text, "DITTO copie BELLE ESPECE!",
|
|
"TRANSFORM keeps the actor name and resolves the target species name")
|
|
|
|
battle.events = {}
|
|
battle.trainer = { name = "DRESSEUR", items = { "RAW_ITEM" } }
|
|
battle:enemyUseItem("RAW_ITEM")
|
|
T.eq(battle.events[#battle.events].text, "DRESSEUR emploie BEL OBJET!",
|
|
"trainer items resolve their display name before formatting")
|
|
|
|
battle.events = {}
|
|
Battle.MOVE_EFFECTS.EFFECT_RAIN_DANCE(battle, player, enemy)
|
|
T.eq(battle.events[1].text, "La pluie commence!",
|
|
"weather table sources are translated when emitted, after module load")
|
|
end
|
|
|
|
do
|
|
local expected = {
|
|
sleep = "MON s'endort!",
|
|
poison = "MON est empoisonne!",
|
|
toxic = "MON est gravement empoisonne!",
|
|
paralyze = "MON est paralyse!",
|
|
burn = "MON est brule!",
|
|
freeze = "MON est gele!",
|
|
}
|
|
for status, want in pairs(expected) do
|
|
local battle = battleStub({})
|
|
local mon = { nickname = "MON", hp = 16, maxHp = 16, volatile = {} }
|
|
battle.player = mon
|
|
T.check(battle:applyStatus(mon, status), status .. " can be inflicted")
|
|
T.eq(battle.events[#battle.events].text, want,
|
|
status .. " uses its complete translated template")
|
|
end
|
|
|
|
local battle = battleStub({})
|
|
local mon = { nickname = "MON", hp = 16, maxHp = 16,
|
|
status = "poison", volatile = {} }
|
|
battle.player = mon
|
|
battle:tickStatus(mon)
|
|
T.eq(battle.events[1].text, "MON souffre du poison!",
|
|
"a deferred residual status message is translated at emission")
|
|
end
|
|
|
|
do
|
|
local Registry = require("src.mods.Registry")
|
|
local Schemas = require("src.mods.Schemas")
|
|
local statuses = Registry.new("statuses", Schemas.REGISTRIES.statuses)
|
|
statuses.base = function() return Battle.STATUSES end
|
|
-- This is the exact label-only patch translation mods author. The vanilla
|
|
-- record must not retain a redundant hudLabel that shadows the patch.
|
|
statuses:patch("poison", { label = "PSN-FR" }, "translation")
|
|
statuses:register("custom", { id = "custom", label = "CUSTOM" }, "mod")
|
|
statuses:register("hidden", {
|
|
id = "hidden", label = "CUSTOM", substatus = true,
|
|
}, "mod")
|
|
local merged = {}
|
|
for id in pairs(Battle.STATUSES) do merged[id] = statuses:get(id) end
|
|
merged.custom = statuses:get("custom")
|
|
merged.hidden = statuses:get("hidden")
|
|
|
|
T.eq(merged.poison.hudLabel, nil,
|
|
"a real label-only Registry patch has no stale vanilla hudLabel")
|
|
local state = setmetatable({ contest = false,
|
|
game = { data = { gen2Statuses = merged } },
|
|
}, { __index = BattleState })
|
|
T.same(state:menuLabels(), { "COMBAT", "EQUIPE", "SAC", "FUITE" },
|
|
"the four battle menu labels are looked up without changing action ids")
|
|
T.eq(state:statusTag({ status = "poison" }, "player"), "PSN-FR",
|
|
"a label-only registry patch controls the HUD without a second lookup")
|
|
T.eq(state:statusTag({ status = "custom" }, "player"), "CUSTOM",
|
|
"a newly registered status label is complete authored display content")
|
|
T.eq(state:statusTag({ status = "hidden" }, "player"), nil,
|
|
"a modded substatus remains absent from the major-status HUD slot")
|
|
|
|
state.contest = true
|
|
state.save = { bugContest = { balls = 7 } }
|
|
T.eq(state:menuLabels()[3], "BALLS:07",
|
|
"the contest ball label and count use a harvested format template")
|
|
|
|
state.game = nil
|
|
T.eq(state:statusTag({ status = "poison" }, "player"), "PSN-CATALOGUE",
|
|
"only the built-in no-dataset fallback uses the strings catalog")
|
|
T.eq(state:statusTag({ status = "confuse" }, "player"), nil,
|
|
"built-in confusion remains a substatus and never gets a HUD tag")
|
|
end
|
|
|
|
do
|
|
local state = setmetatable({}, { __index = BattleState })
|
|
state:refuseSwitch(false, "%s is already out.", "PIKA")
|
|
T.eq(state.message, "PIKA SORT DEJA.",
|
|
"refuseSwitch translates and formats its source exactly once")
|
|
state:refuseShift()
|
|
T.eq(state.message, "AUCUNE VOLONTE!",
|
|
"refuseShift does not look up its translated fallback a second time")
|
|
end
|
|
|
|
do
|
|
local drawn = {}
|
|
local originalPrint = Chrome.printThrough
|
|
Chrome.printThrough = function(text) drawn[#drawn + 1] = text end
|
|
local state = setmetatable({
|
|
game = { data = {
|
|
moves = { MOD_MOVE = { type = "MOD_TYPE" } },
|
|
type_chart = { types = {
|
|
MOD_TYPE = { name = "TYPE LOCALISE", category = "special" },
|
|
} },
|
|
} },
|
|
battle = {
|
|
player = {},
|
|
moveDisabled = function() return false end,
|
|
},
|
|
}, { __index = BattleState })
|
|
state:drawMoveInfoBox({ id = "MOD_MOVE", pp = 3, maxPp = 5 })
|
|
Chrome.printThrough = originalPrint
|
|
local seen = {}
|
|
for _, text in ipairs(drawn) do seen[text] = true end
|
|
T.check(seen["TYPE LOCALISE"],
|
|
"the TYPE/ box uses the live type registry display name verbatim")
|
|
T.check(not seen["MOD_TYPE"] and not seen["MAUVAIS-TYPE-RETRADUIT"],
|
|
"the TYPE/ box renders neither the raw id nor a second catalog lookup")
|
|
end
|
|
|
|
do
|
|
local drawn = {}
|
|
Chrome.clear = function() end
|
|
Chrome.box = function() end
|
|
Chrome.cursorThrough = function() end
|
|
Chrome.printThrough = function(text) drawn[#drawn + 1] = text end
|
|
GbcPalette.setBgp = function() return nil end
|
|
|
|
local state = setmetatable({
|
|
battle = { player = {}, enemy = {} },
|
|
phase = "ask-nickname", messageTimer = 0, nicknameIndex = 1,
|
|
bottomUIVisible = function() return true end,
|
|
drawHud = function() end,
|
|
printMessage = function() end,
|
|
}, { __index = BattleState })
|
|
state:drawPanel()
|
|
local seen = {}
|
|
for _, text in ipairs(drawn) do seen[text] = true end
|
|
T.check(seen.OUI and seen.NON,
|
|
"battle YES/NO choices are translated at draw time")
|
|
end
|
|
|
|
Strings.load({})
|
|
T.check(not Strings.active(), "the catalog is unloaded after the test")
|
|
|
|
T.finish("gen2 battle translation")
|