mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-27 14:02:01 -04:00
f11b9c762f
Badge lines are end-battle texts (SaveEndBattleTextPointers), so they are
printed while the battle sound engine is still loaded. A text sound command
names an id rather than a sound, and that engine's SFX_Headers_2 gives those
ids different sounds: the id behind sound_get_item_1 / sound_level_up is
SFX_Level_Up there, and the id behind sound_get_key_item is SFX_Ball_Poof.
The port resolved the jingle by name, so it always played the overworld
reading (Get_Item1 / Get_Key_Item) -- the wrong jingle for receiving the
BOULDERBADGE, and the same collision for Cerulean, Saffron, Cinnabar and
Viridian in Red, Blue and Yellow alike.
Add victories.badgeSoundFor(), which answers with the jingle that is actually
audible for the badge hand-over:
Red/Blue Brock Level_Up, Misty Ball_Poof, Sabrina Ball_Poof,
Blaine Ball_Poof, Giovanni Level_Up
Yellow the same, except Misty: CeruleanGym.asm drops the command from
that line, so Yellow's Cascade Badge has no jingle
Vermilion, Celadon and Fuchsia carry no sound on their badge lines in any
version. The raw `badgeSound` field keeps the overworld-engine name, which is
what the TM texts (printed in the overworld) genuinely need.
Tests: pin the literal per-version jingle and its page for all five badge
leaders in tests/engine/gym_badge_jingle_page_bug1982.lua (53 checks), and
have the #1606 and driver suites assert through badgeSoundFor.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
150 lines
5.7 KiB
Lua
150 lines
5.7 KiB
Lua
-- A gym leader's badge line prints from TrainerBattleVictory (#1606):
|
|
-- scripts/CeruleanGym.asm:111, PewterGym.asm:117, home/trainers.asm:341
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local T = require("tests.modkit")
|
|
local gyms = require("data.scripts.gyms")
|
|
local victories = require("data.scripts.victories")
|
|
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
|
|
|
|
-- one page of text per label, so a joined chain is readable in a failure
|
|
local text = {}
|
|
for _, key in ipairs({ "OPP_BROCK#1", "OPP_MISTY#1", "OPP_LT_SURGE#1",
|
|
"OPP_ERIKA#1", "OPP_KOGA#1", "OPP_SABRINA#1",
|
|
"OPP_BLAINE#1", "OPP_GIOVANNI#3" }) do
|
|
for _, label in ipairs(victories[key].dialogue or {}) do
|
|
text[label] = "text:" .. label
|
|
end
|
|
for _, label in ipairs(victories[key].tmPre or {}) do
|
|
text[label] = "text:" .. label
|
|
end
|
|
for _, label in ipairs(victories[key].tmDialogue or {}) do
|
|
text[label] = "text:" .. label
|
|
end
|
|
end
|
|
|
|
local fakeGame = { data = { text = text }, save = { flags = {} } }
|
|
|
|
local armed, armedSound, armedSoundPage
|
|
local fakeOw = {
|
|
engageTrainer = function(_, _, _, endBattleText, _, endBattleSound, _,
|
|
endBattleSoundPage)
|
|
armed, armedSound, armedSoundPage =
|
|
endBattleText, endBattleSound, endBattleSoundPage
|
|
end,
|
|
}
|
|
|
|
local function armedFor(mapId, textId, victoryKey)
|
|
armed, armedSound, armedSoundPage = nil, nil, nil
|
|
gyms[mapId].talk[textId](fakeGame, fakeOw, { id = "npc#1" }, function() end)
|
|
local labels = victories[victoryKey].dialogue
|
|
local want = {}
|
|
for i, label in ipairs(labels) do want[i] = text[label] end
|
|
return armed, table.concat(want, "\f")
|
|
end
|
|
|
|
-- every leader, in badge order
|
|
local leaders = {
|
|
{ "PEWTER_GYM", "TEXT_PEWTERGYM_BROCK", "OPP_BROCK#1" },
|
|
{ "CERULEAN_GYM", "TEXT_CERULEANGYM_MISTY", "OPP_MISTY#1" },
|
|
{ "VERMILION_GYM", "TEXT_VERMILIONGYM_LT_SURGE", "OPP_LT_SURGE#1" },
|
|
{ "CELADON_GYM", "TEXT_CELADONGYM_ERIKA", "OPP_ERIKA#1" },
|
|
{ "FUCHSIA_GYM", "TEXT_FUCHSIAGYM_KOGA", "OPP_KOGA#1" },
|
|
{ "SAFFRON_GYM", "TEXT_SAFFRONGYM_SABRINA", "OPP_SABRINA#1" },
|
|
{ "CINNABAR_GYM", "TEXT_CINNABARGYM_BLAINE", "OPP_BLAINE#1" },
|
|
{ "VIRIDIAN_GYM", "TEXT_VIRIDIANGYM_GIOVANNI", "OPP_GIOVANNI#3" },
|
|
}
|
|
for _, entry in ipairs(leaders) do
|
|
local got, want = armedFor(entry[1], entry[2], entry[3])
|
|
T.eq(got, want, entry[3] .. " arms its badge line for the battle screen")
|
|
-- the dialogue's sound command rides the armed line onto the battle
|
|
-- screen too (sound_get_item_1 / sound_get_key_item) (#1606)
|
|
T.eq(armedSound, victories.badgeSoundFor(entry[3]),
|
|
entry[3] .. " arms its badge jingle beside the line")
|
|
end
|
|
|
|
-- Brock's armed label is one text chain of two text_far pages
|
|
-- (PewterGymBrockReceivedBoulderBadgeText), so both ride the battle screen
|
|
local brock = select(1, armedFor("PEWTER_GYM", "TEXT_PEWTERGYM_BROCK",
|
|
"OPP_BROCK#1"))
|
|
T.check(brock:find("\f", 1, true) ~= nil,
|
|
"Brock's badge line keeps its BoulderBadgeInfo page")
|
|
|
|
-- the beaten branch still talks instead of re-engaging
|
|
armed = nil
|
|
fakeGame.save.flags.EVENT_BEAT_MISTY = true
|
|
fakeGame.save.flags.EVENT_GOT_TM11 = true
|
|
local realStack = { push = function() end }
|
|
gyms.CERULEAN_GYM.talk.TEXT_CERULEANGYM_MISTY(
|
|
{ data = { text = text }, save = fakeGame.save, stack = realStack },
|
|
fakeOw, { id = "npc#1" }, function() end)
|
|
T.eq(armed, nil, "a beaten leader does not re-arm the badge line")
|
|
fakeGame.save.flags.EVENT_BEAT_MISTY = nil
|
|
fakeGame.save.flags.EVENT_GOT_TM11 = nil
|
|
|
|
-- checkVictoryRewards must not reprint what the battle screen showed
|
|
local boxes
|
|
local textBoxStub = {
|
|
new = function(_, str, onDone)
|
|
boxes[#boxes + 1] = str
|
|
return { onDone = onDone }
|
|
end,
|
|
soundOpts = function() return nil end,
|
|
}
|
|
local pushed
|
|
local rewardGame = {
|
|
data = { text = text, items = { TM_BUBBLEBEAM = { name = "TM11" } } },
|
|
save = { flags = {}, inventory = {}, player = { name = "RED" } },
|
|
stack = { push = function(_, box) pushed[#pushed + 1] = box end },
|
|
}
|
|
T.check(setUpvalue(OW.checkVictoryRewards, "Game", rewardGame),
|
|
"Game upvalue on checkVictoryRewards")
|
|
-- TextBox is only named inside the rewardChain closure; the chunk-level
|
|
-- upvalue cell is shared, so any closure that names it will do
|
|
T.check(setUpvalue(OW.engageTrainer, "TextBox", textBoxStub),
|
|
"TextBox upvalue on the reward chain")
|
|
|
|
local fakeSelf = setmetatable({
|
|
map = { id = "CERULEAN_GYM", def = { label = "CeruleanGym" } },
|
|
runVictoryHook = function() end,
|
|
}, { __index = OW })
|
|
|
|
local function rewardPages(shownOnBattleScreen)
|
|
boxes, pushed = {}, {}
|
|
rewardGame.save.flags = {}
|
|
rewardGame.save.inventory = {}
|
|
fakeSelf:checkVictoryRewards("OPP_MISTY", 1, shownOnBattleScreen)
|
|
-- the chain pushes one box at a time; walk it to the end
|
|
local i = 1
|
|
while pushed[i] do
|
|
local box = pushed[i]
|
|
i = i + 1
|
|
if box.onDone then box.onDone() end
|
|
end
|
|
return table.concat(boxes, "\f")
|
|
end
|
|
|
|
local badge = text["_CeruleanGymMistyReceivedCascadeBadgeText"]
|
|
local onMap = rewardPages(false)
|
|
T.check(onMap:find(badge, 1, true) ~= nil,
|
|
"without the battle-screen line the reward chain still shows the badge text")
|
|
local afterBattleScreen = rewardPages(true)
|
|
T.eq(afterBattleScreen:find(badge, 1, true), nil,
|
|
"the badge line is not reprinted on the map once the battle screen showed it")
|
|
T.check(afterBattleScreen:find(text["_CeruleanGymMistyCascadeBadgeInfoText"],
|
|
1, true) ~= nil,
|
|
"the TM hand-over still runs on the map")
|
|
T.check(rewardGame.save.inventory.CASCADEBADGE == 1, "the badge is still given")
|
|
|
|
T.finish("gym end battle text (#1606)")
|