Fix StationMovement in Multiplayer, still works singleplayer

This commit is contained in:
JonShard
2026-08-10 13:02:19 +02:00
parent dbaad35e50
commit bf31f85b02
5 changed files with 144 additions and 42 deletions
+95 -17
View File
@@ -7,11 +7,15 @@ extends Node
var move_handle_rigid: RigidBody3D
var original_collision_layer: int = 0
var original_y_pos: float = 0.0
var original_handle_y_pos: float = 0.0
var original_station_transform: Transform3D = Transform3D.IDENTITY
@export var moving: bool = false
var ghost_materials: Array[StandardMaterial3D] = []
# Ghost update throttling (clients send unreliable RPCs to server at this rate)
var ghost_send_interval: float = 0.1 # seconds (10 Hz)
var _ghost_send_accum: float = 0.0
const GHOST_COLOR_VALID: Color = Color(0, 1, 0, 0.35)
const GHOST_COLOR_INVALID: Color = Color(1, 0, 0, 0.35)
@@ -42,7 +46,7 @@ func _ready() -> void:
move_handle_rigid = move_handle as RigidBody3D
original_collision_layer = station.collision_layer
original_y_pos = move_handle.transform.origin.y
original_handle_y_pos = move_handle.transform.origin.y
original_station_transform = station.global_transform
move_handle.picked_up.connect(_handle_pickup)
move_handle.dropped.connect(_handle_drop)
@@ -93,16 +97,21 @@ func _handle_pickup(_by: Node) -> void:
if move_handle.get_picked_up_by() is XRToolsSnapZone:
print("StationMovement handle pickup by snap zone, dropping and resetting position")
move_handle.drop()
move_handle.global_position = station.global_position + Vector3(0, original_y_pos, 0)
move_handle.global_position = station.global_position + Vector3(0, original_handle_y_pos, 0)
move_handle.rotation = station.rotation
return
moving = true
move_ghost.visible = true
station.visible = false
station.collision_layer = 0
original_station_transform = station.global_transform
move_ghost.global_transform = original_station_transform
if NetworkManager.is_online():
# Ask the server to update visibility (reliable)
server_update_visibility.rpc_id(1, true, false)
else:
# If not connected to server (single-player or test), apply locally
move_ghost.visible = true
station.visible = false
func _handle_drop(_by: Node) -> void:
@@ -111,24 +120,43 @@ func _handle_drop(_by: Node) -> void:
move_ghost.visible = false
station.visible = true
station.collision_layer = original_collision_layer
if _is_move_position_valid():
station.global_transform = move_ghost.global_transform
else:
station.global_transform = original_station_transform
move_handle.global_position = station.global_position + Vector3(0, original_y_pos, 0)
move_handle.rotation = station.rotation
var is_valid: bool = _is_move_position_valid()
if NetworkManager.is_online():
server_handle_drop.rpc_id(1, move_ghost.global_transform, is_valid)
else:
# If not connected to server (single-player or test), apply locally only when valid
if is_valid:
station.global_transform = move_ghost.global_transform
move_handle.global_transform = move_ghost.global_transform.translated(Vector3(0, original_handle_y_pos, 0))
print("StationMovement handle drop (local apply), station authority: ", station.get_multiplayer_authority(), " move_handle authority: ", move_handle.get_multiplayer_authority())
else:
station.global_transform = original_station_transform
move_handle.global_transform = original_station_transform.translated(Vector3(0, original_handle_y_pos, 0))
print("StationMovement handle drop (local reject), restoring original position")
func _process(_delta: float) -> void:
if not moving:
return
# Update the ghost locally for instant feedback and send throttled unreliable RPCs to the server
move_ghost.visible = true
station.visible = false
move_ghost.global_transform = Helper.get_snapped_transform(move_handle)
move_ghost.global_transform.origin.y = original_station_transform.origin.y
if _is_move_position_valid():
_set_ghost_color(GHOST_COLOR_VALID)
else:
_set_ghost_color(GHOST_COLOR_INVALID)
var ghost_color = GHOST_COLOR_VALID if _is_move_position_valid() else GHOST_COLOR_INVALID
_set_ghost_color(ghost_color)
# Throttle and send ghost transform updates to server so other clients see the preview
_ghost_send_accum += _delta
if _ghost_send_accum >= ghost_send_interval:
_ghost_send_accum = 0.0
# Unreliable RPC to the server to update the server-side ghost; server will replicate to clients
# Use rpc_unreliable_id to avoid blocking traffic
if NetworkManager.is_online():
server_update_move_ghost_transform.rpc_id(1, move_ghost.global_transform, ghost_color)
func _is_move_position_valid() -> bool:
@@ -146,9 +174,59 @@ func _is_move_position_valid() -> bool:
return false
for area in move_ghost.get_overlapping_areas():
print("Overlapping area: ", area)
#print("Overlapping area: ", area)
if area == move_ghost:
continue
return false
return true
@rpc("any_peer", "call_remote", "reliable")
func server_handle_drop(new_transform: Transform3D, client_thinks_valid: bool):
if not multiplayer.is_server():
return
if not (client_thinks_valid):
# Reset state for everyone, including server
var original_trans = original_station_transform # Make a copy to avoid server_update_station_transform setting it before move handle uses it
server_update_station_transform.rpc(original_trans)
server_update_move_ghost_transform.rpc(original_trans)
server_update_handle_transform.rpc(original_trans.translated(Vector3(0, original_handle_y_pos, 0)))
server_update_visibility.rpc(false, true)
print("StationMovement server_handle_drop: transform rejected (invalid)")
return
server_update_station_transform.rpc(new_transform)
server_update_move_ghost_transform.rpc(new_transform)
server_update_handle_transform.rpc(new_transform.translated(Vector3(0, original_handle_y_pos, 0)))
server_update_visibility.rpc(false, true)
print("StationMovement server_handle_drop: transform applied")
@rpc("any_peer", "call_local", "reliable") # Server updates clients
func server_update_station_transform(new_transform: Transform3D) -> void:
print("StationMovement server_update_station_transform")
station.global_transform = new_transform
original_station_transform = new_transform
@rpc("any_peer", "call_local", "reliable") # Server updates clients
func server_update_handle_transform(new_transform: Transform3D) -> void:
print("StationMovement server_update_handle_transform")
move_handle.global_transform = new_transform
@rpc("any_peer", "call_local", "reliable")
func server_update_visibility(ghost_visible: bool, station_visible: bool) -> void:
print("StationMovement server_update_visibility")
move_ghost.visible = ghost_visible
station.visible = station_visible
@rpc("any_peer", "call_local", "unreliable")
func server_update_move_ghost_transform(new_transform: Transform3D, new_color: Color = Color.YELLOW) -> void:
print("StationMovement server_update_move_ghost_transform")
move_ghost.global_transform = new_transform
_set_ghost_color(new_color)