From e965cddde7122d7bacd7c927c39c5babc3346e1e Mon Sep 17 00:00:00 2001 From: JonShard Date: Fri, 21 Aug 2026 15:46:01 +0200 Subject: [PATCH] Fix Plates not disable/lock on clients in multiplayer --- Containers/plate.tscn | 8 ++++- Containers/plate_controller.gd | 25 +++++++++++-- Net/net_pickable.gd | 4 +-- Stations/table.gd | 64 +++++++--------------------------- Stations/table.tscn | 9 ++++- documentation/Notes.md | 48 ++++++------------------- 6 files changed, 63 insertions(+), 95 deletions(-) diff --git a/Containers/plate.tscn b/Containers/plate.tscn index b768e51..c24bddc 100644 --- a/Containers/plate.tscn +++ b/Containers/plate.tscn @@ -43,6 +43,9 @@ properties/3/replication_mode = 2 properties/4/path = NodePath("PlateController:is_dirty") properties/4/spawn = false 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] collision_layer = 4 @@ -51,8 +54,11 @@ freeze_mode = 1 script = ExtResource("1_nq5sa") 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") +net_pickable = NodePath("../NetPickable") +dirty_node = NodePath("../Dirty") +container = NodePath("../Container") [node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1365637864] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.007171631, 0) diff --git a/Containers/plate_controller.gd b/Containers/plate_controller.gd index a97db18..7d7ea33 100644 --- a/Containers/plate_controller.gd +++ b/Containers/plate_controller.gd @@ -1,14 +1,31 @@ class_name PlateController extends Node -@onready var dirty_node: Node3D = $"../Dirty" -@onready var container: ItemContainer = $"../Container" +@export var net_pickable: NetPickable +@export var dirty_node: Node3D +@export var container: ItemContainer + ## 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 ## what other systems read to ask "what is on this plate" without walking them. @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: get: return is_dirty @@ -29,4 +46,6 @@ func get_food_items() -> Array[FoodItem]: return container.contained_items func _ready() -> void: - Helper.check_reference(self, dirty_node, Node3D) \ No newline at end of file + Helper.check_reference(self, dirty_node, Node3D) + Helper.check_reference(self, net_pickable, NetPickable) + Helper.check_reference(self, container, ItemContainer) \ No newline at end of file diff --git a/Net/net_pickable.gd b/Net/net_pickable.gd index d527d21..c13c5c4 100644 --- a/Net/net_pickable.gd +++ b/Net/net_pickable.gd @@ -33,13 +33,13 @@ var _original_freeze_mode: RigidBody3D.FreezeMode # 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 +@export var _original_enabled: bool ## 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 ## pickable's actual `enabled` property, so this is how other systems express ## "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: _pickable = get_parent() as XRToolsPickable diff --git a/Stations/table.gd b/Stations/table.gd index eeea008..5168c27 100644 --- a/Stations/table.gd +++ b/Stations/table.gd @@ -8,13 +8,13 @@ extends SharedInventoryStation @export var eating_duration: float = 5.0 @export var money_sound: AudioStream -@onready var label_3d: Label3D = $Label3D -@onready var label_3d_time: Label3D = $Label3DTime -@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 +@export var label_3d: Label3D +@export var label_3d_time: Label3D +@export var snap_zones: Array[XRToolsSnapZone] +@export var progress_bar: ProgressBar3D +@export var audio_player: AudioStreamPlayer3D +@export var customers: Node3D +@export var player_detect_area_3d: Area3D const lbl_thinking: String = "..." const lbl_ordering: String = "!" @@ -66,16 +66,6 @@ func server_place_order() -> void: 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 func get_exposed_food_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 if not held_object: 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: items.append_array(plate.container.contained_items) continue @@ -119,10 +109,11 @@ func clearAllFood() -> void: if not held_object: 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) if plate: plate.container.clear() + plate.enabled = true plate.is_dirty = true # Side or other food item directly on table @@ -157,9 +148,6 @@ func _ready() -> void: Helper.check_reference(self, progress_bar, ProgressBar3D) 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() Helper.check_reference(self, audio_player, AudioStreamPlayer3D) # 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]) -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: SweetLogger.debug("Absorb item if correct, item: {0}", [_item]) if not _item: @@ -231,7 +205,7 @@ func _absorb_item_if_correct(_item: Node) -> void: return # 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]) 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 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") - (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) food_item.is_absorbed = true _update_state_from_orders() @@ -273,7 +247,7 @@ func _collect_money_from_food(): if not held_object: 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) if plate: for fi: FoodItem in plate.get_food_items(): @@ -359,19 +333,7 @@ func _state_end(oldState: TableState): GameManager.game_over() -## 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, - # 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: TableState.IDLE: progress_bar.set_bar_visible(false) diff --git a/Stations/table.tscn b/Stations/table.tscn index cb49806..6f3365e 100644 --- a/Stations/table.tscn +++ b/Stations/table.tscn @@ -33,9 +33,16 @@ visible = false [node name="SnapZone" parent="." index="5" unique_id=1315859105] 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") 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")]) pickup_sound = ExtResource("3_0s6ir") drop_sound = ExtResource("3_0s6ir") diff --git a/documentation/Notes.md b/documentation/Notes.md index 2d3cfde..a7c2ef5 100644 --- a/documentation/Notes.md +++ b/documentation/Notes.md @@ -1,4 +1,4 @@ -### Full game systems (hardest part) +### Scope - Full game systems (hardest part) - Star day modifiers - research unlock - Mess and cleaning (How it works in VR?) @@ -19,29 +19,18 @@ - Freshness - 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 - 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. - Table does not accept clinet that was already in player detect zone - -### 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 - +- A hamburger in a station in build mode stays but becomes kinematic when entering build mode again, from build mode. ### Multiplayer bugs -- despan flicker for clien broken - client keep autorityu of thrown object until lands - in build mode, snap zones off, stations disabled - players can steal from each others hands @@ -63,7 +52,7 @@ extre rolling pin mop bin -## Food dispensers +### Food dispensers burger buns potato @@ -74,7 +63,7 @@ pasta plates meat cheese -## Automation +### Automation belt inserter splitter @@ -83,12 +72,12 @@ blender dishwasher flinger / catapult -## Storage +### Storage shelf freezer frozen prep station equivilant -## Control +### Control Phone Napkins (no mess 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 - - - ## Modifiers - Disposable plates. no washing. much less money - ## Weird ideas - 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 - Reasearch is its own currency. - 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 \ No newline at end of file