Add transactional RV decor synchronization

This commit is contained in:
Alexander Sellite 2026-08-31 05:48:36 -04:00
parent ee992d9f0c
commit 6578d3e870
7 changed files with 663 additions and 48 deletions

View file

@ -47,6 +47,12 @@ func setup(
_on_local_space_changed
):
_network_home.local_space_changed.connect(_on_local_space_changed)
if not _network_home.local_manifest_rejected.is_connected(
_on_local_manifest_rejected
):
_network_home.local_manifest_rejected.connect(
_on_local_manifest_rejected
)
if not _home_state.changed.is_connected(_on_home_changed):
_home_state.changed.connect(_on_home_changed)
if not _toolbar.placement_mode_requested.is_connected(
@ -84,7 +90,13 @@ func _input(event: InputEvent) -> void:
and pointer_button.button_index == MOUSE_BUTTON_LEFT
and not pointer_button.pressed
):
_finish_drag()
# A gameplay menu can take ownership between the press and release. Do
# not let that release finalize decor behind the menu; preserve the
# original transform instead.
if _can_decorate():
_finish_drag()
else:
_cancel_drag()
get_viewport().set_input_as_handled()
@ -120,14 +132,17 @@ func _unhandled_input(event: InputEvent) -> void:
func _process(delta: float) -> void:
if _drag_instance_id.is_empty():
return
# Menus and other gameplay input handoffs must cancel an active drag before
# evaluating a held/released pointer. This prevents a same-frame TAB press
# followed by left-button release from committing a move behind the UI.
if not _can_decorate():
_cancel_drag()
return
if not Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
_finish_drag()
return
if not _drag_target_valid:
return
if not _can_decorate():
_cancel_drag()
return
var current: Dictionary = _home_world.get_decor_preview_transform(
_drag_owner_fingerprint, _drag_instance_id
)
@ -146,6 +161,9 @@ func _process(delta: float) -> void:
func _begin_drag(screen_position: Vector2) -> bool:
if _network_home.has_pending_local_decor_mutation():
_toolbar.report_status("waiting for the previous decor update")
return false
var owner_fingerprint: String = (
_network_home.get_local_home_owner_fingerprint()
)
@ -331,22 +349,34 @@ static func _placement_position(placement: Dictionary) -> Vector3:
func _place_carried_decor(decor_id: StringName) -> void:
if _network_home.has_pending_local_decor_mutation():
_toolbar.report_status("waiting for the previous decor update")
return
var owner_fingerprint: String = (
_network_home.get_local_home_owner_fingerprint()
)
var forward: Vector3 = -_player.global_basis.z
forward.y = 0.0
forward = forward.normalized()
# RV transitions keep the gameplay root axis-aligned so its transform can be
# synchronized independently from the presentation. Use the visual facing
# direction instead; otherwise carried decor is always placed along world -Z
# instead of in front of the character the player sees.
var forward: Vector3 = _player.get_facing_direction()
var global_position: Vector3 = _player.global_position + forward * 1.45
var local_position: Vector3 = _home_world.interior_global_to_local(
owner_fingerprint, global_position
)
var placement_yaw: float = atan2(-forward.x, -forward.z)
var instance_id: String = _network_home.place_local_decor(
decor_id, local_position, _player.global_rotation.y, _mode
decor_id, local_position, placement_yaw, _mode
)
if instance_id.is_empty():
_toolbar.report_status("make room in front of you to place this decor")
return
# A local host can reject its own optimistic manifest synchronously. Do not
# claim success after that rollback or leave the player looking for decor that
# was never accepted into the room.
if _home_state.get_placement(instance_id).is_empty():
_toolbar.report_status("decor placement was not accepted")
return
_select_instance(instance_id)
_toolbar.report_status("decor placed")
@ -366,6 +396,9 @@ func _select_instance(instance_id: String) -> void:
func _rotate_selected() -> void:
if not _can_decorate() or _selected_instance_id.is_empty():
return
if _network_home.has_pending_local_decor_mutation():
_toolbar.report_status("waiting for the previous decor update")
return
var placement: Dictionary = _home_state.get_placement(
_selected_instance_id
)
@ -392,6 +425,9 @@ func _rotate_selected() -> void:
func _store_selected() -> void:
if _selected_instance_id.is_empty():
return
if _network_home.has_pending_local_decor_mutation():
_toolbar.report_status("waiting for the previous decor update")
return
if _network_home.remove_local_decor(_selected_instance_id):
_clear_selection()
_toolbar.report_status("decor packed into storage")
@ -418,6 +454,12 @@ func _on_local_space_changed(
_refresh_toolbar()
func _on_local_manifest_rejected(message: String) -> void:
_clear_selection()
if _toolbar != null and _toolbar.visible:
_toolbar.report_status(message)
func _on_home_changed() -> void:
if (
not _selected_instance_id.is_empty()