Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ee835ea295 | |||
| 61d92052ac | |||
| 0ebd0a4a85 |
+14
-18
@@ -91,10 +91,6 @@ func _add_item(item: Node3D) -> void:
|
||||
plate_controller.contained_ids = updated
|
||||
|
||||
NetworkManager.despawn_item(item)
|
||||
# In case the container is on a table that needs to register this addition,
|
||||
# ask all table in scene to absorb any new items.
|
||||
print("Container group call absorb_items()")
|
||||
get_tree().call_group("table", "absorb_items")
|
||||
|
||||
|
||||
func erase_item(item: FoodItem) -> void:
|
||||
@@ -158,19 +154,22 @@ func refresh_visuals(ids: Array[String]) -> void:
|
||||
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.
|
||||
# Strip interactivity from a display-only copy. It is instantiated straight from
|
||||
# its scene rather than spawned through NetWorld, so it is not part of the
|
||||
# replicated world at all — it exists only to be looked at, and must not be
|
||||
# grabbable or collidable.
|
||||
#
|
||||
# It gets no synchronizers either: NetReplication.attach only ever runs for
|
||||
# direct children of the content root, and this is parented under a plate.
|
||||
func _make_cosmetic(visual: Node3D) -> void:
|
||||
var net_pickable := visual.get_node_or_null("NetPickable")
|
||||
if net_pickable:
|
||||
# Detach and free it outright rather than queue_free(): this runs before
|
||||
var despawn_timer := visual.get_node_or_null("DespawningItem")
|
||||
if despawn_timer:
|
||||
# Detached and freed outright rather than queue_free()d: this runs before
|
||||
# `visual` is added to the tree, and a merely-queued node still enters the
|
||||
# tree with its parent and runs _ready() (which starts syncing and logging)
|
||||
# before the queued deletion lands at the end of the frame.
|
||||
visual.remove_child(net_pickable)
|
||||
net_pickable.free()
|
||||
# tree with its parent and runs a frame of _process before the queued
|
||||
# deletion lands at the end of the frame.
|
||||
visual.remove_child(despawn_timer)
|
||||
despawn_timer.free()
|
||||
if visual is RigidBody3D:
|
||||
visual.freeze = true
|
||||
# STATIC, not KINEMATIC: a kinematic body is still driven by the physics
|
||||
@@ -197,9 +196,6 @@ func _make_cosmetic(visual: Node3D) -> void:
|
||||
func _remove_from_physics(visual: Node3D) -> void:
|
||||
if visual is RigidBody3D:
|
||||
PhysicsServer3D.body_set_space((visual as RigidBody3D).get_rid(), RID())
|
||||
var despawning_item = visual.get_node_or_null("DespawningItem")
|
||||
if despawning_item:
|
||||
NetworkManager.despawn_item(despawning_item)
|
||||
|
||||
|
||||
#plate (Pickalbe)
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
[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="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="6_el51w"]
|
||||
[ext_resource type="Script" uid="uid://bwd0pe2udb5xo" path="res://Net/net_pickable.gd" id="20_netpk"]
|
||||
|
||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_kek77"]
|
||||
height = 0.0635376
|
||||
@@ -27,29 +26,11 @@ metadata/_custom_type_script = "uid://dvobm6vcfnqe8"
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_vlqg6"]
|
||||
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]
|
||||
collision_layer = 4
|
||||
collision_mask = 196615
|
||||
freeze_mode = 1
|
||||
script = ExtResource("1_nq5sa")
|
||||
second_hand_grab = 1
|
||||
|
||||
[node name="PlateController" type="Node" parent="." unique_id=1503264193]
|
||||
script = ExtResource("1_vjsmi")
|
||||
@@ -154,6 +135,3 @@ polygon = PackedVector2Array(-0.053057775, 0.2586278, 0.09469998, 0.40791017, 0.
|
||||
depth = 0.02
|
||||
material = SubResource("StandardMaterial3D_vlqg6")
|
||||
|
||||
[node name="NetPickable" type="MultiplayerSynchronizer" parent="." unique_id=1770751620]
|
||||
replication_config = SubResource("SceneReplicationConfig_np_plate")
|
||||
script = ExtResource("20_netpk")
|
||||
|
||||
@@ -13,8 +13,6 @@ extends Node
|
||||
## the cosmetic result via the setter below.
|
||||
@export var contained_ids: Array[String] = []: set = _set_contained_ids
|
||||
|
||||
func get_food_items() -> Array[FoodItem]:
|
||||
return container.contained_items
|
||||
|
||||
func _ready() -> void:
|
||||
if not dirty_node:
|
||||
@@ -32,11 +30,11 @@ func _process(_delta: float) -> void:
|
||||
|
||||
|
||||
func _set_contained_ids(value: Array[String]) -> void:
|
||||
# Only rebuild when the contents actually changed. This property is
|
||||
# replicated in ALWAYS mode, so the synchronizer assigns it every network
|
||||
# tick on every peer that doesn't own the plate — and refresh_visuals()
|
||||
# frees and re-instantiates a scene per item each time. That was thousands
|
||||
# of throwaway nodes per run (and a log line from each one's NetPickable).
|
||||
# Only rebuild when the contents actually changed. refresh_visuals() frees and
|
||||
# re-instantiates a scene per item, so a redundant call is thousands of
|
||||
# throwaway nodes over a session. NetReplication uses ON_CHANGE for state, so
|
||||
# the synchronizer should not be assigning this unless it really changed — but
|
||||
# a local write can still repeat a value, and this stays cheap either way.
|
||||
if contained_ids == value:
|
||||
return
|
||||
contained_ids = value
|
||||
|
||||
+3
-3
@@ -11,7 +11,7 @@ enum GameState {
|
||||
}
|
||||
static var game_state: GameState = GameState.RUNNING
|
||||
|
||||
|
||||
|
||||
static func _restart():
|
||||
print("restart Game")
|
||||
game_state = GameState.RUNNING
|
||||
@@ -27,15 +27,15 @@ static func get_random_meal() -> String:
|
||||
|
||||
|
||||
static func get_random_side() -> String:
|
||||
print("GameManager: get_random_side() ")
|
||||
if sides_in_play.size() == 0:
|
||||
push_error("GameManager: get_random_side() called but sides_in_play is empty")
|
||||
return ""
|
||||
var rand_index = randi() % sides_in_play.size()
|
||||
print("GameManager: get_random_side() list=%s returning %s" % [sides_in_play, sides_in_play[rand_index]])
|
||||
print("GameManager: get_random_side() returning ", sides_in_play[rand_index])
|
||||
return sides_in_play[rand_index]
|
||||
|
||||
|
||||
|
||||
static func game_over():
|
||||
print("Game Over")
|
||||
game_state = GameState.GAME_OVER
|
||||
|
||||
+3
-20
@@ -6,10 +6,9 @@
|
||||
[ext_resource type="Script" uid="uid://dvobm6vcfnqe8" path="res://addons/godot-xr-tools/hands/poses/hand_pose_settings.gd" id="4_wb51u"]
|
||||
[ext_resource type="PackedScene" uid="uid://c25yxb0vt53vc" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_left.tscn" id="5_wqyr2"]
|
||||
[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://bfj80jh13t6e5" path="res://prefabs/food_item.tscn" id="8_t9y3x"]
|
||||
[ext_resource type="PackedScene" uid="uid://b5ukku8i0hilb" path="res://prefabs/despawning_item.tscn" id="10_46e7r"]
|
||||
[ext_resource type="Script" uid="uid://bwd0pe2udb5xo" path="res://Net/net_pickable.gd" id="20_netpk"]
|
||||
[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://b5ukku8i0hilb" path="res://Prefabs/despawning_item.tscn" id="10_46e7r"]
|
||||
|
||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_vlqg6"]
|
||||
height = 0.10708985
|
||||
@@ -28,19 +27,7 @@ metadata/_custom_type_script = "uid://dvobm6vcfnqe8"
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_hoqox"]
|
||||
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")]
|
||||
second_hand_grab = 1
|
||||
|
||||
[node name="CollisionShape3D" parent="." index="0"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05776599, 0)
|
||||
@@ -87,8 +74,4 @@ type = 2
|
||||
|
||||
[node name="CombinableItem" parent="." index="5" unique_id=996936271 instance=ExtResource("7_wb51u")]
|
||||
|
||||
[node name="NetPickable" type="MultiplayerSynchronizer" parent="." index="6" unique_id=2086885420]
|
||||
replication_config = SubResource("SceneReplicationConfig_np_buns")
|
||||
script = ExtResource("20_netpk")
|
||||
|
||||
[node name="DespawningItem" parent="." index="7" unique_id=303090111 instance=ExtResource("10_46e7r")]
|
||||
|
||||
+3
-20
@@ -6,10 +6,9 @@
|
||||
[ext_resource type="Script" uid="uid://dvobm6vcfnqe8" path="res://addons/godot-xr-tools/hands/poses/hand_pose_settings.gd" id="4_mgacb"]
|
||||
[ext_resource type="PackedScene" uid="uid://ctw7nbntd5pcj" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_right.tscn" id="5_vw7i6"]
|
||||
[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://3lr2dhy62rhk" path="res://prefabs/combinable_item.tscn" id="8_wmrff"]
|
||||
[ext_resource type="PackedScene" uid="uid://b5ukku8i0hilb" path="res://prefabs/despawning_item.tscn" id="10_6ypk4"]
|
||||
[ext_resource type="Script" uid="uid://bwd0pe2udb5xo" path="res://Net/net_pickable.gd" id="20_netpk"]
|
||||
[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://b5ukku8i0hilb" path="res://Prefabs/despawning_item.tscn" id="10_6ypk4"]
|
||||
|
||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_gkni8"]
|
||||
height = 0.1
|
||||
@@ -34,19 +33,7 @@ script = ExtResource("4_mgacb")
|
||||
closed_pose = ExtResource("6_cp3eg")
|
||||
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")]
|
||||
second_hand_grab = 1
|
||||
|
||||
[node name="CollisionShape3D" parent="." index="0"]
|
||||
shape = SubResource("CylinderShape3D_gkni8")
|
||||
@@ -68,8 +55,4 @@ type = 2
|
||||
|
||||
[node name="CombinableItem" parent="." index="5" unique_id=996936271 instance=ExtResource("8_wmrff")]
|
||||
|
||||
[node name="NetPickable" type="MultiplayerSynchronizer" parent="." index="6" unique_id=1070495412]
|
||||
replication_config = SubResource("SceneReplicationConfig_np_charcoal")
|
||||
script = ExtResource("20_netpk")
|
||||
|
||||
[node name="DespawningItem" parent="." index="7" unique_id=303090111 instance=ExtResource("10_6ypk4")]
|
||||
|
||||
+3
-20
@@ -6,10 +6,9 @@
|
||||
[ext_resource type="Script" uid="uid://dvobm6vcfnqe8" path="res://addons/godot-xr-tools/hands/poses/hand_pose_settings.gd" id="4_hc3f7"]
|
||||
[ext_resource type="PackedScene" uid="uid://ctw7nbntd5pcj" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_right.tscn" id="5_a0a5b"]
|
||||
[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://b5ukku8i0hilb" path="res://prefabs/despawning_item.tscn" id="10_uygc5"]
|
||||
[ext_resource type="PackedScene" uid="uid://bfj80jh13t6e5" path="res://prefabs/food_item.tscn" id="11_vjw41"]
|
||||
[ext_resource type="Script" uid="uid://bwd0pe2udb5xo" path="res://Net/net_pickable.gd" id="20_netpk"]
|
||||
[ext_resource type="PackedScene" uid="uid://3lr2dhy62rhk" path="res://Prefabs/combinable_item.tscn" id="7_bdp75"]
|
||||
[ext_resource type="PackedScene" uid="uid://b5ukku8i0hilb" path="res://Prefabs/despawning_item.tscn" id="10_uygc5"]
|
||||
[ext_resource type="PackedScene" uid="uid://bfj80jh13t6e5" path="res://Prefabs/food_item.tscn" id="11_vjw41"]
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_m1xbq"]
|
||||
size = Vector3(0.1, 0.1, 0.1)
|
||||
@@ -31,19 +30,7 @@ script = ExtResource("4_hc3f7")
|
||||
closed_pose = ExtResource("6_bdp75")
|
||||
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")]
|
||||
second_hand_grab = 1
|
||||
|
||||
[node name="CollisionShape3D" parent="." index="0"]
|
||||
shape = SubResource("BoxShape3D_m1xbq")
|
||||
@@ -65,8 +52,4 @@ type = 1
|
||||
|
||||
[node name="CombinableItem" parent="." index="5" unique_id=996936271 instance=ExtResource("7_bdp75")]
|
||||
|
||||
[node name="NetPickable" type="MultiplayerSynchronizer" parent="." index="6" unique_id=1858332513]
|
||||
replication_config = SubResource("SceneReplicationConfig_np_cube")
|
||||
script = ExtResource("20_netpk")
|
||||
|
||||
[node name="DespawningItem" parent="." index="7" unique_id=303090111 instance=ExtResource("10_uygc5")]
|
||||
|
||||
+3
-20
@@ -1,15 +1,14 @@
|
||||
[gd_scene format=3 uid="uid://dpot5qie20vf6"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://c8l60rnugru40" path="res://addons/godot-xr-tools/objects/pickable.tscn" id="1_fco8w"]
|
||||
[ext_resource type="Texture2D" uid="uid://cexxfyw03hr81" path="res://textures/1.png" id="2_r8jvx"]
|
||||
[ext_resource type="Texture2D" uid="uid://cexxfyw03hr81" path="res://Textures/1.png" id="2_r8jvx"]
|
||||
[ext_resource type="PackedScene" uid="uid://c25yxb0vt53vc" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_left.tscn" id="3_qixic"]
|
||||
[ext_resource type="Animation" uid="uid://db62hs5s4n2b3" path="res://addons/godot-xr-tools/hands/animations/left/Grip 4.res" id="4_bw8vh"]
|
||||
[ext_resource type="Script" uid="uid://dvobm6vcfnqe8" path="res://addons/godot-xr-tools/hands/poses/hand_pose_settings.gd" id="5_w8sii"]
|
||||
[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="PackedScene" uid="uid://b5ukku8i0hilb" path="res://prefabs/despawning_item.tscn" id="10_l4hsd"]
|
||||
[ext_resource type="PackedScene" uid="uid://bfj80jh13t6e5" path="res://prefabs/food_item.tscn" id="10_yxtxs"]
|
||||
[ext_resource type="Script" uid="uid://bwd0pe2udb5xo" path="res://Net/net_pickable.gd" id="20_netpk"]
|
||||
[ext_resource type="PackedScene" uid="uid://b5ukku8i0hilb" path="res://Prefabs/despawning_item.tscn" id="10_l4hsd"]
|
||||
[ext_resource type="PackedScene" uid="uid://bfj80jh13t6e5" path="res://Prefabs/food_item.tscn" id="10_yxtxs"]
|
||||
|
||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_fco8w"]
|
||||
height = 0.0338974
|
||||
@@ -28,19 +27,7 @@ script = ExtResource("5_w8sii")
|
||||
closed_pose = ExtResource("7_wqxjj")
|
||||
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")]
|
||||
second_hand_grab = 1
|
||||
|
||||
[node name="CollisionShape3D" parent="." index="0"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.00077831745, 0)
|
||||
@@ -64,8 +51,4 @@ hand_pose = SubResource("Resource_qyiot")
|
||||
id = "raw_burger"
|
||||
type = 2
|
||||
|
||||
[node name="NetPickable" type="MultiplayerSynchronizer" parent="." index="5" unique_id=1382968688]
|
||||
replication_config = SubResource("SceneReplicationConfig_np_burger")
|
||||
script = ExtResource("20_netpk")
|
||||
|
||||
[node name="DespawningItem" parent="." index="6" unique_id=303090111 instance=ExtResource("10_l4hsd")]
|
||||
|
||||
@@ -6,10 +6,9 @@
|
||||
[ext_resource type="Script" uid="uid://dvobm6vcfnqe8" path="res://addons/godot-xr-tools/hands/poses/hand_pose_settings.gd" id="5_ychvb"]
|
||||
[ext_resource type="PackedScene" uid="uid://ctw7nbntd5pcj" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_right.tscn" id="6_etdv2"]
|
||||
[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://b5ukku8i0hilb" path="res://prefabs/despawning_item.tscn" id="10_6p8pj"]
|
||||
[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="Script" uid="uid://bwd0pe2udb5xo" path="res://Net/net_pickable.gd" id="20_netpk"]
|
||||
[ext_resource type="PackedScene" uid="uid://b5ukku8i0hilb" path="res://Prefabs/despawning_item.tscn" id="10_6p8pj"]
|
||||
[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"]
|
||||
|
||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_fco8w"]
|
||||
height = 0.04777527
|
||||
@@ -28,19 +27,7 @@ script = ExtResource("5_ychvb")
|
||||
closed_pose = ExtResource("7_rl64h")
|
||||
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")]
|
||||
second_hand_grab = 1
|
||||
|
||||
[node name="CollisionShape3D" parent="." index="0"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.0072289705, 0)
|
||||
@@ -66,8 +53,4 @@ type = 2
|
||||
|
||||
[node name="CombinableItem" parent="." index="5" unique_id=996936271 instance=ExtResource("10_ut7mg")]
|
||||
|
||||
[node name="NetPickable" type="MultiplayerSynchronizer" parent="." index="6" unique_id=925955029]
|
||||
replication_config = SubResource("SceneReplicationConfig_np_cookedburger")
|
||||
script = ExtResource("20_netpk")
|
||||
|
||||
[node name="DespawningItem" parent="." index="7" unique_id=303090111 instance=ExtResource("10_6p8pj")]
|
||||
|
||||
+2
-19
@@ -6,9 +6,8 @@
|
||||
[ext_resource type="Script" uid="uid://dvobm6vcfnqe8" path="res://addons/godot-xr-tools/hands/poses/hand_pose_settings.gd" id="4_cljtj"]
|
||||
[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="PackedScene" uid="uid://bfj80jh13t6e5" path="res://prefabs/food_item.tscn" id="7_yy3y8"]
|
||||
[ext_resource type="PackedScene" uid="uid://b5ukku8i0hilb" path="res://prefabs/despawning_item.tscn" id="9_ej8jm"]
|
||||
[ext_resource type="Script" uid="uid://bwd0pe2udb5xo" path="res://Net/net_pickable.gd" id="20_netpk"]
|
||||
[ext_resource type="PackedScene" uid="uid://bfj80jh13t6e5" path="res://Prefabs/food_item.tscn" id="7_yy3y8"]
|
||||
[ext_resource type="PackedScene" uid="uid://b5ukku8i0hilb" path="res://Prefabs/despawning_item.tscn" id="9_ej8jm"]
|
||||
|
||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_fco8w"]
|
||||
height = 0.10392761
|
||||
@@ -30,20 +29,8 @@ albedo_color = Color(0.6784191, 0.54546416, 0.016760282, 1)
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_6l01i"]
|
||||
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")]
|
||||
gravity_scale = 0.04
|
||||
second_hand_grab = 1
|
||||
|
||||
[node name="CollisionShape3D" parent="." index="0"]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.02, 0.02, 0)
|
||||
@@ -96,8 +83,4 @@ id = "hamburger"
|
||||
type = 0
|
||||
sell_value = 4
|
||||
|
||||
[node name="NetPickable" type="MultiplayerSynchronizer" parent="." index="6" unique_id=56164120]
|
||||
replication_config = SubResource("SceneReplicationConfig_np_hamburger")
|
||||
script = ExtResource("20_netpk")
|
||||
|
||||
[node name="DespawningItem" parent="." index="7" unique_id=303090111 instance=ExtResource("9_ej8jm")]
|
||||
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
extends Node
|
||||
|
||||
## The whole interaction layer: two RPCs, for every object in the game.
|
||||
##
|
||||
## Registered as the "NetGrab" autoload so its RPCs resolve to the same node path
|
||||
## on every peer.
|
||||
##
|
||||
## A hand picks something up locally the instant the player grabs it — no round
|
||||
## trip, no waiting — and tells the server. The server answers by moving the
|
||||
## object's NetXform to that peer, which is what actually makes the prediction
|
||||
## work: a MultiplayerSynchronizer never applies inbound state on the peer that
|
||||
## owns it, so from that moment the holder's own hand drives the object and the
|
||||
## server's copy follows instead of fighting it. Releasing hands NetXform back,
|
||||
## and the server decides where the object really ends up.
|
||||
##
|
||||
## Note what is NOT here. There is no per-object networking component, no
|
||||
## replicated "who is holding this" field to keep in step with the authority, and
|
||||
## no grant/reject/force-release negotiation. Authority is the single source of
|
||||
## truth for who simulates an object, and NetWorld.apply_physics_role reads it.
|
||||
|
||||
|
||||
## Starts watching a pickable. Called by NetWorld for every replicated object, so
|
||||
## no scene has to include a networking node to take part.
|
||||
func watch(item: XRToolsPickable) -> void:
|
||||
if item.picked_up.is_connected(_on_picked_up):
|
||||
return
|
||||
item.picked_up.connect(_on_picked_up)
|
||||
item.dropped.connect(_on_dropped)
|
||||
|
||||
|
||||
func _on_picked_up(item: Node3D) -> void:
|
||||
if not NetworkManager.is_online():
|
||||
return
|
||||
# A station snap zone grabbing something is the server placing it, not a
|
||||
# player taking it, so it must not move authority anywhere.
|
||||
if not (item.get_picked_up_by() is XRToolsFunctionPickup):
|
||||
return
|
||||
NetworkManager.log_line("%s grabbed by hand, claiming it" % item.name)
|
||||
if NetworkManager.is_server():
|
||||
_claim(item.get_path(), 1)
|
||||
else:
|
||||
# rpc_id() to ourselves is rejected by Godot, which is why the server
|
||||
# branch calls straight through instead.
|
||||
_request_grab.rpc_id(1, item.get_path())
|
||||
|
||||
|
||||
func _on_dropped(item: Node3D) -> void:
|
||||
if not NetworkManager.is_online():
|
||||
return
|
||||
# Only the peer that was actually driving the object reports a release. A drop
|
||||
# caused by losing authority (see NetWorld.apply_physics_role) must not be
|
||||
# echoed back as if the player had let go.
|
||||
var xform := item.get_node_or_null(NetReplication.XFORM_NAME)
|
||||
if not xform or not xform.is_multiplayer_authority():
|
||||
return
|
||||
# Our own final transform travels with the release. We were driving the object
|
||||
# right up to this moment and our position updates ride the synchronizer's
|
||||
# separate, unordered channel, which this reliable message routinely
|
||||
# overtakes — so the server's copy can still be back where the object was
|
||||
# before we carried it away. Deciding the snap from that stale position
|
||||
# teleports the object straight back into the station it was just taken from.
|
||||
if NetworkManager.is_server():
|
||||
_settle(item.get_path(), item.global_transform, item.linear_velocity, item.angular_velocity)
|
||||
else:
|
||||
_request_release.rpc_id(
|
||||
1, item.get_path(), item.global_transform,
|
||||
item.linear_velocity, item.angular_velocity
|
||||
)
|
||||
|
||||
|
||||
# --- server side -----------------------------------------------------------
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _request_grab(item_path: NodePath) -> void:
|
||||
if not NetworkManager.is_server():
|
||||
return
|
||||
_claim(item_path, multiplayer.get_remote_sender_id())
|
||||
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _request_release(item_path: NodePath, xform: Transform3D, lin: Vector3, ang: Vector3) -> void:
|
||||
if not NetworkManager.is_server():
|
||||
return
|
||||
_settle(item_path, xform, lin, ang)
|
||||
|
||||
|
||||
func _claim(item_path: NodePath, peer: int) -> void:
|
||||
var item := get_node_or_null(item_path)
|
||||
if not item:
|
||||
return
|
||||
NetworkManager.log_line("grant %s to peer %d" % [item.name, peer])
|
||||
# Order matters: hand the object over first, so the station's zone has already
|
||||
# stopped owning it by the time it is told to let go.
|
||||
_set_xform_authority.rpc(item_path, peer)
|
||||
NetStations.release_from_zones(self, item)
|
||||
|
||||
|
||||
func _settle(item_path: NodePath, xform: Transform3D, lin: Vector3, ang: Vector3) -> void:
|
||||
var item := get_node_or_null(item_path)
|
||||
if not item:
|
||||
return
|
||||
NetworkManager.log_line("release %s" % item.name)
|
||||
if item is Node3D:
|
||||
item.global_transform = xform
|
||||
_set_xform_authority.rpc(item_path, 1)
|
||||
if item is RigidBody3D:
|
||||
item.linear_velocity = lin
|
||||
item.angular_velocity = ang
|
||||
NetStations.try_snap.call_deferred(self, item)
|
||||
|
||||
|
||||
## set_multiplayer_authority is a local call, so every peer has to run it for
|
||||
## them to agree on who is driving the object.
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func _set_xform_authority(item_path: NodePath, peer: int) -> void:
|
||||
var item := get_node_or_null(item_path)
|
||||
if not item:
|
||||
return
|
||||
var xform := item.get_node_or_null(NetReplication.XFORM_NAME)
|
||||
if not xform:
|
||||
return
|
||||
xform.set_multiplayer_authority(peer)
|
||||
NetworkManager.apply_physics_role(item)
|
||||
|
||||
|
||||
## Hands everything a departing peer was holding back to the server. Without
|
||||
## this, anything still in their hand when they dropped out would be frozen
|
||||
## forever on every remaining peer, waiting on an authority that has gone.
|
||||
func reclaim_from(peer: int) -> void:
|
||||
if not NetworkManager.is_server():
|
||||
return
|
||||
for item in NetworkManager.replicated_objects():
|
||||
var xform := item.get_node_or_null(NetReplication.XFORM_NAME)
|
||||
if xform and xform.get_multiplayer_authority() == peer:
|
||||
NetworkManager.log_line("reclaiming %s from departed peer %d" % [item.name, peer])
|
||||
_settle(item.get_path(), item.global_transform, Vector3.ZERO, Vector3.ZERO)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bv82nioivbmnx
|
||||
@@ -1,209 +0,0 @@
|
||||
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
|
||||
|
||||
# This item's own baked freeze_mode (e.g. plate.tscn bakes KINEMATIC, not the
|
||||
# RigidBody3D default of STATIC) — captured once so it can be restored when
|
||||
# this peer regains ownership, instead of getting stuck on whatever
|
||||
# apply_held_state() last forced it to while non-authority.
|
||||
var _original_freeze_mode: int
|
||||
|
||||
# Same idea for the pickable's authored `enabled` flag, which the non-authority
|
||||
# branch of apply_held_state() clears while someone else is holding the item.
|
||||
var _original_enabled: bool
|
||||
|
||||
# Whether the "our own hand still holds this" guard has already been logged for
|
||||
# the current grab. apply_held_state() runs every network tick, so without this
|
||||
# the guard message repeats for as long as you hold the item.
|
||||
var _grab_race_logged := false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_pickable = get_parent() as XRToolsPickable
|
||||
if not _pickable:
|
||||
push_error("NetPickable must be a child of an XRToolsPickable")
|
||||
return
|
||||
_original_freeze_mode = _pickable.freeze_mode
|
||||
_original_enabled = _pickable.enabled
|
||||
_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:
|
||||
var old := net_held_by
|
||||
net_held_by = value
|
||||
if old != value and NetworkManager.is_online():
|
||||
print("%s net_held_by: %d -> %d (local state: %s, authority=%d)" % [
|
||||
_pickable.name, old, value, _holder_desc(), get_multiplayer_authority()
|
||||
])
|
||||
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.
|
||||
##
|
||||
## IMPORTANT: this runs on every network tick, not just on a real change.
|
||||
## net_held_by is replicated in ALWAYS mode, so the synchronizer assigns it every
|
||||
## tick on non-authority peers — unchanged value included — and that assignment
|
||||
## lands in _set_net_held_by(), which calls this. So every branch here has to be
|
||||
## idempotent and silent when there is nothing to do: otherwise each item logs a
|
||||
## line and rewrites four physics properties every tick on every peer that
|
||||
## doesn't own it.
|
||||
func apply_held_state() -> void:
|
||||
if not _pickable:
|
||||
return
|
||||
if not NetworkManager.is_online() or is_multiplayer_authority():
|
||||
_grab_race_logged = false
|
||||
# We own this item's simulation (offline, loose+server, or currently
|
||||
# holding it). If it's not actively in our own hand right now, make
|
||||
# sure it isn't still left frozen/collision-less from a previous
|
||||
# non-authority period (e.g. right after regaining authority when a
|
||||
# client released it) — while actually held, XRToolsPickable's own
|
||||
# pick_up()/let_go() already manage these fields, so leave those be.
|
||||
if not _pickable.is_picked_up():
|
||||
var changed := _pickable.freeze_mode != _original_freeze_mode \
|
||||
or _pickable.collision_mask != _pickable.original_collision_mask
|
||||
if changed:
|
||||
if NetworkManager.is_online():
|
||||
print(
|
||||
"%s: reclaiming ownership, restoring freeze_mode %d->%d collision_mask %d->%d" % [
|
||||
_pickable.name, _pickable.freeze_mode, _original_freeze_mode,
|
||||
_pickable.collision_mask, _pickable.original_collision_mask
|
||||
]
|
||||
)
|
||||
_pickable.freeze_mode = _original_freeze_mode
|
||||
_pickable.collision_mask = _pickable.original_collision_mask
|
||||
# Unlike freeze/collision (which XRToolsPickable manages itself while
|
||||
# held), `enabled` is only ever written by the non-authority branch
|
||||
# below, so it must be restored here or it stays false forever: once a
|
||||
# client grabbed this item, every other peer set enabled=false, and
|
||||
# regaining authority left it that way. On the server that silently
|
||||
# broke everything downstream — hands couldn't pick the item up again,
|
||||
# and a station snap zone would "snap" it (emitting has_picked_up, so
|
||||
# e.g. a plate still got marked dirty) while pick_up() bailed out on
|
||||
# the disabled item, leaving the zone holding an item with no grab
|
||||
# driver that then fell out of the station.
|
||||
if _pickable.enabled != _original_enabled:
|
||||
if NetworkManager.is_online():
|
||||
print("%s: reclaiming ownership, restoring enabled %s->%s" % [
|
||||
_pickable.name, _pickable.enabled, _original_enabled
|
||||
])
|
||||
_pickable.enabled = _original_enabled
|
||||
return
|
||||
# A net_held_by/position sync update can race ahead of the
|
||||
# authority-handoff RPC that's about to confirm a grab we just made
|
||||
# optimistically (they travel on different channels with no ordering
|
||||
# guarantee). Don't let a stale sync value yank an item out of our own
|
||||
# hand mid-grab — only an explicit force_release_item rejection, or
|
||||
# actually losing authority for real, should end a grab we initiated.
|
||||
if _pickable.is_picked_up() and _pickable.get_picked_up_by() is XRToolsFunctionPickup:
|
||||
# Log once per grab, not once per tick.
|
||||
if NetworkManager.is_online() and not _grab_race_logged:
|
||||
_grab_race_logged = true
|
||||
print(
|
||||
"%s: ignoring non-authority sync (net_held_by=%d) — still actively held by our own hand (grab-race guard)" % [
|
||||
_pickable.name, net_held_by
|
||||
]
|
||||
)
|
||||
return
|
||||
_grab_race_logged = false
|
||||
# Someone else owns it: stop simulating locally, just follow the sync.
|
||||
if _pickable.is_picked_up():
|
||||
if NetworkManager.is_online():
|
||||
print(
|
||||
"%s: was held by %s on this peer, but authority now says peer %d owns it — force-dropping" % [
|
||||
_pickable.name, _holder_desc(), net_held_by
|
||||
]
|
||||
)
|
||||
_pickable.drop()
|
||||
# Bail out when we're already in the follow-the-sync state. Without this the
|
||||
# writes below (and the line logged with them) repeated every tick for every
|
||||
# item on every non-authority peer — 90% of the log, plus four redundant
|
||||
# physics-property writes per item per tick. The comparison also means we
|
||||
# still re-apply if something else perturbs the state (e.g. let_go()
|
||||
# restoring the collision mask after a force-drop).
|
||||
var want_enabled := (net_held_by == 0)
|
||||
if _pickable.freeze \
|
||||
and _pickable.freeze_mode == RigidBody3D.FREEZE_MODE_KINEMATIC \
|
||||
and _pickable.collision_mask == 0 \
|
||||
and _pickable.enabled == want_enabled:
|
||||
return
|
||||
if NetworkManager.is_online():
|
||||
print("%s: freezing (non-authority, owner=peer %d)" % [_pickable.name, net_held_by])
|
||||
_pickable.freeze = true
|
||||
_pickable.freeze_mode = RigidBody3D.FREEZE_MODE_KINEMATIC
|
||||
_pickable.collision_mask = 0
|
||||
_pickable.enabled = want_enabled
|
||||
|
||||
|
||||
## 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):
|
||||
# e.g. a station snap zone grabbed it (server-side auto-snap, or the
|
||||
# addon's own "grab out of a snap zone" shortcut mid-cascade) — not a
|
||||
# player-initiated hand grab, so no authority request from here.
|
||||
if NetworkManager.is_online():
|
||||
print("%s picked up by %s (not a hand) — no authority request" % [_pickable.name, _holder_desc()])
|
||||
return
|
||||
if NetworkManager.is_online():
|
||||
print("%s grabbed by hand (authority was peer %d), requesting authority" % [_pickable.name, net_held_by])
|
||||
NetworkManager.request_item_authority_from(_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():
|
||||
if NetworkManager.is_online():
|
||||
print("%s dropped locally, but we aren't its authority (peer %d is) — not reporting" % [_pickable.name, net_held_by])
|
||||
return
|
||||
if NetworkManager.is_online():
|
||||
print("%s dropped, reporting release to server (lin=%s ang=%s)" % [
|
||||
_pickable.name, _pickable.linear_velocity, _pickable.angular_velocity
|
||||
])
|
||||
# Send our own final transform too: we were the authority until now, and the
|
||||
# server's copy may not have received our last position sync yet.
|
||||
NetworkManager.release_item_authority_from(
|
||||
_pickable.get_path(), _pickable.linear_velocity, _pickable.angular_velocity,
|
||||
_pickable.global_transform
|
||||
)
|
||||
|
||||
|
||||
## Human-readable description of what's currently holding this item on THIS
|
||||
## peer, for diagnosing desyncs between a hand's own "what am I holding"
|
||||
## bookkeeping and the item's actual grab state (see the snap-zone-grab race
|
||||
## in NetworkManager._try_snap_into_station for a real example).
|
||||
func _holder_desc() -> String:
|
||||
if not _pickable or not _pickable.is_picked_up():
|
||||
return "loose"
|
||||
var by := _pickable.get_picked_up_by()
|
||||
if not by:
|
||||
return "held(no grabber?)"
|
||||
if by is XRToolsFunctionPickup:
|
||||
return "hand(%s)" % by.get_path()
|
||||
if by is XRToolsSnapZone:
|
||||
var station := by.get_parent()
|
||||
return "zone(%s)" % (station.name if station else str(by.get_path()))
|
||||
return "other(%s: %s)" % [by.get_class(), by.get_path()]
|
||||
@@ -1 +0,0 @@
|
||||
uid://bwd0pe2udb5xo
|
||||
@@ -0,0 +1,188 @@
|
||||
extends Node
|
||||
class_name NetReplication
|
||||
|
||||
## Builds MultiplayerSynchronizers for any node by convention, so no scene needs
|
||||
## a hand-authored replication config.
|
||||
##
|
||||
## Every replicated node gets TWO synchronizers, both generated here:
|
||||
##
|
||||
## NetSync script state (is_dirty, contained_ids, time_cooked, ...).
|
||||
## Always owned by the server. Gameplay outcomes are the server's to
|
||||
## decide, so this never changes hands.
|
||||
## NetXform position + quaternion. Owned by the server while the object is
|
||||
## loose, and handed to a peer for as long as that peer holds it.
|
||||
##
|
||||
## Splitting them is what makes client-side grab prediction work. A
|
||||
## MultiplayerSynchronizer never applies inbound state on the peer that owns it,
|
||||
## so giving the holder NetXform means their own hand drives the object with no
|
||||
## round trip and no fight with the server's copy — while is_dirty and friends
|
||||
## keep flowing one way, server to client, exactly as before.
|
||||
##
|
||||
## The obvious alternative, per-peer visibility (set_visibility_for), does NOT
|
||||
## work: MultiplayerSpawner also uses synchronizer visibility to decide whether
|
||||
## a node should exist on a peer, so hiding an object from its holder despawns
|
||||
## it in their hand. Verified in test/spike/.
|
||||
##
|
||||
## The convention for what gets replicated:
|
||||
## * a Node3D replicates `position` and `quaternion` (never `scale` — nothing
|
||||
## in this game animates scale as gameplay state)
|
||||
## * every script variable, on the node and on any scripted descendant, whose
|
||||
## name does not start with "_" and whose declared type is serialisable
|
||||
##
|
||||
## Underscore-prefixed vars are the opt-out, and they already mark exactly the
|
||||
## state that must not be replicated: @onready node references, cached lookups,
|
||||
## per-frame bookkeeping.
|
||||
|
||||
## SceneReplicationConfig's replication modes. The enum is not exposed under a
|
||||
## friendly name in GDScript, so they are spelled out here.
|
||||
const MODE_ALWAYS := 1
|
||||
const MODE_ON_CHANGE := 2
|
||||
|
||||
const SYNC_NAME := "NetSync"
|
||||
const XFORM_NAME := "NetXform"
|
||||
|
||||
## Types that survive a network round trip. Object/Callable/Signal/RID cannot be
|
||||
## serialised, and are exactly what @onready and cached references hold.
|
||||
const SYNCABLE_TYPES := [
|
||||
TYPE_BOOL, TYPE_INT, TYPE_FLOAT, TYPE_STRING, TYPE_STRING_NAME,
|
||||
TYPE_VECTOR2, TYPE_VECTOR3, TYPE_QUATERNION, TYPE_TRANSFORM3D,
|
||||
TYPE_COLOR, TYPE_PACKED_STRING_ARRAY, TYPE_ARRAY,
|
||||
]
|
||||
|
||||
|
||||
## Adds both synchronizers to `node`, unless they are already there. Idempotent.
|
||||
##
|
||||
## Must run on EVERY peer, not just the authority: the client's copy is built by
|
||||
## MultiplayerSpawner straight from the .tscn, so it only has synchronizers if
|
||||
## something puts them there. NetWorld calls this from the content root's
|
||||
## child_entered_tree on both sides — early enough that they enter the tree
|
||||
## inside the spawner's own add_child(), which is what lets them pick up the
|
||||
## spawn payload (verified in test/spike/: a late joiner receives position and
|
||||
## script state from a synchronizer that exists only at runtime).
|
||||
static func attach(node: Node) -> void:
|
||||
if node is Node3D and not node.has_node(XFORM_NAME):
|
||||
_add_sync(node, XFORM_NAME, _transform_config())
|
||||
if not node.has_node(SYNC_NAME):
|
||||
_add_sync(node, SYNC_NAME, state_config(node))
|
||||
|
||||
|
||||
static func _add_sync(node: Node, sync_name: String, config: SceneReplicationConfig) -> void:
|
||||
var sync := MultiplayerSynchronizer.new()
|
||||
sync.name = sync_name
|
||||
sync.replication_config = config
|
||||
# Sync every network tick. ON_CHANGE properties are only sent when they
|
||||
# actually change regardless of this interval.
|
||||
sync.replication_interval = 0.0
|
||||
# Inherit the node's authority rather than defaulting to the server. Items are
|
||||
# server-owned and this changes nothing for them, but a player avatar sets its
|
||||
# authority to the peer it belongs to in _enter_tree — which has already run by
|
||||
# the time we get here — and its synchronizer has to agree, or the owning peer
|
||||
# would be receiving its own head and hands back from the server.
|
||||
sync.set_multiplayer_authority(node.get_multiplayer_authority())
|
||||
node.add_child(sync)
|
||||
|
||||
|
||||
## Transform-only config. ALWAYS rather than ON_CHANGE: a carried or simulated
|
||||
## object changes every tick anyway, so ON_CHANGE would only add a comparison
|
||||
## per property per tick. spawn = true so a replicated object arrives already in
|
||||
## the right place instead of sitting at the origin for a frame.
|
||||
static func _transform_config() -> SceneReplicationConfig:
|
||||
var config := SceneReplicationConfig.new()
|
||||
_add(config, ".:position", true, MODE_ALWAYS)
|
||||
_add(config, ".:quaternion", true, MODE_ALWAYS)
|
||||
return config
|
||||
|
||||
|
||||
## Script-state config for `node` and its scripted descendants. Public so the
|
||||
## tests can inspect what the convention picked up without spinning up a session.
|
||||
##
|
||||
## ON_CHANGE, not ALWAYS: the value is then only sent — and, crucially, only
|
||||
## ASSIGNED on the receiving peer — when it actually changes. Under ALWAYS every
|
||||
## replicated setter becomes a per-tick hot path, which is how this project
|
||||
## previously ended up rebuilding every plate's visuals 60 times a second.
|
||||
static func state_config(node: Node) -> SceneReplicationConfig:
|
||||
var config := SceneReplicationConfig.new()
|
||||
for path in _script_var_paths(node, node):
|
||||
_add(config, path, true, MODE_ON_CHANGE)
|
||||
return config
|
||||
|
||||
|
||||
static func _add(config: SceneReplicationConfig, path: String, spawn: bool, mode: int) -> void:
|
||||
var np := NodePath(path)
|
||||
config.add_property(np)
|
||||
config.property_set_spawn(np, spawn)
|
||||
config.property_set_replication_mode(np, mode)
|
||||
|
||||
|
||||
## Whether a script comes from a third-party addon rather than this game.
|
||||
##
|
||||
## Addon components are local plumbing, and replicating their configuration is
|
||||
## not merely wasteful — it is actively wrong. godot-xr-tools' snap zones and
|
||||
## pickables both expose a public `enabled`, which is precisely the flag each
|
||||
## peer has to set for ITSELF: a client disables its stations' snap zones because
|
||||
## placement is the server's decision, and disables a pickable another player is
|
||||
## holding. Replicating those meant the server helpfully sent `enabled = true`
|
||||
## straight back over every client's gate, so stations on clients went on
|
||||
## grabbing objects out of the local player's hands.
|
||||
##
|
||||
## Their values are authored in the scene file and therefore already identical on
|
||||
## every peer, so nothing is lost by leaving them alone.
|
||||
static func _is_addon(script: Script) -> bool:
|
||||
return script.resource_path.begins_with("res://addons/")
|
||||
|
||||
|
||||
## Whether a typed array holds something that can cross the wire.
|
||||
##
|
||||
## An array of nodes cannot, and this game has several: ItemContainer's
|
||||
## meal_positions/side_positions are Array[Node3D], and contained_items is
|
||||
## Array[FoodItem]. Replicating one would try to serialise live node references.
|
||||
##
|
||||
## GDScript reports the element type three different ways, so all three are
|
||||
## handled here (verified against the real scripts):
|
||||
## Array[String] hint 23, hint_string "4:" -> element type 4
|
||||
## Array[Node3D] hint 23, hint_string "24/34:Node3D" -> element type 24
|
||||
## Array[FoodItem] hint 31, hint_string "FoodItem" -> a class name
|
||||
## An untyped Array reports an empty hint_string and is excluded too: it can hold
|
||||
## anything, including nodes, so there is no safe answer.
|
||||
static func _is_syncable_array(prop: Dictionary) -> bool:
|
||||
var hint_string := str(prop["hint_string"])
|
||||
if hint_string.is_empty():
|
||||
return false
|
||||
var head := hint_string.split(":")[0].split("/")[0]
|
||||
if not head.is_valid_int():
|
||||
# A bare class name, e.g. "FoodItem".
|
||||
return false
|
||||
return SYNCABLE_TYPES.has(head.to_int()) and head.to_int() != TYPE_ARRAY
|
||||
|
||||
|
||||
## Every "<relative path>:<var>" on `node` and its scripted descendants.
|
||||
## Descends through children but stops at anything carrying its own synchronizer
|
||||
## — that subtree replicates itself and must not be replicated twice.
|
||||
static func _script_var_paths(root: Node, node: Node) -> Array[String]:
|
||||
var paths: Array[String] = []
|
||||
var prefix: String = "." if node == root else str(root.get_path_to(node))
|
||||
var script: Script = node.get_script() as Script
|
||||
# Note the descent below still happens for an addon-scripted node — only its
|
||||
# own properties are skipped. plate.tscn's root is godot-xr-tools' pickable.gd
|
||||
# and its PlateController child is where the game state actually lives.
|
||||
if script and _is_addon(script):
|
||||
script = null
|
||||
if script:
|
||||
for prop in script.get_script_property_list():
|
||||
var prop_name := str(prop["name"])
|
||||
if prop_name.begins_with("_"):
|
||||
continue
|
||||
if not (prop["usage"] & PROPERTY_USAGE_SCRIPT_VARIABLE):
|
||||
continue
|
||||
if not SYNCABLE_TYPES.has(prop["type"]):
|
||||
continue
|
||||
if prop["type"] == TYPE_ARRAY and not _is_syncable_array(prop):
|
||||
continue
|
||||
paths.append("%s:%s" % [prefix, prop_name])
|
||||
for child in node.get_children():
|
||||
if child is MultiplayerSynchronizer or child is MultiplayerSpawner:
|
||||
continue
|
||||
if child.has_node(SYNC_NAME) or child.has_node(XFORM_NAME):
|
||||
continue
|
||||
paths.append_array(_script_var_paths(root, child))
|
||||
return paths
|
||||
@@ -0,0 +1 @@
|
||||
uid://bmuqb0yywjfyw
|
||||
@@ -0,0 +1,64 @@
|
||||
extends Node
|
||||
class_name NetStations
|
||||
|
||||
## Server-side placement rules for stations. Generic over the "station" group —
|
||||
## there is nothing per-station here, and nothing any individual station scene
|
||||
## has to opt into.
|
||||
##
|
||||
## Placement is a server decision for the same reason cooking is: every peer runs
|
||||
## its own copy of a snap zone, so letting each decide independently means the
|
||||
## same object gets grabbed in two places at once.
|
||||
|
||||
|
||||
## Every snap zone belonging to a station. Some stations (Table) have more than
|
||||
## one, so this is not a one-per-station lookup.
|
||||
static func zones(context: Node) -> Array:
|
||||
var found := []
|
||||
for station in context.get_tree().get_nodes_in_group("station"):
|
||||
for child in station.get_children():
|
||||
if child is XRToolsSnapZone:
|
||||
found.append(child)
|
||||
return found
|
||||
|
||||
|
||||
## Frees an object from whatever station is currently holding it, because
|
||||
## someone has just taken it by hand.
|
||||
static func release_from_zones(context: Node, item: Node) -> void:
|
||||
for zone in zones(context):
|
||||
if zone.picked_up_object == item:
|
||||
zone.drop_object()
|
||||
# Make the zone forget it as well. These zones are snap_mode=RANGE, so
|
||||
# every frame they re-grab anything still listed in their grab area — and
|
||||
# Jolt does NOT emit body_exited when let_go() switches the object's
|
||||
# collision layer back out of the zone's mask, so the entry never clears
|
||||
# on its own. The station then snatches the object straight back off the
|
||||
# player who just took it and teleports it home. Bringing it near again
|
||||
# re-adds it properly, and releasing next to a station is handled
|
||||
# explicitly by try_snap below.
|
||||
if zone._object_in_grab_area.has(item):
|
||||
zone._object_in_grab_area.erase(item)
|
||||
|
||||
|
||||
## Snaps a just-released object into the nearest empty station zone in range.
|
||||
##
|
||||
## Call this DEFERRED. XRToolsFunctionPickup's own "grab an object out of a snap
|
||||
## zone" path calls zone.drop_object() before it calls pick_up() on the hand's
|
||||
## behalf, and drop_object() synchronously fires the `dropped` signal that lands
|
||||
## here. Running inline would immediately re-snap the object into the very zone
|
||||
## it is still physically inside, stealing it before the hand's own pick_up()
|
||||
## later in the same call stack ever runs — leaving the hand pointing at an
|
||||
## object whose grab driver belongs to the zone, which crashes on the next
|
||||
## controller press. The is_picked_up() check below is the second guard, for the
|
||||
## case where something grabs it for real before this runs.
|
||||
static func try_snap(context: Node, item: Node) -> void:
|
||||
if not (item is Node3D) or not is_instance_valid(item):
|
||||
return
|
||||
if item.has_method("is_picked_up") and item.is_picked_up():
|
||||
return
|
||||
for zone in zones(context):
|
||||
if is_instance_valid(zone.picked_up_object):
|
||||
continue
|
||||
if zone.global_position.distance_to(item.global_position) <= zone.grab_distance:
|
||||
NetworkManager.log_line("snapped %s into %s" % [item.name, zone.get_parent().name])
|
||||
zone.pick_up_object(item)
|
||||
return
|
||||
@@ -0,0 +1 @@
|
||||
uid://cgp15pxa7ui1r
|
||||
@@ -0,0 +1,251 @@
|
||||
extends Node
|
||||
class_name NetWorld
|
||||
|
||||
## Owns the replicated contents of the world: what exists, where it lives, and
|
||||
## which peer is allowed to simulate it.
|
||||
##
|
||||
## Everything here runs on Godot's stock MultiplayerSpawner in its DEFAULT mode.
|
||||
## There is no spawn_function and no payload dictionary: the server instantiates
|
||||
## a scene, sets it up, and adds it under the spawn path — the spawner replicates
|
||||
## the creation (and later the deletion) to every peer, including late joiners,
|
||||
## and the synchronizers NetReplication attaches carry the state.
|
||||
##
|
||||
## The two hooks that make this generic, rather than something each object opts
|
||||
## into:
|
||||
##
|
||||
## * content_root.child_entered_tree -> NetReplication.attach, on EVERY peer.
|
||||
## The client's copy is built by the spawner straight from the .tscn, so this
|
||||
## is what gives it synchronizers at all. It also runs early enough for them
|
||||
## to pick up the spawn payload.
|
||||
## * the same hook, on clients only -> _gate. One rule decides what a client is
|
||||
## not allowed to simulate, for any object, whatever it happens to be.
|
||||
|
||||
## Directories scanned for spawnable scenes. Anything in them can be spawned
|
||||
## over the network without being registered by hand anywhere.
|
||||
const SPAWNABLE_DIRS := [
|
||||
"res://Items/",
|
||||
"res://Containers/",
|
||||
"res://Stations/",
|
||||
"res://Prefabs/",
|
||||
]
|
||||
|
||||
var _spawner: MultiplayerSpawner
|
||||
var _content_root: Node
|
||||
|
||||
|
||||
## Wires this up to the world scene's spawner and content root. Must run on every
|
||||
## peer BEFORE it connects, so the spawnable list is identical by the time any
|
||||
## spawn packet can arrive.
|
||||
func setup(spawner: MultiplayerSpawner, content_root: Node) -> void:
|
||||
_spawner = spawner
|
||||
_content_root = content_root
|
||||
_register_spawnables()
|
||||
_content_root.child_entered_tree.connect(_on_content_child_entered)
|
||||
|
||||
|
||||
## Registers every scene under SPAWNABLE_DIRS, sorted.
|
||||
##
|
||||
## The sort is not cosmetic. Auto-spawn puts an INDEX into this list on the wire,
|
||||
## not a path, so a peer whose list is ordered differently instantiates the wrong
|
||||
## scene entirely. Directory listing order is not guaranteed to match across
|
||||
## machines, so it is pinned here.
|
||||
func _register_spawnables() -> void:
|
||||
var scenes: Array[String] = []
|
||||
for dir in SPAWNABLE_DIRS:
|
||||
for file in ResourceLoader.list_directory(dir):
|
||||
if file.ends_with(".tscn"):
|
||||
scenes.append(dir + file)
|
||||
scenes.sort()
|
||||
for scene in scenes:
|
||||
_spawner.add_spawnable_scene(scene)
|
||||
NetworkManager.log_line("Registered %d spawnable scenes" % scenes.size())
|
||||
|
||||
|
||||
func _on_content_child_entered(node: Node) -> void:
|
||||
# Our own synchronizers re-enter here as children of the node, not of the
|
||||
# content root, so this only ever sees spawned roots — but the guard is cheap
|
||||
# and makes the intent explicit.
|
||||
if node is MultiplayerSynchronizer:
|
||||
return
|
||||
NetReplication.attach(node)
|
||||
# Grab handling is wired up centrally, so no scene has to carry a networking
|
||||
# component to be pickable over the network.
|
||||
if node is XRToolsPickable:
|
||||
NetGrab.watch(node)
|
||||
if not NetworkManager.owns_world():
|
||||
_gate(node)
|
||||
# Deferred, in this order, because both depend on the node being fully
|
||||
# constructed: XRToolsPickable captures original_collision_mask in an @onready,
|
||||
# which has not run yet at child_entered_tree time. Reading it now would
|
||||
# record 0 as the object's authored collision mask and it would never collide
|
||||
# with anything again.
|
||||
_remember_authored.call_deferred(node)
|
||||
apply_physics_role.call_deferred(node)
|
||||
|
||||
|
||||
# --- spawning --------------------------------------------------------------
|
||||
|
||||
## Creates a networked object. Server-only when online (the spawner replicates
|
||||
## it from there); works directly when offline.
|
||||
##
|
||||
## `props` are applied BEFORE the node enters the tree, so they ride the spawn
|
||||
## packet as the synchronizer's spawn properties and every peer builds the object
|
||||
## already configured. Note that this means their setters run before the node is
|
||||
## in the tree, where @onready references are still null — any setter reachable
|
||||
## this way has to be null-guarded.
|
||||
func spawn(scene_path: String, xform: Transform3D, node_name: String = "", props: Dictionary = {}) -> Node:
|
||||
if not NetworkManager.owns_world():
|
||||
return null
|
||||
var scene: PackedScene = load(scene_path)
|
||||
if not scene:
|
||||
push_error("NetWorld.spawn: could not load scene %s" % scene_path)
|
||||
return null
|
||||
var inst := scene.instantiate()
|
||||
if node_name != "":
|
||||
inst.name = node_name
|
||||
for key in props:
|
||||
inst.set(key, props[key])
|
||||
if inst is Node3D:
|
||||
inst.transform = xform
|
||||
_content_root.add_child(inst)
|
||||
# global_transform can only be honoured once the node has a parent to be
|
||||
# global relative to. Content roots are normally at the origin, but a debug
|
||||
# scene is free to move one.
|
||||
if inst is Node3D:
|
||||
inst.global_transform = xform
|
||||
NetworkManager.log_line("spawn: %s as %s" % [scene_path.get_file(), inst.name])
|
||||
return inst
|
||||
|
||||
|
||||
## Destroys a networked object everywhere. The spawner broadcasts the despawn
|
||||
## when a tracked node leaves the tree on the authority, so freeing it here is
|
||||
## the whole implementation — there is no despawn RPC any more.
|
||||
##
|
||||
## Callers reach this holding all sorts of nodes (a component such as
|
||||
## DespawningItem, or a visual deep inside a plate), so it walks up to the object
|
||||
## the spawner actually knows about. Getting that wrong used to leave a ghost
|
||||
## copy on every client, which then blocked the station it was sitting in.
|
||||
func despawn(node: Node) -> void:
|
||||
if not NetworkManager.owns_world() or not is_instance_valid(node):
|
||||
return
|
||||
var root := _spawned_root(node)
|
||||
if not root:
|
||||
# Not part of the replicated world at all (a cosmetic copy parented under
|
||||
# a plate, say). Freeing it locally is all that was ever meant.
|
||||
node.queue_free()
|
||||
return
|
||||
NetworkManager.log_line("despawn: %s" % root.name)
|
||||
root.queue_free()
|
||||
|
||||
|
||||
# The ancestor that is a direct child of the content root — i.e. the node the
|
||||
# spawner tracks — or null if this node is not part of the replicated world.
|
||||
func _spawned_root(node: Node) -> Node:
|
||||
var current := node
|
||||
while current:
|
||||
if current.get_parent() == _content_root:
|
||||
return current
|
||||
current = current.get_parent()
|
||||
return null
|
||||
|
||||
|
||||
## Every replicated object currently in the world.
|
||||
func objects() -> Array[Node]:
|
||||
var found: Array[Node] = []
|
||||
if _content_root:
|
||||
for child in _content_root.get_children():
|
||||
found.append(child)
|
||||
return found
|
||||
|
||||
|
||||
# --- what a client is not allowed to simulate ------------------------------
|
||||
|
||||
## The one gating rule, applied to every replicated object on peers that do not
|
||||
## own world logic.
|
||||
##
|
||||
## Stations decide things (what has cooked, what is clean, what snaps where) and
|
||||
## those decisions are the server's, so a client neither runs their logic nor
|
||||
## lets their snap zones grab anything. Without this each peer independently
|
||||
## grabs and simulates the same shared object, and the copies drift apart.
|
||||
##
|
||||
## Note what is NOT here: display code. A client still has to show a lit hob and
|
||||
## a dirty plate, so anything visual must be driven from a replicated value's
|
||||
## setter rather than from _process — which is where it belongs anyway, since
|
||||
## that is the only version that also works for a late joiner.
|
||||
func _gate(node: Node) -> void:
|
||||
if node.is_in_group("station"):
|
||||
node.set_process(false)
|
||||
var zones := _snap_zones_of(node)
|
||||
for zone in zones:
|
||||
zone.enabled = false
|
||||
zone.set_process(false)
|
||||
NetworkManager.log_line("gated %s (%d snap zones)" % [node.name, zones.size()])
|
||||
|
||||
|
||||
func _snap_zones_of(node: Node) -> Array:
|
||||
var zones := []
|
||||
for child in node.get_children():
|
||||
if child is XRToolsSnapZone:
|
||||
zones.append(child)
|
||||
return zones
|
||||
|
||||
|
||||
## Puts a body into the right physics state for whether this peer is currently
|
||||
## driving it, which is exactly "do we own its NetXform".
|
||||
##
|
||||
## The authority IS the state here. The old implementation carried a replicated
|
||||
## `net_held_by` peer id alongside the authority and reconciled the two by hand,
|
||||
## which is what made held items get stuck frozen, or disabled forever, when the
|
||||
## two disagreed. There is only one source of truth now.
|
||||
##
|
||||
## Called when an object is created and again whenever its NetXform changes
|
||||
## hands, which is the complete set of moments the answer can change.
|
||||
func apply_physics_role(node: Node) -> void:
|
||||
if not (node is RigidBody3D):
|
||||
return
|
||||
var xform := node.get_node_or_null(NetReplication.XFORM_NAME)
|
||||
if not xform:
|
||||
return
|
||||
var body: RigidBody3D = node
|
||||
if xform.is_multiplayer_authority():
|
||||
# We simulate it: restore whatever the scene authored. XRToolsPickable
|
||||
# manages freeze and collision itself while an object is actually in a
|
||||
# hand, so a held object is left alone.
|
||||
if body is XRToolsPickable and body.is_picked_up():
|
||||
return
|
||||
body.freeze = false
|
||||
body.freeze_mode = body.get_meta("net_freeze_mode", body.freeze_mode)
|
||||
if body is XRToolsPickable:
|
||||
body.collision_mask = body.original_collision_mask
|
||||
# `enabled` has to be restored explicitly. Nothing else ever writes it
|
||||
# back: the non-authority branch below clears it, so once any peer had
|
||||
# held this object every other peer left it disabled forever. On the
|
||||
# server that quietly broke everything downstream — hands could no
|
||||
# longer pick the object up, while a snap zone still reported having
|
||||
# grabbed it.
|
||||
body.enabled = body.get_meta("net_enabled", body.enabled)
|
||||
return
|
||||
# Someone else drives it: stop simulating and just follow the sync. Kinematic
|
||||
# rather than static so the incoming transform can still move it.
|
||||
if body is XRToolsPickable and body.is_picked_up():
|
||||
body.drop()
|
||||
body.freeze = true
|
||||
body.freeze_mode = RigidBody3D.FREEZE_MODE_KINEMATIC
|
||||
body.collision_mask = 0
|
||||
if body is XRToolsPickable:
|
||||
# Still grabbable if the SERVER owns it, because that just means the object
|
||||
# is lying around loose — being able to pick those up is the entire point.
|
||||
# Only an object held by another player is off limits, and that is exactly
|
||||
# when NetXform belongs to a peer other than 1.
|
||||
body.enabled = xform.get_multiplayer_authority() == 1
|
||||
|
||||
|
||||
# Records the state the scene authored, before gating has a chance to overwrite
|
||||
# it, so reclaiming an object restores what it was built with rather than
|
||||
# whatever the frozen-follower state last forced on it.
|
||||
func _remember_authored(node: Node) -> void:
|
||||
if not (node is RigidBody3D) or node.has_meta("net_freeze_mode"):
|
||||
return
|
||||
node.set_meta("net_freeze_mode", node.freeze_mode)
|
||||
if node is XRToolsPickable:
|
||||
node.set_meta("net_enabled", node.enabled)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bgo0xq4uupuhi
|
||||
+78
-373
@@ -1,36 +1,34 @@
|
||||
extends Node
|
||||
|
||||
## Client-server session manager for VRyHungry (listen-server model).
|
||||
## Session manager for VRyHungry (listen-server model: the host is peer 1 and
|
||||
## also plays).
|
||||
##
|
||||
## Registered as the "NetworkManager" autoload. Owns transport (ENet), tracks
|
||||
## the session, and is the single place that reassigns multiplayer authority
|
||||
## (only the server does so). The world scene (main.gd) registers its spawners
|
||||
## here via [method register_world]; higher layers (players, items, stations)
|
||||
## build on top of this in later phases.
|
||||
## Registered as the "NetworkManager" autoload. This owns the transport and the
|
||||
## session lifecycle, and nothing else — what exists in the world and who is
|
||||
## allowed to simulate it belongs to NetWorld, and interaction belongs to
|
||||
## NetGrab. The spawn_item/despawn_item pair below are deliberately thin
|
||||
## forwards, so game code keeps one obvious place to call.
|
||||
|
||||
const DEFAULT_PORT := 24565
|
||||
const MAX_CLIENTS := 7
|
||||
|
||||
## Emitted on every peer (including the server for its own local player) when a
|
||||
## player peer joins. On the server this fires for each remote peer; the server
|
||||
## uses it to spawn that peer's player.
|
||||
## uses it to spawn that peer's avatar.
|
||||
signal player_joined(peer_id: int)
|
||||
signal player_left(peer_id: int)
|
||||
signal session_started(is_server: bool)
|
||||
signal session_ended()
|
||||
signal connection_failed()
|
||||
|
||||
# World hooks, registered by main.gd once the scene tree exists.
|
||||
var _world: Node = null
|
||||
var _players_spawner: MultiplayerSpawner = null
|
||||
var _items_spawner: MultiplayerSpawner = null
|
||||
var _content_root: Node = null
|
||||
var _net_world: NetWorld = null
|
||||
|
||||
var _log_file: FileAccess
|
||||
|
||||
## Reason the session ended, shown by the menu on the next _ready() (see
|
||||
## Reason the session ended, shown by the menu on its 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.
|
||||
## does not exist yet at the moment the session actually ends.
|
||||
var last_status := ""
|
||||
|
||||
|
||||
@@ -43,9 +41,9 @@ func _ready() -> void:
|
||||
multiplayer.server_disconnected.connect(_on_server_disconnected)
|
||||
|
||||
|
||||
# --- Public API ------------------------------------------------------------
|
||||
# --- session ---------------------------------------------------------------
|
||||
|
||||
## Start hosting. The host is peer 1 and also plays (listen server).
|
||||
## Start hosting. The host is peer 1 and also plays.
|
||||
func host(port: int = DEFAULT_PORT) -> Error:
|
||||
var peer := ENetMultiplayerPeer.new()
|
||||
var err := peer.create_server(port, MAX_CLIENTS)
|
||||
@@ -55,8 +53,7 @@ func host(port: int = DEFAULT_PORT) -> Error:
|
||||
multiplayer.multiplayer_peer = peer
|
||||
log_line("HOST started on port %d (peer id %d)" % [port, multiplayer.get_unique_id()])
|
||||
session_started.emit(true)
|
||||
# The host's own local player joins immediately.
|
||||
_on_player_present(multiplayer.get_unique_id())
|
||||
player_joined.emit(multiplayer.get_unique_id())
|
||||
return OK
|
||||
|
||||
|
||||
@@ -79,9 +76,9 @@ func leave() -> void:
|
||||
session_ended.emit()
|
||||
|
||||
|
||||
# Restore Godot's default OfflineMultiplayerPeer (rather than leaving the peer
|
||||
# null), so is_multiplayer_authority()/get_unique_id() keep working while we are
|
||||
# back in single-player / menu state.
|
||||
# Restore Godot's default OfflineMultiplayerPeer rather than leaving the peer
|
||||
# null: is_multiplayer_authority() and get_unique_id() both throw on a null peer,
|
||||
# and plenty of code keeps calling them while we are back in menu state.
|
||||
func _go_offline() -> void:
|
||||
if multiplayer.multiplayer_peer:
|
||||
multiplayer.multiplayer_peer.close()
|
||||
@@ -89,297 +86,6 @@ func _go_offline() -> void:
|
||||
unregister_world()
|
||||
|
||||
|
||||
# --- Item spawning ---------------------------------------------------------
|
||||
|
||||
## Spawn a networked item. Server-only when online (replicates to all peers via
|
||||
## the ItemsSpawner, including late joiners); works directly when offline.
|
||||
## node_name gives the spawned node a deterministic, identical name on every
|
||||
## 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():
|
||||
return null
|
||||
var data := {"scene": scene_path, "xform": xform, "name": node_name, "props": props}
|
||||
var via := "spawner" if (is_online() and _items_spawner) else "offline"
|
||||
log_line("spawn_item: %s (name=%s, via=%s)" % [scene_path.get_file(), node_name, via])
|
||||
if is_online() and _items_spawner:
|
||||
return _items_spawner.spawn(data)
|
||||
# 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)
|
||||
if inst:
|
||||
var parent: Node = _content_root if _content_root else get_tree().current_scene
|
||||
if parent:
|
||||
parent.add_child(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 not owns_world() or not is_instance_valid(node):
|
||||
return
|
||||
log_line("despawn_item: %s" % node.name)
|
||||
# Items that came from the ItemsSpawner are despawned on every peer
|
||||
# automatically when they leave the tree here. Items baked into a scene file
|
||||
# are unknown to the spawner, so their removal has to be broadcast
|
||||
# explicitly — otherwise every client keeps a ghost copy of an item the
|
||||
# server has consumed, which then blocks the station it was sitting in and
|
||||
# gets grabbed instead of the real item that replaced it.
|
||||
if is_online() and not _is_spawner_tracked(node):
|
||||
_despawn_static_item.rpc(node.get_path())
|
||||
node.queue_free()
|
||||
|
||||
|
||||
# Items the ItemsSpawner replicates live under its spawn path; anything else was
|
||||
# baked into the scene file and the spawner knows nothing about it.
|
||||
func _is_spawner_tracked(node: Node) -> bool:
|
||||
return _content_root != null and _content_root.is_ancestor_of(node)
|
||||
|
||||
|
||||
@rpc("authority", "call_remote", "reliable")
|
||||
func _despawn_static_item(path: NodePath) -> void:
|
||||
var node := get_node_or_null(path)
|
||||
if node:
|
||||
log_line("despawn_static_item: freeing %s (the server consumed it)" % node.name)
|
||||
node.queue_free()
|
||||
|
||||
|
||||
# MultiplayerSpawner custom spawn function: runs on every peer to build the node
|
||||
# from the replicated payload.
|
||||
func _spawn_item_from_data(data: Variant) -> Node:
|
||||
var scene: PackedScene = load(data["scene"])
|
||||
if not scene:
|
||||
push_error("spawn_item: could not load scene %s" % str(data.get("scene")))
|
||||
return null
|
||||
var inst := scene.instantiate()
|
||||
if inst is Node3D:
|
||||
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
|
||||
|
||||
|
||||
# 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)
|
||||
log_line("gated station (non-owner peer): %s" % node.name)
|
||||
|
||||
|
||||
## Gate every station already sitting in the scene tree, for peers that don't
|
||||
## own world logic. Stations that arrive through spawn_item() are gated as they
|
||||
## are built (see _spawn_item_from_data), but ones baked into a scene file never
|
||||
## pass through there — leaving a client running its own snap zones, which then
|
||||
## grab items straight out of the local hand and fight the server's
|
||||
## authoritative placement. Idempotent, so it's safe on every session start.
|
||||
func gate_existing_stations() -> void:
|
||||
if owns_world():
|
||||
return
|
||||
for station in get_tree().get_nodes_in_group("station"):
|
||||
_gate_station(station)
|
||||
|
||||
|
||||
# --- Item grab-authority transfer -----------------------------------------
|
||||
|
||||
## Called by NetPickable when this peer grabs an item by hand. Godot rejects
|
||||
## rpc_id() targeting your own peer id ("RPC on yourself is not allowed"), so
|
||||
## when we ARE the server this runs the logic directly instead of round-
|
||||
## tripping an RPC to ourselves — otherwise every host-side grab/drop was
|
||||
## silently failing to run its server-side half (no denial checks, and
|
||||
## crucially no auto-snap-into-station on release).
|
||||
func request_item_authority_from(item_path: NodePath) -> void:
|
||||
if is_server():
|
||||
_grant_or_reject_item_authority(item_path, multiplayer.get_unique_id())
|
||||
else:
|
||||
_request_item_authority_rpc.rpc_id(1, item_path)
|
||||
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _request_item_authority_rpc(item_path: NodePath) -> void:
|
||||
if not is_server():
|
||||
return
|
||||
_grant_or_reject_item_authority(item_path, multiplayer.get_remote_sender_id())
|
||||
|
||||
|
||||
## Runs on the server (called directly if the requester IS the server, or via
|
||||
## the RPC above otherwise). If the item was snapped into a station, the
|
||||
## station releases it so the grabber cleanly takes ownership.
|
||||
func _grant_or_reject_item_authority(item_path: NodePath, sender: int) -> void:
|
||||
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.
|
||||
log_line("request_item_authority: DENIED %s to peer %d (already held by %d)" % [item.name, sender, np.net_held_by])
|
||||
_force_release_item_to(sender, item_path)
|
||||
return
|
||||
log_line("request_item_authority: granting %s to peer %d" % [str(item.name) if item else str(item_path), sender])
|
||||
# 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.
|
||||
_set_item_authority.rpc(item_path, sender)
|
||||
if item:
|
||||
_release_from_snap_zones(item)
|
||||
|
||||
|
||||
## Called by NetPickable when this peer releases an item, forwarding its throw
|
||||
## velocity so the server can resume simulating it. Same self-RPC issue as
|
||||
## above: runs directly if we're the server.
|
||||
func release_item_authority_from(item_path: NodePath, lin: Vector3, ang: Vector3, xform: Transform3D) -> void:
|
||||
if is_server():
|
||||
_do_release_item_authority(item_path, lin, ang, xform, multiplayer.get_unique_id())
|
||||
else:
|
||||
_release_item_authority_rpc.rpc_id(1, item_path, lin, ang, xform)
|
||||
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _release_item_authority_rpc(item_path: NodePath, lin: Vector3, ang: Vector3, xform: Transform3D) -> void:
|
||||
if not is_server():
|
||||
return
|
||||
_do_release_item_authority(item_path, lin, ang, xform, multiplayer.get_remote_sender_id())
|
||||
|
||||
|
||||
## Runs on the server. If released next to a station, the server snaps it in
|
||||
## (server-authoritative placement).
|
||||
func _do_release_item_authority(item_path: NodePath, lin: Vector3, ang: Vector3, xform: Transform3D, sender: int) -> void:
|
||||
log_line("release_item_authority: %s released by peer %d" % [str(item_path), sender])
|
||||
_set_item_authority.rpc(item_path, 1)
|
||||
var item := get_node_or_null(item_path)
|
||||
if item is RigidBody3D:
|
||||
# Adopt the releasing peer's own final transform rather than trusting our
|
||||
# copy's. That peer was the item's authority right up to this moment, and
|
||||
# its position updates travel on the synchronizer's separate, unordered
|
||||
# channel — this reliable RPC routinely overtakes them, leaving our copy
|
||||
# still sitting where the item was BEFORE the peer carried it away. The
|
||||
# snap decision below then reads that stale position and teleports the
|
||||
# item straight back into the station it was just picked up from.
|
||||
item.global_transform = xform
|
||||
item.freeze = false
|
||||
item.linear_velocity = lin
|
||||
item.angular_velocity = ang
|
||||
_try_snap_into_station.call_deferred(item)
|
||||
|
||||
|
||||
# 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:
|
||||
var zones := []
|
||||
for station in get_tree().get_nodes_in_group("station"):
|
||||
for child in station.get_children():
|
||||
if child is XRToolsSnapZone:
|
||||
zones.append(child)
|
||||
return zones
|
||||
|
||||
|
||||
# If the item is snapped into any station, drop it from that station.
|
||||
func _release_from_snap_zones(item: Node) -> void:
|
||||
for zone in _station_snap_zones():
|
||||
if zone.picked_up_object == item:
|
||||
log_line("releasing %s from %s's snap zone (authority just granted elsewhere)" % [item.name, zone.get_parent().name])
|
||||
zone.drop_object()
|
||||
# Make the zone forget the item as well. These zones are snap_mode=RANGE,
|
||||
# so every frame they re-grab anything still listed in their grab area
|
||||
# that can be picked up — and Jolt does not emit body_exited when let_go()
|
||||
# switches the item's collision layer back out of the zone's mask, so the
|
||||
# entry goes stale and never clears. The station then snatches the item
|
||||
# straight back off the player who just took it, teleporting it home.
|
||||
# Bringing it near again re-adds it properly (a held item is on the layer
|
||||
# the zone watches), and releasing next to a station is handled
|
||||
# explicitly by _try_snap_into_station.
|
||||
if zone._object_in_grab_area.has(item):
|
||||
zone._object_in_grab_area.erase(item)
|
||||
|
||||
|
||||
# Snap the item into the nearest empty station snap zone within grab range.
|
||||
#
|
||||
# Called deferred from _do_release_item_authority: XRToolsFunctionPickup's own
|
||||
# "grab an item out of a snap zone" path calls zone.drop_object() BEFORE it
|
||||
# calls pick_up() on the hand's behalf. drop_object()'s let_go() synchronously
|
||||
# fires the pickable's `dropped` signal, which (via NetPickable) lands here —
|
||||
# if this ran synchronously it would immediately re-snap the item into the
|
||||
# very same zone it's still physically inside, stealing it away before the
|
||||
# hand's own pick_up() call (later in the same call stack) ever runs. That
|
||||
# leaves XRToolsFunctionPickup.picked_up_object pointing at an item whose
|
||||
# _grab_driver actually belongs to the zone — a stale reference that crashes
|
||||
# (null _grab_driver) the next time a controller button is pressed. Deferring
|
||||
# lets the hand's pick_up() go first; the is_picked_up() check below is a
|
||||
# second guard in case the item gets grabbed for real before this runs.
|
||||
func _try_snap_into_station(item: Node) -> void:
|
||||
if not (item is Node3D):
|
||||
return
|
||||
if item.has_method("is_picked_up") and item.is_picked_up():
|
||||
var by: Node = null
|
||||
if item.has_method("get_picked_up_by"):
|
||||
by = item.get_picked_up_by()
|
||||
log_line("skipped snapping %s: already held by %s (grab-race guard)" % [item.name, by.get_path() if by else "?"])
|
||||
return
|
||||
for zone in _station_snap_zones():
|
||||
if is_instance_valid(zone.picked_up_object):
|
||||
continue
|
||||
if zone.global_position.distance_to(item.global_position) <= zone.grab_distance:
|
||||
log_line("snapped %s into %s" % [item.name, zone.get_parent().name])
|
||||
zone.pick_up_object(item)
|
||||
return
|
||||
log_line("no station in range to snap %s into (or none empty)" % item.name)
|
||||
|
||||
|
||||
# Server broadcasts an authority assignment so every peer agrees on who owns the
|
||||
# item (set_multiplayer_authority is a local call and must run everywhere).
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func _set_item_authority(item_path: NodePath, peer: int) -> void:
|
||||
var item := get_node_or_null(item_path)
|
||||
if not item:
|
||||
return
|
||||
log_line("_set_item_authority: %s -> peer %d" % [item.name, peer])
|
||||
item.set_multiplayer_authority(peer) # recursive: item + synchronizer + NetPickable
|
||||
var np := item.get_node_or_null("NetPickable")
|
||||
if np:
|
||||
np.net_held_by = 0 if peer == 1 else peer
|
||||
np.apply_held_state()
|
||||
|
||||
|
||||
## Rejects peer's optimistic grab (the item was already legitimately held by
|
||||
## someone else). Same self-RPC concern: if the rejected peer is the server
|
||||
## itself, apply it directly rather than rpc_id-ing ourselves.
|
||||
func _force_release_item_to(peer: int, item_path: NodePath) -> void:
|
||||
if peer == 1:
|
||||
_do_force_release(item_path)
|
||||
else:
|
||||
force_release_item.rpc_id(peer, item_path)
|
||||
|
||||
|
||||
@rpc("authority", "reliable")
|
||||
func force_release_item(item_path: NodePath) -> void:
|
||||
_do_force_release(item_path)
|
||||
|
||||
|
||||
func _do_force_release(item_path: NodePath) -> void:
|
||||
log_line("force_release_item: dropping %s (server rejected our grab)" % str(item_path))
|
||||
var item := get_node_or_null(item_path)
|
||||
if item and item.has_method("drop"):
|
||||
item.drop()
|
||||
|
||||
|
||||
func is_server() -> bool:
|
||||
return is_online() and multiplayer.is_server()
|
||||
|
||||
@@ -392,71 +98,69 @@ func is_online() -> bool:
|
||||
|
||||
|
||||
## True on the machine that owns authoritative world logic: the server when
|
||||
## online, or the single player when offline. Station logic and spawning should
|
||||
## only run where this is true, so state has one source of truth.
|
||||
## online, or the single player when offline. Station logic and spawning only run
|
||||
## where this is true, so state has one source of truth.
|
||||
func owns_world() -> bool:
|
||||
return not is_online() or is_server()
|
||||
|
||||
|
||||
# --- Station work-progress seam -------------------------------------------
|
||||
# --- world -----------------------------------------------------------------
|
||||
|
||||
## Reusable entry point for a client to contribute work to a station (e.g. a
|
||||
## future chopping/gesture station). The client detects the gesture locally and
|
||||
## calls this; the server validates and accumulates. Timer-driven stations like
|
||||
## the Hob don't need it, but it is the drop-in seam for input-driven ones.
|
||||
@rpc("any_peer", "reliable")
|
||||
func submit_work(station_path: NodePath, amount: float) -> void:
|
||||
if not is_server():
|
||||
return
|
||||
var station := get_node_or_null(station_path)
|
||||
if station and station.has_method("add_work"):
|
||||
log_line("submit_work: peer %d contributed %.2f to %s" % [multiplayer.get_remote_sender_id(), amount, station.name])
|
||||
station.add_work(multiplayer.get_remote_sender_id(), amount)
|
||||
|
||||
|
||||
## 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:
|
||||
## Called by the world scene once its tree exists. Must run before
|
||||
## world_ready()/host()/join() on every peer, so the spawnable scene list is
|
||||
## registered before any spawn packet can arrive.
|
||||
func register_world(world: Node, net_world: NetWorld) -> void:
|
||||
_world = world
|
||||
_players_spawner = players_spawner
|
||||
_items_spawner = items_spawner
|
||||
_content_root = items_spawner.get_node(items_spawner.spawn_path) if items_spawner else world
|
||||
if _items_spawner:
|
||||
_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)])
|
||||
_net_world = net_world
|
||||
log_line("World registered")
|
||||
|
||||
|
||||
## Called when the world scene goes away (disconnect, leaving the session) so
|
||||
## the autoload doesn't hold stale/freed references across a scene reload.
|
||||
## Called when the world scene goes away (disconnect, leaving the session) so the
|
||||
## autoload does not hold freed references across a scene reload.
|
||||
func unregister_world() -> void:
|
||||
_world = null
|
||||
_content_root = null
|
||||
_players_spawner = null
|
||||
_items_spawner = null
|
||||
_net_world = 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
|
||||
## Creates a networked object. See NetWorld.spawn.
|
||||
func spawn_item(scene_path: String, xform: Transform3D, node_name: String = "", props: Dictionary = {}) -> Node:
|
||||
if not _net_world:
|
||||
push_error("spawn_item called with no world registered: %s" % scene_path)
|
||||
return null
|
||||
return _net_world.spawn(scene_path, xform, node_name, props)
|
||||
|
||||
|
||||
## 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
|
||||
## session so that session signals never fire before the world is listening.
|
||||
## Destroys a networked object everywhere. See NetWorld.despawn.
|
||||
func despawn_item(node: Node) -> void:
|
||||
if _net_world:
|
||||
_net_world.despawn(node)
|
||||
|
||||
|
||||
## Puts an object into the right physics state for whether this peer drives it.
|
||||
func apply_physics_role(node: Node) -> void:
|
||||
if _net_world:
|
||||
_net_world.apply_physics_role(node)
|
||||
|
||||
|
||||
## Every replicated object currently in the world.
|
||||
func replicated_objects() -> Array[Node]:
|
||||
if not _net_world:
|
||||
return []
|
||||
return _net_world.objects()
|
||||
|
||||
|
||||
## Called by the world scene after it has registered itself and connected its
|
||||
## listeners. Kicks off any menu- or command-line-driven session, so session
|
||||
## signals never fire before the world is listening for them.
|
||||
func world_ready() -> void:
|
||||
consume_pending_session()
|
||||
|
||||
|
||||
# --- Menu-driven session request -------------------------------------------
|
||||
# --- menu-driven session request -------------------------------------------
|
||||
|
||||
# Set by the main menu's Host/Join buttons before switching to the multiplayer
|
||||
# scene; consumed once that scene's world is ready to listen for session
|
||||
# signals (avoids a race between change_scene_to_file and connection callbacks).
|
||||
# scene; consumed once that scene is ready to listen for session signals (avoids
|
||||
# a race between change_scene_to_file and the connection callbacks).
|
||||
var pending_action := ""
|
||||
var pending_ip := ""
|
||||
|
||||
@@ -478,18 +182,29 @@ func consume_pending_session() -> void:
|
||||
_handle_cmdline()
|
||||
|
||||
|
||||
# --- Session signal handlers ----------------------------------------------
|
||||
## 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 does not exist yet when the session ends.
|
||||
func take_status() -> String:
|
||||
var s := last_status
|
||||
last_status = ""
|
||||
return s
|
||||
|
||||
|
||||
# --- session signal handlers ----------------------------------------------
|
||||
|
||||
func _on_peer_connected(peer_id: int) -> void:
|
||||
log_line("peer_connected: %d" % peer_id)
|
||||
# Only the server reacts by materialising that peer's player.
|
||||
if is_server():
|
||||
_on_player_present(peer_id)
|
||||
player_joined.emit(peer_id)
|
||||
|
||||
func _on_peer_disconnected(peer_id: int) -> void:
|
||||
log_line("peer_disconnected: %d" % peer_id)
|
||||
if is_server():
|
||||
_on_player_absent(peer_id)
|
||||
# Anything still in their hand would otherwise stay frozen on every
|
||||
# remaining peer, waiting on an authority that has gone.
|
||||
NetGrab.reclaim_from(peer_id)
|
||||
player_left.emit(peer_id)
|
||||
|
||||
func _on_connected_to_server() -> void:
|
||||
log_line("connected_to_server (my id=%d)" % multiplayer.get_unique_id())
|
||||
@@ -508,18 +223,7 @@ func _on_server_disconnected() -> void:
|
||||
session_ended.emit()
|
||||
|
||||
|
||||
# Player materialise/dematerialise. Phase 2 wires these to the PlayersSpawner;
|
||||
# for now they announce presence so the transport layer is independently testable.
|
||||
func _on_player_present(peer_id: int) -> void:
|
||||
log_line("player_present: %d" % peer_id)
|
||||
player_joined.emit(peer_id)
|
||||
|
||||
func _on_player_absent(peer_id: int) -> void:
|
||||
log_line("player_absent: %d" % peer_id)
|
||||
player_left.emit(peer_id)
|
||||
|
||||
|
||||
# --- Command-line driven test bootstrap -----------------------------------
|
||||
# --- command-line driven test bootstrap -----------------------------------
|
||||
|
||||
func _handle_cmdline() -> void:
|
||||
var args := OS.get_cmdline_user_args()
|
||||
@@ -535,7 +239,7 @@ func _handle_cmdline() -> void:
|
||||
join(addr)
|
||||
|
||||
|
||||
# --- Logging ---------------------------------------------------------------
|
||||
# --- logging ---------------------------------------------------------------
|
||||
|
||||
func _open_log() -> void:
|
||||
var dir := OS.get_environment("TEMP")
|
||||
@@ -547,6 +251,7 @@ func _open_log() -> void:
|
||||
_log_file = FileAccess.open(path, FileAccess.WRITE)
|
||||
log_line("=== NetworkManager log (pid %d) ===" % OS.get_process_id())
|
||||
|
||||
|
||||
func log_line(s: String) -> void:
|
||||
var id := 0
|
||||
var p := multiplayer.multiplayer_peer
|
||||
|
||||
+55
-131
@@ -1,155 +1,79 @@
|
||||
extends Node
|
||||
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.
|
||||
## Reads the kitchen a scene file authored and describes it as data, so the
|
||||
## server can respawn it as replicated objects.
|
||||
##
|
||||
## Positions below are transcribed verbatim from the previous baked layout in
|
||||
## Scenes/multiPlayer.tscn so the starting world is unchanged.
|
||||
## The authored nodes are a template, not the live world. Baking them into the
|
||||
## scene would mean each peer ran its own unsynced copy; spawning them means the
|
||||
## client receives the server's, through the same path as a mid-session join.
|
||||
|
||||
## Directories whose scenes count as world content, and the base type to look for
|
||||
## in each. Anything instanced from these is picked up automatically — adding a
|
||||
## new station or item needs no change here.
|
||||
const SOURCES := [
|
||||
{"dir": "res://Stations/", "type": "StaticBody3D"},
|
||||
{"dir": "res://Items/", "type": "XRToolsPickable"},
|
||||
{"dir": "res://Containers/", "type": "XRToolsPickable"},
|
||||
]
|
||||
|
||||
|
||||
func get_node_data(node):
|
||||
# {"scene": "res://Stations/Hob.tscn", "name": "Hob",
|
||||
# "xform": Transform3D(Basis(), Vector3(0.29336345, 0.8981018, -1.484)), "props": {}},
|
||||
var data = {}
|
||||
data["scene"] = node.scene_file_path
|
||||
data["name"] = node.name
|
||||
# global, not local: the copies are respawned under WorldContent, so an
|
||||
# authored node that was nested inside another (JonScene parents Counter5
|
||||
# under Counter3) would otherwise land in the wrong place.
|
||||
data["xform"] = node.global_transform
|
||||
data["props"] = {}
|
||||
var scrip = node.get_script()
|
||||
if scrip:
|
||||
for i in scrip.get_script_property_list():
|
||||
# Only authored configuration — i.e. @export vars, which are the ones
|
||||
# the editor exposes. Plain script variables are live runtime state:
|
||||
# copying those and replaying them into a fresh instance re-runs their
|
||||
# setters before the node is in the tree, so any setter touching an
|
||||
# @onready reference blows up (Table's _state calls into its progress
|
||||
# bar, which is still null at that point).
|
||||
if not (i.usage & PROPERTY_USAGE_EDITOR):
|
||||
continue
|
||||
if str(i["name"]).begins_with("_"):
|
||||
continue
|
||||
data["props"][i["name"]] = node.get(i["name"])
|
||||
return data
|
||||
|
||||
|
||||
## The authored station nodes sitting in the current scene: anything instanced
|
||||
## from res://Stations/. Exposed as nodes (not just data) because every peer has
|
||||
## to remove these originals — the server replaces them with replicated copies,
|
||||
## and a client that kept its own would end up showing two of everything.
|
||||
func get_station_nodes() -> Array[Node]:
|
||||
return _authored_nodes("res://Stations/", "StaticBody3D")
|
||||
|
||||
|
||||
## Likewise for authored items. Containers/ counts as items too — plates and
|
||||
## trays are things the player carries, and a client that never received one
|
||||
## would have an incomplete world.
|
||||
func get_item_nodes() -> Array[Node]:
|
||||
var found := _authored_nodes("res://Items/", "XRToolsPickable")
|
||||
found.append_array(_authored_nodes("res://Containers/", "XRToolsPickable"))
|
||||
## Every authored node in the current scene, stations first.
|
||||
##
|
||||
## Order matters: stations have to exist before items, so an item spawning on top
|
||||
## of one lands in a snap zone that is already there.
|
||||
func get_authored_nodes() -> Array[Node]:
|
||||
var found: Array[Node] = []
|
||||
for source in SOURCES:
|
||||
for node in _instances_of(source["dir"], source["type"]):
|
||||
if not found.has(node):
|
||||
found.append(node)
|
||||
return found
|
||||
|
||||
|
||||
func _authored_nodes(dir: String, type: String) -> Array[Node]:
|
||||
func _instances_of(dir: String, type: String) -> Array[Node]:
|
||||
var scenes := []
|
||||
for file in ResourceLoader.list_directory(dir):
|
||||
scenes.append(dir + file)
|
||||
var found: Array[Node] = []
|
||||
for node in get_tree().root.find_children("*", type, true, false):
|
||||
if node.scene_file_path in scenes and not found.has(node):
|
||||
if node.scene_file_path in scenes:
|
||||
found.append(node)
|
||||
return found
|
||||
|
||||
|
||||
func get_stations() -> Array[Dictionary]:
|
||||
## Turns authored nodes into spawn descriptions.
|
||||
func describe(nodes: Array[Node]) -> Array[Dictionary]:
|
||||
var data: Array[Dictionary] = []
|
||||
for node in get_station_nodes():
|
||||
data.append(get_node_data(node))
|
||||
for node in nodes:
|
||||
data.append(_describe_one(node))
|
||||
return data
|
||||
|
||||
|
||||
func get_items() -> Array[Dictionary]:
|
||||
var data: Array[Dictionary] = []
|
||||
for node in get_item_nodes():
|
||||
data.append(get_node_data(node))
|
||||
return data
|
||||
func _describe_one(node: Node) -> Dictionary:
|
||||
return {
|
||||
"scene": node.scene_file_path,
|
||||
"name": node.name,
|
||||
# Global, not local: the copies are respawned under one content root, so
|
||||
# an authored node nested inside another (JonScene parents Counter5 under
|
||||
# Counter3) would otherwise land in the wrong place.
|
||||
"xform": node.global_transform,
|
||||
"props": _authored_props(node),
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static func get_stations_old() -> 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_old() -> 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": {}}
|
||||
# Only authored configuration — @export vars, the ones the editor exposes. Plain
|
||||
# script variables are live runtime state, and replaying those into a fresh
|
||||
# instance re-runs their setters before the node is in the tree, where any setter
|
||||
# touching an @onready reference blows up.
|
||||
func _authored_props(node: Node) -> Dictionary:
|
||||
var props := {}
|
||||
var script: Script = node.get_script() as Script
|
||||
if not script:
|
||||
return props
|
||||
for prop in script.get_script_property_list():
|
||||
var prop_name := str(prop["name"])
|
||||
if not (prop["usage"] & PROPERTY_USAGE_EDITOR) or prop_name.begins_with("_"):
|
||||
continue
|
||||
props[prop_name] = node.get(prop_name)
|
||||
return props
|
||||
|
||||
+78
-43
@@ -1,71 +1,106 @@
|
||||
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.
|
||||
## Networked representation of one connected player.
|
||||
##
|
||||
## The owning peer's local XR rig drives the three transforms below every frame;
|
||||
## the generated NetSync replicates them, and every other peer applies them to
|
||||
## the avatar's head and hands. They are ordinary script variables rather than an
|
||||
## authored replication config, so the avatar goes through exactly the same
|
||||
## convention as every other replicated object.
|
||||
|
||||
@onready var _head: Node3D = $Head
|
||||
@onready var _left_hand: Node3D = $LeftHand
|
||||
@onready var _right_hand: Node3D = $RightHand
|
||||
|
||||
## Replicated pose. Underscore-free by design — that is what marks a variable as
|
||||
## replicated (see NetReplication). Written by the owning peer, applied by
|
||||
## everyone else through the setters.
|
||||
var head_xform: Transform3D = Transform3D.IDENTITY: set = _set_head_xform
|
||||
var left_xform: Transform3D = Transform3D.IDENTITY: set = _set_left_xform
|
||||
var right_xform: Transform3D = Transform3D.IDENTITY: set = _set_right_xform
|
||||
|
||||
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.
|
||||
# The avatar's name is the peer id it belongs to. Authority has to be claimed
|
||||
# here rather than in _ready(): NetReplication reads it when it attaches the
|
||||
# synchronizers, which happens as this node enters the tree.
|
||||
set_multiplayer_authority(str(name).to_int())
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
# LeftHand/RightHand are the full godot-xr-tools glove scenes, whose root
|
||||
# node carries hand.gd (XRToolsHand). That script sets top_level = true
|
||||
# and every physics frame does global_transform = get_parent().
|
||||
# global_transform * offset — i.e. it actively repositions itself to
|
||||
# track a live XRController3D parent. Here the parent is just this
|
||||
# NetPlayer node, not a controller, so left running it fights (and mostly
|
||||
# wins, since it runs every physics tick regardless of our own _process)
|
||||
# against both the local authority's transform copy below and the
|
||||
# replicated values on other peers — the exact "hands stuck near origin,
|
||||
# only occasionally correct" symptom. Disable it everywhere; nothing else
|
||||
# in that script matters here since _controller is always null without a
|
||||
# real controller ancestor (grip/trigger animation already no-ops).
|
||||
# carries hand.gd. That script sets top_level = true and every physics frame
|
||||
# repositions itself to track a live XRController3D parent. Here the parent is
|
||||
# just this avatar, not a controller, so leaving it running fights both the
|
||||
# local copy below and the replicated values on other peers — and mostly wins,
|
||||
# since it runs every physics tick regardless of our _process. That was the
|
||||
# "hands stuck near the origin, only occasionally correct" symptom. Nothing
|
||||
# else in that script matters here; _controller is always null without a real
|
||||
# controller ancestor, so the grip animation already no-ops.
|
||||
_left_hand.set_physics_process(false)
|
||||
_right_hand.set_physics_process(false)
|
||||
|
||||
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 so they don't start stacked on top of each
|
||||
# other. Peer ids from ENet are large effectively-random 32-bit
|
||||
# numbers, so this must be bounded, AND kept well within the
|
||||
# floor's actual footprint (15x15, so ~7.5 units from center) —
|
||||
# a previous version used up to 12 units and could place a
|
||||
# joining player off the edge of the floor.
|
||||
var peer_id := str(name).to_int()
|
||||
if peer_id != 1:
|
||||
var slot := absi(peer_id) % 4
|
||||
origin.position += Vector3((slot - 1.5) * 1.2, 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:
|
||||
if not is_multiplayer_authority():
|
||||
set_process(false)
|
||||
# State that arrived in the spawn packet was applied before these @onready
|
||||
# references existed, so render it now.
|
||||
_apply_pose()
|
||||
return
|
||||
|
||||
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, so spread joiners along X
|
||||
# rather than starting them stacked on top of each other. ENet peer ids
|
||||
# are large effectively-random 32-bit numbers, so this has to be bounded
|
||||
# AND kept well inside the floor's footprint — an earlier version used up
|
||||
# to 12 units and could drop a joining player off the edge.
|
||||
var peer_id := str(name).to_int()
|
||||
if peer_id != 1:
|
||||
var slot := absi(peer_id) % 4
|
||||
origin.position += Vector3((slot - 1.5) * 1.2, 0, 0)
|
||||
# Don't render your own floating head and hands from the inside.
|
||||
_head.visible = false
|
||||
_left_hand.visible = false
|
||||
_right_hand.visible = false
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if _local_camera:
|
||||
_head.global_transform = _local_camera.global_transform
|
||||
head_xform = _local_camera.global_transform
|
||||
if _local_left_hand:
|
||||
_left_hand.global_transform = _local_left_hand.global_transform
|
||||
left_xform = _local_left_hand.global_transform
|
||||
if _local_right_hand:
|
||||
_right_hand.global_transform = _local_right_hand.global_transform
|
||||
right_xform = _local_right_hand.global_transform
|
||||
|
||||
|
||||
# The setters are null-guarded because a spawn-replicated property fires its
|
||||
# setter BEFORE the node is in the tree, when @onready references are still null.
|
||||
func _set_head_xform(value: Transform3D) -> void:
|
||||
head_xform = value
|
||||
if _head:
|
||||
_head.global_transform = value
|
||||
|
||||
|
||||
func _set_left_xform(value: Transform3D) -> void:
|
||||
left_xform = value
|
||||
if _left_hand:
|
||||
_left_hand.global_transform = value
|
||||
|
||||
|
||||
func _set_right_xform(value: Transform3D) -> void:
|
||||
right_xform = value
|
||||
if _right_hand:
|
||||
_right_hand.global_transform = value
|
||||
|
||||
|
||||
func _apply_pose() -> void:
|
||||
_head.global_transform = head_xform
|
||||
_left_hand.global_transform = left_xform
|
||||
_right_hand.global_transform = right_xform
|
||||
|
||||
+1
-26
@@ -1,4 +1,4 @@
|
||||
[gd_scene load_steps=5 format=3]
|
||||
[gd_scene load_steps=4 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"]
|
||||
@@ -8,26 +8,6 @@
|
||||
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")
|
||||
|
||||
@@ -37,8 +17,3 @@ 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
|
||||
|
||||
@@ -57,7 +57,7 @@ func _on_body_entered(body: Node3D) -> void:
|
||||
print("CombinableItem _on_body_entered: other is our own pickable")
|
||||
return
|
||||
|
||||
var other := Helper.find_food_item(body)
|
||||
var other := _find_food_item(body)
|
||||
if not other:
|
||||
print("CombinableItem _on_body_entered: other is not a foodItem")
|
||||
return
|
||||
@@ -71,7 +71,7 @@ func _on_body_entered(body: Node3D) -> void:
|
||||
|
||||
# Instantiate the result of combination and free the two ingredient items
|
||||
func _combine(other_body: Node3D, result: PackedScene) -> void:
|
||||
print("CombinableItem _combine: combining %s + %s into %s" % [_food_item.id, Helper.find_food_item(other_body).id, result.resource_path])
|
||||
print("CombinableItem _combine: combining %s + %s into %s" % [_food_item.id, _find_food_item(other_body).id, result.resource_path])
|
||||
|
||||
# Get Snapzone
|
||||
var snap_zone := _pickable.get_picked_up_by()
|
||||
@@ -91,3 +91,13 @@ func _combine(other_body: Node3D, result: PackedScene) -> void:
|
||||
|
||||
# Snap the result into the now-empty zone.
|
||||
snap_zone.pick_up_object(result_instance)
|
||||
|
||||
|
||||
# Find a FoodItem among the direct children of [param node].
|
||||
func _find_food_item(node: Node) -> FoodItem:
|
||||
if not node:
|
||||
return null
|
||||
for child in node.get_children():
|
||||
if child is FoodItem:
|
||||
return child
|
||||
return null
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[gd_scene format=3 uid="uid://3lr2dhy62rhk"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dounic663dn5q" path="res://prefabs/combinable_item.gd" id="1_q21ti"]
|
||||
[ext_resource type="Script" uid="uid://dounic663dn5q" path="res://Prefabs/combinable_item.gd" id="1_q21ti"]
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_o1b3t"]
|
||||
radius = 0.2
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[gd_scene format=3 uid="uid://7earnjgdmwgx"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bs7cxydoxk1cx" path="res://prefabs/cookable_item.gd" id="1_6hwx6"]
|
||||
[ext_resource type="PackedScene" uid="uid://cucc3gaqu2nab" path="res://items/Charcoal.tscn" id="2_rpt7j"]
|
||||
[ext_resource type="Script" uid="uid://bs7cxydoxk1cx" path="res://Prefabs/cookable_item.gd" id="1_6hwx6"]
|
||||
[ext_resource type="PackedScene" uid="uid://cucc3gaqu2nab" path="res://Items/Charcoal.tscn" id="2_rpt7j"]
|
||||
|
||||
[node name="CookableItem" type="Node" unique_id=280820828]
|
||||
script = ExtResource("1_6hwx6")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[gd_scene format=3 uid="uid://b5ukku8i0hilb"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://deyna316ka4xs" path="res://prefabs/despawning_item.gd" id="1_ayqk8"]
|
||||
[ext_resource type="Script" uid="uid://deyna316ka4xs" path="res://Prefabs/despawning_item.gd" id="1_ayqk8"]
|
||||
|
||||
[node name="DespawningItem" type="Node" unique_id=303090111]
|
||||
script = ExtResource("1_ayqk8")
|
||||
|
||||
@@ -7,4 +7,3 @@ enum Type { MEAL, SIDE, INGREDIENT, NONE }
|
||||
@export var id: String
|
||||
@export var type: Type = Type.NONE
|
||||
@export var sell_value = 1
|
||||
var is_absorbed = false # Used by tables to keep track of whether the item has been registered as delivered already
|
||||
@@ -1,6 +1,6 @@
|
||||
[gd_scene format=3 uid="uid://bfj80jh13t6e5"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://5tpoohkopryv" path="res://prefabs/food_item.gd" id="1_0na5k"]
|
||||
[ext_resource type="Script" uid="uid://5tpoohkopryv" path="res://Prefabs/food_item.gd" id="1_0na5k"]
|
||||
|
||||
[node name="FoodItem" type="Node" unique_id=63948206]
|
||||
script = ExtResource("1_0na5k")
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
extends Node3D
|
||||
|
||||
@export var kitchen_scene: PackedScene
|
||||
@export var hob_scene: PackedScene
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
||||
if not kitchen_scene:
|
||||
push_error("StationSpawner missing kitchen_scene")
|
||||
|
||||
print("Kitchen init")
|
||||
|
||||
if NetworkManager.owns_world():
|
||||
print("Kitchen init is_server")
|
||||
# Later we will do procedural generation here.
|
||||
# For now we just load a scene.
|
||||
NetworkManager.call_deferred("spawn_item", kitchen_scene.resource_path, transform)
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("satisfy_table_orders"):
|
||||
var trans: Transform3D = transform
|
||||
trans.origin += Vector3(-1.2, 0, 0)
|
||||
NetworkManager.call_deferred("spawn_item", hob_scene.resource_path, trans)
|
||||
@@ -1 +0,0 @@
|
||||
uid://fyd2rjramhbb
|
||||
@@ -1,9 +0,0 @@
|
||||
[gd_scene format=3 uid="uid://t83yi6hxti2x"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://fyd2rjramhbb" path="res://prefabs/kitchen_instantiator.gd" id="1_jitfr"]
|
||||
[ext_resource type="PackedScene" uid="uid://damrxtlt7uswf" path="res://content/station_layouts/small_line.tscn" id="2_3awg0"]
|
||||
|
||||
[node name="KitchenInstantiator" type="Node3D" unique_id=291701321]
|
||||
transform = Transform3D(-1, 0, 8.742278e-08, 0, 1, 0, -8.742278e-08, 0, -1, 0, 0, 0)
|
||||
script = ExtResource("1_jitfr")
|
||||
kitchen_scene = ExtResource("2_3awg0")
|
||||
+13
-13
@@ -2,21 +2,21 @@
|
||||
|
||||
[ext_resource type="Script" uid="uid://b4ldkd3ngs0vp" path="res://main.gd" id="1_m3h4l"]
|
||||
[ext_resource type="PackedScene" uid="uid://j5s5wus7tuhb" path="res://XROrigin.tscn" id="2_8pfaf"]
|
||||
[ext_resource type="Material" uid="uid://cmia50cfqxxo4" path="res://textures/dev_material_3d.tres" id="3_gatbn"]
|
||||
[ext_resource type="PackedScene" uid="uid://j7caslh27nor" path="res://stations/Hob.tscn" id="4_3lufs"]
|
||||
[ext_resource type="Sky" uid="uid://c1abtpwhdm2d5" path="res://textures/sky/NightSkyHDRI009_16K.tres" id="5_kh26v"]
|
||||
[ext_resource type="PackedScene" uid="uid://c6rift56ql3f8" path="res://stations/BurgerBunsDispenser.tscn" id="7_tejmp"]
|
||||
[ext_resource type="PackedScene" uid="uid://dpot5qie20vf6" path="res://items/burger.tscn" id="9_rwx83"]
|
||||
[ext_resource type="PackedScene" uid="uid://dvrk268s7gkxh" path="res://stations/sink.tscn" id="12_ah4xa"]
|
||||
[ext_resource type="PackedScene" uid="uid://cnjwtnhwh0i8q" path="res://stations/dirt_station.tscn" id="13_46xi2"]
|
||||
[ext_resource type="PackedScene" uid="uid://ck5tuftqmyiue" path="res://stations/plate_dispenser.tscn" id="14_h88sx"]
|
||||
[ext_resource type="PackedScene" uid="uid://cwnwo4i28upap" path="res://stations/raw_burger_dispenser.tscn" id="15_m3h4l"]
|
||||
[ext_resource type="PackedScene" uid="uid://efaec6ymgabo" path="res://stations/Counter.tscn" id="15_tpcje"]
|
||||
[ext_resource type="PackedScene" uid="uid://caf0xanmxbshy" path="res://stations/table.tscn" id="16_g2k6a"]
|
||||
[ext_resource type="Script" uid="uid://k8ywnvlhcic4" path="res://scenes/testworldLoad.gd" id="16_m3h4l"]
|
||||
[ext_resource type="Material" uid="uid://cmia50cfqxxo4" path="res://Textures/dev_material_3d.tres" id="3_gatbn"]
|
||||
[ext_resource type="PackedScene" uid="uid://j7caslh27nor" path="res://Stations/Hob.tscn" id="4_3lufs"]
|
||||
[ext_resource type="Sky" uid="uid://c1abtpwhdm2d5" path="res://Textures/sky/NightSkyHDRI009_16K.tres" id="5_kh26v"]
|
||||
[ext_resource type="PackedScene" uid="uid://c6rift56ql3f8" path="res://Stations/BurgerBunsDispenser.tscn" id="7_tejmp"]
|
||||
[ext_resource type="PackedScene" uid="uid://dpot5qie20vf6" path="res://Items/burger.tscn" id="9_rwx83"]
|
||||
[ext_resource type="PackedScene" uid="uid://dvrk268s7gkxh" path="res://Stations/sink.tscn" id="12_ah4xa"]
|
||||
[ext_resource type="PackedScene" uid="uid://cnjwtnhwh0i8q" path="res://Stations/dirt_station.tscn" id="13_46xi2"]
|
||||
[ext_resource type="PackedScene" uid="uid://ck5tuftqmyiue" path="res://Stations/plate_dispenser.tscn" id="14_h88sx"]
|
||||
[ext_resource type="PackedScene" uid="uid://cwnwo4i28upap" path="res://Stations/raw_burger_dispenser.tscn" id="15_m3h4l"]
|
||||
[ext_resource type="PackedScene" uid="uid://efaec6ymgabo" path="res://Stations/Counter.tscn" id="15_tpcje"]
|
||||
[ext_resource type="PackedScene" uid="uid://caf0xanmxbshy" path="res://Stations/table.tscn" id="16_g2k6a"]
|
||||
[ext_resource type="Script" uid="uid://k8ywnvlhcic4" path="res://Scenes/testworldLoad.gd" id="16_m3h4l"]
|
||||
[ext_resource type="PackedScene" uid="uid://clujaf3u776a3" path="res://addons/godot-xr-tools/objects/viewport_2d_in_3d.tscn" id="17_gatbn"]
|
||||
[ext_resource type="PackedScene" uid="uid://xtcei2uvd5li" path="res://UI/game_over_panel.tscn" id="18_3lufs"]
|
||||
[ext_resource type="Script" uid="uid://cwijoksyou1ey" path="res://scenes/GameOvercontroler.gd" id="19_3lufs"]
|
||||
[ext_resource type="Script" uid="uid://cwijoksyou1ey" path="res://Scenes/GameOvercontroler.gd" id="19_3lufs"]
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_vlqg6"]
|
||||
size = Vector3(16, 0.1, 16)
|
||||
|
||||
+48
-380
@@ -1,26 +1,21 @@
|
||||
[gd_scene format=3 uid="uid://do6mrslyqe5qe"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://d1cefhe3yyyds" path="res://scenes/multiplayer_world.gd" id="1_57ppd"]
|
||||
[ext_resource type="Script" uid="uid://d1cefhe3yyyds" path="res://Scenes/multiplayer_world.gd" id="1_57ppd"]
|
||||
[ext_resource type="PackedScene" uid="uid://j5s5wus7tuhb" path="res://XROrigin.tscn" id="2_o1b3t"]
|
||||
[ext_resource type="Material" uid="uid://cmia50cfqxxo4" path="res://textures/dev_material_3d.tres" id="3_irf4n"]
|
||||
[ext_resource type="Sky" uid="uid://c1abtpwhdm2d5" path="res://textures/sky/NightSkyHDRI009_16K.tres" id="7_n8fyw"]
|
||||
[ext_resource type="PackedScene" uid="uid://q37nqadkibbp" path="res://scenes/door.tscn" id="10_l51r2"]
|
||||
[ext_resource type="PackedScene" uid="uid://b3m2ag8g5rj4r" path="res://items/hamburger.tscn" id="11_c8vo7"]
|
||||
[ext_resource type="PackedScene" uid="uid://da6yrsnxvsrif" path="res://scenes/wall.tscn" id="11_slo47"]
|
||||
[ext_resource type="PackedScene" uid="uid://bp3v1jl8pctro" path="res://Containers/plate.tscn" id="12_1f3bw"]
|
||||
[ext_resource type="PackedScene" uid="uid://bbg7dwsbxxh1t" path="res://scenes/cube_side_dispense.tscn" id="12_57ppd"]
|
||||
[ext_resource type="PackedScene" uid="uid://dlw5geheupgpt" path="res://stations/hatch.tscn" id="12_dpf3b"]
|
||||
[ext_resource type="PackedScene" uid="uid://e4i6o5oriecx" path="res://items/PickupCube.tscn" id="13_1f3bw"]
|
||||
[ext_resource type="PackedScene" uid="uid://caf0xanmxbshy" path="res://stations/table.tscn" id="13_yjosk"]
|
||||
[ext_resource type="PackedScene" uid="uid://j7caslh27nor" path="res://stations/Hob.tscn" id="14_2ndvi"]
|
||||
[ext_resource type="Material" uid="uid://cmia50cfqxxo4" path="res://Textures/dev_material_3d.tres" id="3_irf4n"]
|
||||
[ext_resource type="PackedScene" uid="uid://j7caslh27nor" path="res://Stations/Hob.tscn" id="4_ctden"]
|
||||
[ext_resource type="Sky" uid="uid://c1abtpwhdm2d5" path="res://Textures/sky/NightSkyHDRI009_16K.tres" id="7_n8fyw"]
|
||||
[ext_resource type="PackedScene" uid="uid://c6rift56ql3f8" path="res://Stations/BurgerBunsDispenser.tscn" id="9_sq0s2"]
|
||||
[ext_resource type="PackedScene" uid="uid://cwnwo4i28upap" path="res://Stations/raw_burger_dispenser.tscn" id="11_ctden"]
|
||||
[ext_resource type="PackedScene" uid="uid://bbg7dwsbxxh1t" path="res://Scenes/cube_side_dispense.tscn" id="12_57ppd"]
|
||||
[ext_resource type="PackedScene" uid="uid://ck5tuftqmyiue" path="res://Stations/plate_dispenser.tscn" id="13_irf4n"]
|
||||
[ext_resource type="PackedScene" uid="uid://dvrk268s7gkxh" path="res://Stations/sink.tscn" id="13_o1b3t"]
|
||||
[ext_resource type="PackedScene" uid="uid://clujaf3u776a3" path="res://addons/godot-xr-tools/objects/viewport_2d_in_3d.tscn" id="14_di04w"]
|
||||
[ext_resource type="PackedScene" uid="uid://efaec6ymgabo" path="res://stations/Counter.tscn" id="15_di04w"]
|
||||
[ext_resource type="PackedScene" uid="uid://cnjwtnhwh0i8q" path="res://Stations/dirt_station.tscn" id="14_irf4n"]
|
||||
[ext_resource type="PackedScene" uid="uid://efaec6ymgabo" path="res://Stations/Counter.tscn" id="15_di04w"]
|
||||
[ext_resource type="PackedScene" uid="uid://xtcei2uvd5li" path="res://UI/game_over_panel.tscn" id="15_eoxxy"]
|
||||
[ext_resource type="PackedScene" uid="uid://ck5tuftqmyiue" path="res://stations/plate_dispenser.tscn" id="15_qxyk8"]
|
||||
[ext_resource type="Script" uid="uid://cwijoksyou1ey" path="res://scenes/GameOvercontroler.gd" id="16_n8fyw"]
|
||||
[ext_resource type="PackedScene" uid="uid://dvrk268s7gkxh" path="res://stations/sink.tscn" id="16_u5c1m"]
|
||||
[ext_resource type="PackedScene" uid="uid://cwnwo4i28upap" path="res://stations/raw_burger_dispenser.tscn" id="17_y7baw"]
|
||||
[ext_resource type="PackedScene" uid="uid://c6rift56ql3f8" path="res://stations/BurgerBunsDispenser.tscn" id="18_wvcs1"]
|
||||
[ext_resource type="PackedScene" uid="uid://caf0xanmxbshy" path="res://Stations/table.tscn" id="16_di04w"]
|
||||
[ext_resource type="Script" uid="uid://cwijoksyou1ey" path="res://Scenes/GameOvercontroler.gd" id="16_n8fyw"]
|
||||
|
||||
[sub_resource type="Environment" id="Environment_bvwq1"]
|
||||
background_mode = 2
|
||||
@@ -44,24 +39,6 @@ size = Vector3(20, 0.1, 20)
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_24d3s"]
|
||||
size = Vector3(20, 10, 0.1)
|
||||
|
||||
[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_vlqg6"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_24d3s"]
|
||||
albedo_color = Color(0.35, 0.12250001, 0.12250001, 1)
|
||||
|
||||
[sub_resource type="PlaneMesh" id="PlaneMesh_vlqg6"]
|
||||
material = SubResource("StandardMaterial3D_24d3s")
|
||||
size = Vector2(6, 10)
|
||||
center_offset = Vector3(3, 0, 0)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_yqrpd"]
|
||||
albedo_color = Color(0.35, 0.35, 0.35, 1)
|
||||
|
||||
[sub_resource type="PlaneMesh" id="PlaneMesh_kk3mq"]
|
||||
material = SubResource("StandardMaterial3D_yqrpd")
|
||||
size = Vector2(6, 10)
|
||||
center_offset = Vector3(-3, 0, 0)
|
||||
|
||||
[node name="Main" type="Node3D" unique_id=1312265607]
|
||||
script = ExtResource("1_57ppd")
|
||||
|
||||
@@ -69,21 +46,16 @@ script = ExtResource("1_57ppd")
|
||||
environment = SubResource("Environment_bvwq1")
|
||||
|
||||
[node name="XROrigin3D" parent="." unique_id=2055526621 groups=["local_xr_origin"] instance=ExtResource("2_o1b3t")]
|
||||
transform = Transform3D(-1, 0, 8.742278e-08, 0, 1, 0, -8.742278e-08, 0, -1, -1.8943893, 0.9667189, -4.301105)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.9667189, 0)
|
||||
|
||||
[node name="WorldContent" type="Node3D" parent="." unique_id=262398468]
|
||||
|
||||
[node name="Players" type="Node3D" parent="." unique_id=1772076868]
|
||||
|
||||
[node name="Stations" type="Node3D" parent="." unique_id=730717613]
|
||||
transform = Transform3D(-1, 0, 8.742278e-08, 0, 1, 0, -8.742278e-08, 0, -1, 1.8000001, 0, 1.8000001)
|
||||
|
||||
[node name="ItemsSpawner" type="MultiplayerSpawner" parent="." unique_id=2051771581]
|
||||
_spawnable_scenes = PackedStringArray("uid://cwwnbx5uat3fw")
|
||||
spawn_path = NodePath("../WorldContent")
|
||||
|
||||
[node name="PlayersSpawner" type="MultiplayerSpawner" parent="." unique_id=619815427]
|
||||
_spawnable_scenes = PackedStringArray("res://Player/net_player.tscn")
|
||||
spawn_path = NodePath("../Players")
|
||||
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1376644384]
|
||||
@@ -96,7 +68,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.05, 0)
|
||||
shape = SubResource("BoxShape3D_vlqg6")
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="Floor/CollisionShape3D" unique_id=1939997056]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.001, -0.003)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00018164515, 0.0006352663, -0.002532661)
|
||||
mesh = SubResource("BoxMesh_24d3s")
|
||||
|
||||
[node name="InvisibleWalls" type="StaticBody3D" parent="." unique_id=315740174]
|
||||
@@ -117,17 +89,45 @@ shape = SubResource("BoxShape3D_24d3s")
|
||||
transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 9.934778, 4.297072, -0.018813023)
|
||||
shape = SubResource("BoxShape3D_24d3s")
|
||||
|
||||
[node name="Table" parent="." unique_id=1863572470 instance=ExtResource("16_di04w")]
|
||||
transform = Transform3D(-1, 0, 8.742277e-08, 0, 1, 0, -8.742277e-08, 0, -1, 3.6294794, 0.54177135, 0.0973109)
|
||||
primary_duration = 35.0
|
||||
|
||||
[node name="Hob" parent="." unique_id=1687971542 instance=ExtResource("4_ctden")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.35077858, 0.50655985, -1.2833805)
|
||||
|
||||
[node name="Sink" parent="." unique_id=2055277359 instance=ExtResource("13_o1b3t")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.24685252, 0.51461196, -1.2828919)
|
||||
|
||||
[node name="DirtStation" parent="." unique_id=160842153 instance=ExtResource("14_irf4n")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.7978771, 0.50655985, -1.2598276)
|
||||
|
||||
[node name="Counter" parent="." unique_id=1487893288 instance=ExtResource("15_di04w")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.94890714, 0.5115015, -1.3041471)
|
||||
|
||||
[node name="Counter2" parent="." unique_id=368890752 instance=ExtResource("15_di04w")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -3.9477215, 0.5115016, 1.6896921)
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -1.5477214, 0.5115016, -0.1103079)
|
||||
|
||||
[node name="Counter4" parent="." unique_id=1748373996 instance=ExtResource("15_di04w")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -3.9477215, 0.5115016, 1.0913646)
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -1.5477214, 0.5115016, -0.70863545)
|
||||
|
||||
[node name="Counter3" parent="." unique_id=1498817646 instance=ExtResource("15_di04w")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -3.9477215, 0.5115016, 2.2925892)
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -1.5477214, 0.5115016, 0.49258912)
|
||||
|
||||
[node name="Counter5" parent="Counter3" unique_id=200341072 instance=ExtResource("15_di04w")]
|
||||
transform = Transform3D(-1, 0, 4.371139e-08, 0, 1, 0, -4.371139e-08, 0, -1, -0.12681937, 2.3841858e-07, 3.538899)
|
||||
|
||||
[node name="BurgerBunsDispenser" parent="." unique_id=1720683779 instance=ExtResource("9_sq0s2")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.5242664, 0.008738995, 1.1073059)
|
||||
|
||||
[node name="RawBurgerDispenser" parent="." unique_id=235630131 instance=ExtResource("11_ctden")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.8896122, 0.008738995, 1.0872328)
|
||||
|
||||
[node name="PlateDispenser" parent="." unique_id=710538846 instance=ExtResource("13_irf4n")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.26631236, 0.008738995, 1.087036)
|
||||
|
||||
[node name="GameOverPanel" parent="." unique_id=1234658657 instance=ExtResource("14_di04w")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.428178, 2.8122003, 3)
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -2.0281777, 2.8122003, 0)
|
||||
scene = ExtResource("15_eoxxy")
|
||||
viewport_size = Vector2(900, 600)
|
||||
transparent = 0
|
||||
@@ -138,336 +138,4 @@ scene_properties_keys = PackedStringArray("game_over_panel.gd")
|
||||
script = ExtResource("16_n8fyw")
|
||||
|
||||
[node name="CubeSideDispenser2" parent="." unique_id=200950572 instance=ExtResource("12_57ppd")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.8484511, 0, 2.9119425)
|
||||
|
||||
[node name="Resturant" type="Node3D" parent="." unique_id=1083608050]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.6212463e-05, -0.010500789, 0.0014204979)
|
||||
|
||||
[node name="StaticBody3D" type="StaticBody3D" parent="Resturant" unique_id=1814285538]
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="Resturant/StaticBody3D" unique_id=1661443874]
|
||||
shape = SubResource("WorldBoundaryShape3D_vlqg6")
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="Resturant" unique_id=888732490]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 0, 0.01, 0)
|
||||
mesh = SubResource("PlaneMesh_vlqg6")
|
||||
|
||||
[node name="MeshInstance3D2" type="MeshInstance3D" parent="Resturant" unique_id=836421800]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 0, 0.01, 0)
|
||||
mesh = SubResource("PlaneMesh_kk3mq")
|
||||
|
||||
[node name="Door" parent="Resturant" unique_id=964400290 instance=ExtResource("10_l51r2")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.4, 0, 0)
|
||||
|
||||
[node name="Walls" type="Node3D" parent="Resturant" unique_id=1804204696]
|
||||
|
||||
[node name="Wall" parent="Resturant/Walls" unique_id=434181749 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.6000001, 0, 0)
|
||||
|
||||
[node name="Wall2" parent="Resturant/Walls" unique_id=1183111925 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4.2, 0, 0)
|
||||
|
||||
[node name="Wall3" parent="Resturant/Walls" unique_id=1561473949 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4.8, 0, 0)
|
||||
|
||||
[node name="Wall4" parent="Resturant/Walls" unique_id=1593528497 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.60000014, 0, 0)
|
||||
|
||||
[node name="Wall5" parent="Resturant/Walls" unique_id=2071060763 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.3841858e-07, 0, 0)
|
||||
|
||||
[node name="Wall6" parent="Resturant/Walls" unique_id=1607012472 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5999999, 0, 0)
|
||||
|
||||
[node name="Wall7" parent="Resturant/Walls" unique_id=990512055 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.8, 0, 0)
|
||||
|
||||
[node name="Wall8" parent="Resturant/Walls" unique_id=751907897 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.200001, 0, 0)
|
||||
|
||||
[node name="Wall9" parent="Resturant/Walls" unique_id=358743133 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.6000004, 0, 0)
|
||||
|
||||
[node name="Wall10" parent="Resturant/Walls" unique_id=1374842161 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.2000002, 0, 0)
|
||||
|
||||
[node name="Wall11" parent="Resturant/Walls" unique_id=1408132826 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.8000002, 0, 0)
|
||||
|
||||
[node name="Wall12" parent="Resturant/Walls" unique_id=1937209307 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.60000014, 0, -6)
|
||||
|
||||
[node name="Wall13" parent="Resturant/Walls" unique_id=1714975985 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.3841858e-07, 0, -6)
|
||||
|
||||
[node name="Wall14" parent="Resturant/Walls" unique_id=1732121903 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5999999, 0, -6)
|
||||
|
||||
[node name="Wall15" parent="Resturant/Walls" unique_id=1311494308 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.2000002, 0, -6)
|
||||
|
||||
[node name="Wall16" parent="Resturant/Walls" unique_id=364484178 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.8000002, 0, -6)
|
||||
|
||||
[node name="Wall17" parent="Resturant/Walls" unique_id=485501605 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.3999999, 0, -6)
|
||||
|
||||
[node name="Wall18" parent="Resturant/Walls" unique_id=378206350 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.9999998, 0, -6)
|
||||
|
||||
[node name="Wall19" parent="Resturant/Walls" unique_id=294380188 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.6, 0, -6)
|
||||
|
||||
[node name="Wall20" parent="Resturant/Walls" unique_id=1122365254 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.7999998, 0, -6)
|
||||
|
||||
[node name="Wall21" parent="Resturant/Walls" unique_id=111446334 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.1999998, 0, -6)
|
||||
|
||||
[node name="Wall22" parent="Resturant/Walls" unique_id=999769595 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.6000001, 0, -6)
|
||||
|
||||
[node name="Wall23" parent="Resturant/Walls" unique_id=1832835858 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.0000002, 0, -6)
|
||||
|
||||
[node name="Wall24" parent="Resturant/Walls" unique_id=927052367 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.4, 0, -6)
|
||||
|
||||
[node name="Wall25" parent="Resturant/Walls" unique_id=1163278593 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.2000003, 0, -6)
|
||||
|
||||
[node name="Wall26" parent="Resturant/Walls" unique_id=1074868471 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.8, 0, -6)
|
||||
|
||||
[node name="Wall27" parent="Resturant/Walls" unique_id=510219954 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4.2, 0, -6)
|
||||
|
||||
[node name="Wall28" parent="Resturant/Walls" unique_id=1615161517 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4.8, 0, -6)
|
||||
|
||||
[node name="Wall29" parent="Resturant/Walls" unique_id=11605666 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.60000014, 0, 6)
|
||||
|
||||
[node name="Wall30" parent="Resturant/Walls" unique_id=2032513926 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.3841858e-07, 0, 6)
|
||||
|
||||
[node name="Wall31" parent="Resturant/Walls" unique_id=1308803807 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5999999, 0, 6)
|
||||
|
||||
[node name="Wall32" parent="Resturant/Walls" unique_id=493869823 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.2000002, 0, 6)
|
||||
|
||||
[node name="Wall33" parent="Resturant/Walls" unique_id=699535234 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.8000002, 0, 6)
|
||||
|
||||
[node name="Wall34" parent="Resturant/Walls" unique_id=238973326 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.3999999, 0, 6)
|
||||
|
||||
[node name="Wall35" parent="Resturant/Walls" unique_id=836717647 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.9999998, 0, 6)
|
||||
|
||||
[node name="Wall36" parent="Resturant/Walls" unique_id=446988148 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.6, 0, 6)
|
||||
|
||||
[node name="Wall37" parent="Resturant/Walls" unique_id=49950412 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.7999998, 0, 6)
|
||||
|
||||
[node name="Wall38" parent="Resturant/Walls" unique_id=607255208 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.1999998, 0, 6)
|
||||
|
||||
[node name="Wall39" parent="Resturant/Walls" unique_id=1089866573 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.6000001, 0, 6)
|
||||
|
||||
[node name="Wall40" parent="Resturant/Walls" unique_id=2078240231 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.0000002, 0, 6)
|
||||
|
||||
[node name="Wall41" parent="Resturant/Walls" unique_id=592669722 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.4, 0, 6)
|
||||
|
||||
[node name="Wall42" parent="Resturant/Walls" unique_id=1393176200 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.2000003, 0, 6)
|
||||
|
||||
[node name="Wall43" parent="Resturant/Walls" unique_id=1885346301 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.8, 0, 6)
|
||||
|
||||
[node name="Wall44" parent="Resturant/Walls" unique_id=2021916640 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4.2, 0, 6)
|
||||
|
||||
[node name="Wall45" parent="Resturant/Walls" unique_id=1289086279 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4.8, 0, 6)
|
||||
|
||||
[node name="Wall46" parent="Resturant/Walls" unique_id=396503450 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8, 0, 1.2000003)
|
||||
|
||||
[node name="Wall47" parent="Resturant/Walls" unique_id=300373285 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8, 0, 0.6000004)
|
||||
|
||||
[node name="Wall49" parent="Resturant/Walls" unique_id=1072273787 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8, 0, 1.8000002)
|
||||
|
||||
[node name="Wall50" parent="Resturant/Walls" unique_id=896022741 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8, 0, 2.4000006)
|
||||
|
||||
[node name="Wall51" parent="Resturant/Walls" unique_id=2089599721 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8000007, 0, -1.7999992)
|
||||
|
||||
[node name="Wall52" parent="Resturant/Walls" unique_id=965479737 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8000007, 0, -2.3999996)
|
||||
|
||||
[node name="Wall53" parent="Resturant/Walls" unique_id=43762332 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8000007, 0, -3)
|
||||
|
||||
[node name="Wall54" parent="Resturant/Walls" unique_id=921007094 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8000007, 0, -1.1999998)
|
||||
|
||||
[node name="Wall55" parent="Resturant/Walls" unique_id=2144411980 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8, 0, -0.5999994)
|
||||
|
||||
[node name="Wall56" parent="Resturant/Walls" unique_id=370330637 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8, 0, 4.2000003)
|
||||
|
||||
[node name="Wall57" parent="Resturant/Walls" unique_id=537562206 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8, 0, 3.6000004)
|
||||
|
||||
[node name="Wall58" parent="Resturant/Walls" unique_id=1918288432 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8, 0, 3.0000002)
|
||||
|
||||
[node name="Wall59" parent="Resturant/Walls" unique_id=944759951 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8, 0, 4.8000007)
|
||||
|
||||
[node name="Wall60" parent="Resturant/Walls" unique_id=1128237563 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8, 0, 5.4000006)
|
||||
|
||||
[node name="Wall61" parent="Resturant/Walls" unique_id=1755969864 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8000007, 0, -3.5999994)
|
||||
|
||||
[node name="Wall62" parent="Resturant/Walls" unique_id=949006312 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8000007, 0, -4.2)
|
||||
|
||||
[node name="Wall63" parent="Resturant/Walls" unique_id=1826688195 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8000007, 0, -4.799999)
|
||||
|
||||
[node name="Wall64" parent="Resturant/Walls" unique_id=116523059 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8000007, 0, -5.3999996)
|
||||
|
||||
[node name="Wall65" parent="Resturant/Walls" unique_id=711153775 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.8, 0, 1.2000003)
|
||||
|
||||
[node name="Wall66" parent="Resturant/Walls" unique_id=741047 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.8, 0, 0.6000004)
|
||||
|
||||
[node name="Wall67" parent="Resturant/Walls" unique_id=664067327 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.8, 0, 1.8000002)
|
||||
|
||||
[node name="Wall68" parent="Resturant/Walls" unique_id=1294537251 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.8, 0, 2.4000006)
|
||||
|
||||
[node name="Wall69" parent="Resturant/Walls" unique_id=1152383 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.7999997, 0, -1.7999992)
|
||||
|
||||
[node name="Wall70" parent="Resturant/Walls" unique_id=1710172322 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.7999997, 0, -2.3999996)
|
||||
|
||||
[node name="Wall71" parent="Resturant/Walls" unique_id=597518110 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.7999997, 0, -3)
|
||||
|
||||
[node name="Wall72" parent="Resturant/Walls" unique_id=1028573693 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.7999997, 0, -1.1999998)
|
||||
|
||||
[node name="Wall73" parent="Resturant/Walls" unique_id=1232042046 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.8, 0, -0.5999994)
|
||||
|
||||
[node name="Wall74" parent="Resturant/Walls" unique_id=1159091046 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.8, 0, 4.2000003)
|
||||
|
||||
[node name="Wall75" parent="Resturant/Walls" unique_id=904470924 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.8, 0, 3.6000004)
|
||||
|
||||
[node name="Wall76" parent="Resturant/Walls" unique_id=1709758099 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.8, 0, 3.0000002)
|
||||
|
||||
[node name="Wall77" parent="Resturant/Walls" unique_id=1252446997 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.8, 0, 4.8000007)
|
||||
|
||||
[node name="Wall78" parent="Resturant/Walls" unique_id=1207579491 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.8, 0, 5.4000006)
|
||||
|
||||
[node name="Wall79" parent="Resturant/Walls" unique_id=1798073342 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.7999997, 0, -3.5999994)
|
||||
|
||||
[node name="Wall80" parent="Resturant/Walls" unique_id=36005990 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.7999997, 0, -4.2)
|
||||
|
||||
[node name="Wall81" parent="Resturant/Walls" unique_id=79516947 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.7999997, 0, -4.799999)
|
||||
|
||||
[node name="Wall82" parent="Resturant/Walls" unique_id=1363235805 instance=ExtResource("11_slo47")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.7999997, 0, -5.3999996)
|
||||
|
||||
[node name="Hatches" type="Node3D" parent="Resturant" unique_id=1561045769]
|
||||
|
||||
[node name="ServingCounter" parent="Resturant/Hatches" unique_id=1341321603 instance=ExtResource("12_dpf3b")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.2, 0, 0)
|
||||
|
||||
[node name="ServingCounter2" parent="Resturant/Hatches" unique_id=1325232371 instance=ExtResource("12_dpf3b")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.8000001, 0, 0)
|
||||
|
||||
[node name="ServingCounter3" parent="Resturant/Hatches" unique_id=539914523 instance=ExtResource("12_dpf3b")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.2, 0, 0)
|
||||
|
||||
[node name="ServingCounter4" parent="Resturant/Hatches" unique_id=2078770951 instance=ExtResource("12_dpf3b")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.4, 0, 0)
|
||||
|
||||
[node name="ServingCounter5" parent="Resturant/Hatches" unique_id=2019049083 instance=ExtResource("12_dpf3b")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3, 0, 0)
|
||||
|
||||
[node name="Table2" parent="Resturant" unique_id=1457688277 instance=ExtResource("13_yjosk")]
|
||||
transform = Transform3D(1, 0, -1.7484555e-07, 0, 1, 0, 1.7484555e-07, 0, 1, 0.5999999, 0.48000002, -3)
|
||||
|
||||
[node name="Hob" parent="Resturant" unique_id=172418174 instance=ExtResource("14_2ndvi")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 4.2000003, 0.54, 1.7999992)
|
||||
|
||||
[node name="Hob2" parent="Resturant" unique_id=980569400 instance=ExtResource("14_2ndvi")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 4.200001, 0.54, 1.1999999)
|
||||
|
||||
[node name="Counter" parent="Resturant" unique_id=1487893288 instance=ExtResource("15_di04w")]
|
||||
transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 4.200001, 0.54, 2.3999996)
|
||||
|
||||
[node name="Counter2" parent="Resturant" unique_id=5128912 instance=ExtResource("15_di04w")]
|
||||
transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 4.200001, 0.54, 3)
|
||||
|
||||
[node name="Counter3" parent="Resturant" unique_id=1298458612 instance=ExtResource("15_di04w")]
|
||||
transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 4.2000003, 0.54, 0.5999999)
|
||||
|
||||
[node name="Counter4" parent="Resturant" unique_id=1764980142 instance=ExtResource("15_di04w")]
|
||||
transform = Transform3D(1.3113416e-07, 0, 1, 0, 1, 0, -1, 0, 1.3113416e-07, 4.7683716e-07, 0.54, 2.3999996)
|
||||
|
||||
[node name="Counter5" parent="Resturant" unique_id=1015820252 instance=ExtResource("15_di04w")]
|
||||
transform = Transform3D(1.3113416e-07, 0, 1, 0, 1, 0, -1, 0, 1.3113416e-07, 4.7683716e-07, 0.54, 3)
|
||||
|
||||
[node name="PlateDispenser" parent="Resturant" unique_id=710538846 instance=ExtResource("15_qxyk8")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.6)
|
||||
|
||||
[node name="Sink" parent="Resturant" unique_id=2055277359 instance=ExtResource("16_u5c1m")]
|
||||
transform = Transform3D(-1, 0, 8.742278e-08, 0, 1, 0, -8.742278e-08, 0, -1, 0.6, 0.48000002, -0.6)
|
||||
|
||||
[node name="Counter6" parent="Resturant" unique_id=2141915043 instance=ExtResource("15_di04w")]
|
||||
transform = Transform3D(-1, 0, 8.742277e-08, 0, 1, 0, -8.742277e-08, 0, -1, 4.7683716e-07, 0.54, -0.6000006)
|
||||
|
||||
[node name="RawBurgerDispenser" parent="Resturant" unique_id=235630131 instance=ExtResource("17_y7baw")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1.2)
|
||||
|
||||
[node name="BurgerBunsDispenser" parent="Resturant" unique_id=1720683779 instance=ExtResource("18_wvcs1")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1.8000001)
|
||||
|
||||
[node name="Table3" parent="Resturant" unique_id=1863572470 instance=ExtResource("13_yjosk")]
|
||||
transform = Transform3D(-1, 0, 2.6226832e-07, 0, 1, 0, -2.6226832e-07, 0, -1, 3, 0.48000002, -3)
|
||||
|
||||
[node name="Hamburger" parent="." unique_id=1675596942 instance=ExtResource("11_c8vo7")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.8000001, -0.6)
|
||||
|
||||
[node name="Plate" parent="." unique_id=190487773 instance=ExtResource("12_1f3bw")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.3800001, -0.6)
|
||||
|
||||
[node name="PickableObject" parent="." unique_id=1084951838 instance=ExtResource("13_1f3bw")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.6, 1.2, -0.6)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.35154933, 0, 1.1119425)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
[gd_scene format=3 uid="uid://bbg7dwsbxxh1t"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://0y6wnjcsum6" path="res://stations/item_dispenser.gd" id="1_4m60m"]
|
||||
[ext_resource type="PackedScene" uid="uid://e4i6o5oriecx" path="res://items/PickupCube.tscn" id="2_nn8tr"]
|
||||
[ext_resource type="Script" uid="uid://0y6wnjcsum6" path="res://Stations/item_dispenser.gd" id="1_4m60m"]
|
||||
[ext_resource type="PackedScene" uid="uid://e4i6o5oriecx" path="res://Items/PickupCube.tscn" id="2_nn8tr"]
|
||||
[ext_resource type="Script" uid="uid://cquqe4f1m1sw6" path="res://addons/godot-xr-tools/objects/snap_zone.gd" id="3_ahn1e"]
|
||||
[ext_resource type="PackedScene" uid="uid://mpapt5mkbuao" path="res://prefabs/slide_off_dome.tscn" id="4_nn8tr"]
|
||||
[ext_resource type="PackedScene" uid="uid://mpapt5mkbuao" path="res://Prefabs/slide_off_dome.tscn" id="4_nn8tr"]
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_xmbo2"]
|
||||
radius = 0.3
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
[gd_scene format=3 uid="uid://q37nqadkibbp"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_p8umn"]
|
||||
albedo_color = Color(0.38, 0.35162666, 0.3268, 1)
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_vlqg6"]
|
||||
size = Vector3(1.2, 0.78000003, 0.6)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_b53mu"]
|
||||
albedo_color = Color(0.46, 0.32261336, 0.2024, 1)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_hgrxd"]
|
||||
albedo_color = Color(0.46, 0.46, 0.46, 1)
|
||||
metallic = 0.73
|
||||
metallic_specular = 0.83
|
||||
roughness = 0.3
|
||||
|
||||
[node name="Door" type="StaticBody3D" unique_id=964400290]
|
||||
|
||||
[node name="CSGCombinerWall" type="CSGCombiner3D" parent="." unique_id=1966373401]
|
||||
use_collision = true
|
||||
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="CSGCombinerWall" unique_id=1920740063]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 3.1, 0)
|
||||
size = Vector3(0.6, 1.8000001, 0.6)
|
||||
material = SubResource("StandardMaterial3D_p8umn")
|
||||
|
||||
[node name="CSGBox3D3" type="CSGBox3D" parent="CSGCombinerWall" unique_id=747780779]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.5999999, 3.1, 0)
|
||||
size = Vector3(0.6, 1.8000001, 0.6)
|
||||
material = SubResource("StandardMaterial3D_p8umn")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1127621100]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.3, 2.6302783, 0)
|
||||
shape = SubResource("BoxShape3D_vlqg6")
|
||||
|
||||
[node name="CSGCombinerDoor" type="CSGCombiner3D" parent="." unique_id=522743190]
|
||||
transform = Transform3D(0.087155804, 0, -0.9961947, 0, 1, 0, 0.9961947, 0, 0.087155804, -0.89999986, 0, 0)
|
||||
use_collision = true
|
||||
|
||||
[node name="CSGBox3D5" type="CSGBox3D" parent="CSGCombinerDoor" unique_id=34425292]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.29999995, 1.1, 0)
|
||||
size = Vector3(0.6, 2.2, 0.05)
|
||||
material = SubResource("StandardMaterial3D_b53mu")
|
||||
|
||||
[node name="CSGBox3D6" type="CSGBox3D" parent="CSGCombinerDoor" unique_id=397451563]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.48594975, 1.1787889, -0.0049031973)
|
||||
size = Vector3(0.22, 0.42, 0.06)
|
||||
material = SubResource("StandardMaterial3D_hgrxd")
|
||||
|
||||
[node name="CSGCylinder3D" type="CSGCylinder3D" parent="CSGCombinerDoor" unique_id=3701079]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.29999995, 1.6800001, 0)
|
||||
operation = 2
|
||||
radius = 0.15
|
||||
height = 0.2
|
||||
sides = 32
|
||||
|
||||
[node name="CSGCombinerDoor2" type="CSGCombiner3D" parent="." unique_id=454018491]
|
||||
transform = Transform3D(-0.087155804, 8.709011e-08, 0.9961947, 0, 1, -8.742278e-08, 0.9961947, 7.619402e-09, 0.087155804, 0.3000002, 0, 0)
|
||||
use_collision = true
|
||||
|
||||
[node name="CSGBox3D5" type="CSGBox3D" parent="CSGCombinerDoor2" unique_id=312337639]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.29999995, 1.1, 0)
|
||||
size = Vector3(0.6, 2.2, 0.05)
|
||||
material = SubResource("StandardMaterial3D_b53mu")
|
||||
|
||||
[node name="CSGBox3D6" type="CSGBox3D" parent="CSGCombinerDoor2" unique_id=359915386]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.49211836, 1.1787889, -0.0049031973)
|
||||
size = Vector3(0.22, 0.42, 0.06)
|
||||
material = SubResource("StandardMaterial3D_hgrxd")
|
||||
|
||||
[node name="CSGCylinder3D" type="CSGCylinder3D" parent="CSGCombinerDoor2" unique_id=1793882462]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.29999995, 1.6800001, 0)
|
||||
operation = 2
|
||||
radius = 0.15
|
||||
height = 0.2
|
||||
sides = 32
|
||||
+12
-12
@@ -2,19 +2,19 @@
|
||||
|
||||
[ext_resource type="Script" uid="uid://b4ldkd3ngs0vp" path="res://main.gd" id="1_72gy5"]
|
||||
[ext_resource type="PackedScene" uid="uid://j5s5wus7tuhb" path="res://XROrigin.tscn" id="2_8wkh3"]
|
||||
[ext_resource type="Material" uid="uid://cmia50cfqxxo4" path="res://textures/dev_material_3d.tres" id="3_m56cs"]
|
||||
[ext_resource type="PackedScene" uid="uid://j7caslh27nor" path="res://stations/Hob.tscn" id="4_5kvh0"]
|
||||
[ext_resource type="Sky" uid="uid://c1abtpwhdm2d5" path="res://textures/sky/NightSkyHDRI009_16K.tres" id="5_l1qm6"]
|
||||
[ext_resource type="PackedScene" uid="uid://c0lknik4noobs" path="res://items/BurgerBuns.tscn" id="6_bktvt"]
|
||||
[ext_resource type="PackedScene" uid="uid://c6rift56ql3f8" path="res://stations/BurgerBunsDispenser.tscn" id="7_1nkd0"]
|
||||
[ext_resource type="Material" uid="uid://cmia50cfqxxo4" path="res://Textures/dev_material_3d.tres" id="3_m56cs"]
|
||||
[ext_resource type="PackedScene" uid="uid://j7caslh27nor" path="res://Stations/Hob.tscn" id="4_5kvh0"]
|
||||
[ext_resource type="Sky" uid="uid://c1abtpwhdm2d5" path="res://Textures/sky/NightSkyHDRI009_16K.tres" id="5_l1qm6"]
|
||||
[ext_resource type="PackedScene" uid="uid://c0lknik4noobs" path="res://Items/BurgerBuns.tscn" id="6_bktvt"]
|
||||
[ext_resource type="PackedScene" uid="uid://c6rift56ql3f8" path="res://Stations/BurgerBunsDispenser.tscn" id="7_1nkd0"]
|
||||
[ext_resource type="PackedScene" uid="uid://bp3v1jl8pctro" path="res://Containers/plate.tscn" id="8_l1owj"]
|
||||
[ext_resource type="PackedScene" uid="uid://dpot5qie20vf6" path="res://items/burger.tscn" id="9_220hi"]
|
||||
[ext_resource type="PackedScene" uid="uid://b3m2ag8g5rj4r" path="res://items/hamburger.tscn" id="10_qacki"]
|
||||
[ext_resource type="PackedScene" uid="uid://e4i6o5oriecx" path="res://items/PickupCube.tscn" id="11_npf8s"]
|
||||
[ext_resource type="PackedScene" uid="uid://dvrk268s7gkxh" path="res://stations/sink.tscn" id="12_8apyq"]
|
||||
[ext_resource type="PackedScene" uid="uid://cnjwtnhwh0i8q" path="res://stations/dirt_station.tscn" id="13_jnwcx"]
|
||||
[ext_resource type="PackedScene" uid="uid://cfc4ho67u4r5e" path="res://items/cooked_burger.tscn" id="14_1lg2m"]
|
||||
[ext_resource type="PackedScene" uid="uid://efaec6ymgabo" path="res://stations/Counter.tscn" id="15_yy81s"]
|
||||
[ext_resource type="PackedScene" uid="uid://dpot5qie20vf6" path="res://Items/burger.tscn" id="9_220hi"]
|
||||
[ext_resource type="PackedScene" uid="uid://b3m2ag8g5rj4r" path="res://Items/hamburger.tscn" id="10_qacki"]
|
||||
[ext_resource type="PackedScene" uid="uid://e4i6o5oriecx" path="res://Items/PickupCube.tscn" id="11_npf8s"]
|
||||
[ext_resource type="PackedScene" uid="uid://dvrk268s7gkxh" path="res://Stations/sink.tscn" id="12_8apyq"]
|
||||
[ext_resource type="PackedScene" uid="uid://cnjwtnhwh0i8q" path="res://Stations/dirt_station.tscn" id="13_jnwcx"]
|
||||
[ext_resource type="PackedScene" uid="uid://cfc4ho67u4r5e" path="res://Items/cooked_burger.tscn" id="14_1lg2m"]
|
||||
[ext_resource type="PackedScene" uid="uid://efaec6ymgabo" path="res://Stations/Counter.tscn" id="15_yy81s"]
|
||||
[ext_resource type="PackedScene" uid="uid://clujaf3u776a3" path="res://addons/godot-xr-tools/objects/viewport_2d_in_3d.tscn" id="16_vp"]
|
||||
[ext_resource type="PackedScene" path="res://UI/main_menu_panel.tscn" id="17_panel"]
|
||||
|
||||
|
||||
+4
-14
@@ -1,12 +1,9 @@
|
||||
[gd_scene format=3 uid="uid://c30i6h32w8p47"]
|
||||
|
||||
[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="Material" uid="uid://cmia50cfqxxo4" path="res://textures/dev_material_3d.tres" id="3_75ecy"]
|
||||
[ext_resource type="Sky" uid="uid://c1abtpwhdm2d5" path="res://textures/sky/NightSkyHDRI009_16K.tres" id="5_c3xgf"]
|
||||
[ext_resource type="Script" uid="uid://fyd2rjramhbb" path="res://prefabs/kitchen_instantiator.gd" id="6_pw2j5"]
|
||||
[ext_resource type="PackedScene" uid="uid://damrxtlt7uswf" path="res://content/station_layouts/small_line.tscn" id="7_0sjqq"]
|
||||
[ext_resource type="PackedScene" uid="uid://j7caslh27nor" path="res://stations/Hob.tscn" id="8_0sjqq"]
|
||||
[ext_resource type="Material" uid="uid://cmia50cfqxxo4" path="res://Textures/dev_material_3d.tres" id="3_75ecy"]
|
||||
[ext_resource type="Sky" uid="uid://c1abtpwhdm2d5" path="res://Textures/sky/NightSkyHDRI009_16K.tres" id="5_c3xgf"]
|
||||
[ext_resource type="Script" uid="uid://biu4qr3nr1eny" path="res://test/mp_test_driver.gd" id="99_mptst"]
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_vlqg6"]
|
||||
@@ -58,7 +55,6 @@ environment = SubResource("Environment_bvwq1")
|
||||
spawn_path = NodePath("../WorldContent")
|
||||
|
||||
[node name="PlayersSpawner" type="MultiplayerSpawner" parent="." unique_id=106645565]
|
||||
_spawnable_scenes = PackedStringArray("res://Player/net_player.tscn")
|
||||
spawn_path = NodePath("../Players")
|
||||
|
||||
[node name="StaticBody3D" type="StaticBody3D" parent="." unique_id=4404969]
|
||||
@@ -79,11 +75,5 @@ shape = SubResource("BoxShape3D_arao0")
|
||||
transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 7.502467, 9.197384, -0.018813023)
|
||||
shape = SubResource("BoxShape3D_arao0")
|
||||
|
||||
[node name="TestDriver" type="Node" parent="." unique_id=580575192]
|
||||
[node name="TestDriver" type="Node" parent="."]
|
||||
script = ExtResource("99_mptst")
|
||||
|
||||
[node name="KitchenInstantiator" type="Node3D" parent="." unique_id=1328198547]
|
||||
transform = Transform3D(1, 0, -1.7484555e-07, 0, 1, 0, 1.7484555e-07, 0, 1, 0, 0, -1.2)
|
||||
script = ExtResource("6_pw2j5")
|
||||
kitchen_scene = ExtResource("7_0sjqq")
|
||||
hob_scene = ExtResource("8_0sjqq")
|
||||
|
||||
+57
-66
@@ -1,26 +1,26 @@
|
||||
extends Node3D
|
||||
|
||||
## World script for the multiplayer scene. Consumes the host/join request set
|
||||
## 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.
|
||||
## World script for the multiplayer scene. Starts the host/join the menu asked
|
||||
## for, turns the authored kitchen into replicated objects, gives every connected
|
||||
## peer an avatar, and returns to the menu when the session ends.
|
||||
##
|
||||
## The world's actual content (stations, items) is NOT baked into this scene —
|
||||
## it's spawned at runtime from Net/world_layout.gd via NetworkManager, so a
|
||||
## joining client receives it from the server (MultiplayerSpawner replays
|
||||
## existing spawns to late joiners) instead of relying on its own local copy
|
||||
## matching.
|
||||
## The world's contents are NOT left baked into the scene file. Whatever the
|
||||
## scene authors is treated as a template: the server spawns real copies of it
|
||||
## through NetWorld and every peer drops its own authored originals. A client
|
||||
## therefore shows the server's world rather than assuming its local copy of the
|
||||
## scene matches — which is also exactly the path a player joining mid-session
|
||||
## takes.
|
||||
|
||||
const PLAYER_SCENE := preload("res://Player/net_player.tscn")
|
||||
const PLAYER_SCENE := "res://Player/net_player.tscn"
|
||||
|
||||
## Whether to spawn the full WorldLayout on the machine that owns the world.
|
||||
## The real game scene wants this; focused debug scenes (test/) bake their own
|
||||
## handful of stations and items instead and turn it off, so the thing under
|
||||
## test isn't sharing the world with a second copy of the whole kitchen.
|
||||
## Whether to build the full kitchen on the machine that owns the world. The real
|
||||
## game scene wants this; focused debug scenes bake their own handful of objects
|
||||
## and turn it off, so the thing under test is not sharing the world with a
|
||||
## second copy of the whole kitchen.
|
||||
@export var populate_from_layout: bool = true
|
||||
|
||||
var xr_interface: XRInterface
|
||||
var _net_world: NetWorld
|
||||
var _populated := false
|
||||
|
||||
|
||||
@@ -30,34 +30,38 @@ func _ready() -> void:
|
||||
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
|
||||
get_viewport().use_xr = true
|
||||
|
||||
NetworkManager.register_world(self, $PlayersSpawner, $ItemsSpawner)
|
||||
# Both roots go through the same generic replication hook, so a player avatar
|
||||
# is just another replicated object — nothing about it is special-cased.
|
||||
_net_world = NetWorld.new()
|
||||
_net_world.name = "NetWorld"
|
||||
add_child(_net_world)
|
||||
_net_world.setup($ItemsSpawner, $WorldContent)
|
||||
$PlayersSpawner.add_spawnable_scene(PLAYER_SCENE)
|
||||
$Players.child_entered_tree.connect(_on_player_entered)
|
||||
|
||||
NetworkManager.register_world(self, _net_world)
|
||||
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.connection_failed.connect(_on_connection_failed)
|
||||
|
||||
# world_ready() is what actually calls host()/join() (or the cmdline
|
||||
# equivalent). Populating before this point is wrong for EVERY case, not
|
||||
# world_ready() is what actually calls host()/join() (or the command-line
|
||||
# equivalent). Populating before this point would be wrong for every case, not
|
||||
# just offline: is_online() is still false until host()/join() runs, so
|
||||
# owns_world() would read true for a joining client too, and it would
|
||||
# build its own local copy instead of receiving the server's via the
|
||||
# spawner. host() emits session_started synchronously, which populates
|
||||
# via _on_session_started below; the explicit call after world_ready()
|
||||
# only matters for the case where neither host() nor join() ran (no
|
||||
# pending session, no cmdline args) — running this scene directly offline.
|
||||
# owns_world() reads true for a joining client too and it would build its own
|
||||
# copy instead of receiving the server's. host() emits session_started
|
||||
# synchronously, which populates via _on_session_started; the call after
|
||||
# world_ready() only covers the case where neither ran — this scene opened
|
||||
# directly, offline.
|
||||
NetworkManager.world_ready()
|
||||
_populate_world_if_owner()
|
||||
|
||||
get_tree().create_timer(3.0).timeout.connect(_log_world_state)
|
||||
|
||||
|
||||
# Temporary-ish sanity check: confirms WorldContent actually ended up
|
||||
# populated on this peer (whether by spawning it or by receiving it via
|
||||
# replication), so a silent replication failure shows up in the net log
|
||||
# instead of just an empty-looking world.
|
||||
func _log_world_state() -> void:
|
||||
NetworkManager.log_line("World state: WorldContent=%d children, Players=%d children" % [$WorldContent.get_child_count(), $Players.get_child_count()])
|
||||
func _on_player_entered(node: Node) -> void:
|
||||
if node is MultiplayerSynchronizer:
|
||||
return
|
||||
NetReplication.attach(node)
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
@@ -65,57 +69,42 @@ func _exit_tree() -> void:
|
||||
|
||||
|
||||
func _on_session_started(_is_server: bool) -> void:
|
||||
NetworkManager.gate_existing_stations()
|
||||
_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.
|
||||
## Turns the authored template into replicated objects, exactly once, on the
|
||||
## machine that owns world logic. Safe to call from several entry points.
|
||||
func _populate_world_if_owner() -> void:
|
||||
if _populated or not populate_from_layout:
|
||||
return
|
||||
# WorldLayout reads the live scene tree to find what to replicate, so it
|
||||
# needs to be an instance sitting in that tree — its methods can't be called
|
||||
# on the class itself.
|
||||
# WorldLayout reads the live scene tree to find what to replicate, so it has
|
||||
# to be an instance sitting in that tree.
|
||||
var layout := WorldLayout.new()
|
||||
add_child(layout)
|
||||
var authored := layout.get_station_nodes()
|
||||
authored.append_array(layout.get_item_nodes())
|
||||
var authored := layout.get_authored_nodes()
|
||||
|
||||
# The stations and items authored into the scene file are a *template*, not
|
||||
# the live world. Only the server turns them into real objects, spawned
|
||||
# through NetworkManager so they replicate. Every peer therefore drops its
|
||||
# own authored copies: the client would otherwise show its local originals
|
||||
# on top of the server's replicated ones, and the two sets would drift apart
|
||||
# because only the server's are synced.
|
||||
# Every peer drops its authored copies. The client would otherwise show its
|
||||
# local originals on top of the server's replicated ones, and the two sets
|
||||
# would drift apart because only the server's are synced.
|
||||
if not NetworkManager.owns_world():
|
||||
NetworkManager.log_line("Clearing %d authored nodes; the server's copies replace them" % authored.size())
|
||||
_remove_authored(authored)
|
||||
layout.queue_free()
|
||||
return
|
||||
|
||||
_init_recipes()
|
||||
_populated = true
|
||||
var stations := layout.get_stations()
|
||||
var items := layout.get_items()
|
||||
GameManager.meals_in_play = ["hamburger"]
|
||||
var objects := layout.describe(authored)
|
||||
layout.queue_free()
|
||||
_remove_authored(authored)
|
||||
NetworkManager.log_line("Populating world: %d stations, %d items" % [stations.size(), items.size()])
|
||||
for d in stations:
|
||||
NetworkManager.spawn_item(d["scene"], d["xform"], d["name"], d["props"])
|
||||
for d in items:
|
||||
NetworkManager.log_line("Populating world: %d objects" % objects.size())
|
||||
for d in objects:
|
||||
NetworkManager.spawn_item(d["scene"], d["xform"], d["name"], d["props"])
|
||||
NetworkManager.log_line("World populated")
|
||||
|
||||
|
||||
func _init_recipes():
|
||||
GameManager.meals_in_play = ["hamburger"]
|
||||
GameManager.sides_in_play = ["cube"]
|
||||
|
||||
|
||||
# Free the authored template nodes. Done immediately rather than with
|
||||
# queue_free() so the names are released before the replicated copies are
|
||||
# spawned under the same ones.
|
||||
# Freed immediately rather than queue_free()d, so the names are released before
|
||||
# the replicated copies are spawned under the same ones.
|
||||
func _remove_authored(nodes: Array[Node]) -> void:
|
||||
for node in nodes:
|
||||
if is_instance_valid(node):
|
||||
@@ -123,15 +112,17 @@ func _remove_authored(nodes: Array[Node]) -> void:
|
||||
node.free()
|
||||
|
||||
|
||||
## Only the server (or the single offline machine) materialises player
|
||||
## avatars; MultiplayerSpawner replicates the result to everyone else,
|
||||
## including late joiners.
|
||||
## Only the server (or the single offline machine) creates avatars;
|
||||
## MultiplayerSpawner replicates the result to everyone else, late joiners
|
||||
## included.
|
||||
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()
|
||||
var p: Node = load(PLAYER_SCENE).instantiate()
|
||||
# The name is the peer id, which is how net_player.gd knows whose avatar it
|
||||
# is on every peer — MultiplayerSpawner replicates the name.
|
||||
p.name = str(peer_id)
|
||||
$Players.add_child(p, true)
|
||||
$Players.add_child(p)
|
||||
NetworkManager.log_line("Spawned avatar for peer %d" % peer_id)
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
[gd_scene format=3 uid="uid://dbgu4r127ke5v"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b4ldkd3ngs0vp" path="res://main.gd" id="1_1ymas"]
|
||||
[ext_resource type="Sky" uid="uid://c1abtpwhdm2d5" path="res://textures/sky/NightSkyHDRI009_16K.tres" id="2_1jq86"]
|
||||
[ext_resource type="Sky" uid="uid://c1abtpwhdm2d5" path="res://Textures/sky/NightSkyHDRI009_16K.tres" id="2_1jq86"]
|
||||
[ext_resource type="PackedScene" uid="uid://j5s5wus7tuhb" path="res://XROrigin.tscn" id="3_bryd6"]
|
||||
[ext_resource type="Material" uid="uid://cmia50cfqxxo4" path="res://textures/dev_material_3d.tres" id="4_kpgm6"]
|
||||
[ext_resource type="PackedScene" uid="uid://j7caslh27nor" path="res://stations/Hob.tscn" id="5_gfdi7"]
|
||||
[ext_resource type="PackedScene" uid="uid://dvrk268s7gkxh" path="res://stations/sink.tscn" id="6_fmmg3"]
|
||||
[ext_resource type="PackedScene" uid="uid://efaec6ymgabo" path="res://stations/Counter.tscn" id="7_jnxsa"]
|
||||
[ext_resource type="PackedScene" uid="uid://e4i6o5oriecx" path="res://items/PickupCube.tscn" id="8_xl5nn"]
|
||||
[ext_resource type="Material" uid="uid://cmia50cfqxxo4" path="res://Textures/dev_material_3d.tres" id="4_kpgm6"]
|
||||
[ext_resource type="PackedScene" uid="uid://j7caslh27nor" path="res://Stations/Hob.tscn" id="5_gfdi7"]
|
||||
[ext_resource type="PackedScene" uid="uid://dvrk268s7gkxh" path="res://Stations/sink.tscn" id="6_fmmg3"]
|
||||
[ext_resource type="PackedScene" uid="uid://efaec6ymgabo" path="res://Stations/Counter.tscn" id="7_jnxsa"]
|
||||
[ext_resource type="PackedScene" uid="uid://e4i6o5oriecx" path="res://Items/PickupCube.tscn" id="8_xl5nn"]
|
||||
|
||||
[sub_resource type="Environment" id="Environment_bvwq1"]
|
||||
background_mode = 2
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
[gd_scene format=3 uid="uid://da6yrsnxvsrif"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_p8umn"]
|
||||
albedo_color = Color(0.38, 0.35162666, 0.3268, 1)
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_vlqg6"]
|
||||
size = Vector3(0.6, 0.34027833, 0.6)
|
||||
|
||||
[node name="Wall" type="StaticBody3D" unique_id=434181749]
|
||||
|
||||
[node name="CSGCombiner3D" type="CSGCombiner3D" parent="." unique_id=671294008]
|
||||
use_collision = true
|
||||
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="CSGCombiner3D" unique_id=308631620]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0)
|
||||
size = Vector3(0.6, 4, 0.6)
|
||||
material = SubResource("StandardMaterial3D_p8umn")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1166811333]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.0501393, 0)
|
||||
shape = SubResource("BoxShape3D_vlqg6")
|
||||
@@ -3,12 +3,12 @@
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://mmasxwqyqmib"
|
||||
path="res://.godot/imported/220175__gameaudio__pop-click.wav-6ef2d3317a82c347b715936ede79653a.sample"
|
||||
path="res://.godot/imported/220175__gameaudio__pop-click.wav-1330523e09eacb15d341a14b80e4481e.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sounds/220175__gameaudio__pop-click.wav"
|
||||
dest_files=["res://.godot/imported/220175__gameaudio__pop-click.wav-6ef2d3317a82c347b715936ede79653a.sample"]
|
||||
source_file="res://Sounds/220175__gameaudio__pop-click.wav"
|
||||
dest_files=["res://.godot/imported/220175__gameaudio__pop-click.wav-1330523e09eacb15d341a14b80e4481e.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://ecva70pect61"
|
||||
path="res://.godot/imported/220178__gameaudio__click.wav-566ebf31c82573e187ff8a1db97a7861.sample"
|
||||
path="res://.godot/imported/220178__gameaudio__click.wav-ca46bf33c1c48e1e526f04c43ca7d450.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sounds/220178__gameaudio__click.wav"
|
||||
dest_files=["res://.godot/imported/220178__gameaudio__click.wav-566ebf31c82573e187ff8a1db97a7861.sample"]
|
||||
source_file="res://Sounds/220178__gameaudio__click.wav"
|
||||
dest_files=["res://.godot/imported/220178__gameaudio__click.wav-ca46bf33c1c48e1e526f04c43ca7d450.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://b3wxawyrvuc6s"
|
||||
path="res://.godot/imported/220179__gameaudio__click-metal-ting.wav-82be282871110eef8fea8249619832d7.sample"
|
||||
path="res://.godot/imported/220179__gameaudio__click-metal-ting.wav-b01c457287fe42298a36c3de7c536c68.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sounds/220179__gameaudio__click-metal-ting.wav"
|
||||
dest_files=["res://.godot/imported/220179__gameaudio__click-metal-ting.wav-82be282871110eef8fea8249619832d7.sample"]
|
||||
source_file="res://Sounds/220179__gameaudio__click-metal-ting.wav"
|
||||
dest_files=["res://.godot/imported/220179__gameaudio__click-metal-ting.wav-b01c457287fe42298a36c3de7c536c68.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://b7sr2sb1ks5en"
|
||||
path="res://.godot/imported/220180__gameaudio__click-pop.wav-32216325683d314fa3adc66d13e633f4.sample"
|
||||
path="res://.godot/imported/220180__gameaudio__click-pop.wav-9e467150046193d705dd830885c89cd1.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sounds/220180__gameaudio__click-pop.wav"
|
||||
dest_files=["res://.godot/imported/220180__gameaudio__click-pop.wav-32216325683d314fa3adc66d13e633f4.sample"]
|
||||
source_file="res://Sounds/220180__gameaudio__click-pop.wav"
|
||||
dest_files=["res://.godot/imported/220180__gameaudio__click-pop.wav-9e467150046193d705dd830885c89cd1.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://1ccsqbju32ey"
|
||||
path="res://.godot/imported/220187__gameaudio__loosedeny-casual-1.wav-2e7c1356ffec0a1e5b732b21f42e21a0.sample"
|
||||
path="res://.godot/imported/220187__gameaudio__loosedeny-casual-1.wav-cb0e560d42c6026ded2d2e240e614433.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sounds/220187__gameaudio__loosedeny-casual-1.wav"
|
||||
dest_files=["res://.godot/imported/220187__gameaudio__loosedeny-casual-1.wav-2e7c1356ffec0a1e5b732b21f42e21a0.sample"]
|
||||
source_file="res://Sounds/220187__gameaudio__loosedeny-casual-1.wav"
|
||||
dest_files=["res://.godot/imported/220187__gameaudio__loosedeny-casual-1.wav-cb0e560d42c6026ded2d2e240e614433.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://bdnsgt7xt3261"
|
||||
path="res://.godot/imported/220194__gameaudio__click-heavy.wav-43269bff3c60e2451b470f929c6b6bb8.sample"
|
||||
path="res://.godot/imported/220194__gameaudio__click-heavy.wav-aca40ed286d93eb365d527a162fd3129.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sounds/220194__gameaudio__click-heavy.wav"
|
||||
dest_files=["res://.godot/imported/220194__gameaudio__click-heavy.wav-43269bff3c60e2451b470f929c6b6bb8.sample"]
|
||||
source_file="res://Sounds/220194__gameaudio__click-heavy.wav"
|
||||
dest_files=["res://.godot/imported/220194__gameaudio__click-heavy.wav-aca40ed286d93eb365d527a162fd3129.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://daiv8ubwdbidf"
|
||||
path="res://.godot/imported/220195__gameaudio__click-wooden-1.wav-f161e2c5a53d686c65d143b45f33acb1.sample"
|
||||
path="res://.godot/imported/220195__gameaudio__click-wooden-1.wav-9368ef861eabf2cf1d5bd0db33c616c8.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sounds/220195__gameaudio__click-wooden-1.wav"
|
||||
dest_files=["res://.godot/imported/220195__gameaudio__click-wooden-1.wav-f161e2c5a53d686c65d143b45f33acb1.sample"]
|
||||
source_file="res://Sounds/220195__gameaudio__click-wooden-1.wav"
|
||||
dest_files=["res://.godot/imported/220195__gameaudio__click-wooden-1.wav-9368ef861eabf2cf1d5bd0db33c616c8.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://qed775b4j1gg"
|
||||
path="res://.godot/imported/220196__gameaudio__click-wooden-2.wav-372f3d1af9c561baf2360e14b5b7050b.sample"
|
||||
path="res://.godot/imported/220196__gameaudio__click-wooden-2.wav-45bb63efefaf817b60155beec9343d74.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sounds/220196__gameaudio__click-wooden-2.wav"
|
||||
dest_files=["res://.godot/imported/220196__gameaudio__click-wooden-2.wav-372f3d1af9c561baf2360e14b5b7050b.sample"]
|
||||
source_file="res://Sounds/220196__gameaudio__click-wooden-2.wav"
|
||||
dest_files=["res://.godot/imported/220196__gameaudio__click-wooden-2.wav-45bb63efefaf817b60155beec9343d74.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://bqtk1adk8umbx"
|
||||
path="res://.godot/imported/220197__gameaudio__click-basic.wav-61aa24994882b9ab5848ecead3d7aba9.sample"
|
||||
path="res://.godot/imported/220197__gameaudio__click-basic.wav-9f173e2b7206278e95710ab077d62c32.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sounds/220197__gameaudio__click-basic.wav"
|
||||
dest_files=["res://.godot/imported/220197__gameaudio__click-basic.wav-61aa24994882b9ab5848ecead3d7aba9.sample"]
|
||||
source_file="res://Sounds/220197__gameaudio__click-basic.wav"
|
||||
dest_files=["res://.godot/imported/220197__gameaudio__click-basic.wav-9f173e2b7206278e95710ab077d62c32.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://ysgfx76jkx2q"
|
||||
path="res://.godot/imported/220198__gameaudio__click-with-two-parts.wav-23adbbdcc4d995994bf9700f6b6e244d.sample"
|
||||
path="res://.godot/imported/220198__gameaudio__click-with-two-parts.wav-089977567529b37b0fd9cd019eb7b3c2.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sounds/220198__gameaudio__click-with-two-parts.wav"
|
||||
dest_files=["res://.godot/imported/220198__gameaudio__click-with-two-parts.wav-23adbbdcc4d995994bf9700f6b6e244d.sample"]
|
||||
source_file="res://Sounds/220198__gameaudio__click-with-two-parts.wav"
|
||||
dest_files=["res://.godot/imported/220198__gameaudio__click-with-two-parts.wav-089977567529b37b0fd9cd019eb7b3c2.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://dtyh22ccebiv5"
|
||||
path="res://.godot/imported/220199__gameaudio__click-higher.wav-0ea6f7a1d32e528aace111f0ce8b9631.sample"
|
||||
path="res://.godot/imported/220199__gameaudio__click-higher.wav-d1c1550d99a0cc873012c4010877005b.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sounds/220199__gameaudio__click-higher.wav"
|
||||
dest_files=["res://.godot/imported/220199__gameaudio__click-higher.wav-0ea6f7a1d32e528aace111f0ce8b9631.sample"]
|
||||
source_file="res://Sounds/220199__gameaudio__click-higher.wav"
|
||||
dest_files=["res://.godot/imported/220199__gameaudio__click-higher.wav-d1c1550d99a0cc873012c4010877005b.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://cywjbbeintjew"
|
||||
path="res://.godot/imported/220208__gameaudio__click-pop-two-part.wav-9e40de3d9bebd124d0fe54a4f4cc4db9.sample"
|
||||
path="res://.godot/imported/220208__gameaudio__click-pop-two-part.wav-04cb5eb3e0b3459771dbab42bbd24be9.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sounds/220208__gameaudio__click-pop-two-part.wav"
|
||||
dest_files=["res://.godot/imported/220208__gameaudio__click-pop-two-part.wav-9e40de3d9bebd124d0fe54a4f4cc4db9.sample"]
|
||||
source_file="res://Sounds/220208__gameaudio__click-pop-two-part.wav"
|
||||
dest_files=["res://.godot/imported/220208__gameaudio__click-pop-two-part.wav-04cb5eb3e0b3459771dbab42bbd24be9.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://claw3d2wnyr6a"
|
||||
path="res://.godot/imported/220212__gameaudio__ping-bing.wav-04a5460471fa8fe82fe5d9bc5d946bb0.sample"
|
||||
path="res://.godot/imported/220212__gameaudio__ping-bing.wav-2a1049829e71a0034c7e8d18745663f1.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sounds/220212__gameaudio__ping-bing.wav"
|
||||
dest_files=["res://.godot/imported/220212__gameaudio__ping-bing.wav-04a5460471fa8fe82fe5d9bc5d946bb0.sample"]
|
||||
source_file="res://Sounds/220212__gameaudio__ping-bing.wav"
|
||||
dest_files=["res://.godot/imported/220212__gameaudio__ping-bing.wav-2a1049829e71a0034c7e8d18745663f1.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
importer="mp3"
|
||||
type="AudioStreamMP3"
|
||||
uid="uid://clkjwcawdqvta"
|
||||
path="res://.godot/imported/money.mp3-e3eae11b0c1923c268268b900bde9157.mp3str"
|
||||
path="res://.godot/imported/money.mp3-95c717b8402507d80974f4f4287b0b63.mp3str"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sounds/money.mp3"
|
||||
dest_files=["res://.godot/imported/money.mp3-e3eae11b0c1923c268268b900bde9157.mp3str"]
|
||||
source_file="res://Sounds/money.mp3"
|
||||
dest_files=["res://.godot/imported/money.mp3-95c717b8402507d80974f4f4287b0b63.mp3str"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
[gd_scene format=3 uid="uid://c6rift56ql3f8"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://0y6wnjcsum6" path="res://stations/item_dispenser.gd" id="1_myl3s"]
|
||||
[ext_resource type="Script" uid="uid://0y6wnjcsum6" path="res://Stations/item_dispenser.gd" id="1_myl3s"]
|
||||
[ext_resource type="Script" uid="uid://cquqe4f1m1sw6" path="res://addons/godot-xr-tools/objects/snap_zone.gd" id="1_p30r1"]
|
||||
[ext_resource type="PackedScene" uid="uid://c0lknik4noobs" path="res://items/BurgerBuns.tscn" id="2_1q74o"]
|
||||
[ext_resource type="PackedScene" uid="uid://mpapt5mkbuao" path="res://prefabs/slide_off_dome.tscn" id="4_rf1b2"]
|
||||
[ext_resource type="PackedScene" uid="uid://c0lknik4noobs" path="res://Items/BurgerBuns.tscn" id="2_1q74o"]
|
||||
[ext_resource type="PackedScene" uid="uid://mpapt5mkbuao" path="res://Prefabs/slide_off_dome.tscn" id="4_rf1b2"]
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_xmbo2"]
|
||||
radius = 0.3
|
||||
|
||||
+37
-136
@@ -1,50 +1,11 @@
|
||||
[gd_scene format=3 uid="uid://j7caslh27nor"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bsn8vhv5adxdo" path="res://stations/hob.gd" id="1_7jc4g"]
|
||||
[ext_resource type="Script" uid="uid://bsn8vhv5adxdo" path="res://Stations/hob.gd" id="1_7jc4g"]
|
||||
[ext_resource type="Script" uid="uid://cquqe4f1m1sw6" path="res://addons/godot-xr-tools/objects/snap_zone.gd" id="2_er1dp"]
|
||||
[ext_resource type="AudioStream" uid="uid://bqtk1adk8umbx" path="res://sounds/220197__gameaudio__click-basic.wav" id="3_6oyg1"]
|
||||
[ext_resource type="AudioStream" uid="uid://bqtk1adk8umbx" path="res://Sounds/220197__gameaudio__click-basic.wav" id="3_6oyg1"]
|
||||
[ext_resource type="PackedScene" uid="uid://bxbocxdaayvwx" path="res://UI/progress_bar.tscn" id="3_7uuqv"]
|
||||
[ext_resource type="Material" uid="uid://d1djvskv4n4m3" path="res://textures/hob_off.tres" id="4_8qpxk"]
|
||||
[ext_resource type="Material" uid="uid://db7c7w4apwti7" path="res://textures/hob_on.tres" id="6_m7l4u"]
|
||||
[ext_resource type="Script" uid="uid://bu0g23cfrqy0m" path="res://stations/station_movement.gd" id="7_akjp4"]
|
||||
[ext_resource type="Script" uid="uid://bfrlpbsqg5lqr" path="res://addons/godot-xr-tools/objects/pickable.gd" id="7_d4kl5"]
|
||||
[ext_resource type="PackedScene" uid="uid://c25yxb0vt53vc" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_left.tscn" id="8_w3ous"]
|
||||
[ext_resource type="Animation" uid="uid://db62hs5s4n2b3" path="res://addons/godot-xr-tools/hands/animations/left/Grip 4.res" id="9_rdqhn"]
|
||||
[ext_resource type="Script" uid="uid://dvobm6vcfnqe8" path="res://addons/godot-xr-tools/hands/poses/hand_pose_settings.gd" id="10_hyphg"]
|
||||
[ext_resource type="PackedScene" uid="uid://ctw7nbntd5pcj" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_right.tscn" id="11_sexl6"]
|
||||
[ext_resource type="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="12_0t4ed"]
|
||||
[ext_resource type="Script" uid="uid://bwd0pe2udb5xo" path="res://Net/net_pickable.gd" id="15_trny8"]
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_m1xbq"]
|
||||
size = Vector3(0.1, 0.1, 0.1)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_24d3s"]
|
||||
albedo_color = Color(0.1764706, 1, 1, 1)
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_vlqg6"]
|
||||
material = SubResource("StandardMaterial3D_24d3s")
|
||||
size = Vector3(0.1, 0.1, 0.1)
|
||||
|
||||
[sub_resource type="Resource" id="Resource_lc22d"]
|
||||
script = ExtResource("10_hyphg")
|
||||
closed_pose = ExtResource("9_rdqhn")
|
||||
metadata/_custom_type_script = "uid://dvobm6vcfnqe8"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_qyiot"]
|
||||
script = ExtResource("10_hyphg")
|
||||
closed_pose = ExtResource("12_0t4ed")
|
||||
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
|
||||
[ext_resource type="Material" uid="uid://d1djvskv4n4m3" path="res://Textures/hob_off.tres" id="4_8qpxk"]
|
||||
[ext_resource type="Material" uid="uid://db7c7w4apwti7" path="res://Textures/hob_on.tres" id="6_m7l4u"]
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_kdxnr"]
|
||||
size = Vector3(0.6, 1, 0.6)
|
||||
@@ -54,29 +15,12 @@ size = Vector3(0.6, 0.12802735, 0.6)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_gkb3v"]
|
||||
|
||||
[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
|
||||
properties/2/path = NodePath(".:cooking_result")
|
||||
properties/2/spawn = true
|
||||
properties/2/replication_mode = 1
|
||||
properties/3/path = NodePath("Hob:position")
|
||||
properties/3/spawn = true
|
||||
properties/3/replication_mode = 1
|
||||
properties/4/path = NodePath("Hob:rotation")
|
||||
properties/4/spawn = true
|
||||
properties/4/replication_mode = 1
|
||||
|
||||
[sub_resource type="Animation" id="Animation_7uuqv"]
|
||||
length = 0.001
|
||||
tracks/0/type = "value"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath("Hob/CSGCombiner3D/CSGTorus3D3:material")
|
||||
tracks/0/path = NodePath("CSGCombiner3D/CSGTorus3D3:material")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
@@ -88,7 +32,7 @@ tracks/0/keys = {
|
||||
tracks/1/type = "value"
|
||||
tracks/1/imported = false
|
||||
tracks/1/enabled = true
|
||||
tracks/1/path = NodePath("Hob/CSGCombiner3D/CSGTorus3D2:material")
|
||||
tracks/1/path = NodePath("CSGCombiner3D/CSGTorus3D2:material")
|
||||
tracks/1/interp = 1
|
||||
tracks/1/loop_wrap = true
|
||||
tracks/1/keys = {
|
||||
@@ -100,7 +44,7 @@ tracks/1/keys = {
|
||||
tracks/2/type = "value"
|
||||
tracks/2/imported = false
|
||||
tracks/2/enabled = true
|
||||
tracks/2/path = NodePath("Hob/CSGCombiner3D/CSGTorus3D:material")
|
||||
tracks/2/path = NodePath("CSGCombiner3D/CSGTorus3D:material")
|
||||
tracks/2/interp = 1
|
||||
tracks/2/loop_wrap = true
|
||||
tracks/2/keys = {
|
||||
@@ -112,7 +56,7 @@ tracks/2/keys = {
|
||||
tracks/3/type = "value"
|
||||
tracks/3/imported = false
|
||||
tracks/3/enabled = true
|
||||
tracks/3/path = NodePath("Hob/CSGCombiner3D/CSGTorus3D3:material:emission_energy_multiplier")
|
||||
tracks/3/path = NodePath("CSGCombiner3D/CSGTorus3D3:material:emission_energy_multiplier")
|
||||
tracks/3/interp = 1
|
||||
tracks/3/loop_wrap = true
|
||||
tracks/3/keys = {
|
||||
@@ -124,7 +68,7 @@ tracks/3/keys = {
|
||||
tracks/4/type = "value"
|
||||
tracks/4/imported = false
|
||||
tracks/4/enabled = true
|
||||
tracks/4/path = NodePath("Hob/OmniLight3D:light_energy")
|
||||
tracks/4/path = NodePath("OmniLight3D:light_energy")
|
||||
tracks/4/interp = 1
|
||||
tracks/4/loop_wrap = true
|
||||
tracks/4/keys = {
|
||||
@@ -140,7 +84,7 @@ loop_mode = 1
|
||||
tracks/0/type = "value"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath("Hob/CSGCombiner3D/CSGTorus3D3:material")
|
||||
tracks/0/path = NodePath("CSGCombiner3D/CSGTorus3D3:material")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
@@ -152,7 +96,7 @@ tracks/0/keys = {
|
||||
tracks/1/type = "value"
|
||||
tracks/1/imported = false
|
||||
tracks/1/enabled = true
|
||||
tracks/1/path = NodePath("Hob/CSGCombiner3D/CSGTorus3D2:material")
|
||||
tracks/1/path = NodePath("CSGCombiner3D/CSGTorus3D2:material")
|
||||
tracks/1/interp = 1
|
||||
tracks/1/loop_wrap = true
|
||||
tracks/1/keys = {
|
||||
@@ -164,7 +108,7 @@ tracks/1/keys = {
|
||||
tracks/2/type = "value"
|
||||
tracks/2/imported = false
|
||||
tracks/2/enabled = true
|
||||
tracks/2/path = NodePath("Hob/CSGCombiner3D/CSGTorus3D:material")
|
||||
tracks/2/path = NodePath("CSGCombiner3D/CSGTorus3D:material")
|
||||
tracks/2/interp = 1
|
||||
tracks/2/loop_wrap = true
|
||||
tracks/2/keys = {
|
||||
@@ -176,7 +120,7 @@ tracks/2/keys = {
|
||||
tracks/3/type = "value"
|
||||
tracks/3/imported = false
|
||||
tracks/3/enabled = true
|
||||
tracks/3/path = NodePath("Hob/CSGCombiner3D/CSGTorus3D3:material:emission_energy_multiplier")
|
||||
tracks/3/path = NodePath("CSGCombiner3D/CSGTorus3D3:material:emission_energy_multiplier")
|
||||
tracks/3/interp = 1
|
||||
tracks/3/loop_wrap = true
|
||||
tracks/3/keys = {
|
||||
@@ -188,7 +132,7 @@ tracks/3/keys = {
|
||||
tracks/4/type = "value"
|
||||
tracks/4/imported = false
|
||||
tracks/4/enabled = true
|
||||
tracks/4/path = NodePath("Hob/OmniLight3D:light_energy")
|
||||
tracks/4/path = NodePath("OmniLight3D:light_energy")
|
||||
tracks/4/interp = 1
|
||||
tracks/4/loop_wrap = true
|
||||
tracks/4/keys = {
|
||||
@@ -204,46 +148,13 @@ _data = {
|
||||
&"hob": SubResource("Animation_6oyg1")
|
||||
}
|
||||
|
||||
[node name="Hob" type="Node3D" unique_id=172418174 groups=["station"]]
|
||||
|
||||
[node name="MoveHandle" type="RigidBody3D" parent="." unique_id=1675596942 groups=["platalbe_item"]]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.08, 0)
|
||||
collision_layer = 4
|
||||
collision_mask = 196615
|
||||
axis_lock_linear_y = true
|
||||
axis_lock_angular_x = true
|
||||
axis_lock_angular_z = true
|
||||
mass = 0.004
|
||||
gravity_scale = 0.5
|
||||
linear_damp = 100.0
|
||||
script = ExtResource("7_d4kl5")
|
||||
release_mode = 1
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="MoveHandle" unique_id=1023455695]
|
||||
shape = SubResource("BoxShape3D_m1xbq")
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="MoveHandle" unique_id=1223689787]
|
||||
mesh = SubResource("BoxMesh_vlqg6")
|
||||
|
||||
[node name="GrabPointHandLeft" parent="MoveHandle" unique_id=1571481674 instance=ExtResource("8_w3ous")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.067650706, 0.04601878, -0.08606844)
|
||||
hand_pose = SubResource("Resource_lc22d")
|
||||
|
||||
[node name="GrabPointHandRight" parent="MoveHandle" unique_id=514404634 instance=ExtResource("11_sexl6")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.06531982, 0.042291984, -0.08604182)
|
||||
hand_pose = SubResource("Resource_qyiot")
|
||||
|
||||
[node name="NetPickable" type="MultiplayerSynchronizer" parent="MoveHandle" unique_id=1858332513]
|
||||
replication_config = SubResource("SceneReplicationConfig_np_cube")
|
||||
script = ExtResource("15_trny8")
|
||||
|
||||
[node name="Hob" type="StaticBody3D" parent="." unique_id=1332123047]
|
||||
[node name="Hob" type="StaticBody3D" unique_id=1687971542 groups=["station"]]
|
||||
script = ExtResource("1_7jc4g")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="Hob" unique_id=1283846023]
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1283846023]
|
||||
shape = SubResource("BoxShape3D_kdxnr")
|
||||
|
||||
[node name="XRToolsSnapZone" type="Area3D" parent="Hob" unique_id=538157739 groups=["station"]]
|
||||
[node name="XRToolsSnapZone" type="Area3D" parent="." unique_id=538157739 groups=["station"]]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5270121, 0)
|
||||
collision_layer = 65536
|
||||
collision_mask = 65540
|
||||
@@ -252,112 +163,102 @@ stash_sound = ExtResource("3_6oyg1")
|
||||
snap_mode = 1
|
||||
metadata/_custom_type_script = "uid://cquqe4f1m1sw6"
|
||||
|
||||
[node name="CollisionShape3D2" type="CollisionShape3D" parent="Hob/XRToolsSnapZone" unique_id=370920392]
|
||||
[node name="CollisionShape3D2" type="CollisionShape3D" parent="XRToolsSnapZone" unique_id=370920392]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.000975132, 0)
|
||||
shape = SubResource("BoxShape3D_7uuqv")
|
||||
|
||||
[node name="AudioStreamPlayer3D" type="AudioStreamPlayer3D" parent="Hob/XRToolsSnapZone" unique_id=1991627595]
|
||||
[node name="AudioStreamPlayer3D" type="AudioStreamPlayer3D" parent="XRToolsSnapZone" unique_id=1991627595]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.022546828, -0.5270121, 0.36528283)
|
||||
|
||||
[node name="CSGCombiner3D" type="CSGCombiner3D" parent="Hob" unique_id=70901698]
|
||||
[node name="CSGCombiner3D" type="CSGCombiner3D" parent="." unique_id=70901698]
|
||||
|
||||
[node name="CSGBox3D" type="CSGBox3D" parent="Hob/CSGCombiner3D" unique_id=1111505172]
|
||||
[node name="CSGBox3D" type="CSGBox3D" parent="CSGCombiner3D" unique_id=1111505172]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.00894776)
|
||||
size = Vector3(0.6, 1, 0.5821045)
|
||||
|
||||
[node name="CSGCylinder3D" type="CSGCylinder3D" parent="Hob/CSGCombiner3D/CSGBox3D" unique_id=1338463939]
|
||||
[node name="CSGCylinder3D" type="CSGCylinder3D" parent="CSGCombiner3D/CSGBox3D" unique_id=1338463939]
|
||||
transform = Transform3D(0.8943019, 0, 0, 0, 0.8943019, 0, 0, 0, 0.8943019, 0, 0.5103538, -7.827557e-05)
|
||||
operation = 2
|
||||
radius = 0.25976563
|
||||
height = 0.08898926
|
||||
sides = 32
|
||||
|
||||
[node name="CSGTorus3D3" type="CSGTorus3D" parent="Hob/CSGCombiner3D" unique_id=666891341]
|
||||
[node name="CSGTorus3D3" type="CSGTorus3D" parent="CSGCombiner3D" unique_id=666891341]
|
||||
transform = Transform3D(0.8943019, 0, 0, 0, 0.8943019, 0, 0, 0, 0.8943019, 0, 0.5003869, -7.827557e-05)
|
||||
inner_radius = 0.09153879
|
||||
outer_radius = 0.120084204
|
||||
sides = 32
|
||||
material = ExtResource("4_8qpxk")
|
||||
|
||||
[node name="CSGTorus3D2" type="CSGTorus3D" parent="Hob/CSGCombiner3D" unique_id=24541894]
|
||||
[node name="CSGTorus3D2" type="CSGTorus3D" parent="CSGCombiner3D" unique_id=24541894]
|
||||
transform = Transform3D(0.8943019, 0, 0, 0, 0.8943019, 0, 0, 0, 0.8943019, 0, 0.5003869, -7.827557e-05)
|
||||
inner_radius = 0.15036294
|
||||
outer_radius = 0.17592031
|
||||
sides = 32
|
||||
material = ExtResource("4_8qpxk")
|
||||
|
||||
[node name="CSGTorus3D" type="CSGTorus3D" parent="Hob/CSGCombiner3D" unique_id=1901978664]
|
||||
[node name="CSGTorus3D" type="CSGTorus3D" parent="CSGCombiner3D" unique_id=1901978664]
|
||||
transform = Transform3D(0.8943019, 0, 0, 0, 0.8943019, 0, 0, 0, 0.8943019, 0, 0.5003869, -7.827557e-05)
|
||||
inner_radius = 0.20426142
|
||||
outer_radius = 0.2321898
|
||||
sides = 32
|
||||
material = ExtResource("4_8qpxk")
|
||||
|
||||
[node name="CSGCylinder3D" type="CSGCylinder3D" parent="Hob/CSGCombiner3D" unique_id=1107239311]
|
||||
[node name="CSGCylinder3D" type="CSGCylinder3D" parent="CSGCombiner3D" unique_id=1107239311]
|
||||
transform = Transform3D(0.8943019, 0, 0, 0, 0.8943019, 0, 0, 0, 0.8943019, 0, 0.4498226, -7.827557e-05)
|
||||
radius = 0.071777344
|
||||
height = 0.09637451
|
||||
sides = 32
|
||||
|
||||
[node name="CSGCylinder3D2" type="CSGCylinder3D" parent="Hob/CSGCombiner3D" unique_id=435540815]
|
||||
[node name="CSGCylinder3D2" type="CSGCylinder3D" parent="CSGCombiner3D" unique_id=435540815]
|
||||
transform = Transform3D(0.8943019, 0, 0, 0, -3.9091177e-08, 0.8943019, 0, -0.8943019, -3.9091177e-08, 0, 0.48668858, -0.0020651226)
|
||||
radius = 0.008
|
||||
height = 0.44487303
|
||||
|
||||
[node name="CSGCylinder3D3" type="CSGCylinder3D" parent="Hob/CSGCombiner3D" unique_id=878171801]
|
||||
[node name="CSGCylinder3D3" type="CSGCylinder3D" parent="CSGCombiner3D" unique_id=878171801]
|
||||
transform = Transform3D(-3.9091177e-08, -0.8943019, -3.9091177e-08, 0, -3.9091177e-08, 0.8943019, -0.8943019, 3.9091177e-08, 1.7087296e-15, 0, 0.48668858, -0.0020651226)
|
||||
radius = 0.008
|
||||
height = 0.44487303
|
||||
|
||||
[node name="CSGCylinder3D4" type="CSGCylinder3D" parent="Hob/CSGCombiner3D" unique_id=1619646436]
|
||||
[node name="CSGCylinder3D4" type="CSGCylinder3D" parent="CSGCombiner3D" unique_id=1619646436]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, -0.20412213, 0.42728558, 0.26171687)
|
||||
radius = 0.044433594
|
||||
height = 0.10932617
|
||||
material = SubResource("StandardMaterial3D_gkb3v")
|
||||
|
||||
[node name="CSGCylinder3D5" type="CSGCylinder3D" parent="Hob/CSGCombiner3D" unique_id=548846612]
|
||||
[node name="CSGCylinder3D5" type="CSGCylinder3D" parent="CSGCombiner3D" unique_id=548846612]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.12531614, 0.42728558, 0.26171687)
|
||||
radius = 0.044433594
|
||||
height = 0.10932617
|
||||
material = SubResource("StandardMaterial3D_gkb3v")
|
||||
|
||||
[node name="CSGCylinder3D6" type="CSGCylinder3D" parent="Hob/CSGCombiner3D" unique_id=1208989647]
|
||||
[node name="CSGCylinder3D6" type="CSGCylinder3D" parent="CSGCombiner3D" unique_id=1208989647]
|
||||
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0.22979808, 0.42728558, 0.26171687)
|
||||
radius = 0.044433594
|
||||
height = 0.10932617
|
||||
material = SubResource("StandardMaterial3D_gkb3v")
|
||||
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="Hob/CSGCombiner3D" unique_id=465340362]
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="CSGCombiner3D" unique_id=465340362]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.039733887, -0.07685089, 0.29250562)
|
||||
operation = 2
|
||||
size = Vector3(1.0999756, 0.8462982, 0.072387695)
|
||||
|
||||
[node name="CSGBox3D3" type="CSGBox3D" parent="Hob/CSGCombiner3D" unique_id=1813079759]
|
||||
[node name="CSGBox3D3" type="CSGBox3D" parent="CSGCombiner3D" unique_id=1813079759]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.003479004, -0.08253479, 0.2656765)
|
||||
size = Vector3(0.55, 0.835, 0.072)
|
||||
|
||||
[node name="CSGBox3D4" type="CSGBox3D" parent="Hob/CSGCombiner3D" unique_id=1978186019]
|
||||
[node name="CSGBox3D4" type="CSGBox3D" parent="CSGCombiner3D" unique_id=1978186019]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.003479004, -0.45215607, 0.24655426)
|
||||
size = Vector3(0.55, 0.096, 0.14)
|
||||
|
||||
[node name="Sync" type="MultiplayerSynchronizer" parent="Hob" unique_id=768992259]
|
||||
root_path = NodePath("../..")
|
||||
replication_config = SubResource("SceneReplicationConfig_np_hob")
|
||||
|
||||
[node name="ProgressBar3D" parent="Hob" unique_id=654673176 instance=ExtResource("3_7uuqv")]
|
||||
[node name="ProgressBar3D" parent="." unique_id=654673176 instance=ExtResource("3_7uuqv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.74736404, -0.09216304)
|
||||
|
||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="Hob" unique_id=1525460719]
|
||||
root_node = NodePath("../..")
|
||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="." unique_id=1525460719]
|
||||
libraries/ = SubResource("AnimationLibrary_ac5f3")
|
||||
|
||||
[node name="OmniLight3D" type="OmniLight3D" parent="Hob" unique_id=1618457623]
|
||||
[node name="OmniLight3D" type="OmniLight3D" parent="." unique_id=1618457623]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5053487, 0)
|
||||
light_color = Color(1, 0.13725491, 0, 1)
|
||||
light_energy = 0.0
|
||||
omni_range = 0.5
|
||||
|
||||
[node name="Movement" type="Node" parent="Hob" unique_id=1004484356 node_paths=PackedStringArray("move_handle", "station")]
|
||||
script = ExtResource("7_akjp4")
|
||||
move_handle = NodePath("../../MoveHandle")
|
||||
station = NodePath("..")
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
[gd_scene format=3 uid="uid://cwwnbx5uat3fw"]
|
||||
|
||||
[node name="CSGBox3D" type="CSGBox3D" unique_id=725518829]
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[gd_scene format=3 uid="uid://cnjwtnhwh0i8q"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://q7tpwmrbmu1m" path="res://stations/dirt_station.gd" id="1_hc1d4"]
|
||||
[ext_resource type="Script" uid="uid://q7tpwmrbmu1m" path="res://Stations/dirt_station.gd" id="1_hc1d4"]
|
||||
[ext_resource type="Script" uid="uid://cquqe4f1m1sw6" path="res://addons/godot-xr-tools/objects/snap_zone.gd" id="2_v0ytd"]
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_24d3s"]
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
[gd_scene format=3 uid="uid://dlw5geheupgpt"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cquqe4f1m1sw6" path="res://addons/godot-xr-tools/objects/snap_zone.gd" id="1_oowyn"]
|
||||
[ext_resource type="AudioStream" uid="uid://bqtk1adk8umbx" path="res://sounds/220197__gameaudio__click-basic.wav" id="2_4mvh7"]
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_hgrxd"]
|
||||
size = Vector3(0.6, 0.12802735, 0.6)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_vlqg6"]
|
||||
albedo_color = Color(0.45, 0.3012, 0.17099999, 1)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_p8umn"]
|
||||
albedo_color = Color(0.38, 0.35162666, 0.3268, 1)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_oowyn"]
|
||||
albedo_color = Color(0.38, 0.35162666, 0.3268, 1)
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_vlqg6"]
|
||||
size = Vector3(0.6, 0.34027833, 0.6)
|
||||
|
||||
[node name="Hatch" type="StaticBody3D" unique_id=1341321603]
|
||||
|
||||
[node name="XRToolsSnapZone" type="Area3D" parent="." unique_id=2093161322 groups=["station"]]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.2495586, 0)
|
||||
collision_layer = 65536
|
||||
collision_mask = 65540
|
||||
script = ExtResource("1_oowyn")
|
||||
stash_sound = ExtResource("2_4mvh7")
|
||||
snap_mode = 1
|
||||
metadata/_custom_type_script = "uid://cquqe4f1m1sw6"
|
||||
|
||||
[node name="CollisionShape3D2" type="CollisionShape3D" parent="XRToolsSnapZone" unique_id=1997533747]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.002, 0)
|
||||
shape = SubResource("BoxShape3D_hgrxd")
|
||||
|
||||
[node name="AudioStreamPlayer3D" type="AudioStreamPlayer3D" parent="XRToolsSnapZone" unique_id=1729156185]
|
||||
|
||||
[node name="CSGCombiner3D" type="CSGCombiner3D" parent="." unique_id=2092063969]
|
||||
use_collision = true
|
||||
|
||||
[node name="CSGBox3D" type="CSGBox3D" parent="CSGCombiner3D" unique_id=1242328035]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.48000002, 0)
|
||||
size = Vector3(0.599, 1, 0.3)
|
||||
material = SubResource("StandardMaterial3D_vlqg6")
|
||||
|
||||
[node name="CSGBox3D2" type="CSGBox3D" parent="CSGCombiner3D" unique_id=1252817409]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.07, 0)
|
||||
size = Vector3(0.6, 0.3, 0.6)
|
||||
material = SubResource("StandardMaterial3D_p8umn")
|
||||
|
||||
[node name="CSGCylinder3D" type="CSGCylinder3D" parent="CSGCombiner3D/CSGBox3D2" unique_id=1648749119]
|
||||
transform = Transform3D(-4.371139e-08, -1, 0, 1, -4.371139e-08, 0, 0, 0, 1, 0.005000025, 0, 0)
|
||||
operation = 1
|
||||
radius = 0.25
|
||||
height = 0.67
|
||||
sides = 32
|
||||
|
||||
[node name="CSGBox3D3" type="CSGBox3D" parent="CSGCombiner3D" unique_id=740791264]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.14530161, 0.48000002, -0.15)
|
||||
size = Vector3(0.120000005, 1, 0.120000005)
|
||||
material = SubResource("StandardMaterial3D_vlqg6")
|
||||
|
||||
[node name="CSGBox3D4" type="CSGBox3D" parent="CSGCombiner3D" unique_id=249869254]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.14650056, 0.48000002, -0.15)
|
||||
size = Vector3(0.120000005, 1, 0.120000005)
|
||||
material = SubResource("StandardMaterial3D_vlqg6")
|
||||
|
||||
[node name="CSGBox3D5" type="CSGBox3D" parent="CSGCombiner3D" unique_id=1242869868]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 3.1, 0)
|
||||
size = Vector3(0.6, 1.8000001, 0.6)
|
||||
material = SubResource("StandardMaterial3D_oowyn")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1832317008]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.0501393, 0)
|
||||
shape = SubResource("BoxShape3D_vlqg6")
|
||||
+1
-1
@@ -86,7 +86,7 @@ func _on_object_picked_up(_item) -> void:
|
||||
# The setters above already refresh the display and start the flames.
|
||||
|
||||
|
||||
func _on_object_dropped(_item) -> void:
|
||||
func _on_object_dropped() -> void:
|
||||
print("Hob: object drop ")
|
||||
time_cooked = 0
|
||||
cooking_result = ""
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
[gd_scene format=3 uid="uid://ck5tuftqmyiue"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://0y6wnjcsum6" path="res://stations/item_dispenser.gd" id="1_jm0ik"]
|
||||
[ext_resource type="Script" uid="uid://0y6wnjcsum6" path="res://Stations/item_dispenser.gd" id="1_jm0ik"]
|
||||
[ext_resource type="PackedScene" uid="uid://bp3v1jl8pctro" path="res://Containers/plate.tscn" id="2_tfo2i"]
|
||||
[ext_resource type="Script" uid="uid://cquqe4f1m1sw6" path="res://addons/godot-xr-tools/objects/snap_zone.gd" id="3_lr065"]
|
||||
[ext_resource type="PackedScene" uid="uid://mpapt5mkbuao" path="res://prefabs/slide_off_dome.tscn" id="4_tfo2i"]
|
||||
[ext_resource type="PackedScene" uid="uid://mpapt5mkbuao" path="res://Prefabs/slide_off_dome.tscn" id="4_tfo2i"]
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_xmbo2"]
|
||||
radius = 0.3
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
[gd_scene format=3 uid="uid://cwnwo4i28upap"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://0y6wnjcsum6" path="res://stations/item_dispenser.gd" id="1_gkb3v"]
|
||||
[ext_resource type="PackedScene" uid="uid://dpot5qie20vf6" path="res://items/burger.tscn" id="2_mep2a"]
|
||||
[ext_resource type="Script" uid="uid://0y6wnjcsum6" path="res://Stations/item_dispenser.gd" id="1_gkb3v"]
|
||||
[ext_resource type="PackedScene" uid="uid://dpot5qie20vf6" path="res://Items/burger.tscn" id="2_mep2a"]
|
||||
[ext_resource type="Script" uid="uid://cquqe4f1m1sw6" path="res://addons/godot-xr-tools/objects/snap_zone.gd" id="3_3xuvi"]
|
||||
[ext_resource type="Texture2D" uid="uid://cexxfyw03hr81" path="res://textures/1.png" id="4_1f5le"]
|
||||
[ext_resource type="PackedScene" uid="uid://mpapt5mkbuao" path="res://prefabs/slide_off_dome.tscn" id="5_mep2a"]
|
||||
[ext_resource type="Texture2D" uid="uid://cexxfyw03hr81" path="res://Textures/1.png" id="4_1f5le"]
|
||||
[ext_resource type="PackedScene" uid="uid://mpapt5mkbuao" path="res://Prefabs/slide_off_dome.tscn" id="5_mep2a"]
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_xmbo2"]
|
||||
radius = 0.3
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@ func _on_object_picked_up(_item) -> void:
|
||||
is_washing = plate.is_dirty
|
||||
|
||||
|
||||
func _on_object_dropped(_item) -> void:
|
||||
func _on_object_dropped() -> void:
|
||||
print("Sink: object drop ")
|
||||
_reset_sink()
|
||||
|
||||
|
||||
+1
-12
@@ -1,6 +1,6 @@
|
||||
[gd_scene format=4 uid="uid://dvrk268s7gkxh"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://byh5j25mwt3oc" path="res://stations/sink.gd" id="1_7hh4b"]
|
||||
[ext_resource type="Script" uid="uid://byh5j25mwt3oc" path="res://Stations/sink.gd" id="1_7hh4b"]
|
||||
[ext_resource type="Script" uid="uid://cquqe4f1m1sw6" path="res://addons/godot-xr-tools/objects/snap_zone.gd" id="2_3ocod"]
|
||||
[ext_resource type="PackedScene" uid="uid://bxbocxdaayvwx" path="res://UI/progress_bar.tscn" id="4_1pinr"]
|
||||
|
||||
@@ -15,14 +15,6 @@ stereo = true
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_ai6d4"]
|
||||
size = Vector3(0.5, 0.128, 0.5)
|
||||
|
||||
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_np_sink"]
|
||||
properties/0/path = NodePath(".:time_washed")
|
||||
properties/0/spawn = true
|
||||
properties/0/replication_mode = 1
|
||||
properties/1/path = NodePath(".:is_washing")
|
||||
properties/1/spawn = true
|
||||
properties/1/replication_mode = 1
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_0gbf8"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_bvwq1"]
|
||||
@@ -234,9 +226,6 @@ transform = Transform3D(1, 0, 0, 0, 0.9367828, -0.34991136, 0, 0.34991136, 0.936
|
||||
operation = 2
|
||||
size = Vector3(1, 1.2053223, 0.352417)
|
||||
|
||||
[node name="Sync" type="MultiplayerSynchronizer" parent="." unique_id=1601242574]
|
||||
replication_config = SubResource("SceneReplicationConfig_np_sink")
|
||||
|
||||
[node name="ProgressBar3D" parent="." unique_id=654673176 instance=ExtResource("4_1pinr")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.8164611, -0.035980098)
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
extends "res://stations/hob.gd"
|
||||
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
pass # Replace with function body.
|
||||
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
func _process(delta: float) -> void:
|
||||
pass
|
||||
@@ -1 +0,0 @@
|
||||
uid://d0jbuttv8klii
|
||||
@@ -1,45 +0,0 @@
|
||||
extends Node
|
||||
|
||||
@export var snap_grid_size: float = 0.6
|
||||
|
||||
@export var move_handle: XRToolsPickable
|
||||
@export var station: StaticBody3D
|
||||
|
||||
var move_handle_rigid: RigidBody3D
|
||||
var original_collision_layer
|
||||
var original_y_pos: float
|
||||
|
||||
# TODO: Reject placing station in occupied spot.
|
||||
# TODO: Make ghost version of station that is displayed instead of model.
|
||||
# TODO: public toggle to disable handle in build mode.
|
||||
# TODO: If game exits build mode, drop the handle before disabling it.
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not station:
|
||||
push_error("StationMovement is missing referece to station")
|
||||
|
||||
move_handle_rigid = move_handle as RigidBody3D
|
||||
original_collision_layer = station.collision_layer
|
||||
original_y_pos = move_handle.transform.origin.y
|
||||
move_handle.picked_up.connect(_handle_pickup)
|
||||
move_handle.dropped.connect(_handle_drop)
|
||||
|
||||
|
||||
func _handle_pickup(_by):
|
||||
print("StationMovement handle pickup")
|
||||
station.collision_layer = 0
|
||||
|
||||
|
||||
func _handle_drop(_by):
|
||||
print("StationMovement handle drop")
|
||||
station.collision_layer = original_collision_layer
|
||||
move_handle.global_position = station.global_position + Vector3(0, original_y_pos, 0)
|
||||
move_handle.rotation = station.rotation
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
station.global_position.x = snappedf(move_handle.global_position.x, snap_grid_size)
|
||||
station.global_position.z = snappedf(move_handle.global_position.z, snap_grid_size)
|
||||
station.global_rotation_degrees.y = snappedf(move_handle.global_rotation_degrees.y, 90)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
uid://bu0g23cfrqy0m
|
||||
+137
-234
@@ -4,10 +4,9 @@ extends StaticBody3D
|
||||
const GAME_MANAGER = preload("res://GameManager.gd")
|
||||
|
||||
@export var thinking_duration: float = 3.0
|
||||
@export var ordering_duration: float = 50.0
|
||||
@export var primary_duration: float = 70.0
|
||||
@export var friend_duration: float = 12.0
|
||||
@export var eating_duration: float = 5.0
|
||||
@export var primary_duration: float = 60.0
|
||||
@export var friend_duration: float = 10.0
|
||||
@export var eating_duration: float = 4.0
|
||||
@export var money_sound: AudioStream
|
||||
|
||||
@onready var label_3d: Label3D = $Label3D
|
||||
@@ -15,11 +14,8 @@ const GAME_MANAGER = preload("res://GameManager.gd")
|
||||
@onready var snap_zones: Array[XRToolsSnapZone] = []
|
||||
@onready var progress_bar: ProgressBar3D = $ProgressBar3D
|
||||
@onready var audio_player: AudioStreamPlayer3D = $AudioStreamPlayer3D
|
||||
@onready var customers: Node3D = $Customers
|
||||
@onready var player_detect_area_3d: Area3D = $PlayerDetectArea3D
|
||||
|
||||
const lbl_thinking: String = "..."
|
||||
const lbl_ordering: String = "!"
|
||||
const lbl_thinking: String = "Thinking"
|
||||
const lbl_gameover: String = "Game Over!"
|
||||
const lbl_eating: String = "Eating..."
|
||||
|
||||
@@ -28,73 +24,26 @@ const lbl_eating: String = "Eating..."
|
||||
enum TableState {
|
||||
EMPTY, # Just finished eating or havent eaten yet
|
||||
THINKING,
|
||||
ORDERING, # Wating for player to take their order
|
||||
WAITING_PRIMARY, # Inital wait to be served
|
||||
WAITING_FRIEND, # Waiting for friends food to arrive
|
||||
EATING
|
||||
}
|
||||
|
||||
## State is server-authoritative and synced (see table.tscn's Sync node);
|
||||
## 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
|
||||
var _state_time: float = 0.0
|
||||
var _state_duration: float = 0.0 # set in _set_state_time
|
||||
var _original_orders: Array[String] = []
|
||||
var _unsatisfied_orders: Array[String] = []
|
||||
var _players_count: int = 0
|
||||
|
||||
|
||||
func place_order() -> void:
|
||||
print("Table place_order()")
|
||||
var new_orders: Array[String] = _unsatisfied_orders.duplicate()
|
||||
for _i in range(0, randi_range(1, 2)):
|
||||
new_orders.append(GAME_MANAGER.get_random_meal())
|
||||
for _i in range(0, randi_range(0, 1)):
|
||||
new_orders.append(GAME_MANAGER.get_random_side())
|
||||
_unsatisfied_orders = new_orders
|
||||
_original_orders = _unsatisfied_orders.duplicate()
|
||||
|
||||
|
||||
func absorb_items():
|
||||
print("Table: absorb_items()")
|
||||
for snap_zone_node in snap_zones:
|
||||
var held_object = snap_zone_node.picked_up_object
|
||||
if not held_object:
|
||||
continue
|
||||
|
||||
_absorb_item_if_correct(held_object)
|
||||
|
||||
|
||||
func satisfyAllOrders() -> void:
|
||||
print("Table: satisfyAllOrders()")
|
||||
_unsatisfied_orders.clear()
|
||||
_original_orders.clear()
|
||||
clearAllFood()
|
||||
_setState(TableState.EMPTY)
|
||||
|
||||
|
||||
func clearAllFood() -> void:
|
||||
print("Table: clearAllFood()")
|
||||
for zone in snap_zones:
|
||||
var held_object = zone.picked_up_object
|
||||
if not held_object:
|
||||
continue
|
||||
|
||||
var plate: PlateController = _get_plate_controller_from_item(held_object)
|
||||
var food_item: FoodItem = Helper.find_food_item(held_object)
|
||||
if plate:
|
||||
plate.container.clear()
|
||||
plate.is_dirty = true
|
||||
|
||||
# Side or other food item directly on table
|
||||
elif food_item and food_item.type == FoodItem.Type.SIDE:
|
||||
zone.drop_object()
|
||||
NetworkManager.despawn_item(held_object)
|
||||
|
||||
## State is server-authoritative and replicated. Transitions only ever run where
|
||||
## NetworkManager.owns_world() is true — the whole station's _process is gated
|
||||
## off for non-owners (NetWorld._gate) — and the setters below just refresh the
|
||||
## display, so the server (via _setState) and clients (via incoming sync) show
|
||||
## the same thing. Use _setState(), never assign `state` directly.
|
||||
##
|
||||
## These are deliberately not underscore-prefixed: that prefix is what marks a
|
||||
## variable as private and unreplicated (see NetReplication), and this is exactly
|
||||
## the state that has to reach every peer. state_duration is replicated for the
|
||||
## same reason — the progress bar divides by it, and on a client that never ran a
|
||||
## transition it would otherwise still be zero.
|
||||
var state: TableState = TableState.EMPTY: set = _set_state
|
||||
var state_time: float = 0.0: set = _set_state_time
|
||||
var state_duration: float = 0.0 # set in _setState alongside state_time
|
||||
var unsatisfied_orders: Array[String] = []: set = _set_unsatisfied_orders
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -105,7 +54,6 @@ func _ready() -> void:
|
||||
if not progress_bar or progress_bar is not ProgressBar3D:
|
||||
push_error("Table missing reference to progressbar, or wrong type:", progress_bar)
|
||||
progress_bar.y_billboard = true
|
||||
progress_bar.exponential = true
|
||||
# Render whatever state already arrived from the server before we were in
|
||||
# the tree (see the guard in _refresh_display).
|
||||
_refresh_display.call_deferred()
|
||||
@@ -131,123 +79,79 @@ func _ready() -> void:
|
||||
|
||||
func _on_object_picked_up(_item) -> void:
|
||||
print("Table: object picked up: ", _item)
|
||||
if _state == TableState.EATING:
|
||||
if state == TableState.EATING:
|
||||
return
|
||||
_absorb_item_if_correct(_item)
|
||||
|
||||
|
||||
func _on_object_dropped(_item) -> void:
|
||||
print("Table: object dropped, item: ", _item)
|
||||
# If then player picks up a food_item (side) the table has already registerd, unregister
|
||||
var food_item: FoodItem = Helper.find_food_item(_item)
|
||||
if food_item and food_item.type == FoodItem.Type.SIDE:
|
||||
var original_count = _original_orders.count(food_item.id)
|
||||
var unsatisfied_count = _unsatisfied_orders.count(food_item.id)
|
||||
if original_count > unsatisfied_count:
|
||||
_unsatisfied_orders.append(food_item.id)
|
||||
func _on_object_dropped() -> void:
|
||||
print("Table: object dropped ")
|
||||
|
||||
|
||||
# original : burger cube, cube
|
||||
# unsatisfied : burger cube
|
||||
# table :
|
||||
|
||||
# There is no on_stay signal, we have to track player with bool
|
||||
func _on_player_enter(_body):
|
||||
_players_count += 1
|
||||
print("Table _on_player_enter() pleryers_count: ", _players_count)
|
||||
|
||||
|
||||
func _on_player_exit(_body):
|
||||
_players_count -= 1
|
||||
print("Table _on_player_exit() pleryers_count: ", _players_count)
|
||||
|
||||
|
||||
func _place_order_if_player():
|
||||
if _state != TableState.ORDERING:
|
||||
return
|
||||
if _players_count > 0:
|
||||
place_order()
|
||||
_setState(TableState.WAITING_PRIMARY)
|
||||
|
||||
|
||||
# func _get_plate_controller_from_item(_item: Node) -> PlateController:
|
||||
# return _item.get_children().filter(func(c): return c is PlateController).front() as PlateController
|
||||
|
||||
|
||||
func _get_plate_controller_from_item(_item: Node) -> PlateController:
|
||||
if not _item:
|
||||
print("_item is null")
|
||||
return null
|
||||
|
||||
for child in _item.get_children():
|
||||
if child is PlateController:
|
||||
print("found child")
|
||||
return child
|
||||
|
||||
print("no match")
|
||||
return null
|
||||
func _get_plate_from_item(_item: Node) -> PlateController:
|
||||
return _item.get_children().filter(func(c): return c is PlateController).front() as PlateController
|
||||
|
||||
|
||||
func _absorb_item_if_correct(_item: Node) -> void:
|
||||
print("Table _absorb_item_if_correct, item: ", _item)
|
||||
if not _item:
|
||||
return
|
||||
if not (_state == TableState.WAITING_PRIMARY or _state == TableState.WAITING_FRIEND):
|
||||
# Get plate controller in childer of _item (hopefully a plate XRpickable)
|
||||
var plate = _get_plate_from_item(_item)
|
||||
if not plate:
|
||||
print("Table: held object is not a Plate")
|
||||
return
|
||||
print("Table: held a Plate!")
|
||||
|
||||
# Plate: Get plate controller in child of _item (hopefully a plate XRpickable)
|
||||
var plate_controller = _get_plate_controller_from_item(_item)
|
||||
print("Table _absorb_item_if_correct plate_controller ref: ", plate_controller)
|
||||
_item.print_tree_pretty()
|
||||
|
||||
if plate_controller:
|
||||
# Absorm items from the plate we want
|
||||
for food_item: FoodItem in plate_controller.container.contained_items: # TOOD: handle registering the meal again when a side is added to the plate
|
||||
if food_item.id in _unsatisfied_orders and not food_item.is_absorbed:
|
||||
print("Table: _absorb_item_if_correct held a plate with FoodItem that is in unsatisfied orders, removing it")
|
||||
(plate_controller.get_parent() as XRToolsPickable).get_picked_up_by().enabled = false # Lock meal that is deliverd
|
||||
_unsatisfied_orders.erase(food_item.id)
|
||||
food_item.is_absorbed = true
|
||||
_update_state_from_orders()
|
||||
return
|
||||
|
||||
# Side pickable item, no container
|
||||
var food_item: FoodItem = Helper.find_food_item(_item)
|
||||
if food_item and food_item.type == FoodItem.Type.SIDE:
|
||||
print("Table: _absorb_item_if_correct held a side that is in unsatisfied orders, removing it")
|
||||
# Do not lock snap zone here. Problem if table want many meals, but to many sides have filled up slots, so can't place plates.
|
||||
_unsatisfied_orders.erase(food_item.id)
|
||||
_update_state_from_orders()
|
||||
return
|
||||
# Absorm items from the plate we want
|
||||
for food_item in plate.container.contained_items:
|
||||
print("Table: held a plate with FoodItem: ", food_item.id)
|
||||
if food_item.id in unsatisfied_orders:
|
||||
print("Table: held FoodItem is in unsatisfied orders, removing it")
|
||||
# Reassign rather than mutate in place. This property has a setter that
|
||||
# refreshes the label, and mutating an array never fires it — so the
|
||||
# peer that actually served the food would be the one peer whose table
|
||||
# still showed the order outstanding. duplicate() also preserves the
|
||||
# Array[String] typing the property requires.
|
||||
var remaining := unsatisfied_orders.duplicate()
|
||||
remaining.erase(food_item.id)
|
||||
unsatisfied_orders = remaining
|
||||
if unsatisfied_orders.size() > 0 and state != TableState.EATING:
|
||||
_setState(TableState.WAITING_FRIEND)
|
||||
|
||||
print("Table: absorb_item_if_correct() held object is not a Plate or Side")
|
||||
# Table has everything it wants. Start eating
|
||||
elif unsatisfied_orders.is_empty() and state != TableState.EATING:
|
||||
GAME_MANAGER.money += food_item.sell_value
|
||||
audio_player.stream = money_sound
|
||||
audio_player.play()
|
||||
_setState(TableState.EATING)
|
||||
|
||||
|
||||
func _update_state_from_orders():
|
||||
print("Table _update_state_from_orders: unsatisfied_orders: ", _unsatisfied_orders)
|
||||
if _unsatisfied_orders.size() > 0 and _state != TableState.EATING:
|
||||
_setState(TableState.WAITING_FRIEND)
|
||||
|
||||
# Table has everything it wants. Start eating
|
||||
elif _unsatisfied_orders.is_empty() and _state != TableState.EATING:
|
||||
_setState(TableState.EATING)
|
||||
func place_order() -> void:
|
||||
print("Table: place_order()")
|
||||
var new_orders = unsatisfied_orders.duplicate()
|
||||
new_orders.append(GAME_MANAGER.get_random_meal())
|
||||
new_orders.append(GAME_MANAGER.get_random_meal())
|
||||
_set_unsatisfied_orders(new_orders)
|
||||
|
||||
|
||||
func _collect_money_from_food():
|
||||
func satisfyAllOrders() -> void:
|
||||
print("Table: satisfyAllOrders()")
|
||||
# Assigned, not cleared in place, for the same reason as in
|
||||
# _absorb_item_if_correct: clear() would not fire the setter.
|
||||
unsatisfied_orders = []
|
||||
clearAllPlates()
|
||||
_setState(TableState.EMPTY)
|
||||
|
||||
|
||||
func clearAllPlates() -> void:
|
||||
print("Table: clearAllPlates()")
|
||||
for snap_zone_node in snap_zones:
|
||||
var held_object = snap_zone_node.picked_up_object
|
||||
if not held_object:
|
||||
continue
|
||||
|
||||
var plate: PlateController = _get_plate_controller_from_item(held_object)
|
||||
var food_item: FoodItem = Helper.find_food_item(held_object)
|
||||
var plate = _get_plate_from_item(held_object)
|
||||
if plate:
|
||||
for fi: FoodItem in plate.get_food_items():
|
||||
GAME_MANAGER.money += fi.sell_value
|
||||
elif food_item:
|
||||
GAME_MANAGER.money += food_item.sell_value
|
||||
|
||||
print("Table _collect_money_from_food(), money: ", GAME_MANAGER.money)
|
||||
plate.container.clear()
|
||||
plate.is_dirty = true
|
||||
|
||||
|
||||
func _set_snap_zones_enabled(value: bool) -> void:
|
||||
@@ -256,113 +160,112 @@ func _set_snap_zones_enabled(value: bool) -> void:
|
||||
|
||||
|
||||
## Server-only state transition: sets the new state's timer and assigns
|
||||
## _state (whose setter refreshes the display on every peer).
|
||||
## state (whose setter refreshes the display on every peer).
|
||||
func _setState(newState: TableState) -> void:
|
||||
print("Table State START: ", TableState.keys()[newState])
|
||||
print("Table set state: ", TableState.keys()[newState])
|
||||
|
||||
match newState:
|
||||
TableState.EMPTY:
|
||||
_state_time = randf_range(3, 7)
|
||||
_state_duration = _state_time
|
||||
customers.visible = false
|
||||
_state = newState
|
||||
state_time = 5.0
|
||||
state_duration = 5.0
|
||||
TableState.THINKING:
|
||||
_state_time = thinking_duration
|
||||
_state_duration = thinking_duration # Can't be done in _set_state(), will set duration inside timer
|
||||
_state = newState
|
||||
TableState.ORDERING:
|
||||
_state_time = ordering_duration
|
||||
_state_duration = ordering_duration
|
||||
_state = newState
|
||||
state_time = thinking_duration
|
||||
state_duration = thinking_duration # Can't be done in _set_state(), will set duration inside timer
|
||||
TableState.WAITING_PRIMARY:
|
||||
_state_time = primary_duration
|
||||
_state_duration = primary_duration
|
||||
_state = newState
|
||||
# Absorm meals that were already in the table when the state starts
|
||||
for zone in snap_zones:
|
||||
_absorb_item_if_correct(zone.picked_up_object)
|
||||
state_time = primary_duration
|
||||
state_duration = primary_duration
|
||||
TableState.WAITING_FRIEND:
|
||||
_state_time = friend_duration
|
||||
_state_duration = friend_duration
|
||||
_state = newState
|
||||
state_time = friend_duration
|
||||
state_duration = friend_duration
|
||||
TableState.EATING:
|
||||
_state_time = eating_duration
|
||||
_state_duration = eating_duration
|
||||
_collect_money_from_food()
|
||||
state_time = eating_duration
|
||||
state_duration = eating_duration
|
||||
_set_snap_zones_enabled(false)
|
||||
audio_player.stream = money_sound
|
||||
audio_player.play()
|
||||
_state = newState
|
||||
|
||||
|
||||
# When state timer is finished, do this stuff before moving to next state
|
||||
func _state_end(oldState: TableState):
|
||||
print("Table State END : ", TableState.keys()[oldState])
|
||||
match oldState:
|
||||
TableState.EMPTY:
|
||||
customers.visible = true
|
||||
_setState(TableState.THINKING)
|
||||
|
||||
TableState.THINKING:
|
||||
_setState(TableState.ORDERING)
|
||||
|
||||
TableState.EATING:
|
||||
clearAllFood()
|
||||
_set_snap_zones_enabled(true)
|
||||
_setState(TableState.EMPTY)
|
||||
|
||||
TableState.ORDERING, TableState.WAITING_PRIMARY, TableState.WAITING_FRIEND:
|
||||
label_3d.text = lbl_gameover
|
||||
GAME_MANAGER.game_over()
|
||||
state = newState
|
||||
|
||||
|
||||
## Pure presentation, driven off the current (locally authoritative or
|
||||
## synced-from-server) state. Runs on every peer.
|
||||
func _refresh_display() -> void:
|
||||
# _state, _state_time and _unsatisfied_orders are replicated with spawn=true,
|
||||
# state, state_time and unsatisfied_orders are replicated with spawn=true,
|
||||
# and MultiplayerSpawner applies a spawn payload BEFORE the node enters the
|
||||
# tree — so these setters fire while the @onready children below are still
|
||||
# null. Bail out until _ready() has resolved them; _ready() calls back in
|
||||
# once it has, so nothing that arrived early is lost.
|
||||
if not progress_bar or not label_3d or not label_3d_time:
|
||||
push_error("Table missing progress bar or label")
|
||||
return
|
||||
match _state:
|
||||
match state:
|
||||
TableState.EMPTY:
|
||||
progress_bar.set_bar_visible(false)
|
||||
label_3d.text = ""
|
||||
label_3d.text = "empty"
|
||||
TableState.THINKING:
|
||||
progress_bar.set_bar_visible(false)
|
||||
label_3d.text = lbl_thinking
|
||||
TableState.ORDERING:
|
||||
progress_bar.set_bar_visible(true)
|
||||
progress_bar.override_fill_color(Color.RED if _state_time < 10 else Color.YELLOW)
|
||||
label_3d.text = lbl_ordering
|
||||
TableState.WAITING_PRIMARY:
|
||||
label_3d.text = "%s\n%s" % [TableState.keys()[_state], "\n".join(_unsatisfied_orders)]
|
||||
label_3d.text = "%s\n%s" % [TableState.keys()[state], "\n".join(unsatisfied_orders)]
|
||||
progress_bar.set_bar_visible(true)
|
||||
progress_bar.override_fill_color(Color.RED if _state_time < 10 else Color.YELLOW)
|
||||
progress_bar.override_fill_color(Color.RED if state_time < 10 else Color.YELLOW)
|
||||
TableState.WAITING_FRIEND:
|
||||
label_3d.text = "%s\n%s" % [TableState.keys()[_state], "\n".join(_unsatisfied_orders)]
|
||||
label_3d.text = "%s\n%s" % [TableState.keys()[state], "\n".join(unsatisfied_orders)]
|
||||
progress_bar.set_bar_visible(true)
|
||||
progress_bar.override_fill_color(Color.RED if _state_time < 5 else Color.YELLOW)
|
||||
progress_bar.override_fill_color(Color.RED if state_time < 5 else Color.YELLOW)
|
||||
TableState.EATING:
|
||||
label_3d.text = lbl_eating
|
||||
progress_bar.set_bar_visible(false)
|
||||
|
||||
label_3d_time.text = "%.1f" % _state_time
|
||||
progress_bar.set_progress(clampf(float(_state_time) / _state_duration, 0.0, 1.0))
|
||||
label_3d_time.text = "%.1f" % state_time
|
||||
var progress := state_time / state_duration if state_duration > 0.0 else 0.0
|
||||
progress_bar.set_progress(clampf(progress, 0.0, 1.0))
|
||||
|
||||
|
||||
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:
|
||||
_refresh_display()
|
||||
_place_order_if_player()
|
||||
|
||||
# Wait for state to finish
|
||||
if _state_time > 0.0:
|
||||
_state_time = max(0.0, _state_time - delta)
|
||||
if state_time > 0.0:
|
||||
state_time = max(0.0, state_time - delta)
|
||||
return
|
||||
|
||||
if GAME_MANAGER.game_state == GAME_MANAGER.GameState.GAME_OVER:
|
||||
return
|
||||
|
||||
_state_end(_state)
|
||||
# When state timer is finished, do this stuff before moving to next state
|
||||
match state:
|
||||
TableState.EMPTY:
|
||||
print("Table State EMPTY finish")
|
||||
_setState(TableState.THINKING)
|
||||
|
||||
TableState.THINKING:
|
||||
print("Table State THINKING finish")
|
||||
place_order()
|
||||
_setState(TableState.WAITING_PRIMARY)
|
||||
|
||||
TableState.WAITING_PRIMARY:
|
||||
print("Table State WAITING_PRIMARY finish")
|
||||
label_3d.text = lbl_gameover
|
||||
GAME_MANAGER.game_over()
|
||||
|
||||
TableState.WAITING_FRIEND:
|
||||
print("Table State WAITING_FRIEND finish")
|
||||
label_3d.text = lbl_gameover
|
||||
GAME_MANAGER.game_over()
|
||||
|
||||
TableState.EATING:
|
||||
print("Table State EATING finish")
|
||||
clearAllPlates()
|
||||
_set_snap_zones_enabled(true)
|
||||
_setState(TableState.EMPTY)
|
||||
|
||||
+5
-31
@@ -1,9 +1,9 @@
|
||||
[gd_scene format=3 uid="uid://caf0xanmxbshy"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://caeikc7e3igkd" path="res://stations/table.gd" id="1_2vcpj"]
|
||||
[ext_resource type="Script" uid="uid://caeikc7e3igkd" path="res://Stations/table.gd" id="1_2vcpj"]
|
||||
[ext_resource type="Script" uid="uid://cquqe4f1m1sw6" path="res://addons/godot-xr-tools/objects/snap_zone.gd" id="1_8j1nt"]
|
||||
[ext_resource type="AudioStream" uid="uid://clkjwcawdqvta" path="res://sounds/money.mp3" id="2_jslhm"]
|
||||
[ext_resource type="AudioStream" uid="uid://daiv8ubwdbidf" path="res://sounds/220195__gameaudio__click-wooden-1.wav" id="3_0s6ir"]
|
||||
[ext_resource type="AudioStream" uid="uid://clkjwcawdqvta" path="res://Sounds/money.mp3" id="2_jslhm"]
|
||||
[ext_resource type="AudioStream" uid="uid://daiv8ubwdbidf" path="res://Sounds/220195__gameaudio__click-wooden-1.wav" id="3_0s6ir"]
|
||||
[ext_resource type="PackedScene" uid="uid://bxbocxdaayvwx" path="res://UI/progress_bar.tscn" id="3_kjf1i"]
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_24d3s"]
|
||||
@@ -15,21 +15,7 @@ radius = 0.6
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_24d3s"]
|
||||
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
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_vlqg6"]
|
||||
size = Vector3(2.1, 0.5, 2.1)
|
||||
|
||||
[node name="Table" type="StaticBody3D" unique_id=1863572470 groups=["station", "table"]]
|
||||
[node name="Table" type="StaticBody3D" unique_id=1863572470 groups=["station"]]
|
||||
script = ExtResource("1_2vcpj")
|
||||
money_sound = ExtResource("2_jslhm")
|
||||
|
||||
@@ -187,26 +173,14 @@ vertical_alignment = 0
|
||||
line_spacing = -15.0
|
||||
|
||||
[node name="Label3DTime" type="Label3D" parent="." unique_id=1534849507]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.513246, 0)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.4532461, 0)
|
||||
pixel_size = 0.003
|
||||
billboard = 2
|
||||
text = "20.1s"
|
||||
|
||||
[node name="Sync" type="MultiplayerSynchronizer" parent="." unique_id=2000411505]
|
||||
replication_config = SubResource("SceneReplicationConfig_np_table")
|
||||
|
||||
[node name="ProgressBar3D" parent="." unique_id=654673176 instance=ExtResource("3_kjf1i")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.3676775, 0)
|
||||
y_billboard = true
|
||||
|
||||
[node name="AudioStreamPlayer3D" type="AudioStreamPlayer3D" parent="." unique_id=724610642]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.2935008, 0)
|
||||
|
||||
[node name="PlayerDetectArea3D" type="Area3D" parent="." unique_id=913445150]
|
||||
collision_mask = 524288
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="PlayerDetectArea3D" unique_id=1538221021]
|
||||
shape = SubResource("BoxShape3D_vlqg6")
|
||||
|
||||
[connection signal="body_entered" from="PlayerDetectArea3D" to="." method="_on_player_enter"]
|
||||
[connection signal="body_exited" from="PlayerDetectArea3D" to="." method="_on_player_exit"]
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
|
||||
|
||||
|
||||
- Move recipes into a Yaml file that is read, and handles symetry through a Static manager class?
|
||||
A `FoodItem` has an ID.
|
||||
Example:
|
||||
```yaml
|
||||
combining: # The combinging of two items, entries always have a list of exactly two items
|
||||
cooked_burger:
|
||||
- cooked_burger
|
||||
- burger_buns
|
||||
dough:
|
||||
- flour
|
||||
- water
|
||||
|
||||
cooking:
|
||||
cooked_burger:
|
||||
ingredient: raw_burger
|
||||
time: 3
|
||||
charcoal:
|
||||
ingredient: cooked_burger
|
||||
time: 4
|
||||
charcoal:
|
||||
ingredient: toast
|
||||
time: 2
|
||||
|
||||
augmenting: # Process of adding an item onto another without changing it's type, but toggling attributes.
|
||||
cooked_burger:
|
||||
has_tomato: sliced_tomato
|
||||
has_cheese: cheese
|
||||
salad:
|
||||
has_tomato: sliced_tomato
|
||||
has_olives: olives
|
||||
|
||||
chopping:
|
||||
salad:
|
||||
ingredient: lettuce
|
||||
work: 50 # Wave arms faster makes work go faster
|
||||
|
||||
rolling:
|
||||
pie_base:
|
||||
ingredient: dough
|
||||
work: 80
|
||||
```
|
||||
```py
|
||||
on_area_enter(other):
|
||||
var result = RecipeManager.getCombination(my_id, other_id)
|
||||
if result:
|
||||
convertInto(result)
|
||||
```
|
||||
|
||||
- Counters are stations like hobs that convert items by chopping, rolling ect depending on `RecipeManager`,
|
||||
and only converts items after hand gestures are done enough?
|
||||
|
||||
- Plates are lists of `FoodItems`?
|
||||
Plate[`Hamburger`] accepts `Chips` => Plate[`Hamburger`, `Chips`]
|
||||
Plate[`Hamburger`, `Chips`] accepts `Cheese` => Plate[`Hamburger(Cheese=true)`, `Chips`]
|
||||
The logic for combining has to be expanded for plates.
|
||||
- Plates have a property `is_dirty` they get after a customer clears the plate, and is disabled after being processed in `Sink`
|
||||
@@ -3,7 +3,7 @@
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cexxfyw03hr81"
|
||||
path.s3tc="res://.godot/imported/1.png-28d6e3d31e46c95f0dea1abf906c0f74.s3tc.ctex"
|
||||
path.s3tc="res://.godot/imported/1.png-1282cd758f0d245fa29065df8c6fe28d.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
@@ -11,8 +11,8 @@ metadata={
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://textures/1.png"
|
||||
dest_files=["res://.godot/imported/1.png-28d6e3d31e46c95f0dea1abf906c0f74.s3tc.ctex"]
|
||||
source_file="res://Textures/1.png"
|
||||
dest_files=["res://.godot/imported/1.png-1282cd758f0d245fa29065df8c6fe28d.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cxsy4fi0iv1dx"
|
||||
path.s3tc="res://.godot/imported/2.png-3b4d648e04c4a84e8b8f6c622069553c.s3tc.ctex"
|
||||
path.s3tc="res://.godot/imported/2.png-58b544529b3c8efe890c8b21f147e810.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
@@ -11,8 +11,8 @@ metadata={
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://textures/2.png"
|
||||
dest_files=["res://.godot/imported/2.png-3b4d648e04c4a84e8b8f6c622069553c.s3tc.ctex"]
|
||||
source_file="res://Textures/2.png"
|
||||
dest_files=["res://.godot/imported/2.png-58b544529b3c8efe890c8b21f147e810.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cxeeadx0c2ygg"
|
||||
path.s3tc="res://.godot/imported/3.png-ff3babb0d8b4fdc49e4a4961a679d180.s3tc.ctex"
|
||||
path.s3tc="res://.godot/imported/3.png-ea971c9bd22bfef88dcd66b7417b5707.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
@@ -11,8 +11,8 @@ metadata={
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://textures/3.png"
|
||||
dest_files=["res://.godot/imported/3.png-ff3babb0d8b4fdc49e4a4961a679d180.s3tc.ctex"]
|
||||
source_file="res://Textures/3.png"
|
||||
dest_files=["res://.godot/imported/3.png-ea971c9bd22bfef88dcd66b7417b5707.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://xpf60mxi3cf"
|
||||
path.s3tc="res://.godot/imported/4.png-7c86e7f37dd6d943dc49fdd7899e8cb6.s3tc.ctex"
|
||||
path.s3tc="res://.godot/imported/4.png-352134ccf44954619410b58ec193bd16.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
@@ -11,8 +11,8 @@ metadata={
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://textures/4.png"
|
||||
dest_files=["res://.godot/imported/4.png-7c86e7f37dd6d943dc49fdd7899e8cb6.s3tc.ctex"]
|
||||
source_file="res://Textures/4.png"
|
||||
dest_files=["res://.godot/imported/4.png-352134ccf44954619410b58ec193bd16.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b7n8d4krwbcvl"
|
||||
path.s3tc="res://.godot/imported/5.png-b386ff019a5e522d985ab44e8f6c88d9.s3tc.ctex"
|
||||
path.s3tc="res://.godot/imported/5.png-be0d39347a68c1d063c92edeb31b0a20.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
@@ -11,8 +11,8 @@ metadata={
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://textures/5.png"
|
||||
dest_files=["res://.godot/imported/5.png-b386ff019a5e522d985ab44e8f6c88d9.s3tc.ctex"]
|
||||
source_file="res://Textures/5.png"
|
||||
dest_files=["res://.godot/imported/5.png-be0d39347a68c1d063c92edeb31b0a20.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://eqp04wybmg"
|
||||
path="res://.godot/imported/devTex.svg-246e77b8f216572aac74f8f3d27707b4.ctex"
|
||||
path="res://.godot/imported/devTex.svg-7096dfe1fce7d6bfeb43d4ba1bd108ed.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://textures/devTex.svg"
|
||||
dest_files=["res://.godot/imported/devTex.svg-246e77b8f216572aac74f8f3d27707b4.ctex"]
|
||||
source_file="res://Textures/devTex.svg"
|
||||
dest_files=["res://.godot/imported/devTex.svg-7096dfe1fce7d6bfeb43d4ba1bd108ed.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[gd_resource type="StandardMaterial3D" format=3 uid="uid://cmia50cfqxxo4"]
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://eqp04wybmg" path="res://textures/devTex.svg" id="1_3jxsn"]
|
||||
[ext_resource type="Texture2D" uid="uid://eqp04wybmg" path="res://Textures/devTex.svg" id="1_3jxsn"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_0gbf8"]
|
||||
transparency = 1
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://hfkxs3ncjc4m"
|
||||
path="res://.godot/imported/NightSkyHDRI009.png-30c624de99c676c23642a0e7a463453d.ctex"
|
||||
path="res://.godot/imported/NightSkyHDRI009.png-5fecb5274d03e5767c649d4bae381a39.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://textures/sky/NightSkyHDRI009.png"
|
||||
dest_files=["res://.godot/imported/NightSkyHDRI009.png-30c624de99c676c23642a0e7a463453d.ctex"]
|
||||
source_file="res://Textures/sky/NightSkyHDRI009.png"
|
||||
dest_files=["res://.godot/imported/NightSkyHDRI009.png-5fecb5274d03e5767c649d4bae381a39.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[gd_resource type="Sky" format=3 uid="uid://c1abtpwhdm2d5"]
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://ci0hjoabnf8nu" path="res://textures/sky/NightSkyHDRI009_16K_HDR.exr" id="acg_mft3ic9d"]
|
||||
[ext_resource type="Texture2D" uid="uid://ci0hjoabnf8nu" path="res://Textures/sky/NightSkyHDRI009_16K_HDR.exr" id="acg_mft3ic9d"]
|
||||
|
||||
[sub_resource type="PanoramaSkyMaterial" id="acg_025sfqr4"]
|
||||
panorama = ExtResource("acg_mft3ic9d")
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ci0hjoabnf8nu"
|
||||
path.bptc="res://.godot/imported/NightSkyHDRI009_16K_HDR.exr-43fd92962b13d105254e11ecfdaf0858.bptc.ctex"
|
||||
path.bptc="res://.godot/imported/NightSkyHDRI009_16K_HDR.exr-9a5d77664863ac9d0a6a2889b31099ca.bptc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
@@ -11,8 +11,8 @@ metadata={
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://textures/sky/NightSkyHDRI009_16K_HDR.exr"
|
||||
dest_files=["res://.godot/imported/NightSkyHDRI009_16K_HDR.exr-43fd92962b13d105254e11ecfdaf0858.bptc.ctex"]
|
||||
source_file="res://Textures/sky/NightSkyHDRI009_16K_HDR.exr"
|
||||
dest_files=["res://.godot/imported/NightSkyHDRI009_16K_HDR.exr-9a5d77664863ac9d0a6a2889b31099ca.bptc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c64om8q11trnm"
|
||||
path="res://.godot/imported/NightSkyHDRI009_16K_TONEMAPPED.jpg-772032c9d36bbd100929d978b6fbe78b.ctex"
|
||||
path="res://.godot/imported/NightSkyHDRI009_16K_TONEMAPPED.jpg-8d036fe183f31a1e4946600f7e0a089a.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://textures/sky/NightSkyHDRI009_16K_TONEMAPPED.jpg"
|
||||
dest_files=["res://.godot/imported/NightSkyHDRI009_16K_TONEMAPPED.jpg-772032c9d36bbd100929d978b6fbe78b.ctex"]
|
||||
source_file="res://Textures/sky/NightSkyHDRI009_16K_TONEMAPPED.jpg"
|
||||
dest_files=["res://.godot/imported/NightSkyHDRI009_16K_TONEMAPPED.jpg-8d036fe183f31a1e4946600f7e0a089a.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ func _on_john_pressed() -> void:
|
||||
|
||||
func _on_host_pressed() -> void:
|
||||
NetworkManager.request_host()
|
||||
get_tree().change_scene_to_file.call_deferred(MULTIPLAYER_SCENE)
|
||||
get_tree().change_scene_to_file.call_deferred("res://Scenes/JonScene.tscn")
|
||||
|
||||
|
||||
func _on_join_pressed() -> void:
|
||||
|
||||
@@ -17,7 +17,6 @@ transparent_bg = true
|
||||
size = Vector2i(320, 80)
|
||||
|
||||
[node name="ProgressBar" type="ProgressBar" parent="Sprite3D/SubViewport" unique_id=727723524]
|
||||
custom_minimum_size = Vector2(0, 400)
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
@@ -31,4 +30,3 @@ grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 4
|
||||
show_percentage = false
|
||||
|
||||
@@ -2,7 +2,6 @@ extends Node3D
|
||||
class_name ProgressBar3D
|
||||
|
||||
@export var y_billboard: bool = false
|
||||
@export var exponential:bool = false
|
||||
|
||||
@onready var progress_bar: ProgressBar = $Sprite3D/SubViewport/ProgressBar
|
||||
@onready var sprite_3d: Sprite3D = $Sprite3D
|
||||
@@ -15,10 +14,7 @@ func _ready() -> void:
|
||||
$Sprite3D.billboard = BaseMaterial3D.BillboardMode.BILLBOARD_FIXED_Y
|
||||
|
||||
func set_progress(value: float) -> void:
|
||||
if exponential:
|
||||
progress_bar.value = clamp(pow(value, 2.1), 0.0, 1.0) * 100
|
||||
else:
|
||||
progress_bar.value = clamp(value, 0.0, 1.0) * 100
|
||||
progress_bar.value = clamp(value, 0.0, 1.0) * 100
|
||||
|
||||
|
||||
func get_progress() -> float:
|
||||
|
||||
@@ -7,7 +7,7 @@ extends Area3D
|
||||
signal has_picked_up(what)
|
||||
|
||||
## Signal emitted when the snap-zone drops something
|
||||
signal has_dropped(what)
|
||||
signal has_dropped
|
||||
|
||||
# Signal emitted when the highlight state changes
|
||||
signal highlight_updated(pickable, enable)
|
||||
@@ -164,9 +164,9 @@ func drop_object() -> void:
|
||||
return
|
||||
|
||||
# let go of this object
|
||||
has_dropped.emit(picked_up_object)
|
||||
picked_up_object.let_go(self, Vector3.ZERO, Vector3.ZERO)
|
||||
picked_up_object = null
|
||||
has_dropped.emit()
|
||||
highlight_updated.emit(self, enabled)
|
||||
|
||||
|
||||
|
||||
@@ -1,352 +0,0 @@
|
||||
[gd_scene format=3 uid="uid://bu6jt26pq2y0k"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://q37nqadkibbp" path="res://scenes/door.tscn" id="1_hgrxd"]
|
||||
[ext_resource type="PackedScene" uid="uid://j7caslh27nor" path="res://stations/Hob.tscn" id="1_p8umn"]
|
||||
[ext_resource type="PackedScene" uid="uid://caf0xanmxbshy" path="res://stations/table.tscn" id="2_d63j6"]
|
||||
[ext_resource type="PackedScene" uid="uid://dlw5geheupgpt" path="res://stations/hatch.tscn" id="3_d63j6"]
|
||||
[ext_resource type="PackedScene" uid="uid://da6yrsnxvsrif" path="res://scenes/wall.tscn" id="4_41vpv"]
|
||||
[ext_resource type="PackedScene" uid="uid://efaec6ymgabo" path="res://stations/Counter.tscn" id="6_orihb"]
|
||||
[ext_resource type="PackedScene" uid="uid://ck5tuftqmyiue" path="res://stations/plate_dispenser.tscn" id="7_pfjwm"]
|
||||
[ext_resource type="PackedScene" uid="uid://dvrk268s7gkxh" path="res://stations/sink.tscn" id="8_sov4h"]
|
||||
[ext_resource type="PackedScene" uid="uid://cwnwo4i28upap" path="res://stations/raw_burger_dispenser.tscn" id="9_7cge2"]
|
||||
[ext_resource type="PackedScene" uid="uid://c6rift56ql3f8" path="res://stations/BurgerBunsDispenser.tscn" id="10_7e78q"]
|
||||
|
||||
[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_vlqg6"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_24d3s"]
|
||||
albedo_color = Color(0.35, 0.12250001, 0.12250001, 1)
|
||||
|
||||
[sub_resource type="PlaneMesh" id="PlaneMesh_vlqg6"]
|
||||
material = SubResource("StandardMaterial3D_24d3s")
|
||||
size = Vector2(6, 10)
|
||||
center_offset = Vector3(3, 0, 0)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_yqrpd"]
|
||||
albedo_color = Color(0.35, 0.35, 0.35, 1)
|
||||
|
||||
[sub_resource type="PlaneMesh" id="PlaneMesh_kk3mq"]
|
||||
material = SubResource("StandardMaterial3D_yqrpd")
|
||||
size = Vector2(6, 10)
|
||||
center_offset = Vector3(-3, 0, 0)
|
||||
|
||||
[node name="Resturant" type="Node3D" unique_id=1083608050]
|
||||
|
||||
[node name="StaticBody3D" type="StaticBody3D" parent="." unique_id=1814285538]
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D" unique_id=1661443874]
|
||||
shape = SubResource("WorldBoundaryShape3D_vlqg6")
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=888732490]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 0, 0, 0)
|
||||
mesh = SubResource("PlaneMesh_vlqg6")
|
||||
|
||||
[node name="MeshInstance3D2" type="MeshInstance3D" parent="." unique_id=836421800]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 0, 0, 0)
|
||||
mesh = SubResource("PlaneMesh_kk3mq")
|
||||
|
||||
[node name="Door" parent="." unique_id=964400290 instance=ExtResource("1_hgrxd")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.4, 0, 0)
|
||||
|
||||
[node name="Walls" type="Node3D" parent="." unique_id=1804204696]
|
||||
|
||||
[node name="Wall" parent="Walls" unique_id=434181749 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.6000001, 0, 0)
|
||||
|
||||
[node name="Wall2" parent="Walls" unique_id=1183111925 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4.2, 0, 0)
|
||||
|
||||
[node name="Wall3" parent="Walls" unique_id=1561473949 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4.8, 0, 0)
|
||||
|
||||
[node name="Wall4" parent="Walls" unique_id=1593528497 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.60000014, 0, 0)
|
||||
|
||||
[node name="Wall5" parent="Walls" unique_id=2071060763 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.3841858e-07, 0, 0)
|
||||
|
||||
[node name="Wall6" parent="Walls" unique_id=1607012472 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5999999, 0, 0)
|
||||
|
||||
[node name="Wall7" parent="Walls" unique_id=990512055 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.8, 0, 0)
|
||||
|
||||
[node name="Wall8" parent="Walls" unique_id=751907897 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.200001, 0, 0)
|
||||
|
||||
[node name="Wall9" parent="Walls" unique_id=358743133 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.6000004, 0, 0)
|
||||
|
||||
[node name="Wall10" parent="Walls" unique_id=1374842161 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.2000002, 0, 0)
|
||||
|
||||
[node name="Wall11" parent="Walls" unique_id=1408132826 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.8000002, 0, 0)
|
||||
|
||||
[node name="Wall12" parent="Walls" unique_id=1937209307 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.60000014, 0, -6)
|
||||
|
||||
[node name="Wall13" parent="Walls" unique_id=1714975985 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.3841858e-07, 0, -6)
|
||||
|
||||
[node name="Wall14" parent="Walls" unique_id=1732121903 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5999999, 0, -6)
|
||||
|
||||
[node name="Wall15" parent="Walls" unique_id=1311494308 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.2000002, 0, -6)
|
||||
|
||||
[node name="Wall16" parent="Walls" unique_id=364484178 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.8000002, 0, -6)
|
||||
|
||||
[node name="Wall17" parent="Walls" unique_id=485501605 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.3999999, 0, -6)
|
||||
|
||||
[node name="Wall18" parent="Walls" unique_id=378206350 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.9999998, 0, -6)
|
||||
|
||||
[node name="Wall19" parent="Walls" unique_id=294380188 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.6, 0, -6)
|
||||
|
||||
[node name="Wall20" parent="Walls" unique_id=1122365254 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.7999998, 0, -6)
|
||||
|
||||
[node name="Wall21" parent="Walls" unique_id=111446334 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.1999998, 0, -6)
|
||||
|
||||
[node name="Wall22" parent="Walls" unique_id=999769595 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.6000001, 0, -6)
|
||||
|
||||
[node name="Wall23" parent="Walls" unique_id=1832835858 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.0000002, 0, -6)
|
||||
|
||||
[node name="Wall24" parent="Walls" unique_id=927052367 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.4, 0, -6)
|
||||
|
||||
[node name="Wall25" parent="Walls" unique_id=1163278593 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.2000003, 0, -6)
|
||||
|
||||
[node name="Wall26" parent="Walls" unique_id=1074868471 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.8, 0, -6)
|
||||
|
||||
[node name="Wall27" parent="Walls" unique_id=510219954 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4.2, 0, -6)
|
||||
|
||||
[node name="Wall28" parent="Walls" unique_id=1615161517 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4.8, 0, -6)
|
||||
|
||||
[node name="Wall29" parent="Walls" unique_id=11605666 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.60000014, 0, 6)
|
||||
|
||||
[node name="Wall30" parent="Walls" unique_id=2032513926 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.3841858e-07, 0, 6)
|
||||
|
||||
[node name="Wall31" parent="Walls" unique_id=1308803807 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5999999, 0, 6)
|
||||
|
||||
[node name="Wall32" parent="Walls" unique_id=493869823 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.2000002, 0, 6)
|
||||
|
||||
[node name="Wall33" parent="Walls" unique_id=699535234 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.8000002, 0, 6)
|
||||
|
||||
[node name="Wall34" parent="Walls" unique_id=238973326 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.3999999, 0, 6)
|
||||
|
||||
[node name="Wall35" parent="Walls" unique_id=836717647 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.9999998, 0, 6)
|
||||
|
||||
[node name="Wall36" parent="Walls" unique_id=446988148 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.6, 0, 6)
|
||||
|
||||
[node name="Wall37" parent="Walls" unique_id=49950412 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.7999998, 0, 6)
|
||||
|
||||
[node name="Wall38" parent="Walls" unique_id=607255208 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.1999998, 0, 6)
|
||||
|
||||
[node name="Wall39" parent="Walls" unique_id=1089866573 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.6000001, 0, 6)
|
||||
|
||||
[node name="Wall40" parent="Walls" unique_id=2078240231 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.0000002, 0, 6)
|
||||
|
||||
[node name="Wall41" parent="Walls" unique_id=592669722 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.4, 0, 6)
|
||||
|
||||
[node name="Wall42" parent="Walls" unique_id=1393176200 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.2000003, 0, 6)
|
||||
|
||||
[node name="Wall43" parent="Walls" unique_id=1885346301 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.8, 0, 6)
|
||||
|
||||
[node name="Wall44" parent="Walls" unique_id=2021916640 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4.2, 0, 6)
|
||||
|
||||
[node name="Wall45" parent="Walls" unique_id=1289086279 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4.8, 0, 6)
|
||||
|
||||
[node name="Wall46" parent="Walls" unique_id=396503450 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8, 0, 1.2000003)
|
||||
|
||||
[node name="Wall47" parent="Walls" unique_id=300373285 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8, 0, 0.6000004)
|
||||
|
||||
[node name="Wall49" parent="Walls" unique_id=1072273787 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8, 0, 1.8000002)
|
||||
|
||||
[node name="Wall50" parent="Walls" unique_id=896022741 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8, 0, 2.4000006)
|
||||
|
||||
[node name="Wall51" parent="Walls" unique_id=2089599721 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8000007, 0, -1.7999992)
|
||||
|
||||
[node name="Wall52" parent="Walls" unique_id=965479737 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8000007, 0, -2.3999996)
|
||||
|
||||
[node name="Wall53" parent="Walls" unique_id=43762332 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8000007, 0, -3)
|
||||
|
||||
[node name="Wall54" parent="Walls" unique_id=921007094 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8000007, 0, -1.1999998)
|
||||
|
||||
[node name="Wall55" parent="Walls" unique_id=2144411980 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8, 0, -0.5999994)
|
||||
|
||||
[node name="Wall56" parent="Walls" unique_id=370330637 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8, 0, 4.2000003)
|
||||
|
||||
[node name="Wall57" parent="Walls" unique_id=537562206 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8, 0, 3.6000004)
|
||||
|
||||
[node name="Wall58" parent="Walls" unique_id=1918288432 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8, 0, 3.0000002)
|
||||
|
||||
[node name="Wall59" parent="Walls" unique_id=944759951 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8, 0, 4.8000007)
|
||||
|
||||
[node name="Wall60" parent="Walls" unique_id=1128237563 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8, 0, 5.4000006)
|
||||
|
||||
[node name="Wall61" parent="Walls" unique_id=1755969864 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8000007, 0, -3.5999994)
|
||||
|
||||
[node name="Wall62" parent="Walls" unique_id=949006312 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8000007, 0, -4.2)
|
||||
|
||||
[node name="Wall63" parent="Walls" unique_id=1826688195 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8000007, 0, -4.799999)
|
||||
|
||||
[node name="Wall64" parent="Walls" unique_id=116523059 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -4.8000007, 0, -5.3999996)
|
||||
|
||||
[node name="Wall65" parent="Walls" unique_id=711153775 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.8, 0, 1.2000003)
|
||||
|
||||
[node name="Wall66" parent="Walls" unique_id=741047 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.8, 0, 0.6000004)
|
||||
|
||||
[node name="Wall67" parent="Walls" unique_id=664067327 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.8, 0, 1.8000002)
|
||||
|
||||
[node name="Wall68" parent="Walls" unique_id=1294537251 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.8, 0, 2.4000006)
|
||||
|
||||
[node name="Wall69" parent="Walls" unique_id=1152383 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.7999997, 0, -1.7999992)
|
||||
|
||||
[node name="Wall70" parent="Walls" unique_id=1710172322 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.7999997, 0, -2.3999996)
|
||||
|
||||
[node name="Wall71" parent="Walls" unique_id=597518110 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.7999997, 0, -3)
|
||||
|
||||
[node name="Wall72" parent="Walls" unique_id=1028573693 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.7999997, 0, -1.1999998)
|
||||
|
||||
[node name="Wall73" parent="Walls" unique_id=1232042046 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.8, 0, -0.5999994)
|
||||
|
||||
[node name="Wall74" parent="Walls" unique_id=1159091046 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.8, 0, 4.2000003)
|
||||
|
||||
[node name="Wall75" parent="Walls" unique_id=904470924 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.8, 0, 3.6000004)
|
||||
|
||||
[node name="Wall76" parent="Walls" unique_id=1709758099 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.8, 0, 3.0000002)
|
||||
|
||||
[node name="Wall77" parent="Walls" unique_id=1252446997 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.8, 0, 4.8000007)
|
||||
|
||||
[node name="Wall78" parent="Walls" unique_id=1207579491 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.8, 0, 5.4000006)
|
||||
|
||||
[node name="Wall79" parent="Walls" unique_id=1798073342 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.7999997, 0, -3.5999994)
|
||||
|
||||
[node name="Wall80" parent="Walls" unique_id=36005990 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.7999997, 0, -4.2)
|
||||
|
||||
[node name="Wall81" parent="Walls" unique_id=79516947 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.7999997, 0, -4.799999)
|
||||
|
||||
[node name="Wall82" parent="Walls" unique_id=1363235805 instance=ExtResource("4_41vpv")]
|
||||
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 4.7999997, 0, -5.3999996)
|
||||
|
||||
[node name="Hatches" type="Node3D" parent="." unique_id=1561045769]
|
||||
|
||||
[node name="ServingCounter" parent="Hatches" unique_id=1341321603 instance=ExtResource("3_d63j6")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.2, 0, 0)
|
||||
|
||||
[node name="ServingCounter2" parent="Hatches" unique_id=1325232371 instance=ExtResource("3_d63j6")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.8000001, 0, 0)
|
||||
|
||||
[node name="ServingCounter3" parent="Hatches" unique_id=539914523 instance=ExtResource("3_d63j6")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.2, 0, 0)
|
||||
|
||||
[node name="ServingCounter4" parent="Hatches" unique_id=2078770951 instance=ExtResource("3_d63j6")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.4, 0, 0)
|
||||
|
||||
[node name="ServingCounter5" parent="Hatches" unique_id=2019049083 instance=ExtResource("3_d63j6")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3, 0, 0)
|
||||
|
||||
[node name="Table" parent="." unique_id=1863572470 instance=ExtResource("2_d63j6")]
|
||||
transform = Transform3D(-1, 0, 8.742278e-08, 0, 1, 0, -8.742278e-08, 0, -1, 3, 0.48000002, -3)
|
||||
|
||||
[node name="Table2" parent="." unique_id=1457688277 instance=ExtResource("2_d63j6")]
|
||||
transform = Transform3D(1, 0, -1.7484555e-07, 0, 1, 0, 1.7484555e-07, 0, 1, 0.5999999, 0.48000002, -3)
|
||||
|
||||
[node name="Hob" parent="." unique_id=172418174 instance=ExtResource("1_p8umn")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 4.2000003, 0.54, 1.7999992)
|
||||
|
||||
[node name="Hob2" parent="." unique_id=980569400 instance=ExtResource("1_p8umn")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 4.200001, 0.54, 1.1999999)
|
||||
|
||||
[node name="Counter" parent="." unique_id=1487893288 instance=ExtResource("6_orihb")]
|
||||
transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 4.200001, 0.54, 2.3999996)
|
||||
|
||||
[node name="Counter2" parent="." unique_id=5128912 instance=ExtResource("6_orihb")]
|
||||
transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 4.200001, 0.54, 3)
|
||||
|
||||
[node name="Counter3" parent="." unique_id=1298458612 instance=ExtResource("6_orihb")]
|
||||
transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 4.2000003, 0.54, 0.5999999)
|
||||
|
||||
[node name="Counter4" parent="." unique_id=1764980142 instance=ExtResource("6_orihb")]
|
||||
transform = Transform3D(1.3113416e-07, 0, 1, 0, 1, 0, -1, 0, 1.3113416e-07, 4.7683716e-07, 0.54, 2.3999996)
|
||||
|
||||
[node name="Counter5" parent="." unique_id=1015820252 instance=ExtResource("6_orihb")]
|
||||
transform = Transform3D(1.3113416e-07, 0, 1, 0, 1, 0, -1, 0, 1.3113416e-07, 4.7683716e-07, 0.54, 3)
|
||||
|
||||
[node name="PlateDispenser" parent="." unique_id=710538846 instance=ExtResource("7_pfjwm")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.6)
|
||||
|
||||
[node name="Sink" parent="." unique_id=2055277359 instance=ExtResource("8_sov4h")]
|
||||
transform = Transform3D(-1, 0, 8.742278e-08, 0, 1, 0, -8.742278e-08, 0, -1, 0.6, 0.48000002, -0.6)
|
||||
|
||||
[node name="Counter6" parent="." unique_id=2141915043 instance=ExtResource("6_orihb")]
|
||||
transform = Transform3D(-1, 0, 8.742277e-08, 0, 1, 0, -8.742277e-08, 0, -1, 4.7683716e-07, 0.54, -0.6000006)
|
||||
|
||||
[node name="RawBurgerDispenser" parent="." unique_id=235630131 instance=ExtResource("9_7cge2")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1.2)
|
||||
|
||||
[node name="BurgerBunsDispenser" parent="." unique_id=1720683779 instance=ExtResource("10_7e78q")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1.8000001)
|
||||
@@ -1,16 +0,0 @@
|
||||
[gd_scene format=3 uid="uid://damrxtlt7uswf"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://j7caslh27nor" path="res://stations/Hob.tscn" id="1_1wuvq"]
|
||||
[ext_resource type="PackedScene" uid="uid://efaec6ymgabo" path="res://stations/Counter.tscn" id="2_3qwcq"]
|
||||
[ext_resource type="PackedScene" uid="uid://dvrk268s7gkxh" path="res://stations/sink.tscn" id="3_3qwcq"]
|
||||
|
||||
[node name="SmallLine" type="Node3D" unique_id=1844128818]
|
||||
|
||||
[node name="Hob" parent="." unique_id=1687971542 instance=ExtResource("1_1wuvq")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.42000002, 0)
|
||||
|
||||
[node name="Counter" parent="." unique_id=1487893288 instance=ExtResource("2_3qwcq")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.6, 0.42000002, 0)
|
||||
|
||||
[node name="Sink" parent="." unique_id=2055277359 instance=ExtResource("3_3qwcq")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.6, 0.42000002, 0)
|
||||
@@ -0,0 +1,129 @@
|
||||
# High-level replication rebuild
|
||||
|
||||
Rebuild VRyHungry's multiplayer on Godot's stock `MultiplayerSpawner` and
|
||||
`MultiplayerSynchronizer`, so that adding a new object to the game requires no
|
||||
networking code at all. Replaces the bespoke spawn payloads, per-scene
|
||||
replication configs, and grab-authority handshake in place today.
|
||||
|
||||
Baseline being replaced: `logs/mptest_report.txt`, 146/146 checks passing as of
|
||||
2026-07-28 19:47. That is the bar the rebuild has to clear.
|
||||
|
||||
## The constraint that shapes everything
|
||||
|
||||
`MultiplayerSpawner` replicates **node creation, name, and deletion** — nothing
|
||||
more. All state comes from `MultiplayerSynchronizer` plus a
|
||||
`SceneReplicationConfig`. There is no engine switch for "sync everything
|
||||
regardless of what it is".
|
||||
|
||||
The way to get there anyway: a `SceneReplicationConfig` is a plain `Resource`,
|
||||
so it can be **built in code from a convention** and attached at runtime. One
|
||||
helper then covers every object in the game, and nothing has to be authored
|
||||
per scene.
|
||||
|
||||
## Verified engine behaviour
|
||||
|
||||
Established by `test/spike/`, run against Godot 4.7.stable. These are load
|
||||
bearing — the design below is only correct because these hold.
|
||||
|
||||
| # | Question | Result |
|
||||
|---|----------|--------|
|
||||
| Q1 | Does default auto-spawn carry the spawn properties of a synchronizer attached at **runtime**, not baked into the `.tscn`? | **Yes.** A peer joining 4s after the spawn received `pos=(5,6,7) health=42 label=hello`. |
|
||||
| Q2 | Do `ON_CHANGE` properties reach a late joiner, and do later changes propagate? | **Yes**, both. |
|
||||
| Q3 | Can `set_visibility_for(peer, false)` suppress one peer's updates without despawning the node there? | **No.** The node is despawned and respawned on every visibility flip. Unusable — it would make a held object vanish from the holder's hand. |
|
||||
| Q4 | Can handing one of two synchronizers to a client let it drive the transform locally while server-owned state keeps arriving? | **Yes.** Client showed its own `(1,1,1)` and ignored the server's competing `(-9,-9,-9)`; the server saw `(1,1,1)`; server-owned `health=123` still arrived. |
|
||||
|
||||
Two traps worth recording. `set_visibility_for` is only an *override* on top of
|
||||
`public_visibility`, which defaults to `true` — calling it alone does nothing.
|
||||
And the spike's first version chained relative timers, which drifted enough that
|
||||
the client's observations straddled the server's actions; it now runs both peers
|
||||
on one absolute clock.
|
||||
|
||||
## Design
|
||||
|
||||
### Layer 1 — `Net/net_replication.gd`
|
||||
|
||||
The convention-based config builder. `NetReplication.attach(node)` gives a node
|
||||
two generated synchronizers:
|
||||
|
||||
- **`NetSync`** — script state. Always server-owned; gameplay outcomes are the
|
||||
server's to decide. `ON_CHANGE`.
|
||||
- **`NetXform`** — `position` + `quaternion`. Server-owned while the object is
|
||||
loose, handed to a peer for the duration of a hold. `ALWAYS`.
|
||||
|
||||
What gets replicated, by convention:
|
||||
|
||||
- a `Node3D` replicates `position` and `quaternion` (never `scale` — nothing in
|
||||
this game animates scale as gameplay state)
|
||||
- every script variable, on the node and any scripted descendant, whose name
|
||||
does not start with `_` and whose declared type is serialisable
|
||||
|
||||
Underscore-prefixed vars are the opt-out, and they already mark exactly the
|
||||
state that must not cross the wire: `@onready` references, cached lookups,
|
||||
per-frame bookkeeping.
|
||||
|
||||
`ON_CHANGE` for state is a deliberate correctness choice, not just bandwidth.
|
||||
Under `ALWAYS` the synchronizer *assigns* every property every tick on receiving
|
||||
peers, so every replicated setter becomes a per-frame hot path — which is how
|
||||
this project previously ended up rebuilding every plate's visuals 60 times a
|
||||
second and rewriting four physics properties per item per tick.
|
||||
|
||||
### Layer 2 — `Net/net_world.gd`
|
||||
|
||||
- Spawnable scenes registered by **sorted** directory scan of `Items/`,
|
||||
`Containers/`, `Stations/`, `Prefabs/`. Auto-spawn transmits an *index* into
|
||||
that array, so the order must be identical on every peer or clients build the
|
||||
wrong scene.
|
||||
- `spawn()` = instantiate → set transform/name → `add_child` under the content
|
||||
root. No `spawn_function`, no payload dictionary.
|
||||
- `despawn()` = `queue_free()` on the server. The `_despawn_static_item` RPC
|
||||
disappears: with nothing baked into the world scene, the spawner covers every
|
||||
case.
|
||||
- `NetReplication.attach` runs from the content root's `child_entered_tree` on
|
||||
**both** peers. That is early enough for the synchronizers to enter the tree
|
||||
inside the spawner's own `add_child`, which is what lets them pick up the
|
||||
spawn payload (Q1).
|
||||
- **Client gating becomes one generic rule** applied at the same hook: disable
|
||||
`_process`, disable every `XRToolsSnapZone`, freeze `RigidBody3D`. This
|
||||
replaces `_gate_station` + `gate_existing_stations` and applies to anything,
|
||||
not just things that happen to be stations.
|
||||
|
||||
### Layer 3 — `Net/net_grab.gd` and `Net/net_stations.gd`
|
||||
|
||||
Two RPCs for the entire game: `request_grab(item)` and
|
||||
`request_release(item, xform, lin, ang)`.
|
||||
|
||||
A client's hand picks the item up locally the instant the player grabs — no
|
||||
round trip — and asks the server to confirm. The server grants by moving
|
||||
`NetXform` authority to that peer (Q4), which is precisely what makes the local
|
||||
copy stop applying inbound transform updates. On release, authority returns to
|
||||
the server, which decides where the item actually ends up: server-authoritative
|
||||
snapping into stations lives in `net_stations.gd`, generic over the `station`
|
||||
group rather than per-station.
|
||||
|
||||
Deleted outright: `Net/net_pickable.gd` (209 lines), `net_held_by` and its
|
||||
held-state juggling, `_set_item_authority`, the grant/reject/force-release
|
||||
negotiation, and `_gate_station`.
|
||||
|
||||
### Layer 4 — tests
|
||||
|
||||
`test/mp_test_driver.gd` (1458 lines) splits into orchestration, steps,
|
||||
asserts, snapshot, and report modules. The scenario list and the sync audit
|
||||
carry over unchanged — that audit compares what is actually *rendered* on both
|
||||
peers, which is what caught the plate-visuals desync that targeted assertions
|
||||
missed. Assertions written against old internals (`net_held_by`, "the client is
|
||||
the item's authority after grabbing") are rewritten against the new model.
|
||||
|
||||
## Risks
|
||||
|
||||
- **Replacing a green suite.** Mitigated by running the headless suite after
|
||||
each layer rather than only at the end.
|
||||
- **The harness changes alongside the code it checks.** Mitigated by keeping
|
||||
scenario list and audit logic byte-identical wherever possible, so it remains
|
||||
an independent check rather than one shaped to fit the new code.
|
||||
- **Blanket state replication picks up more vars than the old hand-authored
|
||||
configs.** `ON_CHANGE` makes this cheap, but any setter reached this way still
|
||||
has to be idempotent and null-guarded — a `spawn = true` property fires its
|
||||
setter *before* the node is in the tree, so `@onready` refs are null there.
|
||||
- **Client→client transform** relies on `SceneMultiplayer.server_relay` (on by
|
||||
default) to forward a holder's updates to the other clients. Fine for a
|
||||
listen-server, worth remembering if the topology ever changes.
|
||||
@@ -1,10 +0,0 @@
|
||||
class_name Helper
|
||||
|
||||
# Find a FoodItem among the direct children of [param node].
|
||||
static func find_food_item(node: Node) -> FoodItem:
|
||||
if not node:
|
||||
return null
|
||||
for child in node.get_children():
|
||||
if child is FoodItem:
|
||||
return child
|
||||
return null
|
||||
@@ -1 +0,0 @@
|
||||
uid://cx1wi21lhnimb
|
||||
@@ -4,18 +4,3 @@ function_pickup.gd:
|
||||
Add nil check in func _pick_up_object(target: Node3D)
|
||||
440: if target and target.has_method("pick_up"):
|
||||
target.pick_up(self)
|
||||
|
||||
|
||||
Add (what) to singal dropped in snap_zone
|
||||
signal has_dropped(what)
|
||||
# Pickup Method: Drop the currently picked up object
|
||||
...
|
||||
func drop_object() -> void:
|
||||
if not is_instance_valid(picked_up_object):
|
||||
return
|
||||
|
||||
# let go of this object
|
||||
has_dropped.emit(picked_up_object)
|
||||
picked_up_object.let_go(self, Vector3.ZERO, Vector3.ZERO)
|
||||
picked_up_object = null
|
||||
highlight_updated.emit(self, enabled)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
extends Node3D
|
||||
|
||||
const RECIPE_MANAGER = preload("res://RecipeManager.gd")
|
||||
const GAME_MANAGER = preload("res://GameManager.gd")
|
||||
|
||||
var xr_interface :XRInterface
|
||||
|
||||
func _ready():
|
||||
RECIPE_MANAGER.print_all_recipes()
|
||||
|
||||
GAME_MANAGER.meals_in_play = ["hamburger"]
|
||||
|
||||
xr_interface = XRServer.find_interface("OpenXR")
|
||||
if xr_interface and xr_interface.is_initialized():
|
||||
print("OpenXR started :)")
|
||||
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
|
||||
get_viewport().use_xr = true
|
||||
else:
|
||||
print("OpenXR failed to start. Check your headset...")
|
||||
@@ -0,0 +1 @@
|
||||
uid://b4ldkd3ngs0vp
|
||||
@@ -0,0 +1,96 @@
|
||||
[gd_scene format=3 uid="uid://clw8ai6kngqxb"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b4ldkd3ngs0vp" path="res://main.gd" id="1_0xm2m"]
|
||||
[ext_resource type="PackedScene" uid="uid://j5s5wus7tuhb" path="res://XROrigin.tscn" id="2_h2yge"]
|
||||
[ext_resource type="Material" uid="uid://cmia50cfqxxo4" path="res://Textures/dev_material_3d.tres" id="3_272bh"]
|
||||
[ext_resource type="PackedScene" uid="uid://e4i6o5oriecx" path="res://Items/PickupCube.tscn" id="3_h2yge"]
|
||||
[ext_resource type="PackedScene" uid="uid://j7caslh27nor" path="res://Stations/Hob.tscn" id="3_lquwl"]
|
||||
[ext_resource type="PackedScene" uid="uid://efaec6ymgabo" path="res://Stations/Counter.tscn" id="4_7mycd"]
|
||||
[ext_resource type="Sky" uid="uid://c1abtpwhdm2d5" path="res://Textures/sky/NightSkyHDRI009_16K.tres" id="6_7mycd"]
|
||||
[ext_resource type="PackedScene" uid="uid://dpot5qie20vf6" path="res://Items/burger.tscn" id="8_5vw27"]
|
||||
[ext_resource type="PackedScene" uid="uid://bp3v1jl8pctro" path="res://Containers/plate.tscn" id="9_kek77"]
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_vlqg6"]
|
||||
size = Vector3(5, 0.1, 5)
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_24d3s"]
|
||||
material = ExtResource("3_272bh")
|
||||
size = Vector3(5, 0.1, 5)
|
||||
|
||||
[sub_resource type="Environment" id="Environment_bvwq1"]
|
||||
background_mode = 2
|
||||
sky = ExtResource("6_7mycd")
|
||||
reflected_light_source = 2
|
||||
tonemap_mode = 3
|
||||
ssr_enabled = true
|
||||
ssao_enabled = true
|
||||
ssil_enabled = true
|
||||
sdfgi_enabled = true
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_kek77"]
|
||||
size = Vector3(5.1, 10, 0.1)
|
||||
|
||||
[node name="Main" type="Node3D" unique_id=1312265607]
|
||||
script = ExtResource("1_0xm2m")
|
||||
|
||||
[node name="XROrigin3D" parent="." unique_id=2055526621 instance=ExtResource("2_h2yge")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.8820953, 0)
|
||||
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1376644384]
|
||||
transform = Transform3D(-0.8660254, -0.43301278, 0.24999999, 0.3022632, -0.05510214, 0.9516306, -0.39829266, 0.8997021, 0.17860365, 0, 0, 0)
|
||||
|
||||
[node name="Floor" type="StaticBody3D" parent="." unique_id=122279514]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.05, 0)
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="Floor" unique_id=958841648]
|
||||
shape = SubResource("BoxShape3D_vlqg6")
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="Floor/CollisionShape3D" unique_id=1939997056]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00018164515, 0.0006352663, -0.002532661)
|
||||
mesh = SubResource("BoxMesh_24d3s")
|
||||
|
||||
[node name="Hob" parent="." unique_id=1687971542 instance=ExtResource("3_lquwl")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.29336345, 0.8981018, -1.484)
|
||||
|
||||
[node name="Counter" parent="." unique_id=1487893288 instance=ExtResource("4_7mycd")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.73186195, 0.8981018, -1.484)
|
||||
|
||||
[node name="PickableObject" parent="." unique_id=1675596942 instance=ExtResource("3_h2yge")]
|
||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, -0.70505303, 1.5761534, -1.0317149)
|
||||
|
||||
[node name="PickableObject2" parent="." unique_id=713087634 instance=ExtResource("3_h2yge")]
|
||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, 0.28315204, 1.5607953, -1.0657519)
|
||||
|
||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=1177077250]
|
||||
environment = SubResource("Environment_bvwq1")
|
||||
|
||||
[node name="OmniLight3D" type="OmniLight3D" parent="." unique_id=812168256]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.2856425, 1.4939771, -1.4959787)
|
||||
visible = false
|
||||
light_color = Color(1, 0.38431373, 0, 1)
|
||||
light_energy = 1.784
|
||||
shadow_enabled = true
|
||||
|
||||
[node name="burger" parent="." unique_id=1554047278 instance=ExtResource("8_5vw27")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.482068, -1.2252241)
|
||||
|
||||
[node name="Plate" parent="." unique_id=190487773 instance=ExtResource("9_kek77")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.22971058, 1.699144, -1.8499806)
|
||||
|
||||
[node name="StaticBody3D" type="StaticBody3D" parent="." unique_id=82945083]
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D" unique_id=1909896160]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 4.688614, 2.5070028)
|
||||
shape = SubResource("BoxShape3D_kek77")
|
||||
|
||||
[node name="CollisionShape3D2" type="CollisionShape3D" parent="StaticBody3D" unique_id=39538720]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 4.688614, -2.5446289)
|
||||
shape = SubResource("BoxShape3D_kek77")
|
||||
|
||||
[node name="CollisionShape3D3" type="CollisionShape3D" parent="StaticBody3D" unique_id=1984048740]
|
||||
transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, -2.525816, 4.688614, -0.018813243)
|
||||
shape = SubResource("BoxShape3D_kek77")
|
||||
|
||||
[node name="CollisionShape3D4" type="CollisionShape3D" parent="StaticBody3D" unique_id=1484088721]
|
||||
transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 2.525816, 4.688614, -0.018813023)
|
||||
shape = SubResource("BoxShape3D_kek77")
|
||||
+1
-1
@@ -20,6 +20,7 @@ config/icon="res://icon.svg"
|
||||
XRToolsUserSettings="*uid://bqgb8i74tm0t"
|
||||
XRToolsRumbleManager="*uid://by853dk86g1qw"
|
||||
NetworkManager="*res://Net/network_manager.gd"
|
||||
NetGrab="*res://Net/net_grab.gd"
|
||||
GlobalKeyEvents="*uid://c60unagog5oi1"
|
||||
Signals="*uid://fuux6cxfjwdc"
|
||||
|
||||
@@ -46,7 +47,6 @@ import/blender/enabled=false
|
||||
|
||||
platalbe_item="Meals, sides, augments for meals"
|
||||
station="Hobs, Counters ..."
|
||||
table="all nodes with table script"
|
||||
|
||||
[input]
|
||||
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
extends Node
|
||||
class_name MpAsserts
|
||||
|
||||
## The per-step checks. Every one is synchronous and side-effect free: it looks
|
||||
## at the world as it currently is on THIS peer and returns a verdict.
|
||||
##
|
||||
## Most of these are run on both peers for the same step, which is the point —
|
||||
## the server's authoritative outcome has to be what the client sees too.
|
||||
|
||||
var view: MpWorldView
|
||||
var snapshot: MpSnapshot
|
||||
var hand: XRToolsFunctionPickup
|
||||
|
||||
|
||||
func setup(p_view: MpWorldView, p_snapshot: MpSnapshot, p_hand: XRToolsFunctionPickup) -> void:
|
||||
view = p_view
|
||||
snapshot = p_snapshot
|
||||
hand = p_hand
|
||||
|
||||
|
||||
## On the server "snapped" means the zone owns the object. On a client the snap
|
||||
## is server-authoritative and never happens locally, so what must be true there
|
||||
## is that the replicated object actually sits in the zone.
|
||||
func snapped(item_name: String, station: String) -> Dictionary:
|
||||
var item := view.find(item_name)
|
||||
var zone := view.zone_of(station)
|
||||
if not item:
|
||||
return {"ok": false, "detail": "'%s' does not exist on this peer" % item_name}
|
||||
if not zone:
|
||||
return {"ok": false, "detail": "station '%s' has no snap zone on this peer" % station}
|
||||
var dist := item.global_position.distance_to(zone.global_position)
|
||||
var problems: Array[String] = []
|
||||
if dist > MpSteps.SNAP_TOLERANCE:
|
||||
problems.append("%s is %.3fm from %s's zone (tolerance %.2f)"
|
||||
% [item_name, dist, station, MpSteps.SNAP_TOLERANCE])
|
||||
if NetworkManager.owns_world():
|
||||
if zone.picked_up_object != item:
|
||||
problems.append("%s's zone holds %s, not %s" % [station, zone.picked_up_object, item_name])
|
||||
elif not item.is_picked_up():
|
||||
problems.append("%s's zone claims %s but it has no grab driver (half-snapped)" % [station, item_name])
|
||||
if problems.is_empty():
|
||||
return {"ok": true, "detail": "%s is snapped into %s; %s" % [item_name, station, view.diag(item, hand)]}
|
||||
return {"ok": false, "detail": "%s | %s" % [", ".join(problems), view.diag(item, hand)]}
|
||||
|
||||
|
||||
func dirty(plate_name: String, want_dirty: bool) -> Dictionary:
|
||||
var plate := view.find(plate_name)
|
||||
if not plate:
|
||||
return {"ok": false, "detail": "'%s' does not exist on this peer" % plate_name}
|
||||
var pc := plate.get_node_or_null("PlateController")
|
||||
if not pc:
|
||||
return {"ok": false, "detail": "'%s' has no PlateController" % plate_name}
|
||||
if pc.is_dirty != want_dirty:
|
||||
return {"ok": false, "detail": "%s.is_dirty is %s, expected %s; %s"
|
||||
% [plate_name, pc.is_dirty, want_dirty, view.diag(plate, hand)]}
|
||||
return {"ok": true, "detail": "%s.is_dirty == %s as expected" % [plate_name, want_dirty]}
|
||||
|
||||
|
||||
## A consumed object must be gone on EVERY peer, not just the one that consumed it.
|
||||
func gone(item_name: String) -> Dictionary:
|
||||
var item := view.find(item_name)
|
||||
if item and is_instance_valid(item):
|
||||
return {"ok": false, "detail": "'%s' still exists on this peer at %s (it should have been consumed)"
|
||||
% [item_name, item.global_position]}
|
||||
return {"ok": true, "detail": "'%s' is gone, as expected" % item_name}
|
||||
|
||||
|
||||
func food_exists(food_id: String) -> Dictionary:
|
||||
var item := view.find_by_food_id(food_id)
|
||||
if not item:
|
||||
return {"ok": false, "detail": "no item with food id '%s' exists on this peer" % food_id}
|
||||
return {"ok": true, "detail": "'%s' exists: %s at %s" % [food_id, item.name, item.global_position]}
|
||||
|
||||
|
||||
func plate_contains(plate_name: String, food_id: String) -> Dictionary:
|
||||
var plate := view.find(plate_name)
|
||||
if not plate:
|
||||
return {"ok": false, "detail": "'%s' does not exist on this peer" % plate_name}
|
||||
var pc := plate.get_node_or_null("PlateController")
|
||||
if not pc:
|
||||
return {"ok": false, "detail": "'%s' has no PlateController" % plate_name}
|
||||
if not (food_id in pc.contained_ids):
|
||||
return {"ok": false, "detail": "%s holds %s, expected it to contain '%s'"
|
||||
% [plate_name, str(pc.contained_ids), food_id]}
|
||||
return {"ok": true, "detail": "%s contains %s" % [plate_name, str(pc.contained_ids)]}
|
||||
|
||||
|
||||
## Food shown on a plate is a cosmetic child of it, so it has to stay put when
|
||||
## the plate is picked up and carried. If it drifts, the player sees the burger
|
||||
## fly off the plate.
|
||||
func plate_visuals(plate_name: String) -> Dictionary:
|
||||
var plate := view.find(plate_name)
|
||||
if not plate:
|
||||
return {"ok": false, "detail": "'%s' does not exist on this peer" % plate_name}
|
||||
var off := snapshot.max_visual_offset(plate)
|
||||
if off < 0.0:
|
||||
return {"ok": false, "detail": "%s is not showing any food to check" % plate_name}
|
||||
if off > MpSnapshot.MAX_VISUAL_OFFSET:
|
||||
return {"ok": false, "detail": "%s's food has come off the plate: %.3fm away (limit %.2f). %s"
|
||||
% [plate_name, off, MpSnapshot.MAX_VISUAL_OFFSET, snapshot.visual_diag(plate)]}
|
||||
return {"ok": true, "detail": "%s's food is still on it (%.3fm from centre). %s"
|
||||
% [plate_name, off, snapshot.visual_diag(plate)]}
|
||||
|
||||
|
||||
## A station's progress bar must show the same thing to everyone: visible while
|
||||
## the station is working, gone once it has finished. Only the world owner runs
|
||||
## station logic, so a client can only get this right if the display is driven
|
||||
## from replicated state.
|
||||
func station_bar(station_name: String, want_visible: bool) -> Dictionary:
|
||||
var station := view.find(station_name)
|
||||
if not station:
|
||||
return {"ok": false, "detail": "'%s' does not exist on this peer" % station_name}
|
||||
var bar := station.get_node_or_null("ProgressBar3D") as ProgressBar3D
|
||||
if not bar:
|
||||
return {"ok": false, "detail": "%s has no ProgressBar3D" % station_name}
|
||||
var shown := bar.is_bar_visible()
|
||||
var detail := "%s bar visible=%s progress=%.0f%%" % [station_name, shown, bar.get_progress()]
|
||||
if "cooking_result" in station:
|
||||
detail += " cooking='%s'" % station.cooking_result
|
||||
if "is_washing" in station:
|
||||
detail += " washing=%s" % station.is_washing
|
||||
if shown != want_visible:
|
||||
return {"ok": false, "detail": "expected %s's bar to be %s, but %s"
|
||||
% [station_name, "visible" if want_visible else "hidden", detail]}
|
||||
return {"ok": true, "detail": detail}
|
||||
|
||||
|
||||
## A station that has had its object taken away must not still be holding it.
|
||||
func zone_empty(station_name: String) -> Dictionary:
|
||||
var zone := view.zone_of(station_name)
|
||||
if not zone:
|
||||
return {"ok": false, "detail": "station '%s' has no snap zone on this peer" % station_name}
|
||||
if not NetworkManager.owns_world():
|
||||
# Client zones are gated off entirely; they never hold anything.
|
||||
return {"ok": true, "detail": "%s: client zones are gated, nothing to check" % station_name}
|
||||
if is_instance_valid(zone.picked_up_object):
|
||||
return {"ok": false, "detail": "%s's zone still holds %s after it was taken away"
|
||||
% [station_name, zone.picked_up_object]}
|
||||
return {"ok": true, "detail": "%s's zone is empty" % station_name}
|
||||
@@ -0,0 +1 @@
|
||||
uid://dnhqfkoir57q6
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user