mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 00:10:56 +02:00
464 lines
20 KiB
Lua
464 lines
20 KiB
Lua
-- Prize money for beating a trainer, plus what Mom does with her cut.
|
|
-- ROM-free:
|
|
-- luajit tests/gen2_prize_test.lua
|
|
--
|
|
-- The numbers below are traceable to pokegold: Falkner's ¥900 is
|
|
-- TrainerClassAttributes' 25 times his level 9 Pidgeotto times the four adds
|
|
-- in WinTrainerBattle, and the Mom item table is data/items/mom_phone.asm
|
|
-- verbatim.
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local S = require("tests.harness").suite("gen2 prize money")
|
|
local check, eq = S.check, S.eq
|
|
|
|
local Prize = require("src.battle.gen2.Prize")
|
|
local Battle = require("src.battle.gen2.Battle")
|
|
local MomShopping = require("src.core.gen2.MomShopping")
|
|
local Decorations = require("src.core.gen2.Decorations")
|
|
local Events = require("src.world.gen2.Events")
|
|
local Save = require("src.core.gen2.Save")
|
|
local Trainers = require("src.world.gen2.Trainers")
|
|
|
|
-- --------------------------------------------------------------- fixtures
|
|
|
|
-- A save with nothing on it but the two money accounts the payout writes.
|
|
local function record(money, saved, active, savingMoney)
|
|
return {
|
|
player = { name = "GOLD", money = money or 0 },
|
|
mom = { savedMoney = saved or 0, active = active or false,
|
|
savingMoney = savingMoney or false },
|
|
}
|
|
end
|
|
|
|
-- ------------------------------------------------- ComputeTrainerReward
|
|
|
|
-- engine/battle/read_trainer_party.asm: base reward times wCurPartyLevel, and
|
|
-- wCurPartyLevel is whatever the LAST party row left behind.
|
|
eq(Prize.reward(25, 9), 225, "Falkner's quarter is 25 x 9")
|
|
eq(Prize.reward(4, 4), 16, "a YOUNGSTER's is 4 x 4")
|
|
eq(Prize.reward(nil, 9), 0, "a class with no base reward pays nothing")
|
|
eq(Prize.reward(25, nil), 0, "and neither does a party with no level")
|
|
-- hProduct + 2 / + 3 with a zero on top: the product is kept modulo 65536.
|
|
eq(Prize.reward(255, 300), (255 * 300) % 0x10000,
|
|
"the product is truncated to wBattleReward's low two bytes")
|
|
|
|
eq(Prize.rewardLevel({ { level = 7 }, { level = 9 } }), 9,
|
|
"the level is the LAST roster row, not the highest")
|
|
eq(Prize.rewardLevel({ { level = 40 }, { level = 3 } }), 3,
|
|
"even when the last row is the weakest")
|
|
eq(Prize.rewardLevel({}), 0, "and an empty party has none")
|
|
|
|
-- ---------------------------------------------------- the four quarters
|
|
|
|
-- WinTrainerBattle's `ld c, 4`: wBattleReward is added FOUR times, and only
|
|
-- then doubled twice for the text. Falkner really does pay ¥900.
|
|
local falkner = record(0)
|
|
local award = Prize.award(falkner, { baseMoney = 25, level = 9 })
|
|
eq(award.quarter, 225, "one quarter of Falkner's prize")
|
|
eq(award.total, 900, "and the figure the text prints")
|
|
eq(falkner.player.money, 900, "all four quarters land in the wallet")
|
|
eq(award.toMom, 0, "with Mom taking none of them")
|
|
|
|
-- Mom is not saving yet: `active` alone is the bank conversation having
|
|
-- happened, and MOM_SAVING_SOME_MONEY_F is a separate bit.
|
|
local idle = record(0, 0, true, false)
|
|
Prize.award(idle, { baseMoney = 25, level = 9 })
|
|
eq(idle.player.money, 900, "an active-but-not-saving Mom takes nothing")
|
|
eq(idle.mom.savedMoney, 0, "and her account does not move")
|
|
|
|
-- The standing 25%: one of the four adds goes to wMomsMoney.
|
|
local saving = record(0, 0, true, true)
|
|
local skimmed = Prize.award(saving, { baseMoney = 25, level = 9 })
|
|
eq(skimmed.toMom, 1, "MOM_SAVING_SOME_MONEY_F takes one quarter")
|
|
eq(saving.mom.savedMoney, 225, "which is a quarter of the prize")
|
|
eq(saving.player.money, 675, "and the wallet keeps the other three")
|
|
eq(skimmed.total, 900, "while the text still names the whole figure")
|
|
|
|
-- .CheckMaxedOutMomMoney: with her account at the cap the carry is clear, b
|
|
-- is 0 and the KeepItAll text is chosen -- she stops skimming rather than
|
|
-- throwing the quarter away.
|
|
local maxed = record(0, Prize.MAX_MONEY, true, true)
|
|
local kept = Prize.award(maxed, { baseMoney = 25, level = 9 })
|
|
eq(kept.toMom, 0, "a maxed-out Mom takes nothing")
|
|
eq(kept.mode, 0, "and the text falls back to KeepItAll")
|
|
eq(maxed.player.money, 900, "so the whole prize reaches the wallet")
|
|
eq(maxed.mom.savedMoney, Prize.MAX_MONEY, "and her account is untouched")
|
|
|
|
-- AddBattleMoneyToAccount clamps at MAX_MONEY rather than wrapping.
|
|
local rich = record(Prize.MAX_MONEY - 100)
|
|
Prize.award(rich, { baseMoney = 25, level = 9 })
|
|
eq(rich.player.money, Prize.MAX_MONEY, "the wallet clamps at the cap")
|
|
local richMom = record(0, Prize.MAX_MONEY - 100, true, true)
|
|
Prize.award(richMom, { baseMoney = 100, level = 100 })
|
|
eq(richMom.mom.savedMoney, Prize.MAX_MONEY, "and so does Mom's account")
|
|
|
|
-- The Amulet Coin doubles wBattleReward BEFORE the split, so Mom's cut
|
|
-- doubles with everything else.
|
|
local coin = record(0, 0, true, true)
|
|
local doubled = Prize.award(coin, { baseMoney = 25, level = 9,
|
|
amuletCoin = true })
|
|
eq(doubled.total, 1800, "an AMULET COIN doubles the prize")
|
|
eq(coin.mom.savedMoney, 450, "including the quarter Mom takes")
|
|
eq(coin.player.money, 1350, "and the three the wallet keeps")
|
|
|
|
-- The half and all settings are Crystal's -- BankOfMom never writes them in
|
|
-- Gold -- but WinTrainerBattle's loop reads them, and 3 means FOUR quarters
|
|
-- (the `inc a`), not three.
|
|
local half = record(0, 0, true, 2)
|
|
eq(Prize.award(half, { baseMoney = 25, level = 9 }).toMom, 2,
|
|
"MOM_SAVING_HALF_MONEY_F takes two quarters")
|
|
eq(half.mom.savedMoney, 450, "which really is half")
|
|
local all = record(0, 0, true, 3)
|
|
eq(Prize.award(all, { baseMoney = 25, level = 9 }).toMom, 4,
|
|
"both bits together take all four")
|
|
eq(all.player.money, 0, "so the wallet gets nothing")
|
|
eq(all.mom.savedMoney, 900, "and Mom banks the lot")
|
|
|
|
-- data/text/battle.asm. The half and all lines really do replace the money
|
|
-- line rather than following it.
|
|
local plain = Prize.message({ total = 900, mode = 0 }, "GOLD")
|
|
check(plain:find("900", 1, true) ~= nil, "the money line names the figure")
|
|
check(plain:find("MOM", 1, true) == nil, "and does not mention MOM")
|
|
local some = Prize.message({ total = 900, mode = 1 }, "GOLD")
|
|
check(some:find("900", 1, true) ~= nil, "SentSomeToMomText keeps the figure")
|
|
check(some:find("Sent some to MOM!", 1, true) ~= nil, "and adds the line")
|
|
eq(Prize.message({ total = 900, mode = 2 }, "GOLD"), "Sent half to MOM!",
|
|
"SentHalfToMomText is the whole text")
|
|
eq(Prize.message({ total = 900, mode = 3 }, "GOLD"), "Sent all to MOM!",
|
|
"and so is SentAllToMomText")
|
|
check(plain:find("\n", 1, true) == nil,
|
|
"no line marker: Chrome.wrap breaks a battle message itself")
|
|
|
|
-- ------------------------------------------------- reached from a battle
|
|
|
|
-- The whole point of the file: a real Battle, beaten, pays the save. These
|
|
-- fixtures are the shapes Trainers.party and Mon.new produce, cut down to
|
|
-- what the faint path reads.
|
|
local DATA = {
|
|
pokemon = {
|
|
RATTATA = { id = "RATTATA", name = "RATTATA", index = 19, baseExp = 57,
|
|
growthRate = "MEDIUM_FAST", stats = { hp = 30, attack = 56,
|
|
defense = 35, speed = 72, specialAttack = 25, specialDefense = 35 },
|
|
types = { "NORMAL" } },
|
|
PIDGEY = { id = "PIDGEY", name = "PIDGEY", index = 15, baseExp = 55,
|
|
growthRate = "MEDIUM_SLOW", stats = { hp = 40, attack = 45,
|
|
defense = 40, speed = 56, specialAttack = 35, specialDefense = 35 },
|
|
types = { "NORMAL", "FLYING" } },
|
|
},
|
|
moves = {},
|
|
}
|
|
|
|
local function mon(species, level, hp, item)
|
|
return { species = species, name = species, nickname = species,
|
|
level = level, hp = hp, maxHp = 30, item = item,
|
|
stats = { hp = 30, attack = 10, defense = 10, speed = 10,
|
|
specialAttack = 10, specialDefense = 10 },
|
|
moves = {}, experience = 0, statExp = {}, dvs = {} }
|
|
end
|
|
|
|
local function beat(save, opts)
|
|
opts = opts or {}
|
|
local enemy = mon("PIDGEY", 7, 1)
|
|
local last = mon("PIDGEY", 9, 0)
|
|
local battle = Battle.new({
|
|
data = DATA,
|
|
party = { mon("RATTATA", 10, 20, opts.playerItem) },
|
|
save = save,
|
|
trainer = { name = "FALKNER", className = "LEADER",
|
|
baseMoney = 25, party = { enemy, last } },
|
|
})
|
|
-- The last mon standing faints, which is the arm WinTrainerBattle sits in.
|
|
battle.enemy = last
|
|
battle.enemyIndex = 2
|
|
battle.enemyParty[1].hp = 0
|
|
last.hp = 0
|
|
battle:resolveFaints()
|
|
return battle
|
|
end
|
|
|
|
local paid = record(0)
|
|
local won = beat(paid)
|
|
eq(won.outcome, "win", "the battle ends on a win")
|
|
eq(paid.player.money, 900, "and the save is paid the cart's figure")
|
|
check(won.prize ~= nil, "the battle keeps the award it made")
|
|
|
|
-- The money line is an EVENT, so the battle screen shows it the same way it
|
|
-- shows every other line: no new seam, no new phase.
|
|
local sawMoney = nil
|
|
for _, event in ipairs(won.events) do
|
|
if event.kind == "money" then sawMoney = event end
|
|
end
|
|
check(sawMoney ~= nil, "a `money` event is emitted")
|
|
check(sawMoney and sawMoney.text and sawMoney.text:find("900", 1, true) ~= nil,
|
|
"carrying the line StdBattleTextbox would print")
|
|
|
|
-- CheckAmuletCoin latches on the SEND-OUT, so the lead's held item counts.
|
|
local coined = record(0)
|
|
beat(coined, { playerItem = Prize.AMULET_COIN })
|
|
eq(coined.player.money, 1800, "a lead holding the AMULET COIN doubles it")
|
|
|
|
-- A battle with no save (a headless turn-order test, or a link battle) pays
|
|
-- nobody rather than throwing.
|
|
local noSave = beat(nil)
|
|
eq(noSave.outcome, "win", "a battle with no save still ends")
|
|
|
|
-- A WILD battle pays nothing: WinTrainerBattle is the trainer arm and nothing
|
|
-- else reaches it.
|
|
local wildSave = record(0)
|
|
local wild = Battle.new({ data = DATA, save = wildSave,
|
|
party = { mon("RATTATA", 10, 20) }, wild = mon("PIDGEY", 3, 0) })
|
|
wild:resolveFaints()
|
|
eq(wildSave.player.money, 0, "beating a wild mon pays nothing")
|
|
|
|
-- --------------------------------------------- the class attribute row
|
|
|
|
-- TrainerClassAttributes is SEVEN bytes and the base reward is the THIRD, so
|
|
-- Trainers.lookup has to carry it through to the battle or the payout is
|
|
-- always zero.
|
|
local TRAINER_DATA = { classes = { FALKNER = { id = "FALKNER", index = 1,
|
|
name = "FALKNER", baseMoney = 25, items = {},
|
|
trainers = { [1] = { id = "FALKNER1", name = "FALKNER",
|
|
party = { { species = "PIDGEY", level = 7 } } } } } } }
|
|
local looked = Trainers.lookup(TRAINER_DATA, 1, 1)
|
|
eq(looked and looked.baseMoney, 25, "the class's base reward survives lookup")
|
|
|
|
-- ------------------------------------------------------- Mom goes shopping
|
|
|
|
local function momSave(saved)
|
|
local save = Save.normalize({ mom = { savedMoney = saved, active = true,
|
|
savingMoney = true }, party = {} })
|
|
return save
|
|
end
|
|
|
|
-- NewGame's seed, which is also what MomShopping.state fills in for a file
|
|
-- that predates it.
|
|
local seeded = MomShopping.state(momSave(0))
|
|
eq(seeded.whichItem, 0, "the ladder starts on its first rung")
|
|
eq(seeded.triggerBalance, MomShopping.MOM_MONEY,
|
|
"and the consolation threshold at MOM_MONEY")
|
|
|
|
-- data/items/mom_phone.asm: the four dolls and their thresholds. These are
|
|
-- the only way a Gold player gets any of them.
|
|
local DOLLS = {
|
|
{ trigger = 10000, deco = 35, label = "CHARMANDER" },
|
|
{ trigger = 30000, deco = 32, label = "CLEFAIRY" },
|
|
{ trigger = 50000, deco = 30, label = "PIKACHU" },
|
|
{ trigger = 100000, deco = 26, label = "BIG SNORLAX" },
|
|
}
|
|
local dollRows = 0
|
|
for _, row in ipairs(MomShopping.ITEMS_2) do
|
|
if row.kind == 2 then dollRows = dollRows + 1 end
|
|
end
|
|
eq(dollRows, 4, "MomItems_2 carries four dolls")
|
|
for _, doll in ipairs(DOLLS) do
|
|
local found = nil
|
|
for _, row in ipairs(MomShopping.ITEMS_2) do
|
|
if row.item == doll.deco then found = row end
|
|
end
|
|
check(found ~= nil, doll.label .. "'s doll is on the list")
|
|
eq(found and found.trigger, doll.trigger,
|
|
"and unlocks at its own savings threshold")
|
|
eq(Decorations.attributes(doll.deco) ~= nil, true,
|
|
"and its DECO id names a real decoration row")
|
|
end
|
|
|
|
-- Under the first rung and off a MOM_MONEY multiple: nothing happens.
|
|
local quiet = momSave(500)
|
|
check(MomShopping.tryBuy(quiet, { events = Events.new() }) == nil,
|
|
"with too little saved Mom buys nothing")
|
|
|
|
-- The ladder, one rung per won trainer battle. 10000 banked is past the
|
|
-- first three triggers, and each buy comes out of the savings -- which is why
|
|
-- the doll at 10000 does NOT follow them: three purchases have dropped the
|
|
-- balance under its own trigger and it has to be earned back.
|
|
local ladder = momSave(10000)
|
|
local events = Events.new()
|
|
local bought = {}
|
|
for _ = 1, 4 do
|
|
bought[#bought + 1] = MomShopping.tryBuy(ladder, { events = events,
|
|
random = function() return 0 end })
|
|
end
|
|
eq(bought[1] and bought[1].item, "SUPER_POTION", "rung one is a SUPER POTION")
|
|
eq(bought[2] and bought[2].item, "REPEL", "rung two a REPEL")
|
|
eq(bought[3] and bought[3].item, "SUPER_POTION", "rung three another")
|
|
check(bought[4] == nil, "and the doll waits: the buying spent its trigger")
|
|
eq(ladder.mom.whichItem, 3, "the ladder has moved on three rungs")
|
|
eq(ladder.mom.savedMoney, 10000 - 600 - 270 - 600,
|
|
"and every cost came out of her savings")
|
|
eq(ladder.pcItems.SUPER_POTION, 2, "the two potions are in the PC")
|
|
eq(ladder.pcItems.REPEL, 1, "and so is the REPEL")
|
|
|
|
-- Saved back up to the trigger, the doll arrives -- and it is a decoration
|
|
-- flag, not a PC item, because Mom_GiveItemOrDoll's doll arm goes through
|
|
-- DecorationFlagAction_c.
|
|
ladder.mom.savedMoney = 10000
|
|
local doll = MomShopping.tryBuy(ladder, { events = events })
|
|
eq(doll and doll.kind, "doll", "rung four is the doll")
|
|
eq(doll and doll.item, 35, "the CHARMANDER doll")
|
|
check(Decorations.owns(events, 35), "which the player now owns")
|
|
eq(ladder.mom.savedMoney, 10000 - 1800, "and she paid 1800 for it")
|
|
eq(ladder.pcItems[35], nil, "a doll does not go in the PC")
|
|
|
|
-- MomItems_1: the consolation buy, which fires only when the LADDER has
|
|
-- nothing to offer (the next rung's trigger is out of reach) and the savings
|
|
-- land EXACTLY on a MOM_MONEY multiple the threshold has not passed.
|
|
local function stalledSave(saved)
|
|
local save = momSave(saved)
|
|
MomShopping.state(save)
|
|
-- Rung two wants 4000; anything below that leaves the ladder stalled.
|
|
save.mom.whichItem = 1
|
|
return save
|
|
end
|
|
local exact = stalledSave(MomShopping.MOM_MONEY)
|
|
local consolation = MomShopping.tryBuy(exact, { events = Events.new(),
|
|
random = function() return 1 end })
|
|
eq(consolation and consolation.set, 1, "an exact 2300 buys off MomItems_1")
|
|
eq(consolation and consolation.item, "ANTIDOTE", "the row the roll picked")
|
|
eq(exact.mom.whichItem, 1, "and the ladder has NOT moved")
|
|
eq(exact.mom.triggerBalance, MomShopping.MOM_MONEY * 2,
|
|
"while the threshold has climbed a step")
|
|
exact.mom.savedMoney = MomShopping.MOM_MONEY
|
|
check(MomShopping.tryBuy(exact, { events = Events.new(),
|
|
random = function() return 1 end }) == nil,
|
|
"so the same 2300 cannot pay twice")
|
|
|
|
-- Overshooting the threshold is `.less_than`: the balance is left where the
|
|
-- walk pushed it and nothing is bought.
|
|
local between = stalledSave(MomShopping.MOM_MONEY + 1)
|
|
check(MomShopping.tryBuy(between, { events = Events.new() }) == nil,
|
|
"a balance between two rungs buys nothing")
|
|
eq(between.mom.triggerBalance, MomShopping.MOM_MONEY * 2,
|
|
"and the threshold has walked past it")
|
|
|
|
-- GetMapPhoneService: no reception, no call, and the balance is not even
|
|
-- looked at -- so Mom tries again after the next trainer.
|
|
local noService = stalledSave(MomShopping.MOM_MONEY)
|
|
check(MomShopping.tryBuy(noService, { events = Events.new(),
|
|
phoneService = false }) == nil, "a map with no phone service buys nothing")
|
|
eq(noService.mom.triggerBalance, MomShopping.MOM_MONEY,
|
|
"and leaves the threshold alone")
|
|
|
|
-- Mom_GetScriptPointer's two scripts.
|
|
local itemPages = MomShopping.pages({ kind = "item" })
|
|
local dollPages = MomShopping.pages({ kind = "doll" })
|
|
eq(#itemPages, 4, ".ItemScript is four writetexts")
|
|
eq(#dollPages, 4, "and so is .DollScript")
|
|
check(itemPages[4]:find("PC", 1, true) ~= nil, "the item ends up in the PC")
|
|
check(dollPages[4]:find("room", 1, true) ~= nil, "and the doll in your room")
|
|
check(itemPages[1] == dollPages[1], "both open on the same greeting")
|
|
|
|
-- A doll cannot fail; an item into a full PC can, and then nothing is
|
|
-- deducted (Mom_GiveItemOrDoll's no-carry return).
|
|
local fullPc = momSave(10000)
|
|
fullPc.pcItems = {}
|
|
for i = 1, 50 do fullPc.pcItems["FILLER_" .. i] = 1 end
|
|
local refused = MomShopping.tryBuy(fullPc, { events = Events.new() })
|
|
check(refused == nil, "a full PC refuses the purchase")
|
|
eq(fullPc.mom.savedMoney, 10000, "and nothing is deducted")
|
|
eq(fullPc.mom.whichItem, 0, "and the ladder does not move")
|
|
|
|
-- ------------------------------------------------ the two World call sites
|
|
--
|
|
-- A model with a green suite and no door is the failure this file exists to
|
|
-- avoid, so both seams are driven through World:startBattle itself rather
|
|
-- than called by hand: the battle screen is replaced through the screens
|
|
-- REGISTRY (the same door a mod uses), which hands back the real `onDone`
|
|
-- closure Script_reloadmapafterbattle's two arms live in.
|
|
|
|
local World = require("src.world.gen2.World")
|
|
local Screens = require("src.ui.Screens")
|
|
|
|
local function battleWorld(save)
|
|
local captured = {}
|
|
-- Screens caches a factory by id alone, so a second world would otherwise
|
|
-- be handed the first one's closure.
|
|
Screens.invalidate()
|
|
local game = {
|
|
data = {
|
|
pokemon = DATA.pokemon, moves = DATA.moves,
|
|
screens = {
|
|
Gen2BattleState = function(_, opts)
|
|
captured.opts = opts
|
|
return { screenId = "Gen2BattleState" }
|
|
end,
|
|
},
|
|
},
|
|
save = save,
|
|
stack = { push = function() end, pop = function() end },
|
|
}
|
|
local world = World.new(game)
|
|
game.world = world
|
|
world.map = { def = { id = "TEST_MAP", phoneService = true } }
|
|
world.maps = { TEST_MAP = world.map.def }
|
|
world.events = Events.new()
|
|
-- The render and audio halves, which have no place in a headless run.
|
|
world.playBattleMusic = function() end
|
|
world.battleMusicContext = function() return nil end
|
|
world.pushBattleTransition = function() return nil end
|
|
world.restoreMapMusic = function() end
|
|
world.healParty = function() end
|
|
world.warpToSpawn = function() end
|
|
return world, captured
|
|
end
|
|
|
|
local lossSave = Save.normalize({ party = {},
|
|
player = { name = "GOLD", money = 3001 },
|
|
mom = { savedMoney = 4000, active = true, savingMoney = true } })
|
|
local lossWorld, lossCaptured = battleWorld(lossSave)
|
|
lossWorld:startBattle({ trainer = { name = "FALKNER", baseMoney = 25,
|
|
party = { mon("PIDGEY", 9, 5) } } })
|
|
check(lossCaptured.opts ~= nil, "startBattle pushes the battle screen")
|
|
check(lossCaptured.opts and lossCaptured.opts.save == lossSave,
|
|
"handing it the save the payout writes")
|
|
lossCaptured.opts.onDone("lose")
|
|
eq(lossSave.player.money, 1500, "a lost battle halves the wallet")
|
|
eq(lossSave.mom.savedMoney, 4000,
|
|
"and leaves Mom's savings alone -- HalveMoney shifts wMoney only")
|
|
|
|
-- The Bug Contest arm: `checkflag ENGINE_BUG_CONTEST_TIMER / iftrue
|
|
-- .bug_contest` skips both callasms, so a wipe in the park is free.
|
|
local parkSave = Save.normalize({ party = {},
|
|
player = { name = "GOLD", money = 3000 } })
|
|
parkSave.bugContest = { active = true, start = { day = 0, hour = 0,
|
|
minute = 0, second = 0 } }
|
|
local parkWorld, parkCaptured = battleWorld(parkSave)
|
|
parkWorld:startBattle({ trainer = { name = "FALKNER", baseMoney = 25,
|
|
party = { mon("PIDGEY", 9, 5) } } })
|
|
parkCaptured.opts.onDone("lose")
|
|
eq(parkSave.player.money, 3000, "a wipe during the Bug Contest costs nothing")
|
|
|
|
-- The win arm: MomTriesToBuySomething, queued the way LoadMemScript queues it
|
|
-- so the four lines land after the trainer's own after-battle script.
|
|
local shopSave = Save.normalize({ party = {},
|
|
player = { name = "GOLD", money = 0 },
|
|
mom = { savedMoney = 900, active = true, savingMoney = true } })
|
|
local shopWorld, shopCaptured = battleWorld(shopSave)
|
|
shopWorld:startBattle({ trainer = { name = "FALKNER", baseMoney = 25,
|
|
party = { mon("PIDGEY", 9, 5) } } })
|
|
check(shopWorld.queuedScript == nil, "nothing is queued before the battle")
|
|
shopCaptured.opts.onDone("win")
|
|
check(shopWorld.queuedScript ~= nil, "a won trainer battle lets Mom shop")
|
|
eq(shopSave.mom.savedMoney, 900 - 600, "and she pays for what she bought")
|
|
eq(shopSave.pcItems.SUPER_POTION, 1, "which is waiting in the PC")
|
|
local sawRaw = false
|
|
for _, row in ipairs(shopWorld.queuedScript or {}) do
|
|
if row.op == "rawtext" then sawRaw = true end
|
|
end
|
|
check(sawRaw, "the queued script speaks the call's four lines")
|
|
|
|
-- A won WILD battle does not: Script_reloadmapafterbattle's `.was_wild` arm
|
|
-- goes to the box-full check instead.
|
|
local wildWorld, wildCaptured = battleWorld(Save.normalize({ party = {},
|
|
player = { name = "GOLD", money = 0 },
|
|
mom = { savedMoney = 900, active = true, savingMoney = true } }))
|
|
wildWorld.roamMonsAfterBattle = function() end
|
|
wildWorld:startBattle({ wild = mon("PIDGEY", 3, 5) })
|
|
wildCaptured.opts.onDone("win")
|
|
check(wildWorld.queuedScript == nil, "a wild battle gives Mom no chance")
|
|
|
|
-- The registry cache is process-wide, so the fake screen must not outlive
|
|
-- this file: tests/run_tests.lua dofiles every suite into one process.
|
|
Screens.invalidate()
|
|
|
|
return S.finish()
|