mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 00:10:56 +02:00
681 lines
26 KiB
Lua
681 lines
26 KiB
Lua
-- The Pokecenter PC and the player's item PC.
|
|
--
|
|
-- luajit tests/gen2_pc_screens_test.lua (GOLD_CACHE for the map runs)
|
|
--
|
|
-- Three layers, matching how the cart reaches them:
|
|
--
|
|
-- * World:interact's CheckFacingTileForStdScript arm
|
|
-- (engine/events/std_collision.asm): an A press on a COLL_PC tile runs
|
|
-- PCScript out of std_scripts, which is the ONLY way any Pokecenter PC
|
|
-- opens -- no Pokecenter map carries a PC bg event.
|
|
-- * CenterPcMenu (engine/events/pokecenter_pc.asm PokemonCenterPC): the
|
|
-- whose-PC list and its .ChooseWhichPCListToUse gating, the party gate,
|
|
-- and the rows it opens.
|
|
-- * ItemPcMenu (_PlayersPC + the PlayerWithdraw/Deposit/TossItemMenu rows):
|
|
-- items moving bag <-> save.pcItems for real, with the cart's caps and
|
|
-- refusals.
|
|
--
|
|
-- With a GOLD_CACHE the whole chain runs on the real Cherrygrove Pokecenter
|
|
-- and the real bedroom map: a real Map, a real Vm over the extracted scripts,
|
|
-- and World:openPc pushing the real screens.
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
love = love or {}
|
|
love.graphics = love.graphics or {
|
|
getColor = function() return 1, 1, 1, 1 end,
|
|
setColor = function() end,
|
|
rectangle = function() end,
|
|
print = function() end,
|
|
printf = function() end,
|
|
draw = function() end,
|
|
newQuad = function() return {} end,
|
|
newImage = function() return nil end,
|
|
getShader = function() return nil end,
|
|
setShader = function() end,
|
|
newShader = function() error("no shaders in this harness") end,
|
|
getDimensions = function() return 160, 144 end,
|
|
push = function() end, pop = function() end,
|
|
translate = function() end, scale = function() end,
|
|
circle = function() end, clear = function() end,
|
|
setLineWidth = function() end,
|
|
}
|
|
love.math = love.math or {
|
|
random = function(a, b)
|
|
if b then return a end
|
|
return a and 1 or 0.5
|
|
end,
|
|
}
|
|
love.filesystem = love.filesystem or {
|
|
load = function() return nil end,
|
|
getInfo = function() return nil end,
|
|
read = function() return nil end,
|
|
write = function() return true end,
|
|
remove = function() return true end,
|
|
}
|
|
love.timer = love.timer or { getTime = function() return 0 end }
|
|
|
|
require("src.core.Logger").warn = function() end
|
|
|
|
local S = require("tests.harness").suite("gen2 pc screens")
|
|
local check, eq = S.check, S.eq
|
|
|
|
local Bag = require("src.inventory.Bag")
|
|
local CenterPcMenu = require("src.ui.gen2.CenterPcMenu")
|
|
local ItemPcMenu = require("src.ui.gen2.ItemPcMenu")
|
|
local Map = require("src.world.gen2.Map")
|
|
local Save = require("src.core.gen2.Save")
|
|
local Screens = require("src.ui.Screens")
|
|
local Vm = require("src.script.gen2.Vm")
|
|
local World = require("src.world.gen2.World")
|
|
|
|
local function newInput()
|
|
local input = { pressed = {} }
|
|
function input:press(...)
|
|
for _, button in ipairs({ ... }) do self.pressed[button] = true end
|
|
end
|
|
function input:wasPressed(button)
|
|
if self.pressed[button] then
|
|
self.pressed[button] = nil
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
function input:isDown() return false end
|
|
return input
|
|
end
|
|
|
|
local ITEMS = {
|
|
POTION = { id = "POTION", name = "POTION", pocket = "ITEM", index = 2,
|
|
canToss = true },
|
|
ANTIDOTE = { id = "ANTIDOTE", name = "ANTIDOTE", pocket = "ITEM", index = 9,
|
|
canToss = true },
|
|
BICYCLE = { id = "BICYCLE", name = "BICYCLE", pocket = "KEY_ITEM", index = 6,
|
|
canToss = false },
|
|
HM_CUT = { id = "HM_CUT", name = "HM01", pocket = "TM_HM", index = 0xf3,
|
|
canToss = false },
|
|
}
|
|
|
|
local function newGame(save, items)
|
|
local input = newInput()
|
|
return {
|
|
input = input,
|
|
save = save,
|
|
data = { audio = {}, pokemon = {}, items = items or ITEMS },
|
|
stack = { _items = {},
|
|
push = function(self, s) self._items[#self._items + 1] = s end,
|
|
pop = function(self) return table.remove(self._items) end,
|
|
top = function(self) return self._items[#self._items] end,
|
|
},
|
|
}, input
|
|
end
|
|
|
|
local function newSave(partySize)
|
|
local save = Save.newGame({ playerName = "GOLD", trainerId = 1234 })
|
|
for i = 1, partySize or 1 do
|
|
save.party[i] = { species = "CYNDAQUIL", nickname = "MON" .. i,
|
|
hp = 20, maxHp = 20, level = 10 }
|
|
end
|
|
return save
|
|
end
|
|
|
|
-- Drive a screen: press the buttons one at a time, updating after each.
|
|
local function press(screen, input, ...)
|
|
for _, button in ipairs({ ... }) do
|
|
input:press(button)
|
|
screen:update(0)
|
|
end
|
|
end
|
|
|
|
local function entryIds(entries)
|
|
local ids = {}
|
|
for _, entry in ipairs(entries) do ids[#ids + 1] = entry.id end
|
|
return table.concat(ids, ",")
|
|
end
|
|
|
|
-- ------------------------------------------------- the ids resolve
|
|
|
|
Screens.invalidate()
|
|
local sg = { data = {} }
|
|
eq(Screens.get(sg, "Gen2CenterPcMenu"), CenterPcMenu,
|
|
"Gen2CenterPcMenu resolves to its builtin")
|
|
eq(Screens.get(sg, "Gen2ItemPcMenu"), ItemPcMenu,
|
|
"Gen2ItemPcMenu resolves to its builtin")
|
|
|
|
-- ------------------------------------------------- the whose-PC gating
|
|
|
|
-- PCPC_BEFORE_POKEDEX: three rows.
|
|
do
|
|
local save = newSave(1)
|
|
local game, input = newGame(save)
|
|
local pc = CenterPcMenu.new(game, { save = save, onClose = function() end })
|
|
check(pc.message ~= nil, "the PC boots with the turn-on line")
|
|
press(pc, input, "a")
|
|
eq(pc.message, nil, "which one A clears")
|
|
eq(entryIds(pc.entries), "bills,players,turnoff",
|
|
"no #DEX: BILL's PC / <PLAYER>'s PC / TURN OFF")
|
|
eq(pc.entries[2].label, "GOLD's PC", "the item PC row carries the name")
|
|
end
|
|
|
|
-- PCPC_BEFORE_HOF: PROF.OAK's PC appears with CheckReceivedDex.
|
|
do
|
|
local save = newSave(1)
|
|
save.engineFlags = { [CenterPcMenu.ENGINE_POKEDEX] = true }
|
|
local game = newGame(save)
|
|
local pc = CenterPcMenu.new(game, { save = save })
|
|
eq(entryIds(pc.entries), "bills,players,oaks,turnoff",
|
|
"with the #DEX the OAK row slots in above TURN OFF")
|
|
end
|
|
|
|
-- PCPC_POSTGAME: HALL OF FAME appears once wHallOfFameCount is non-zero.
|
|
do
|
|
local save = newSave(1)
|
|
save.engineFlags = { [CenterPcMenu.ENGINE_POKEDEX] = true }
|
|
save.hallOfFame.count = 1
|
|
local game = newGame(save)
|
|
local pc = CenterPcMenu.new(game, { save = save })
|
|
eq(entryIds(pc.entries), "bills,players,oaks,hof,turnoff",
|
|
"a champion sees all five rows")
|
|
end
|
|
|
|
-- PC_CheckPartyForPokemon: no party, no PC.
|
|
do
|
|
local save = newSave(0)
|
|
local game, input = newGame(save)
|
|
local closed = false
|
|
local pc = CenterPcMenu.new(game, { save = save,
|
|
onClose = function() closed = true end })
|
|
check(pc.message ~= nil, "an empty party gets the Bzzzzt! refusal")
|
|
press(pc, input, "a")
|
|
check(closed, "and the PC never opens")
|
|
eq(#game.stack._items, 0, "nothing was pushed")
|
|
end
|
|
|
|
-- ------------------------------------------------- the rows open the screens
|
|
|
|
-- <PLAYER>'s PC opens the item PC (PLAYERSPC_NORMAL).
|
|
do
|
|
local save = newSave(1)
|
|
local game, input = newGame(save)
|
|
local pc = CenterPcMenu.new(game, { save = save })
|
|
press(pc, input, "a") -- boot line
|
|
press(pc, input, "down", "a") -- <PLAYER>'s PC
|
|
check(pc.message ~= nil, "PokecenterPlayersPCText comes up first")
|
|
press(pc, input, "a", "a") -- both pages
|
|
local top = game.stack:top()
|
|
check(top ~= nil, "then the item PC is pushed")
|
|
eq(top and top.screenId, "Gen2ItemPcMenu", "as Gen2ItemPcMenu")
|
|
eq(entryIds(top.entries), "withdraw,deposit,toss,mailbox,logoff",
|
|
"with _PlayersPC's PLAYERSPC_NORMAL rows, LOG OFF last")
|
|
end
|
|
|
|
-- BILL's PC opens the storage system with _BillsPC's own five rows.
|
|
do
|
|
local save = newSave(1)
|
|
local game, input = newGame(save)
|
|
local pc = CenterPcMenu.new(game, { save = save })
|
|
press(pc, input, "a") -- boot line
|
|
press(pc, input, "a") -- BILL's PC (row 1)
|
|
press(pc, input, "a", "a") -- PokecenterBillsPCText, both pages
|
|
local top = game.stack:top()
|
|
eq(top and top.screenId, "Gen2PcMenu", "BILL's PC pushes the storage menu")
|
|
eq(entryIds(top.entries), "withdraw,deposit,changebox,move,seeya",
|
|
"with no MAIL BOX row: that lives on the item PC")
|
|
end
|
|
|
|
-- PROF.OAK's PC: the yes/no, the counts, the rating, the link-closed line.
|
|
do
|
|
local save = newSave(1)
|
|
save.engineFlags = { [CenterPcMenu.ENGINE_POKEDEX] = true }
|
|
save.pokedex = { seen = { A = true, B = true, C = true },
|
|
caught = { A = true, B = true } }
|
|
local game, input = newGame(save)
|
|
local pc = CenterPcMenu.new(game, { save = save })
|
|
press(pc, input, "a") -- boot line
|
|
press(pc, input, "down", "down", "a") -- PROF.OAK's PC
|
|
press(pc, input, "a", "a") -- PokecenterOaksPCText, both pages
|
|
check(pc.confirm ~= nil, "OakPCText1 asks for the yes/no")
|
|
press(pc, input, "a") -- YES
|
|
check(pc.message ~= nil, "and the rating flow starts")
|
|
local sawCounts, sawRating = false, false
|
|
for _ = 1, 12 do
|
|
if not pc.message then break end
|
|
local page = pc.message.pages[pc.message.page]
|
|
for _, line in ipairs(page) do
|
|
if line == "3 #MON seen" then sawCounts = true end
|
|
if line == "Look for #MON" then sawRating = true end
|
|
end
|
|
press(pc, input, "a")
|
|
end
|
|
check(sawCounts, "the counts page names 3 seen")
|
|
check(sawRating, "2 owned lands on OakRating01")
|
|
check(pc.closed == false, "the OAK flow drops back to the menu, not out")
|
|
end
|
|
|
|
-- TURN OFF: the Link closed line, then the shutdown.
|
|
do
|
|
local save = newSave(1)
|
|
local game, input = newGame(save)
|
|
local closed = false
|
|
local pc = CenterPcMenu.new(game, { save = save,
|
|
onClose = function() closed = true end })
|
|
press(pc, input, "a") -- boot line
|
|
press(pc, input, "up", "a") -- TURN OFF (wraps to the last row)
|
|
check(pc.message ~= nil, "TurnOffPC prints the Link closed line")
|
|
press(pc, input, "a")
|
|
check(closed, "and carries into .shutdown")
|
|
end
|
|
|
|
-- ------------------------------------------------- the item PC, for real
|
|
|
|
-- DEPOSIT: bag -> save.pcItems through the PACK chooser.
|
|
do
|
|
local save = newSave(1)
|
|
local game, input = newGame(save)
|
|
check(Bag.add(save, "POTION", 5, game.data), "five POTIONs in the bag")
|
|
local pc = ItemPcMenu.new(game, { save = save, items = ITEMS })
|
|
eq(pc.message, nil, "PLAYERSPC_NORMAL boots straight to the menu")
|
|
press(pc, input, "down", "a") -- DEPOSIT ITEM
|
|
eq(pc.phase, "deposit", "DEPOSIT opens the PACK as a chooser")
|
|
check(pc.pack ~= nil, "held by the screen, the way the mart sells")
|
|
press(pc, input, "a") -- choose POTION
|
|
check(pc.qtyState ~= nil, "the quantity selector comes up")
|
|
press(pc, input, "up") -- 2
|
|
press(pc, input, "a") -- deposit x2
|
|
eq(save.pcItems.POTION, 2, "two POTIONs land in the PC")
|
|
eq(save.inventory.POTION, 3, "and leave the bag")
|
|
check(pc.message ~= nil, "with _PlayersPCDepositItemsText up")
|
|
eq(pc.message.pages[1][1], "Deposited 2", "naming the count")
|
|
press(pc, input, "a") -- clear it
|
|
press(pc, input, "b") -- close the PACK
|
|
eq(pc.phase, "menu", "B drops back to the item PC menu")
|
|
end
|
|
|
|
-- .TryDepositItem's .no_toss: a KEY ITEM stays in the bag, silently.
|
|
do
|
|
local save = newSave(1)
|
|
local game, input = newGame(save)
|
|
Bag.add(save, "BICYCLE", 1, game.data)
|
|
local pc = ItemPcMenu.new(game, { save = save, items = ITEMS })
|
|
press(pc, input, "down", "a") -- DEPOSIT ITEM
|
|
press(pc, input, "right", "right") -- ITEM -> BALL -> KEY_ITEM pocket
|
|
press(pc, input, "a") -- choose the BICYCLE
|
|
eq(pc.qtyState, nil, "no quantity selector for a KEY ITEM")
|
|
eq(pc.message, nil, "no message either: .no_toss is a bare ret")
|
|
eq(save.pcItems.BICYCLE, nil, "and the BICYCLE never leaves the bag")
|
|
eq(save.inventory.BICYCLE, 1, "still there")
|
|
end
|
|
|
|
-- An empty bag never opens the PACK (.CheckItemsInBag).
|
|
do
|
|
local save = newSave(1)
|
|
local game, input = newGame(save)
|
|
local pc = ItemPcMenu.new(game, { save = save, items = ITEMS })
|
|
press(pc, input, "down", "a")
|
|
eq(pc.phase, "menu", "DEPOSIT refuses with nothing to deposit")
|
|
eq(pc.message.pages[1][1], "No items here!", "with _PlayersPCNoItemsText")
|
|
end
|
|
|
|
-- WITHDRAW: save.pcItems -> bag, with the no-room refusal.
|
|
do
|
|
local save = newSave(1)
|
|
local game, input = newGame(save)
|
|
save.pcItems = { POTION = 2 }
|
|
local pc = ItemPcMenu.new(game, { save = save, items = ITEMS })
|
|
press(pc, input, "a") -- WITHDRAW ITEM
|
|
eq(pc.phase, "withdraw", "the PC item list opens")
|
|
eq(pc.rows[1] and pc.rows[1].id, "POTION", "with the POTION stack on it")
|
|
press(pc, input, "a") -- choose it
|
|
check(pc.qtyState ~= nil, "stackable: the selector asks how many")
|
|
press(pc, input, "a") -- x1
|
|
eq(save.inventory.POTION, 1, "one POTION reaches the bag")
|
|
eq(save.pcItems.POTION, 1, "one stays behind")
|
|
eq(pc.message.pages[1][1], "Withdrew 1", "_PlayersPCWithdrewItemsText")
|
|
press(pc, input, "a")
|
|
|
|
-- Fill the ITEM pocket: ReceiveItem answers no-carry and the stack stays.
|
|
Bag.remove(save, "POTION", 1)
|
|
for i = 1, Bag.capacity(game.data, "ITEM") do
|
|
check(Bag.add(save, "FILL_" .. i, 1, game.data), "filler " .. i .. " fits")
|
|
end
|
|
press(pc, input, "a", "a") -- choose the POTION stack again, x1
|
|
eq(pc.message.pages[1][1], "There's no room",
|
|
"a full pocket is _PlayersPCNoRoomWithdrawText")
|
|
eq(save.pcItems.POTION, 1, "and the stack never left the PC")
|
|
press(pc, input, "a", "b") -- clear, back to menu
|
|
end
|
|
|
|
-- TOSS: the quantity, the yes/no, the discard -- and the KEY ITEM refusal.
|
|
do
|
|
local save = newSave(1)
|
|
local game, input = newGame(save)
|
|
save.pcItems = { POTION = 3, HM_CUT = 1 }
|
|
local pc = ItemPcMenu.new(game, { save = save, items = ITEMS })
|
|
press(pc, input, "down", "down", "a") -- TOSS ITEM
|
|
eq(pc.phase, "toss", "the toss list opens")
|
|
press(pc, input, "a") -- POTION (index 2 sorts it first)
|
|
check(pc.qtyState ~= nil, "TossItemFromPC asks how many")
|
|
press(pc, input, "up", "up", "a") -- x3
|
|
check(pc.confirm ~= nil, "then .ItemsThrowAwayText asks yes/no")
|
|
eq(pc.confirm.prompt[1], "Throw away 3", "naming the count")
|
|
press(pc, input, "a") -- YES
|
|
eq(save.pcItems.POTION, nil, "the stack is discarded")
|
|
eq(pc.message.pages[1][1], "Discarded", "_ItemsDiscardedText")
|
|
press(pc, input, "a")
|
|
|
|
press(pc, input, "a") -- the HM is the only row left
|
|
eq(pc.qtyState, nil, "an HM never reaches the selector")
|
|
eq(pc.message.pages[1][1], "That's too impor-",
|
|
".CantToss: _ItemsTooImportantText")
|
|
press(pc, input, "a", "b")
|
|
end
|
|
|
|
-- The PC's fifty stacks (ReceiveItem over wNumPCItems).
|
|
do
|
|
local save = newSave(1)
|
|
local game, input = newGame(save)
|
|
Bag.add(save, "POTION", 1, game.data)
|
|
save.pcItems = {}
|
|
for i = 1, ItemPcMenu.PC_ITEM_CAPACITY do save.pcItems["S" .. i] = 1 end
|
|
local pc = ItemPcMenu.new(game, { save = save, items = ITEMS })
|
|
press(pc, input, "down", "a") -- DEPOSIT ITEM
|
|
press(pc, input, "a") -- choose POTION
|
|
press(pc, input, "a") -- x1
|
|
eq(pc.message.pages[1][1], "There's no room to",
|
|
"the fifty-first stack is _PlayersPCNoRoomDepositText")
|
|
eq(save.inventory.POTION, 1, "and the POTION stays in the bag")
|
|
end
|
|
|
|
-- PLAYERSPC_HOUSE: the boot line, DECORATION, TURN OFF, and the answer out.
|
|
do
|
|
local save = newSave(1)
|
|
local game, input = newGame(save)
|
|
local answered = nil
|
|
local pc = ItemPcMenu.new(game, { save = save, items = ITEMS, house = true,
|
|
onClose = function(changed) answered = changed end })
|
|
check(pc.message ~= nil, "_PlayersHousePC opens on PlayersPCTurnOnText")
|
|
press(pc, input, "a")
|
|
eq(entryIds(pc.entries), "withdraw,deposit,toss,mailbox,decoration,turnoff",
|
|
"PLAYERSPC_HOUSE carries DECORATION and ends on TURN OFF")
|
|
-- The DECORATION row pushes the decoration menu and carries `changed` out.
|
|
press(pc, input, "up", "up", "a")
|
|
local top = game.stack:top()
|
|
eq(top and top.screenId, "Gen2DecorationMenu", "DECORATION opens the menu")
|
|
top.onDone(true)
|
|
eq(pc.changedDecorations, true, "a moved decoration is remembered")
|
|
press(pc, input, "b")
|
|
eq(answered, true, "and answered out, for PlayersHousePCScript's iftrue")
|
|
end
|
|
|
|
-- ------------------------------------------------- the A press, wired
|
|
|
|
-- World:interact's CheckFacingTileForStdScript arm, on a stub map: COLL_PC
|
|
-- runs PCScript, COLL_RADIO runs Radio1Script, floor runs nothing.
|
|
do
|
|
local started = nil
|
|
local world = setmetatable({
|
|
map = {
|
|
def = { bgEvents = {} },
|
|
cellCollision = function(_, x, _) return x == 1 and 0x93
|
|
or (x == 3 and 0x94) or 0x00 end,
|
|
},
|
|
player = { facing = "up", cellX = 1, cellY = 1, moving = false },
|
|
npcs = {},
|
|
events = { get = function() return false end },
|
|
stdScripts = { scripts = {
|
|
PCScript = { key = "s:pc" },
|
|
Radio1Script = { key = "s:radio" },
|
|
} },
|
|
vm = { start = function(_, key) started = key return true end,
|
|
running = function() return false end },
|
|
}, { __index = World })
|
|
check(world:interact(), "A on a COLL_PC tile is claimed")
|
|
eq(started, "s:pc", "and runs PCScript")
|
|
world.player.cellX = 3
|
|
world:interact()
|
|
eq(started, "s:radio", "COLL_RADIO runs Radio1Script")
|
|
started = nil
|
|
world.player.cellX = 5
|
|
check(not world:interact(), "a plain floor tile claims nothing")
|
|
eq(started, nil, "and starts nothing")
|
|
end
|
|
|
|
-- ------------------------------------------------- the real thing
|
|
|
|
local cache = os.getenv("GOLD_CACHE")
|
|
local function loadCache(name)
|
|
local chunk = cache
|
|
and loadfile(cache .. "/data/generated/" .. name .. ".lua")
|
|
return chunk and chunk()
|
|
end
|
|
|
|
local mapsData = loadCache("maps")
|
|
local tilesetsData = loadCache("tilesets")
|
|
local scriptsData = loadCache("scripts")
|
|
local textData = loadCache("text")
|
|
local stdScriptsData = loadCache("std_scripts")
|
|
local constsData = loadCache("constants")
|
|
local itemsData = loadCache("items")
|
|
|
|
local haveCache = mapsData and tilesetsData and scriptsData and stdScriptsData
|
|
and constsData and (constsData.specialOrder ~= nil)
|
|
|
|
if not haveCache then
|
|
check(true, "no GOLD_CACHE: real-map PC checks (SKIP)")
|
|
else
|
|
local function realWorld(mapId, save)
|
|
local def = mapsData[mapId]
|
|
if not def then return nil end
|
|
local game = newGame(save, itemsData or ITEMS)
|
|
local world = World.new(game)
|
|
game.world = world
|
|
world.map = Map.new(def, tilesetsData[def.tileset] or {})
|
|
world.maps = mapsData
|
|
world.stdScripts = stdScriptsData
|
|
world.text = textData
|
|
world.constants = constsData
|
|
world.pollTimeOfDay = function() end
|
|
world.vm = Vm.new(scriptsData, textData, world.events, {
|
|
specialOrder = constsData.specialOrder,
|
|
specials = world:specialHooks(),
|
|
openPc = function() world:openPc() end,
|
|
})
|
|
return world, game
|
|
end
|
|
|
|
-- The Cherrygrove Pokecenter: find the PC by its collision, stand under it,
|
|
-- press A, and the whole chain runs -- PCScript out of std_scripts, the
|
|
-- PokemonCenterPC special, World:openPc, the whose-PC menu.
|
|
do
|
|
local save = newSave(1)
|
|
local world, game = realWorld("CHERRYGROVE_POKECENTER_1F", save)
|
|
check(world ~= nil, "the cache carries CHERRYGROVE_POKECENTER_1F")
|
|
if world then
|
|
local pcX, pcY
|
|
for cy = 0, world.map.heightCells - 1 do
|
|
for cx = 0, world.map.widthCells - 1 do
|
|
if world.map:cellCollision(cx, cy) == 0x93 then pcX, pcY = cx, cy end
|
|
end
|
|
end
|
|
check(pcX ~= nil, "and its PC is a COLL_PC tile")
|
|
if pcX then
|
|
world.player = { facing = "up", cellX = pcX, cellY = pcY + 1,
|
|
moving = false }
|
|
check(world:interact(), "the A press at the PC is claimed")
|
|
for _ = 1, 8 do
|
|
if not world.vm:running() then break end
|
|
world.vm:update()
|
|
end
|
|
local top = game.stack:top()
|
|
eq(top and top.screenId, "Gen2CenterPcMenu",
|
|
"and the whose-PC menu is on the stack")
|
|
if top and top.screenId == "Gen2CenterPcMenu" then
|
|
local input = game.input
|
|
press(top, input, "a") -- boot line
|
|
eq(entryIds(top.entries), "bills,players,turnoff",
|
|
"a fresh save sees the BEFORE_POKEDEX list")
|
|
-- Deposit a POTION through <PLAYER>'s PC, for real.
|
|
Bag.add(save, "POTION", 2, game.data)
|
|
press(top, input, "down", "a", "a", "a")
|
|
local itemPc = game.stack:top()
|
|
eq(itemPc and itemPc.screenId, "Gen2ItemPcMenu",
|
|
"<PLAYER>'s PC opens the item PC")
|
|
if itemPc and itemPc.screenId == "Gen2ItemPcMenu" then
|
|
press(itemPc, input, "down", "a") -- DEPOSIT ITEM
|
|
press(itemPc, input, "a") -- the POTION row
|
|
press(itemPc, input, "a") -- x1
|
|
eq(save.pcItems.POTION, 1, "the POTION lands in save.pcItems")
|
|
eq(save.inventory.POTION, 1, "and leaves the bag")
|
|
press(itemPc, input, "a", "b", "b") -- message, PACK, log off
|
|
eq(game.stack:top(), top, "LOG OFF drops back to the whose-PC menu")
|
|
end
|
|
press(top, input, "b") -- shutdown
|
|
eq(game.stack:top(), nil, "and B logs the whole PC off")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- The bedroom: the real PLAYERS_HOUSE_2F bg event, the blocking
|
|
-- PlayersHousePC special, the item PC -- and the FALSE answered back to
|
|
-- PlayersHousePCScript when no decoration moved.
|
|
do
|
|
local save = newSave(1)
|
|
local world, game = realWorld("PLAYERS_HOUSE_2F", save)
|
|
check(world ~= nil, "the cache carries PLAYERS_HOUSE_2F")
|
|
if world then
|
|
local pcEvent
|
|
for _, ev in ipairs(world.map.def.bgEvents or {}) do
|
|
if ev.kind == 1 then pcEvent = ev end -- BGEVENT_UP: the PC
|
|
end
|
|
check(pcEvent ~= nil, "the bedroom PC is its BGEVENT_UP event")
|
|
if pcEvent then
|
|
world.player = { facing = "up", cellX = pcEvent.x, cellY = pcEvent.y + 1,
|
|
moving = false }
|
|
check(world:interact(), "the A press at the bedroom PC is claimed")
|
|
for _ = 1, 4 do
|
|
if game.stack:top() then break end
|
|
world.vm:update()
|
|
end
|
|
local top = game.stack:top()
|
|
eq(top and top.screenId, "Gen2ItemPcMenu",
|
|
"and the bedroom PC is the ITEM PC, not the storage system")
|
|
if top and top.screenId == "Gen2ItemPcMenu" then
|
|
eq(top.house, true, "in its PLAYERSPC_HOUSE shape")
|
|
check(top.message ~= nil, "with the turn-on line up")
|
|
local input = game.input
|
|
press(top, input, "a", "b") -- boot line, then TURN OFF via B
|
|
eq(game.stack:top(), nil, "closing it pops the screen")
|
|
for _ = 1, 6 do
|
|
if not world.vm:running() then break end
|
|
world.vm:update()
|
|
end
|
|
check(not world.vm:running(),
|
|
"and PlayersHousePCScript runs to its end off the FALSE answer")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ------------------------------------------- MOVE POKéMON W/O MAIL, in full
|
|
--
|
|
-- The regression this block exists for: A on a mon used to move it instantly
|
|
-- to "the next box with room", with the confirmation string computed and then
|
|
-- thrown away -- so from the player's chair the PC ATE the mon. Nothing on
|
|
-- screen named a destination and nothing was printed, which is the shape of
|
|
-- the bug report ("Move Pokemon w/o mail deletes the Pokemon when you select
|
|
-- it"). These checks pin the cart's own flow instead
|
|
-- (_MovePKMNWithoutMail, engine/pokemon/bills_pc.asm:480): a submenu, a
|
|
-- destination the player picks, and the mon accounted for at every step.
|
|
do
|
|
local BoxMenu = require("src.ui.gen2.BoxMenu")
|
|
local Boxes = require("src.core.gen2.Boxes")
|
|
|
|
local function census(save)
|
|
local n = #(save.party or {})
|
|
for i = 1, Boxes.NUM_BOXES do n = n + Boxes.count(save, i) end
|
|
return n
|
|
end
|
|
|
|
local save = newSave(3)
|
|
local box = Boxes.box(save, 1)
|
|
box[1] = { species = "PIDGEY", nickname = "BOXED1", hp = 10, maxHp = 10,
|
|
level = 3 }
|
|
box[2] = { species = "RATTATA", nickname = "BOXED2", hp = 10, maxHp = 10,
|
|
level = 3 }
|
|
local game, input = newGame(save)
|
|
local menu = BoxMenu.new(game, { save = save, mode = "move",
|
|
onClose = function() end })
|
|
eq(census(save), 5, "five mons before anything is moved")
|
|
eq(menu:prompt(), "Choose a <PK><MN>.", "PCString_ChooseaPKMN opens the list")
|
|
|
|
-- 1. A opens .MoveMonWOMailSubmenu rather than moving anything.
|
|
press(menu, input, "a")
|
|
eq(menu.phase, "submenu", "A on a mon opens the submenu")
|
|
eq(menu:prompt(), "What's up?", "under PCString_WhatsUp")
|
|
eq(#Boxes.box(save, 1), 2, "and the box is untouched by opening it")
|
|
|
|
-- CANCEL and B both come back with the mon exactly where it was.
|
|
press(menu, input, "b")
|
|
eq(menu.phase, nil, "B closes the submenu")
|
|
eq(#Boxes.box(save, 1), 2, "still two in BOX1")
|
|
|
|
-- 2. MOVE asks where, and the mon is STILL in its box while it asks.
|
|
press(menu, input, "a", "a")
|
|
eq(menu.phase, "insert", "MOVE opens the insert cursor")
|
|
eq(menu:prompt(), "Move to where?", "under PCString_MoveToWhere")
|
|
eq(#Boxes.box(save, 1), 2, "nothing has left the box yet")
|
|
eq(census(save), 5, "and nothing has left the save")
|
|
|
|
-- B there is .b_button_2: the backed-up position, nothing moved.
|
|
press(menu, input, "b")
|
|
eq(menu.phase, nil, "B backs out of the insert cursor")
|
|
eq(#Boxes.box(save, 1), 2, "with the mon still in BOX1")
|
|
eq(census(save), 5, "and the census unchanged")
|
|
|
|
-- 3. right walks to BOX2 and A inserts there -- the destination the player
|
|
-- chose, named on screen, with the cart's own confirmation.
|
|
press(menu, input, "a", "a", "right")
|
|
eq(menu.boxIndex, 2, "right walks the insert cursor to the next box")
|
|
eq(menu:title(), "BOX2", "and the header names it")
|
|
press(menu, input, "a")
|
|
eq(census(save), 5, "the mon still exists")
|
|
eq(#Boxes.box(save, 1), 1, "one left BOX1")
|
|
eq(Boxes.box(save, 2)[1].nickname, "BOXED1", "and it is in BOX2")
|
|
eq(menu.message, "Saving\xe2\x80\xa6 Leave ON!",
|
|
"MovePKMNWithoutMail_InsertMon's own line says so")
|
|
|
|
-- 4. left twice reaches the PARTY (wBillsPC_LoadedBox 0), which the old
|
|
-- screen could not show at all.
|
|
press(menu, input, "a", "left", "left")
|
|
eq(menu.boxIndex, 0, "left wraps through BOX1 to the party")
|
|
eq(menu:title(), "PARTY <PK><MN>", "BillsPC_BoxName's .party arm")
|
|
eq(#menu:list(), 3, "and the list is the party")
|
|
|
|
-- A party mon moves into a box, and the party shrinks by exactly one.
|
|
press(menu, input, "a", "a", "right")
|
|
eq(menu.boxIndex, 1, "right from the party is BOX1")
|
|
press(menu, input, "a")
|
|
eq(#save.party, 2, "the party is one shorter")
|
|
eq(census(save), 5, "and the mon is still in the save")
|
|
eq(Boxes.box(save, 1)[1].nickname, "MON1", "sitting where the cursor was")
|
|
|
|
-- BillsPC_CheckMail_PreventBlackout: a party of two may not send one away.
|
|
press(menu, input, "a", "left")
|
|
eq(menu.boxIndex, 0, "back on the party")
|
|
press(menu, input, "a", "a")
|
|
eq(menu.phase, nil, "the MOVE is refused outright")
|
|
eq(menu.message, "It's your last <PK><MN>!", "with PCString_ItsYourLastPKMN")
|
|
eq(#save.party, 2, "and the party is untouched")
|
|
|
|
-- .MoveMonWOMailSubmenu has no RELEASE row, so SELECT on this screen must
|
|
-- not open the withdraw list's release question either.
|
|
press(menu, input, "a")
|
|
press(menu, input, "select")
|
|
eq(#game.stack._items, 0, "SELECT on the move screen opens nothing")
|
|
eq(census(save), 5, "and releases nothing")
|
|
end
|
|
|
|
S.finish()
|