Skip the gym leader TM hand-over when the bag is full

The originals run GiveItem before printing the received texts, and when
the bag can't hold the TM they print a make-room line instead and leave
EVENT_GOT_TM* unset, so talking to the leader again retries the give.
The victory reward path added the TM straight into the inventory, so a
full bag went to 21/20.

Route the gym TM give through Bag.add, split the TM lines out of the
victory dialogue table into tmPre/tmDialogue/noRoom, and port the
beaten-leader middle branch that re-runs the ReceiveTM script. Saves
that already hold the TM without the flag count as received so they
don't collect a second copy.

Refs #797
This commit is contained in:
johnjohto
2026-08-04 13:44:46 -04:00
parent 1820f411ae
commit bbcaac7b71
5 changed files with 219 additions and 34 deletions
+37 -14
View File
@@ -18,6 +18,26 @@ local M = {
VIRIDIAN_GYM = { city = "VIRIDIAN CITY", leader = "GIOVANNI", badge = "EARTHBADGE" }, VIRIDIAN_GYM = { city = "VIRIDIAN CITY", leader = "GIOVANNI", badge = "EARTHBADGE" },
} }
-- The originals' middle branch shared by every leader's text_asm: beaten
-- but EVENT_GOT_TM* unset means the bag was full when the victory script
-- ran GiveItem, so talking to the leader re-runs the ReceiveTM script.
-- Returns true when the retry took over the talk. A save from before
-- #797 already holds the TM without the flag; treat the owned TM as
-- received so those saves fall through to the advice text instead of
-- collecting a second copy.
local function retryTmGive(game, ow, victoryKey, done)
local reward = require("data.scripts.victories")[victoryKey]
if not (reward and reward.gotFlag) then return false end
if game.save.flags[reward.gotFlag] then return false end
local owned = game.save.inventory and game.save.inventory[reward.item] or 0
if owned > 0 then
game.save.flags[reward.gotFlag] = true
return false
end
ow:offerGymTm(reward, done)
return true
end
-- scripts/PewterGym.asm PewterGymBrockText (text_asm): CheckEvent -- scripts/PewterGym.asm PewterGymBrockText (text_asm): CheckEvent
-- EVENT_BEAT_BROCK branches his dialogue. Before the badge he prints -- EVENT_BEAT_BROCK branches his dialogue. Before the badge he prints
-- _PewterGymBrockPreBattleText and engages the leader battle -- _PewterGymBrockPreBattleText and engages the leader battle
@@ -25,12 +45,14 @@ local M = {
-- badge/TM34 rewards and EVENT_BEAT_BROCK come from -- badge/TM34 rewards and EVENT_BEAT_BROCK come from
-- data/scripts/victories.lua OPP_BROCK#1). After the badge his -- data/scripts/victories.lua OPP_BROCK#1). After the badge his
-- .afterBeat branch prints _PewterGymBrockPostBattleAdviceText ("Go to -- .afterBeat branch prints _PewterGymBrockPostBattleAdviceText ("Go to
-- the GYM in CERULEAN..."). The original's middle branch (beat but -- the GYM in CERULEAN..."). The middle branch (beat but TM34 not yet
-- TM34 not yet handed over, CheckEventReuseA EVENT_GOT_TM34) is -- handed over, CheckEventReuseA EVENT_GOT_TM34 -> call
-- unreachable in the port: the TM is granted with the victory. -- PewterGymScriptReceiveTM34) retries the TM give when the bag was full
-- at the victory (#797).
M.PEWTER_GYM.talk = { M.PEWTER_GYM.talk = {
TEXT_PEWTERGYM_BROCK = function(game, ow, npc, done) TEXT_PEWTERGYM_BROCK = function(game, ow, npc, done)
if game.save.flags.EVENT_BEAT_BROCK then if game.save.flags.EVENT_BEAT_BROCK then
if retryTmGive(game, ow, "OPP_BROCK#1", done) then return end
local TextBox = require("src.render.TextBox") local TextBox = require("src.render.TextBox")
game.stack:push(TextBox.new(game, game.stack:push(TextBox.new(game,
game.data.text._PewterGymBrockPostBattleAdviceText game.data.text._PewterGymBrockPostBattleAdviceText
@@ -48,16 +70,17 @@ M.PEWTER_GYM.talk = {
-- (engageTrainer shows that same pre-battle text via resolveText; the -- (engageTrainer shows that same pre-battle text via resolveText; the
-- badge/TM rewards and the beat flag come from data/scripts/victories.lua) -- badge/TM rewards and the beat flag come from data/scripts/victories.lua)
-- -- and once beaten print the post-battle advice text. As with Brock, -- -- and once beaten print the post-battle advice text. As with Brock,
-- the originals' middle branch (beaten but the TM not yet handed over, -- the middle branch (beaten but the TM not yet handed over,
-- CheckEventReuseA EVENT_GOT_TM*) is unreachable in the port: the TM is -- CheckEventReuseA EVENT_GOT_TM*) retries the TM give when the bag was
-- granted with the victory. -- full at the victory.
-- afterAdvice, when given, takes over `done`: it is handed (game, ow, npc, -- afterAdvice, when given, takes over `done`: it is handed (game, ow, npc,
-- done) and must call done() itself once whatever it's doing (e.g. a fade -- done) and must call done() itself once whatever it's doing (e.g. a fade
-- around a HideObject) finishes, rather than having it invoked -- around a HideObject) finishes, rather than having it invoked
-- automatically. Only Giovanni's farewell uses this. -- automatically. Only Giovanni's farewell uses this.
local function leaderTalk(beatFlag, adviceLabel, fallback, afterAdvice) local function leaderTalk(beatFlag, adviceLabel, fallback, afterAdvice, victoryKey)
return function(game, ow, npc, done) return function(game, ow, npc, done)
if game.save.flags[beatFlag] then if game.save.flags[beatFlag] then
if victoryKey and retryTmGive(game, ow, victoryKey, done) then return end
local TextBox = require("src.render.TextBox") local TextBox = require("src.render.TextBox")
local finish = done local finish = done
if afterAdvice then if afterAdvice then
@@ -79,42 +102,42 @@ end
M.CERULEAN_GYM.talk = { M.CERULEAN_GYM.talk = {
TEXT_CERULEANGYM_MISTY = leaderTalk("EVENT_BEAT_MISTY", TEXT_CERULEANGYM_MISTY = leaderTalk("EVENT_BEAT_MISTY",
"_CeruleanGymMistyTM11ExplanationText", "_CeruleanGymMistyTM11ExplanationText",
"TM11 teaches\nBUBBLEBEAM!"), "TM11 teaches\nBUBBLEBEAM!", nil, "OPP_MISTY#1"),
} }
-- scripts/VermilionGym.asm VermilionGymLTSurgeText .got_tm24_already -- scripts/VermilionGym.asm VermilionGymLTSurgeText .got_tm24_already
M.VERMILION_GYM.talk = { M.VERMILION_GYM.talk = {
TEXT_VERMILIONGYM_LT_SURGE = leaderTalk("EVENT_BEAT_LT_SURGE", TEXT_VERMILIONGYM_LT_SURGE = leaderTalk("EVENT_BEAT_LT_SURGE",
"_VermilionGymLTSurgePostBattleAdviceText", "_VermilionGymLTSurgePostBattleAdviceText",
"A little word of\nadvice, kid!"), "A little word of\nadvice, kid!", nil, "OPP_LT_SURGE#1"),
} }
-- scripts/CeladonGym.asm CeladonGymErikaText .afterBeat -- scripts/CeladonGym.asm CeladonGymErikaText .afterBeat
M.CELADON_GYM.talk = { M.CELADON_GYM.talk = {
TEXT_CELADONGYM_ERIKA = leaderTalk("EVENT_BEAT_ERIKA", TEXT_CELADONGYM_ERIKA = leaderTalk("EVENT_BEAT_ERIKA",
"_CeladonGymErikaPostBattleAdviceText", "_CeladonGymErikaPostBattleAdviceText",
"You are cataloging\nPOKéMON? I must\nsay I'm impressed."), "You are cataloging\nPOKéMON? I must\nsay I'm impressed.", nil, "OPP_ERIKA#1"),
} }
-- scripts/FuchsiaGym.asm FuchsiaGymKogaText .afterBeat -- scripts/FuchsiaGym.asm FuchsiaGymKogaText .afterBeat
M.FUCHSIA_GYM.talk = { M.FUCHSIA_GYM.talk = {
TEXT_FUCHSIAGYM_KOGA = leaderTalk("EVENT_BEAT_KOGA", TEXT_FUCHSIAGYM_KOGA = leaderTalk("EVENT_BEAT_KOGA",
"_FuchsiaGymKogaPostBattleAdviceText", "_FuchsiaGymKogaPostBattleAdviceText",
"When afflicted by\nTOXIC, POKéMON\nsuffer more and\nmore as battle\nprogresses!"), "When afflicted by\nTOXIC, POKéMON\nsuffer more and\nmore as battle\nprogresses!", nil, "OPP_KOGA#1"),
} }
-- scripts/SaffronGym.asm SaffronGymSabrinaText .afterBeat -- scripts/SaffronGym.asm SaffronGymSabrinaText .afterBeat
M.SAFFRON_GYM.talk = { M.SAFFRON_GYM.talk = {
TEXT_SAFFRONGYM_SABRINA = leaderTalk("EVENT_BEAT_SABRINA", TEXT_SAFFRONGYM_SABRINA = leaderTalk("EVENT_BEAT_SABRINA",
"_SaffronGymSabrinaPostBattleAdviceText", "_SaffronGymSabrinaPostBattleAdviceText",
"Everyone has\npsychic power!\nPeople just don't\nrealize it!"), "Everyone has\npsychic power!\nPeople just don't\nrealize it!", nil, "OPP_SABRINA#1"),
} }
-- scripts/CinnabarGym.asm CinnabarGymBlaineText .afterBeat -- scripts/CinnabarGym.asm CinnabarGymBlaineText .afterBeat
M.CINNABAR_GYM.talk = { M.CINNABAR_GYM.talk = {
TEXT_CINNABARGYM_BLAINE = leaderTalk("EVENT_BEAT_BLAINE", TEXT_CINNABARGYM_BLAINE = leaderTalk("EVENT_BEAT_BLAINE",
"_CinnabarGymBlainePostBattleAdviceText", "_CinnabarGymBlainePostBattleAdviceText",
"FIRE BLAST is the\nultimate fire\ntechnique!"), "FIRE BLAST is the\nultimate fire\ntechnique!", nil, "OPP_BLAINE#1"),
} }
-- scripts/ViridianGym.asm ViridianGymGiovanniText .afterBeat: after the -- scripts/ViridianGym.asm ViridianGymGiovanniText .afterBeat: after the
@@ -142,7 +165,7 @@ M.VIRIDIAN_GYM.talk = {
"VIRIDIAN_GYM", "VIRIDIANGYM_GIOVANNI") "VIRIDIAN_GYM", "VIRIDIANGYM_GIOVANNI")
end end
end, done)) end, done))
end), end, "OPP_GIOVANNI#3"),
} }
return M return M
+50 -8
View File
@@ -18,6 +18,16 @@
-- script). Leaders are not def_trainers entries, so engageTrainer has -- script). Leaders are not def_trainers entries, so engageTrainer has
-- no header.won -- checkVictoryRewards shows this chain instead of a -- no header.won -- checkVictoryRewards shows this chain instead of a
-- synthetic "received badge/TM" stub. -- synthetic "received badge/TM" stub.
--
-- Gym entries split the TM hand-over out of `dialogue`, mirroring the
-- originals' GiveItem check (`call GiveItem` / `jr nc, .BagFull`):
-- `tmPre` is the ReceiveTM script's lead-in (badge info / "Wait! Take
-- this!"), shown at the victory and again when a beaten leader retries
-- the hand-over; `tmDialogue` shows only when the TM actually goes in
-- the bag; `noRoom` is the "make room" line shown instead when the bag
-- is full; `gotFlag` (pokered's EVENT_GOT_TM*) is set only on a
-- successful give, which is what makes the leader's talk script retry
-- later (gyms.lua).
local function range(prefix, first, last) local function range(prefix, first, last)
local t = {} local t = {}
@@ -33,6 +43,8 @@ return {
-- escort NPC and the first Route 22 rival stay gone after the badge. -- escort NPC and the first Route 22 rival stay gone after the badge.
["OPP_BROCK#1"] = { badge = "BOULDERBADGE", flag = "EVENT_BEAT_BROCK", ["OPP_BROCK#1"] = { badge = "BOULDERBADGE", flag = "EVENT_BEAT_BROCK",
item = "TM_BIDE", item = "TM_BIDE",
gotFlag = "EVENT_GOT_TM34",
noRoom = "_PewterGymTM34NoRoomText",
deactivate = { "EVENT_BEAT_PEWTER_GYM_TRAINER_0" }, deactivate = { "EVENT_BEAT_PEWTER_GYM_TRAINER_0" },
hide = { hide = {
{ "PEWTER_CITY", "PEWTERCITY_YOUNGSTER" }, { "PEWTER_CITY", "PEWTERCITY_YOUNGSTER" },
@@ -41,69 +53,99 @@ return {
dialogue = { dialogue = {
"_PewterGymBrockReceivedBoulderBadgeText", "_PewterGymBrockReceivedBoulderBadgeText",
"_PewterGymBrockBoulderBadgeInfoText", "_PewterGymBrockBoulderBadgeInfoText",
"_PewterGymBrockWaitTakeThisText", },
tmPre = { "_PewterGymBrockWaitTakeThisText" },
tmDialogue = {
"_PewterGymReceivedTM34Text", "_PewterGymReceivedTM34Text",
"_TM34ExplanationText", "_TM34ExplanationText",
} }, } },
["OPP_MISTY#1"] = { badge = "CASCADEBADGE", flag = "EVENT_BEAT_MISTY", ["OPP_MISTY#1"] = { badge = "CASCADEBADGE", flag = "EVENT_BEAT_MISTY",
item = "TM_BUBBLEBEAM", item = "TM_BUBBLEBEAM",
gotFlag = "EVENT_GOT_TM11",
noRoom = "_CeruleanGymMistyTM11NoRoomText",
deactivate = range("EVENT_BEAT_CERULEAN_GYM_TRAINER_", 0, 1), deactivate = range("EVENT_BEAT_CERULEAN_GYM_TRAINER_", 0, 1),
dialogue = { dialogue = {
"_CeruleanGymMistyReceivedCascadeBadgeText", "_CeruleanGymMistyReceivedCascadeBadgeText",
"_CeruleanGymMistyCascadeBadgeInfoText", },
tmPre = { "_CeruleanGymMistyCascadeBadgeInfoText" },
tmDialogue = {
"_CeruleanGymMistyReceivedTM11Text", "_CeruleanGymMistyReceivedTM11Text",
} }, } },
["OPP_LT_SURGE#1"] = { badge = "THUNDERBADGE", flag = "EVENT_BEAT_LT_SURGE", ["OPP_LT_SURGE#1"] = { badge = "THUNDERBADGE", flag = "EVENT_BEAT_LT_SURGE",
item = "TM_THUNDERBOLT", item = "TM_THUNDERBOLT",
gotFlag = "EVENT_GOT_TM24",
noRoom = "_VermilionGymLTSurgeTM24NoRoomText",
deactivate = range("EVENT_BEAT_VERMILION_GYM_TRAINER_", 0, 2), deactivate = range("EVENT_BEAT_VERMILION_GYM_TRAINER_", 0, 2),
dialogue = { dialogue = {
"_VermilionGymLTSurgeReceivedThunderBadgeText", "_VermilionGymLTSurgeReceivedThunderBadgeText",
"_VermilionGymLTSurgeThunderBadgeInfoText", },
tmPre = { "_VermilionGymLTSurgeThunderBadgeInfoText" },
tmDialogue = {
"_VermilionGymLTSurgeReceivedTM24Text", "_VermilionGymLTSurgeReceivedTM24Text",
"_TM24ExplanationText", "_TM24ExplanationText",
} }, } },
["OPP_ERIKA#1"] = { badge = "RAINBOWBADGE", flag = "EVENT_BEAT_ERIKA", ["OPP_ERIKA#1"] = { badge = "RAINBOWBADGE", flag = "EVENT_BEAT_ERIKA",
item = "TM_MEGA_DRAIN", item = "TM_MEGA_DRAIN",
gotFlag = "EVENT_GOT_TM21",
noRoom = "_CeladonGymTM21NoRoomText",
deactivate = range("EVENT_BEAT_CELADON_GYM_TRAINER_", 0, 6), deactivate = range("EVENT_BEAT_CELADON_GYM_TRAINER_", 0, 6),
dialogue = { dialogue = {
"_CeladonGymErikaReceivedRainbowBadgeText", "_CeladonGymErikaReceivedRainbowBadgeText",
"_CeladonGymRainbowBadgeInfoText", },
tmPre = { "_CeladonGymRainbowBadgeInfoText" },
tmDialogue = {
"_CeladonGymReceivedTM21Text", "_CeladonGymReceivedTM21Text",
"_TM21ExplanationText", "_TM21ExplanationText",
} }, } },
["OPP_KOGA#1"] = { badge = "SOULBADGE", flag = "EVENT_BEAT_KOGA", ["OPP_KOGA#1"] = { badge = "SOULBADGE", flag = "EVENT_BEAT_KOGA",
item = "TM_TOXIC", item = "TM_TOXIC",
gotFlag = "EVENT_GOT_TM06",
noRoom = "_FuchsiaGymKogaTM06NoRoomText",
deactivate = range("EVENT_BEAT_FUCHSIA_GYM_TRAINER_", 0, 5), deactivate = range("EVENT_BEAT_FUCHSIA_GYM_TRAINER_", 0, 5),
dialogue = { dialogue = {
"_FuchsiaGymKogaReceivedSoulBadgeText", "_FuchsiaGymKogaReceivedSoulBadgeText",
"_FuchsiaGymKogaSoulBadgeInfoText", },
tmPre = { "_FuchsiaGymKogaSoulBadgeInfoText" },
tmDialogue = {
"_FuchsiaGymKogaReceivedTM06Text", "_FuchsiaGymKogaReceivedTM06Text",
"_FuchsiaGymKogaTM06ExplanationText", "_FuchsiaGymKogaTM06ExplanationText",
} }, } },
["OPP_SABRINA#1"] = { badge = "MARSHBADGE", flag = "EVENT_BEAT_SABRINA", ["OPP_SABRINA#1"] = { badge = "MARSHBADGE", flag = "EVENT_BEAT_SABRINA",
item = "TM_PSYWAVE", item = "TM_PSYWAVE",
gotFlag = "EVENT_GOT_TM46",
noRoom = "_SaffronGymSabrinaTM46NoRoomText",
deactivate = range("EVENT_BEAT_SAFFRON_GYM_TRAINER_", 0, 6), deactivate = range("EVENT_BEAT_SAFFRON_GYM_TRAINER_", 0, 6),
dialogue = { dialogue = {
"_SaffronGymSabrinaReceivedMarshBadgeText", "_SaffronGymSabrinaReceivedMarshBadgeText",
"_SaffronGymSabrinaMarshBadgeInfoText", },
tmPre = { "_SaffronGymSabrinaMarshBadgeInfoText" },
tmDialogue = {
"_SaffronGymSabrinaReceivedTM46Text", "_SaffronGymSabrinaReceivedTM46Text",
"_TM46ExplanationText", "_TM46ExplanationText",
} }, } },
["OPP_BLAINE#1"] = { badge = "VOLCANOBADGE", flag = "EVENT_BEAT_BLAINE", ["OPP_BLAINE#1"] = { badge = "VOLCANOBADGE", flag = "EVENT_BEAT_BLAINE",
item = "TM_FIRE_BLAST", item = "TM_FIRE_BLAST",
gotFlag = "EVENT_GOT_TM38",
noRoom = "_CinnabarGymBlaineTM38NoRoomText",
deactivate = range("EVENT_BEAT_CINNABAR_GYM_TRAINER_", 0, 6), deactivate = range("EVENT_BEAT_CINNABAR_GYM_TRAINER_", 0, 6),
dialogue = { dialogue = {
"_CinnabarGymBlaineReceivedVolcanoBadgeText", "_CinnabarGymBlaineReceivedVolcanoBadgeText",
"_CinnabarGymBlaineVolcanoBadgeInfoText", },
tmPre = { "_CinnabarGymBlaineVolcanoBadgeInfoText" },
tmDialogue = {
"_CinnabarGymBlaineReceivedTM38Text", "_CinnabarGymBlaineReceivedTM38Text",
"_CinnabarGymBlaineTM38ExplanationText", "_CinnabarGymBlaineTM38ExplanationText",
} }, } },
["OPP_GIOVANNI#3"] = { badge = "EARTHBADGE", flag = "EVENT_BEAT_GIOVANNI", ["OPP_GIOVANNI#3"] = { badge = "EARTHBADGE", flag = "EVENT_BEAT_GIOVANNI",
item = "TM_FISSURE", item = "TM_FISSURE",
gotFlag = "EVENT_GOT_TM27",
noRoom = "_ViridianGymGiovanniTM27NoRoomText",
deactivate = range("EVENT_BEAT_VIRIDIAN_GYM_TRAINER_", 0, 7), deactivate = range("EVENT_BEAT_VIRIDIAN_GYM_TRAINER_", 0, 7),
dialogue = { dialogue = {
"_ViridianGymGiovanniReceivedEarthBadgeText", "_ViridianGymGiovanniReceivedEarthBadgeText",
"_ViridianGymGiovanniEarthBadgeInfoText", },
tmPre = { "_ViridianGymGiovanniEarthBadgeInfoText" },
tmDialogue = {
"_ViridianGymGiovanniReceivedTM27Text", "_ViridianGymGiovanniReceivedTM27Text",
"_ViridianGymGiovanniTM27ExplanationText", "_ViridianGymGiovanniTM27ExplanationText",
} }, } },
+6 -2
View File
@@ -234,8 +234,12 @@ What was ported from pokered's engine code and where it came from.
pre-battle text and engages the leader battle (badge/TM via pre-battle text and engages the leader battle (badge/TM via
data/scripts/victories.lua); post-badge talk prints the leader's data/scripts/victories.lua); post-badge talk prints the leader's
post-battle advice text (Misty's is her TM11 explanation). The post-battle advice text (Misty's is her TM11 explanation). The
originals' middle branch (beaten but TM not handed over) is originals' middle branch (beaten but TM not handed over,
unreachable since the TM is granted with the victory. Giovanni's CheckEventReuseA EVENT_GOT_TM*) is ported too: the victory's GiveItem
goes through the bag's capacity check, a full bag shows the leader's
"make room" text instead of the received lines and leaves
EVENT_GOT_TM* unset, and talking to the leader re-runs the ReceiveTM
script until the TM goes in (#797). Giovanni's
farewell (`ViridianGymGiovanniText` .afterBeat) hides him inside a farewell (`ViridianGymGiovanniText` .afterBeat) hides him inside a
fade-to-black/fade-in Transition matching ViridianGym.asm's fade-to-black/fade-in Transition matching ViridianGym.asm's
GBFadeOutToBlack → HideObject → GBFadeInFromBlack, persisted GBFadeOutToBlack → HideObject → GBFadeInFromBlack, persisted
+66 -6
View File
@@ -3003,6 +3003,22 @@ function OverworldState:engageTrainer(npc, onDone)
end)) end))
end end
-- Shared GiveItem step for the victory rewards (pokered home/give.asm):
-- the item goes through the bag's capacity check, and only a successful
-- add sets the reward's gotFlag (EVENT_GOT_TM*) and copies the item name
-- into wStringBuffer for the "{RAM:wStringBuffer}" received texts.
local function giveVictoryItem(reward)
if not require("src.inventory.Bag").add(Game.save, reward.item, 1, Game.data) then
return false
end
if reward.gotFlag then
Game.save.flags[reward.gotFlag] = true
end
local idef = Game.data.items[reward.item]
Game.stringBuffer = idef and idef.name or reward.item
return true
end
-- Badges/items awarded after specific battles (data/scripts/victories.lua). -- Badges/items awarded after specific battles (data/scripts/victories.lua).
-- `deactivate` retires unfought gym/dojo trainers the way the originals' -- `deactivate` retires unfought gym/dojo trainers the way the originals'
-- SetEvent / SetEventRange do after the leader victory. -- SetEvent / SetEventRange do after the leader victory.
@@ -3031,12 +3047,13 @@ function OverworldState:checkVictoryRewards(trainerClass, partyIndex)
if reward.badge then if reward.badge then
Game.save.inventory[reward.badge] = 1 Game.save.inventory[reward.badge] = 1
end end
local tmGiven = false
if reward.item then if reward.item then
local inv = Game.save.inventory -- pokered GiveItem (home/give.asm): AddItemToInventory first, and a
inv[reward.item] = (inv[reward.item] or 0) + 1 -- full bag (jr nc, .BagFull) skips the received lines for the "make
local idef = Game.data.items[reward.item] -- room" text, leaving EVENT_GOT_TM* unset so the leader's talk script
-- GiveItem -> CopyToStringBuffer for "{RAM:wStringBuffer}" received texts -- retries the hand-over later (offerGymTm via gyms.lua)
Game.stringBuffer = idef and idef.name or reward.item tmGiven = giveVictoryItem(reward)
end end
local lines = {} local lines = {}
if reward.dialogue then if reward.dialogue then
@@ -3046,13 +3063,29 @@ function OverworldState:checkVictoryRewards(trainerClass, partyIndex)
table.insert(lines, text[label]) table.insert(lines, text[label])
end end
end end
if reward.item then
for _, label in ipairs(reward.tmPre or {}) do
if text[label] and text[label] ~= "" then
table.insert(lines, text[label])
end
end
if tmGiven then
for _, label in ipairs(reward.tmDialogue or {}) do
if text[label] and text[label] ~= "" then
table.insert(lines, text[label])
end
end
elseif reward.noRoom and text[reward.noRoom] and text[reward.noRoom] ~= "" then
table.insert(lines, text[reward.noRoom])
end
end
elseif reward.badge or reward.item then elseif reward.badge or reward.item then
if reward.badge then if reward.badge then
local name = Game.data.items[reward.badge] and Game.data.items[reward.badge].name local name = Game.data.items[reward.badge] and Game.data.items[reward.badge].name
or reward.badge or reward.badge
table.insert(lines, Strings("%s received\nthe %s!", Game.save.player.name, name)) table.insert(lines, Strings("%s received\nthe %s!", Game.save.player.name, name))
end end
if reward.item then if tmGiven then
local name = Game.stringBuffer or reward.item local name = Game.stringBuffer or reward.item
table.insert(lines, Strings("%s received\n%s!", Game.save.player.name, name)) table.insert(lines, Strings("%s received\n%s!", Game.save.player.name, name))
end end
@@ -3063,6 +3096,33 @@ function OverworldState:checkVictoryRewards(trainerClass, partyIndex)
self:runVictoryHook() self:runVictoryHook()
end end
-- A beaten leader re-running their ReceiveTM script when the bag was full
-- at the victory (pokered's middle branch, e.g. PewterGymBrockText
-- CheckEventReuseA EVENT_GOT_TM34 -> call PewterGymScriptReceiveTM34).
-- The script's lead-in lines (tmPre: badge info / "Wait! Take this!")
-- show again, then the same GiveItem check decides between the received
-- lines and the "make room" text.
function OverworldState:offerGymTm(reward, done)
local text = Game.data.text or {}
local lines = {}
local function addLine(label)
if label and text[label] and text[label] ~= "" then
table.insert(lines, text[label])
end
end
for _, label in ipairs(reward.tmPre or {}) do addLine(label) end
if giveVictoryItem(reward) then
for _, label in ipairs(reward.tmDialogue or {}) do addLine(label) end
else
addLine(reward.noRoom)
end
if #lines > 0 then
Game.stack:push(TextBox.new(Game, table.concat(lines, "\f"), done))
elseif done then
done()
end
end
-- pokered reloads the map after every battle, re-running the map -- pokered reloads the map after every battle, re-running the map
-- script (e.g. LoreleiShowOrHideExitBlock); this hook is the port's -- script (e.g. LoreleiShowOrHideExitBlock); this hook is the port's
-- equivalent so seals/toggles refresh without leaving the map -- equivalent so seals/toggles refresh without leaving the map
+60 -4
View File
@@ -183,7 +183,7 @@ do
new = function(game, text, done) return { text = text, onDone = done } end, new = function(game, text, done) return { text = text, onDone = done } end,
} }
local function driveLeader(mapId, textConst, beatFlag) local function driveLeader(mapId, textConst, beatFlag, gotFlag)
local pushed, engaged local pushed, engaged
local game = { local game = {
save = { flags = {} }, save = { flags = {} },
@@ -198,6 +198,9 @@ do
mapId .. " leader talk (no badge) engages the leader battle") mapId .. " leader talk (no badge) engages the leader battle")
engaged, pushed = nil, nil engaged, pushed = nil, nil
game.save.flags[beatFlag] = true game.save.flags[beatFlag] = true
-- the advice/farewell branch is pokered's .afterBeat, reached only
-- once EVENT_GOT_TM* is set (the TM went into the bag)
if gotFlag then game.save.flags[gotFlag] = true end
local state = {} local state = {}
script(game, ow, { def = {} }, function() state.doneCalled = true end) script(game, ow, { def = {} }, function() state.doneCalled = true end)
check(pushed and not engaged, check(pushed and not engaged,
@@ -206,17 +209,17 @@ do
end end
local _, box = driveLeader("CERULEAN_GYM", "TEXT_CERULEANGYM_MISTY", local _, box = driveLeader("CERULEAN_GYM", "TEXT_CERULEANGYM_MISTY",
"EVENT_BEAT_MISTY") "EVENT_BEAT_MISTY", "EVENT_GOT_TM11")
eq(box and box.text, Data.text._CeruleanGymMistyTM11ExplanationText, eq(box and box.text, Data.text._CeruleanGymMistyTM11ExplanationText,
"Misty (beaten) shows the TM11 explanation text") "Misty (beaten) shows the TM11 explanation text")
_, box = driveLeader("CINNABAR_GYM", "TEXT_CINNABARGYM_BLAINE", _, box = driveLeader("CINNABAR_GYM", "TEXT_CINNABARGYM_BLAINE",
"EVENT_BEAT_BLAINE") "EVENT_BEAT_BLAINE", "EVENT_GOT_TM38")
eq(box and box.text, Data.text._CinnabarGymBlainePostBattleAdviceText, eq(box and box.text, Data.text._CinnabarGymBlainePostBattleAdviceText,
"Blaine (beaten) shows his post-battle advice text") "Blaine (beaten) shows his post-battle advice text")
local game, gbox, state = driveLeader("VIRIDIAN_GYM", "TEXT_VIRIDIANGYM_GIOVANNI", local game, gbox, state = driveLeader("VIRIDIAN_GYM", "TEXT_VIRIDIANGYM_GIOVANNI",
"EVENT_BEAT_GIOVANNI") "EVENT_BEAT_GIOVANNI", "EVENT_GOT_TM27")
eq(gbox and gbox.text, Data.text._ViridianGymGiovanniPostBattleAdviceText, eq(gbox and gbox.text, Data.text._ViridianGymGiovanniPostBattleAdviceText,
"Giovanni (beaten) shows his farewell text") "Giovanni (beaten) shows his farewell text")
@@ -392,6 +395,59 @@ do
check(cinnabarNpc and ow:trainerDefeated(cinnabarNpc), check(cinnabarNpc and ow:trainerDefeated(cinnabarNpc),
"unfought Cinnabar trainer is defeated via seeded header event") "unfought Cinnabar trainer is defeated via seeded header event")
-- #797: a full bag at the victory skips the TM hand-over (pokered's
-- `call GiveItem` / `jr nc, .BagFull`): badge and beat flag still land,
-- but EVENT_GOT_TM34 stays unset and the "make room" line replaces the
-- received/explanation texts. Talking to Brock afterwards re-runs the
-- ReceiveTM script and grants the TM once there is room.
while Game.stack:top() do Game.stack:pop() end
Game.save = SaveData.newGame()
Game.save.flags = {}
Game.save.inventory = {}
Game.save.defeatedTrainers = {}
local Bag = require("src.inventory.Bag")
for i = 1, Bag.capacity(Data) do
Bag.add(Game.save, "FULLBAG_" .. i, 1, Data)
end
eq(Bag.slots(Game.save), Bag.capacity(Data), "bag is full before Brock")
Game.stack:push(OW, "PEWTER_GYM", 4, 13, "up")
ow = Game.stack:top()
ow:checkVictoryRewards("OPP_BROCK", 1)
local fullBagText = stackedDialogue()
check(Game.save.flags.EVENT_BEAT_BROCK,
"full bag: Brock victory still sets EVENT_BEAT_BROCK")
check(Game.save.inventory.BOULDERBADGE == 1,
"full bag: Brock victory still awards BOULDERBADGE")
check(not Game.save.flags.EVENT_GOT_TM34,
"full bag: EVENT_GOT_TM34 stays unset (bag_full branch)")
check(Game.save.inventory.TM_BIDE == nil,
"full bag: TM34 is not forced into the bag")
check(fullBagText:find("room", 1, true) ~= nil,
"full bag: victory dialogue shows Brock's make-room line")
check(fullBagText:find("BIDE", 1, true) == nil,
"full bag: received/explanation texts are skipped")
-- make room, then talk to Brock: the middle branch re-runs ReceiveTM34
while Game.stack:top() do Game.stack:pop() end
Bag.remove(Game.save, "FULLBAG_1", 1)
local brockTalk = init.talkScript("PEWTER_GYM", "TEXT_PEWTERGYM_BROCK")
check(brockTalk ~= nil, "Brock's talk script is registered")
brockTalk(Game, ow, { def = {} }, function() end)
local retryText = stackedDialogue()
check(retryText:find("Wait!", 1, true) ~= nil,
"retry: ReceiveTM34 lead-in (Wait! Take this!) shows again")
check(retryText:find("BIDE", 1, true) ~= nil,
"retry: received/explanation texts show once the TM fits")
eq(Game.save.inventory.TM_BIDE, 1, "retry: TM34 goes into the bag")
check(Game.save.flags.EVENT_GOT_TM34, "retry: EVENT_GOT_TM34 is set")
-- once the TM is handed over, Brock falls back to his advice text
while Game.stack:top() do Game.stack:pop() end
brockTalk(Game, ow, { def = {} }, function() end)
local adviceText = stackedDialogue()
check(adviceText:find("CERULEAN", 1, true) ~= nil,
"after the TM: Brock shows his post-battle advice text")
while Game.stack:top() do Game.stack:pop() end while Game.stack:top() do Game.stack:pop() end
end end