Files
gen1recomp/tests/engine/bag_split_stack_bug2671.lua
T

316 lines
11 KiB
Lua

-- engine/items/inventory.asm:59
-- engine/events/pokemart.asm:152
package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
local check, eq = T.check, T.eq
love = love or require("tests.love_stub")
local function glyphs(text)
local n = 0
for _ in tostring(text):gmatch("[%z\1-\127\194-\244][\128-\191]*") do
n = n + 1
end
return n
end
local realFont = package.loaded["src.render.Font"]
package.loaded["src.render.Font"] = {
BORDER = { tl = 1, tr = 2, bl = 3, br = 4, h = 5, v = 6 },
draw = function() end,
drawCode = function() end,
drawBox = function() end,
width = function(text) return glyphs(text) * 8 end,
split = function(text)
local out = {}
for s in tostring(text):gmatch("[%z\1-\127\194-\244][\128-\191]*") do
out[#out + 1] = s
end
return out
end,
spansFitting = function(spans, pixels)
return math.min(#spans, math.floor(pixels / 8))
end,
encode = function(text) return { text } end,
advanceOf = function() return 8 end,
}
local RELOAD = {
"src.ui.QuantityBox", "src.ui.ShopMenu", "src.ui.ListMenu",
"src.ui.ChoiceBox", "src.ui.Menu", "src.ui.Theme", "src.render.TextBox",
}
for _, m in ipairs(RELOAD) do package.loaded[m] = nil end
local GameVersion = require("src.core.GameVersion")
local Bag = require("src.inventory.Bag")
local ShopMenu = require("src.ui.ShopMenu")
local TextBox = require("src.render.TextBox")
local SaveData = require("src.core.SaveData")
local BAG_FULL = "You can't carry\nany more items."
local BOUGHT = "Here you are!\nThank you!"
local function boxText(box)
local out = {}
for _, page in ipairs(box.pages or {}) do
for _, line in ipairs(page) do out[#out + 1] = line end
end
return table.concat(out, " ")
end
local function rowsOf(save, data)
local out = {}
for _, r in ipairs(Bag.rows(save, data)) do
out[#out + 1] = r.id .. "x" .. tostring(r.count)
end
return table.concat(out, ",")
end
local function newGame(money, inventory, order)
local stack = {}
local game
game = {
data = {
items = {
X_ACCURACY = { name = "X ACCURACY", price = 950, index = 46 },
POTION = { name = "POTION", price = 300, index = 20 },
},
text = {
_PokemartItemBagFullText = BAG_FULL,
_PokemartBoughtItemText = BOUGHT,
},
constants = {},
field = {},
},
save = { money = money, inventory = inventory, bagOrder = order },
input = {
wasPressed = function() return false end,
isDown = function() return false end,
},
}
game.stack = {
push = function(_, s) stack[#stack + 1] = s end,
pop = function() return table.remove(stack) end,
top = function() return stack[#stack] end,
}
return game, stack
end
local function drain(game)
for _ = 1, 600 do
local top = game.stack:top()
if not (top and top.stay and not top.stayShown) then return end
top:update(1 / 60)
end
end
local function fill(game, n)
for i = 1, n do
local id = "FILLER_" .. i
game.save.inventory[id] = 1
game.save.bagOrder[#game.save.bagOrder + 1] = id
end
end
local function buy(game, stack, id, qty)
local menu = ShopMenu.new(game, { id }, function() end)
menu.items[1].onSelect()
drain(game)
local list = stack[#stack]
list.onChoose({ value = id })
local qtyBox = stack[#stack]
qtyBox.onDone(qty)
local confirm = stack[#stack]
game.stack:pop()
confirm.choice(true)
return stack[#stack]
end
GameVersion.set("red")
do
local game = newGame(0, { X_ACCURACY = 2, POTION = 5 }, { "X_ACCURACY", "POTION" })
eq(Bag.add(game.save, "X_ACCURACY", 99, game.data), true,
"2 + 99 X ACCURACY fits with a free slot")
eq(game.save.inventory.X_ACCURACY, 101, "the total is 101")
eq(rowsOf(game.save, game.data), "X_ACCURACYx99,POTIONx5,X_ACCURACYx2",
"the first slot maxes at 99 and the overflow lands at the end")
eq(Bag.slots(game.save, game.data), 3, "three bag slots")
Bag.remove(game.save, "X_ACCURACY", 2, game.data, 2)
eq(rowsOf(game.save, game.data), "X_ACCURACYx99,POTIONx5",
"tossing the overflow row removes that slot")
Bag.add(game.save, "X_ACCURACY", 2, game.data)
Bag.remove(game.save, "X_ACCURACY", 99, game.data, 1)
eq(rowsOf(game.save, game.data), "POTIONx5,X_ACCURACYx2",
"emptying the first slot keeps the later slot where it was")
end
do
local game = newGame(0, { X_ACCURACY = 99 }, { "X_ACCURACY" })
fill(game, 19)
eq(Bag.add(game.save, "X_ACCURACY", 1, game.data), false,
"a 20-slot bag refuses the overflow slot")
eq(game.save.inventory.X_ACCURACY, 99, "and changes nothing")
eq(#game.save.bagOrder, 20, "no slot is added")
end
do
local game = newGame(0, { X_ACCURACY = 149 }, { "X_ACCURACY", "X_ACCURACY" })
fill(game, 18)
eq(Bag.add(game.save, "X_ACCURACY", 10, game.data), false,
"a full bag refuses once the maxed first slot overflows")
eq(game.save.inventory.X_ACCURACY, 149, "and changes nothing")
end
do
local game, stack = newGame(104276, { X_ACCURACY = 2 }, { "X_ACCURACY" })
local box = buy(game, stack, "X_ACCURACY", 99)
if check(getmetatable(box) == TextBox, "mart: a text box follows the YES") then
check(boxText(box):find("Here you are!", 1, true) ~= nil,
"mart: the clerk hands the items over")
check(boxText(box):find("can't carry", 1, true) == nil,
"mart: no bag-full refusal")
end
eq(game.save.money, 104276 - 94050, "mart: 94050 is paid")
eq(rowsOf(game.save, game.data), "X_ACCURACYx99,X_ACCURACYx2",
"mart: two X ACCURACY rows")
end
do
local game, stack = newGame(3000, { X_ACCURACY = 99 }, { "X_ACCURACY" })
fill(game, 19)
local box = buy(game, stack, "X_ACCURACY", 1)
if check(getmetatable(box) == TextBox, "mart full: a text box follows the YES") then
check(boxText(box):find("can't carry", 1, true) ~= nil,
"mart full: the bag-full refusal")
end
eq(game.save.money, 3000, "mart full: no money changes hands")
end
do
local game, stack = newGame(0, { X_ACCURACY = 101, POTION = 5 },
{ "X_ACCURACY", "POTION", "X_ACCURACY" })
local menu = ShopMenu.new(game, {}, function() end)
menu.items[2].onSelect()
drain(game)
local list = stack[#stack]
local counts = {}
for _, it in ipairs(list.items) do
if not it.cancel then counts[#counts + 1] = tostring(it.count) end
end
eq(table.concat(counts, ","), "99,5,2", "sell list shows each slot's own count")
list.index = 3
list.onChoose(list.items[3])
local qtyBox = stack[#stack]
eq(qtyBox.max, 2, "selling from the overflow row caps at that row")
qtyBox.onDone(2)
local confirm = stack[#stack]
game.stack:pop()
confirm.choice(true)
eq(rowsOf(game.save, game.data), "X_ACCURACYx99,POTIONx5", "the sold row is gone")
eq(game.save.money, 950, "paid half price for two")
end
do
local game = newGame(0, { X_ACCURACY = 101, POTION = 5 },
{ "X_ACCURACY", "POTION", "X_ACCURACY" })
local back = assert(SaveData.decode(SaveData.encode(game.save)))
SaveData.validate(back, game.data)
eq(rowsOf(back, game.data), "X_ACCURACYx99,POTIONx5,X_ACCURACYx2",
"split slots survive a save round trip")
end
do
local pc, order = { POTION = 99 }, { "POTION" }
local data = newGame(0, {}, {}).data
eq(Bag.addTo(pc, order, "POTION", 50, data, Bag.storeSlots(pc, data), 50), true,
"PC deposit past 99 splits the box slot")
eq(table.concat(order, ","), "POTION,POTION", "two box slots")
local full, fullOrder = { POTION = 1 }, { "POTION" }
for i = 1, 49 do
full["FILLER_" .. i] = 1
fullOrder[#fullOrder + 1] = "FILLER_" .. i
end
full.POTION = 99
eq(Bag.addTo(full, fullOrder, "POTION", 1, data, Bag.storeSlots(full, data), 50), false,
"a 50-slot box refuses the overflow slot")
end
do
local game = newGame(0, { X_ACCURACY = 101, POTION = 5 },
{ "X_ACCURACY", "POTION", "X_ACCURACY" })
Bag.remove(game.save, "X_ACCURACY", 5, game.data, 1)
eq(rowsOf(game.save, game.data), "X_ACCURACYx94,POTIONx5,X_ACCURACYx2",
"tossing from the first slot leaves 94 and 2")
eq(Bag.slots(game.save, game.data), 3, "both X ACCURACY slots stay")
local back = assert(SaveData.decode(SaveData.encode(game.save)))
SaveData.validate(back, game.data)
eq(rowsOf(back, game.data), "X_ACCURACYx94,POTIONx5,X_ACCURACYx2",
"a 94/2 split survives a save round trip")
Bag.remove(game.save, "X_ACCURACY", 1, game.data)
eq(rowsOf(game.save, game.data), "X_ACCURACYx93,POTIONx5,X_ACCURACYx2",
"a slotless remove takes from the first slot")
Bag.add(game.save, "X_ACCURACY", 10, game.data)
eq(rowsOf(game.save, game.data), "X_ACCURACYx99,POTIONx5,X_ACCURACYx6",
"adding tops the first slot up and carries the rest to the next")
Bag.swap(game.save, 1, 3, game.data)
eq(rowsOf(game.save, game.data), "X_ACCURACYx6,POTIONx5,X_ACCURACYx99",
"SELECT on two slots of one item moves the 99 to the second pick")
Bag.swap(game.save, 1, 2, game.data)
eq(rowsOf(game.save, game.data), "POTIONx5,X_ACCURACYx6,X_ACCURACYx99",
"swapping different items keeps each slot's count")
Bag.swap(game.save, 3, 2, game.data)
eq(rowsOf(game.save, game.data), "POTIONx5,X_ACCURACYx99,X_ACCURACYx6",
"the 99 follows the second pick when the sum exceeds 99")
Bag.remove(game.save, "X_ACCURACY", 90, game.data, 2)
Bag.swap(game.save, 2, 3, game.data)
eq(rowsOf(game.save, game.data), "POTIONx5,X_ACCURACYx15",
"SELECT merges two slots that fit in one")
eq(game.save.bagStacks, nil, "a merged stack carries no per-slot list")
end
do
local game, stack = newGame(0, { X_ACCURACY = 101, POTION = 5 },
{ "X_ACCURACY", "POTION", "X_ACCURACY" })
local menu = ShopMenu.new(game, {}, function() end)
menu.items[2].onSelect()
drain(game)
local list = stack[#stack]
local function sellRow(row, qty)
list.index = row
list.onChoose(list.items[row])
local qtyBox = stack[#stack]
qtyBox.onDone(qty)
local confirm = stack[#stack]
game.stack:pop()
confirm.choice(true)
end
list.scroll = 1
sellRow(3, 1)
eq(list.scroll, 1, "selling part of a row keeps the list scroll")
eq(list.index, 3, "and the cursor")
sellRow(3, 1)
eq(rowsOf(game.save, game.data), "X_ACCURACYx99,POTIONx5", "the emptied row is gone")
eq(list.scroll, 0, "an emptied row resets the sell list scroll")
eq(list.index, 1, "and puts the cursor on the first row")
end
do
GameVersion.set("gold")
local game = newGame(0, { POTION = 99 }, { "POTION" })
game.data.items.POTION.pocket = "ITEM"
eq(Bag.add(game.save, "POTION", 1, game.data), false,
"Gen 2 pockets keep one row per id")
eq(#game.save.bagOrder, 1, "no duplicate row in Gen 2")
GameVersion.set("red")
end
package.loaded["src.render.Font"] = realFont
for _, m in ipairs(RELOAD) do package.loaded[m] = nil end
require("src.ui.Screens").invalidate()
T.finish()