231 lines
6.6 KiB
GDScript
231 lines
6.6 KiB
GDScript
@tool
|
|
class_name PersistentItem
|
|
extends XRToolsPickable
|
|
|
|
|
|
## Persistent Item Instance Node
|
|
##
|
|
## The [PersistentItem] type is for instances of items managed by the
|
|
## persistence system. The [PersistentItem] type extends from
|
|
## [XRToolsPickable] because most items will be moved and carried by the user,
|
|
## however picking up and moving may be disabled by editing the
|
|
## [XRToolsPickable] and [RigidBody3D] settings.
|
|
##
|
|
## [PersistentItem] objects may be placed in persistent zones extending from
|
|
## [PersistentZone] and will have their state information (such as zone and
|
|
## position) managed in the [PersistentWorld] store. That data can be saved to
|
|
## file and loaded back.
|
|
##
|
|
## Extending from [PersistentItem] allows objects to extend the information
|
|
## persisted to the [PersistentWorld] store by overriding the
|
|
## [method _load_world_state] and [method _save_world_state] methods.
|
|
|
|
|
|
# Group for world-data properties
|
|
@export_group("World Data")
|
|
|
|
## This property specifies the unique ID (or base ID) for this item
|
|
@export var item_id : String
|
|
|
|
## This property specifies the [PersistentItemType] of this item
|
|
@export var item_type : PersistentItemType
|
|
|
|
## This property indicates whether this object was dynamically created
|
|
@export var item_dynamic := false
|
|
|
|
# Group for auto-return properties
|
|
@export_group("Auto Return")
|
|
|
|
## Automatically return to the last pocket when dropped
|
|
@export var auto_return := false
|
|
|
|
## Timeout for auto-return
|
|
@export var auto_return_timeout := 2.0
|
|
|
|
|
|
# Destroyed flag
|
|
var _destroyed := false
|
|
|
|
# Last pocket this object was in
|
|
var _last_pocket : PersistentPocket
|
|
|
|
# Auto-return timer node
|
|
var _auto_return_timer : Timer
|
|
|
|
|
|
# Add support for is_xr_class
|
|
func is_xr_class(p_name : String) -> bool:
|
|
return p_name == "PersistentItem" or super(p_name)
|
|
|
|
|
|
# Called when the node enters the scene tree for the first time.
|
|
func _ready():
|
|
super()
|
|
|
|
# Subscribe to picked_up and dropped signals
|
|
picked_up.connect(_on_picked_up)
|
|
dropped.connect(_on_dropped)
|
|
|
|
|
|
# Get configuration warnings
|
|
func _get_configuration_warnings() -> PackedStringArray:
|
|
var warnings := PackedStringArray()
|
|
|
|
# Verify item ID is set
|
|
if not item_id:
|
|
warnings.append("PersistentItem ID not zet")
|
|
|
|
# Verify the item type is set
|
|
if not item_type:
|
|
warnings.append("PersistentItem Type not set")
|
|
|
|
# Verify item is in persistent group
|
|
if not is_in_group("persistent"):
|
|
warnings.append("PersistentItem 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 is called when the [PersistentItem] is dropped and freed
|
|
func drop_and_free():
|
|
super()
|
|
|
|
# Propagate destruction to this node and all children
|
|
propagate_notification(Persistent.NOTIFICATION_DESTROY)
|
|
|
|
|
|
# This method loads the item state from [PersistentWorld]. If the
|
|
# [PersistentWorld] indicates this item is destroyed then it queues itself
|
|
# and all children for destruction, otherwise it restores the items state.
|
|
func _load_state() -> void:
|
|
# Restore the item state
|
|
var state = PersistentWorld.instance.get_value(item_id)
|
|
if not state is Dictionary:
|
|
return
|
|
|
|
# If the item is recorded as having been destroyed then destroy it
|
|
if state.get("destroyed", false):
|
|
propagate_notification(Persistent.NOTIFICATION_DESTROY)
|
|
return
|
|
|
|
# Restore the item state
|
|
_load_world_state(state)
|
|
|
|
|
|
# This method saves the state of the item to [PersistentWorld]. If the item
|
|
# is destroyed then the destroyed state is saved to the [PersistentWorld].
|
|
func _save_state() -> void:
|
|
# Handle saving destroyed state
|
|
if _destroyed:
|
|
# Dynamic items can just have their ID cleared
|
|
if item_dynamic:
|
|
PersistentWorld.instance.clear_value(item_id)
|
|
return
|
|
|
|
# Design-time items must be saved with the destroyed state
|
|
PersistentWorld.instance.set_value(item_id, { destroyed = true })
|
|
return
|
|
|
|
# Populate the state information
|
|
var state := {}
|
|
_save_world_state(state)
|
|
PersistentWorld.instance.set_value(item_id, state)
|
|
|
|
|
|
# This method destroys the item by marking it as destroyed, saving the
|
|
# destroyed state to the [PersistentWorld], and queueing the instance for
|
|
# destruction.
|
|
func _destroy() -> void:
|
|
# Mark the item as destroyed and save state
|
|
_destroyed = true
|
|
_save_state()
|
|
|
|
# Ensure the item is queued for destruction
|
|
queue_free()
|
|
|
|
|
|
## This method restores item state from the [param state] world data. The
|
|
## base implementation just restores the location. Classes extending from
|
|
## [PersistentItem] can override this method to load additional item state by
|
|
## calling super() to load the basic information and then reading additional
|
|
## state information from the dictionary.
|
|
func _load_world_state(state : Dictionary) -> void:
|
|
# Restore the location
|
|
var location = state.get("location")
|
|
if location is Transform3D:
|
|
global_transform = location
|
|
|
|
|
|
## This method saves item state to the [param state] world data. The base
|
|
## implementation just saves the type and location. Classes extending from
|
|
## [PersistentItem] can override this method to save additional item state by
|
|
## calling super() to save the basic information and then writing additional
|
|
## state information to the dictionary.
|
|
func _save_world_state(state : Dictionary) -> void:
|
|
# Save the type and location
|
|
state["type"] = item_type.type_id
|
|
state["location"] = global_transform
|
|
|
|
|
|
# Start the auto-return timer
|
|
func _start_auto_return_timer() -> void:
|
|
# Construct the auto-return timer on first use
|
|
if not _auto_return_timer:
|
|
_auto_return_timer = Timer.new()
|
|
_auto_return_timer.one_shot = true
|
|
_auto_return_timer.timeout.connect(_on_auto_return)
|
|
add_child(_auto_return_timer)
|
|
|
|
# Start the auto-return timer
|
|
_auto_return_timer.start(auto_return_timeout)
|
|
|
|
|
|
# Called when this object is picked up
|
|
func _on_picked_up(_pickable) -> void:
|
|
# Save the last pocket
|
|
if get_picked_up_by() is PersistentPocket:
|
|
_last_pocket = get_picked_up_by()
|
|
|
|
# Stop any auto-return timer
|
|
if _auto_return_timer:
|
|
_auto_return_timer.stop()
|
|
|
|
|
|
# Called when this object is dropped
|
|
func _on_dropped(_pickable) -> void:
|
|
# Start the auto-return timer if possible
|
|
if auto_return and _last_pocket:
|
|
_start_auto_return_timer()
|
|
|
|
|
|
# Called when the auto-return timer expires
|
|
func _on_auto_return() -> void:
|
|
# Skip if the last pocket is invalid
|
|
if not is_instance_valid(_last_pocket):
|
|
return
|
|
|
|
# Skip if the last pocket is already holding an object
|
|
if is_instance_valid(_last_pocket.picked_up_object):
|
|
return
|
|
|
|
# Instruct the pocket to pick us up
|
|
_last_pocket.pick_up_object.call_deferred(self)
|