325 lines
9.8 KiB
GDScript
325 lines
9.8 KiB
GDScript
@tool
|
|
class_name PersistentZone
|
|
extends XRToolsSceneBase
|
|
|
|
|
|
## Persistent Zone Node
|
|
##
|
|
## The [PersistentNode] class is an extension of [XRToolsSceneBase] which
|
|
## manages the state of the zones [PersistentItem] objects through the
|
|
## persistence system.
|
|
|
|
|
|
# Group for world-data properties
|
|
@export_group("World Data")
|
|
|
|
## This property specifies the persistent zone information
|
|
@export var zone_info : PersistentZoneInfo
|
|
|
|
signal numberOfFiresSignal(number: int)
|
|
var numberOfFires = 0;
|
|
|
|
|
|
# Add support for is_xr_class
|
|
func is_xr_class(p_name : String) -> bool:
|
|
return p_name == "PersistentZone" or super(p_name)
|
|
|
|
|
|
# Called when the node enters the scene tree for the first time.
|
|
func _ready() -> void:
|
|
var fire_scene = preload("res://assets/fire/fire.tscn")
|
|
var ground = $World/Ground/MeshInstance3D
|
|
for i in range(30):
|
|
spawn_on_surface(ground, fire_scene)
|
|
numberOfFiresSignal.emit(numberOfFires)
|
|
# call the base
|
|
super()
|
|
|
|
|
|
# Get configuration warnings
|
|
func _get_configuration_warnings() -> PackedStringArray:
|
|
var warnings := PackedStringArray()
|
|
|
|
# Verify zone info is set
|
|
if not zone_info:
|
|
warnings.append("Zone ID not zet")
|
|
|
|
# Return warnings
|
|
return warnings
|
|
|
|
|
|
## Handle zone loaded
|
|
func scene_loaded(user_data = null):
|
|
super(user_data)
|
|
|
|
# Save the current zone
|
|
GameState.current_zone = self
|
|
|
|
# Find all PersistentItem instances designed into the zone
|
|
var items_in_zone := {}
|
|
for node in XRTools.find_xr_children(self, "*", "PersistentItem"):
|
|
items_in_zone[node.item_id] = node
|
|
|
|
# Find the zone items the PersistentWorld thinks should be in this zone.
|
|
var zone_items = PersistentWorld.instance.get_value(zone_info.zone_id)
|
|
|
|
# Free items designed into the zone but PersistentWorld thinks should
|
|
# be removed.
|
|
if zone_items is Array:
|
|
for item_id in items_in_zone:
|
|
if not zone_items.has(item_id):
|
|
var item : PersistentItem = items_in_zone[item_id]
|
|
item.get_parent().remove_child(item)
|
|
item.queue_free()
|
|
|
|
# Load world-state for all items in the zone
|
|
propagate_notification(Persistent.NOTIFICATION_LOAD_STATE)
|
|
|
|
# Create items missing from the zone but PersistentWorld thinks should be
|
|
# present.
|
|
if zone_items is Array:
|
|
for item_id in zone_items:
|
|
if not items_in_zone.has(item_id):
|
|
create_item_instance(item_id)
|
|
|
|
# Create items held by the players left hand
|
|
var left_pickup := XRToolsFunctionPickup.find_left($XROrigin3D)
|
|
var left_item_id = PersistentWorld.instance.get_value("player.left_hand")
|
|
if left_pickup and left_item_id is String:
|
|
var left_instance := create_item_instance(left_item_id)
|
|
if left_instance:
|
|
left_instance.global_transform = left_pickup.global_transform
|
|
left_pickup._pick_up_object.call_deferred(left_instance)
|
|
|
|
# Create items held by the players right hand
|
|
var right_pickup := XRToolsFunctionPickup.find_right($XROrigin3D)
|
|
var right_item_id = PersistentWorld.instance.get_value("player.right_hand")
|
|
if right_pickup and right_item_id is String:
|
|
var right_instance := create_item_instance(right_item_id)
|
|
if right_instance:
|
|
right_instance.global_transform = right_pickup.global_transform
|
|
right_pickup._pick_up_object.call_deferred(right_instance)
|
|
|
|
|
|
## Handle zone exiting
|
|
func scene_exiting(user_data = null):
|
|
super(user_data)
|
|
|
|
# Ensure the zone state is saved before exiting the zone
|
|
save_world_state()
|
|
|
|
# Clear the current zone
|
|
GameState.current_zone = self
|
|
|
|
|
|
## This method saves the state of the zone to the [PersistentWorld]. This gets
|
|
## called upon exiting the zone; but it should also be called before saving
|
|
## the game.
|
|
func save_world_state() -> void:
|
|
# Save world-state for all items in the zone
|
|
propagate_notification(Persistent.NOTIFICATION_SAVE_STATE)
|
|
|
|
# Identify items held directly by the zone
|
|
var items_in_zone : Array[String] = []
|
|
for node in get_tree().get_nodes_in_group("persistent"):
|
|
if is_item_held_by_zone(node):
|
|
items_in_zone.append(node.item_id)
|
|
|
|
# Save the items held by the zone
|
|
PersistentWorld.instance.set_value(zone_info.zone_id, items_in_zone)
|
|
|
|
# Handle items held in the players left hand
|
|
var left_pickup := XRToolsFunctionPickup.find_left($XROrigin3D)
|
|
var left_item := _get_held_persistent_item(left_pickup)
|
|
if left_item:
|
|
# The player.left_hand holds the item
|
|
PersistentWorld.instance.set_value("player.left_hand", left_item.item_id)
|
|
else:
|
|
# The player.left_hand is empty
|
|
PersistentWorld.instance.clear_value("player.left_hand")
|
|
|
|
# Handle items held in the players right hand
|
|
var right_pickup := XRToolsFunctionPickup.find_right($XROrigin3D)
|
|
var right_item := _get_held_persistent_item(right_pickup)
|
|
if right_item:
|
|
# The player.right_hand holds the item
|
|
PersistentWorld.instance.set_value("player.right_hand", right_item.item_id)
|
|
else:
|
|
# The player.right_hand is empty
|
|
PersistentWorld.instance.clear_value("player.right_hand")
|
|
|
|
|
|
## Find the [PersistentZone] containing a given node
|
|
static func find_instance(node : Node) -> PersistentZone:
|
|
return XRTools.find_xr_ancestor(
|
|
node,
|
|
"*",
|
|
"PersistentZone") as PersistentZone
|
|
|
|
|
|
# Create a [PersistentItem] from its [param item_id]. This is used when
|
|
# loading a scene that contains an item carried by the user from a different
|
|
# scene.
|
|
func create_item_instance(item_id : String) -> PersistentItem:
|
|
# Get the items state information
|
|
var state = PersistentWorld.instance.get_value(item_id)
|
|
if not state is Dictionary:
|
|
push_warning("Item %s not in world-data" % item_id)
|
|
return null
|
|
|
|
# Get the items type_id
|
|
var item_type_id = state.get("type")
|
|
if not item_type_id is String:
|
|
push_warning("Item %s does not define type" % item_id)
|
|
return null
|
|
|
|
# Get the PersistentItemType
|
|
var item_type := PersistentWorld.instance.item_database.get_type(item_type_id)
|
|
if not item_type:
|
|
push_warning("Item type %s not in database" % item_type_id)
|
|
return null
|
|
|
|
# Load the item scene
|
|
var item_scene : PackedScene = load(item_type.instance_scene)
|
|
if not item_scene:
|
|
push_warning("Item scene %s not valid" % item_type.instance_scene)
|
|
return null
|
|
|
|
# Construct the item
|
|
var item : PersistentItem = item_scene.instantiate()
|
|
if not item:
|
|
push_warning("Item scene %s not valid" % item_type.instance_scene)
|
|
return null
|
|
|
|
# Initialize the item
|
|
item.item_id = item_id
|
|
item.item_type = item_type
|
|
item.propagate_notification(Persistent.NOTIFICATION_LOAD_STATE)
|
|
add_child(item)
|
|
return item
|
|
|
|
|
|
# This method returns true if the node is an item held by a zone rather than
|
|
# being held by some sort of persistent object such as a PersistentPocket or
|
|
# an XRToolsFunctionPickup.
|
|
static func is_item_held_by_zone(node : Node) -> bool:
|
|
# Skip if not valid
|
|
if not is_instance_valid(node):
|
|
return false
|
|
|
|
# Skip if not an PersistentItem
|
|
if not node is PersistentItem:
|
|
return false
|
|
|
|
# If the node isn't held by anything valid then it's held by the zone
|
|
if not is_instance_valid(node.get_picked_up_by()):
|
|
return true
|
|
|
|
# If held by a PersistentPocket then it's not held by the zone
|
|
if node.get_picked_up_by() is PersistentPocket:
|
|
return false
|
|
|
|
# If held by an XRToolsFunctionPickup the it's not held by the zone
|
|
if node.get_picked_up_by() is XRToolsFunctionPickup:
|
|
return false
|
|
|
|
# Node is held by a non-persistent mechanism in the zone
|
|
push_warning("Item ", node.item_id, " held by non-persistent ", node.get_picked_up_by())
|
|
return true
|
|
|
|
func spawn_on_surface(target_mesh_instance: MeshInstance3D, scene_to_spawn: PackedScene):
|
|
var original_mesh = target_mesh_instance.mesh
|
|
if original_mesh == null:
|
|
print("Mesh is missing.")
|
|
return
|
|
|
|
var mesh : ArrayMesh
|
|
|
|
# Handle QuadMesh or other primitive Meshes
|
|
if original_mesh is PrimitiveMesh:
|
|
mesh = ArrayMesh.new()
|
|
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, original_mesh.get_mesh_arrays())
|
|
elif original_mesh is ArrayMesh:
|
|
mesh = original_mesh
|
|
else:
|
|
print("Unsupported mesh type.")
|
|
return
|
|
|
|
# Get surface data (assuming surface 0)
|
|
if mesh.get_surface_count() == 0:
|
|
print("Mesh has no surfaces.")
|
|
return
|
|
|
|
var arrays = mesh.surface_get_arrays(0)
|
|
var vertices = arrays[Mesh.ARRAY_VERTEX]
|
|
var indices = arrays[Mesh.ARRAY_INDEX]
|
|
|
|
if vertices.is_empty() or indices.is_empty():
|
|
print("Mesh has no usable vertices or indices.")
|
|
return
|
|
|
|
# Try finding a face pointing mostly upwards
|
|
var found_point = false
|
|
var point = Vector3.ZERO
|
|
var max_attempts = 100 # prevent infinite loops
|
|
var attempts = 0
|
|
|
|
while not found_point and attempts < max_attempts:
|
|
attempts += 1
|
|
|
|
# Choose a random triangle
|
|
var tri_index = randi() % (indices.size() / 3)
|
|
var i0 = indices[tri_index * 3]
|
|
var i1 = indices[tri_index * 3 + 1]
|
|
var i2 = indices[tri_index * 3 + 2]
|
|
|
|
var a = vertices[i0]
|
|
var b = vertices[i1]
|
|
var c = vertices[i2]
|
|
|
|
# Calculate the face normal
|
|
var normal = ((b - a).cross(c - a)).normalized()
|
|
|
|
# Check if face normal points mostly up (adjust threshold as needed)
|
|
if normal.dot(Vector3.UP) > 0.7:
|
|
# Get random point in triangle (barycentric coordinates)
|
|
var r1 = randf()
|
|
var r2 = randf()
|
|
if r1 + r2 >= 1.0:
|
|
r1 = 1.0 - r1
|
|
r2 = 1.0 - r2
|
|
point = a + (b - a) * r1 + (c - a) * r2
|
|
found_point = true
|
|
numberOfFires += 1
|
|
|
|
if not found_point:
|
|
print("No upward facing face found after", max_attempts, "attempts.")
|
|
return
|
|
|
|
# Transform to world space
|
|
point = target_mesh_instance.global_transform * point
|
|
|
|
# Instance and place your object
|
|
var new_object = scene_to_spawn.instantiate()
|
|
new_object.global_transform.origin = point
|
|
new_object.scale = Vector3(3.0, 3.0, 3.0)
|
|
get_tree().current_scene.add_child(new_object)
|
|
|
|
# This method returns the persistent item primarily held by the pickup
|
|
static func _get_held_persistent_item(pickup : XRToolsFunctionPickup) -> PersistentItem:
|
|
# Fail if no pickup
|
|
if not is_instance_valid(pickup):
|
|
return null
|
|
|
|
# Fail if item is not a PersistentItem
|
|
var item := pickup.picked_up_object as PersistentItem
|
|
if not item:
|
|
return null
|
|
|
|
# Fail if not active pickup, but merely second-hand grab
|
|
if item.get_picked_up_by() != pickup:
|
|
return null
|
|
|
|
# Return the item
|
|
return item
|