Systems
InventorySystem
Items and stacks in containers saved in player profiles, Roblox Tools, equipment with stats, crafting with stations, chests, world pickups, selling and shops.
Getting it
local InventorySystem = Engine:GetSystem("InventorySystem")Overview
InventorySystem is a complete inventory built around Roblox Tools, on top of DataService:
- Items you define once, with stack sizes, weight, categories, tags, stats and your own data. Every stack keeps its own
Idand can carry its ownData, such as durability or a custom name. - Containers with slots, weight limits and filters. Every player gets the containers you define, by default one 30-slot
Backpack, saved in their profile and mirrored to their client. - Tools: an item with a Tool is a Tool in the player's Backpack, given back after every respawn.
- Equipment slots that put an item's Accessory on the character and add up its stats.
- Using items, with cooldowns and consumables.
- Crafting with ingredients, tools that are kept, currency costs, unlocks and crafting stations.
- World containers for chests and stashes, and pickups that lie in the world with a ProximityPrompt.
- Selling items and item shops through EconomySystem.
- Offline and cross-server gives and takes that are applied exactly once.
Inventories live in player profiles. By default the system creates and runs its own profile store (Engine.Inventory) and starts a session for every player. If your game already has a player profile store, see Profiles.
Get the system in a server script that runs at start-up, and define your items, player containers and equipment slots there, before the first player joins.
local ServerStorage = game:GetService("ServerStorage")
local InventorySystem = Engine:GetSystem("InventorySystem")
InventorySystem:DefineItem("Sword", {
Name = "Iron Sword",
Tool = ServerStorage.Tools.Sword, -- the player holds it like any Tool
Droppable = true,
})
InventorySystem:DefineItem("Apple", {
MaxStack = 20,
Consume = true,
Use = function(player)
local humanoid = player.Character and player.Character:FindFirstChildOfClass("Humanoid")
if not humanoid then
return false -- refused: the apple is given back
end
humanoid.Health += 20
end,
})
InventorySystem:DefineItem("Wood", { MaxStack = 50 })
InventorySystem:DefineItem("Plank", { MaxStack = 50 })
InventorySystem:DefineRecipe("Planks", { Inputs = { Wood = 1 }, Outputs = { Plank = 4 } })
-- Somewhere in your game
local result = InventorySystem:Give(player, "Wood", 10)
if not result.Ok then
warn("Could not give wood:", result.Reason) -- for example "no space"
endlocal InventorySystem = Engine:GetSystem("InventorySystem")
local function show(slot, stack)
slotLabels[slot].Text = if stack then string.format("%s x%d", stack.Item, stack.Amount) else ""
end
InventorySystem:WaitForContainerAsync("Backpack"):andThen(function(backpack)
for slot = 1, backpack.Size do
show(slot, backpack:GetSlot(slot))
end
backpack.Changed:Connect(show)
end)
craftButton.Activated:Connect(function()
InventorySystem:CraftAsync("Planks"):catch(showError) -- for example "missing ingredients"
end)Items
DefineItem
InventorySystem:DefineItem(id: string, options: ItemOptions?): Item| Option | Description |
|---|---|
Name, Description, Icon, Category | For your UI. Name defaults to the id. |
Tags | A list of strings that container and equipment filters can match. |
MaxStack | Units one stack holds (default DefaultMaxStack). |
Weight | The weight of one unit, for containers with a MaxWeight. |
Slot | The equipment slot the item fits, such as "Head". |
Stats | { [name] = number }, added up over everything the player has equipped. |
Tool | A Tool the player can hold. See Tools. |
Accessory | An Accessory put on the character while the item is equipped. |
Model | A Model or BasePart that shows the item when it lies in the world as a pickup. |
Use | function(player, stack, container) -> (boolean?, string?), called when the item is used. Return false, with an optional reason, to refuse. |
Consume | Take one unit each time the item is used. |
Cooldown | Seconds before the player can use this item again. |
Droppable | The player may drop it: from the client, and with Backspace when it is a Tool. |
SellPrice, SellCurrency | What :Sell pays per unit, in an EconomySystem currency. |
Replicate | Send the definition to clients (default true). |
Data | Your own data, sent to clients with the definition. |
An Item has the same fields, read-only, plus Id and Usable. :GetItem(id), :FindItem(id) and :GetItems() look items up.
Stacks
A stack is { Id, Item, Amount, Data?, Slot }. Its Id stays the same when the whole stack moves to another slot or container, and through saves. Data is the stack's own data, such as durability or a custom name: two stacks only merge when their Data is equal. A stack of an item that is no longer defined is kept in the player's profile, never deleted.
Containers
Containers hold stacks in numbered slots. Players get their containers automatically (Player containers), and world containers are for chests and stashes. Both have the same API.
| Member | Description |
|---|---|
:Add(itemId, amount?, options?) | Adds items, filling existing stacks first. Options: { Data?, Slot?, Partial? }, where Slot is the slot to try first. |
:Remove(itemId, amount?, options?) | Removes items, from the last slots first. Options: { Partial? }. |
:RemoveAt(slot, amount?) | Removes from one slot, the whole stack when amount is nil. |
:Move(fromSlot, toSlot, amount?) | Moves within the container: into an empty slot, onto a stack of the same item, or swaps two stacks. |
:Transfer(fromSlot, target, toSlot?, amount?) | Moves to another container. Without toSlot, the items go wherever they fit. |
:SetData(slot, data?) | Replaces a stack's Data. |
:Sort(comparator?) | Merges and sorts the stacks, by category, name and id unless you pass comparator(itemA, itemB) -> boolean. |
:Clear() | Empties the container. |
:GetSlot(slot), :GetStacks(), :FindStack(stackId) | Read stacks. |
:Count(itemId), :Has(itemId, amount?) | |
:CanAdd(itemId, amount?, data?), :GetSpaceFor(itemId, data?) | Whether, and how many, fit. |
:FirstEmptySlot(), :IsEmpty() | |
Name, Kind, Owner, Weight, UsedSlots, FreeSlots, Tools, Carried, Access, Destroyed | Read-only. Kind is "Player", "World" or "Equipment". |
Size, MaxWeight | Settable. A player container keeps a raised Size or MaxWeight for that player. Lowering Size errors while a slot above the new size holds a stack. |
Changed: Signal<(slot, stack?, previous?)> | A slot changed. |
ItemAdded, ItemRemoved: Signal<(itemId, amount)> |
Every change returns a Result: { Ok, Amount, Reason? }. Add and Remove are all-or-nothing: when not everything fits or is there, nothing changes. Pass Partial = true to take what fits instead. Move and Transfer move as much as fits, and Amount tells you how much that was. Reasons: "no space", "too heavy", "not accepted", "not enough items", "empty slot", "slot occupied", "invalid amount", "unknown item", "same container" and "container destroyed".
Filters and weight
Accepts limits what a container holds: a list of item ids, categories, Slot names or tags (an item fits when any of them matches), or a function (item, amount, data) -> boolean. MaxWeight caps the total Weight of the contents.
Player containers
Every player gets the containers in PlayerContainers, by default one 30-slot Backpack that gives Tools. They are saved in the player's profile, loaded when the session starts and mirrored to the player's client.
InventorySystem:DefinePlayerContainer("Vault", { Size = 50, Carried = false })| Option | Description |
|---|---|
Size | Slots, from 1 to MaxContainerSize. |
MaxWeight, Accepts | See Filters and weight. |
Tools | Items with a Tool in this container are Tools in the player's Backpack. |
Carried | :Give, :Take, :Count, :Has, crafting and pickups use this container (default true). A bank or vault sets it to false. |
Access | "ReadWrite" (default) or "Read": whether the player's client may change it. |
Order | The order for your UI. |
Define player containers before the first inventory loads: in a server script at start-up, or in the PlayerContainers setting. Later calls error.
| Method | Description |
|---|---|
:GetPlayerContainer(player, name?) | One of the player's containers, the DefaultContainer when name is nil. Errors while the inventory is not loaded. |
:FindPlayerContainer(player, name?) | The same, or nil. |
:GetPlayerContainerAsync(player, name?, timeout?) | Waits for the inventory to load. |
:GetPlayerContainers(player) | All of them. |
:Give(player, itemId, amount?, options?) | Adds items to the DefaultContainer, then to the other carried containers. Options: { Data?, Container?, Partial? }. |
:Take(player, itemId, amount?, options?) | Removes items from the carried containers. Options: { Container?, Partial? }. |
:Count(player, itemId, container?), :Has(player, itemId, amount?, container?) | Across the carried containers, or in one. |
:CanGive(player, itemId, amount?, options?) | |
:HasProfile(player), :GetProfileAsync(player, timeout?) | Whether the player's inventory is loaded, and their profile. |
InventoryLoaded: Signal<player> | A player's inventory loaded. |
ItemAdded, ItemRemoved: Signal<(player, itemId, amount, container)> | Items entered or left one of a player's containers. |
Give and Take return a Result, all-or-nothing unless you pass Partial = true, with the reason "no inventory" while the player's inventory is not loaded. Methods on this page that take a container for a player accept a Container or the name of one of the player's containers.
Offline and cross-server
| Method | Description |
|---|---|
:GiveAsync(target, itemId, amount?, options?) | Gives to a Player or a UserId, wherever they are, through a DataService edit that is applied exactly once. |
:TakeAsync(target, itemId, amount?, options?) | Takes the same way. |
Options: { Data?, Container?, Partial?, WaitForApply? }. Both resolve with { Ok, Status, Amount?, Reason? }, where Status is the edit result ("applied", "queued", "rejected" or "missing"). For a player in this server the change happens right away. Use them for gifts, admin tools and rewards from other places.
Tools
An item with a Tool in a container with Tools = true, such as the default Backpack, is a Tool in the player's Backpack: one Tool per stack, given back after every respawn. Each Tool has the attributes EngineItem, EngineStack (the stack's Id) and EngineAmount. The Tool you pass in DefineItem is a template that is cloned for every stack, so keep it out of the world, for example in ServerStorage, and leave its Archivable on.
When a Droppable Tool is dropped with Backspace, it becomes a pickup. Any other Tool that leaves the player is put back in the Backpack.
| Member | Description |
|---|---|
:EquipTool(player, stackId) | Makes the player hold that stack's Tool, and returns whether it worked. |
:UnequipTools(player) | |
:GetEquippedTool(player) | The Tool the player holds, and its stack. |
:FindStackByTool(tool) | The stack and container behind a Tool. |
ToolEquipped, ToolUnequipped: Signal<(player, tool, stack?)> |
Holding a Tool is not the same as equipment: equipment slots are for items the player wears.
Equipment
Equipment slots hold what a player wears or carries on them, such as a helmet. Define them in the EquipmentSlots setting, or before the first inventory loads with:
InventorySystem:DefineEquipmentSlot("Head")
InventorySystem:DefineItem("IronHelmet", {
Slot = "Head",
Accessory = ServerStorage.Accessories.IronHelmet,
Stats = { Armor = 5 },
})
-- Later, for a helmet in the player's Backpack
InventorySystem:Equip(player, "Backpack", slot)
print(InventorySystem:GetStats(player).Armor) --> 5| Slot option | Description |
|---|---|
Accepts | Item ids, categories, Slot names or tags the slot takes. By default the slot's own name, so items with Slot = "Head" fit the Head slot. |
Capacity | Units of one stack the slot holds (default 1), such as arrows in a quiver. |
Tools | Items with a Tool in this slot are Tools in the player's Backpack. |
Order | The order for your UI. |
| Member | Description |
|---|---|
:Equip(player, container, slot, slotName?) | Moves the stack in that slot into an equipment slot: slotName, or the first one that takes it, preferring an empty one. What was there goes back into the container. |
:Unequip(player, slotName, container?, toSlot?) | Moves it back, into the DefaultContainer unless you name another. |
:GetEquipment(player), :FindEquipment(player) | The player's Equipment: :GetSlot(name), :GetEquipped(), :IsEquipped(itemId), :CanEquip(itemId, slotName?), Slots, Stats, and the Changed and StatsChanged signals. |
:GetStats(player) | The Stats of everything equipped, added up. |
Equipped, Unequipped: Signal<(player, slotName, stack)> | |
StatsChanged: Signal<(player, stats)> |
An equipped item's Accessory is put on the character, and again after every respawn (EquipAccessories). Equipment is saved with the inventory.
Using items
InventorySystem:Use(player, container, slot): ResultCalls the item's Use. With Consume, one unit is taken, and given back when Use returns false or errors. After a use, Cooldown blocks the same item for that many seconds, and one item cannot be used twice at the same moment.
A refused use returns the reason Use gave, or "refused". Other reasons: "not usable", "cooldown", "busy", and "use failed" when Use errored. ItemUsed: Signal<(player, itemId, stack)> fires after every use, and :IsOnCooldown(player, itemId) tells whether the cooldown still runs.
Crafting
DefineRecipe
InventorySystem:DefineRecipe(id: string, options: RecipeOptions): Recipe| Option | Description |
|---|---|
Outputs | { [itemId] = amount } made per craft. Required. |
Inputs | { [itemId] = amount } used up per craft. |
Catalysts | { [itemId] = amount } the player must have but keeps, such as a hammer. |
Cost | { [currency] = amount } paid per craft through EconomySystem. |
Station | The station the player must be at. |
Locked | The player needs the recipe unlocked first. |
Requires | function(player, recipe, amount) -> (boolean, reason?) for your own rule. |
Name, Description, Icon, Category, Data | For your UI. |
Replicate | Send the recipe to clients (default true). Only replicated recipes can be crafted from the client. |
Recipes name items by id, so define the items first.
| Member | Description |
|---|---|
:Craft(player, recipeId, options?) | Crafts. Options: { Amount?, Station? }. Takes the inputs from the carried containers and puts the outputs there. |
:CanCraft(player, recipeId, options?) | (boolean, reason?). |
:GetCraftableAmount(player, recipeId) | How many times the player could craft it now, at most MaxCraftAmount. |
:UnlockRecipe(player, recipeId), :LockRecipe(player, recipeId), :IsRecipeUnlocked(player, recipeId) | Unlocks are saved in the player's profile. |
:GetRecipe(id), :FindRecipe(id), :GetRecipes() | |
Crafted: Signal<(player, recipe, amount)> |
A craft is all-or-nothing: when the outputs do not fit, nothing is taken. A currency Cost is paid first and refunded when the craft cannot finish. Reasons: "missing ingredients", "missing tools" (catalysts), "no space", "insufficient" (currency), "recipe locked", "not at station", "requirement not met" or the reason your Requires gave.
Stations
anvilPrompt.Triggered:Connect(function(player)
InventorySystem:OpenStation(player, "Anvil", { Anchor = workspace.Anvil })
end):OpenStation(player, station, { Anchor?, MaxDistance? }) lets the player craft that station's recipes, from the server and from their client. It ends with :CloseStation(player, station?), or once the player is farther than MaxDistance (default MaxOpenDistance) from the Anchor. :IsAtStation(player, station) checks it. In your own server code you can also pass Station to :Craft when you know the player is there.
World containers
local chest = InventorySystem:CreateContainer("Chest_1", { Size = 20 })
chest:Add("Apple", 5)
chestPrompt.Triggered:Connect(function(player)
chest:Open(player, { Anchor = chestModel })
end):CreateContainer(name, { Size, MaxWeight?, Accepts?, Contents? }) makes a container that belongs to no player, for chests, stashes and shared storage. :GetContainer(name), :FindContainer(name) and :GetContainers() look them up, and container:Destroy() removes one.
World containers are not saved for you. :Serialize() returns the contents to save yourself, for example in a Store, and Contents or :Load(stored) puts them back.
| Member | Description |
|---|---|
container:Open(player, options?) | Shows the container on the player's client. Options: { Access?, Anchor?, MaxDistance? }; with Access = "Read" the player can look but not take. |
container:Close(player), container:IsOpenFor(player), container:GetViewers() | |
ContainerOpened, ContainerClosed: Signal<(player, container)> |
A request from a player who is farther than MaxDistance (default MaxOpenDistance) from the Anchor closes the container for them. Player containers can be opened to other players too, for example to let staff look into someone's backpack.
Pickups
InventorySystem:SpawnPickup(itemId: string, amount: number?, at: Vector3 | CFrame, options: PickupOptions?): PickupPuts items in the world with a ProximityPrompt. Picking them up gives them to the player's carried containers; whatever does not fit stays on the ground.
| Option | Description |
|---|---|
Data | The stack's Data. |
Owner, OwnerSeconds | Only this player can take it, for OwnerSeconds or for as long as it lies there. |
Lifetime | Seconds before it despawns (default PickupLifetime; 0 keeps it). |
Model | A Model or BasePart to show. Defaults to the item's Model, then its Tool's Handle, then a small part. |
Parent | Where it goes (default Workspace). |
:Drop(player, container, slot, amount?) takes items out of a player's container and drops them in front of the character. A Pickup has Id, Item, Amount, Data, Instance, Owner, :Collect(player), :Destroy(), and the PickedUp and Rejected signals. :FindPickup(id) and :GetPickups() look pickups up. PickedUp: Signal<(player, pickup, amount)> and Dropped: Signal<(player, pickup)> fire on the system.
Selling and shops
EconomySystem:CreateCurrency("Coins", { Leaderstat = true })
InventorySystem:DefineItem("Gem", { MaxStack = 99, SellPrice = 40, SellCurrency = "Coins" })
InventorySystem:DefineItem("Arrow", { MaxStack = 99 })
InventorySystem:CreateShop("Fletcher", {
Currency = "Coins",
Items = {
{ Item = "Arrow", Amount = 20, Price = 30 }, -- 20 arrows for 30 coins
},
}):Sell(player, container, slot, amount?) pays the item's SellPrice per unit in its SellCurrency, and returns { Ok, Amount, Total, Reason? }. Sold: Signal<(player, itemId, amount, total)> fires after each sale.
:CreateShop(name, options) makes an EconomySystem shop that sells items. Each entry is { Item, Amount?, Price } plus the other shop item options (Id, Name, Currency, Limit, Requires, ...). The shop checks for space before it charges, then gives the items. Players buy through the shop as usual, with PurchaseAsync.
Profiles
| Member | Description |
|---|---|
:BindProfileStore(store) | Keep inventories in your own profile store. |
ProfileStoreName, ProfilePath, IsOwnedStore, DefaultContainer, ItemCount, RecipeCount, ContainerCount, PickupCount, LoadedCount | Read-only. |
To keep the inventory in your own player profiles, call :BindProfileStore(store) before the first player joins, and start your sessions with Player = player, because the system only loads inventories for sessions that have a player. The inventory is then stored under ProfilePath (default "Inventory") in your profiles.
Under ProfilePath the system keeps Containers (the slots of every player container, and a raised Size or MaxWeight), Equipment and the unlocked Recipes. It adds these tables itself. When your data already holds a value that is not a table at ProfilePath, it errors instead of overwriting that value. Stacks that cannot be read are dropped with a warning when an inventory loads.
Client
The client InventorySystem shows the player's own containers, and every container opened to them, as read-only containers that stay up to date.
| Member | Description |
|---|---|
:GetContainer(name, owner?), :FindContainer(name, owner?), :GetContainers() | A container by name. Pass owner to get another player's container that was opened to this player. |
:WaitForContainerAsync(name, timeout?) | |
:Count(itemId, container?), :Has(itemId, amount?, container?) | Across the carried containers, or in one. |
:GetItem(id), :FindItem(id), :GetItems() | The replicated item catalog (ReplicateCatalog). |
:GetRecipe(id), :FindRecipe(id), :GetRecipes(), :GetEquipmentSlots() | |
:WaitForCatalogAsync(timeout?) | |
:GetEquipped(slotName), :GetEquipment(), :GetStats() | The local player's equipment. |
:CanCraft(recipeId, amount?), :GetCraftableAmount(recipeId), :IsRecipeUnlocked(recipeId) | Checked on the client, for your UI. |
:GetStations(), :IsAtStation(station) | |
:CraftAsync(recipeId, amount?), :UnequipAsync(slotName, to?, toSlot?) | Requests to the server. |
ContainerAdded, ContainerRemoved: Signal<container> | |
EquipmentChanged: Signal<(slotName, stack?, previous?)>, StatsChanged: Signal<stats> | |
StationOpened, StationClosed: Signal<station>, CatalogChanged, RecipesChanged | |
IsReady, HasProfile, ProfileStoreName, ProfilePath, DefaultContainer, ClientActions | Read-only. |
A client container has the read methods of a server container (:GetSlot, :GetStacks, :FindStack, :Count, :Has, :CanAdd, :GetSpaceFor, :FirstEmptySlot, :IsEmpty), the properties Name, Owner, Kind, Size, MaxWeight, Weight, UsedSlots, FreeSlots, Access and IsOpen, the Changed, Resized and Closed signals, and these requests:
| Request | Description |
|---|---|
:MoveAsync(fromSlot, toSlot, amount?) | |
:TransferAsync(fromSlot, target, toSlot?, amount?) | To another container, such as an open chest. |
:UseAsync(slot), :EquipAsync(slot, equipmentSlot?), :SortAsync() | |
:DropAsync(slot, amount?) | Only for Droppable items. |
:SellAsync(slot, amount?) | Resolves with { Amount, Total }. |
:CloseAsync() | Closes a container that was opened to the player. |
The server checks every request again: a "Read" container cannot be changed, only the player's own containers and those opened to them can be reached, and an opened container closes when the player walks away from its Anchor. Requests are rate-limited (ClientRequestRatePerSec) and reject with the reason, such as "no space", "read only" or "too far". ClientActions = false turns them all off.
Configuration
DefaultConfiguration/Systems/InventorySystem26 settings| Setting | Default | What it does |
|---|---|---|
| ProfileStoreName | "Engine.Inventory" | ProfileStore the system owns when no store is bound with :BindProfileStore. |
| ProfilePath | "Inventory" | Path inside a profile's Data the system keeps its state under. |
| ProfileTimeout | 30 | Seconds the Async methods wait for a player's profile session before rejecting. |
| PlayerContainers | { Backpack = { Size = 30, Tools = true }, } | Containers every player gets, same shape as a :DefinePlayerContainer call. |
| DefaultContainer | "Backpack" | Player container :Give, crafting results and pickups go to when the call names none. |
| EquipmentSlots | {} | Equipment slots every player gets, same shape as a :DefineEquipmentSlot call. |
| DefaultMaxStack | 1 | Units one stack holds when an item's definition passes no MaxStack. |
| MaxContainerSize | 1000 | Most slots one container may have. |
| EquipAccessories | true | Put the Accessory of an equipped item on the player's character. |
| MaxCraftAmount | 100 | Most times one craft may make a recipe at once. |
| CraftTransactionType | "Gameplay" | Roblox transaction type a recipe's currency cost is logged under. |
| SellTransactionType | "Shop" | Roblox transaction type item sales are logged under. |
| PickupLifetime | 300 | Seconds a dropped item lies in the world before it despawns; 0 keeps it until taken. |
| PickupActionText | "Pick up" | ActionText of a pickup's ProximityPrompt. |
| PickupHoldDuration | 0 | Seconds a player holds a pickup's prompt before the item is taken. |
| PickupMaxDistance | 10 | Studs from which a pickup's ProximityPrompt can be triggered. |
| PickupSize | 1 | Studs per side of the part a pickup gets when its item has no Model or Tool handle. |
| DropDistance | 3 | Studs in front of the character a dropped item lands. |
| MaxOpenDistance | 15 | Studs a player may stand from an opened container's or station's Anchor before it closes. |
| ReplicateCatalog | true | Send item, recipe, container and equipment definitions to clients. |
| ClientActions | true | Accept move, use, drop, sort, equip, craft and sell requests from clients. |
| ClientRequestRatePerSec | 10 | Per-player rate limit on client inventory requests. |
| ClientRequestBurst | 20 | Burst that request limiter allows. |
| ClientTimeout | 10 | Seconds the client waits for the catalog or its profile before rejecting. |
| GiveEditName | "Engine.Inventory.Give" | DataService edit name offline :GiveAsync calls travel under. |
| TakeEditName | "Engine.Inventory.Take" | DataService edit name offline :TakeAsync calls travel under. |
Types
export type Item
export type ItemOptions
export type ItemDescription
export type Stack = { Id: string, Item: string, Amount: number, Data: { [string]: any }?, Slot: number }
export type Result = { Ok: boolean, Amount: number, Reason: string? }
export type Container
export type ContainerOptions
export type AddOptions = { Data: { [string]: any }?, Slot: number?, Partial: boolean? }
export type RemoveOptions = { Partial: boolean? }
export type OpenOptions = { Access: "Read" | "ReadWrite" | nil, Anchor: (BasePart | Model)?, MaxDistance: number? }
export type Stored
export type Comparator = (a: Item, b: Item) -> boolean
export type PlayerContainerOptions
export type PlayerContainerDefinition
export type GiveOptions = { Data: { [string]: any }?, Container: string?, Partial: boolean? }
export type TakeOptions = { Container: string?, Partial: boolean? }
export type AsyncOptions
export type RemoteResult
export type Equipment
export type EquippedStack
export type EquipmentSlotOptions
export type EquipmentSlotDefinition
export type Recipe
export type RecipeOptions
export type CraftOptions = { Amount: number?, Station: string? }
export type Pickup
export type PickupOptions
export type SellResult = { Ok: boolean, Amount: number, Total: number, Reason: string? }
export type ShopItemOptions
export type ShopOptions
export type Catalog