Refactor container to work in muliplayer
This commit is contained in:
+150
-150
@@ -11,11 +11,16 @@ extends Node3D
|
|||||||
|
|
||||||
@onready var _meal_container: Node3D = $MealContainer
|
@onready var _meal_container: Node3D = $MealContainer
|
||||||
@onready var _side_container: Node3D = $SidesContainer
|
@onready var _side_container: Node3D = $SidesContainer
|
||||||
|
|
||||||
var contained_items: Array[FoodItem] # Expose nice list of others to read
|
var contained_items: Array[FoodItem] # Expose nice list of others to read
|
||||||
|
|
||||||
|
# Item roots the server has promised a slot to but whose absorb RPC hasn't run
|
||||||
|
# yet. body_entered fires again for a body already inside the area whenever its
|
||||||
|
# collision_mask is rewritten — which apply_held_state() does the moment item
|
||||||
|
# authority moves back to the server, i.e. exactly when a client drops food on a
|
||||||
|
# plate. Both calls would otherwise see the same free slot and absorb twice.
|
||||||
|
# Keyed on the item root / pickable.
|
||||||
|
var _pending_absorb: Array[Node3D] = []
|
||||||
|
|
||||||
# Called when the node enters the scene tree for the first time.
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
area_3d.body_entered.connect(_on_body_entered)
|
area_3d.body_entered.connect(_on_body_entered)
|
||||||
if not area_3d:
|
if not area_3d:
|
||||||
@@ -29,15 +34,14 @@ func _ready() -> void:
|
|||||||
func _on_body_entered(body: Node3D) -> void:
|
func _on_body_entered(body: Node3D) -> void:
|
||||||
if not NetworkManager.owns_world():
|
if not NetworkManager.owns_world():
|
||||||
return
|
return
|
||||||
SweetLogger.debug("Enabled: {0}", [enabled])
|
|
||||||
if not enabled:
|
if not enabled:
|
||||||
SweetLogger.debug("Disabled in _on_body_entered body")
|
SweetLogger.debug("Disabled in _on_body_entered body")
|
||||||
return
|
return
|
||||||
if not body.is_in_group(target_group):
|
if not body.is_in_group(target_group):
|
||||||
|
SweetLogger.debug("{0} is not in group {1}", [body.name, target_group])
|
||||||
return
|
return
|
||||||
SweetLogger.debug("Body: {0}", [body])
|
SweetLogger.debug("Body: {0}", [body])
|
||||||
|
|
||||||
|
|
||||||
# If one of us is in a station
|
# If one of us is in a station
|
||||||
var picked_by = xr_pickable.get_picked_up_by()
|
var picked_by = xr_pickable.get_picked_up_by()
|
||||||
var body_pickable = body as XRToolsPickable
|
var body_pickable = body as XRToolsPickable
|
||||||
@@ -45,179 +49,175 @@ func _on_body_entered(body: Node3D) -> void:
|
|||||||
if (picked_by and picked_by.is_in_group("station_zone")) or (body_picked_by and body_picked_by.is_in_group("station_zone")):
|
if (picked_by and picked_by.is_in_group("station_zone")) or (body_picked_by and body_picked_by.is_in_group("station_zone")):
|
||||||
|
|
||||||
# If enough space, add item
|
# If enough space, add item
|
||||||
var food_item = body.get_node("FoodItem")
|
var food_item = Helper.find_food_item(body) as FoodItem
|
||||||
SweetLogger.debug("In station found {0} of type {1}: {2}", [target_group, FoodItem.Type.keys()[food_item.type], body.name], "container.gd", "_on_body_entered")
|
SweetLogger.debug("In station found {0} of type {1}: {2}", [target_group, FoodItem.Type.keys()[food_item.type], body.name], "container.gd", "_on_body_entered")
|
||||||
var meal_count := contained_items.filter(func(f): return f.type == FoodItem.Type.MEAL).size()
|
SweetLogger.debug("{0} pending={1} contained={2}", [get_path(), _pending_absorb.size(), contained_items.size()])
|
||||||
var side_count := contained_items.filter(func(f): return f.type == FoodItem.Type.SIDE).size()
|
if body in _pending_absorb or _is_contained(body):
|
||||||
if food_item.type == FoodItem.Type.MEAL and meal_count < meal_positions.size():
|
SweetLogger.debug("{0} is already in {1}", [body.name, get_parent().name])
|
||||||
SweetLogger.debug("Adding meal")
|
return
|
||||||
_add_item(body)
|
if food_item.type == FoodItem.Type.MEAL and _free_slots(FoodItem.Type.MEAL) > 0:
|
||||||
if food_item.type == FoodItem.Type.SIDE and side_count < side_positions.size():
|
SweetLogger.info("{0}, Adding meal", [name])
|
||||||
SweetLogger.debug("Adding side")
|
_add_item(body, food_item)
|
||||||
_add_item(body)
|
if food_item.type == FoodItem.Type.SIDE and _free_slots(FoodItem.Type.SIDE) > 0:
|
||||||
|
SweetLogger.info("{0}, Adding side", [name])
|
||||||
|
_add_item(body, food_item)
|
||||||
|
|
||||||
|
|
||||||
|
# Already sitting in one of this container's slots.
|
||||||
|
func _is_contained(item: Node3D) -> bool:
|
||||||
|
return item.get_parent() == _meal_container or item.get_parent() == _side_container
|
||||||
|
|
||||||
# Contents are synced as data (ids on the plate's PlateController), not
|
|
||||||
# reparented nodes: reparenting a MultiplayerSpawner-tracked item out of
|
|
||||||
# WorldContent would despawn it on every client the instant it happened.
|
|
||||||
func _add_item(item: Node3D) -> void:
|
|
||||||
var pickable = item as XRToolsPickable
|
|
||||||
if pickable and pickable.is_picked_up():
|
|
||||||
pickable.drop()
|
|
||||||
|
|
||||||
var food_node := item.get_node_or_null("FoodItem") as FoodItem
|
# Room left for [param type], counting slots already promised to items whose
|
||||||
if not food_node:
|
# absorb RPC hasn't landed yet.
|
||||||
return
|
func _free_slots(type: FoodItem.Type) -> int:
|
||||||
|
var is_meal := type == FoodItem.Type.MEAL
|
||||||
|
var used := (_meal_container if is_meal else _side_container).get_child_count()
|
||||||
|
for item in _pending_absorb:
|
||||||
|
var food_item := Helper.find_food_item(item)
|
||||||
|
if food_item and food_item.type == type:
|
||||||
|
used += 1
|
||||||
|
return (meal_positions if is_meal else side_positions).size() - used
|
||||||
|
|
||||||
# Copy the data out before despawning the real item.
|
|
||||||
var data := FoodItem.new()
|
|
||||||
data.id = food_node.id
|
|
||||||
data.type = food_node.type
|
|
||||||
data.sell_value = food_node.sell_value
|
|
||||||
contained_items.append(data)
|
|
||||||
|
|
||||||
var plate_controller := xr_pickable.get_node_or_null("PlateController") as PlateController
|
func _add_item(item: Node3D, food_item: FoodItem) -> void:
|
||||||
if plate_controller:
|
SweetLogger.debug("->[]")
|
||||||
# Reassign rather than append in place. contained_ids has a setter that
|
# Claim the slot now: the RPC below only runs once the current frame's
|
||||||
# rebuilds the plate's visuals, and mutating the array never triggers it —
|
# signal handlers have all had their turn.
|
||||||
# so the peer that actually added the food was the one peer that never
|
_pending_absorb.append(item)
|
||||||
# redrew the plate. Remote peers looked right (the synchronizer assigns
|
everyone_absorb_item.rpc(item.get_path())
|
||||||
# the value there, which does fire the setter), and the stale peer only
|
|
||||||
# caught up if someone else took the plate and sent the value back.
|
|
||||||
# duplicate() keeps the Array[String] typing that the property requires.
|
|
||||||
var updated := plate_controller.contained_ids.duplicate()
|
|
||||||
updated.append(food_node.id)
|
|
||||||
plate_controller.contained_ids = updated
|
|
||||||
|
|
||||||
NetworkManager.despawn_item(item)
|
|
||||||
# In case the container is on a table that needs to register this addition,
|
# In case the container is on a table that needs to register this addition,
|
||||||
# ask all table in scene to absorb any new items.
|
# ask all table in scene to absorb any new items.
|
||||||
SweetLogger.debug("Group call absorb_items()")
|
SweetLogger.debug("Group call absorb_items()")
|
||||||
get_tree().call_group("table", "absorb_items")
|
get_tree().call_group("table", "absorb_items")
|
||||||
|
|
||||||
|
|
||||||
|
## Runs on every peer: the item stops being an independent networked object and
|
||||||
|
## becomes a child of this container, carried by the container's transform.
|
||||||
|
@rpc("authority", "call_local", "reliable")
|
||||||
|
func everyone_absorb_item(item_path: NodePath) -> void:
|
||||||
|
var item := Helper.get_node_from_path(self, item_path) as Node3D
|
||||||
|
if not item:
|
||||||
|
SweetLogger.error("Container cant find item")
|
||||||
|
return
|
||||||
|
var food_item := Helper.find_first_child_of_type(item, FoodItem) as FoodItem
|
||||||
|
if not food_item:
|
||||||
|
SweetLogger.error("Container cant find food item")
|
||||||
|
return
|
||||||
|
# The reservation is now being honoured (or is about to be refused); either way it
|
||||||
|
# stops holding a slot from here on.
|
||||||
|
_pending_absorb.erase(item)
|
||||||
|
if _is_contained(item):
|
||||||
|
SweetLogger.warning("{0} is already in {1}", [item.name, get_parent().name])
|
||||||
|
return
|
||||||
|
|
||||||
|
# Pick the slot
|
||||||
|
var target: Node3D
|
||||||
|
var slot: Node3D
|
||||||
|
if food_item.type == FoodItem.Type.MEAL and _meal_container.get_child_count() < meal_positions.size():
|
||||||
|
target = _meal_container
|
||||||
|
slot = meal_positions[_meal_container.get_child_count()]
|
||||||
|
elif food_item.type == FoodItem.Type.SIDE and _side_container.get_child_count() < side_positions.size():
|
||||||
|
target = _side_container
|
||||||
|
slot = side_positions[_side_container.get_child_count()]
|
||||||
|
else:
|
||||||
|
SweetLogger.error("{0}: no free slot for {1}", [name, item.name])
|
||||||
|
return
|
||||||
|
|
||||||
|
_hand_over_to_container(item)
|
||||||
|
item.reparent(target, false) # keep_global_transform=false: the slot is expressed in container-local space
|
||||||
|
item.position = slot.position
|
||||||
|
item.rotation = slot.rotation
|
||||||
|
# After the reparent, not before: re-entering the tree puts the body back
|
||||||
|
# into the physics space, so it can only be taken out again once it's there.
|
||||||
|
_remove_from_physics(item)
|
||||||
|
|
||||||
|
contained_items.append(food_item)
|
||||||
|
_publish_contained_ids()
|
||||||
|
SweetLogger.info("{0} absorbed {1}, contained_items: {2}", [get_parent().name, item.name, contained_items.size()])
|
||||||
|
|
||||||
|
|
||||||
|
## Server decision, mirrored to every peer.
|
||||||
func erase_item(item: FoodItem) -> void:
|
func erase_item(item: FoodItem) -> void:
|
||||||
contained_items.erase(item)
|
if not NetworkManager.owns_world():
|
||||||
var plate_controller := xr_pickable.get_node_or_null("PlateController") as PlateController
|
return
|
||||||
if plate_controller:
|
everyone_erase_item.rpc(item.get_parent().get_path())
|
||||||
# Same reason as _add_item: assign so the visuals actually refresh.
|
|
||||||
var remaining := plate_controller.contained_ids.duplicate()
|
|
||||||
remaining.erase(item.id)
|
|
||||||
plate_controller.contained_ids = remaining
|
|
||||||
|
|
||||||
|
|
||||||
|
@rpc("authority", "call_local", "reliable")
|
||||||
|
func everyone_erase_item(item_path: NodePath) -> void:
|
||||||
|
var item := Helper.get_node_from_path(self, item_path) as Node3D
|
||||||
|
if not item:
|
||||||
|
return
|
||||||
|
contained_items.erase(Helper.find_first_child_of_type(item, FoodItem))
|
||||||
|
item.queue_free()
|
||||||
|
_publish_contained_ids()
|
||||||
|
|
||||||
|
|
||||||
|
## Server decision, mirrored to every peer.
|
||||||
func clear() -> void:
|
func clear() -> void:
|
||||||
|
if not NetworkManager.owns_world():
|
||||||
|
return
|
||||||
|
everyone_clear.rpc()
|
||||||
|
|
||||||
|
|
||||||
|
@rpc("authority", "call_local", "reliable")
|
||||||
|
func everyone_clear() -> void:
|
||||||
|
for child in _meal_container.get_children() + _side_container.get_children():
|
||||||
|
child.queue_free()
|
||||||
contained_items.clear()
|
contained_items.clear()
|
||||||
var plate_controller := xr_pickable.get_node_or_null("PlateController") as PlateController
|
_publish_contained_ids()
|
||||||
if plate_controller:
|
|
||||||
plate_controller.contained_ids = []
|
|
||||||
|
|
||||||
|
|
||||||
## Rebuilds the purely-cosmetic visual representation of the plate's contents
|
# Hand the item over to the container: from here on the container's parent (e.g.
|
||||||
## from a synced id list. Runs on every peer (called from PlateController
|
# a plate a client is carrying with local authority) is the only thing that
|
||||||
## whenever contained_ids changes, whether set locally or by the network).
|
# decides where the item is, so it must stop behaving as a pickable and stop
|
||||||
func refresh_visuals(ids: Array[String]) -> void:
|
# replicating a transform of its own.
|
||||||
for child in _meal_container.get_children():
|
func _hand_over_to_container(item: Node3D) -> void:
|
||||||
child.queue_free()
|
var pickable := item as XRToolsPickable
|
||||||
for child in _side_container.get_children():
|
if pickable:
|
||||||
child.queue_free()
|
if pickable.is_picked_up():
|
||||||
|
pickable.drop()
|
||||||
var meal_idx := 0
|
pickable.enabled = false
|
||||||
var side_idx := 0
|
var net_pickable := item.get_node_or_null("NetPickable") as NetPickable
|
||||||
for id in ids:
|
|
||||||
var scene := RecipeManager.get_item_scene(id)
|
|
||||||
if not scene:
|
|
||||||
continue
|
|
||||||
var visual := scene.instantiate()
|
|
||||||
var food_node := visual.get_node_or_null("FoodItem") as FoodItem
|
|
||||||
|
|
||||||
var container_root: Node3D
|
|
||||||
var positions: Array[Node3D]
|
|
||||||
var idx: int
|
|
||||||
if food_node and food_node.type == FoodItem.Type.MEAL and meal_idx < meal_positions.size():
|
|
||||||
container_root = _meal_container
|
|
||||||
positions = meal_positions
|
|
||||||
idx = meal_idx
|
|
||||||
meal_idx += 1
|
|
||||||
elif food_node and food_node.type == FoodItem.Type.SIDE and side_idx < side_positions.size():
|
|
||||||
container_root = _side_container
|
|
||||||
positions = side_positions
|
|
||||||
idx = side_idx
|
|
||||||
side_idx += 1
|
|
||||||
else:
|
|
||||||
visual.queue_free()
|
|
||||||
continue
|
|
||||||
|
|
||||||
_make_cosmetic(visual)
|
|
||||||
container_root.add_child(visual)
|
|
||||||
# Must happen after add_child: entering the world is what puts the body
|
|
||||||
# into the physics space, so it can only be taken out again afterwards.
|
|
||||||
_remove_from_physics(visual)
|
|
||||||
visual.position = positions[idx].position
|
|
||||||
visual.rotation = positions[idx].rotation
|
|
||||||
|
|
||||||
|
|
||||||
# Strip interactivity/networking from a display-only copy: it's not spawned
|
|
||||||
# through NetworkManager, so it must never try to sync (its NetPickable child,
|
|
||||||
# if any, would have no corresponding replicated identity on other peers) or
|
|
||||||
# be grabbable/collidable.
|
|
||||||
func _make_cosmetic(visual: Node3D) -> void:
|
|
||||||
var net_pickable := visual.get_node_or_null("NetPickable")
|
|
||||||
if net_pickable:
|
if net_pickable:
|
||||||
# Detach and free it outright rather than queue_free(): this runs before
|
net_pickable.transform_owned = false
|
||||||
# `visual` is added to the tree, and a merely-queued node still enters the
|
else:
|
||||||
# tree with its parent and runs _ready() (which starts syncing and logging)
|
SweetLogger.error("{0}: {1} has no NetPickable", [name, item.name])
|
||||||
# before the queued deletion lands at the end of the frame.
|
var despawning := Helper.find_first_child_of_type(item, DespawningItem) as DespawningItem
|
||||||
visual.remove_child(net_pickable)
|
if despawning:
|
||||||
net_pickable.free()
|
despawning.enabled = false
|
||||||
if visual is RigidBody3D:
|
|
||||||
visual.freeze = true
|
|
||||||
# STATIC, not KINEMATIC: a kinematic body is still driven by the physics
|
|
||||||
# engine (see _remove_from_physics), and "static decoration" is what this
|
|
||||||
# actually is.
|
|
||||||
visual.freeze_mode = RigidBody3D.FREEZE_MODE_STATIC
|
|
||||||
visual.collision_layer = 0
|
|
||||||
visual.collision_mask = 0
|
|
||||||
if visual is XRToolsPickable:
|
|
||||||
visual.enabled = false
|
|
||||||
visual.set_process(false)
|
|
||||||
visual.set_physics_process(false)
|
|
||||||
|
|
||||||
|
|
||||||
# Take a display-only copy out of the physics simulation completely.
|
# Take the item out of the physics simulation entirely so it simply follows its
|
||||||
|
# new parent.
|
||||||
#
|
#
|
||||||
# Freezing is not enough. Under Jolt (this project's physics engine) a frozen
|
# Freezing is not enough. Under Jolt (this project's physics engine) a frozen
|
||||||
# KINEMATIC RigidBody3D is still simulated: it is driven toward its target
|
# KINEMATIC RigidBody3D is still simulated: it is driven toward its target
|
||||||
# transform by velocity rather than being teleported. Parented to a plate that
|
# transform by velocity rather than being teleported. Parented to a plate that
|
||||||
# gets picked up and carried, it therefore lags behind, keeps its velocity, and
|
# gets picked up and carried, it therefore lags behind, keeps its velocity and
|
||||||
# overshoots — so the food visibly slid off the plate and ended up metres away,
|
# overshoots — the food visibly slid off the plate, differently on each peer.
|
||||||
# differently on each peer since each simulates its own copy. A body with no
|
# A body with no space is never touched by the engine.
|
||||||
# space is never touched by the engine, so it simply follows its parent.
|
func _remove_from_physics(item: Node3D) -> void:
|
||||||
func _remove_from_physics(visual: Node3D) -> void:
|
var body := item as RigidBody3D
|
||||||
if visual is RigidBody3D:
|
if not body:
|
||||||
PhysicsServer3D.body_set_space((visual as RigidBody3D).get_rid(), RID())
|
return
|
||||||
var despawning_item = visual.get_node_or_null("DespawningItem")
|
body.freeze_mode = RigidBody3D.FREEZE_MODE_STATIC
|
||||||
if despawning_item:
|
body.freeze = true
|
||||||
NetworkManager.despawn_item(despawning_item)
|
body.collision_layer = 0
|
||||||
|
body.collision_mask = 0
|
||||||
|
PhysicsServer3D.body_set_space(body.get_rid(), RID())
|
||||||
|
|
||||||
|
|
||||||
#plate (Pickalbe)
|
# The contents are real child nodes now; contained_ids is just the synced
|
||||||
#XRGrapPoints
|
# summary that the plate's UI and the multiplayer tests read.
|
||||||
#container (script) (meal positions[1], side positions[4])
|
func _publish_contained_ids() -> void:
|
||||||
#area
|
var plate_controller := xr_pickable.get_node_or_null("PlateController") as PlateController
|
||||||
#meals
|
if not plate_controller:
|
||||||
#meal - burger
|
return
|
||||||
#sides
|
var ids: Array[String] = []
|
||||||
#side - chips
|
for food_item in contained_items:
|
||||||
#side - onion rings
|
ids.append(food_item.id)
|
||||||
|
plate_controller.contained_ids = ids
|
||||||
#tray (Pickalbe)
|
SweetLogger.debug("{0} now contains: {1}", [get_parent().name, ids])
|
||||||
#XRGrapPoints
|
|
||||||
#container (script) (meal positions[4], side positions[0])
|
|
||||||
#food items
|
|
||||||
#meals
|
|
||||||
#meal - cookie
|
|
||||||
#meal - cookie
|
|
||||||
#meal - cookie
|
|
||||||
#meal - cookie
|
|
||||||
|
|||||||
@@ -35,13 +35,13 @@ properties/1/path = NodePath(".:quaternion")
|
|||||||
properties/1/spawn = false
|
properties/1/spawn = false
|
||||||
properties/1/replication_mode = 1
|
properties/1/replication_mode = 1
|
||||||
properties/2/path = NodePath("NetPickable:net_held_by")
|
properties/2/path = NodePath("NetPickable:net_held_by")
|
||||||
properties/2/spawn = true
|
properties/2/spawn = false
|
||||||
properties/2/replication_mode = 1
|
properties/2/replication_mode = 1
|
||||||
properties/3/path = NodePath("PlateController:contained_ids")
|
properties/3/path = NodePath("PlateController:contained_ids")
|
||||||
properties/3/spawn = true
|
properties/3/spawn = false
|
||||||
properties/3/replication_mode = 1
|
properties/3/replication_mode = 2
|
||||||
properties/4/path = NodePath("PlateController:is_dirty")
|
properties/4/path = NodePath("PlateController:is_dirty")
|
||||||
properties/4/spawn = true
|
properties/4/spawn = false
|
||||||
properties/4/replication_mode = 1
|
properties/4/replication_mode = 1
|
||||||
|
|
||||||
[node name="Plate" type="RigidBody3D" unique_id=190487773]
|
[node name="Plate" type="RigidBody3D" unique_id=190487773]
|
||||||
|
|||||||
@@ -6,12 +6,10 @@ extends Node
|
|||||||
|
|
||||||
@export var is_dirty: bool = false
|
@export var is_dirty: bool = false
|
||||||
|
|
||||||
## Synced plate contents (item ids), replacing reparented child nodes so
|
## Synced summary of the plate's contents (item ids). The contents themselves
|
||||||
## contents survive replication (a MultiplayerSpawner-tracked item would
|
## are the real item nodes ItemContainer reparents under the plate; this is only
|
||||||
## despawn on every client the instant it was reparented out of WorldContent).
|
## what other systems read to ask "what is on this plate" without walking them.
|
||||||
## Server writes it via ItemContainer; every peer (server included) renders
|
@export var contained_ids: Array[String] = []
|
||||||
## the cosmetic result via the setter below.
|
|
||||||
@export var contained_ids: Array[String] = []: set = _set_contained_ids
|
|
||||||
|
|
||||||
func get_food_items() -> Array[FoodItem]:
|
func get_food_items() -> Array[FoodItem]:
|
||||||
return container.contained_items
|
return container.contained_items
|
||||||
@@ -29,22 +27,3 @@ func _process(_delta: float) -> void:
|
|||||||
else:
|
else:
|
||||||
container.enabled = true
|
container.enabled = true
|
||||||
dirty_node.visible = false
|
dirty_node.visible = false
|
||||||
|
|
||||||
|
|
||||||
func _set_contained_ids(value: Array[String]) -> void:
|
|
||||||
# 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).
|
|
||||||
if contained_ids == value:
|
|
||||||
return
|
|
||||||
contained_ids = value
|
|
||||||
# Deferred: this can be written by the replicated spawn payload before
|
|
||||||
# this node's own @onready vars (container) have resolved.
|
|
||||||
_refresh_visuals.call_deferred()
|
|
||||||
|
|
||||||
|
|
||||||
func _refresh_visuals() -> void:
|
|
||||||
if container:
|
|
||||||
container.refresh_visuals(contained_ids)
|
|
||||||
|
|||||||
+14
-55
@@ -1,82 +1,52 @@
|
|||||||
[gd_scene format=3 uid="uid://b3m2ag8g5rj4r"]
|
[gd_scene format=3 uid="uid://b3m2ag8g5rj4r"]
|
||||||
|
|
||||||
[ext_resource type="PackedScene" uid="uid://c8l60rnugru40" path="res://addons/godot-xr-tools/objects/pickable.tscn" id="1_3gf3l"]
|
[ext_resource type="PackedScene" uid="uid://d8jo0402pgl6" path="res://abstract/food_item.tscn" id="1_food_item"]
|
||||||
[ext_resource type="PackedScene" uid="uid://c25yxb0vt53vc" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_left.tscn" id="2_0mkco"]
|
|
||||||
[ext_resource type="Animation" uid="uid://db62hs5s4n2b3" path="res://addons/godot-xr-tools/hands/animations/left/Grip 4.res" id="3_6tnvi"]
|
|
||||||
[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"]
|
|
||||||
|
|
||||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_fco8w"]
|
[sub_resource type="CylinderShape3D" id="CylinderShape3D_fco8w"]
|
||||||
height = 0.10392761
|
height = 0.10392761
|
||||||
radius = 0.096191406
|
radius = 0.096191406
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_lc22d"]
|
|
||||||
script = ExtResource("4_cljtj")
|
|
||||||
closed_pose = ExtResource("3_6tnvi")
|
|
||||||
metadata/_custom_type_script = "uid://dvobm6vcfnqe8"
|
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_qyiot"]
|
|
||||||
script = ExtResource("4_cljtj")
|
|
||||||
closed_pose = ExtResource("6_fh0f6")
|
|
||||||
metadata/_custom_type_script = "uid://dvobm6vcfnqe8"
|
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_3gf3l"]
|
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_3gf3l"]
|
||||||
albedo_color = Color(0.6784191, 0.54546416, 0.016760282, 1)
|
albedo_color = Color(0.6784191, 0.54546416, 0.016760282, 1)
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_6l01i"]
|
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_6l01i"]
|
||||||
albedo_color = Color(0.29, 0.101500005, 0, 1)
|
albedo_color = Color(0.29, 0.101500005, 0, 1)
|
||||||
|
|
||||||
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_np_hamburger"]
|
[node name="Hamburger" unique_id=779551583 groups=["platalbe_item"] instance=ExtResource("1_food_item")]
|
||||||
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
|
gravity_scale = 0.04
|
||||||
second_hand_grab = 1
|
|
||||||
|
|
||||||
[node name="CollisionShape3D" parent="." index="0"]
|
[node name="CollisionShape3D" parent="." index="0"]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.02, 0.02, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.02, 0.02, 0)
|
||||||
shape = SubResource("CylinderShape3D_fco8w")
|
shape = SubResource("CylinderShape3D_fco8w")
|
||||||
|
|
||||||
[node name="GrabPointHandLeft" parent="." index="1" unique_id=1571481674 instance=ExtResource("2_0mkco")]
|
[node name="FoodItem" parent="." index="5" unique_id=63948206]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.067650706, 0.04601878, -0.08606844)
|
id = "hamburger"
|
||||||
hand_pose = SubResource("Resource_lc22d")
|
type = 0
|
||||||
|
sell_value = 4
|
||||||
|
|
||||||
[node name="GrabPointHandRight" parent="." index="2" unique_id=514404634 instance=ExtResource("5_dunns")]
|
[node name="CombinableItem" parent="." index="7" unique_id=996936271]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.06531982, 0.042291984, -0.08604182)
|
enabled = false
|
||||||
hand_pose = SubResource("Resource_qyiot")
|
|
||||||
|
|
||||||
[node name="CSGCombiner3DBuns" type="CSGCombiner3D" parent="." index="3" unique_id=678881053]
|
[node name="CSGCombiner3DBuns" type="CSGCombiner3D" parent="Model" parent_id_path=PackedInt32Array(1015960306) index="0" unique_id=678881053]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.021904588, 0.025076538, -0.0044593215)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.021904588, 0.025076538, -0.0044593215)
|
||||||
autosmooth = true
|
autosmooth = true
|
||||||
smoothing_angle = 138.9
|
smoothing_angle = 138.9
|
||||||
|
|
||||||
[node name="CSGCylinder3D2" type="CSGCylinder3D" parent="CSGCombiner3DBuns" index="0" unique_id=776920274]
|
[node name="CSGCylinder3D2" type="CSGCylinder3D" parent="Model/CSGCombiner3DBuns" index="0" unique_id=776920274]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.022625268, -0.03479519, 0.0027926378)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.022625268, -0.03479519, 0.0027926378)
|
||||||
radius = 0.085
|
radius = 0.085
|
||||||
height = 0.04
|
height = 0.04
|
||||||
sides = 16
|
sides = 16
|
||||||
material = SubResource("StandardMaterial3D_3gf3l")
|
material = SubResource("StandardMaterial3D_3gf3l")
|
||||||
|
|
||||||
[node name="CSGCylinder3D3" type="CSGCylinder3D" parent="CSGCombiner3DBuns" index="1" unique_id=448728548]
|
[node name="CSGCylinder3D3" type="CSGCylinder3D" parent="Model/CSGCombiner3DBuns" index="1" unique_id=448728548]
|
||||||
transform = Transform3D(0.82770383, -0.5611652, 0, 0.5611652, 0.82770383, 0, 0, 0, 1, -0.07384333, 0.015895892, 0)
|
transform = Transform3D(0.82770383, -0.5611652, 0, 0.5611652, 0.82770383, 0, 0, 0, 1, -0.07384333, 0.015895892, 0)
|
||||||
radius = 0.085
|
radius = 0.085
|
||||||
height = 0.04
|
height = 0.04
|
||||||
sides = 16
|
sides = 16
|
||||||
material = SubResource("StandardMaterial3D_3gf3l")
|
material = SubResource("StandardMaterial3D_3gf3l")
|
||||||
|
|
||||||
[node name="CSGCylinder3D4" type="CSGCylinder3D" parent="CSGCombiner3DBuns/CSGCylinder3D3" index="0" unique_id=837229235]
|
[node name="CSGCylinder3D4" type="CSGCylinder3D" parent="Model/CSGCombiner3DBuns/CSGCylinder3D3" index="0" unique_id=837229235]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.9604645e-08, 0.040259957, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.9604645e-08, 0.040259957, 0)
|
||||||
radius = 0.085
|
radius = 0.085
|
||||||
height = 0.04
|
height = 0.04
|
||||||
@@ -84,20 +54,9 @@ sides = 16
|
|||||||
cone = true
|
cone = true
|
||||||
material = SubResource("StandardMaterial3D_3gf3l")
|
material = SubResource("StandardMaterial3D_3gf3l")
|
||||||
|
|
||||||
[node name="CSGCylinder3D" type="CSGCylinder3D" parent="." index="4" unique_id=59319982]
|
[node name="CSGCylinder3D" type="CSGCylinder3D" parent="Model/CSGCombiner3DBuns" index="2" unique_id=59319982]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.02356495, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.021904588, -0.0015115887, 0.0044593215)
|
||||||
radius = 0.08300781
|
radius = 0.08300781
|
||||||
height = 0.03
|
height = 0.03
|
||||||
sides = 16
|
sides = 16
|
||||||
material = SubResource("StandardMaterial3D_6l01i")
|
material = SubResource("StandardMaterial3D_6l01i")
|
||||||
|
|
||||||
[node name="FoodItem" parent="." index="5" unique_id=63948206 instance=ExtResource("7_yy3y8")]
|
|
||||||
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")]
|
|
||||||
|
|||||||
+58
-45
@@ -1,3 +1,4 @@
|
|||||||
|
class_name NetPickable
|
||||||
extends MultiplayerSynchronizer
|
extends MultiplayerSynchronizer
|
||||||
|
|
||||||
## Networked sync component for a pickable item. Added as a child literally
|
## Networked sync component for a pickable item. Added as a child literally
|
||||||
@@ -8,33 +9,37 @@ extends MultiplayerSynchronizer
|
|||||||
## every other peer freezes their local copy and just follows the synced
|
## every other peer freezes their local copy and just follows the synced
|
||||||
## transform.
|
## transform.
|
||||||
|
|
||||||
## 0 = loose/server-simulated; otherwise the peer id currently holding it.
|
## Nobody is holding the item, so it is loose and simulated by the server. Not
|
||||||
|
## the same as "the server holds it" — that would be the server's peer id.
|
||||||
|
const NOT_HELD := 0
|
||||||
|
|
||||||
|
## NOT_HELD, or the peer id currently holding it.
|
||||||
## Replicated at spawn and on change so a late joiner sees the current holder.
|
## 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 net_held_by: int = NOT_HELD: set = _set_net_held_by
|
||||||
|
|
||||||
|
## Transform properties, kept apart from the rest of the replicated set because
|
||||||
|
## set_transform_owned() drops them while something else drives the item.
|
||||||
|
const TRANSFORM_PROPERTIES: Array[String] = [".:position", ".:quaternion"]
|
||||||
|
|
||||||
|
## False while something else drives this item's transform — e.g. it has been
|
||||||
|
## reparented into a container and is carried by its new parent. See set_transform_owned().
|
||||||
|
var transform_owned: bool = true: set = set_transform_owned
|
||||||
|
|
||||||
var _pickable: XRToolsPickable
|
var _pickable: XRToolsPickable
|
||||||
|
|
||||||
# This item's own baked freeze_mode (e.g. plate.tscn bakes KINEMATIC, not the
|
# 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
|
# RigidBody3D default of STATIC) — captured once so it can be restored.
|
||||||
# this peer regains ownership, instead of getting stuck on whatever
|
|
||||||
# apply_held_state() last forced it to while non-authority.
|
|
||||||
var _original_freeze_mode: RigidBody3D.FreezeMode
|
var _original_freeze_mode: RigidBody3D.FreezeMode
|
||||||
|
|
||||||
# Same idea for the pickable's authored `enabled` flag, which the non-authority
|
# 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.
|
# branch of apply_held_state() clears while someone else is holding the item.
|
||||||
var _original_enabled: bool
|
var _original_enabled: bool
|
||||||
|
|
||||||
# Intent set by external game logic (e.g. a station enabling/disabling a tool),
|
## Intent set by external game logic (e.g. a station enabling/disabling a tool),
|
||||||
# independent of hold state. apply_held_state() is the sole writer of the
|
## independent of hold state. apply_held_state() is the sole writer of the
|
||||||
# pickable's actual `enabled` property, so this is how other systems express
|
## pickable's actual `enabled` property, so this is how other systems express
|
||||||
# "should be enabled" without fighting that reconciliation every tick.
|
## "should be enabled" without fighting that reconciliation every tick.
|
||||||
var _tool_enabled: bool = true
|
var grabbable: bool = true: set = set_grabbable
|
||||||
|
|
||||||
# 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:
|
func _ready() -> void:
|
||||||
_pickable = get_parent() as XRToolsPickable
|
_pickable = get_parent() as XRToolsPickable
|
||||||
@@ -45,11 +50,8 @@ func _ready() -> void:
|
|||||||
# Configure Multiplayer Synchronizer
|
# Configure Multiplayer Synchronizer
|
||||||
# This overwrites any changes made in the inspector.
|
# This overwrites any changes made in the inspector.
|
||||||
replication_config = SceneReplicationConfig.new()
|
replication_config = SceneReplicationConfig.new()
|
||||||
var properties: Array[NodePath] = [".:position", ".:quaternion", ".:enabled", ".:visible"]
|
for property_path in TRANSFORM_PROPERTIES + [".:enabled", ".:visible"]:
|
||||||
for property_path in properties:
|
_add_always_property(property_path)
|
||||||
replication_config.add_property(property_path)
|
|
||||||
replication_config.property_set_replication_mode(property_path, SceneReplicationConfig.REPLICATION_MODE_ALWAYS)
|
|
||||||
replication_config.property_set_spawn(property_path, false)
|
|
||||||
# Unlike the properties above, this one is replicated at spawn too, so a
|
# Unlike the properties above, this one is replicated at spawn too, so a
|
||||||
# late joiner sees the current holder immediately.
|
# late joiner sees the current holder immediately.
|
||||||
var held_by_path := NodePath("NetPickable:net_held_by")
|
var held_by_path := NodePath("NetPickable:net_held_by")
|
||||||
@@ -69,14 +71,38 @@ func _ready() -> void:
|
|||||||
apply_held_state.call_deferred()
|
apply_held_state.call_deferred()
|
||||||
|
|
||||||
|
|
||||||
## Called by external game logic (e.g. Counter._enable_tool/_disable_tool) to
|
func _add_always_property(property_path: String) -> void:
|
||||||
|
replication_config.add_property(property_path)
|
||||||
|
replication_config.property_set_replication_mode(property_path, SceneReplicationConfig.REPLICATION_MODE_ALWAYS)
|
||||||
|
replication_config.property_set_spawn(property_path, false)
|
||||||
|
|
||||||
|
|
||||||
|
## Set false by whatever takes over driving this item's transform (e.g. an
|
||||||
|
## ItemContainer reparenting it onto a plate). Disables apply_held_state()
|
||||||
|
## Without this net_held_by tick and would keep restoring `enabled`, `freeze` and the
|
||||||
|
## collision mask.
|
||||||
|
func set_transform_owned(value: bool) -> void:
|
||||||
|
if transform_owned == value:
|
||||||
|
return
|
||||||
|
transform_owned = value
|
||||||
|
for property_path in TRANSFORM_PROPERTIES:
|
||||||
|
if value:
|
||||||
|
_add_always_property(property_path)
|
||||||
|
else:
|
||||||
|
replication_config.remove_property(property_path)
|
||||||
|
# Ours again: re-derive the physics state we stopped maintaining.
|
||||||
|
if value:
|
||||||
|
apply_held_state()
|
||||||
|
|
||||||
|
|
||||||
|
## Set by external game logic (e.g. Counter._enable_tool/_disable_tool) to
|
||||||
## express whether this item should be usable right now, independent of hold
|
## express whether this item should be usable right now, independent of hold
|
||||||
## state. Reapplies immediately so the change takes effect without waiting for
|
## state. Reapplies immediately so the change takes effect without waiting for
|
||||||
## the next net_held_by tick.
|
## the next net_held_by tick.
|
||||||
func set_tool_enabled(value: bool) -> void:
|
func set_grabbable(value: bool) -> void:
|
||||||
if _tool_enabled == value:
|
if grabbable == value:
|
||||||
return
|
return
|
||||||
_tool_enabled = value
|
grabbable = value
|
||||||
apply_held_state()
|
apply_held_state()
|
||||||
|
|
||||||
|
|
||||||
@@ -91,19 +117,12 @@ func _set_net_held_by(value: int) -> void:
|
|||||||
## Puts the item in the right physics state for whether this peer currently
|
## 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
|
## owns it. Called locally after net_held_by changes, and directly by
|
||||||
## NetworkManager._set_item_authority right after an authority handoff.
|
## 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:
|
func apply_held_state() -> void:
|
||||||
if not _pickable:
|
if not _pickable:
|
||||||
return
|
return
|
||||||
|
if not transform_owned: # We don't own it.
|
||||||
|
return
|
||||||
if not NetworkManager.is_online() or (is_inside_tree() and is_multiplayer_authority()) :
|
if not NetworkManager.is_online() or (is_inside_tree() and is_multiplayer_authority()) :
|
||||||
_grab_race_logged = false
|
|
||||||
# We own this item's simulation (offline, loose+server, or currently
|
# 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
|
# 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
|
# sure it isn't still left frozen/collision-less from a previous
|
||||||
@@ -128,7 +147,7 @@ func apply_held_state() -> void:
|
|||||||
# e.g. a plate still got marked dirty) while pick_up() bailed out on
|
# 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
|
# the disabled item, leaving the zone holding an item with no grab
|
||||||
# driver that then fell out of the station.
|
# driver that then fell out of the station.
|
||||||
var want_enabled_authority := _original_enabled and _tool_enabled
|
var want_enabled_authority := _original_enabled and grabbable
|
||||||
if _pickable.enabled != want_enabled_authority:
|
if _pickable.enabled != want_enabled_authority:
|
||||||
if NetworkManager.is_online():
|
if NetworkManager.is_online():
|
||||||
SweetLogger.debug("{0}: reclaiming ownership, restoring enabled {1}->{2}", [_pickable.name, _pickable.enabled, want_enabled_authority])
|
SweetLogger.debug("{0}: reclaiming ownership, restoring enabled {1}->{2}", [_pickable.name, _pickable.enabled, want_enabled_authority])
|
||||||
@@ -141,12 +160,7 @@ func apply_held_state() -> void:
|
|||||||
# hand mid-grab — only an explicit force_release_item rejection, or
|
# hand mid-grab — only an explicit force_release_item rejection, or
|
||||||
# actually losing authority for real, should end a grab we initiated.
|
# actually losing authority for real, should end a grab we initiated.
|
||||||
if _pickable.is_picked_up() and _pickable.get_picked_up_by() is XRToolsFunctionPickup:
|
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
|
|
||||||
SweetLogger.debug("{0}: ignoring non-authority sync (net_held_by={1}) — still actively held by our own hand (grab-race guard)", [_pickable.name, net_held_by])
|
|
||||||
return
|
return
|
||||||
_grab_race_logged = false
|
|
||||||
# Someone else owns it: stop simulating locally, just follow the sync.
|
# Someone else owns it: stop simulating locally, just follow the sync.
|
||||||
if _pickable.is_picked_up():
|
if _pickable.is_picked_up():
|
||||||
if NetworkManager.is_online():
|
if NetworkManager.is_online():
|
||||||
@@ -159,14 +173,14 @@ func apply_held_state() -> void:
|
|||||||
# physics-property writes per item per tick. The comparison also means we
|
# 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()
|
# still re-apply if something else perturbs the state (e.g. let_go()
|
||||||
# restoring the collision mask after a force-drop).
|
# restoring the collision mask after a force-drop).
|
||||||
var want_enabled := (net_held_by == 0) and _tool_enabled
|
var want_enabled := (net_held_by == NOT_HELD) and grabbable
|
||||||
if _pickable.freeze \
|
if _pickable.freeze \
|
||||||
and _pickable.freeze_mode == RigidBody3D.FREEZE_MODE_KINEMATIC \
|
and _pickable.freeze_mode == RigidBody3D.FREEZE_MODE_KINEMATIC \
|
||||||
and _pickable.collision_mask == 0 \
|
and _pickable.collision_mask == 0 \
|
||||||
and _pickable.enabled == want_enabled:
|
and _pickable.enabled == want_enabled:
|
||||||
return
|
return
|
||||||
# if NetworkManager.is_online(): # This was spamming that Knife was frozen every frame.
|
if NetworkManager.is_online(): # This was spamming that Knife was frozen every frame.
|
||||||
# print("%s: freezing (non-authority, owner=peer %d)" % [_pickable.name, net_held_by])
|
SweetLogger.debug("{0}: freezing (non-authority, owner=peer {1})", [_pickable.name, net_held_by])
|
||||||
_pickable.freeze = true
|
_pickable.freeze = true
|
||||||
_pickable.freeze_mode = RigidBody3D.FREEZE_MODE_KINEMATIC
|
_pickable.freeze_mode = RigidBody3D.FREEZE_MODE_KINEMATIC
|
||||||
_pickable.collision_mask = 0
|
_pickable.collision_mask = 0
|
||||||
@@ -177,8 +191,7 @@ func apply_held_state() -> void:
|
|||||||
## authority immediately so the throw/drop can be reconciled, but let the grab
|
## authority immediately so the throw/drop can be reconciled, but let the grab
|
||||||
## happen instantly here rather than waiting on the round trip.
|
## happen instantly here rather than waiting on the round trip.
|
||||||
func _on_picked_up(_p) -> void:
|
func _on_picked_up(_p) -> void:
|
||||||
var by := _pickable.get_picked_up_by()
|
if not (_pickable.get_picked_up_by() is XRToolsFunctionPickup):
|
||||||
if not (by is XRToolsFunctionPickup):
|
|
||||||
# e.g. a station snap zone grabbed it (server-side auto-snap, or the
|
# 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
|
# addon's own "grab out of a snap zone" shortcut mid-cascade) — not a
|
||||||
# player-initiated hand grab, so no authority request from here.
|
# player-initiated hand grab, so no authority request from here.
|
||||||
|
|||||||
+13
-2
@@ -10,6 +10,8 @@ extends Node
|
|||||||
|
|
||||||
const DEFAULT_PORT := 24565
|
const DEFAULT_PORT := 24565
|
||||||
const MAX_CLIENTS := 7
|
const MAX_CLIENTS := 7
|
||||||
|
## Godot's fixed peer id for the server.
|
||||||
|
const SERVER_PEER_ID := 1
|
||||||
|
|
||||||
## Emitted on every peer (including the server for its own local player) when a
|
## 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
|
## player peer joins. On the server this fires for each remote peer; the server
|
||||||
@@ -194,7 +196,7 @@ func _grant_or_reject_item_authority(item_path: NodePath, sender: int) -> void:
|
|||||||
var item := get_node_or_null(item_path)
|
var item := get_node_or_null(item_path)
|
||||||
if item:
|
if item:
|
||||||
var np := item.get_node_or_null("NetPickable")
|
var np := item.get_node_or_null("NetPickable")
|
||||||
if np and np.net_held_by != 0 and np.net_held_by != sender:
|
if np and np.net_held_by != NetPickable.NOT_HELD and np.net_held_by != sender:
|
||||||
# Already legitimately held by a different live peer: reject the
|
# Already legitimately held by a different live peer: reject the
|
||||||
# requester's optimistic client-side grab instead of stealing it.
|
# 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])
|
log_line("request_item_authority: DENIED %s to peer %d (already held by %d)" % [item.name, sender, np.net_held_by])
|
||||||
@@ -320,9 +322,18 @@ func _set_item_authority(item_path: NodePath, peer: int) -> void:
|
|||||||
return
|
return
|
||||||
log_line("_set_item_authority: %s -> peer %d" % [item.name, peer])
|
log_line("_set_item_authority: %s -> peer %d" % [item.name, peer])
|
||||||
item.set_multiplayer_authority(peer) # recursive: item + synchronizer + NetPickable
|
item.set_multiplayer_authority(peer) # recursive: item + synchronizer + NetPickable
|
||||||
|
# ...except an ItemContainer under it. Deciding what a plate absorbs stays a
|
||||||
|
# server call, broadcast with an "authority" RPC — and Godot only lets a
|
||||||
|
# node's current authority send those. Letting the recursion above hand the
|
||||||
|
# container to whichever client is holding the plate made the client reject
|
||||||
|
# the server's own everyone_absorb_item, so the food was absorbed on the
|
||||||
|
# server only and stayed loose (and grabbable, at its old path) on the client.
|
||||||
|
var container := Helper.find_first_child_of_type(item, ItemContainer)
|
||||||
|
if container:
|
||||||
|
container.set_multiplayer_authority(SERVER_PEER_ID)
|
||||||
var np := item.get_node_or_null("NetPickable")
|
var np := item.get_node_or_null("NetPickable")
|
||||||
if np:
|
if np:
|
||||||
np.net_held_by = 0 if peer == 1 else peer
|
np.net_held_by = NetPickable.NOT_HELD if peer == SERVER_PEER_ID else peer
|
||||||
np.apply_held_state()
|
np.apply_held_state()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ extends Node
|
|||||||
# Define the Enum at the top of your script
|
# Define the Enum at the top of your script
|
||||||
enum Type { MEAL, SIDE, INGREDIENT, NONE }
|
enum Type { MEAL, SIDE, INGREDIENT, NONE }
|
||||||
|
|
||||||
|
@export var enabled: bool = true
|
||||||
|
|
||||||
@export var id: String
|
@export var id: String
|
||||||
@export var type: Type = Type.NONE
|
@export var type: Type = Type.NONE
|
||||||
@export var sell_value = 1
|
@export var sell_value = 1
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
[ext_resource type="Script" uid="uid://d1cefhe3yyyds" path="res://Net/multiplayer_world.gd" id="1_nlnup"]
|
[ext_resource type="Script" uid="uid://d1cefhe3yyyds" path="res://Net/multiplayer_world.gd" id="1_nlnup"]
|
||||||
[ext_resource type="PackedScene" uid="uid://j5s5wus7tuhb" path="res://prefabs/XROrigin.tscn" id="2_8pfaf"]
|
[ext_resource type="PackedScene" uid="uid://j5s5wus7tuhb" path="res://prefabs/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="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_old.tscn" id="4_3lufs"]
|
[ext_resource type="PackedScene" uid="uid://b052o1fgwq5bw" 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="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://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://dpot5qie20vf6" path="res://items/burger.tscn" id="9_rwx83"]
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
[ext_resource type="PackedScene" uid="uid://dlw5geheupgpt" path="res://stations/hatch.tscn" id="12_dpf3b"]
|
[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://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://caf0xanmxbshy" path="res://stations/table.tscn" id="13_yjosk"]
|
||||||
[ext_resource type="PackedScene" uid="uid://j7caslh27nor" path="res://stations/Hob_old.tscn" id="14_2ndvi"]
|
[ext_resource type="PackedScene" uid="uid://b052o1fgwq5bw" path="res://stations/Hob.tscn" id="14_2ndvi"]
|
||||||
[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://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://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://xtcei2uvd5li" path="res://UI/game_over_panel.tscn" id="15_eoxxy"]
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
[ext_resource type="PackedScene" uid="uid://bnwb7imcotkod" path="res://prefabs/build_mode_controller.tscn" id="10_464r1"]
|
[ext_resource type="PackedScene" uid="uid://bnwb7imcotkod" path="res://prefabs/build_mode_controller.tscn" id="10_464r1"]
|
||||||
[ext_resource type="PackedScene" uid="uid://3i1xb74cfsh5" path="res://test/vr_spectator_camera.tscn" id="11_5wx0o"]
|
[ext_resource type="PackedScene" uid="uid://3i1xb74cfsh5" path="res://test/vr_spectator_camera.tscn" id="11_5wx0o"]
|
||||||
[ext_resource type="PackedScene" uid="uid://dxe05wp60jg3l" path="res://scenes/queue_controller.tscn" id="11_de1dy"]
|
[ext_resource type="PackedScene" uid="uid://dxe05wp60jg3l" path="res://scenes/queue_controller.tscn" id="11_de1dy"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://b052o1fgwq5bw" path="res://stations/Hob.tscn" id="12_hob"]
|
||||||
[ext_resource type="Script" uid="uid://biu4qr3nr1eny" path="res://test/mp_test_driver.gd" id="99_mptst"]
|
[ext_resource type="Script" uid="uid://biu4qr3nr1eny" path="res://test/mp_test_driver.gd" id="99_mptst"]
|
||||||
|
|
||||||
[sub_resource type="BoxShape3D" id="BoxShape3D_vlqg6"]
|
[sub_resource type="BoxShape3D" id="BoxShape3D_vlqg6"]
|
||||||
@@ -31,9 +32,6 @@ sdfgi_enabled = true
|
|||||||
[sub_resource type="BoxShape3D" id="BoxShape3D_arao0"]
|
[sub_resource type="BoxShape3D" id="BoxShape3D_arao0"]
|
||||||
size = Vector3(15, 20, 0.1)
|
size = Vector3(15, 20, 0.1)
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_464r1"]
|
|
||||||
metadata/__load_path__ = "res://stations/Hob_old.tscn"
|
|
||||||
|
|
||||||
[node name="Main" type="Node3D" unique_id=1312265607 node_paths=PackedStringArray("items_spawner", "players_spawner")]
|
[node name="Main" type="Node3D" unique_id=1312265607 node_paths=PackedStringArray("items_spawner", "players_spawner")]
|
||||||
script = ExtResource("1_kdan8")
|
script = ExtResource("1_kdan8")
|
||||||
items_spawner = NodePath("ItemsSpawner")
|
items_spawner = NodePath("ItemsSpawner")
|
||||||
@@ -94,7 +92,7 @@ script = ExtResource("99_mptst")
|
|||||||
transform = Transform3D(1, 0, -1.7484555e-07, 0, 1, 0, 1.7484555e-07, 0, 1, 0, 0, -1.2)
|
transform = Transform3D(1, 0, -1.7484555e-07, 0, 1, 0, 1.7484555e-07, 0, 1, 0, 0, -1.2)
|
||||||
script = ExtResource("6_pw2j5")
|
script = ExtResource("6_pw2j5")
|
||||||
kitchen_scene = ExtResource("7_0sjqq")
|
kitchen_scene = ExtResource("7_0sjqq")
|
||||||
hob_scene = SubResource("Resource_464r1")
|
hob_scene = ExtResource("12_hob")
|
||||||
|
|
||||||
[node name="DayController" parent="." unique_id=1418639156 instance=ExtResource("9_5wx0o")]
|
[node name="DayController" parent="." unique_id=1418639156 instance=ExtResource("9_5wx0o")]
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
[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://prefabs/XROrigin.tscn" id="3_bryd6"]
|
[ext_resource type="PackedScene" uid="uid://j5s5wus7tuhb" path="res://prefabs/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="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_old.tscn" id="5_gfdi7"]
|
[ext_resource type="PackedScene" uid="uid://b052o1fgwq5bw" path="res://stations/Hob.tscn" id="5_gfdi7"]
|
||||||
[ext_resource type="PackedScene" uid="uid://efaec6ymgabo" path="res://stations/Counter.tscn" id="7_jnxsa"]
|
[ext_resource type="PackedScene" uid="uid://efaec6ymgabo" path="res://stations/Counter.tscn" id="7_jnxsa"]
|
||||||
[ext_resource type="PackedScene" uid="uid://bnwb7imcotkod" path="res://prefabs/build_mode_controller.tscn" id="9_hooyg"]
|
[ext_resource type="PackedScene" uid="uid://bnwb7imcotkod" path="res://prefabs/build_mode_controller.tscn" id="9_hooyg"]
|
||||||
[ext_resource type="PackedScene" uid="uid://caf0xanmxbshy" path="res://stations/table.tscn" id="11_gaw43"]
|
[ext_resource type="PackedScene" uid="uid://caf0xanmxbshy" path="res://stations/table.tscn" id="11_gaw43"]
|
||||||
|
|||||||
+2
-2
@@ -36,13 +36,13 @@ func _process(delta: float) -> void:
|
|||||||
func _enable_tool(_item: XRToolsPickable):
|
func _enable_tool(_item: XRToolsPickable):
|
||||||
SweetLogger.debug("Enable tool {0} on {1}", [_item.name, name])
|
SweetLogger.debug("Enable tool {0} on {1}", [_item.name, name])
|
||||||
_item.visible = true
|
_item.visible = true
|
||||||
_item.get_node("NetPickable").set_tool_enabled(true)
|
_item.get_node("NetPickable").grabbable = true
|
||||||
|
|
||||||
|
|
||||||
func _disable_tool(_item: XRToolsPickable):
|
func _disable_tool(_item: XRToolsPickable):
|
||||||
SweetLogger.debug("Disable tool {0} on {1}", [_item.name, name])
|
SweetLogger.debug("Disable tool {0} on {1}", [_item.name, name])
|
||||||
_item.visible = false
|
_item.visible = false
|
||||||
_item.get_node("NetPickable").set_tool_enabled(false)
|
_item.get_node("NetPickable").grabbable = false
|
||||||
|
|
||||||
func _disable_all_tools():
|
func _disable_all_tools():
|
||||||
SweetLogger.debug("Disable all tools on {0}", [name])
|
SweetLogger.debug("Disable all tools on {0}", [name])
|
||||||
|
|||||||
+13
-8
@@ -35,9 +35,11 @@ properties/1/replication_mode = 1
|
|||||||
properties/2/path = NodePath("NetPickable:net_held_by")
|
properties/2/path = NodePath("NetPickable:net_held_by")
|
||||||
properties/2/spawn = true
|
properties/2/spawn = true
|
||||||
properties/2/replication_mode = 1
|
properties/2/replication_mode = 1
|
||||||
properties/3/path = NodePath(".:enabled")
|
|
||||||
properties/3/spawn = false
|
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_rb7yx"]
|
||||||
properties/3/replication_mode = 1
|
properties/0/path = NodePath("FoodItem:enabled")
|
||||||
|
properties/0/spawn = false
|
||||||
|
properties/0/replication_mode = 1
|
||||||
|
|
||||||
[node name="FoodItem" unique_id=1675596942 instance=ExtResource("1_cvkx3")]
|
[node name="FoodItem" unique_id=1675596942 instance=ExtResource("1_cvkx3")]
|
||||||
second_hand_grab = 1
|
second_hand_grab = 1
|
||||||
@@ -58,14 +60,17 @@ hand_pose = SubResource("Resource_qyiot")
|
|||||||
replication_config = SubResource("SceneReplicationConfig_np_burger")
|
replication_config = SubResource("SceneReplicationConfig_np_burger")
|
||||||
script = ExtResource("9_8m8gg")
|
script = ExtResource("9_8m8gg")
|
||||||
|
|
||||||
[node name="FoodItem" parent="." index="4" unique_id=63948206 instance=ExtResource("8_tkk28")]
|
[node name="MultiplayerSynchronizerFoodItem" type="MultiplayerSynchronizer" parent="." index="4" unique_id=225229041]
|
||||||
|
replication_config = SubResource("SceneReplicationConfig_rb7yx")
|
||||||
|
|
||||||
|
[node name="FoodItem" parent="." index="5" unique_id=63948206 instance=ExtResource("8_tkk28")]
|
||||||
id = "item_id"
|
id = "item_id"
|
||||||
sell_value = 0
|
sell_value = 0
|
||||||
|
|
||||||
[node name="DespawningItem" parent="." index="5" unique_id=303090111 instance=ExtResource("10_vkoit")]
|
[node name="DespawningItem" parent="." index="6" unique_id=303090111 instance=ExtResource("10_vkoit")]
|
||||||
|
|
||||||
[node name="CombinableItem" parent="." index="6" unique_id=996936271 instance=ExtResource("11_combinable")]
|
[node name="CombinableItem" parent="." index="7" unique_id=996936271 instance=ExtResource("11_combinable")]
|
||||||
|
|
||||||
[node name="SoundManager" type="Node3D" parent="." index="7" unique_id=118155212]
|
[node name="SoundManager" type="Node3D" parent="." index="8" unique_id=118155212]
|
||||||
|
|
||||||
[node name="Model" type="Node3D" parent="." index="8" unique_id=1015960306]
|
[node name="Model" type="Node3D" parent="." index="9" unique_id=1015960306]
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ const TIMESTAMP_BG_COLOR = "#1e3a5f"
|
|||||||
#===================================================================================#
|
#===================================================================================#
|
||||||
## Column widths for alignment (in characters)
|
## Column widths for alignment (in characters)
|
||||||
const PEER_ID_COLUMN_WIDTH = 6
|
const PEER_ID_COLUMN_WIDTH = 6
|
||||||
const LOG_TYPE_COLUMN_WIDTH = 10
|
const LOG_TYPE_COLUMN_WIDTH = 12
|
||||||
## Default mm:ss:ms; with SHOW_TIMESTAMP_HOURS, hh:mm:ss:ms
|
## Default mm:ss:ms; with SHOW_TIMESTAMP_HOURS, hh:mm:ss:ms
|
||||||
const TIMESTAMP_COLUMN_WIDTH_MMSSMS = 9
|
const TIMESTAMP_COLUMN_WIDTH_MMSSMS = 9
|
||||||
const TIMESTAMP_COLUMN_WIDTH_HHMMSSMS = 12
|
const TIMESTAMP_COLUMN_WIDTH_HHMMSSMS = 12
|
||||||
|
|||||||
Binary file not shown.
@@ -1,7 +1,7 @@
|
|||||||
[gd_scene format=3 uid="uid://bu6jt26pq2y0k"]
|
[gd_scene format=3 uid="uid://bu6jt26pq2y0k"]
|
||||||
|
|
||||||
[ext_resource type="PackedScene" uid="uid://q37nqadkibbp" path="res://environment/door.tscn" id="1_hgrxd"]
|
[ext_resource type="PackedScene" uid="uid://q37nqadkibbp" path="res://environment/door.tscn" id="1_hgrxd"]
|
||||||
[ext_resource type="PackedScene" uid="uid://j7caslh27nor" path="res://stations/Hob_old.tscn" id="1_p8umn"]
|
[ext_resource type="PackedScene" uid="uid://b052o1fgwq5bw" 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://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://dlw5geheupgpt" path="res://stations/hatch.tscn" id="3_d63j6"]
|
||||||
[ext_resource type="PackedScene" uid="uid://da6yrsnxvsrif" path="res://environment/wall.tscn" id="4_41vpv"]
|
[ext_resource type="PackedScene" uid="uid://da6yrsnxvsrif" path="res://environment/wall.tscn" id="4_41vpv"]
|
||||||
|
|||||||
@@ -9,12 +9,18 @@
|
|||||||
[ext_resource type="PackedScene" uid="uid://caf0xanmxbshy" path="res://stations/table.tscn" id="7_0342k"]
|
[ext_resource type="PackedScene" uid="uid://caf0xanmxbshy" path="res://stations/table.tscn" id="7_0342k"]
|
||||||
[ext_resource type="PackedScene" uid="uid://cnjwtnhwh0i8q" path="res://stations/dirt_station.tscn" id="8_b5gb1"]
|
[ext_resource type="PackedScene" uid="uid://cnjwtnhwh0i8q" path="res://stations/dirt_station.tscn" id="8_b5gb1"]
|
||||||
[ext_resource type="PackedScene" uid="uid://sc6i0i1f0o48" path="res://stations/potato_dispenser.tscn" id="9_ok0xr"]
|
[ext_resource type="PackedScene" uid="uid://sc6i0i1f0o48" path="res://stations/potato_dispenser.tscn" id="9_ok0xr"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://b3m2ag8g5rj4r" path="res://items/hamburger.tscn" id="10_ay2lk"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://duwrbvwcqcuk3" path="res://items/chips.tscn" id="11_m7v4h"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bp3v1jl8pctro" path="res://Containers/plate.tscn" id="12_h4a0y"]
|
||||||
|
|
||||||
[node name="SmallLine" type="Node3D" unique_id=1844128818]
|
[node name="SmallLine" type="Node3D" unique_id=1844128818]
|
||||||
|
|
||||||
[node name="Counter" parent="." unique_id=1487893288 instance=ExtResource("2_3qwcq")]
|
[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, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.6, 0, 0)
|
||||||
|
|
||||||
|
[node name="Counter2" parent="." unique_id=707500902 instance=ExtResource("2_3qwcq")]
|
||||||
|
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -1.8000001, 0, 1.8000001)
|
||||||
|
|
||||||
[node name="Sink" parent="." unique_id=2055277359 instance=ExtResource("3_3qwcq")]
|
[node name="Sink" parent="." unique_id=2055277359 instance=ExtResource("3_3qwcq")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.2, 0, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.2, 0, 0)
|
||||||
|
|
||||||
@@ -30,7 +36,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.8000001, 0, 0)
|
|||||||
[node name="Table" parent="." unique_id=987495548 instance=ExtResource("7_0342k")]
|
[node name="Table" parent="." unique_id=987495548 instance=ExtResource("7_0342k")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.4, 0, 1.2)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.4, 0, 1.2)
|
||||||
|
|
||||||
[node name="Hob" parent="." unique_id=707500902 instance=ExtResource("1_1wuvq")]
|
[node name="Hob" parent="." unique_id=484464865 instance=ExtResource("1_1wuvq")]
|
||||||
|
|
||||||
[node name="DirtStation" parent="." unique_id=784427347 instance=ExtResource("8_b5gb1")]
|
[node name="DirtStation" parent="." unique_id=784427347 instance=ExtResource("8_b5gb1")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.8000001, 0, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.8000001, 0, 0)
|
||||||
@@ -38,4 +44,13 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.8000001, 0, 0)
|
|||||||
[node name="PotatoDispenser" parent="." unique_id=522124817 instance=ExtResource("9_ok0xr")]
|
[node name="PotatoDispenser" parent="." unique_id=522124817 instance=ExtResource("9_ok0xr")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.8000001, 0, 0.6)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.8000001, 0, 0.6)
|
||||||
|
|
||||||
|
[node name="Hamburger" parent="." unique_id=1675596942 instance=ExtResource("10_ay2lk")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.2, 0)
|
||||||
|
|
||||||
|
[node name="Chips" parent="." unique_id=740493636 instance=ExtResource("11_m7v4h")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.8000001, 1.2, 1.8000001)
|
||||||
|
|
||||||
|
[node name="Plate" parent="." unique_id=190487773 instance=ExtResource("12_h4a0y")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.6, 1.2, 0)
|
||||||
|
|
||||||
[editable path="Table"]
|
[editable path="Table"]
|
||||||
|
|||||||
+23
-7
@@ -13,6 +13,20 @@ static func find_food_item(node: Node) -> FoodItem:
|
|||||||
return null
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
# Returns "script.gd::caller_function" of whoever originally called into Helper, skipping recursive self-calls.
|
||||||
|
static func _get_caller_label() -> String:
|
||||||
|
var stack := get_stack()
|
||||||
|
# stack[0] = _get_caller_label, stack[1] = the Helper function that called this
|
||||||
|
var self_function: String = stack[1].function if stack.size() > 1 else ""
|
||||||
|
var caller_index := 2
|
||||||
|
while caller_index < stack.size() and stack[caller_index].function == self_function:
|
||||||
|
caller_index += 1
|
||||||
|
if caller_index >= stack.size():
|
||||||
|
return "unknown"
|
||||||
|
var caller_script: String = (stack[caller_index].source as String).get_file()
|
||||||
|
return "{0}::{1}".format([caller_script, stack[caller_index].function])
|
||||||
|
|
||||||
|
|
||||||
static func find_first_child_of_type(node: Node, type: Variant) -> Node:
|
static func find_first_child_of_type(node: Node, type: Variant) -> Node:
|
||||||
for child in node.get_children():
|
for child in node.get_children():
|
||||||
if is_instance_of(child, type):
|
if is_instance_of(child, type):
|
||||||
@@ -20,20 +34,22 @@ static func find_first_child_of_type(node: Node, type: Variant) -> Node:
|
|||||||
var nested := find_first_child_of_type(child, type)
|
var nested := find_first_child_of_type(child, type)
|
||||||
if nested:
|
if nested:
|
||||||
return nested
|
return nested
|
||||||
|
#SweetLogger.warning("{0}: Could not find child of type {1} from node at: {2}", [_get_caller_label(), type.get_global_name(), node.get_path()])
|
||||||
return null
|
return null
|
||||||
|
|
||||||
|
|
||||||
# Returns true if decendant is a decendant node of root, false otherwise. Returns false if either node is null.
|
# Returns true if decendant is a decendant node of root, false otherwise. Returns false if either node is null.
|
||||||
static func is_node_decendant_of(decendant: Node, root: Node) -> bool:
|
static func is_node_decendant_of(decendant: Node, root: Node) -> bool:
|
||||||
if not decendant or not root:
|
if not decendant or not root:
|
||||||
SweetLogger.debug("Decendant or root is null")
|
SweetLogger.debug("{0}: Decendant or root is null", [_get_caller_label()])
|
||||||
return false
|
return false
|
||||||
var current: Node = decendant
|
var current: Node = decendant
|
||||||
while current:
|
while current:
|
||||||
if current == root:
|
if current == root:
|
||||||
SweetLogger.debug("Decendant {0} is a descendant of root {1}", [decendant.name, root.name])
|
SweetLogger.debug("{0}: Decendant {1} is a descendant of root {2}", [_get_caller_label(), decendant.name, root.name])
|
||||||
return true
|
return true
|
||||||
current = current.get_parent()
|
current = current.get_parent()
|
||||||
SweetLogger.debug("Decendant {0} is NOT a descendant of root {1}", [decendant.name, root.name])
|
SweetLogger.debug("{0}: Decendant {1} is NOT a descendant of root {2}", [_get_caller_label(), decendant.name, root.name])
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
|
||||||
@@ -50,7 +66,7 @@ static func get_snapped_transform(node: Node3D) -> Transform3D:
|
|||||||
static func get_node_from_path(from: Node, node_path: NodePath) -> Node3D:
|
static func get_node_from_path(from: Node, node_path: NodePath) -> Node3D:
|
||||||
var item := from.get_node_or_null(node_path) as Node3D
|
var item := from.get_node_or_null(node_path) as Node3D
|
||||||
if not item:
|
if not item:
|
||||||
SweetLogger.warning("Could not resolve node from node_path: {0}, are the trees in sync?", [node_path])
|
SweetLogger.warning("{0}: Could not resolve node from node_path: {1}, are the trees in sync?", [_get_caller_label(), node_path])
|
||||||
return null
|
return null
|
||||||
return item
|
return item
|
||||||
|
|
||||||
@@ -58,12 +74,12 @@ static func get_node_from_path(from: Node, node_path: NodePath) -> Node3D:
|
|||||||
|
|
||||||
static func play_sound(player: AudioStreamPlayer3D, stream: AudioStream, pitch_scale: float = 1):
|
static func play_sound(player: AudioStreamPlayer3D, stream: AudioStream, pitch_scale: float = 1):
|
||||||
if not player:
|
if not player:
|
||||||
SweetLogger.warning("AudioStreamPlayer3D is null, can not play sound")
|
SweetLogger.warning("{0}: AudioStreamPlayer3D is null, can not play sound", [_get_caller_label()])
|
||||||
return
|
return
|
||||||
if not stream:
|
if not stream:
|
||||||
SweetLogger.warning("AudioStream is null, can not play sound")
|
SweetLogger.warning("{0}: AudioStream is null, can not play sound", [_get_caller_label()])
|
||||||
return
|
return
|
||||||
player.pitch_scale = pitch_scale
|
player.pitch_scale = pitch_scale
|
||||||
player.stream = stream
|
player.stream = stream
|
||||||
player.play()
|
player.play()
|
||||||
SweetLogger.debug("Play sound: {0} with pitch_scale: {1} on {2}", [stream.resource_path, pitch_scale, player.get_parent().name])
|
SweetLogger.debug("{0}: Play sound: {1} with pitch_scale: {2} on {3}", [_get_caller_label(), stream.resource_path, pitch_scale, player.get_parent().name])
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://be8o8m6xpp3kc
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
[ext_resource type="PackedScene" uid="uid://j5s5wus7tuhb" path="res://prefabs/XROrigin.tscn" id="2_j7vd1"]
|
[ext_resource type="PackedScene" uid="uid://j5s5wus7tuhb" path="res://prefabs/XROrigin.tscn" id="2_j7vd1"]
|
||||||
[ext_resource type="Material" uid="uid://cmia50cfqxxo4" path="res://textures/dev_material_3d.tres" id="3_ssbaf"]
|
[ext_resource type="Material" uid="uid://cmia50cfqxxo4" path="res://textures/dev_material_3d.tres" id="3_ssbaf"]
|
||||||
[ext_resource type="Sky" uid="uid://c1abtpwhdm2d5" path="res://textures/sky/NightSkyHDRI009_16K.tres" id="4_081u3"]
|
[ext_resource type="Sky" uid="uid://c1abtpwhdm2d5" path="res://textures/sky/NightSkyHDRI009_16K.tres" id="4_081u3"]
|
||||||
[ext_resource type="PackedScene" uid="uid://j7caslh27nor" path="res://stations/Hob_old.tscn" id="5_t1fa7"]
|
[ext_resource type="PackedScene" uid="uid://b052o1fgwq5bw" path="res://stations/Hob.tscn" id="5_t1fa7"]
|
||||||
[ext_resource type="PackedScene" uid="uid://dvrk268s7gkxh" path="res://stations/sink.tscn" id="6_6jhmh"]
|
[ext_resource type="PackedScene" uid="uid://dvrk268s7gkxh" path="res://stations/sink.tscn" id="6_6jhmh"]
|
||||||
[ext_resource type="PackedScene" uid="uid://bp3v1jl8pctro" path="res://Containers/plate.tscn" id="7_bowes"]
|
[ext_resource type="PackedScene" uid="uid://bp3v1jl8pctro" path="res://Containers/plate.tscn" id="7_bowes"]
|
||||||
[ext_resource type="PackedScene" uid="uid://cnjwtnhwh0i8q" path="res://stations/dirt_station.tscn" id="8_pvl84"]
|
[ext_resource type="PackedScene" uid="uid://cnjwtnhwh0i8q" path="res://stations/dirt_station.tscn" id="8_pvl84"]
|
||||||
|
|||||||
Reference in New Issue
Block a user