DEMI-PROJECT-PACKAGE {"files":[{"hash":"sha256:237d8022dc22454fb64d16dfdfb297061a1ff1778c74372f8e5b78b4a844c690","path":"README.md","size":1983},{"hash":"sha256:0a0c94933d468e9c7220157c785d3c61daa00d291337cf23eca22fe8f8a7ed17","path":"scripts/demi/gameplay/inventory.lua","size":1701},{"hash":"sha256:05425275ce56d779cb5365b17d3c226c0793a1b2acc29f89278cc963a72f2735","path":"tests/inventory_test.lua","size":698}],"format_version":1,"manifest":{"dependencies":{"demi.gameplay.events":"^1.0.0"},"engine":"^0.1.0","exported_events":["inventory_changed","equipment_changed"],"files":["README.md","scripts/demi/gameplay/inventory.lua","tests/inventory_test.lua"],"format_version":1,"name":"demi.gameplay.inventory","public_modules":["demi.gameplay.inventory"],"version":"1.1.0"},"package_type":"DemiProjectPackage"}# Inventory and equipment Package: `demi.gameplay.inventory`. Stores one count per item ID and maps equipment slots to owned items. Capacity limits distinct item IDs, not total items. Exports `demi.gameplay.inventory`; depends on `demi.gameplay.events`. See [package installation](../../README.md). ```lua local Events = require("demi.gameplay.events") local Inventory = require("demi.gameplay.inventory") local events = Events.new() local inventory = Inventory.new(events, 20) assert(inventory:add("potion", 8, 5) == 5) inventory:add("sword", 1, 1) assert(inventory:equip("hand", "sword")) events:flush() local saved = inventory:save() local restored = Inventory.new(events) assert(restored:load(saved)) ``` `add(item, count, maximum)` takes a per-item cap on each call; the cap is not stored as an item definition. `remove(item, count)` returns the amount removed. `equip(slot, nil)` clears a slot. Removing the last item does not automatically unequip it, so the game must enforce that rule if needed. Supply valid counts and caps; a cap below the existing count can make `add` return a negative value. The caller owns and flushes the event bus. Successful additions/removals queue `inventory_changed` with the resulting count; accepted equip calls queue `equipment_changed`. State changes before dispatch. Keep the unsubscribe function returned by `events:on` and call it when its owner is destroyed. `load` emits no events. `save` returns a Lua table, not a disk save or independent snapshot: its `stacks` and `equipment` tables are shared with the inventory. `load` also retains the supplied tables and only checks that the outer value and `stacks` are tables. The game must validate loaded content and serialize/copy it as needed. This package does not load item assets, enforce equipment types, apply item effects, or provide inventory UI. From the repository root, test with: ```sh ./build/linux-debug/demi package test packages/sources/demi.gameplay.inventory ``` local Inventory = {} Inventory.__index = Inventory function Inventory.new(events, capacity) return setmetatable({ events = assert(events), capacity = capacity or 20, stacks = {}, equipment = {} }, Inventory) end function Inventory:add(item, count, maximum) count, maximum = count or 1, maximum or math.huge if count <= 0 then return 0 end local current = self.stacks[item] or 0 local added = math.min(count, maximum - current) if current == 0 and added > 0 then local unique = 0; for _ in pairs(self.stacks) do unique = unique + 1 end if unique >= self.capacity then return 0 end end if added > 0 then self.stacks[item] = current + added; self.events:emit("inventory_changed", { item = item, count = self.stacks[item] }) end return added end function Inventory:remove(item, count) local current = self.stacks[item] or 0; local removed = math.min(current, math.max(0, count or 1)) local left = current - removed; self.stacks[item] = left > 0 and left or nil if removed > 0 then self.events:emit("inventory_changed", { item = item, count = left }) end return removed end function Inventory:equip(slot, item) if item and not self.stacks[item] then return false end self.equipment[slot] = item; self.events:emit("equipment_changed", { slot = slot, item = item }); return true end function Inventory:save() return { stacks = self.stacks, equipment = self.equipment, capacity = self.capacity } end function Inventory:load(value) if type(value) ~= "table" or type(value.stacks) ~= "table" then return false end self.stacks, self.equipment, self.capacity = value.stacks, value.equipment or {}, value.capacity or self.capacity return true end return Inventory local Test = require("demi.test") local Events = require("demi.gameplay.events") local Inventory = require("demi.gameplay.inventory") Test.case("capacity and stacks clamp", function() local inventory = Inventory.new(Events.new(), 1) Test.equal(inventory:add("coin", 8, 5), 5); Test.equal(inventory:add("key", 1), 0) Test.equal(inventory:remove("coin", 99), 5); Test.equal(inventory.stacks.coin, nil) end) Test.case("save load keeps equipment", function() local first = Inventory.new(Events.new()); first:add("sword"); first:equip("hand", "sword") local second = Inventory.new(Events.new()); Test.equal(second:load(first:save()), true) Test.equal(second.equipment.hand, "sword") end)