63 lines
2.4 KiB
GDScript
63 lines
2.4 KiB
GDScript
extends Node3D
|
|
|
|
## NS-16: composes ground pick + server authority; see `ground_pick.gd` /
|
|
## `position_authority_client.gd`. NS-18: interaction POST + radius glow preview (see child nodes).
|
|
## NS-23: bakes `NavigationRegion3D` on startup; boot snap vs nav goal from authority signal.
|
|
|
|
const MOVE_REJECT_MSG_SECONDS: float = 4.0
|
|
|
|
## Bump on each rejection so older one-shot timers do not clear a newer message.
|
|
var _move_reject_msg_token: int = 0
|
|
|
|
@onready var _camera: Camera3D = $World/Camera3D
|
|
@onready var _nav_region: NavigationRegion3D = $World/NavigationRegion3D
|
|
@onready var _player: CharacterBody3D = $Player
|
|
@onready var _ground_pick: Node3D = $GroundPick
|
|
@onready var _authority: Node = $PositionAuthorityClient
|
|
@onready var _radius_preview: Node3D = $World/InteractionMarkers
|
|
@onready var _move_reject_label: Label = $UICanvas/MoveRejectLabel
|
|
|
|
|
|
func _ready() -> void:
|
|
await get_tree().process_frame
|
|
# Default `bake_navigation_mesh()` bakes on a background thread; sync bake ensures the map
|
|
# exists before any `NavigationAgent3D` queries (see Godot navigation_using_navigationagents).
|
|
_nav_region.bake_navigation_mesh(false)
|
|
await get_tree().physics_frame
|
|
await get_tree().physics_frame
|
|
_ground_pick.set("fallback_camera", _camera)
|
|
_ground_pick.connect("target_chosen", Callable(self, "_on_target_chosen"))
|
|
_authority.connect(
|
|
"authoritative_position_received", Callable(self, "_on_authoritative_position")
|
|
)
|
|
_authority.connect("move_rejected", Callable(self, "_on_move_rejected"))
|
|
_authority.call("sync_from_server")
|
|
if _radius_preview.has_method("setup_player"):
|
|
_radius_preview.call("setup_player", _player)
|
|
|
|
|
|
func _on_target_chosen(world: Vector3) -> void:
|
|
_authority.call("submit_move_target", world)
|
|
|
|
|
|
func _on_authoritative_position(world: Vector3, apply_as_snap: bool) -> void:
|
|
if apply_as_snap:
|
|
_player.snap_to_server(world)
|
|
else:
|
|
_player.set_authoritative_nav_goal(world)
|
|
|
|
|
|
func _on_move_rejected(reason_code: String) -> void:
|
|
# Rejected POST never reached VERIFY_GET, so no new nav target was applied; do not clear an
|
|
# in-flight path from a prior successful move.
|
|
push_warning("Move rejected: %s" % reason_code)
|
|
_move_reject_msg_token += 1
|
|
var token: int = _move_reject_msg_token
|
|
_move_reject_label.text = "Move rejected: %s" % reason_code
|
|
get_tree().create_timer(MOVE_REJECT_MSG_SECONDS).timeout.connect(
|
|
func():
|
|
if token == _move_reject_msg_token:
|
|
_move_reject_label.text = "",
|
|
CONNECT_ONE_SHOT
|
|
)
|