mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-21 05:00:43 +02:00
Merge branch 'dev' of https://github.com/bryanthaboi/gen1recomp into dev
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
-- BattleState:throwBall()'s can't-be-caught path used to queue two
|
||||
-- separate Strings() literals ("It dodged the\nthrown BALL!" then "This
|
||||
-- POKéMON\ncan't be caught!"). The real ROM label _ItemUseBallText00
|
||||
-- combines both as one \f-paged string. TextBox.new() would split \f
|
||||
-- itself, but throwBall() queues through self:sayNext(), which goes
|
||||
-- through the battle queue's own BattleState:startMessage() -- and that
|
||||
-- one only splits on \n/\v, not \f (confirmed live: the \f landed
|
||||
-- mid-line and the second sentence overflowed off the box instead of
|
||||
-- starting a fresh page). The fix resolves the label once, then splits
|
||||
-- it the same way TextBox.lua does and queues one sayNext per page. This
|
||||
-- test fakes the label and checks the two pages reach the queue as two
|
||||
-- separate messages, in order, not merged into one with a raw \f still
|
||||
-- inside it.
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.modkit")
|
||||
local Data = T.fixtures.fresh()
|
||||
require("src.render.Font").load(Data)
|
||||
|
||||
local BattleState = require("src.battle.BattleState")
|
||||
local Pokemon = require("src.pokemon.Pokemon")
|
||||
local SaveData = require("src.core.SaveData")
|
||||
|
||||
local function mkbattle()
|
||||
local save = SaveData.newGame()
|
||||
save.party = { Pokemon.new(Data, "FIXMON_A", 10) }
|
||||
local game = { data = Data, save = save,
|
||||
stack = { top = function() return nil end, push = function() end } }
|
||||
local battle = BattleState.newWild(game, "FIXMON_C", 8)
|
||||
battle.ghost = true -- forces the can't-be-caught path
|
||||
return battle
|
||||
end
|
||||
|
||||
-- throwBall defers its message-queuing work into self.queue via one
|
||||
-- top-level self:act(fn); run only that one function to reach the
|
||||
-- say() calls the fix touches. It's the last entry throwBall itself
|
||||
-- appends (after the immediate sayAuto), and it further queues its own
|
||||
-- self:act(function() self:executeAction(...) end) for the actual enemy
|
||||
-- turn -- deliberately NOT run here (out of scope, and running the queue
|
||||
-- generically after mutation risks looping into a real turn simulation)
|
||||
local function runThrowBallAct(battle)
|
||||
for i = #battle.queue, 1, -1 do
|
||||
if battle.queue[i].fn then
|
||||
battle.queue[i].fn()
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function textEntries(battle)
|
||||
local out = {}
|
||||
for _, entry in ipairs(battle.queue) do
|
||||
if entry.text then out[#out + 1] = entry.text end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
-- translated: the faked label's two \f-separated pages reach the queue
|
||||
-- as two separate messages, in order, and neither one still contains a
|
||||
-- raw \f (which would mean the battle queue's own renderer has to deal
|
||||
-- with it, and it can't)
|
||||
do
|
||||
local battle = mkbattle()
|
||||
Data.text._ItemUseBallText00 = "FAKE-DODGE!\fFAKE-CANTCATCH!"
|
||||
battle:throwBall("FIX_BALL")
|
||||
runThrowBallAct(battle)
|
||||
local texts = textEntries(battle)
|
||||
T.eq(texts[1], "FAKE-DODGE!", "page 1 reaches the queue on its own")
|
||||
T.eq(texts[2], "FAKE-CANTCATCH!", "page 2 follows right after, still in order")
|
||||
for _, t in ipairs(texts) do
|
||||
T.check(not t:find("\f", 1, true), "no queued message still carries a raw \\f")
|
||||
end
|
||||
Data.text._ItemUseBallText00 = nil
|
||||
end
|
||||
|
||||
-- vanilla: no catalog entry still falls back to the two English pages,
|
||||
-- split the same way
|
||||
do
|
||||
local battle = mkbattle()
|
||||
battle:throwBall("FIX_BALL")
|
||||
runThrowBallAct(battle)
|
||||
local texts = textEntries(battle)
|
||||
T.eq(texts[1], "It dodged the\nthrown BALL!", "vanilla page 1")
|
||||
T.eq(texts[2], "This POKéMON\ncan't be caught!", "vanilla page 2")
|
||||
end
|
||||
|
||||
T.finish("battle_ball_dodge_romtext")
|
||||
@@ -0,0 +1,77 @@
|
||||
-- BattleState:storeCaughtMon() queues up to two plain-Lua-literal
|
||||
-- messages: the new-Pokedex-data line (_ItemUseBallText06) and, when the
|
||||
-- party is full, the box-transfer line (_ItemUseBallText07/08, keyed on
|
||||
-- EVENT_MET_BILL -- two full, independently-translated ROM strings, not
|
||||
-- one template with a substituted PC name). This test fakes all three
|
||||
-- labels and checks the queued messages use them, not the English
|
||||
-- literals.
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.modkit")
|
||||
local Data = T.fixtures.fresh()
|
||||
require("src.render.Font").load(Data)
|
||||
|
||||
local BattleState = require("src.battle.BattleState")
|
||||
local Pokemon = require("src.pokemon.Pokemon")
|
||||
local SaveData = require("src.core.SaveData")
|
||||
|
||||
local function findText(battle, needle)
|
||||
for _, entry in ipairs(battle.queue) do
|
||||
if entry.text and entry.text:find(needle, 1, true) then return entry.text end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function mkbattle(partySize, metBill)
|
||||
local save = SaveData.newGame()
|
||||
save.party = {}
|
||||
for i = 1, partySize do
|
||||
save.party[i] = Pokemon.new(Data, "FIXMON_A", 5)
|
||||
end
|
||||
save.flags = save.flags or {}
|
||||
save.flags.EVENT_MET_BILL = metBill
|
||||
local game = { data = Data, save = save,
|
||||
stack = { top = function() return nil end, push = function() end } }
|
||||
return BattleState.newWild(game, "FIXMON_C", 8)
|
||||
end
|
||||
|
||||
-- empty party: Party.add succeeds, only the new-Pokedex-data message fires
|
||||
do
|
||||
local battle = mkbattle(0, false)
|
||||
Data.text._ItemUseBallText06 = "FAKE-DEX {RAM:wEnemyMonNick} FAKE!"
|
||||
battle:storeCaughtMon()
|
||||
T.eq(findText(battle, "FAKE-DEX"), "FAKE-DEX " .. battle.enemy.name .. " FAKE!",
|
||||
"a translated _ItemUseBallText06 reaches the new-Pokedex-data message")
|
||||
Data.text._ItemUseBallText06 = nil
|
||||
end
|
||||
|
||||
-- full party, EVENT_MET_BILL true: box transfer via _ItemUseBallText07
|
||||
do
|
||||
local battle = mkbattle(6, true)
|
||||
Data.text._ItemUseBallText07 = "FAKE-BILL {RAM:wBoxMonNicks} FAKE!"
|
||||
battle:storeCaughtMon()
|
||||
T.eq(findText(battle, "FAKE-BILL"), "FAKE-BILL " .. battle.enemy.name .. " FAKE!",
|
||||
"EVENT_MET_BILL true routes through the translated _ItemUseBallText07")
|
||||
Data.text._ItemUseBallText07 = nil
|
||||
end
|
||||
|
||||
-- full party, EVENT_MET_BILL false: box transfer via _ItemUseBallText08
|
||||
do
|
||||
local battle = mkbattle(6, false)
|
||||
Data.text._ItemUseBallText08 = "FAKE-SOMEONE {RAM:wBoxMonNicks} FAKE!"
|
||||
battle:storeCaughtMon()
|
||||
T.eq(findText(battle, "FAKE-SOMEONE"), "FAKE-SOMEONE " .. battle.enemy.name .. " FAKE!",
|
||||
"EVENT_MET_BILL false routes through the translated _ItemUseBallText08")
|
||||
Data.text._ItemUseBallText08 = nil
|
||||
end
|
||||
|
||||
-- vanilla, full party, EVENT_MET_BILL true: English literal, BILL's PC
|
||||
do
|
||||
local battle = mkbattle(6, true)
|
||||
battle:storeCaughtMon()
|
||||
T.eq(findText(battle, "transferred"),
|
||||
battle.enemy.name .. " was\ntransferred to\nBILL's PC!",
|
||||
"no catalog entry falls back to the English BILL's-PC literal")
|
||||
end
|
||||
|
||||
T.finish("battle_catch_messages_romtext")
|
||||
@@ -0,0 +1,64 @@
|
||||
-- BattleState:onFaint's "%s\nfainted!" collapsed two distinct ROM
|
||||
-- strings (_EnemyMonFaintedText already carries its own "Enemy" wording;
|
||||
-- _PlayerMonFaintedText does not) into one literal, substituted with
|
||||
-- displayName(battler) -- which itself runs the enemy name through a
|
||||
-- SEPARATE Strings("Enemy %s", ...) call. The fix passes the raw
|
||||
-- battler.name and lets each label supply its own wording. This test
|
||||
-- fakes both labels and checks the raw name reaches the right one, with
|
||||
-- no "Enemy" ever duplicated.
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.modkit")
|
||||
local Data = T.fixtures.fresh()
|
||||
require("src.render.Font").load(Data)
|
||||
|
||||
local BattleState = require("src.battle.BattleState")
|
||||
local Pokemon = require("src.pokemon.Pokemon")
|
||||
local SaveData = require("src.core.SaveData")
|
||||
|
||||
local function mkbattle()
|
||||
local save = SaveData.newGame()
|
||||
save.party = { Pokemon.new(Data, "FIXMON_A", 10) }
|
||||
local game = { data = Data, save = save,
|
||||
stack = { top = function() return nil end, push = function() end } }
|
||||
return BattleState.newWild(game, "FIXMON_C", 8)
|
||||
end
|
||||
|
||||
-- finds the queued say-message text (onFaint queues several entries: a
|
||||
-- wait, then the say)
|
||||
local function findText(battle)
|
||||
for _, entry in ipairs(battle.queue) do
|
||||
if entry.text then return entry.text end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- enemy: translated _EnemyMonFaintedText reaches onFaint, raw name only
|
||||
do
|
||||
local battle = mkbattle()
|
||||
Data.text._EnemyMonFaintedText = "FAKE-ENEMY {RAM:wEnemyMonNick} FAKE!"
|
||||
battle:onFaint(battle.enemy)
|
||||
T.eq(findText(battle), "FAKE-ENEMY " .. battle.enemy.name .. " FAKE!",
|
||||
"a translated _EnemyMonFaintedText reaches the enemy faint message")
|
||||
Data.text._EnemyMonFaintedText = nil
|
||||
end
|
||||
|
||||
-- enemy vanilla: the English literal already carries "Enemy " itself
|
||||
do
|
||||
local battle = mkbattle()
|
||||
battle:onFaint(battle.enemy)
|
||||
T.eq(findText(battle), "Enemy " .. battle.enemy.name .. "\nfainted!",
|
||||
"no catalog entry falls back to the English literal, Enemy included")
|
||||
end
|
||||
|
||||
-- player: translated _PlayerMonFaintedText reaches onFaint
|
||||
do
|
||||
local battle = mkbattle()
|
||||
Data.text._PlayerMonFaintedText = "FAKE-PLAYER {RAM:wBattleMonNick} FAKE!"
|
||||
battle:onFaint(battle.player)
|
||||
T.eq(findText(battle), "FAKE-PLAYER " .. battle.player.name .. " FAKE!",
|
||||
"a translated _PlayerMonFaintedText reaches the player faint message")
|
||||
Data.text._PlayerMonFaintedText = nil
|
||||
end
|
||||
|
||||
T.finish("battle_fainted_message_romtext")
|
||||
@@ -0,0 +1,129 @@
|
||||
-- BoxMenu's RELEASE confirmation ("Once released,\n%s is\ngone forever.
|
||||
-- OK?") used to be a bare Lua literal. tests/engine/pc_release.lua only
|
||||
-- ever runs with an empty Data.text, so it can't tell a properly-wired
|
||||
-- t._OnceReleasedText or "..." fallback apart from a literal that never
|
||||
-- looked at t at all -- every assertion there passes either way. This
|
||||
-- test drives the same interactive release flow with a faked
|
||||
-- Data.text._OnceReleasedText and checks the pushed TextBox's raw text
|
||||
-- (captured via a TextBox.new spy, so real pagination/choice behavior is
|
||||
-- untouched) uses the translated value, not the English literal.
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.modkit")
|
||||
local Data = T.fixtures.fresh()
|
||||
local ids = T.fixtures.ids
|
||||
require("src.render.Font").load(Data)
|
||||
|
||||
local Pokemon = require("src.pokemon.Pokemon")
|
||||
local Boxes = require("src.pokemon.Boxes")
|
||||
local TextBox = require("src.render.TextBox")
|
||||
local BoxMenu = require("src.ui.BoxMenu")
|
||||
local ListMenu = require("src.ui.ListMenu")
|
||||
local ChoiceBox = require("src.ui.ChoiceBox")
|
||||
local SaveData = require("src.core.SaveData")
|
||||
local Sound = require("src.core.Sound")
|
||||
|
||||
local realCry, realPlay = Sound.playCry, Sound.play
|
||||
Sound.playCry = function() end
|
||||
Sound.play = function() end
|
||||
|
||||
-- spy: records every raw text TextBox.new receives, without disturbing
|
||||
-- pagination/choice re-push, so the interactive flow behaves exactly like
|
||||
-- pc_release.lua's
|
||||
local captured
|
||||
local realNew = TextBox.new
|
||||
TextBox.new = function(game, text, onDone, opts)
|
||||
captured[#captured + 1] = text
|
||||
return realNew(game, text, onDone, opts)
|
||||
end
|
||||
|
||||
local stack = { states = {} }
|
||||
function stack:push(s) self.states[#self.states + 1] = s end
|
||||
function stack:pop()
|
||||
local t = self.states[#self.states]
|
||||
self.states[#self.states] = nil
|
||||
return t
|
||||
end
|
||||
function stack:top() return self.states[#self.states] end
|
||||
function stack:update(dt)
|
||||
local t = self:top()
|
||||
if t and t.update then t:update(dt) end
|
||||
end
|
||||
|
||||
local pressed = {}
|
||||
local function press(btn)
|
||||
pressed = { [btn] = true }
|
||||
stack:update(1 / 60)
|
||||
pressed = {}
|
||||
end
|
||||
|
||||
local function topMt() return getmetatable(stack:top()) end
|
||||
local function mash(btn, cond, n)
|
||||
for _ = 1, (n or 400) do
|
||||
if cond() then return true end
|
||||
press(btn)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function mkGame()
|
||||
stack.states = {}
|
||||
captured = {}
|
||||
local game = {
|
||||
data = Data,
|
||||
save = SaveData.newGame(),
|
||||
stack = stack,
|
||||
input = {
|
||||
wasPressed = function(_, key) return pressed[key] or false end,
|
||||
isDown = function() return false end,
|
||||
},
|
||||
}
|
||||
game.save.options = game.save.options or {}
|
||||
game.save.options.textSpeed = 1
|
||||
local box = Boxes.active(game.save)
|
||||
box[1] = Pokemon.new(Data, ids.species[1], 5)
|
||||
return game, box
|
||||
end
|
||||
|
||||
local function releaseFirstMon(game)
|
||||
stack:push(BoxMenu.new(game))
|
||||
press("down"); press("down"); press("a") -- open RELEASE list
|
||||
T.check(topMt() == ListMenu, "RELEASE opens the box list")
|
||||
press("a") -- choose the first (only) mon
|
||||
T.check(mash("a", function() return topMt() == ChoiceBox end),
|
||||
"confirm choice opens")
|
||||
-- the confirmation TextBox is captured[1] the moment it was pushed,
|
||||
-- before this mash even ran
|
||||
return captured[1]
|
||||
end
|
||||
|
||||
-- monName (BoxMenu.lua): mon.nickname or def.name
|
||||
local function monName(box)
|
||||
local mon = box[1]
|
||||
local def = Data.pokemon[mon.species]
|
||||
return mon.nickname or def.name
|
||||
end
|
||||
|
||||
-- translated: the fake value must reach the pushed TextBox
|
||||
do
|
||||
local game, box = mkGame()
|
||||
local name = monName(box)
|
||||
Data.text._OnceReleasedText = "FAKE {RAM:wStringBuffer} released!"
|
||||
local text = releaseFirstMon(game)
|
||||
T.eq(text, "FAKE " .. name .. " released!",
|
||||
"a translated _OnceReleasedText reaches the release confirmation")
|
||||
Data.text._OnceReleasedText = nil
|
||||
end
|
||||
|
||||
-- vanilla: with no catalog entry, the English literal still substitutes
|
||||
do
|
||||
local game, box = mkGame()
|
||||
local name = monName(box)
|
||||
local text = releaseFirstMon(game)
|
||||
T.eq(text, "Once released,\n" .. name .. " is\ngone forever. OK?",
|
||||
"no catalog entry still falls back to the English literal")
|
||||
end
|
||||
|
||||
TextBox.new = realNew
|
||||
Sound.playCry, Sound.play = realCry, realPlay
|
||||
T.finish("box_release_confirmation_romtext")
|
||||
@@ -0,0 +1,139 @@
|
||||
-- Two OverworldController.lua messages used to be bare Lua literals:
|
||||
-- applyFieldPoison()'s "%s\nfainted!" (the third of three collapsed
|
||||
-- fainted-message ROM strings, _PokemonFaintedText) and
|
||||
-- useSoftboiledFieldMove()'s "It won't have\nany effect."/"%s's HP\nwas
|
||||
-- restored!" (the same _ItemUseNoEffectText/_PotionText labels
|
||||
-- ItemEffects.lua's real potion message already uses -- _PotionText's
|
||||
-- second slot is the actual amount healed, which the old literal never
|
||||
-- showed at all).
|
||||
--
|
||||
-- Uses the debug.setupvalue technique already established in
|
||||
-- oaks_pc_flow.lua to fake the module-level Game/TextBox upvalues
|
||||
-- ROM-free, without going through the heavy OverworldState:enter().
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.modkit")
|
||||
local Data = T.fixtures.fresh()
|
||||
|
||||
local SaveData = require("src.core.SaveData")
|
||||
local Pokemon = require("src.pokemon.Pokemon")
|
||||
local OW = require("src.world.OverworldController")
|
||||
|
||||
local function setUpvalue(fn, name, val)
|
||||
local i = 1
|
||||
while true do
|
||||
local n = debug.getupvalue(fn, i)
|
||||
if not n then return false end
|
||||
if n == name then debug.setupvalue(fn, i, val); return true end
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
local pushed = {}
|
||||
local textBoxStub = {
|
||||
new = function(_, text, onDone, opts)
|
||||
return { text = text, onDone = onDone, opts = opts }
|
||||
end,
|
||||
}
|
||||
local realSound = package.loaded["src.core.Sound"]
|
||||
package.loaded["src.core.Sound"] = { play = function() end, playCry = function() end }
|
||||
|
||||
local function mkGame()
|
||||
local save = SaveData.newGame()
|
||||
save.party = { Pokemon.new(Data, "FIXMON_A", 20) }
|
||||
pushed = {}
|
||||
return {
|
||||
data = Data, save = save,
|
||||
stack = { push = function(_, item) pushed[#pushed + 1] = item end },
|
||||
}
|
||||
end
|
||||
|
||||
for _, name in ipairs({ "applyFieldPoison", "useSoftboiledFieldMove" }) do
|
||||
T.check(setUpvalue(OW[name], "Game", mkGame()), ("Game upvalue on %s"):format(name))
|
||||
T.check(setUpvalue(OW[name], "TextBox", textBoxStub), ("TextBox upvalue on %s"):format(name))
|
||||
end
|
||||
|
||||
local fakeSelf = setmetatable({}, { __index = OW })
|
||||
|
||||
-- ---- applyFieldPoison: _PokemonFaintedText ----
|
||||
do
|
||||
local game = mkGame()
|
||||
setUpvalue(OW.applyFieldPoison, "Game", game)
|
||||
local mon = game.save.party[1]
|
||||
mon.status = "PSN"
|
||||
mon.hp = 1 -- one poison tick (1 dmg by default) faints it
|
||||
game.save.poisonSteps = 3 -- (3+1) % 4 == 0: this step ticks poison
|
||||
|
||||
Data.text._PokemonFaintedText = "FAKE {RAM:wNameBuffer} FAKE!"
|
||||
fakeSelf:applyFieldPoison()
|
||||
T.eq(pushed[1] and pushed[1].text, "FAKE " .. (mon.nickname or "FIXMON A") .. " FAKE!",
|
||||
"a translated _PokemonFaintedText reaches the field-poison faint message")
|
||||
Data.text._PokemonFaintedText = nil
|
||||
end
|
||||
|
||||
-- vanilla: no catalog entry, English literal
|
||||
do
|
||||
local game = mkGame()
|
||||
setUpvalue(OW.applyFieldPoison, "Game", game)
|
||||
local mon = game.save.party[1]
|
||||
mon.status = "PSN"
|
||||
mon.hp = 1
|
||||
game.save.poisonSteps = 3
|
||||
|
||||
fakeSelf:applyFieldPoison()
|
||||
T.eq(pushed[1] and pushed[1].text,
|
||||
(mon.nickname or "FIXMON A") .. "\nfainted!",
|
||||
"no catalog entry falls back to the English fainted literal")
|
||||
end
|
||||
|
||||
-- ---- useSoftboiledFieldMove: _ItemUseNoEffectText / _PotionText ----
|
||||
do
|
||||
local game = mkGame()
|
||||
setUpvalue(OW.useSoftboiledFieldMove, "Game", game)
|
||||
local user = Pokemon.new(Data, "FIXMON_A", 20)
|
||||
local target = Pokemon.new(Data, "FIXMON_B", 20)
|
||||
target.hp = target.stats.hp -- already full: no effect
|
||||
|
||||
Data.text._ItemUseNoEffectText = "FAKE-NOEFFECT!"
|
||||
local ok = fakeSelf:useSoftboiledFieldMove(user, target)
|
||||
T.check(ok == false, "a full-HP target reports no effect")
|
||||
T.eq(pushed[1] and pushed[1].text, "FAKE-NOEFFECT!",
|
||||
"a translated _ItemUseNoEffectText reaches the no-effect message")
|
||||
Data.text._ItemUseNoEffectText = nil
|
||||
end
|
||||
|
||||
do
|
||||
local game = mkGame()
|
||||
setUpvalue(OW.useSoftboiledFieldMove, "Game", game)
|
||||
local user = Pokemon.new(Data, "FIXMON_A", 20)
|
||||
local target = Pokemon.new(Data, "FIXMON_B", 20)
|
||||
target.hp = target.stats.hp - 10 -- missing exactly 10 HP
|
||||
|
||||
Data.text._PotionText = "FAKE {RAM:wNameBuffer} healed {NUM:wHPBarHPDifference, 2, 3}!"
|
||||
local ok = fakeSelf:useSoftboiledFieldMove(user, target)
|
||||
T.check(ok == true, "a damaged target heals successfully")
|
||||
T.eq(pushed[1] and pushed[1].text,
|
||||
"FAKE " .. (target.nickname or "FIXMON B") .. " healed 10!",
|
||||
"a translated _PotionText reaches the heal message, amount included")
|
||||
Data.text._PotionText = nil
|
||||
end
|
||||
|
||||
-- vanilla: no catalog entry, the fallback's single %s slot still fills
|
||||
-- correctly (the amount is silently dropped by design, same as
|
||||
-- ItemEffects.lua's own _PotionText fallback -- not a regression, this
|
||||
-- matches the pre-fix literal's behavior exactly)
|
||||
do
|
||||
local game = mkGame()
|
||||
setUpvalue(OW.useSoftboiledFieldMove, "Game", game)
|
||||
local user = Pokemon.new(Data, "FIXMON_A", 20)
|
||||
local target = Pokemon.new(Data, "FIXMON_B", 20)
|
||||
target.hp = target.stats.hp - 10
|
||||
local ok = fakeSelf:useSoftboiledFieldMove(user, target)
|
||||
T.check(ok == true, "a damaged target heals successfully (vanilla)")
|
||||
T.eq(pushed[1] and pushed[1].text,
|
||||
(target.nickname or "FIXMON B") .. "'s HP\nwas restored!",
|
||||
"no catalog entry falls back to the English literal (no amount shown)")
|
||||
end
|
||||
|
||||
package.loaded["src.core.Sound"] = realSound
|
||||
T.finish("overworld_field_faint_heal_romtext")
|
||||
@@ -0,0 +1,85 @@
|
||||
-- OverworldState:tryHiddenObject()'s "%s found\n%s!" message used to
|
||||
-- substitute both the player name and the item name into one bare Lua
|
||||
-- literal. The real _FoundHiddenItemText label leads with a {PLAYER}
|
||||
-- named token, which romText auto-fills from a 2-arg call in the same
|
||||
-- order the literal already used -- this test checks both slots land
|
||||
-- correctly (an accidental argument swap is the easy mistake this shape
|
||||
-- invites).
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.modkit")
|
||||
local Data = T.fixtures.fresh()
|
||||
|
||||
local SaveData = require("src.core.SaveData")
|
||||
local OW = require("src.world.OverworldController")
|
||||
|
||||
local function setUpvalue(fn, name, val)
|
||||
local i = 1
|
||||
while true do
|
||||
local n = debug.getupvalue(fn, i)
|
||||
if not n then return false end
|
||||
if n == name then debug.setupvalue(fn, i, val); return true end
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
local pushed = {}
|
||||
local textBoxStub = {
|
||||
new = function(_, text, onDone, opts)
|
||||
return { text = text, onDone = onDone, opts = opts }
|
||||
end,
|
||||
soundOpts = function() return {} end,
|
||||
}
|
||||
|
||||
local MAP_ID = "FIX_TOWN"
|
||||
Data.field.hiddenItems[MAP_ID] = { { x = 3, y = 3, item = "FIX_BALL" } }
|
||||
|
||||
local function mkGame()
|
||||
local save = SaveData.newGame()
|
||||
save.player.name = "FAKEPLAYER"
|
||||
pushed = {}
|
||||
return {
|
||||
data = Data, save = save,
|
||||
stack = { push = function(_, item) pushed[#pushed + 1] = item end },
|
||||
}
|
||||
end
|
||||
|
||||
T.check(setUpvalue(OW.tryHiddenObject, "Game", mkGame()), "Game upvalue on tryHiddenObject")
|
||||
T.check(setUpvalue(OW.tryHiddenObject, "TextBox", textBoxStub), "TextBox upvalue on tryHiddenObject")
|
||||
|
||||
local fakeSelf = setmetatable({ map = { id = MAP_ID } }, { __index = OW })
|
||||
|
||||
-- translated: player name and item name both land in the right slots
|
||||
do
|
||||
local game = mkGame()
|
||||
setUpvalue(OW.tryHiddenObject, "Game", game)
|
||||
Data.text._FoundHiddenItemText = "FAKE {PLAYER} found FAKE {RAM:wNameBuffer} FAKE!"
|
||||
local found = fakeSelf:tryHiddenObject(3, 3)
|
||||
T.check(found == true, "the hidden item at (3,3) is found")
|
||||
T.eq(pushed[1] and pushed[1].text,
|
||||
"FAKE FAKEPLAYER found FAKE FIX BALL FAKE!",
|
||||
"a translated _FoundHiddenItemText fills both {PLAYER} and the item name")
|
||||
Data.text._FoundHiddenItemText = nil
|
||||
end
|
||||
|
||||
-- vanilla: no catalog entry, so romText falls back to plain
|
||||
-- Strings(fallback, ...) -- the fallback literal is "%s found\n%s!" (both
|
||||
-- slots plain %s, matching the pre-fix literal's own shape), not the
|
||||
-- {PLAYER} token the real label uses, since Strings() never does
|
||||
-- {TOKEN} substitution on its own. (A {PLAYER}-token fallback would
|
||||
-- still render correctly too, since the real TextBox.new always runs
|
||||
-- TextBox.substitute over whatever text it's given -- but this test
|
||||
-- stubs TextBox without that call, and the fallback shouldn't lean on a
|
||||
-- substitution pass happening downstream regardless.)
|
||||
do
|
||||
local game = mkGame()
|
||||
setUpvalue(OW.tryHiddenObject, "Game", game)
|
||||
game.save.hiddenTaken = {} -- fresh spot
|
||||
local found = fakeSelf:tryHiddenObject(3, 3)
|
||||
T.check(found == true, "the hidden item is found again in a fresh game")
|
||||
T.eq(pushed[1] and pushed[1].text, "FAKEPLAYER found\nFIX BALL!",
|
||||
"with no catalog entry, the fallback still fills both the player "
|
||||
.. "and item name via plain %s substitution")
|
||||
end
|
||||
|
||||
T.finish("overworld_hidden_item_romtext")
|
||||
@@ -0,0 +1,39 @@
|
||||
-- SlotMachine:resolveWin's "%s lined up!\nScored %d coins!" message used
|
||||
-- to interpolate the symbol id AND the payout into one bare Lua literal.
|
||||
-- The real _LinedUpText label has no slot for the symbol at all (the
|
||||
-- original ROM drew it separately) -- only for the coin count. This test
|
||||
-- fakes _LinedUpText and checks the symbol is concatenated in front of the
|
||||
-- translated suffix, with the payout correctly substituted into it.
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.modkit")
|
||||
local Data = T.fixtures.fresh()
|
||||
|
||||
local SlotMachine = require("src.ui.SlotMachine")
|
||||
|
||||
local function mkSelf()
|
||||
local game = { data = Data, save = { coins = 0 } }
|
||||
return setmetatable({ game = game, allowMatchesCounter = 0 }, SlotMachine)
|
||||
end
|
||||
|
||||
-- translated: the fake suffix reaches self.message, with the symbol
|
||||
-- concatenated in front and the payout substituted into the fake text
|
||||
do
|
||||
local self = mkSelf()
|
||||
Data.text._LinedUpText = " FAKE-SUFFIX {RAM:wStringBuffer}!"
|
||||
self:resolveWin({ symbol = "CHERRY", payout = 8 })
|
||||
T.eq(self.message, "CHERRY FAKE-SUFFIX 8!",
|
||||
"a translated _LinedUpText reaches the lined-up message")
|
||||
Data.text._LinedUpText = nil
|
||||
end
|
||||
|
||||
-- vanilla: with no catalog entry, the English literal still substitutes,
|
||||
-- with its own leading space (the original had no slot for the symbol)
|
||||
do
|
||||
local self = mkSelf()
|
||||
self:resolveWin({ symbol = "CHERRY", payout = 8 })
|
||||
T.eq(self.message, "CHERRY lined up!\nScored 8 coins!",
|
||||
"no catalog entry still falls back to the English literal")
|
||||
end
|
||||
|
||||
T.finish("slot_machine_lined_up_romtext")
|
||||
Reference in New Issue
Block a user