diff --git a/Net/network_manager.gd b/Net/network_manager.gd index c2e40e8..5f17160 100644 --- a/Net/network_manager.gd +++ b/Net/network_manager.gd @@ -165,6 +165,12 @@ func _spawn_item_from_data(data: Variant) -> Node: inst.set(key, data["props"][key]) if not owns_world(): _gate_station(inst) + # ...and again once the node is in the tree. Gating before _ready() is what + # keeps a station from ever ticking on a client, but a station's own + # _ready() runs afterwards and can undo it (table.gd re-arms its state + # machine with set_process(true)). The deferred pass runs after every + # _ready() in this frame and re-asserts the gate. + _gate_station.call_deferred(inst) return inst @@ -187,11 +193,17 @@ func _gate_station(node: Node) -> void: if child.is_in_group("station"): _gate_station(child) return + # Only report a gate that actually changed something: this runs a second time + # (deferred) for every spawned station, and on a re-gate that found nothing to + # do there is nothing worth logging. + var changed := node.is_processing() for zone in node.find_children("*", "XRToolsSnapZone", true, false): + changed = changed or zone.enabled or zone.is_processing() zone.enabled = false zone.set_process(false) node.set_process(false) - log_line("gated station (non-owner peer): %s" % node.name) + if changed: + log_line("gated station (non-owner peer): %s" % node.name) ## Gate every station already sitting in the scene tree, for peers that don't diff --git a/Stations/table.gd b/Stations/table.gd index ac2df11..a281805 100644 --- a/Stations/table.gd +++ b/Stations/table.gd @@ -132,7 +132,13 @@ func _ready() -> void: for snap_zone_node in snap_zones: snap_zone_node.has_picked_up.connect(_on_object_picked_up) snap_zone_node.has_dropped.connect(_on_object_dropped) - set_process(true) + # Never on a client: the state machine is server-authoritative and _state is + # synced down. NetworkManager._gate_station() already turned processing off, + # but a spawned station is gated BEFORE its _ready() runs, so an unconditional + # set_process(true) here would quietly re-arm the FSM on every client — and + # _state_end(EATING) re-enables the snap zones, which then fight the server + # for items sitting on the table. + set_process(NetworkManager.owns_world()) _set_state(TableState.EMPTY) @@ -258,8 +264,11 @@ func _collect_money_from_food(): func _set_snap_zones_enabled(value: bool) -> void: + # Enabling is the world owner's call only — a client's zones stay gated no + # matter what state its copy of the FSM thinks it is in. + var enabled := value and NetworkManager.owns_world() for zone in snap_zones: - zone.enabled = value + zone.enabled = enabled ## Server-only state transition: sets the new state's timer and assigns