-- Parity test: gym leader TM award respects the bag cap (#797). -- -- scripts/PewterGym.asm, PewterGymScriptReceiveTM34: after -- SetEvent EVENT_BEAT_BROCK it runs `lb bc, TM_BIDE, 1` / `call GiveItem` -- / `jr nc, .BagFull`. On success it prints TEXT_PEWTERGYM_RECEIVED_TM34 -- (and the TM34 explanation); on carry-clear it prints -- TEXT_PEWTERGYM_TM34_NO_ROOM instead. Both paths fall through to -- .gymVictory, so the badge lands either way and the TM is simply lost. -- The same `jr nc, .BagFull` shape is in CeruleanGym.asm, VermilionGym.asm, -- CeladonGym.asm, FuchsiaGym.asm, SaffronGym.asm, CinnabarGym.asm and -- ViridianGym.asm. -- -- The port drives this through OverworldState:checkVictoryRewards over -- data/scripts/victories.lua (gym leaders are not def_trainers entries, so -- src/script/Commands.lua give_item -- which has always handled a full bag -- -- is never on this path). Each gym entry splits the hand-over into -- tmPre (the lead-in), tmDialogue (GiveItem succeeded) and noRoom (the -- .BagFull line), with gotFlag (EVENT_GOT_TM*) set only on success so the -- leader's talk script can retry later (offerGymTm via gyms.lua). 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.PALLET_TOWN) then Data:load() end local S = require("tests.harness").suite("parity gym TM bag full #797") local check, eq = S.check, S.eq local Bag = require("src.inventory.Bag") local victories = require("data.scripts.victories") -- === (1) data shape: every gym reward carries both branches === -- A future gym edit must not silently drop the alternate line, so the -- table check is cheap insurance over the eight leader entries. do local n = 0 for key, entry in pairs(victories) do if entry.badge then n = n + 1 check(type(entry.tmDialogue) == "table" and #entry.tmDialogue > 0, key .. " has a tmDialogue tail (the GiveItem-succeeded text)") check(type(entry.noRoom) == "string", key .. " names a noRoom text (.BagFull branch)") local body = entry.noRoom and (Data.text or {})[entry.noRoom] check(type(body) == "string" and body ~= "", key .. " noRoom label resolves to extracted text") check(type(entry.gotFlag) == "string" and entry.gotFlag:find("EVENT_GOT_"), key .. " carries the EVENT_GOT_TM* retry flag") -- the received-TM tail must not still be baked into `dialogue`, -- or the full-bag path would print it anyway for _, label in ipairs(entry.dialogue or {}) do for _, tail in ipairs(entry.tmDialogue or {}) do check(label ~= tail, key .. " dialogue no longer repeats " .. tostring(tail)) end end end end eq(n, 8, "all eight gym leader rewards checked") end -- === (2) behavior: Brock with a full bag vs an empty one === require("src.render.Font").load(Data) local Game = require("src.core.Game") local Input = require("src.core.Input") local StateStack = require("src.core.StateStack") local Renderer = require("src.render.Renderer") local SaveData = require("src.core.SaveData") local OW = require("src.world.OverworldController") Game.data = Data Game.input = Input; Input:init() Game.renderer = Renderer; Renderer:init() Game.stack = StateStack; StateStack:init() -- concatenates the pages of the TextBox checkVictoryRewards pushed local function stackedDialogue() local top = Game.stack:top() if not (top and top.pages) then return "" end local parts = {} for _, page in ipairs(top.pages) do parts[#parts + 1] = table.concat(page, "\n") end return table.concat(parts, "\n") end local function freshSave() while Game.stack:top() do Game.stack:pop() end Game.save = SaveData.newGame() Game.save.flags = {} Game.save.inventory = {} Game.save.bagOrder = nil Game.save.defeatedTrainers = {} end -- --- full bag: the TM is refused, the NoRoom line replaces the TM text --- freshSave() local cap = Bag.capacity(Data) -- Bag.slots only counts non-badge ids, so distinct filler ids fill it for i = 1, cap do Game.save.inventory["FILLER" .. i] = 1 end eq(Bag.slots(Game.save), cap, "bag starts at BAG_ITEM_CAPACITY") Game.stack:push(OW, "PEWTER_GYM", 4, 13, "up") local ow = Game.stack:top() ow:checkVictoryRewards("OPP_BROCK", 1) local fullText = stackedDialogue() check(Game.save.inventory.TM_BIDE == nil, "full bag: TM_BIDE is refused (GiveItem carry clear)") eq(Bag.slots(Game.save), cap, "full bag: no 21st slot appears (the reporter's symptom)") check(Game.save.inventory.BOULDERBADGE == 1, "full bag: .gymVictory still awards BOULDERBADGE") check(Game.save.flags.EVENT_BEAT_BROCK, "full bag: EVENT_BEAT_BROCK is still set") check(fullText:find("room for this", 1, true) ~= nil, "full bag: dialogue prints _PewterGymTM34NoRoomText") check(fullText:find("BIDE", 1, true) == nil, "full bag: the TM34 explanation is skipped") check(fullText:find("FLASH", 1, true) ~= nil, "full bag: the BoulderBadge speech still runs") check(not Game.save.flags.EVENT_GOT_TM34, "full bag: EVENT_GOT_TM34 stays unset so the talk script retries") -- --- empty bag: the success tail still appends and the TM lands --- freshSave() Game.stack:push(OW, "PEWTER_GYM", 4, 13, "up") ow = Game.stack:top() ow:checkVictoryRewards("OPP_BROCK", 1) local okText = stackedDialogue() check(Game.save.inventory.TM_BIDE == 1, "empty bag: TM_BIDE lands in the bag") local order = Bag.order(Game.save) check(order[1] == "TM_BIDE", "empty bag: Bag.add kept the wBagItems order (bagOrder) honest") check(okText:find("BIDE", 1, true) ~= nil, "empty bag: tmDialogue (TM34 explanation) still appends") check(Game.save.flags.EVENT_GOT_TM34, "empty bag: EVENT_GOT_TM34 is set on a successful give") check(okText:find("room for this", 1, true) == nil, "empty bag: the NoRoom line is not printed") -- --- one more leader, to prove the split is not Pewter-only --- freshSave() for i = 1, cap do Game.save.inventory["FILLER" .. i] = 1 end Game.stack:push(OW, "CERULEAN_GYM", 4, 10, "up") ow = Game.stack:top() ow:checkVictoryRewards("OPP_MISTY", 1) local mistyText = stackedDialogue() check(Game.save.inventory.TM_BUBBLEBEAM == nil, "Misty full bag: TM_BUBBLEBEAM is refused") check(Game.save.inventory.CASCADEBADGE == 1, "Misty full bag: CASCADEBADGE still awarded") eq(Bag.slots(Game.save), cap, "Misty full bag: still at capacity") check(mistyText:find((Data.text or {})._CeruleanGymMistyTM11NoRoomText :match("^[^\n]+") or "\1", 1, true) ~= nil, "Misty full bag: dialogue prints _CeruleanGymMistyTM11NoRoomText") while Game.stack:top() do Game.stack:pop() end S.finish()