Joey Eamigh 9989fab018
addons?
2025-10-10 14:07:23 -04:00

477 lines
24 KiB
GDScript

#============================================================================
# item_registry.gd |
#============================================================================
# This file is part of: |
# INVENTORY MANAGER |
# https://github.com/Rubonnek/inventory-manager |
#============================================================================
# Copyright (c) 2024-2025 Wilson Enrique Alvarez Torres |
# |
# Permission is hereby granted, free of charge, to any person obtaining |
# a copy of this software and associated documentation files (the |
# "Software"), to deal in the Software without restriction, including |
# without limitation the rights to use, copy, modify, merge, publish, |
# distribute, sublicense, andor sell copies of the Software, and to |
# permit persons to whom the Software is furnished to do so, subject to |
# the following conditions: |
# |
# The above copyright notice and this permission notice shall be |
# included in all copies or substantial portions of the Software. |
# |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
#============================================================================
extends RefCounted
class_name ItemRegistry
## Holds a list of items and data the InventoryManager will use to handle them.
##
## Each ItemRegistry must initialized with a set of item IDs provided by the user which can then be used to track item metadata such as item name, description, stack capacity, stack count limit and their metadata.
## Emitted when an item is modified.
signal item_modified(p_item_id : int)
var _m_item_registry_dictionary : Dictionary
var _m_item_registry_entries_dictionary : Dictionary
enum _registry_key {
ITEM_ENTRIES,
METADATA,
}
enum _item_entry_key {
NAME,
DESCRIPTION,
ICON,
STACK_CAPACITY,
STACK_COUNT_LIMIT,
METADATA,
}
const DEFAULT_STACK_CAPACITY : int = 99
const DEFAULT_STACK_COUNT_LIMIT : int = 0 # a stack count of 0 means the stack count limit is infinite
## Adds an item to the registry.
func add_item(p_item_id : int, p_name : String = "", p_description : String = "", p_icon : Texture2D = null, p_stack_capacity : int = DEFAULT_STACK_CAPACITY, p_stack_count : int = DEFAULT_STACK_COUNT_LIMIT, p_metadata : Dictionary = {}) -> void:
if not p_item_id >= 0:
push_error("ItemRegistry: Unable to add item to registry. The item IDs are required to be greater or equal to 0.")
return
if p_stack_capacity <= 0:
push_error("ItemRegistry: : Attempting to add item ID %d with invalid stack capacity %d. Stack capacity must be a positive integer." % p_item_id, p_stack_capacity)
return
if p_stack_count < 0:
push_error("ItemRegistry: : Attempting to add item ID %d with invalid stack count %d. Stack count must be equal or greater than zero." % p_item_id, p_stack_count)
return
if _m_item_registry_entries_dictionary.has(p_item_id):
push_warning("ItemRegistry: Item ID %d is already registered:\n\n%s\n\nRe-registering will overwrite previous data." % [p_item_id, str(prettify(p_item_id))])
var item_registry_entry_dictionary : Dictionary = _m_item_registry_entries_dictionary.get(p_item_id, {})
_m_item_registry_entries_dictionary[p_item_id] = item_registry_entry_dictionary
if not p_name.is_empty():
item_registry_entry_dictionary[_item_entry_key.NAME] = p_name
if not p_description.is_empty():
item_registry_entry_dictionary[_item_entry_key.DESCRIPTION] = p_description
if is_instance_valid(p_icon):
item_registry_entry_dictionary[_item_entry_key.ICON] = p_icon
if p_stack_capacity != DEFAULT_STACK_CAPACITY:
item_registry_entry_dictionary[_item_entry_key.STACK_CAPACITY] = p_stack_capacity
if p_stack_count != DEFAULT_STACK_COUNT_LIMIT:
item_registry_entry_dictionary[_item_entry_key.STACK_COUNT_LIMIT] = p_stack_count
if not p_metadata.is_empty():
item_registry_entry_dictionary[_item_entry_key.METADATA] = p_metadata
item_modified.emit(p_item_id)
## Removes the item and all the associated data from the registry.
func remove_item(p_item_id : int) -> void:
var success : bool = _m_item_registry_entries_dictionary.erase(p_item_id)
if success:
item_modified.emit(p_item_id)
## Returns true if the item is registered. Returns false otherwise.
func has_item(p_item_id : int) -> bool:
return _m_item_registry_entries_dictionary.has(p_item_id)
## Sets the item name.
func set_name(p_item_id : int, p_name : String) -> void:
var item_registry_entry_dictionary : Dictionary = _m_item_registry_entries_dictionary.get(p_item_id, {})
if not _m_item_registry_entries_dictionary.has(p_item_id):
_m_item_registry_entries_dictionary[p_item_id] = item_registry_entry_dictionary
if p_name.is_empty():
var _success : bool = item_registry_entry_dictionary.erase(_item_entry_key.NAME)
else:
item_registry_entry_dictionary[_item_entry_key.NAME] = p_name
item_modified.emit(p_item_id)
## Returns the item name.
func get_name(p_item_id : int) -> String:
var item_registry_entry_dictionary : Dictionary = _m_item_registry_entries_dictionary.get(p_item_id, {})
return item_registry_entry_dictionary.get(_item_entry_key.NAME, "")
## Returns true if the item has a registered name.
func has_name(p_item_id : int) -> bool:
var item_registry_entry_dictionary : Dictionary = _m_item_registry_entries_dictionary.get(p_item_id, {})
return item_registry_entry_dictionary.has(_item_entry_key.NAME)
## Sets the item description.
func set_description(p_item_id : int, p_description : String) -> void:
var item_registry_entry_dictionary : Dictionary = _m_item_registry_entries_dictionary.get(p_item_id, {})
if not _m_item_registry_entries_dictionary.has(p_item_id):
_m_item_registry_entries_dictionary[p_item_id] = item_registry_entry_dictionary
if p_description.is_empty():
var _success : bool = item_registry_entry_dictionary.erase(_item_entry_key.DESCRIPTION)
else:
item_registry_entry_dictionary[_item_entry_key.DESCRIPTION] = p_description
item_modified.emit(p_item_id)
## Returns the item description.
func get_description(p_item_id : int) -> String:
var item_registry_entry_dictionary : Dictionary = _m_item_registry_entries_dictionary.get(p_item_id, {})
return item_registry_entry_dictionary.get(_item_entry_key.DESCRIPTION, "")
## Returns true if the item has a description.
func has_description(p_item_id : int) -> bool:
var item_registry_entry_dictionary : Dictionary = _m_item_registry_entries_dictionary.get(p_item_id, {})
return item_registry_entry_dictionary.has(_item_entry_key.DESCRIPTION)
## Sets the item icon.
func set_icon(p_item_id : int, p_texture : Texture2D) -> void:
var item_registry_entry_dictionary : Dictionary = _m_item_registry_entries_dictionary.get(p_item_id, {})
if not _m_item_registry_entries_dictionary.has(p_item_id):
_m_item_registry_entries_dictionary[p_item_id] = item_registry_entry_dictionary
if not is_instance_valid(p_texture):
var _success : bool = item_registry_entry_dictionary.erase(_item_entry_key.ICON)
else:
item_registry_entry_dictionary[_item_entry_key.ICON] = p_texture
item_modified.emit(p_item_id)
## Returns the item icon. Returns [code]null[/code] if there's none.
func get_icon(p_item_id : int) -> Texture2D:
var item_registry_entry_dictionary : Dictionary = _m_item_registry_entries_dictionary.get(p_item_id, {})
return item_registry_entry_dictionary.get(_item_entry_key.ICON, null)
## Returns true if the item has an icon.
func has_icon(p_item_id : int) -> bool:
var item_registry_entry_dictionary : Dictionary = _m_item_registry_entries_dictionary.get(p_item_id, {})
return item_registry_entry_dictionary.has(_item_entry_key.ICON)
## Sets the maximum number of items per stack for the item.
func set_stack_capacity(p_item_id : int, p_stack_capacity : int) -> void:
var item_registry_entry_dictionary : Dictionary = _m_item_registry_entries_dictionary.get(p_item_id, {})
if not _m_item_registry_entries_dictionary.has(p_item_id):
_m_item_registry_entries_dictionary[p_item_id] = item_registry_entry_dictionary
if p_stack_capacity <= 0:
push_warning("ItemRegistry: Attempted to set a stack capacity with a negative number which should be positive instead. Ignoring.")
return
elif p_stack_capacity == DEFAULT_STACK_CAPACITY:
var _success : bool = item_registry_entry_dictionary.erase(_item_entry_key.STACK_CAPACITY)
else:
item_registry_entry_dictionary[_item_entry_key.STACK_CAPACITY] = p_stack_capacity
item_modified.emit(p_item_id)
## Returns the stack capacity for the item.
func get_stack_capacity(p_item_id : int) -> int:
var item_registry_entry_dictionary : Dictionary = _m_item_registry_entries_dictionary.get(p_item_id, {})
return item_registry_entry_dictionary.get(_item_entry_key.STACK_CAPACITY, DEFAULT_STACK_CAPACITY)
## Returns true if the item has a stack_capacity different than 99, the default value.
func has_stack_capacity(p_item_id : int) -> bool:
var item_registry_entry_dictionary : Dictionary = _m_item_registry_entries_dictionary.get(p_item_id, {})
return item_registry_entry_dictionary.has(_item_entry_key.STACK_CAPACITY)
## Sets the maximum number of stacks for the item.
func set_stack_count_limit(p_item_id : int, p_stack_count : int = 0) -> void:
var item_registry_entry_dictionary : Dictionary = _m_item_registry_entries_dictionary.get(p_item_id, {})
if not _m_item_registry_entries_dictionary.has(p_item_id):
_m_item_registry_entries_dictionary[p_item_id] = item_registry_entry_dictionary
if p_stack_count < 0:
push_warning("ItemRegistry: Attempted to set an invalid stack count. The stack count must be equal or greater than zero. Ignoring.")
return
elif p_stack_count == DEFAULT_STACK_COUNT_LIMIT:
var _success : bool = item_registry_entry_dictionary.erase(_item_entry_key.STACK_COUNT_LIMIT)
else:
item_registry_entry_dictionary[_item_entry_key.STACK_COUNT_LIMIT] = p_stack_count
item_modified.emit(p_item_id)
## Returns the maximum number of items per stack for the item.
func get_stack_count(p_item_id : int) -> int:
var item_registry_entry_dictionary : Dictionary = _m_item_registry_entries_dictionary.get(p_item_id, {})
return item_registry_entry_dictionary.get(_item_entry_key.STACK_COUNT_LIMIT, DEFAULT_STACK_COUNT_LIMIT)
## Returns true if the item has a stack count set different than 0, the default value..
func has_stack_count(p_item_id : int) -> bool:
var item_registry_entry_dictionary : Dictionary = _m_item_registry_entries_dictionary.get(p_item_id, {})
if not _m_item_registry_entries_dictionary.has(p_item_id):
_m_item_registry_entries_dictionary[p_item_id] = item_registry_entry_dictionary
return item_registry_entry_dictionary.has(_item_entry_key.STACK_COUNT_LIMIT)
## Returns true if the stack count for the item is set to greater than 0. Returns false otherwise.
func is_stack_count_limited(p_item_id : int) -> bool:
return get_stack_count(p_item_id) > 0
## Returns an array with the registered items.
func keys() -> PackedInt64Array:
var array : PackedInt64Array = _m_item_registry_entries_dictionary.keys()
return array
## Attaches the specified metadata to the related item.
func set_item_metadata(p_item_id : int, p_key : Variant, p_value : Variant) -> void:
if not _m_item_registry_entries_dictionary.has(p_item_id):
push_error("ItemRegistry: Attempting to set item metadata on unregistered item with id %d. Ignoring call." % p_item_id)
return
var item_registry_entry_dictionary : Dictionary = _m_item_registry_entries_dictionary[p_item_id]
var item_metadata : Dictionary = item_registry_entry_dictionary.get(_item_entry_key.METADATA, {})
if item_metadata.is_empty():
item_registry_entry_dictionary[_item_entry_key.METADATA] = item_metadata
item_metadata[p_key] = p_value
item_modified.emit(p_item_id)
## Sets the item metadata data.
func set_item_metadata_data(p_item_id : int, p_metadata : Dictionary) -> void:
if not _m_item_registry_entries_dictionary.has(p_item_id):
push_error("ItemRegistry: Attempting to set item metadata on unregistered item with id %d. Ignoring call." % p_item_id)
return
var item_registry_entry_dictionary : Dictionary = _m_item_registry_entries_dictionary[p_item_id]
item_registry_entry_dictionary[_item_entry_key.METADATA] = p_metadata
item_modified.emit(p_item_id)
## Returns the specified metadata for the item.
func get_item_metadata(p_item_id : int, p_key : Variant, p_default_value : Variant = null) -> Variant:
var item_registry_entry_dictionary : Dictionary = _m_item_registry_entries_dictionary.get(p_item_id, {})
var item_metadata : Dictionary = item_registry_entry_dictionary.get(_item_entry_key.METADATA, {})
return item_metadata.get(p_key, p_default_value)
## Returns a reference to the internal metadata dictionary.[br]
## [br]
## [color=yellow]Warning:[/color] Use with caution. Modifying this dictionary will directly modify the installed metadata for the item.
func get_item_metadata_data(p_item_id : int) -> Dictionary:
var item_registry_entry_dictionary : Dictionary = _m_item_registry_entries_dictionary.get(p_item_id, {})
var item_metadata : Dictionary = item_registry_entry_dictionary.get(_item_entry_key.METADATA, {})
if not _m_item_registry_dictionary.has(_item_entry_key.METADATA):
# There's a chance the user wants to modify it externally and have it update the ItemRegistry automatically -- make sure we store a reference of that metadata:
_m_item_registry_dictionary[_item_entry_key.METADATA] = item_metadata
return item_metadata
## Returns true if the item metadata has the specified key:
func has_item_metadata_key(p_item_id : int, p_key : Variant) -> bool:
if not _m_item_registry_entries_dictionary.has(p_item_id):
push_error("ItemRegistry: Attempting to get item metadata on unregistered item with id %d. Returning false." % p_item_id)
return false
var item_registry_entry_dictionary : Dictionary = _m_item_registry_entries_dictionary[p_item_id]
var item_metadata : Dictionary = item_registry_entry_dictionary.get(_item_entry_key.METADATA, {})
return item_metadata.has(p_key)
## Returns true if the item has some metadata.
func has_item_metadata(p_item_id : int) -> bool:
var item_registry_entry_dictionary : Dictionary = _m_item_registry_entries_dictionary.get(p_item_id, {})
var item_metadata : Dictionary = item_registry_entry_dictionary.get(_item_entry_key.METADATA, {})
return not item_metadata.is_empty()
## Sets the specified metadata for the item registry.
func set_registry_metadata(p_key : Variant, p_value : Variant) -> void:
var metadata : Dictionary = _m_item_registry_dictionary.get(_registry_key.METADATA, {})
metadata[p_key] = p_value
if not _m_item_registry_dictionary.has(_registry_key.METADATA):
_m_item_registry_dictionary[_registry_key.METADATA] = metadata
__sync_registry_metadata_with_debugger()
## Returns the specified metadata from the item registry.
func get_registry_metadata(p_key : Variant, p_default_value : Variant = null) -> Variant:
var metadata : Dictionary = _m_item_registry_dictionary.get(_registry_key.METADATA, {})
return metadata.get(p_key, p_default_value)
## Returns a reference to the internal metadata dictionary.
func get_registry_metadata_data() -> Dictionary:
var metadata : Dictionary = _m_item_registry_dictionary.get(_registry_key.METADATA, {})
if not _m_item_registry_dictionary.has(_registry_key.METADATA):
# There's a chance the user wants to modify it externally and have it update the item registry automatically -- make sure we store a reference of that metadata:
_m_item_registry_dictionary[_registry_key.METADATA] = metadata
return metadata
## Returns true if the item registry has some metadata.
func has_registry_metadata() -> bool:
var metadata : Dictionary = _m_item_registry_dictionary.get(_registry_key.METADATA, {})
return not metadata.is_empty()
## Appends all the items of another item registry.
func append(p_item_registry : ItemRegistry) -> void:
var registry_data : Dictionary = p_item_registry.get_data()
var entries_data : Dictionary = registry_data.get(_registry_key.ITEM_ENTRIES, {})
for item_id : int in entries_data:
var item_registry_entry_dictionary : Dictionary = entries_data[item_id]
var name : String = item_registry_entry_dictionary.get(_item_entry_key.NAME, "")
var description : String = item_registry_entry_dictionary.get(_item_entry_key.DESCRIPTION, "")
var icon : Texture2D = item_registry_entry_dictionary.get(_item_entry_key.ICON, null)
var stack_capacity : int = item_registry_entry_dictionary.get(_item_entry_key.STACK_CAPACITY, DEFAULT_STACK_CAPACITY)
var stack_count : int = item_registry_entry_dictionary.get(_item_entry_key.STACK_COUNT_LIMIT, DEFAULT_STACK_COUNT_LIMIT)
var metadata : Dictionary = item_registry_entry_dictionary.get(_item_entry_key.METADATA, {})
add_item(item_id, name, description, icon, stack_capacity, stack_count, metadata)
## Returns a reference to the internal dictionary where all the item registry data is stored.[br]
## [br]
## [color=yellow]Warning:[/color] Use with caution. Modifying this dictionary will directly modify the item registry data.
func get_data() -> Dictionary:
return _m_item_registry_dictionary
## Overwrites the item registry data.
func set_data(p_data : Dictionary) -> void:
# Track old item IDs:
var item_ids_changed : Dictionary = _m_item_registry_entries_dictionary
# Update data
_m_item_registry_dictionary = p_data
_m_item_registry_entries_dictionary = _m_item_registry_dictionary[_registry_key.ITEM_ENTRIES]
# Track new item IDs:
for item_id : int in _m_item_registry_entries_dictionary:
item_ids_changed[item_id] = true
# Send a signal about all the ids that changed:
for item_id : int in item_ids_changed:
item_modified.emit(item_id)
if EngineDebugger.is_active():
# NOTE: Do not use any of API calls directly here when setting values to avoid sending unnecessary data to the debugger about the duplicated item_registry entry being sent to display
# Process each entry data
var duplicated_registry_data : Dictionary = _m_item_registry_dictionary.duplicate(true)
for item_id : int in duplicated_registry_data:
# The debugger viewer requires certain objects to be stringified before sending -- duplicate the entry data to avoid overriding the runtime data:
var duplicated_item_registry_entry_dictionary : Dictionary = _m_item_registry_entries_dictionary[item_id]
duplicated_item_registry_entry_dictionary = duplicated_item_registry_entry_dictionary.duplicate(true)
# Convert the image into an object that we can send into the debugger
if duplicated_item_registry_entry_dictionary.has(_item_entry_key.ICON):
var image : Image = duplicated_item_registry_entry_dictionary[_item_entry_key.ICON]
duplicated_item_registry_entry_dictionary[_item_entry_key.ICON] = var_to_bytes_with_objects(image)
# Process the ItemRegistry metadata:
var metadata : Dictionary = _m_item_registry_dictionary.get(_registry_key.METADATA, {})
if not metadata.is_empty():
var stringified_metadata : Dictionary = {}
for key : Variant in metadata:
var value : Variant = metadata[key]
if key is Callable or key is Object:
stringified_metadata[str(key)] = str(value)
else:
stringified_metadata[key] = str(value)
# Replaced the source metadata with the stringified version that can be displayed remotely:
duplicated_registry_data[_registry_key.METADATA] = stringified_metadata
# Send the data
EngineDebugger.send_message("inventory_manager:item_registry_set_data", [get_instance_id(), duplicated_registry_data])
# Only used by the debugger to inject the data it receives
func __inject(p_item_id : int, p_item_registry_entry_dictionary : Dictionary) -> void:
if p_item_registry_entry_dictionary.is_empty():
var _success : bool = _m_item_registry_entries_dictionary.erase(p_item_id)
else:
_m_item_registry_entries_dictionary[p_item_id] = p_item_registry_entry_dictionary
func __synchronize_item_data_with_the_debugger(p_item_id : int) -> void:
if EngineDebugger.is_active():
# NOTE: Do not use the item_registry API directly here when setting values to avoid sending unnecessary data to the debugger about the duplicated item_registry entry being sent to display
# The debugger viewer requires certain objects to be stringified before sending -- duplicate the entry data to avoid overriding the runtime data:
var item_registry_entry_dictionary : Dictionary = _m_item_registry_entries_dictionary.get(p_item_id, {})
var duplicated_item_registry_entry_dictionary : Dictionary = item_registry_entry_dictionary.duplicate(true)
# Stringify item metadata
var item_metadata : Dictionary = duplicated_item_registry_entry_dictionary.get(_item_entry_key.METADATA, {})
if not item_metadata.is_empty():
var stringified_item_metadata : Dictionary = {}
for key : Variant in item_metadata:
var value : Variant = item_metadata[key]
if key is Callable or key is Object:
stringified_item_metadata[str(key)] = str(value)
else:
stringified_item_metadata[key] = str(value)
duplicated_item_registry_entry_dictionary[_item_entry_key.METADATA] = stringified_item_metadata
# Convert the image into an object that we can send into the debugger
if duplicated_item_registry_entry_dictionary.has(_item_entry_key.ICON):
var texture : Texture2D = duplicated_item_registry_entry_dictionary[_item_entry_key.ICON]
var image : Image = texture.get_image()
duplicated_item_registry_entry_dictionary[_item_entry_key.ICON] = var_to_bytes_with_objects(image)
var item_registry_manager_id : int = get_instance_id()
EngineDebugger.send_message("inventory_manager:item_registry_sync_item_registry_entry", [item_registry_manager_id, p_item_id, duplicated_item_registry_entry_dictionary])
## Returns a human-readable dictionary for the item.
func prettify(p_item_id : int) -> Dictionary:
var item_data : Dictionary = _m_item_registry_entries_dictionary.get(p_item_id, {})
var prettified_item_data : Dictionary = item_data.duplicate(true)
for enum_key : String in _item_entry_key:
var enum_id : int = _item_entry_key[enum_key]
if enum_id in prettified_item_data:
var data : Variant = prettified_item_data[enum_id]
var _success : bool = prettified_item_data.erase(enum_id)
prettified_item_data[enum_key.to_snake_case()] = data
return prettified_item_data
func __sync_registry_metadata_with_debugger() -> void:
if EngineDebugger.is_active():
# Stringify registry metadata
var registry_metadata : Dictionary = _m_item_registry_dictionary.get(_registry_key.METADATA, {})
registry_metadata = registry_metadata.duplicate(true)
var stringified_metadata : Dictionary = {}
for key : Variant in registry_metadata:
var value : Variant = registry_metadata[key]
if key is Callable or key is Object:
stringified_metadata[str(key)] = str(value)
else:
stringified_metadata[key] = str(value)
# Send the stringified metadata
EngineDebugger.send_message("inventory_manager:item_registry_sync_metadata", [get_instance_id(), stringified_metadata])
func _init() -> void:
_m_item_registry_dictionary[_registry_key.ITEM_ENTRIES] = _m_item_registry_entries_dictionary
if EngineDebugger.is_active():
# Register with the debugger
EngineDebugger.send_message("inventory_manager:register_item_registry", [get_instance_id()])
var _success : int = item_modified.connect(__synchronize_item_data_with_the_debugger)