diff --git a/data/scripts/story4.lua b/data/scripts/story4.lua index 943d246a..30b2827e 100644 --- a/data/scripts/story4.lua +++ b/data/scripts/story4.lua @@ -645,12 +645,44 @@ local function e4ExitSeal(flag, closedBlock, openBlock, dontRunText, autoFlag) } end +-- Lorelei/Bruno/Agatha EndBattleScript (pokered): EndTrainerBattle then +-- DisplayTextID -> TalkToTrainer, which prints the AfterBattle text on a +-- win. engageTrainer only shows the won line, so wrap talk like Lance / +-- Game Corner Rocket and push header.after immediately after a win. +local function e4LeaderTalk(afterLabel) + return function(game, ow, npc, done) + done = done or function() end + if ow:trainerDefeated(npc) then + local after = text(game)[afterLabel] + if after then push(game, after, done) else done() end + return + end + ow:engageTrainer(npc, function() + if not ow:trainerDefeated(npc) then + done() + return + end + local after = text(game)[afterLabel] + if after then push(game, after, done) else done() end + end) + end +end + M.LORELEIS_ROOM = e4ExitSeal("EVENT_BEAT_LORELEIS_ROOM_TRAINER_0", 0x24, 0x05, "_LoreleisRoomLoreleiDontRunAwayText", "EVENT_AUTOWALKED_INTO_LORELEIS_ROOM") +M.LORELEIS_ROOM.talk = { + TEXT_LORELEISROOM_LORELEI = e4LeaderTalk("_LoreleisRoomLoreleiAfterBattleText"), +} M.BRUNOS_ROOM = e4ExitSeal("EVENT_BEAT_BRUNOS_ROOM_TRAINER_0", 0x24, 0x05, "_BrunosRoomBrunoDontRunAwayText", "EVENT_AUTOWALKED_INTO_BRUNOS_ROOM") +M.BRUNOS_ROOM.talk = { + TEXT_BRUNOSROOM_BRUNO = e4LeaderTalk("_BrunoAfterBattleText"), +} M.AGATHAS_ROOM = e4ExitSeal("EVENT_BEAT_AGATHAS_ROOM_TRAINER_0", 0x3b, 0x0e, "_AgathasRoomAgathaDontRunAwayText", "EVENT_AUTOWALKED_INTO_AGATHAS_ROOM") +M.AGATHAS_ROOM.talk = { + TEXT_AGATHASROOM_AGATHA = e4LeaderTalk("_AgathaAfterBattleText"), +} -- ------------------------------------------------------------------- -- Lance's room (scripts/LancesRoom.asm). Unlike the other three E4 diff --git a/tests/parity_e4_after.lua b/tests/parity_e4_after.lua new file mode 100644 index 00000000..d8521d62 --- /dev/null +++ b/tests/parity_e4_after.lua @@ -0,0 +1,109 @@ +-- Parity: Lorelei / Bruno / Agatha push AfterBattle text immediately on +-- win (pokered *EndBattleScript -> DisplayTextID -> TalkToTrainer), not +-- only on a later re-talk. Lance is covered by parity_lance.lua. +-- +-- Sources: scripts/LoreleisRoom.asm, BrunosRoom.asm, AgathasRoom.asm +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end +local Data = require("src.core.Data") +if not (Data.maps and Data.maps.LORELEIS_ROOM) then Data:load() end +local Font = require("src.render.Font") +if not pcall(Font.encode, "A") then Font.load(Data) end +local S = require("tests.harness").suite("parity e4 after") +local check, eq = S.check, S.eq + +local mapScripts = require("data.scripts.init") + +local CASES = { + { + map = "LORELEIS_ROOM", text = "TEXT_LORELEISROOM_LORELEI", + after = "_LoreleisRoomLoreleiAfterBattleText", + headerMap = "LoreleisRoom", event = "EVENT_BEAT_LORELEIS_ROOM_TRAINER_0", + npcName = "LORELEISROOM_LORELEI", class = "OPP_LORELEI", + }, + { + map = "BRUNOS_ROOM", text = "TEXT_BRUNOSROOM_BRUNO", + after = "_BrunoAfterBattleText", + headerMap = "BrunosRoom", event = "EVENT_BEAT_BRUNOS_ROOM_TRAINER_0", + npcName = "BRUNOSROOM_BRUNO", class = "OPP_BRUNO", + }, + { + map = "AGATHAS_ROOM", text = "TEXT_AGATHASROOM_AGATHA", + after = "_AgathaAfterBattleText", + headerMap = "AgathasRoom", event = "EVENT_BEAT_AGATHAS_ROOM_TRAINER_0", + npcName = "AGATHASROOM_AGATHA", class = "OPP_AGATHA", + }, +} + +for _, c in ipairs(CASES) do + local after = Data.text[c.after] + check(after ~= nil, c.after .. " extracted") + local header = Data:trainerHeader(c.headerMap, 1) + check(header and header.after == c.after, + c.headerMap .. " header wires after-battle label") + + local talk = mapScripts.talkScript(c.map, c.text) + check(type(talk) == "function", c.map .. " talk wraps engageTrainer") + + local pushed = {} + local game = { + data = Data, + save = { + flags = {}, + defeatedTrainers = {}, + player = { name = "RED", rival = "BLUE" }, + }, + stack = { + push = function(_, state) pushed[#pushed + 1] = state end, + }, + } + local npc = { + def = { name = c.npcName, index = 1, trainerClass = c.class, + trainerParty = 1, text = c.text }, + id = c.map .. ":1", + facePlayer = function() end, + } + local engaged = false + local ow = { + trainerDefeated = function(_, n) + return game.save.defeatedTrainers[n.id] == true + end, + engageTrainer = function(_, n, onDone) + engaged = n == npc + game.save.defeatedTrainers[n.id] = true + game.save.flags[c.event] = true + if onDone then onDone() end + end, + } + + talk(game, ow, npc, function() end) + check(engaged, c.map .. " engages on first talk") + check(#pushed == 1 and pushed[1].pages ~= nil, + c.map .. " win pushes after-battle TextBox") + check(#pushed[1].pages > 0, c.map .. " after-battle TextBox has pages") + + -- loss: no after text + pushed, engaged = {}, false + game.save.defeatedTrainers = {} + game.save.flags = {} + ow.engageTrainer = function(_, n, onDone) + engaged = true + if onDone then onDone() end + end + talk(game, ow, npc, function() end) + check(engaged, c.map .. " still engages on loss path") + eq(#pushed, 0, c.map .. " loss does not push after-battle text") + + -- re-talk after win still shows after text (TalkToTrainer after branch) + pushed = {} + game.save.defeatedTrainers[npc.id] = true + game.save.flags[c.event] = true + ow.engageTrainer = function() + error(c.map .. " re-talk must not re-engage") + end + talk(game, ow, npc, function() end) + check(#pushed == 1 and pushed[1].pages ~= nil, + c.map .. " defeated re-talk shows after text") +end + +S.finish()