1 Commits

Author SHA1 Message Date
JonShard e965cddde7 Fix Plates not disable/lock on clients in multiplayer 2026-08-21 15:46:01 +02:00
6 changed files with 63 additions and 95 deletions
+7 -1
View File
@@ -43,6 +43,9 @@ properties/3/replication_mode = 2
properties/4/path = NodePath("PlateController:is_dirty") properties/4/path = NodePath("PlateController:is_dirty")
properties/4/spawn = false properties/4/spawn = false
properties/4/replication_mode = 1 properties/4/replication_mode = 1
properties/5/path = NodePath("NetPickable:grabbable")
properties/5/spawn = false
properties/5/replication_mode = 1
[node name="Plate" type="RigidBody3D" unique_id=190487773] [node name="Plate" type="RigidBody3D" unique_id=190487773]
collision_layer = 4 collision_layer = 4
@@ -51,8 +54,11 @@ freeze_mode = 1
script = ExtResource("1_nq5sa") script = ExtResource("1_nq5sa")
second_hand_grab = 1 second_hand_grab = 1
[node name="PlateController" type="Node" parent="." unique_id=1503264193] [node name="PlateController" type="Node" parent="." unique_id=1503264193 node_paths=PackedStringArray("net_pickable", "dirty_node", "container")]
script = ExtResource("1_vjsmi") script = ExtResource("1_vjsmi")
net_pickable = NodePath("../NetPickable")
dirty_node = NodePath("../Dirty")
container = NodePath("../Container")
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1365637864] [node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1365637864]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.007171631, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.007171631, 0)
+22 -3
View File
@@ -1,14 +1,31 @@
class_name PlateController class_name PlateController
extends Node extends Node
@onready var dirty_node: Node3D = $"../Dirty" @export var net_pickable: NetPickable
@onready var container: ItemContainer = $"../Container" @export var dirty_node: Node3D
@export var container: ItemContainer
## Synced summary of the plate's contents (item ids). The contents themselves ## Synced summary of the plate's contents (item ids). The contents themselves
## are the real item nodes ItemContainer reparents under the plate; this is only ## are the real item nodes ItemContainer reparents under the plate; this is only
## what other systems read to ask "what is on this plate" without walking them. ## what other systems read to ask "what is on this plate" without walking them.
@export var contained_ids: Array[String] = [] @export var contained_ids: Array[String] = []
@export var enabled = true:
get:
return enabled
set(value):
if NetworkManager.owns_world():
clients_set_enabled.rpc(value)
enabled = value
container.enabled = not is_dirty
net_pickable.grabbable = value
SweetLogger.info("Set plate enabled: {0}", [value])
@rpc("authority", "call_remote", "reliable")
func clients_set_enabled(value: bool) -> void:
enabled = value
@export var is_dirty: bool = false: @export var is_dirty: bool = false:
get: get:
return is_dirty return is_dirty
@@ -29,4 +46,6 @@ func get_food_items() -> Array[FoodItem]:
return container.contained_items return container.contained_items
func _ready() -> void: func _ready() -> void:
Helper.check_reference(self, dirty_node, Node3D) Helper.check_reference(self, dirty_node, Node3D)
Helper.check_reference(self, net_pickable, NetPickable)
Helper.check_reference(self, container, ItemContainer)
+2 -2
View File
@@ -33,13 +33,13 @@ 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 @export 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 grabbable: bool = true: set = set_grabbable @export var grabbable: bool = true: set = set_grabbable
func _ready() -> void: func _ready() -> void:
_pickable = get_parent() as XRToolsPickable _pickable = get_parent() as XRToolsPickable
+13 -51
View File
@@ -8,13 +8,13 @@ extends SharedInventoryStation
@export var eating_duration: float = 5.0 @export var eating_duration: float = 5.0
@export var money_sound: AudioStream @export var money_sound: AudioStream
@onready var label_3d: Label3D = $Label3D @export var label_3d: Label3D
@onready var label_3d_time: Label3D = $Label3DTime @export var label_3d_time: Label3D
@onready var snap_zones: Array[XRToolsSnapZone] = [] @export var snap_zones: Array[XRToolsSnapZone]
@onready var progress_bar: ProgressBar3D = $ProgressBar3D @export var progress_bar: ProgressBar3D
@onready var audio_player: AudioStreamPlayer3D = $AudioStreamPlayer3D @export var audio_player: AudioStreamPlayer3D
@onready var customers: Node3D = $Customers @export var customers: Node3D
@onready var player_detect_area_3d: Area3D = $PlayerDetectArea3D @export var player_detect_area_3d: Area3D
const lbl_thinking: String = "..." const lbl_thinking: String = "..."
const lbl_ordering: String = "!" const lbl_ordering: String = "!"
@@ -66,16 +66,6 @@ func server_place_order() -> void:
place_order() place_order()
func absorb_items():
SweetLogger.debug("->[]")
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)
# Flattens items held across all of this table's snap zones # Flattens items held across all of this table's snap zones
func get_exposed_food_items() -> Array[FoodItem]: func get_exposed_food_items() -> Array[FoodItem]:
var items: Array[FoodItem] = [] var items: Array[FoodItem] = []
@@ -83,7 +73,7 @@ func get_exposed_food_items() -> Array[FoodItem]:
var held_object = zone.picked_up_object var held_object = zone.picked_up_object
if not held_object: if not held_object:
continue continue
var plate: PlateController = _get_plate_controller_from_item(held_object) var plate: PlateController = Helper.find_first_child_of_type(held_object, PlateController)
if plate: if plate:
items.append_array(plate.container.contained_items) items.append_array(plate.container.contained_items)
continue continue
@@ -119,10 +109,11 @@ func clearAllFood() -> void:
if not held_object: if not held_object:
continue continue
var plate: PlateController = _get_plate_controller_from_item(held_object) var plate: PlateController = Helper.find_first_child_of_type(held_object, PlateController)
var food_item: FoodItem = Helper.find_food_item(held_object) var food_item: FoodItem = Helper.find_food_item(held_object)
if plate: if plate:
plate.container.clear() plate.container.clear()
plate.enabled = true
plate.is_dirty = true plate.is_dirty = true
# Side or other food item directly on table # Side or other food item directly on table
@@ -157,9 +148,6 @@ func _ready() -> void:
Helper.check_reference(self, progress_bar, ProgressBar3D) Helper.check_reference(self, progress_bar, ProgressBar3D)
progress_bar.y_billboard = true progress_bar.y_billboard = true
progress_bar.exponential = 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()
Helper.check_reference(self, audio_player, AudioStreamPlayer3D) Helper.check_reference(self, audio_player, AudioStreamPlayer3D)
# Get snap_zones for plates, store in array snap_zones # Get snap_zones for plates, store in array snap_zones
@@ -209,20 +197,6 @@ func _on_player_exit(_body):
SweetLogger.debug("Player exit, players_count: {0}", [_players_count]) SweetLogger.debug("Player exit, players_count: {0}", [_players_count])
func _get_plate_controller_from_item(_item: Node) -> PlateController:
if not _item:
SweetLogger.debug("Item is null")
return null
for child in _item.get_children():
if child is PlateController:
SweetLogger.debug("Found child")
return child
SweetLogger.debug("No match")
return null
func _absorb_item_if_correct(_item: Node) -> void: func _absorb_item_if_correct(_item: Node) -> void:
SweetLogger.debug("Absorb item if correct, item: {0}", [_item]) SweetLogger.debug("Absorb item if correct, item: {0}", [_item])
if not _item: if not _item:
@@ -231,7 +205,7 @@ func _absorb_item_if_correct(_item: Node) -> void:
return return
# Plate: Get plate controller in child of _item (hopefully a plate XRpickable) # Plate: Get plate controller in child of _item (hopefully a plate XRpickable)
var plate_controller = _get_plate_controller_from_item(_item) var plate_controller: PlateController = Helper.find_first_child_of_type(_item, PlateController)
SweetLogger.debug("Plate_controller ref: {0}", [plate_controller]) SweetLogger.debug("Plate_controller ref: {0}", [plate_controller])
if plate_controller: if plate_controller:
@@ -239,7 +213,7 @@ func _absorb_item_if_correct(_item: Node) -> void:
for food_item: FoodItem in plate_controller.container.contained_items: # TOOD: handle registering the meal again when a side is added to the plate 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: if food_item.id in _unsatisfied_orders and not food_item.is_absorbed:
SweetLogger.debug("Held a plate with FoodItem that is in unsatisfied orders, removing it") SweetLogger.debug("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 plate_controller.enabled = false # Lock plate
_unsatisfied_orders.erase(food_item.id) _unsatisfied_orders.erase(food_item.id)
food_item.is_absorbed = true food_item.is_absorbed = true
_update_state_from_orders() _update_state_from_orders()
@@ -273,7 +247,7 @@ func _collect_money_from_food():
if not held_object: if not held_object:
continue continue
var plate: PlateController = _get_plate_controller_from_item(held_object) var plate: PlateController = Helper.find_first_child_of_type(held_object, PlateController)
var food_item: FoodItem = Helper.find_food_item(held_object) var food_item: FoodItem = Helper.find_food_item(held_object)
if plate: if plate:
for fi: FoodItem in plate.get_food_items(): for fi: FoodItem in plate.get_food_items():
@@ -359,19 +333,7 @@ func _state_end(oldState: TableState):
GameManager.game_over() GameManager.game_over()
## Pure presentation, driven off the current (locally authoritative or
## synced-from-server) state. Runs on every peer.
func _refresh_display() -> void: func _refresh_display() -> void:
# _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.
var a := Helper.check_reference(self, progress_bar, ProgressBar3D)
var b := Helper.check_reference(self, label_3d, Label3D)
var c := Helper.check_reference(self, label_3d_time, Label3D)
if not (a and b and c):
return
match _state: match _state:
TableState.IDLE: TableState.IDLE:
progress_bar.set_bar_visible(false) progress_bar.set_bar_visible(false)
+8 -1
View File
@@ -33,9 +33,16 @@ visible = false
[node name="SnapZone" parent="." index="5" unique_id=1315859105] [node name="SnapZone" parent="." index="5" unique_id=1315859105]
enabled = false enabled = false
[node name="Table" type="StaticBody3D" parent="." index="6" unique_id=1863572470 node_paths=PackedStringArray("notification_audio", "ambient_audio", "snap_zone", "synchronizer") groups=["station", "table"]] [node name="Table" type="StaticBody3D" parent="." index="6" unique_id=1863572470 node_paths=PackedStringArray("label_3d", "label_3d_time", "snap_zones", "progress_bar", "audio_player", "customers", "player_detect_area_3d", "notification_audio", "ambient_audio", "snap_zone", "synchronizer") groups=["station", "table"]]
script = ExtResource("1_2vcpj") script = ExtResource("1_2vcpj")
money_sound = ExtResource("2_jslhm") money_sound = ExtResource("2_jslhm")
label_3d = NodePath("Label3D")
label_3d_time = NodePath("Label3DTime")
snap_zones = [NodePath("XRToolsSnapZone")]
progress_bar = NodePath("ProgressBar3D")
audio_player = NodePath("AudioStreamPlayer3D")
customers = NodePath("Customers")
player_detect_area_3d = NodePath("PlayerDetectArea3D")
probe_paths = Array[NodePath]([NodePath("ProbeForward"), NodePath("ProbeRight"), NodePath("ProbeBack"), NodePath("ProbeLeft")]) probe_paths = Array[NodePath]([NodePath("ProbeForward"), NodePath("ProbeRight"), NodePath("ProbeBack"), NodePath("ProbeLeft")])
pickup_sound = ExtResource("3_0s6ir") pickup_sound = ExtResource("3_0s6ir")
drop_sound = ExtResource("3_0s6ir") drop_sound = ExtResource("3_0s6ir")
+11 -37
View File
@@ -1,4 +1,4 @@
### Full game systems (hardest part) ### Scope - Full game systems (hardest part)
- Star day modifiers - Star day modifiers
- research unlock - research unlock
- Mess and cleaning (How it works in VR?) - Mess and cleaning (How it works in VR?)
@@ -19,29 +19,18 @@
- Freshness - Freshness
- Fullbody tracking (Height calibration) - Fullbody tracking (Height calibration)
### Todo
- Complete SharedInventory
- Investigate Table::_absorb_item_if_correct # TOOD: handle registering the meal again when a side is added to the plate
### Bugs ### Bugs
- Plate not locked on clinets by table - Plate not locked on clinets by table
- Rare times, plate not cleared / has buger on it after table eaten. New customer didn't accept it, so must have been empty, but server and client visuals had burger on plate. - Rare times, plate not cleared / has buger on it after table eaten. New customer didn't accept it, so must have been empty, but server and client visuals had burger on plate.
- Table does not accept clinet that was already in player detect zone - Table does not accept clinet that was already in player detect zone
- A hamburger in a station in build mode stays but becomes kinematic when entering build mode again, from build mode.
### Station inheritence suggestion
Station
WorkStation (performs work on a food item to convert it)
Hob
Sink
Counter
StorageStation (one snap zone and list of positions, FILO item dispenser, like a gun magazine)
Shelf
PlateRak
Dishwasher
SharedInventoryStation (one snap zone, shares a list of connected stations with which items exist in each of them)
Table
Belt
Dispenser
### Multiplayer bugs ### Multiplayer bugs
- despan flicker for clien broken
- client keep autorityu of thrown object until lands - client keep autorityu of thrown object until lands
- in build mode, snap zones off, stations disabled - in build mode, snap zones off, stations disabled
- players can steal from each others hands - players can steal from each others hands
@@ -63,7 +52,7 @@ extre rolling pin
mop mop
bin bin
## Food dispensers ### Food dispensers
burger burger
buns buns
potato potato
@@ -74,7 +63,7 @@ pasta plates
meat meat
cheese cheese
## Automation ### Automation
belt belt
inserter inserter
splitter splitter
@@ -83,12 +72,12 @@ blender
dishwasher dishwasher
flinger / catapult flinger / catapult
## Storage ### Storage
shelf shelf
freezer freezer
frozen prep station equivilant frozen prep station equivilant
## Control ### Control
Phone Phone
Napkins (no mess consumable) Napkins (no mess consumable)
sharp cutlery (eat faster consumable) sharp cutlery (eat faster consumable)
@@ -121,14 +110,10 @@ Shared INventory
- On new table available, QueueController tells front customer in queue to sit at table - On new table available, QueueController tells front customer in queue to sit at table
## Modifiers ## Modifiers
- Disposable plates. no washing. much less money - Disposable plates. no washing. much less money
## Weird ideas ## Weird ideas
- carry 4 plates. A snapzone on forearm that only accept plate, and drops it if tilted too much. Real waiter feel. - carry 4 plates. A snapzone on forearm that only accept plate, and drops it if tilted too much. Real waiter feel.
@@ -136,14 +121,3 @@ Shared INventory
## Research ## Research
- Reasearch is its own currency. - Reasearch is its own currency.
- You unlock items in shop with reseach points. Then you can buy them. You "buy" the same tile twice. - You unlock items in shop with reseach points. Then you can buy them. You "buy" the same tile twice.
## NEtwork TOTO
- Make gamemanager a node with syncroniser
- All stations need syncronizers
- Pickable not synced cooked burger on hob example
- Client purchases don't spawn items from shop. RPC adventure
- Client can not move stations including hobs, on_move handle drop is called, network pickable authority is given. but station not moved