asd
This commit is contained in:
+93
-38
@@ -24,7 +24,11 @@ func _ready() -> void:
|
|||||||
push_error("XRPickable node not found in container.gd")
|
push_error("XRPickable node not found in container.gd")
|
||||||
|
|
||||||
|
|
||||||
|
# Absorbing items is a server decision (the container's holder is server-
|
||||||
|
# snapped into a station, matching table.gd/hob.gd's convention).
|
||||||
func _on_body_entered(body: Node3D) -> void:
|
func _on_body_entered(body: Node3D) -> void:
|
||||||
|
if not NetworkManager.owns_world():
|
||||||
|
return
|
||||||
print("Container enabled: ", enabled)
|
print("Container enabled: ", enabled)
|
||||||
if not enabled:
|
if not enabled:
|
||||||
print("Container disabled in _on_body_entered body")
|
print("Container disabled in _on_body_entered body")
|
||||||
@@ -38,63 +42,114 @@ func _on_body_entered(body: Node3D) -> void:
|
|||||||
# If compatible and enough space, add item
|
# If compatible and enough space, add item
|
||||||
var food_item = body.get_node("FoodItem")
|
var food_item = body.get_node("FoodItem")
|
||||||
print("Container in station found %s of type %s: %s" % [target_group, FoodItem.Type.keys()[food_item.type], body.name])
|
print("Container in station found %s of type %s: %s" % [target_group, FoodItem.Type.keys()[food_item.type], body.name])
|
||||||
if food_item.type == FoodItem.Type.MEAL and _meal_container.get_child_count() < meal_positions.size():
|
var meal_count := contained_items.filter(func(f): return f.type == FoodItem.Type.MEAL).size()
|
||||||
|
var side_count := contained_items.filter(func(f): return f.type == FoodItem.Type.SIDE).size()
|
||||||
|
if food_item.type == FoodItem.Type.MEAL and meal_count < meal_positions.size():
|
||||||
print("Container adding meal")
|
print("Container adding meal")
|
||||||
_add_item(body, meal_positions, _meal_container)
|
_add_item(body)
|
||||||
if food_item.type == FoodItem.Type.SIDE and _side_container.get_child_count() < side_positions.size():
|
if food_item.type == FoodItem.Type.SIDE and side_count < side_positions.size():
|
||||||
print("Container adding side")
|
print("Container adding side")
|
||||||
_add_item(body, side_positions, _side_container)
|
_add_item(body)
|
||||||
|
|
||||||
|
|
||||||
func _add_item(item: Node3D, positions: Array[Node3D], container_root: Node3D) -> void:
|
# Contents are synced as data (ids on the plate's PlateController), not
|
||||||
|
# reparented nodes: reparenting a MultiplayerSpawner-tracked item out of
|
||||||
|
# WorldContent would despawn it on every client the instant it happened.
|
||||||
|
func _add_item(item: Node3D) -> void:
|
||||||
var pickable = item as XRToolsPickable
|
var pickable = item as XRToolsPickable
|
||||||
var rigidbody = item as RigidBody3D
|
if pickable and pickable.is_picked_up():
|
||||||
|
pickable.drop()
|
||||||
|
|
||||||
# Drop item
|
var food_node := item.get_node_or_null("FoodItem") as FoodItem
|
||||||
if pickable:
|
if not food_node:
|
||||||
if pickable.is_picked_up():
|
return
|
||||||
pickable.drop()
|
|
||||||
pickable.enabled = false
|
|
||||||
|
|
||||||
# Freeze the rigid body and disable its collisions so it doesn't fight the container
|
# Copy the data out before despawning the real item.
|
||||||
if rigidbody:
|
var data := FoodItem.new()
|
||||||
rigidbody.freeze = true
|
data.id = food_node.id
|
||||||
rigidbody.freeze_mode = RigidBody3D.FREEZE_MODE_KINEMATIC
|
data.type = food_node.type
|
||||||
rigidbody.process_mode = PROCESS_MODE_DISABLED
|
data.sell_value = food_node.sell_value
|
||||||
# Optional: Disable collision layer/mask so it doesn't bump into other food
|
contained_items.append(data)
|
||||||
rigidbody.collision_layer = 0
|
|
||||||
rigidbody.collision_mask = 0
|
|
||||||
|
|
||||||
item.reparent(container_root, false) # false = discard global transform
|
var plate_controller := xr_pickable.get_node_or_null("PlateController") as PlateController
|
||||||
|
if plate_controller:
|
||||||
|
plate_controller.contained_ids.append(food_node.id)
|
||||||
|
|
||||||
# Get target position index
|
NetworkManager.despawn_item(item)
|
||||||
var target_slot_index = container_root.get_child_count() - 1
|
|
||||||
if target_slot_index < positions.size():
|
|
||||||
item.position = positions[target_slot_index].position
|
|
||||||
item.rotation = positions[target_slot_index].rotation
|
|
||||||
|
|
||||||
# Add to list
|
|
||||||
var food_node = item.get_node_or_null("FoodItem")
|
|
||||||
if food_node:
|
|
||||||
contained_items.append(food_node as FoodItem)
|
|
||||||
|
|
||||||
|
|
||||||
func erase_item(item: FoodItem) -> void:
|
func erase_item(item: FoodItem) -> void:
|
||||||
for container_root in [_meal_container, _side_container]:
|
contained_items.erase(item)
|
||||||
for child in container_root.get_children():
|
var plate_controller := xr_pickable.get_node_or_null("PlateController") as PlateController
|
||||||
var food_item = child.get_node_or_null("FoodItem") as FoodItem
|
if plate_controller:
|
||||||
if food_item.id == item.id:
|
plate_controller.contained_ids.erase(item.id)
|
||||||
contained_items.erase(item)
|
|
||||||
child.queue_free()
|
|
||||||
|
|
||||||
|
|
||||||
func clear() -> void:
|
func clear() -> void:
|
||||||
# delete all nodes in containers and contained_items
|
contained_items.clear()
|
||||||
|
var plate_controller := xr_pickable.get_node_or_null("PlateController") as PlateController
|
||||||
|
if plate_controller:
|
||||||
|
plate_controller.contained_ids = []
|
||||||
|
|
||||||
|
|
||||||
|
## Rebuilds the purely-cosmetic visual representation of the plate's contents
|
||||||
|
## from a synced id list. Runs on every peer (called from PlateController
|
||||||
|
## whenever contained_ids changes, whether set locally or by the network).
|
||||||
|
func refresh_visuals(ids: Array[String]) -> void:
|
||||||
for child in _meal_container.get_children():
|
for child in _meal_container.get_children():
|
||||||
child.queue_free()
|
child.queue_free()
|
||||||
for child in _side_container.get_children():
|
for child in _side_container.get_children():
|
||||||
child.queue_free()
|
child.queue_free()
|
||||||
contained_items.clear()
|
|
||||||
|
var meal_idx := 0
|
||||||
|
var side_idx := 0
|
||||||
|
for id in ids:
|
||||||
|
var scene := RecipeManager.get_item_scene(id)
|
||||||
|
if not scene:
|
||||||
|
continue
|
||||||
|
var visual := scene.instantiate()
|
||||||
|
var food_node := visual.get_node_or_null("FoodItem") as FoodItem
|
||||||
|
|
||||||
|
var container_root: Node3D
|
||||||
|
var positions: Array[Node3D]
|
||||||
|
var idx: int
|
||||||
|
if food_node and food_node.type == FoodItem.Type.MEAL and meal_idx < meal_positions.size():
|
||||||
|
container_root = _meal_container
|
||||||
|
positions = meal_positions
|
||||||
|
idx = meal_idx
|
||||||
|
meal_idx += 1
|
||||||
|
elif food_node and food_node.type == FoodItem.Type.SIDE and side_idx < side_positions.size():
|
||||||
|
container_root = _side_container
|
||||||
|
positions = side_positions
|
||||||
|
idx = side_idx
|
||||||
|
side_idx += 1
|
||||||
|
else:
|
||||||
|
visual.queue_free()
|
||||||
|
continue
|
||||||
|
|
||||||
|
_make_cosmetic(visual)
|
||||||
|
container_root.add_child(visual)
|
||||||
|
visual.position = positions[idx].position
|
||||||
|
visual.rotation = positions[idx].rotation
|
||||||
|
|
||||||
|
|
||||||
|
# Strip interactivity/networking from a display-only copy: it's not spawned
|
||||||
|
# through NetworkManager, so it must never try to sync (its NetPickable child,
|
||||||
|
# if any, would have no corresponding replicated identity on other peers) or
|
||||||
|
# be grabbable/collidable.
|
||||||
|
func _make_cosmetic(visual: Node3D) -> void:
|
||||||
|
var net_pickable := visual.get_node_or_null("NetPickable")
|
||||||
|
if net_pickable:
|
||||||
|
net_pickable.queue_free()
|
||||||
|
if visual is RigidBody3D:
|
||||||
|
visual.freeze = true
|
||||||
|
visual.freeze_mode = RigidBody3D.FREEZE_MODE_KINEMATIC
|
||||||
|
visual.collision_layer = 0
|
||||||
|
visual.collision_mask = 0
|
||||||
|
if visual is XRToolsPickable:
|
||||||
|
visual.enabled = false
|
||||||
|
visual.set_process(false)
|
||||||
|
visual.set_physics_process(false)
|
||||||
|
|
||||||
|
|
||||||
#plate (Pickalbe)
|
#plate (Pickalbe)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
[ext_resource type="Script" uid="uid://dvobm6vcfnqe8" path="res://addons/godot-xr-tools/hands/poses/hand_pose_settings.gd" id="4_dhtvl"]
|
[ext_resource type="Script" uid="uid://dvobm6vcfnqe8" path="res://addons/godot-xr-tools/hands/poses/hand_pose_settings.gd" id="4_dhtvl"]
|
||||||
[ext_resource type="PackedScene" uid="uid://ctw7nbntd5pcj" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_right.tscn" id="5_vlcpl"]
|
[ext_resource type="PackedScene" uid="uid://ctw7nbntd5pcj" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_right.tscn" id="5_vlcpl"]
|
||||||
[ext_resource type="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="6_el51w"]
|
[ext_resource type="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="6_el51w"]
|
||||||
|
[ext_resource type="Script" path="res://Net/net_pickable.gd" id="20_netpk"]
|
||||||
|
|
||||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_kek77"]
|
[sub_resource type="CylinderShape3D" id="CylinderShape3D_kek77"]
|
||||||
height = 0.0635376
|
height = 0.0635376
|
||||||
@@ -26,6 +27,23 @@ metadata/_custom_type_script = "uid://dvobm6vcfnqe8"
|
|||||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_vlqg6"]
|
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_vlqg6"]
|
||||||
albedo_color = Color(0.36656043, 0.16690676, 0.12531222, 1)
|
albedo_color = Color(0.36656043, 0.16690676, 0.12531222, 1)
|
||||||
|
|
||||||
|
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_np_plate"]
|
||||||
|
properties/0/path = NodePath(".:position")
|
||||||
|
properties/0/spawn = false
|
||||||
|
properties/0/replication_mode = 1
|
||||||
|
properties/1/path = NodePath(".:quaternion")
|
||||||
|
properties/1/spawn = false
|
||||||
|
properties/1/replication_mode = 1
|
||||||
|
properties/2/path = NodePath("NetPickable:net_held_by")
|
||||||
|
properties/2/spawn = true
|
||||||
|
properties/2/replication_mode = 1
|
||||||
|
properties/3/path = NodePath("PlateController:contained_ids")
|
||||||
|
properties/3/spawn = true
|
||||||
|
properties/3/replication_mode = 1
|
||||||
|
properties/4/path = NodePath("PlateController:is_dirty")
|
||||||
|
properties/4/spawn = true
|
||||||
|
properties/4/replication_mode = 1
|
||||||
|
|
||||||
[node name="Plate" type="RigidBody3D" unique_id=190487773]
|
[node name="Plate" type="RigidBody3D" unique_id=190487773]
|
||||||
collision_layer = 4
|
collision_layer = 4
|
||||||
collision_mask = 196615
|
collision_mask = 196615
|
||||||
@@ -134,3 +152,8 @@ transform = Transform3D(0.21189868, 0, 0, 0, -9.2623855e-09, 0.21189868, 0, -0.2
|
|||||||
polygon = PackedVector2Array(-0.053057775, 0.2586278, 0.09469998, 0.40791017, 0.31746316, 0.26044407, 0.4514293, -0.05809097, 0.105977096, -0.11683442, -4.3913722e-05, 0.02302903)
|
polygon = PackedVector2Array(-0.053057775, 0.2586278, 0.09469998, 0.40791017, 0.31746316, 0.26044407, 0.4514293, -0.05809097, 0.105977096, -0.11683442, -4.3913722e-05, 0.02302903)
|
||||||
depth = 0.02
|
depth = 0.02
|
||||||
material = SubResource("StandardMaterial3D_vlqg6")
|
material = SubResource("StandardMaterial3D_vlqg6")
|
||||||
|
|
||||||
|
[node name="NetPickable" type="MultiplayerSynchronizer" parent="."]
|
||||||
|
root_path = NodePath("..")
|
||||||
|
replication_config = SubResource("SceneReplicationConfig_np_plate")
|
||||||
|
script = ExtResource("20_netpk")
|
||||||
|
|||||||
@@ -6,6 +6,13 @@ extends Node
|
|||||||
|
|
||||||
@export var is_dirty: bool = false
|
@export var is_dirty: bool = false
|
||||||
|
|
||||||
|
## Synced plate contents (item ids), replacing reparented child nodes so
|
||||||
|
## contents survive replication (a MultiplayerSpawner-tracked item would
|
||||||
|
## despawn on every client the instant it was reparented out of WorldContent).
|
||||||
|
## Server writes it via ItemContainer; every peer (server included) renders
|
||||||
|
## the cosmetic result via the setter below.
|
||||||
|
@export var contained_ids: Array[String] = []: set = _set_contained_ids
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
if not dirty_node:
|
if not dirty_node:
|
||||||
@@ -20,3 +27,15 @@ func _process(_delta: float) -> void:
|
|||||||
else:
|
else:
|
||||||
container.enabled = true
|
container.enabled = true
|
||||||
dirty_node.visible = false
|
dirty_node.visible = false
|
||||||
|
|
||||||
|
|
||||||
|
func _set_contained_ids(value: Array[String]) -> void:
|
||||||
|
contained_ids = value
|
||||||
|
# Deferred: this can be written by the replicated spawn payload before
|
||||||
|
# this node's own @onready vars (container) have resolved.
|
||||||
|
_refresh_visuals.call_deferred()
|
||||||
|
|
||||||
|
|
||||||
|
func _refresh_visuals() -> void:
|
||||||
|
if container:
|
||||||
|
container.refresh_visuals(contained_ids)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
[ext_resource type="Animation" uid="uid://bediglpx0rj7i" path="res://addons/godot-xr-tools/hands/animations/left/Grip 5.res" id="6_ftab3"]
|
[ext_resource type="Animation" uid="uid://bediglpx0rj7i" path="res://addons/godot-xr-tools/hands/animations/left/Grip 5.res" id="6_ftab3"]
|
||||||
[ext_resource type="PackedScene" uid="uid://3lr2dhy62rhk" path="res://Prefabs/combinable_item.tscn" id="7_wb51u"]
|
[ext_resource type="PackedScene" uid="uid://3lr2dhy62rhk" path="res://Prefabs/combinable_item.tscn" id="7_wb51u"]
|
||||||
[ext_resource type="PackedScene" uid="uid://bfj80jh13t6e5" path="res://Prefabs/food_item.tscn" id="8_t9y3x"]
|
[ext_resource type="PackedScene" uid="uid://bfj80jh13t6e5" path="res://Prefabs/food_item.tscn" id="8_t9y3x"]
|
||||||
|
[ext_resource type="Script" path="res://Net/net_pickable.gd" id="20_netpk"]
|
||||||
|
|
||||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_vlqg6"]
|
[sub_resource type="CylinderShape3D" id="CylinderShape3D_vlqg6"]
|
||||||
height = 0.10708985
|
height = 0.10708985
|
||||||
@@ -26,6 +27,17 @@ metadata/_custom_type_script = "uid://dvobm6vcfnqe8"
|
|||||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_hoqox"]
|
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_hoqox"]
|
||||||
albedo_color = Color(0.6784191, 0.54546416, 0.016760282, 1)
|
albedo_color = Color(0.6784191, 0.54546416, 0.016760282, 1)
|
||||||
|
|
||||||
|
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_np_buns"]
|
||||||
|
properties/0/path = NodePath(".:position")
|
||||||
|
properties/0/spawn = false
|
||||||
|
properties/0/replication_mode = 1
|
||||||
|
properties/1/path = NodePath(".:quaternion")
|
||||||
|
properties/1/spawn = false
|
||||||
|
properties/1/replication_mode = 1
|
||||||
|
properties/2/path = NodePath("NetPickable:net_held_by")
|
||||||
|
properties/2/spawn = true
|
||||||
|
properties/2/replication_mode = 1
|
||||||
|
|
||||||
[node name="BurgerBuns" unique_id=1088240294 instance=ExtResource("1_g1t48")]
|
[node name="BurgerBuns" unique_id=1088240294 instance=ExtResource("1_g1t48")]
|
||||||
|
|
||||||
[node name="CollisionShape3D" parent="." index="0"]
|
[node name="CollisionShape3D" parent="." index="0"]
|
||||||
@@ -72,3 +84,8 @@ id = "burger_buns"
|
|||||||
type = 2
|
type = 2
|
||||||
|
|
||||||
[node name="CombinableItem" parent="." index="5" unique_id=996936271 instance=ExtResource("7_wb51u")]
|
[node name="CombinableItem" parent="." index="5" unique_id=996936271 instance=ExtResource("7_wb51u")]
|
||||||
|
|
||||||
|
[node name="NetPickable" type="MultiplayerSynchronizer" parent="."]
|
||||||
|
root_path = NodePath("..")
|
||||||
|
replication_config = SubResource("SceneReplicationConfig_np_buns")
|
||||||
|
script = ExtResource("20_netpk")
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
[ext_resource type="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="6_cp3eg"]
|
[ext_resource type="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="6_cp3eg"]
|
||||||
[ext_resource type="PackedScene" uid="uid://bfj80jh13t6e5" path="res://Prefabs/food_item.tscn" id="7_mde49"]
|
[ext_resource type="PackedScene" uid="uid://bfj80jh13t6e5" path="res://Prefabs/food_item.tscn" id="7_mde49"]
|
||||||
[ext_resource type="PackedScene" uid="uid://3lr2dhy62rhk" path="res://Prefabs/combinable_item.tscn" id="8_wmrff"]
|
[ext_resource type="PackedScene" uid="uid://3lr2dhy62rhk" path="res://Prefabs/combinable_item.tscn" id="8_wmrff"]
|
||||||
|
[ext_resource type="Script" path="res://Net/net_pickable.gd" id="20_netpk"]
|
||||||
|
|
||||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_gkni8"]
|
[sub_resource type="CylinderShape3D" id="CylinderShape3D_gkni8"]
|
||||||
custom_solver_bias = 0.1
|
custom_solver_bias = 0.1
|
||||||
@@ -33,6 +34,17 @@ script = ExtResource("4_mgacb")
|
|||||||
closed_pose = ExtResource("6_cp3eg")
|
closed_pose = ExtResource("6_cp3eg")
|
||||||
metadata/_custom_type_script = "uid://dvobm6vcfnqe8"
|
metadata/_custom_type_script = "uid://dvobm6vcfnqe8"
|
||||||
|
|
||||||
|
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_np_charcoal"]
|
||||||
|
properties/0/path = NodePath(".:position")
|
||||||
|
properties/0/spawn = false
|
||||||
|
properties/0/replication_mode = 1
|
||||||
|
properties/1/path = NodePath(".:quaternion")
|
||||||
|
properties/1/spawn = false
|
||||||
|
properties/1/replication_mode = 1
|
||||||
|
properties/2/path = NodePath("NetPickable:net_held_by")
|
||||||
|
properties/2/spawn = true
|
||||||
|
properties/2/replication_mode = 1
|
||||||
|
|
||||||
[node name="PickableObject" unique_id=1675596942 instance=ExtResource("1_r73y2")]
|
[node name="PickableObject" unique_id=1675596942 instance=ExtResource("1_r73y2")]
|
||||||
|
|
||||||
[node name="CollisionShape3D" parent="." index="0"]
|
[node name="CollisionShape3D" parent="." index="0"]
|
||||||
@@ -54,3 +66,8 @@ id = "charcoal"
|
|||||||
type = 2
|
type = 2
|
||||||
|
|
||||||
[node name="CombinableItem" parent="." index="5" unique_id=996936271 instance=ExtResource("8_wmrff")]
|
[node name="CombinableItem" parent="." index="5" unique_id=996936271 instance=ExtResource("8_wmrff")]
|
||||||
|
|
||||||
|
[node name="NetPickable" type="MultiplayerSynchronizer" parent="."]
|
||||||
|
root_path = NodePath("..")
|
||||||
|
replication_config = SubResource("SceneReplicationConfig_np_charcoal")
|
||||||
|
script = ExtResource("20_netpk")
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
[ext_resource type="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="6_bdp75"]
|
[ext_resource type="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="6_bdp75"]
|
||||||
[ext_resource type="PackedScene" uid="uid://3lr2dhy62rhk" path="res://Prefabs/combinable_item.tscn" id="7_bdp75"]
|
[ext_resource type="PackedScene" uid="uid://3lr2dhy62rhk" path="res://Prefabs/combinable_item.tscn" id="7_bdp75"]
|
||||||
[ext_resource type="PackedScene" uid="uid://bfj80jh13t6e5" path="res://Prefabs/food_item.tscn" id="11_vjw41"]
|
[ext_resource type="PackedScene" uid="uid://bfj80jh13t6e5" path="res://Prefabs/food_item.tscn" id="11_vjw41"]
|
||||||
|
[ext_resource type="Script" path="res://Net/net_pickable.gd" id="20_netpk"]
|
||||||
|
|
||||||
[sub_resource type="BoxShape3D" id="BoxShape3D_m1xbq"]
|
[sub_resource type="BoxShape3D" id="BoxShape3D_m1xbq"]
|
||||||
size = Vector3(0.1, 0.1, 0.1)
|
size = Vector3(0.1, 0.1, 0.1)
|
||||||
@@ -29,6 +30,17 @@ script = ExtResource("4_hc3f7")
|
|||||||
closed_pose = ExtResource("6_bdp75")
|
closed_pose = ExtResource("6_bdp75")
|
||||||
metadata/_custom_type_script = "uid://dvobm6vcfnqe8"
|
metadata/_custom_type_script = "uid://dvobm6vcfnqe8"
|
||||||
|
|
||||||
|
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_np_cube"]
|
||||||
|
properties/0/path = NodePath(".:position")
|
||||||
|
properties/0/spawn = false
|
||||||
|
properties/0/replication_mode = 1
|
||||||
|
properties/1/path = NodePath(".:quaternion")
|
||||||
|
properties/1/spawn = false
|
||||||
|
properties/1/replication_mode = 1
|
||||||
|
properties/2/path = NodePath("NetPickable:net_held_by")
|
||||||
|
properties/2/spawn = true
|
||||||
|
properties/2/replication_mode = 1
|
||||||
|
|
||||||
[node name="PickableObject" unique_id=1675596942 groups=["platalbe_item"] instance=ExtResource("1_dbtw8")]
|
[node name="PickableObject" unique_id=1675596942 groups=["platalbe_item"] instance=ExtResource("1_dbtw8")]
|
||||||
|
|
||||||
[node name="CollisionShape3D" parent="." index="0"]
|
[node name="CollisionShape3D" parent="." index="0"]
|
||||||
@@ -50,3 +62,8 @@ id = "cube"
|
|||||||
type = 1
|
type = 1
|
||||||
|
|
||||||
[node name="CombinableItem" parent="." index="5" unique_id=996936271 instance=ExtResource("7_bdp75")]
|
[node name="CombinableItem" parent="." index="5" unique_id=996936271 instance=ExtResource("7_bdp75")]
|
||||||
|
|
||||||
|
[node name="NetPickable" type="MultiplayerSynchronizer" parent="."]
|
||||||
|
root_path = NodePath("..")
|
||||||
|
replication_config = SubResource("SceneReplicationConfig_np_cube")
|
||||||
|
script = ExtResource("20_netpk")
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
[ext_resource type="PackedScene" uid="uid://ctw7nbntd5pcj" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_right.tscn" id="6_mvnl5"]
|
[ext_resource type="PackedScene" uid="uid://ctw7nbntd5pcj" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_right.tscn" id="6_mvnl5"]
|
||||||
[ext_resource type="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="7_wqxjj"]
|
[ext_resource type="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="7_wqxjj"]
|
||||||
[ext_resource type="PackedScene" uid="uid://bfj80jh13t6e5" path="res://Prefabs/food_item.tscn" id="10_yxtxs"]
|
[ext_resource type="PackedScene" uid="uid://bfj80jh13t6e5" path="res://Prefabs/food_item.tscn" id="10_yxtxs"]
|
||||||
|
[ext_resource type="Script" path="res://Net/net_pickable.gd" id="20_netpk"]
|
||||||
|
|
||||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_fco8w"]
|
[sub_resource type="CylinderShape3D" id="CylinderShape3D_fco8w"]
|
||||||
height = 0.0338974
|
height = 0.0338974
|
||||||
@@ -26,6 +27,17 @@ script = ExtResource("5_w8sii")
|
|||||||
closed_pose = ExtResource("7_wqxjj")
|
closed_pose = ExtResource("7_wqxjj")
|
||||||
metadata/_custom_type_script = "uid://dvobm6vcfnqe8"
|
metadata/_custom_type_script = "uid://dvobm6vcfnqe8"
|
||||||
|
|
||||||
|
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_np_burger"]
|
||||||
|
properties/0/path = NodePath(".:position")
|
||||||
|
properties/0/spawn = false
|
||||||
|
properties/0/replication_mode = 1
|
||||||
|
properties/1/path = NodePath(".:quaternion")
|
||||||
|
properties/1/spawn = false
|
||||||
|
properties/1/replication_mode = 1
|
||||||
|
properties/2/path = NodePath("NetPickable:net_held_by")
|
||||||
|
properties/2/spawn = true
|
||||||
|
properties/2/replication_mode = 1
|
||||||
|
|
||||||
[node name="raw_burger" unique_id=1675596942 instance=ExtResource("1_fco8w")]
|
[node name="raw_burger" unique_id=1675596942 instance=ExtResource("1_fco8w")]
|
||||||
|
|
||||||
[node name="CollisionShape3D" parent="." index="0"]
|
[node name="CollisionShape3D" parent="." index="0"]
|
||||||
@@ -49,3 +61,8 @@ hand_pose = SubResource("Resource_qyiot")
|
|||||||
[node name="FoodItem" parent="." index="4" unique_id=63948206 instance=ExtResource("10_yxtxs")]
|
[node name="FoodItem" parent="." index="4" unique_id=63948206 instance=ExtResource("10_yxtxs")]
|
||||||
id = "raw_burger"
|
id = "raw_burger"
|
||||||
type = 2
|
type = 2
|
||||||
|
|
||||||
|
[node name="NetPickable" type="MultiplayerSynchronizer" parent="."]
|
||||||
|
root_path = NodePath("..")
|
||||||
|
replication_config = SubResource("SceneReplicationConfig_np_burger")
|
||||||
|
script = ExtResource("20_netpk")
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
[ext_resource type="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="7_rl64h"]
|
[ext_resource type="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="7_rl64h"]
|
||||||
[ext_resource type="PackedScene" uid="uid://3lr2dhy62rhk" path="res://Prefabs/combinable_item.tscn" id="10_ut7mg"]
|
[ext_resource type="PackedScene" uid="uid://3lr2dhy62rhk" path="res://Prefabs/combinable_item.tscn" id="10_ut7mg"]
|
||||||
[ext_resource type="PackedScene" uid="uid://bfj80jh13t6e5" path="res://Prefabs/food_item.tscn" id="10_zz42p"]
|
[ext_resource type="PackedScene" uid="uid://bfj80jh13t6e5" path="res://Prefabs/food_item.tscn" id="10_zz42p"]
|
||||||
|
[ext_resource type="Script" path="res://Net/net_pickable.gd" id="20_netpk"]
|
||||||
|
|
||||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_fco8w"]
|
[sub_resource type="CylinderShape3D" id="CylinderShape3D_fco8w"]
|
||||||
height = 0.04777527
|
height = 0.04777527
|
||||||
@@ -26,6 +27,17 @@ script = ExtResource("5_ychvb")
|
|||||||
closed_pose = ExtResource("7_rl64h")
|
closed_pose = ExtResource("7_rl64h")
|
||||||
metadata/_custom_type_script = "uid://dvobm6vcfnqe8"
|
metadata/_custom_type_script = "uid://dvobm6vcfnqe8"
|
||||||
|
|
||||||
|
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_np_cookedburger"]
|
||||||
|
properties/0/path = NodePath(".:position")
|
||||||
|
properties/0/spawn = false
|
||||||
|
properties/0/replication_mode = 1
|
||||||
|
properties/1/path = NodePath(".:quaternion")
|
||||||
|
properties/1/spawn = false
|
||||||
|
properties/1/replication_mode = 1
|
||||||
|
properties/2/path = NodePath("NetPickable:net_held_by")
|
||||||
|
properties/2/spawn = true
|
||||||
|
properties/2/replication_mode = 1
|
||||||
|
|
||||||
[node name="CookedBurger" unique_id=1675596942 instance=ExtResource("1_ut7mg")]
|
[node name="CookedBurger" unique_id=1675596942 instance=ExtResource("1_ut7mg")]
|
||||||
|
|
||||||
[node name="CollisionShape3D" parent="." index="0"]
|
[node name="CollisionShape3D" parent="." index="0"]
|
||||||
@@ -51,3 +63,8 @@ id = "cooked_burger"
|
|||||||
type = 2
|
type = 2
|
||||||
|
|
||||||
[node name="CombinableItem" parent="." index="5" unique_id=996936271 instance=ExtResource("10_ut7mg")]
|
[node name="CombinableItem" parent="." index="5" unique_id=996936271 instance=ExtResource("10_ut7mg")]
|
||||||
|
|
||||||
|
[node name="NetPickable" type="MultiplayerSynchronizer" parent="."]
|
||||||
|
root_path = NodePath("..")
|
||||||
|
replication_config = SubResource("SceneReplicationConfig_np_cookedburger")
|
||||||
|
script = ExtResource("20_netpk")
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
[ext_resource type="PackedScene" uid="uid://ctw7nbntd5pcj" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_right.tscn" id="5_dunns"]
|
[ext_resource type="PackedScene" uid="uid://ctw7nbntd5pcj" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_right.tscn" id="5_dunns"]
|
||||||
[ext_resource type="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="6_fh0f6"]
|
[ext_resource type="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="6_fh0f6"]
|
||||||
[ext_resource type="PackedScene" uid="uid://bfj80jh13t6e5" path="res://Prefabs/food_item.tscn" id="7_yy3y8"]
|
[ext_resource type="PackedScene" uid="uid://bfj80jh13t6e5" path="res://Prefabs/food_item.tscn" id="7_yy3y8"]
|
||||||
|
[ext_resource type="Script" path="res://Net/net_pickable.gd" id="20_netpk"]
|
||||||
|
|
||||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_fco8w"]
|
[sub_resource type="CylinderShape3D" id="CylinderShape3D_fco8w"]
|
||||||
height = 0.10392761
|
height = 0.10392761
|
||||||
@@ -28,6 +29,17 @@ albedo_color = Color(0.6784191, 0.54546416, 0.016760282, 1)
|
|||||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_6l01i"]
|
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_6l01i"]
|
||||||
albedo_color = Color(0.29, 0.101500005, 0, 1)
|
albedo_color = Color(0.29, 0.101500005, 0, 1)
|
||||||
|
|
||||||
|
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_np_hamburger"]
|
||||||
|
properties/0/path = NodePath(".:position")
|
||||||
|
properties/0/spawn = false
|
||||||
|
properties/0/replication_mode = 1
|
||||||
|
properties/1/path = NodePath(".:quaternion")
|
||||||
|
properties/1/spawn = false
|
||||||
|
properties/1/replication_mode = 1
|
||||||
|
properties/2/path = NodePath("NetPickable:net_held_by")
|
||||||
|
properties/2/spawn = true
|
||||||
|
properties/2/replication_mode = 1
|
||||||
|
|
||||||
[node name="Hamburger" unique_id=1675596942 groups=["platalbe_item"] instance=ExtResource("1_3gf3l")]
|
[node name="Hamburger" unique_id=1675596942 groups=["platalbe_item"] instance=ExtResource("1_3gf3l")]
|
||||||
gravity_scale = 0.04
|
gravity_scale = 0.04
|
||||||
|
|
||||||
@@ -81,3 +93,8 @@ material = SubResource("StandardMaterial3D_6l01i")
|
|||||||
id = "hamburger"
|
id = "hamburger"
|
||||||
type = 0
|
type = 0
|
||||||
sell_value = 4
|
sell_value = 4
|
||||||
|
|
||||||
|
[node name="NetPickable" type="MultiplayerSynchronizer" parent="."]
|
||||||
|
root_path = NodePath("..")
|
||||||
|
replication_config = SubResource("SceneReplicationConfig_np_hamburger")
|
||||||
|
script = ExtResource("20_netpk")
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
extends MultiplayerSynchronizer
|
||||||
|
|
||||||
|
## Networked sync component for a pickable item. Added as a child literally
|
||||||
|
## named "NetPickable" (network_manager.gd's authority RPCs already expect
|
||||||
|
## this) of every net-synced pickable, replicating its transform and held
|
||||||
|
## state. Only the current multiplayer authority (the server while loose, or
|
||||||
|
## whichever peer is holding it) actually simulates physics for the item;
|
||||||
|
## every other peer freezes their local copy and just follows the synced
|
||||||
|
## transform.
|
||||||
|
|
||||||
|
## 0 = loose/server-simulated; otherwise the peer id currently holding it.
|
||||||
|
## Replicated at spawn and on change so a late joiner sees the current holder.
|
||||||
|
var net_held_by: int = 0: set = _set_net_held_by
|
||||||
|
|
||||||
|
var _pickable: XRToolsPickable
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_pickable = get_parent() as XRToolsPickable
|
||||||
|
if not _pickable:
|
||||||
|
push_error("NetPickable must be a child of an XRToolsPickable")
|
||||||
|
return
|
||||||
|
_pickable.picked_up.connect(_on_picked_up)
|
||||||
|
_pickable.dropped.connect(_on_dropped)
|
||||||
|
# Deferred: the pickable root captures its own original_collision_mask/
|
||||||
|
# original_collision_layer via @onready, which runs AFTER this child's
|
||||||
|
# _ready() but BEFORE the root's _ready() body. Calling apply_held_state
|
||||||
|
# synchronously here would freeze/mask the item before that capture runs,
|
||||||
|
# permanently corrupting the "restore on drop" values.
|
||||||
|
apply_held_state.call_deferred()
|
||||||
|
|
||||||
|
|
||||||
|
func _set_net_held_by(value: int) -> void:
|
||||||
|
net_held_by = value
|
||||||
|
apply_held_state()
|
||||||
|
|
||||||
|
|
||||||
|
## Puts the item in the right physics state for whether this peer currently
|
||||||
|
## owns it. Called locally after net_held_by changes, and directly by
|
||||||
|
## NetworkManager._set_item_authority right after an authority handoff.
|
||||||
|
func apply_held_state() -> void:
|
||||||
|
if not _pickable:
|
||||||
|
return
|
||||||
|
if not NetworkManager.is_online() or is_multiplayer_authority():
|
||||||
|
# We own this item's simulation (offline, loose+server, or currently
|
||||||
|
# holding it): leave physics alone, XRToolsPickable manages the rest.
|
||||||
|
return
|
||||||
|
# Someone else owns it: stop simulating locally, just follow the sync.
|
||||||
|
if _pickable.is_picked_up():
|
||||||
|
_pickable.drop()
|
||||||
|
_pickable.freeze = true
|
||||||
|
_pickable.freeze_mode = RigidBody3D.FREEZE_MODE_KINEMATIC
|
||||||
|
_pickable.collision_mask = 0
|
||||||
|
_pickable.enabled = (net_held_by == 0)
|
||||||
|
|
||||||
|
|
||||||
|
## Local hand grab (not a station snap zone, which is server-only): request
|
||||||
|
## authority immediately so the throw/drop can be reconciled, but let the grab
|
||||||
|
## happen instantly here rather than waiting on the round trip.
|
||||||
|
func _on_picked_up(_p) -> void:
|
||||||
|
var by := _pickable.get_picked_up_by()
|
||||||
|
if not (by is XRToolsFunctionPickup):
|
||||||
|
return
|
||||||
|
NetworkManager.request_item_authority.rpc_id(1, _pickable.get_path())
|
||||||
|
|
||||||
|
|
||||||
|
func _on_dropped(_p) -> void:
|
||||||
|
# Only forward if we're actually still the authority — a drop caused by
|
||||||
|
# apply_held_state() losing authority (see above) must not re-report.
|
||||||
|
if not is_multiplayer_authority():
|
||||||
|
return
|
||||||
|
NetworkManager.release_item_authority.rpc_id(
|
||||||
|
1, _pickable.get_path(), _pickable.linear_velocity, _pickable.angular_velocity
|
||||||
|
)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bwd0pe2udb5xo
|
||||||
+99
-13
@@ -24,9 +24,15 @@ signal connection_failed()
|
|||||||
var _world: Node = null
|
var _world: Node = null
|
||||||
var _players_spawner: MultiplayerSpawner = null
|
var _players_spawner: MultiplayerSpawner = null
|
||||||
var _items_spawner: MultiplayerSpawner = null
|
var _items_spawner: MultiplayerSpawner = null
|
||||||
|
var _content_root: Node = null
|
||||||
|
|
||||||
var _log_file: FileAccess
|
var _log_file: FileAccess
|
||||||
|
|
||||||
|
## Reason the session ended, shown by the menu on the next _ready() (see
|
||||||
|
## take_status). Avoids depending on a live signal connection to a panel that
|
||||||
|
## doesn't exist yet at the moment the session actually ends.
|
||||||
|
var last_status := ""
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
_open_log()
|
_open_log()
|
||||||
@@ -80,26 +86,43 @@ func _go_offline() -> void:
|
|||||||
if multiplayer.multiplayer_peer:
|
if multiplayer.multiplayer_peer:
|
||||||
multiplayer.multiplayer_peer.close()
|
multiplayer.multiplayer_peer.close()
|
||||||
multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new()
|
multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new()
|
||||||
|
unregister_world()
|
||||||
|
|
||||||
|
|
||||||
# --- Item spawning ---------------------------------------------------------
|
# --- Item spawning ---------------------------------------------------------
|
||||||
|
|
||||||
## Spawn a networked item. Server-only when online (replicates to all peers via
|
## Spawn a networked item. Server-only when online (replicates to all peers via
|
||||||
## the ItemsSpawner); works directly when offline. Returns the new node on the
|
## the ItemsSpawner, including late joiners); works directly when offline.
|
||||||
## machine that owns spawning, else null.
|
## node_name gives the spawned node a deterministic, identical name on every
|
||||||
func spawn_item(scene_path: String, xform: Transform3D) -> Node:
|
## peer (needed for NodePath-based RPCs to resolve it); props are applied to
|
||||||
|
## the instance before it enters the tree, so exported vars land correctly.
|
||||||
|
## Returns the new node on the machine that owns spawning, else null.
|
||||||
|
func spawn_item(scene_path: String, xform: Transform3D, node_name: String = "", props: Dictionary = {}) -> Node:
|
||||||
if is_online() and not is_server():
|
if is_online() and not is_server():
|
||||||
return null
|
return null
|
||||||
var data := {"scene": scene_path, "xform": xform}
|
var data := {"scene": scene_path, "xform": xform, "name": node_name, "props": props}
|
||||||
if is_online() and _items_spawner:
|
if is_online() and _items_spawner:
|
||||||
return _items_spawner.spawn(data)
|
return _items_spawner.spawn(data)
|
||||||
# Offline: instantiate directly under the world.
|
# Offline: instantiate directly under the registered content root, or (for
|
||||||
|
# scenes that never call register_world, e.g. the offline menu/dev scenes)
|
||||||
|
# the current scene, so this keeps working without every offline scene
|
||||||
|
# needing to opt in.
|
||||||
var inst := _spawn_item_from_data(data)
|
var inst := _spawn_item_from_data(data)
|
||||||
if _world and inst:
|
if inst:
|
||||||
_world.add_child(inst)
|
var parent: Node = _content_root if _content_root else get_tree().current_scene
|
||||||
|
if parent:
|
||||||
|
parent.add_child(inst)
|
||||||
return inst
|
return inst
|
||||||
|
|
||||||
|
|
||||||
|
## Despawn a server-spawned item. MultiplayerSpawner broadcasts a despawn to
|
||||||
|
## every peer when a tracked node exits the tree on the authority, so this is
|
||||||
|
## the single seam for destroying spawned items (works offline too).
|
||||||
|
func despawn_item(node: Node) -> void:
|
||||||
|
if owns_world() and is_instance_valid(node):
|
||||||
|
node.queue_free()
|
||||||
|
|
||||||
|
|
||||||
# MultiplayerSpawner custom spawn function: runs on every peer to build the node
|
# MultiplayerSpawner custom spawn function: runs on every peer to build the node
|
||||||
# from the replicated payload.
|
# from the replicated payload.
|
||||||
func _spawn_item_from_data(data: Variant) -> Node:
|
func _spawn_item_from_data(data: Variant) -> Node:
|
||||||
@@ -110,9 +133,32 @@ func _spawn_item_from_data(data: Variant) -> Node:
|
|||||||
var inst := scene.instantiate()
|
var inst := scene.instantiate()
|
||||||
if inst is Node3D:
|
if inst is Node3D:
|
||||||
inst.transform = data["xform"]
|
inst.transform = data["xform"]
|
||||||
|
if data.get("name", "") != "":
|
||||||
|
inst.name = data["name"]
|
||||||
|
for key in data.get("props", {}):
|
||||||
|
inst.set(key, data["props"][key])
|
||||||
|
if not owns_world():
|
||||||
|
_gate_station(inst)
|
||||||
return inst
|
return inst
|
||||||
|
|
||||||
|
|
||||||
|
# Stations run their own logic and auto-grab (XRToolsSnapZone with
|
||||||
|
# snap_mode=RANGE) identically on every peer by default, which would let each
|
||||||
|
# peer independently grab/simulate the same shared object. Disable both on
|
||||||
|
# every peer except the one that owns world logic; the server-authoritative
|
||||||
|
# item-authority RPCs are what let clients still grab a server-held item by
|
||||||
|
# hand. Runs before the node enters the tree, so its own _ready() sees the
|
||||||
|
# final (disabled) state.
|
||||||
|
func _gate_station(node: Node) -> void:
|
||||||
|
if not (node is StaticBody3D):
|
||||||
|
return
|
||||||
|
for child in node.get_children():
|
||||||
|
if child is XRToolsSnapZone:
|
||||||
|
child.enabled = false
|
||||||
|
child.set_process(false)
|
||||||
|
node.set_process(false)
|
||||||
|
|
||||||
|
|
||||||
# --- Item grab-authority transfer -----------------------------------------
|
# --- Item grab-authority transfer -----------------------------------------
|
||||||
|
|
||||||
## A client (or host) requests authority over an item it just grabbed. Runs on
|
## A client (or host) requests authority over an item it just grabbed. Runs on
|
||||||
@@ -123,10 +169,17 @@ func request_item_authority(item_path: NodePath) -> void:
|
|||||||
if not is_server():
|
if not is_server():
|
||||||
return
|
return
|
||||||
var sender := multiplayer.get_remote_sender_id()
|
var sender := multiplayer.get_remote_sender_id()
|
||||||
|
var item := get_node_or_null(item_path)
|
||||||
|
if item:
|
||||||
|
var np := item.get_node_or_null("NetPickable")
|
||||||
|
if np and np.net_held_by != 0 and np.net_held_by != sender:
|
||||||
|
# Already legitimately held by a different live peer: reject the
|
||||||
|
# requester's optimistic client-side grab instead of stealing it.
|
||||||
|
force_release_item.rpc_id(sender, item_path)
|
||||||
|
return
|
||||||
# Assign authority + held state first (disables the item on the server so its
|
# Assign authority + held state first (disables the item on the server so its
|
||||||
# snap zone won't re-grab it), then release it from any station.
|
# snap zone won't re-grab it), then release it from any station.
|
||||||
_set_item_authority.rpc(item_path, sender)
|
_set_item_authority.rpc(item_path, sender)
|
||||||
var item := get_node_or_null(item_path)
|
|
||||||
if item:
|
if item:
|
||||||
_release_from_snap_zones(item)
|
_release_from_snap_zones(item)
|
||||||
|
|
||||||
@@ -147,13 +200,14 @@ func release_item_authority(item_path: NodePath, lin: Vector3, ang: Vector3) ->
|
|||||||
_try_snap_into_station(item)
|
_try_snap_into_station(item)
|
||||||
|
|
||||||
|
|
||||||
# All station snap zones in the world (nodes in the "station" group).
|
# All station snap zones in the world (every XRToolsSnapZone child of a node in
|
||||||
|
# the "station" group — some stations, e.g. Table, have more than one).
|
||||||
func _station_snap_zones() -> Array:
|
func _station_snap_zones() -> Array:
|
||||||
var zones := []
|
var zones := []
|
||||||
for station in get_tree().get_nodes_in_group("station"):
|
for station in get_tree().get_nodes_in_group("station"):
|
||||||
var zone = station.get_node_or_null("XRToolsSnapZone")
|
for child in station.get_children():
|
||||||
if zone:
|
if child is XRToolsSnapZone:
|
||||||
zones.append(zone)
|
zones.append(child)
|
||||||
return zones
|
return zones
|
||||||
|
|
||||||
|
|
||||||
@@ -190,6 +244,15 @@ func _set_item_authority(item_path: NodePath, peer: int) -> void:
|
|||||||
np.apply_held_state()
|
np.apply_held_state()
|
||||||
|
|
||||||
|
|
||||||
|
## Server tells a specific client that its optimistic grab was rejected (the
|
||||||
|
## item was already legitimately held by someone else). The client drops it.
|
||||||
|
@rpc("authority", "reliable")
|
||||||
|
func force_release_item(item_path: NodePath) -> void:
|
||||||
|
var item := get_node_or_null(item_path)
|
||||||
|
if item and item.has_method("drop"):
|
||||||
|
item.drop()
|
||||||
|
|
||||||
|
|
||||||
func is_server() -> bool:
|
func is_server() -> bool:
|
||||||
return is_online() and multiplayer.is_server()
|
return is_online() and multiplayer.is_server()
|
||||||
|
|
||||||
@@ -223,16 +286,37 @@ func submit_work(station_path: NodePath, amount: float) -> void:
|
|||||||
station.add_work(multiplayer.get_remote_sender_id(), amount)
|
station.add_work(multiplayer.get_remote_sender_id(), amount)
|
||||||
|
|
||||||
|
|
||||||
## Called by main.gd once the world scene is ready, passing its spawners.
|
## Called by the world scene once it's ready, passing its spawners. Must run
|
||||||
|
## before world_ready()/host()/join() on every peer so the custom spawn
|
||||||
|
## function is installed before any spawn packet can arrive.
|
||||||
func register_world(world: Node, players_spawner: MultiplayerSpawner, items_spawner: MultiplayerSpawner) -> void:
|
func register_world(world: Node, players_spawner: MultiplayerSpawner, items_spawner: MultiplayerSpawner) -> void:
|
||||||
_world = world
|
_world = world
|
||||||
_players_spawner = players_spawner
|
_players_spawner = players_spawner
|
||||||
_items_spawner = items_spawner
|
_items_spawner = items_spawner
|
||||||
|
_content_root = items_spawner.get_node(items_spawner.spawn_path) if items_spawner else world
|
||||||
if _items_spawner:
|
if _items_spawner:
|
||||||
_items_spawner.spawn_function = _spawn_item_from_data
|
_items_spawner.spawn_function = _spawn_item_from_data
|
||||||
log_line("World registered (players_spawner=%s items_spawner=%s)" % [str(players_spawner != null), str(items_spawner != null)])
|
log_line("World registered (players_spawner=%s items_spawner=%s)" % [str(players_spawner != null), str(items_spawner != null)])
|
||||||
|
|
||||||
|
|
||||||
|
## Called when the world scene goes away (disconnect, leaving the session) so
|
||||||
|
## the autoload doesn't hold stale/freed references across a scene reload.
|
||||||
|
func unregister_world() -> void:
|
||||||
|
_world = null
|
||||||
|
_content_root = null
|
||||||
|
_players_spawner = null
|
||||||
|
_items_spawner = null
|
||||||
|
|
||||||
|
|
||||||
|
## Returns the reason the last session ended (if any) and clears it. The menu
|
||||||
|
## pulls this on its own _ready() rather than depending on a live signal
|
||||||
|
## connection to a panel that doesn't exist yet when the session ends.
|
||||||
|
func take_status() -> String:
|
||||||
|
var s := last_status
|
||||||
|
last_status = ""
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
## Called by main.gd after it has registered the world and connected its
|
## Called by main.gd after it has registered the world and connected its
|
||||||
## player_joined/left listeners. Kicks off any menu- or command-line-driven
|
## player_joined/left listeners. Kicks off any menu- or command-line-driven
|
||||||
## session so that session signals never fire before the world is listening.
|
## session so that session signals never fire before the world is listening.
|
||||||
@@ -286,11 +370,13 @@ func _on_connected_to_server() -> void:
|
|||||||
func _on_connection_failed() -> void:
|
func _on_connection_failed() -> void:
|
||||||
log_line("connection_failed")
|
log_line("connection_failed")
|
||||||
_go_offline()
|
_go_offline()
|
||||||
|
last_status = "Could not connect"
|
||||||
connection_failed.emit()
|
connection_failed.emit()
|
||||||
|
|
||||||
func _on_server_disconnected() -> void:
|
func _on_server_disconnected() -> void:
|
||||||
log_line("server_disconnected")
|
log_line("server_disconnected")
|
||||||
_go_offline()
|
_go_offline()
|
||||||
|
last_status = "Host disconnected"
|
||||||
session_ended.emit()
|
session_ended.emit()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
class_name WorldLayout
|
||||||
|
|
||||||
|
## Data-driven description of what's in the multiplayer world. The server (or
|
||||||
|
## the single machine, when offline) spawns every entry through
|
||||||
|
## NetworkManager.spawn_item() instead of baking these into the scene file, so
|
||||||
|
## a joining client receives them from the server rather than assuming its own
|
||||||
|
## copy of the scene matches. Swapping the contents of these two functions is
|
||||||
|
## the only change needed for a future varying/procedural layout.
|
||||||
|
##
|
||||||
|
## Positions below are transcribed verbatim from the previous baked layout in
|
||||||
|
## Scenes/multiPlayer.tscn so the starting world is unchanged.
|
||||||
|
|
||||||
|
static func get_stations() -> Array[Dictionary]:
|
||||||
|
return [
|
||||||
|
{"scene": "res://Stations/Hob.tscn", "name": "Hob",
|
||||||
|
"xform": Transform3D(Basis(), Vector3(0.29336345, 0.8981018, -1.484)), "props": {}},
|
||||||
|
{"scene": "res://Stations/BurgerBunsDispenser.tscn", "name": "BurgerBunsDispenser",
|
||||||
|
"xform": Transform3D(Basis(), Vector3(-1.832131, 0.40028095, -1.4813508)), "props": {}},
|
||||||
|
{"scene": "res://Stations/sink.tscn", "name": "Sink",
|
||||||
|
"xform": Transform3D(Basis(), Vector3(1.3140475, 0.9061539, -1.4941733)), "props": {}},
|
||||||
|
{"scene": "res://Stations/dirt_station.tscn", "name": "DirtStation",
|
||||||
|
"xform": Transform3D(Basis(), Vector3(2.0864775, 0.8981018, -1.244947)), "props": {}},
|
||||||
|
{"scene": "res://Stations/Counter.tscn", "name": "Counter",
|
||||||
|
"xform": Transform3D(Basis(), Vector3(-0.7124918, 0.90304357, -1.4886917)), "props": {}},
|
||||||
|
{"scene": "res://Stations/Counter.tscn", "name": "Counter2",
|
||||||
|
"xform": Transform3D(Vector3(-4.371139e-08, 0.0, 1.0), Vector3(0.0, 1.0, 0.0), Vector3(-1.0, 0.0, -4.371139e-08), Vector3(-1.7648025, 0.90304357, -0.4458799)), "props": {}},
|
||||||
|
{"scene": "res://Stations/Counter.tscn", "name": "Counter3",
|
||||||
|
"xform": Transform3D(Vector3(-4.371139e-08, 0.0, 1.0), Vector3(0.0, 1.0, 0.0), Vector3(-1.0, 0.0, -4.371139e-08), Vector3(-1.7648025, 0.90304357, 0.55367994)), "props": {}},
|
||||||
|
{"scene": "res://Stations/Counter.tscn", "name": "Counter4",
|
||||||
|
"xform": Transform3D(Vector3(-4.371139e-08, 0.0, 1.0), Vector3(0.0, 1.0, 0.0), Vector3(-1.0, 0.0, -4.371139e-08), Vector3(-1.7648025, 0.90304357, 1.5539298)), "props": {}},
|
||||||
|
{"scene": "res://Stations/table.tscn", "name": "Table",
|
||||||
|
"xform": Transform3D(Basis(), Vector3(1.633146, 0.9030438, 1.1343781)),
|
||||||
|
"props": {
|
||||||
|
"initial_thinking_time": 8.0,
|
||||||
|
"initial_primary_time": 40.0,
|
||||||
|
"initial_friend_time": 3.0,
|
||||||
|
"initial_eating_time": 3.0,
|
||||||
|
}},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
static func get_items() -> Array[Dictionary]:
|
||||||
|
var items: Array[Dictionary] = []
|
||||||
|
|
||||||
|
items.append(_item("res://Items/BurgerBuns.tscn", "BurgerBuns", Vector3(-1.8162017, 1.6081157, -1.4714175)))
|
||||||
|
items.append(_item("res://Items/BurgerBuns.tscn", "BurgerBuns2", Vector3(-1.3596323, 1.4110342, -0.22482127)))
|
||||||
|
|
||||||
|
items.append(_item("res://Containers/plate.tscn", "Plate", Vector3(-1.5717233, 1.5454081, 1.5373346)))
|
||||||
|
items.append(_item("res://Containers/plate.tscn", "Plate2", Vector3(-1.5730225, 1.499024, 0.59790254)))
|
||||||
|
items.append(_item("res://Containers/plate.tscn", "Plate3", Vector3(-1.5818124, 1.499024, -0.4634577)))
|
||||||
|
|
||||||
|
items.append(_item("res://Items/burger.tscn", "burger", Vector3(0.6888188, 1.4195822, -1.7110313)))
|
||||||
|
items.append(_item("res://Items/burger.tscn", "burger2", Vector3(0.6931299, 1.5300478, -1.71225)))
|
||||||
|
items.append(_item("res://Items/burger.tscn", "burger3", Vector3(0.69027674, 1.4969791, -1.7210286)))
|
||||||
|
items.append(_item("res://Items/burger.tscn", "burger4", Vector3(0.69134104, 1.4543622, -1.7210286)))
|
||||||
|
|
||||||
|
items.append(_item("res://Items/hamburger.tscn", "Hamburger", Vector3(-1.9352558, 1.623975, 1.2447833)))
|
||||||
|
items.append(_item("res://Items/hamburger.tscn", "Hamburger2", Vector3(-1.9857153, 1.5329368, 0.9472374)))
|
||||||
|
items.append(_item("res://Items/hamburger.tscn", "Hamburger3", Vector3(-1.7201865, 1.5329367, 0.14773655)))
|
||||||
|
|
||||||
|
items.append(_item("res://Items/cooked_burger.tscn", "CookedBurger", Vector3(-0.30671906, 1.4131018, -1.0928738)))
|
||||||
|
items.append(_item("res://Items/cooked_burger.tscn", "CookedBurger2", Vector3(-0.30671906, 1.4496142, -1.0928738)))
|
||||||
|
items.append(_item("res://Items/cooked_burger.tscn", "CookedBurger3", Vector3(-1.3344773, 1.4398065, -0.7096845)))
|
||||||
|
items.append(_item("res://Items/cooked_burger.tscn", "CookedBurger4", Vector3(-0.30671906, 1.525444, -1.0928738)))
|
||||||
|
|
||||||
|
items.append(_item("res://Items/PickupCube.tscn", "PickableObject", Vector3(0.6225724, 1.4792972, -1.0473135)))
|
||||||
|
items.append(_item("res://Items/PickupCube.tscn", "PickableObject2", Vector3(0.6359743, 1.4639391, -1.1673055)))
|
||||||
|
items.append(_item("res://Items/PickupCube.tscn", "PickableObject3", Vector3(0.5142721, 1.4792972, -1.0473135)))
|
||||||
|
items.append(_item("res://Items/PickupCube.tscn", "PickableObject4", Vector3(0.5276739, 1.4639391, -1.1673055)))
|
||||||
|
items.append(_item("res://Items/PickupCube.tscn", "PickableObject5", Vector3(0.73287535, 1.4792972, -1.0473135)))
|
||||||
|
items.append(_item("res://Items/PickupCube.tscn", "PickableObject6", Vector3(0.7462772, 1.4639391, -1.1673055)))
|
||||||
|
items.append(_item("res://Items/PickupCube.tscn", "PickableObject7", Vector3(-1.3556751, 1.4792972, 0.28881657)))
|
||||||
|
items.append(_item("res://Items/PickupCube.tscn", "PickableObject8", Vector3(-1.3422732, 1.4639391, 0.16882455)))
|
||||||
|
items.append(_item("res://Items/PickupCube.tscn", "PickableObject9", Vector3(-1.4639754, 1.4792972, 0.28881657)))
|
||||||
|
items.append(_item("res://Items/PickupCube.tscn", "PickableObject10", Vector3(-1.4505737, 1.4639391, 0.16882455)))
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
static func _item(scene: String, name: String, pos: Vector3) -> Dictionary:
|
||||||
|
return {"scene": scene, "name": name, "xform": Transform3D(Basis(), pos), "props": {}}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://donvkica3drtx
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
extends Node3D
|
||||||
|
|
||||||
|
## Networked representation of one connected player. The owning peer's local
|
||||||
|
## XR rig (found via the "local_xr_origin" group) drives Head/LeftHand/
|
||||||
|
## RightHand each frame; the MultiplayerSynchronizer replicates those
|
||||||
|
## transforms so every other peer sees a matching avatar.
|
||||||
|
|
||||||
|
@onready var _head: Node3D = $Head
|
||||||
|
@onready var _left_hand: Node3D = $LeftHand
|
||||||
|
@onready var _right_hand: Node3D = $RightHand
|
||||||
|
|
||||||
|
var _local_camera: Node3D
|
||||||
|
var _local_left_hand: Node3D
|
||||||
|
var _local_right_hand: Node3D
|
||||||
|
|
||||||
|
|
||||||
|
func _enter_tree() -> void:
|
||||||
|
# MultiplayerSynchronizer resolves authority when this node enters the
|
||||||
|
# tree, so authority must be set here rather than in _ready(), or the
|
||||||
|
# first synced frames go the wrong direction.
|
||||||
|
set_multiplayer_authority(str(name).to_int())
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
if is_multiplayer_authority():
|
||||||
|
var origin := get_tree().get_first_node_in_group("local_xr_origin")
|
||||||
|
if origin:
|
||||||
|
_local_camera = origin.get_node_or_null("XRCamera3D")
|
||||||
|
_local_left_hand = origin.get_node_or_null("XRControllerLeftHand")
|
||||||
|
_local_right_hand = origin.get_node_or_null("XRControllerRightHand")
|
||||||
|
# Every peer's rig is baked at the same spot in the scene; spread
|
||||||
|
# joiners out along X by peer id so they don't start stacked on
|
||||||
|
# top of each other (host/peer 1 keeps the original spot).
|
||||||
|
var peer_id := str(name).to_int()
|
||||||
|
origin.position += Vector3((peer_id - 1) * 1.5, 0, 0)
|
||||||
|
# Don't render your own floating head/hands from the inside.
|
||||||
|
_head.visible = false
|
||||||
|
_left_hand.visible = false
|
||||||
|
_right_hand.visible = false
|
||||||
|
else:
|
||||||
|
set_process(false)
|
||||||
|
|
||||||
|
|
||||||
|
func _process(_delta: float) -> void:
|
||||||
|
if _local_camera:
|
||||||
|
_head.global_transform = _local_camera.global_transform
|
||||||
|
if _local_left_hand:
|
||||||
|
_left_hand.global_transform = _local_left_hand.global_transform
|
||||||
|
if _local_right_hand:
|
||||||
|
_right_hand.global_transform = _local_right_hand.global_transform
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bncuxh7j7ix5q
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
[gd_scene load_steps=5 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://Player/net_player.gd" id="1_np001"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bq86r4yll8po" path="res://addons/godot-xr-tools/hands/scenes/lowpoly/left_fullglove_low.tscn" id="2_np002"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://xqimcf20s2jp" path="res://addons/godot-xr-tools/hands/scenes/lowpoly/right_fullglove_low.tscn" id="3_np003"]
|
||||||
|
|
||||||
|
[sub_resource type="CapsuleMesh" id="CapsuleMesh_np004"]
|
||||||
|
radius = 0.09
|
||||||
|
height = 0.22
|
||||||
|
|
||||||
|
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_np005"]
|
||||||
|
properties/0/path = NodePath("Head:position")
|
||||||
|
properties/0/spawn = false
|
||||||
|
properties/0/replication_mode = 1
|
||||||
|
properties/1/path = NodePath("Head:quaternion")
|
||||||
|
properties/1/spawn = false
|
||||||
|
properties/1/replication_mode = 1
|
||||||
|
properties/2/path = NodePath("LeftHand:position")
|
||||||
|
properties/2/spawn = false
|
||||||
|
properties/2/replication_mode = 1
|
||||||
|
properties/3/path = NodePath("LeftHand:quaternion")
|
||||||
|
properties/3/spawn = false
|
||||||
|
properties/3/replication_mode = 1
|
||||||
|
properties/4/path = NodePath("RightHand:position")
|
||||||
|
properties/4/spawn = false
|
||||||
|
properties/4/replication_mode = 1
|
||||||
|
properties/5/path = NodePath("RightHand:quaternion")
|
||||||
|
properties/5/spawn = false
|
||||||
|
properties/5/replication_mode = 1
|
||||||
|
|
||||||
|
[node name="NetPlayer" type="Node3D"]
|
||||||
|
script = ExtResource("1_np001")
|
||||||
|
|
||||||
|
[node name="Head" type="MeshInstance3D" parent="."]
|
||||||
|
mesh = SubResource("CapsuleMesh_np004")
|
||||||
|
|
||||||
|
[node name="LeftHand" parent="." instance=ExtResource("2_np002")]
|
||||||
|
|
||||||
|
[node name="RightHand" parent="." instance=ExtResource("3_np003")]
|
||||||
|
|
||||||
|
[node name="Sync" type="MultiplayerSynchronizer" parent="."]
|
||||||
|
root_path = NodePath("..")
|
||||||
|
replication_config = SubResource("SceneReplicationConfig_np005")
|
||||||
|
replication_interval = 0.033
|
||||||
@@ -49,7 +49,10 @@ func _on_item_dropped(_item: Node3D) -> void:
|
|||||||
set_deferred("monitoring", false)
|
set_deferred("monitoring", false)
|
||||||
|
|
||||||
# If the other body is a FoodItem, check for a recipe and combine if one exists.
|
# If the other body is a FoodItem, check for a recipe and combine if one exists.
|
||||||
|
# Combining is a server decision (the base item is server-snapped into a zone).
|
||||||
func _on_body_entered(body: Node3D) -> void:
|
func _on_body_entered(body: Node3D) -> void:
|
||||||
|
if not NetworkManager.owns_world():
|
||||||
|
return
|
||||||
if _combining or body == _pickable:
|
if _combining or body == _pickable:
|
||||||
print("CombinableItem _on_body_entered: other is our own pickable")
|
print("CombinableItem _on_body_entered: other is our own pickable")
|
||||||
return
|
return
|
||||||
@@ -78,19 +81,16 @@ func _combine(other_body: Node3D, result: PackedScene) -> void:
|
|||||||
_combining = false
|
_combining = false
|
||||||
return
|
return
|
||||||
|
|
||||||
# Spawn the result at the base item's location, in world space.
|
# Spawn the result through the server (so it replicates to every peer,
|
||||||
|
# including late joiners) at the base item's location, in world space.
|
||||||
var base_transform := _pickable.global_transform
|
var base_transform := _pickable.global_transform
|
||||||
var result_instance: Node3D = result.instantiate()
|
var result_instance: Node3D = NetworkManager.spawn_item(result.resource_path, base_transform)
|
||||||
_pickable.get_tree().current_scene.add_child(result_instance)
|
|
||||||
result_instance.global_transform = base_transform
|
|
||||||
|
|
||||||
# Free the pickable / root of this item and consume the incoming item.
|
# Free the pickable / root of this item and consume the incoming item.
|
||||||
snap_zone.drop_object()
|
snap_zone.drop_object()
|
||||||
_pickable.queue_free()
|
NetworkManager.despawn_item(_pickable)
|
||||||
if other_body.has_method("drop_and_free"): # XRToolsPickable has this method
|
other_body.drop()
|
||||||
other_body.drop_and_free()
|
NetworkManager.despawn_item(other_body)
|
||||||
else:
|
|
||||||
other_body.queue_free()
|
|
||||||
|
|
||||||
# Snap the result into the now-empty zone.
|
# Snap the result into the now-empty zone.
|
||||||
snap_zone.pick_up_object(result_instance)
|
snap_zone.pick_up_object(result_instance)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
[ext_resource type="PackedScene" path="res://UI/main_menu_panel.tscn" id="17_panel"]
|
[ext_resource type="PackedScene" path="res://UI/main_menu_panel.tscn" id="17_panel"]
|
||||||
|
|
||||||
[sub_resource type="BoxShape3D" id="BoxShape3D_vlqg6"]
|
[sub_resource type="BoxShape3D" id="BoxShape3D_vlqg6"]
|
||||||
size = Vector3(5, 0.1, 5)
|
size = Vector3(115, 0.1, 15)
|
||||||
|
|
||||||
[sub_resource type="BoxMesh" id="BoxMesh_24d3s"]
|
[sub_resource type="BoxMesh" id="BoxMesh_24d3s"]
|
||||||
material = ExtResource("3_m56cs")
|
material = ExtResource("3_m56cs")
|
||||||
|
|||||||
+9
-119
@@ -3,22 +3,10 @@
|
|||||||
[ext_resource type="Script" uid="uid://d1cefhe3yyyds" path="res://Scenes/multiplayer_world.gd" id="1_kdan8"]
|
[ext_resource type="Script" uid="uid://d1cefhe3yyyds" path="res://Scenes/multiplayer_world.gd" id="1_kdan8"]
|
||||||
[ext_resource type="PackedScene" uid="uid://j5s5wus7tuhb" path="res://XROrigin.tscn" id="2_g30gi"]
|
[ext_resource type="PackedScene" uid="uid://j5s5wus7tuhb" path="res://XROrigin.tscn" id="2_g30gi"]
|
||||||
[ext_resource type="Material" uid="uid://cmia50cfqxxo4" path="res://Textures/dev_material_3d.tres" id="3_75ecy"]
|
[ext_resource type="Material" uid="uid://cmia50cfqxxo4" path="res://Textures/dev_material_3d.tres" id="3_75ecy"]
|
||||||
[ext_resource type="PackedScene" uid="uid://j7caslh27nor" path="res://Stations/Hob.tscn" id="4_y6cjq"]
|
|
||||||
[ext_resource type="Sky" uid="uid://c1abtpwhdm2d5" path="res://Textures/sky/NightSkyHDRI009_16K.tres" id="5_c3xgf"]
|
[ext_resource type="Sky" uid="uid://c1abtpwhdm2d5" path="res://Textures/sky/NightSkyHDRI009_16K.tres" id="5_c3xgf"]
|
||||||
[ext_resource type="PackedScene" uid="uid://c0lknik4noobs" path="res://Items/BurgerBuns.tscn" id="6_urxur"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://c6rift56ql3f8" path="res://Stations/BurgerBunsDispenser.tscn" id="7_3a3tq"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://bp3v1jl8pctro" path="res://Containers/plate.tscn" id="8_8xmyt"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://dpot5qie20vf6" path="res://Items/burger.tscn" id="9_6lx3u"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://b3m2ag8g5rj4r" path="res://Items/hamburger.tscn" id="10_p5bqy"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://e4i6o5oriecx" path="res://Items/PickupCube.tscn" id="11_jl027"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://dvrk268s7gkxh" path="res://Stations/sink.tscn" id="12_j3sw5"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://cnjwtnhwh0i8q" path="res://Stations/dirt_station.tscn" id="13_sbg0f"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://cfc4ho67u4r5e" path="res://Items/cooked_burger.tscn" id="14_lt5wx"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://efaec6ymgabo" path="res://Stations/Counter.tscn" id="15_7pgth"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://caf0xanmxbshy" path="res://Stations/table.tscn" id="16_8yyag"]
|
|
||||||
|
|
||||||
[sub_resource type="BoxShape3D" id="BoxShape3D_vlqg6"]
|
[sub_resource type="BoxShape3D" id="BoxShape3D_vlqg6"]
|
||||||
size = Vector3(5, 0.1, 5)
|
size = Vector3(15, 0.1, 15)
|
||||||
|
|
||||||
[sub_resource type="BoxMesh" id="BoxMesh_24d3s"]
|
[sub_resource type="BoxMesh" id="BoxMesh_24d3s"]
|
||||||
material = ExtResource("3_75ecy")
|
material = ExtResource("3_75ecy")
|
||||||
@@ -36,7 +24,7 @@ sdfgi_enabled = true
|
|||||||
[node name="Main" type="Node3D" unique_id=1312265607]
|
[node name="Main" type="Node3D" unique_id=1312265607]
|
||||||
script = ExtResource("1_kdan8")
|
script = ExtResource("1_kdan8")
|
||||||
|
|
||||||
[node name="XROrigin3D" parent="." unique_id=2055526621 instance=ExtResource("2_g30gi")]
|
[node name="XROrigin3D" parent="." unique_id=2055526621 groups=["local_xr_origin"] instance=ExtResource("2_g30gi")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.36171648, 0, 0.81835127)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.36171648, 0, 0.81835127)
|
||||||
|
|
||||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1376644384]
|
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1376644384]
|
||||||
@@ -52,114 +40,16 @@ shape = SubResource("BoxShape3D_vlqg6")
|
|||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00018164515, 0.0006352663, -0.002532661)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00018164515, 0.0006352663, -0.002532661)
|
||||||
mesh = SubResource("BoxMesh_24d3s")
|
mesh = SubResource("BoxMesh_24d3s")
|
||||||
|
|
||||||
[node name="Hob" parent="." unique_id=1687971542 instance=ExtResource("4_y6cjq")]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.29336345, 0.8981018, -1.484)
|
|
||||||
|
|
||||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=1177077250]
|
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=1177077250]
|
||||||
environment = SubResource("Environment_bvwq1")
|
environment = SubResource("Environment_bvwq1")
|
||||||
|
|
||||||
[node name="BurgerBuns" parent="." unique_id=1088240294 instance=ExtResource("6_urxur")]
|
[node name="WorldContent" type="Node3D" parent="."]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.8162017, 1.6081157, -1.4714175)
|
|
||||||
|
|
||||||
[node name="BurgerBunsDispenser" parent="." unique_id=1720683779 instance=ExtResource("7_3a3tq")]
|
[node name="Players" type="Node3D" parent="."]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.832131, 0.40028095, -1.4813508)
|
|
||||||
|
|
||||||
[node name="Plate" parent="." unique_id=190487773 instance=ExtResource("8_8xmyt")]
|
[node name="ItemsSpawner" type="MultiplayerSpawner" parent="."]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.5717233, 1.5454081, 1.5373346)
|
spawn_path = NodePath("../WorldContent")
|
||||||
|
|
||||||
[node name="burger" parent="." unique_id=1417604760 instance=ExtResource("9_6lx3u")]
|
[node name="PlayersSpawner" type="MultiplayerSpawner" parent="."]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.6888188, 1.4195822, -1.7110313)
|
_spawnable_scenes = PackedStringArray("res://Player/net_player.tscn")
|
||||||
|
spawn_path = NodePath("../Players")
|
||||||
[node name="burger2" parent="." unique_id=1584460510 instance=ExtResource("9_6lx3u")]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.6931299, 1.5300478, -1.71225)
|
|
||||||
|
|
||||||
[node name="burger3" parent="." unique_id=1884095916 instance=ExtResource("9_6lx3u")]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.69027674, 1.4969791, -1.7210286)
|
|
||||||
|
|
||||||
[node name="burger4" parent="." unique_id=1844818864 instance=ExtResource("9_6lx3u")]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.69134104, 1.4543622, -1.7210286)
|
|
||||||
|
|
||||||
[node name="Hamburger" parent="." unique_id=761091445 instance=ExtResource("10_p5bqy")]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.9352558, 1.623975, 1.2447833)
|
|
||||||
|
|
||||||
[node name="PickableObject" parent="." unique_id=1675596942 instance=ExtResource("11_jl027")]
|
|
||||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, 0.6225724, 1.4792972, -1.0473135)
|
|
||||||
|
|
||||||
[node name="PickableObject2" parent="." unique_id=713087634 instance=ExtResource("11_jl027")]
|
|
||||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, -5.293956e-23, -2.6077032e-08, 1.4901161e-08, 1, 0.6359743, 1.4639391, -1.1673055)
|
|
||||||
|
|
||||||
[node name="PickableObject3" parent="." unique_id=879935619 instance=ExtResource("11_jl027")]
|
|
||||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, 0.5142721, 1.4792972, -1.0473135)
|
|
||||||
|
|
||||||
[node name="PickableObject4" parent="." unique_id=198541902 instance=ExtResource("11_jl027")]
|
|
||||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, -5.293956e-23, -2.6077032e-08, 1.4901161e-08, 1, 0.5276739, 1.4639391, -1.1673055)
|
|
||||||
|
|
||||||
[node name="Hamburger2" parent="." unique_id=1349934579 instance=ExtResource("10_p5bqy")]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.9857153, 1.5329368, 0.9472374)
|
|
||||||
|
|
||||||
[node name="PickableObject5" parent="." unique_id=1021333509 instance=ExtResource("11_jl027")]
|
|
||||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, 0.73287535, 1.4792972, -1.0473135)
|
|
||||||
|
|
||||||
[node name="PickableObject6" parent="." unique_id=1268843669 instance=ExtResource("11_jl027")]
|
|
||||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, -5.293956e-23, -2.6077032e-08, 1.4901161e-08, 1, 0.7462772, 1.4639391, -1.1673055)
|
|
||||||
|
|
||||||
[node name="Sink" parent="." unique_id=2055277359 instance=ExtResource("12_j3sw5")]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.3140475, 0.9061539, -1.4941733)
|
|
||||||
|
|
||||||
[node name="DirtStation" parent="." unique_id=160842153 instance=ExtResource("13_sbg0f")]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.0864775, 0.8981018, -1.244947)
|
|
||||||
|
|
||||||
[node name="Plate2" parent="." unique_id=356790445 instance=ExtResource("8_8xmyt")]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.5730225, 1.499024, 0.59790254)
|
|
||||||
|
|
||||||
[node name="CookedBurger" parent="." unique_id=1127807542 instance=ExtResource("14_lt5wx")]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.30671906, 1.4131018, -1.0928738)
|
|
||||||
|
|
||||||
[node name="CookedBurger2" parent="." unique_id=160088590 instance=ExtResource("14_lt5wx")]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.30671906, 1.4496142, -1.0928738)
|
|
||||||
|
|
||||||
[node name="CookedBurger3" parent="." unique_id=992183367 instance=ExtResource("14_lt5wx")]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.3344773, 1.4398065, -0.7096845)
|
|
||||||
|
|
||||||
[node name="CookedBurger4" parent="." unique_id=1837607086 instance=ExtResource("14_lt5wx")]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.30671906, 1.525444, -1.0928738)
|
|
||||||
|
|
||||||
[node name="BurgerBuns2" parent="." unique_id=1210358958 instance=ExtResource("6_urxur")]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.3596323, 1.4110342, -0.22482127)
|
|
||||||
|
|
||||||
[node name="PickableObject7" parent="." unique_id=641653545 instance=ExtResource("11_jl027")]
|
|
||||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, -1.3556751, 1.4792972, 0.28881657)
|
|
||||||
|
|
||||||
[node name="PickableObject8" parent="." unique_id=1733819363 instance=ExtResource("11_jl027")]
|
|
||||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, -5.293956e-23, -2.6077032e-08, 1.4901161e-08, 1, -1.3422732, 1.4639391, 0.16882455)
|
|
||||||
|
|
||||||
[node name="PickableObject9" parent="." unique_id=12419746 instance=ExtResource("11_jl027")]
|
|
||||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, -1.4639754, 1.4792972, 0.28881657)
|
|
||||||
|
|
||||||
[node name="PickableObject10" parent="." unique_id=313160217 instance=ExtResource("11_jl027")]
|
|
||||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, -5.293956e-23, -2.6077032e-08, 1.4901161e-08, 1, -1.4505737, 1.4639391, 0.16882455)
|
|
||||||
|
|
||||||
[node name="Counter" parent="." unique_id=1487893288 instance=ExtResource("15_7pgth")]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.7124918, 0.90304357, -1.4886917)
|
|
||||||
|
|
||||||
[node name="Counter2" parent="." unique_id=368890752 instance=ExtResource("15_7pgth")]
|
|
||||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -1.7648025, 0.90304357, -0.4458799)
|
|
||||||
|
|
||||||
[node name="Counter3" parent="." unique_id=1498817646 instance=ExtResource("15_7pgth")]
|
|
||||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -1.7648025, 0.90304357, 0.55367994)
|
|
||||||
|
|
||||||
[node name="Counter4" parent="." unique_id=906748771 instance=ExtResource("15_7pgth")]
|
|
||||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -1.7648025, 0.90304357, 1.5539298)
|
|
||||||
|
|
||||||
[node name="Table" parent="." unique_id=1863572470 instance=ExtResource("16_8yyag")]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.633146, 0.9030438, 1.1343781)
|
|
||||||
initial_thinking_time = 8.0
|
|
||||||
initial_primary_time = 40.0
|
|
||||||
initial_friend_time = 3.0
|
|
||||||
initial_eating_time = 3.0
|
|
||||||
|
|
||||||
[node name="Hamburger3" parent="." unique_id=369573848 instance=ExtResource("10_p5bqy")]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.7201865, 1.5329367, 0.14773655)
|
|
||||||
|
|
||||||
[node name="Plate3" parent="." unique_id=2004250522 instance=ExtResource("8_8xmyt")]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.5818124, 1.499024, -0.4634577)
|
|
||||||
|
|||||||
@@ -1,13 +1,21 @@
|
|||||||
extends Node3D
|
extends Node3D
|
||||||
|
|
||||||
## World script for the multiplayer scene. Consumes the host/join request set
|
## World script for the multiplayer scene. Consumes the host/join request set
|
||||||
## by the main menu, and returns to the menu if the session ends.
|
## by the main menu, spawns the world's stations/items on whichever machine
|
||||||
|
## owns the world (server, or the local player when offline), spawns/despawns
|
||||||
|
## a player avatar per connected peer, and returns to the menu if the session
|
||||||
|
## ends.
|
||||||
##
|
##
|
||||||
## Connection-level only for now: no PlayersSpawner/ItemsSpawner here since the
|
## The world's actual content (stations, items) is NOT baked into this scene —
|
||||||
## networked Player/NetPickable scenes aren't ported yet (out of scope for this
|
## it's spawned at runtime from Net/world_layout.gd via NetworkManager, so a
|
||||||
## change) — peers connect, but avatars/items don't sync.
|
## joining client receives it from the server (MultiplayerSpawner replays
|
||||||
|
## existing spawns to late joiners) instead of relying on its own local copy
|
||||||
|
## matching.
|
||||||
|
|
||||||
|
const PLAYER_SCENE := preload("res://Player/net_player.tscn")
|
||||||
|
|
||||||
var xr_interface: XRInterface
|
var xr_interface: XRInterface
|
||||||
|
var _populated := false
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
@@ -16,11 +24,60 @@ func _ready() -> void:
|
|||||||
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
|
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
|
||||||
get_viewport().use_xr = true
|
get_viewport().use_xr = true
|
||||||
|
|
||||||
|
NetworkManager.register_world(self, $PlayersSpawner, $ItemsSpawner)
|
||||||
|
NetworkManager.player_joined.connect(_on_player_joined)
|
||||||
|
NetworkManager.player_left.connect(_on_player_left)
|
||||||
|
NetworkManager.session_started.connect(_on_session_started)
|
||||||
NetworkManager.session_ended.connect(_on_session_ended)
|
NetworkManager.session_ended.connect(_on_session_ended)
|
||||||
NetworkManager.connection_failed.connect(_on_connection_failed)
|
NetworkManager.connection_failed.connect(_on_connection_failed)
|
||||||
|
|
||||||
|
# Offline (single player / dev run): owns_world() is already true, so
|
||||||
|
# populate immediately. Online host case is handled by session_started.
|
||||||
|
_populate_world_if_owner()
|
||||||
|
|
||||||
NetworkManager.world_ready()
|
NetworkManager.world_ready()
|
||||||
|
|
||||||
|
|
||||||
|
func _exit_tree() -> void:
|
||||||
|
NetworkManager.unregister_world()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_session_started(_is_server: bool) -> void:
|
||||||
|
_populate_world_if_owner()
|
||||||
|
|
||||||
|
|
||||||
|
## Spawns the world's stations/items exactly once, on the machine that owns
|
||||||
|
## world logic (server or offline). Safe to call multiple times/entry points.
|
||||||
|
func _populate_world_if_owner() -> void:
|
||||||
|
if not NetworkManager.owns_world() or _populated:
|
||||||
|
return
|
||||||
|
_populated = true
|
||||||
|
GameManager.meals_in_play = ["hamburger"]
|
||||||
|
for d in WorldLayout.get_stations():
|
||||||
|
NetworkManager.spawn_item(d["scene"], d["xform"], d["name"], d["props"])
|
||||||
|
for d in WorldLayout.get_items():
|
||||||
|
NetworkManager.spawn_item(d["scene"], d["xform"], d["name"], d["props"])
|
||||||
|
|
||||||
|
|
||||||
|
## Only the server (or the single offline machine) materialises player
|
||||||
|
## avatars; MultiplayerSpawner replicates the result to everyone else,
|
||||||
|
## including late joiners.
|
||||||
|
func _on_player_joined(peer_id: int) -> void:
|
||||||
|
if not NetworkManager.owns_world() or $Players.has_node(str(peer_id)):
|
||||||
|
return
|
||||||
|
var p := PLAYER_SCENE.instantiate()
|
||||||
|
p.name = str(peer_id)
|
||||||
|
$Players.add_child(p, true)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_player_left(peer_id: int) -> void:
|
||||||
|
if not NetworkManager.owns_world():
|
||||||
|
return
|
||||||
|
var p := $Players.get_node_or_null(str(peer_id))
|
||||||
|
if p:
|
||||||
|
p.queue_free()
|
||||||
|
|
||||||
|
|
||||||
func _on_session_ended() -> void:
|
func _on_session_ended() -> void:
|
||||||
get_tree().change_scene_to_file("res://Scenes/mainMenu.tscn")
|
get_tree().change_scene_to_file("res://Scenes/mainMenu.tscn")
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ collision_layer = 65536
|
|||||||
collision_mask = 65536
|
collision_mask = 65536
|
||||||
script = ExtResource("1_p30r1")
|
script = ExtResource("1_p30r1")
|
||||||
snap_mode = 1
|
snap_mode = 1
|
||||||
initial_object = NodePath("../../BurgerBuns")
|
|
||||||
metadata/_custom_type_script = "uid://cquqe4f1m1sw6"
|
metadata/_custom_type_script = "uid://cquqe4f1m1sw6"
|
||||||
|
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="XRToolsSnapZone" unique_id=1104626493]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="XRToolsSnapZone" unique_id=1104626493]
|
||||||
|
|||||||
@@ -8,6 +8,14 @@
|
|||||||
[sub_resource type="SphereShape3D" id="SphereShape3D_vlqg6"]
|
[sub_resource type="SphereShape3D" id="SphereShape3D_vlqg6"]
|
||||||
radius = 0.3
|
radius = 0.3
|
||||||
|
|
||||||
|
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_np_hob"]
|
||||||
|
properties/0/path = NodePath(".:time_cooked")
|
||||||
|
properties/0/spawn = true
|
||||||
|
properties/0/replication_mode = 1
|
||||||
|
properties/1/path = NodePath(".:cooking_result_time")
|
||||||
|
properties/1/spawn = true
|
||||||
|
properties/1/replication_mode = 1
|
||||||
|
|
||||||
[node name="Hob" type="StaticBody3D" unique_id=1687971542 groups=["station"]]
|
[node name="Hob" type="StaticBody3D" unique_id=1687971542 groups=["station"]]
|
||||||
script = ExtResource("1_7jc4g")
|
script = ExtResource("1_7jc4g")
|
||||||
|
|
||||||
@@ -103,3 +111,7 @@ size = Vector3(0.9842529, 0.095687866, 0.14019775)
|
|||||||
transform = Transform3D(0.94465846, 0, 0, 0, 0.94465846, 0, 0, 0, 0.94465846, -0.003479004, -0.08253479, 0.5616675)
|
transform = Transform3D(0.94465846, 0, 0, 0, 0.94465846, 0, 0, 0, 0.94465846, -0.003479004, -0.08253479, 0.5616675)
|
||||||
operation = 2
|
operation = 2
|
||||||
size = Vector3(0.9842529, 0.8349304, 0.072387695)
|
size = Vector3(0.9842529, 0.8349304, 0.072387695)
|
||||||
|
|
||||||
|
[node name="Sync" type="MultiplayerSynchronizer" parent="."]
|
||||||
|
root_path = NodePath("..")
|
||||||
|
replication_config = SubResource("SceneReplicationConfig_np_hob")
|
||||||
|
|||||||
@@ -7,9 +7,13 @@ func _ready() -> void:
|
|||||||
snap_zone.has_picked_up.connect(_makeDirty)
|
snap_zone.has_picked_up.connect(_makeDirty)
|
||||||
|
|
||||||
func _makeDirty(item) -> void:
|
func _makeDirty(item) -> void:
|
||||||
|
if not NetworkManager.owns_world():
|
||||||
|
return
|
||||||
var plate: PlateController = item.get_node_or_null("PlateController") as PlateController
|
var plate: PlateController = item.get_node_or_null("PlateController") as PlateController
|
||||||
|
if not plate:
|
||||||
|
print("Dirt Station: held object is not a Plate")
|
||||||
|
return
|
||||||
if not plate.is_dirty:
|
if not plate.is_dirty:
|
||||||
plate.is_dirty = true
|
plate.is_dirty = true
|
||||||
print("Dirt Station could not find Dirty in: ", item)
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+6
-5
@@ -38,6 +38,8 @@ func _on_object_dropped() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func convert_held_to_item(_item: PackedScene) -> void:
|
func convert_held_to_item(_item: PackedScene) -> void:
|
||||||
|
if not NetworkManager.owns_world():
|
||||||
|
return
|
||||||
print("Hob converting ", _item)
|
print("Hob converting ", _item)
|
||||||
|
|
||||||
var old_pickable = snap_zone.picked_up_object
|
var old_pickable = snap_zone.picked_up_object
|
||||||
@@ -45,16 +47,15 @@ func convert_held_to_item(_item: PackedScene) -> void:
|
|||||||
push_warning("Hob finished cooking _item, but snap zone is missing its reference")
|
push_warning("Hob finished cooking _item, but snap zone is missing its reference")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Create new item as child of root in the same position
|
# Spawn the new item through the server so it replicates to every peer
|
||||||
|
# (including late joiners), in the old item's position.
|
||||||
var original_transform = old_pickable.global_transform
|
var original_transform = old_pickable.global_transform
|
||||||
var new_scene_instance = _item.instantiate()
|
var new_scene_instance = NetworkManager.spawn_item(_item.resource_path, original_transform)
|
||||||
get_tree().get_root().add_child(new_scene_instance)
|
|
||||||
new_scene_instance.global_transform = original_transform
|
|
||||||
|
|
||||||
# Drop and free the old item and pick up the new one
|
# Drop and free the old item and pick up the new one
|
||||||
print("Hob freeing old_pickable ", old_pickable)
|
print("Hob freeing old_pickable ", old_pickable)
|
||||||
snap_zone.drop_object()
|
snap_zone.drop_object()
|
||||||
old_pickable.queue_free()
|
NetworkManager.despawn_item(old_pickable)
|
||||||
snap_zone.pick_up_object(new_scene_instance)
|
snap_zone.pick_up_object(new_scene_instance)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,10 +13,8 @@ func _ready() -> void:
|
|||||||
|
|
||||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
|
if not NetworkManager.owns_world():
|
||||||
|
return
|
||||||
if not snap_zone.picked_up_object:
|
if not snap_zone.picked_up_object:
|
||||||
var new_item = item_scene.instantiate()
|
var new_item = NetworkManager.spawn_item(item_scene.resource_path, snap_zone.global_transform)
|
||||||
|
|
||||||
# Add the new item to the hob's parent (so it lives in the main world space)
|
|
||||||
get_parent().add_child(new_item)
|
|
||||||
new_item.global_transform = snap_zone.transform
|
|
||||||
snap_zone.pick_up_object(new_item)
|
snap_zone.pick_up_object(new_item)
|
||||||
|
|||||||
@@ -9,6 +9,11 @@
|
|||||||
[sub_resource type="SphereShape3D" id="SphereShape3D_dlkho"]
|
[sub_resource type="SphereShape3D" id="SphereShape3D_dlkho"]
|
||||||
radius = 0.3
|
radius = 0.3
|
||||||
|
|
||||||
|
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_np_sink"]
|
||||||
|
properties/0/path = NodePath(".:progress")
|
||||||
|
properties/0/spawn = true
|
||||||
|
properties/0/replication_mode = 1
|
||||||
|
|
||||||
[node name="Sink" type="StaticBody3D" unique_id=2055277359 groups=["station"]]
|
[node name="Sink" type="StaticBody3D" unique_id=2055277359 groups=["station"]]
|
||||||
script = ExtResource("1_7hh4b")
|
script = ExtResource("1_7hh4b")
|
||||||
plate_scene = ExtResource("2_ai6d4")
|
plate_scene = ExtResource("2_ai6d4")
|
||||||
@@ -75,3 +80,7 @@ size = Vector3(1.4033203, 1.2053223, 0.352417)
|
|||||||
transform = Transform3D(1.3113414e-07, 0.12955962, 0.99157166, 8.8817837e-16, 0.9915716, -0.1295596, -0.9999999, 1.6989695e-08, 1.3002891e-07, 0.6035124, 0.019589934, -0.20879287)
|
transform = Transform3D(1.3113414e-07, 0.12955962, 0.99157166, 8.8817837e-16, 0.9915716, -0.1295596, -0.9999999, 1.6989695e-08, 1.3002891e-07, 0.6035124, 0.019589934, -0.20879287)
|
||||||
operation = 2
|
operation = 2
|
||||||
size = Vector3(1.4033203, 1.2053223, 0.352417)
|
size = Vector3(1.4033203, 1.2053223, 0.352417)
|
||||||
|
|
||||||
|
[node name="Sync" type="MultiplayerSynchronizer" parent="."]
|
||||||
|
root_path = NodePath("..")
|
||||||
|
replication_config = SubResource("SceneReplicationConfig_np_sink")
|
||||||
|
|||||||
+57
-30
@@ -25,9 +25,16 @@ enum TableState {
|
|||||||
WAITING_FRIEND, # Waiting for friends food to arrive
|
WAITING_FRIEND, # Waiting for friends food to arrive
|
||||||
EATING
|
EATING
|
||||||
}
|
}
|
||||||
var _state = TableState.EMPTY # use _setState(), never set directly
|
|
||||||
var _state_time: float = 0.0
|
## State is server-authoritative and synced (see table.tscn's Sync node);
|
||||||
var _unsatisfied_orders: Array[String]
|
## state transitions only ever run where NetworkManager.owns_world() is true
|
||||||
|
## (the whole station's _process is gated off elsewhere for non-owners, see
|
||||||
|
## NetworkManager._gate_station). The setters below just refresh the display,
|
||||||
|
## so both the server (via _setState) and clients (via incoming sync) show
|
||||||
|
## the same text. Use _setState(), never assign _state directly.
|
||||||
|
var _state: TableState = TableState.EMPTY: set = _set_state
|
||||||
|
var _state_time: float = 0.0: set = _set_state_time
|
||||||
|
var _unsatisfied_orders: Array[String] = []: set = _set_unsatisfied_orders
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
if not label_3d:
|
if not label_3d:
|
||||||
@@ -81,7 +88,6 @@ func _absorb_item_if_correct(_item: Node) -> void:
|
|||||||
_unsatisfied_orders.erase(food_item.id)
|
_unsatisfied_orders.erase(food_item.id)
|
||||||
GAME_MANAGER.money += food_item.sell_value
|
GAME_MANAGER.money += food_item.sell_value
|
||||||
_setState(TableState.WAITING_FRIEND)
|
_setState(TableState.WAITING_FRIEND)
|
||||||
_update_order_text()
|
|
||||||
|
|
||||||
# Table has everything it wants. Start eating
|
# Table has everything it wants. Start eating
|
||||||
if _unsatisfied_orders.is_empty():
|
if _unsatisfied_orders.is_empty():
|
||||||
@@ -94,40 +100,63 @@ func place_order() -> void:
|
|||||||
_unsatisfied_orders.append(GAME_MANAGER.get_random_meal())
|
_unsatisfied_orders.append(GAME_MANAGER.get_random_meal())
|
||||||
|
|
||||||
|
|
||||||
func _update_order_text() -> void:
|
## Server-only state transition: sets the new state's timer and assigns
|
||||||
label_3d.text = "%s\n%s" % [TableState.keys()[_state], "\n".join(_unsatisfied_orders)]
|
## _state (whose setter refreshes the display on every peer).
|
||||||
|
|
||||||
|
|
||||||
func _setState(newState: TableState) -> void:
|
func _setState(newState: TableState) -> void:
|
||||||
print("Table set _state: ", TableState.keys()[newState])
|
print("Table set _state: ", TableState.keys()[newState])
|
||||||
|
|
||||||
if newState == TableState.EMPTY:
|
match newState:
|
||||||
label_3d.text = "empty"
|
TableState.EMPTY:
|
||||||
|
_state_time = 0.0
|
||||||
if newState == TableState.THINKING:
|
TableState.THINKING:
|
||||||
_state_time = initial_eating_time
|
_state_time = initial_eating_time
|
||||||
label_3d.text = lbl_thinking
|
TableState.WAITING_PRIMARY:
|
||||||
|
_state_time = initial_primary_time
|
||||||
if newState == TableState.WAITING_PRIMARY:
|
TableState.WAITING_FRIEND:
|
||||||
_state_time = initial_primary_time
|
_state_time = initial_friend_time
|
||||||
_update_order_text()
|
TableState.EATING:
|
||||||
|
_state_time = initial_eating_time
|
||||||
if newState == TableState.WAITING_FRIEND:
|
|
||||||
_state_time = initial_friend_time
|
|
||||||
_update_order_text()
|
|
||||||
|
|
||||||
if newState == TableState.EATING:
|
|
||||||
_state_time = initial_eating_time
|
|
||||||
label_3d.text = lbl_eating
|
|
||||||
|
|
||||||
_state = newState
|
_state = newState
|
||||||
|
|
||||||
|
|
||||||
|
## Pure presentation, driven off the current (locally authoritative or
|
||||||
|
## synced-from-server) state. Runs on every peer.
|
||||||
|
func _refresh_display() -> void:
|
||||||
|
if not label_3d:
|
||||||
|
return
|
||||||
|
match _state:
|
||||||
|
TableState.EMPTY:
|
||||||
|
label_3d.text = "empty"
|
||||||
|
TableState.THINKING:
|
||||||
|
label_3d.text = lbl_thinking
|
||||||
|
TableState.WAITING_PRIMARY, TableState.WAITING_FRIEND:
|
||||||
|
label_3d.text = "%s\n%s" % [TableState.keys()[_state], "\n".join(_unsatisfied_orders)]
|
||||||
|
TableState.EATING:
|
||||||
|
label_3d.text = lbl_eating
|
||||||
|
if label_3d_time:
|
||||||
|
label_3d_time.text = "%.1f" % _state_time
|
||||||
|
|
||||||
|
|
||||||
|
func _set_state(value: TableState) -> void:
|
||||||
|
_state = value
|
||||||
|
_refresh_display()
|
||||||
|
|
||||||
|
|
||||||
|
func _set_state_time(value: float) -> void:
|
||||||
|
_state_time = value
|
||||||
|
_refresh_display()
|
||||||
|
|
||||||
|
|
||||||
|
func _set_unsatisfied_orders(value: Array[String]) -> void:
|
||||||
|
_unsatisfied_orders = value
|
||||||
|
_refresh_display()
|
||||||
|
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
# Wait for state to finish
|
# Wait for state to finish
|
||||||
if _state_time > 0:
|
if _state_time > 0:
|
||||||
_state_time -= delta
|
_state_time -= delta
|
||||||
label_3d_time.text = "%.1f" % _state_time
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# When state timer is finished, do this stuff before moving to next state
|
# When state timer is finished, do this stuff before moving to next state
|
||||||
@@ -136,9 +165,8 @@ func _process(delta: float) -> void:
|
|||||||
_setState(TableState.THINKING)
|
_setState(TableState.THINKING)
|
||||||
|
|
||||||
TableState.THINKING:
|
TableState.THINKING:
|
||||||
_setState(TableState.WAITING_PRIMARY)
|
|
||||||
place_order()
|
place_order()
|
||||||
_update_order_text()
|
_setState(TableState.WAITING_PRIMARY)
|
||||||
|
|
||||||
|
|
||||||
TableState.WAITING_PRIMARY:
|
TableState.WAITING_PRIMARY:
|
||||||
@@ -158,4 +186,3 @@ func _process(delta: float) -> void:
|
|||||||
plate.container.clear()
|
plate.container.clear()
|
||||||
plate.is_dirty = true
|
plate.is_dirty = true
|
||||||
_setState(TableState.EMPTY)
|
_setState(TableState.EMPTY)
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,17 @@ radius = 0.3
|
|||||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_24d3s"]
|
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_24d3s"]
|
||||||
albedo_color = Color(0.31, 0.21576, 0.1333, 1)
|
albedo_color = Color(0.31, 0.21576, 0.1333, 1)
|
||||||
|
|
||||||
|
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_np_table"]
|
||||||
|
properties/0/path = NodePath(".:_state")
|
||||||
|
properties/0/spawn = true
|
||||||
|
properties/0/replication_mode = 1
|
||||||
|
properties/1/path = NodePath(".:_state_time")
|
||||||
|
properties/1/spawn = true
|
||||||
|
properties/1/replication_mode = 1
|
||||||
|
properties/2/path = NodePath(".:_unsatisfied_orders")
|
||||||
|
properties/2/spawn = true
|
||||||
|
properties/2/replication_mode = 1
|
||||||
|
|
||||||
[node name="Table" type="StaticBody3D" unique_id=1863572470 groups=["station"]]
|
[node name="Table" type="StaticBody3D" unique_id=1863572470 groups=["station"]]
|
||||||
script = ExtResource("1_2vcpj")
|
script = ExtResource("1_2vcpj")
|
||||||
|
|
||||||
@@ -155,3 +166,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.3306234, 0)
|
|||||||
pixel_size = 0.003
|
pixel_size = 0.003
|
||||||
billboard = 2
|
billboard = 2
|
||||||
text = "20.1s"
|
text = "20.1s"
|
||||||
|
|
||||||
|
[node name="Sync" type="MultiplayerSynchronizer" parent="."]
|
||||||
|
root_path = NodePath("..")
|
||||||
|
replication_config = SubResource("SceneReplicationConfig_np_table")
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ extends Control
|
|||||||
|
|
||||||
const JON_SCENE := "res://Scenes/JonScene.tscn"
|
const JON_SCENE := "res://Scenes/JonScene.tscn"
|
||||||
const MULTIPLAYER_SCENE := "res://Scenes/multiPlayer.tscn"
|
const MULTIPLAYER_SCENE := "res://Scenes/multiPlayer.tscn"
|
||||||
|
const SETTINGS_PATH := "user://vryhungry_settings.cfg"
|
||||||
|
|
||||||
var _ip := "127.0.0.1"
|
var _ip := "127.0.0.1"
|
||||||
|
|
||||||
@@ -33,8 +34,15 @@ func _ready() -> void:
|
|||||||
_join_menu_btn.pressed.connect(_show_join_view)
|
_join_menu_btn.pressed.connect(_show_join_view)
|
||||||
_join_btn.pressed.connect(_on_join_pressed)
|
_join_btn.pressed.connect(_on_join_pressed)
|
||||||
_back_btn.pressed.connect(_show_root_view)
|
_back_btn.pressed.connect(_show_root_view)
|
||||||
|
_load_ip()
|
||||||
_show_root_view()
|
_show_root_view()
|
||||||
_refresh_ip()
|
_refresh_ip()
|
||||||
|
# Show why we're back here, if the last session ended unexpectedly
|
||||||
|
# (dropped connection, failed join). Pulled rather than pushed, since
|
||||||
|
# NetworkManager may have set this before this panel even existed.
|
||||||
|
var status := NetworkManager.take_status()
|
||||||
|
if not status.is_empty():
|
||||||
|
set_status(status)
|
||||||
|
|
||||||
|
|
||||||
## --server / --join <ip> on the command line skip straight to the multiplayer
|
## --server / --join <ip> on the command line skip straight to the multiplayer
|
||||||
@@ -64,6 +72,7 @@ func _show_root_view() -> void:
|
|||||||
func _show_join_view() -> void:
|
func _show_join_view() -> void:
|
||||||
_root_view.visible = false
|
_root_view.visible = false
|
||||||
_join_view.visible = true
|
_join_view.visible = true
|
||||||
|
set_status("Enter host IP, then Join")
|
||||||
|
|
||||||
|
|
||||||
func _on_key(key: String) -> void:
|
func _on_key(key: String) -> void:
|
||||||
@@ -94,10 +103,26 @@ func _on_join_pressed() -> void:
|
|||||||
if _ip.is_empty():
|
if _ip.is_empty():
|
||||||
set_status("Enter an IP first")
|
set_status("Enter an IP first")
|
||||||
return
|
return
|
||||||
|
_save_ip()
|
||||||
NetworkManager.request_join(_ip)
|
NetworkManager.request_join(_ip)
|
||||||
get_tree().change_scene_to_file.call_deferred(MULTIPLAYER_SCENE)
|
get_tree().change_scene_to_file.call_deferred(MULTIPLAYER_SCENE)
|
||||||
|
|
||||||
|
|
||||||
|
## Remembers the last IP the player tried to join, so the keypad starts
|
||||||
|
## pre-filled next time instead of always defaulting to 127.0.0.1.
|
||||||
|
func _load_ip() -> void:
|
||||||
|
var cfg := ConfigFile.new()
|
||||||
|
if cfg.load(SETTINGS_PATH) == OK:
|
||||||
|
_ip = cfg.get_value("network", "last_ip", _ip)
|
||||||
|
|
||||||
|
|
||||||
|
func _save_ip() -> void:
|
||||||
|
var cfg := ConfigFile.new()
|
||||||
|
cfg.load(SETTINGS_PATH)
|
||||||
|
cfg.set_value("network", "last_ip", _ip)
|
||||||
|
cfg.save(SETTINGS_PATH)
|
||||||
|
|
||||||
|
|
||||||
## Called by network_manager (e.g. on connection_failed after a bounce back to
|
## Called by network_manager (e.g. on connection_failed after a bounce back to
|
||||||
## this menu) to show feedback.
|
## this menu) to show feedback.
|
||||||
func set_status(text: String) -> void:
|
func set_status(text: String) -> void:
|
||||||
|
|||||||
@@ -33,6 +33,15 @@ theme_override_font_sizes/font_size = 26
|
|||||||
text = "VRyHungry"
|
text = "VRyHungry"
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="Status" type="Label" parent="Margin"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 0
|
||||||
|
theme_override_colors/font_color = Color(0.8, 0.8, 0.5, 1)
|
||||||
|
theme_override_font_sizes/font_size = 16
|
||||||
|
text = ""
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
[node name="RootView" type="VBoxContainer" parent="Margin"]
|
[node name="RootView" type="VBoxContainer" parent="Margin"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
@@ -82,14 +91,6 @@ theme_override_font_sizes/font_size = 34
|
|||||||
text = "127.0.0.1"
|
text = "127.0.0.1"
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
|
|
||||||
[node name="Status" type="Label" parent="Margin/JoinView"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_colors/font_color = Color(0.8, 0.8, 0.5, 1)
|
|
||||||
theme_override_font_sizes/font_size = 16
|
|
||||||
text = "Enter host IP, then Join"
|
|
||||||
horizontal_alignment = 1
|
|
||||||
|
|
||||||
[node name="Keypad" type="GridContainer" parent="Margin/JoinView"]
|
[node name="Keypad" type="GridContainer" parent="Margin/JoinView"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ var xr_interface :XRInterface
|
|||||||
func _ready():
|
func _ready():
|
||||||
RECIPE_MANAGER.print_all_recipes()
|
RECIPE_MANAGER.print_all_recipes()
|
||||||
|
|
||||||
GAME_MANAGER.meals_in_play.append("hamburger")
|
GAME_MANAGER.meals_in_play = ["hamburger"]
|
||||||
|
|
||||||
xr_interface = XRServer.find_interface("OpenXR")
|
xr_interface = XRServer.find_interface("OpenXR")
|
||||||
if xr_interface and xr_interface.is_initialized():
|
if xr_interface and xr_interface.is_initialized():
|
||||||
|
|||||||
Reference in New Issue
Block a user