GodotVR/components/persistent/persistent_pocket.gd
2025-10-08 11:10:13 +02:00

180 lines
4.6 KiB
GDScript

@tool
class_name PersistentPocket
extends XRToolsSnapZone
## Persistent Pocket Node
##
## The [PersistentPocket] type holds persistent items managed by the
## persistence system. The [PersistentPocket] type extends from
## [XRToolsSnapZone] to allow [PersistentItem] objects to be snapped or
## removed by the player.
## Enumeration to control pocket behavior when the parent item is held
enum HeldBehavior {
IGNORE, ## Ignore picked_up/dropped changes
ENABLE, ## Enable when picked up
DISABLE ## Disable when picked up
}
# Group for world-data properties
@export_group("World Data")
## This property specifies the unique ID of this pocket
@export var pocket_id : String
# Group for options
@export_group("Options")
## Pocket behavior when held
@export var held_behavior := HeldBehavior.ENABLE : set = _set_held_behavior
# Parent pickable body
var _parent_body : XRToolsPickable
# Add support for is_xr_class
func is_xr_class(p_name : String) -> bool:
return p_name == "PersistentPocket" or super(p_name)
# Called when the node enters the scene tree for the first time.
func _ready():
super()
# Skip initialization if in editor
if Engine.is_editor_hint():
return
# Search for an ancestor XRToolsPickable
_parent_body = XRTools.find_xr_ancestor(self, "*", "XRToolsPickable")
if _parent_body:
_parent_body.picked_up.connect(_on_picked_up)
_parent_body.dropped.connect(_on_dropped)
# Update the held behavior
_update_held_behavior()
# Get configuration warnings
func _get_configuration_warnings() -> PackedStringArray:
var warnings := PackedStringArray()
# Verify pocket ID is set
if not pocket_id:
warnings.append("Pocket ID not zet")
# Verify pocket is in persistent group
if not is_in_group("persistent"):
warnings.append("Pocket not in 'persistent' group")
# Return warnings
return warnings
# Handle notifications
func _notification(what : int) -> void:
# Ignore notifications on freeing objects
if is_queued_for_deletion():
return
match what:
Persistent.NOTIFICATION_LOAD_STATE:
_load_state()
Persistent.NOTIFICATION_SAVE_STATE:
_save_state()
Persistent.NOTIFICATION_DESTROY:
_destroy()
# This method loads the pocket state from [PersistentWorld]. If the
# [PersistentWorld] indicates this pocket holds an item then the item is
# created and picked up by the pocket.
func _load_state() -> void:
# Queue populating the pocket as new nodes cannot be created inside a
# notification handler.
_populate_pocket.call_deferred()
# This method saves the state of the pocket to [PersistentWorld].
func _save_state() -> void:
# Handle pocket not holding on to PersistentItem
if not picked_up_object is PersistentItem:
# Save that the pocket is empty
PersistentWorld.instance.clear_value(pocket_id)
return
# Get the item_id of the PersistentItem in the pocket
var item_id : String = picked_up_object.item_id
# Save that the pocket holds the item
PersistentWorld.instance.set_value(pocket_id, item_id)
# This method destroys the pocket and any item inside it.
func _destroy() -> void:
# Propagate destruction for anything we hold
if is_instance_valid(picked_up_object):
print(self, " propagating destroy to ", picked_up_object.name)
picked_up_object.propagate_notification(Persistent.NOTIFICATION_DESTROY)
picked_up_object.queue_free()
# Populate the contents of a pocket
func _populate_pocket() -> void:
# Get the ID of the item in the pocket
var item_id = PersistentWorld.instance.get_value(pocket_id)
if not item_id is String:
return
# Construct the item for the pocket
var zone = PersistentZone.find_instance(self)
var item := zone.create_item_instance(item_id)
if not item:
return
# Put the item in the pocket
item.global_transform = global_transform
pick_up_object.call_deferred(item)
# Called when the parent pickable body is picked up
func _on_picked_up(_pickable) -> void:
_update_held_behavior()
# Called when the parent pickable body is dropped
func _on_dropped(_pickable) -> void:
_update_held_behavior()
# Called when the held_behavior property has been modified
func _set_held_behavior(p_held_behavior : HeldBehavior) -> void:
held_behavior = p_held_behavior
if is_inside_tree() and _parent_body:
_update_held_behavior()
# Update the pocket enable
func _update_held_behavior() -> void:
# Skip if no valid parent body
if not is_instance_valid(_parent_body):
return
# Test if the parent pickable is held
var is_held := _parent_body.is_picked_up()
# Update the enabled state based on whether the parent body is held and
# the desired behavior
match held_behavior:
HeldBehavior.ENABLE:
enabled = is_held
HeldBehavior.DISABLE:
enabled = not is_held