mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-18 11:44:42 +02:00
141 lines
4.8 KiB
Lua
141 lines
4.8 KiB
Lua
-- Gen 2 keeps FOUR pockets, not Gen 1's single bag (item_data_constants.asm):
|
|
-- Items 20, Balls 12, Key Items 25, TM/HM 57. A distinct item id occupies one
|
|
-- slot of ITS OWN pocket regardless of quantity, and a pocket fills
|
|
-- independently -- which is exactly why the cart can hold every TM, every key
|
|
-- item AND still pick up an HM. Modelling all four as one 20-slot list filled
|
|
-- the bag with TMs and key items by the Ice Path and refused HM07 WATERFALL.
|
|
-- Badges live in the inventory table but are not bag items. save.bagOrder
|
|
-- keeps acquisition order like wBagItems (SELECT can reorder it).
|
|
|
|
local Bag = {}
|
|
|
|
-- MAX_ITEMS / MAX_BALLS / MAX_KEY_ITEMS, and the TM/HM pocket holds one of
|
|
-- every TM plus the seven HMs (NUM_TMS + NUM_HMS). A `mods` bagSize override
|
|
-- replaces the ITEM pocket only, the way the Gen 1 single-bag config did.
|
|
local POCKET_CAPACITY = {
|
|
ITEM = 20,
|
|
BALL = 12,
|
|
KEY_ITEM = 25,
|
|
TM_HM = 64,
|
|
}
|
|
local DEFAULT_CAPACITY = 20
|
|
|
|
local function isBadge(id)
|
|
return id:find("BADGE", 1, true) ~= nil
|
|
end
|
|
|
|
-- Which pocket an id belongs to. Unknown ids (a stale cache, a mod that did
|
|
-- not declare a pocket) fall to ITEM, the Gen 1 behaviour.
|
|
local function pocketOf(id, data)
|
|
data = data or require("src.core.Data")
|
|
local def = data and data.items and data.items[id]
|
|
return (def and def.pocket) or "ITEM"
|
|
end
|
|
Bag.pocketOf = pocketOf
|
|
|
|
-- `data` is injectable for the save editor and headless mod tests. Normal
|
|
-- gameplay may omit it because the loader merges mods into the Data
|
|
-- singleton before any item can be added. A pocket argument gives that
|
|
-- pocket's cap; omitting it keeps the old single-number ITEM answer so
|
|
-- existing callers (and the mod bagSize override) are unchanged.
|
|
function Bag.capacity(data, pocket)
|
|
data = data or require("src.core.Data")
|
|
if pocket and pocket ~= "ITEM" then
|
|
return POCKET_CAPACITY[pocket] or DEFAULT_CAPACITY
|
|
end
|
|
local configured = data and data.constants and data.constants.bagSize
|
|
if type(configured) == "number" and configured >= 1 then
|
|
return math.floor(configured)
|
|
end
|
|
return POCKET_CAPACITY.ITEM
|
|
end
|
|
|
|
-- exported so item lists that share save.inventory (e.g. the PC deposit
|
|
-- menu) can exclude badges the same way the bag does
|
|
Bag.isBadge = isBadge
|
|
|
|
-- Occupied slots, of one pocket when named or of the whole inventory when not.
|
|
function Bag.slots(save, data, pocket)
|
|
local n = 0
|
|
for id in pairs(save.inventory) do
|
|
if not isBadge(id)
|
|
and (not pocket or pocketOf(id, data) == pocket) then
|
|
n = n + 1
|
|
end
|
|
end
|
|
return n
|
|
end
|
|
|
|
-- Acquisition-ordered id list (wBagItems). Rebuilt sorted once for
|
|
-- saves from before the order existed, then maintained incrementally.
|
|
function Bag.order(save)
|
|
local order = save.bagOrder
|
|
if not order then
|
|
order = {}
|
|
for id in pairs(save.inventory) do
|
|
if not isBadge(id) then table.insert(order, id) end
|
|
end
|
|
table.sort(order)
|
|
save.bagOrder = order
|
|
end
|
|
-- drop stale ids, append unknown ones (defensive against direct
|
|
-- inventory writes)
|
|
local seen = {}
|
|
for i = #order, 1, -1 do
|
|
local id = order[i]
|
|
if not save.inventory[id] or seen[id] then
|
|
table.remove(order, i)
|
|
else
|
|
seen[id] = true
|
|
end
|
|
end
|
|
for id in pairs(save.inventory) do
|
|
if not isBadge(id) and not seen[id] then table.insert(order, id) end
|
|
end
|
|
return order
|
|
end
|
|
|
|
-- Add qty of an item; returns false (and adds nothing) when a new slot
|
|
-- is needed and the bag is full, or when the stack would pass 99
|
|
-- (AddItemToInventory's per-slot quantity cap).
|
|
function Bag.add(save, id, qty, data)
|
|
local inv = save.inventory
|
|
-- Only the item's OWN pocket has to have room -- a full ITEM pocket does not
|
|
-- keep a KEY_ITEM or an HM out, which is the whole point of pockets.
|
|
local pocket = pocketOf(id, data)
|
|
if not inv[id] and not isBadge(id)
|
|
and Bag.slots(save, data, pocket) >= Bag.capacity(data, pocket) then
|
|
return false
|
|
end
|
|
if not isBadge(id) and (inv[id] or 0) + (qty or 1) > 99 then
|
|
return false
|
|
end
|
|
-- Insert into the order BEFORE the inventory write: Bag.order's defensive
|
|
-- append reads save.inventory, so writing first made it add the id and the
|
|
-- table.insert below add it again -- one pickup, two bag rows, until the
|
|
-- next order() pass deduped it (and a save taken in between kept both).
|
|
local isNew = not inv[id]
|
|
if isNew and not isBadge(id) then
|
|
table.insert(Bag.order(save), id)
|
|
end
|
|
inv[id] = (inv[id] or 0) + (qty or 1)
|
|
return true
|
|
end
|
|
|
|
-- Remove qty (default 1); clears the slot and its order entry at zero.
|
|
function Bag.remove(save, id, qty)
|
|
local inv = save.inventory
|
|
inv[id] = (inv[id] or 0) - (qty or 1)
|
|
if inv[id] <= 0 then
|
|
inv[id] = nil
|
|
local order = save.bagOrder
|
|
if order then
|
|
for i, oid in ipairs(order) do
|
|
if oid == id then table.remove(order, i) break end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return Bag
|