Class GGInventory extends Node
Provides item storage capabilities to characters and objects.
The inventory component acts as a repository for items and provides methods to manage them. It is highly configurable and extensible through various strategies that modify its behavior.
The inventory offers methods for integration with your game logic, and methods for integration with the user interface used by players.
Managing Items
Basic operations for item management include
Inventory Limits
The
Whenever inventory contents change, the
Using Items
The
Dropping Items
Items can be dropped from the inventory either through game logic via the
Permissions
Permissions are handled in two parts: Object-level access is handled by the [GGEntityAccessManager], while inventory-level permissions are handled by the [GGInventoryAccessPolicy].
First, the inventory integrates with the [GGEntityAccessManager] for permissions handling. The access manager provides a
Second, what an actor can do with the inventory is determined by the inventory's
For custom behavior, extend the [GGInventoryAccessPolicy] and override any methods with your game-specific logic.
Saving/Restoring
Inventory configuration and contents can be saved via
Multiplayer
Players (clients) act on behalf of their character. Any character or object the player interacts with must have a [GGEntityAccessManager] component from which a
In this example, the player drops the item in the first inventory slot on behalf of their character: [codeblock] var actor: CharacterBody2D = $PlayerCharacter var entity_access_manager: GGEntityAccessManager = $PlayerCharacter/GGEntityAccessManager var inventory: GGInventory = $PlayerCharacter/GGInventory var first_item_id: Array[int] = [0] var context_id: int = await entity_access_manager.open_context(actor) inventory.request_drop_items(first_item_id, context_id) entity_access_manager.close_context(context_id) [/codeblock] In most real-world scenarios, the
The host's inventory component listens to the
Since all multiplayer inventory interaction are server-authoritative and request/response-based, multiple clients can interact with the same inventory concurrently.
The inventory offers methods for integration with your game logic, and methods for integration with the user interface used by players.
Managing Items
Basic operations for item management include
add_item()
, get_item()
, has_item()
, and remove_item()
. Items are stored in the contents
property. To organize items, use sort_items()
and stack_items()
. Their behavior is determined by the sorting_strategies
and stacking_strategy
, respectively. Inventory Limits
The
max_items
property determines the item capacity of the inventory. Use item_count()
to determine how many items the inventory contains. The max_weight
property determines the weight capacity of the inventory. item_weight()
returns the combined weight of all items in the inventory. Whenever inventory contents change, the
items_changed
signal is emitted, which the inventory user interface will react to. Using Items
The
use()
method is a façade for using items and is typically invoked through the inventory's user interface. Its behavior is determined by the use_strategy
. It defaults to calling request_use_item()
, which abstracts multiplayer client handling and emits the item_use_requested
signal on the server, which your game logic is expected to react to. Dropping Items
Items can be dropped from the inventory either through game logic via the
drop_items()
method, through player action via the request_drop_items()
method, or because the max_size
was reduced and the inventory can no longer hold as many items. The inventory will emit the items_dropped
signal, which your game logic is expected to handle. Permissions
Permissions are handled in two parts: Object-level access is handled by the [GGEntityAccessManager], while inventory-level permissions are handled by the [GGInventoryAccessPolicy].
First, the inventory integrates with the [GGEntityAccessManager] for permissions handling. The access manager provides a
context_id
which represents an actor for a particular client. Inventory methods such as request_sort_items()
, request_transfer_item_to_inventory()
, request_use_item()
, and other methods following the request_*()
format expect the context_id
as one of the parameters. This allows the host to determine which character the client is acting on behalf of. Second, what an actor can do with the inventory is determined by the inventory's
access_policy
. The [GGInventoryAccessPolicy], despite having a similar name, is technically unrelated to the [GGEntityAccessPolicy] class. The inventory access policy defines what kind of actions an actor can take. The inventory provides façade methods to determine what permissions an actor has: For example, can_write()
determines whether an actor can modify inventory contents at all, or if they have read-only access. In addition, the can_add_item()
, can_remove_item()
, can_use_item()
, and can_drop_item()
determine what an actor can do with a specific item. These permissions are relied on by other behavior such as the transfer_strategy
. For custom behavior, extend the [GGInventoryAccessPolicy] and override any methods with your game-specific logic.
Saving/Restoring
Inventory configuration and contents can be saved via
serialize()
. Use deserialize()
to restore the inventory state. The serialization behavior can be modified via the serialization_strategy
property. Multiplayer
Players (clients) act on behalf of their character. Any character or object the player interacts with must have a [GGEntityAccessManager] component from which a
context_id
has to be obtained via GGEntityAccessManager.open_context()
. In this example, the player drops the item in the first inventory slot on behalf of their character: [codeblock] var actor: CharacterBody2D = $PlayerCharacter var entity_access_manager: GGEntityAccessManager = $PlayerCharacter/GGEntityAccessManager var inventory: GGInventory = $PlayerCharacter/GGInventory var first_item_id: Array[int] = [0] var context_id: int = await entity_access_manager.open_context(actor) inventory.request_drop_items(first_item_id, context_id) entity_access_manager.close_context(context_id) [/codeblock] In most real-world scenarios, the
context_id
is kept open to avoid having to re-open it for every action the player takes. For example, when a user interface shows inventory contents, it'll first open a context_id
, which will only be closed once the user interface is closed. The host's inventory component listens to the
GGEntityAccessManager.context_opened
and GGEntityAccessManager.context_closed
signals. When a context is opened, the inventory contents are sent to the client. While a context is open, any item changes are synchronized with the respective client. Since all multiplayer inventory interaction are server-authoritative and request/response-based, multiple clients can interact with the same inventory concurrently.
# Signals
## Emitted when the inventory's resized (for example, by adjusting the [member max_slots] property).signal inventory_resized(slots: int) ## Emitted when items have changed.signal items_changed(slot_ids: Array[int]) ## Emitted when an item is added. This signal is intended for UI notifications. The [param item] refers to the item in the inventory, which is not necessarily the same instance of the item that was passed to [method add_item] because the item may have been stacked. [br][br]The [param quantity] argument indicates how many items were added, while [code]item.quantity[/code] indicates the total quantity in the item stack. [br][br]Adding a single item with a quantity greater than 1 may result multiple item_added signals as the item gets split up across multiple slots.signal item_added(item: GGItemDataquantity: intslot_id: int) ## Emitted when an item is removed from the inventory. This signal is intended for UI notifications. To detect when an item was dropped into the world, use [signal items_dropped] instead. The [param item] should not be modified.signal item_removed(item: GGItemDataquantity: intslot_id: int) ## Emitted when items are dropped from the inventory. Either through player action, or because the [member max_size] was reduced and the inventory can no longer hold as many items.signal items_dropped(items: Array[GGItemData]actor: Node) ## Emitted when the player requests using an item.signal item_use_requested(item: GGItemDataactor: Node) ## Emitted when an item has expired and was removed from the inventory. Note: Requires the [GGInventoryExpirationsExtension].signal item_expired(item: GGItemData) # Members
## The collection of items that the inventory manages.var contents: GGItemCollection = new()## Allows gaps in the inventory.[br][br]When disabled, the [member contents] aren't allow to have any gaps. The [member GGItemCollection.items] Array is not allowed to have any [code]null[/code] values.[br][br]When enabled, the [member contents] are are padded with [code]null[/code] values. The size of the [member GGItemCollection.items] Array will match the [member max_slots] property.var allow_gaps: bool = true## Maximum number of items this inventory can hold. Adjusting the value automatically resizes the [member contents]' [member GGItemCollection.items] array. If the resized inventory is too small, it will emit [signal items_dropped] with the items that were removed from the inventory.var max_slots: int = 0var weight_limit: bool = false## The maximum weight capacity of this inventory. [br][br]Use the [member item_filter_strategy] with the [GGInventoryItemFilterStrategyWeight] class to enforce the weight limit.var max_weight: float = 0.0## Modifies item expiration for this inventory. Smaller values expire faster, larger values expire slower (e.g. 2.0 means expiration is doubled). [br][br]Note: Requires the [GGInventoryExpirationsExtension].var expiration_multiplier: float = 1.0## The Use Strategy determines what happens when [method use] is called. The default behavior is for the inventory to call [method request_use_item]. If a strategy is specified, it can be used to "intercept" using an item on the client.var use_strategy: GGInventoryUseStrategy## Allows filtering items when they're added to the inventory. This can be used to ensure only specific items can be added, for example for crafting.var item_filter_strategy: GGInventoryItemFilterStrategy = new()## Specifies the behavior of [method GGItemCollection.sort_items].var sorting_strategies: GGInventorySortingStrategy[] = [...]## Responsible for the logic when items are transferred to another inventory.var transfer_strategy: GGInventoryTransferStrategy## Specifies the strategy for stacking items.var stacking_strategy: GGInventoryStackingStrategy## The serialization strategy determines how items are serialized and deserialized for the purpose of sending it to clientsvar serialization_strategy: GGInventorySerializationStrategy = new()## Responsible for determining whether an actor has access to this inventory. This is required for multiplayer.var entity_access_manager: GGEntityAccessManager## The access policy determines what an actor interacting with the inventory is allowed to do.var access_policy: GGInventoryAccessPolicy = new()# Methods
## Counts how many items the inventory contains.func item_count() -> int## The combined weight of all items in the inventory.func item_weight() -> float## Returns the item of a slotfunc get_item(slot_id: int) -> GGItemData## Sets a slot to an item (or null)func set_item(slot_id: intitem: GGItemData) -> GGItemData## An optimized method to swap items in [param source_slot_id] and [param target_slot_id]. Performs an atomic swap, that results in a single [signal items_changed] signal. Other components can use it to detect that an item was moved, but that it is still in the inventory.func swap_items(source_slot_id: inttarget_slot_id: int) -> void## Adds [param item] to the inventory. If [param quantity] is specified, only adds up to that quantity. Note: [method add_item] does not make a copy of the item, so if you're adding half of an item, it's the callers responsibility to first duplicate the item via [method GGItemData.copy].[br][br]Returns the quantity that was added.func add_item(item: GGItemDataquantity: int) -> int## Removes the item from the [param slot_id] and returns it.func remove_item(slot_id: int) -> GGItemData## Checks if the inventory has the [param item]. Will only return true if the item is the exact same instance.func has_item(item: GGItemData) -> bool## Check if the inventory has the [param items] in sufficient quantities. The check will be compare the [param items]' types and quantities.func has_items(items: Array[GGItemData]) -> bool## Returns the total quantity of the item [param type].func get_quantity_by_item_type(type: GGItemType) -> int## Consumes an item in [param slot_id]. If [param quantity] is specified, it'll consume and subtract the specific amount.func consume_item(slot_id: intquantity: int) -> GGItemData## Removes and returns the specified quantity of items from the inventory.[br][br]The returned array will contain the items in the format they were stored in the inventory; so if it requested a stack of 10 wood, and the inventory contained three items of 5 wood, 3 wood, and 8 wood, it'll return an array of 5, 3, and 2 wood.[br][br]If [param allow_partial] was set to [code]true[/code], the caller has to inspect what was returned. Partials means that nothing may have been returned at all. This makes it easy to consume ammo (e.g. request 30 bullets, but if only 10 were available, allow for a partial reload).func consume_items(needed_items: Array[GGItemData]allow_partial: bool) -> Array[GGItemData]## Consumes a specific [param quantity] of an item [param type]. If the inventory holds less than [param min_quantity], an empty array is returned. The caller should inspect the return value for the exact quantities. The returned quantities may be split across multiple items.func consume_item_type(type: GGItemTypequantity: intmin_quantity: int) -> Array[GGItemData]## Returns the position of an open slot that can hold [param item], or [code]-1[/code] if all slots are taken.func find_free_slot_for_item(item: GGItemDataoffset: int) -> int## Whether the [param item] is accepted by this inventory as determined by the [member item_filter_strategy].func accepts_item(item: GGItemData) -> bool## Whether the [param item] is accepted by this inventory in [param slot_id] as determined by the [member item_filter_strategy].func slot_accepts_item(item: GGItemDataslot_id: int) -> bool## Serializes the inventory and returns its state. The implementation is determined by the [member serialization_strategy] and typically includes configuration and contents.func serialize() -> Dictionary## Restores the inventory from the [param state]. The implementation is determined by the [member serialization_strategy] and typically includes configuration and contents.func deserialize(state: Dictionary) -> void## Retrieve the list of currently subscribed clients from the [member entity_access_manager]. These are the "remote_sender_ids". When called on the client, it will only return the client's own peer_id.func get_subscribers() -> Array[int]## Returns true if the [member entity_access_manager] has a context for the [param actor] owned by the [param remote_sender_id].func has_context(actor: Noderemote_sender_id: int) -> bool## Whether the [param actor] can modify the inventory contents. Defers to the [member access_policy].func can_write(actor: Node) -> bool## Whether the [param item] can be added by the [param actor]. Defers to the [member access_policy].func can_add_item(item: GGItemDataactor: Node) -> bool## Whether the [param item] can be removed by the [param actor]. Defers to the [member access_policy].func can_remove_item(item: GGItemDataactor: Node) -> bool## Whether the [param item] can be used by the [param actor]. Defers to the [member access_policy].func can_use_item(item: GGItemDataactor: Node) -> bool## Whether the [param item] can be dropped by the [param actor]. Defers to the [member access_policy].func can_drop_item(item: GGItemDataactor: Node) -> bool## Whether the [param source_inventory]'s [param source_item] can be stacked onto the [param target_item]. Defers to the [member stacking_strategy] for the logic.func can_stack(source_inventory: GGInventorysource_item: GGItemDatatarget_item: GGItemData) -> bool## Attempt to stack the [param source_item] onto a specific [param target_slot_id]. The [param quantity] specifies how much of the item should be stacked. The return value is the quantity that was added for the [param source_item], for which the caller is responsible that it is applied.func stack(source_inventory: GGInventorysource_item: GGItemDatatarget_item_id: intquantity: int) -> int## Stack all stackable items to optimize inventory space. Will only work if the [param actor] Node has write permissions. This method should only be called server-side.func stack_items(actor: Node) -> void## Request stacking all stackable items to optimize inventory space. The [param context_id] must have been created through the [member entity_access_manager] first. Can be called from the server or client.func request_stack_items(context_id: int) -> void## Split the item in [param slot_id] as specified by the [param amount]. Splitting is done on behalf of the [param actor], if they have write permission. This method should only be called server-side.func split_item(slot_id: intamount: intactor: Node) -> void## Split the item in [param slot_id] as specified by the [param amount]. The actor will be retrieved via the [param context_id] which must have been created through the [member entity_access_manager] first. Can be called from the server or client.func request_split_item(slot_id: intamount: intcontext_id: int) -> void## Sort all items according to the [param strategy_id], which is the Nth strategy in the [member sorting_strategies] Array. Sorting is done on behalf of the [param actor], if they have write permission. This method should only be called server-side. Clients should use the [method request_sort_items] method instead.func sort_items(strategy_id: intactor: Node) -> void## Requests sorting items according to the [param strategy_id], which is the Nth strategy in the [member sorting_strategies] Array. The actor will be retrieved via the [param context_id] which must have been created through the [member entity_access_manager] first. Can be called from the server or client.func request_sort_items(strategy_id: intcontext_id: int) -> void## Transfers the items in [param slot_ids] to the [param target_inventory]. The transfer is done on behalf of the [param actor], if they have write permission. This method should only be called server-side. You should use [method request_transfer_items_to_inventory] as it is server/client agnostic.func transfer_items_to_inventory(slot_ids: Array[int]target_inventory: GGInventoryactor: Node) -> void## Transfers the items in the [param slot_ids] to the [param target_inventory]. The transfer is done on behalf of the actor identified by the [param context_id].func request_transfer_items_to_inventory(slot_ids: Array[int]target_inventory: GGInventorycontext_id: int) -> void## Transfers the item in [param slot_id] to the [param target_inventory]. The [param quantity] parameter allows for partial transfers. The transfer is done on behalf of the [param actor], if they have write permission. This method should only be called server-side.func transfer_item_to_inventory(slot_id: inttarget_inventory: GGInventoryquantity: intactor: Node) -> void## Requests transfer of the item in [param slot_id] to the [param target_inventory]. The [param quantity] parameter allows for partial transfers. The transfer is done on behalf of the actor identified by the [param context_id] issued by the [member entity_access_manager]. This method is server/client-agnostic.func request_transfer_item_to_inventory(slot_id: inttarget_inventory: GGInventoryquantity: intcontext_id: int) -> void## Transfers the item in [param source_slot_id] to the [param target_slot_id] in the [param target_inventory]. The [param quantity] parameter allows for partial transfers. The transfer is done on behalf of the [param actor], if they have write permission. This method should only be called by game logic on the server-side. UI logic should use [method request_transfer_item_to_slot] instead.func transfer_item_to_slot(source_slot_id: inttarget_inventory: GGInventorytarget_slot_id: intquantity: intactor: Node) -> void## Requests transfer of the item in [param source_slot_id] to the [param target_slot_id] in the [param target_inventory]. The [param quantity] parameter allows for partial transfers. Can be called from the server or client.func request_transfer_item_to_slot(source_slot_id: inttarget_inventory: GGInventorytarget_slot_id: intquantity: intcontext_id: int) -> void## Use the item in [param slot_id] for [param actor]. Façade method that should be called locally. If no [member use_strategy] is defined, it defaults to calling [method request_use_item] for server-side handling.func use(slot_id: intcontext_id: int) -> void## Use the item in [param slot_id] for [param actor]. Should only be called server-side. This will only emit the [signal item_use_requested] signal. It is up to a listener to react to the item use request.func use_item(slot_id: intactor: Node) -> void## Request the use of the item in [param slot_id] for [param actor]. Can be called from the server or client. This will only emit the [signal item_use_requested] signal on the server. It is up to a listener to react to the item use request.func request_use_item(slot_id: intcontext_id: int) -> void## Drops the item in [param slot_id] from the inventory on behalf of the [param actor]. The [param quantity] allows for specific quantities.func drop_item(slot_id: intquantity: Variantactor: Node) -> voidfunc request_drop_item(slot_id: intquantity: Variantcontext_id: int) -> void## Drops the items in slots specified by [param slot_ids] on behalf of [param actor].func drop_items(slot_ids: Array[int]actor: Node) -> voidfunc request_drop_items(slot_ids: Array[int]context_id: int) -> void## Returns the slot ID of the [param item].func find_item_slot_id(item: GGItemData) -> int## Find an item of a particular [param type].func find_item_by_type(type: GGItemType) -> GGItemData## Sends the inventory contents to the client identified by its [param peer_id]. In most cases, calling this method is not needed. Instead, use the [member entity_access_manager] to open a context, which will automatically sync inventory contents as needed.func send_contents_to_client(peer_id: int) -> void