extends Node3D ## NS-16: walkable ground ray pick. Emits world target for MoveCommand; camera wired from main. ## (No `class_name` so headless/CI can load the project before `.godot` import exists.) signal target_chosen(world: Vector3) ## Fallback when `Viewport.get_camera_3d()` is null (e.g. some editor setups). Set from `main.gd` ## in `_ready`. var fallback_camera: Camera3D func _input(event: InputEvent) -> void: if event is InputEventMouseButton: var mb := event as InputEventMouseButton if mb.button_index == MOUSE_BUTTON_LEFT and mb.pressed: _try_pick(get_viewport().get_mouse_position()) func _try_pick(screen_pos: Vector2) -> void: var cam: Camera3D = get_viewport().get_camera_3d() if cam == null: cam = fallback_camera if cam == null: return var origin: Vector3 = cam.project_ray_origin(screen_pos) var ray_dir: Vector3 = cam.project_ray_normal(screen_pos) var to: Vector3 = origin + ray_dir * 2000.0 var query := PhysicsRayQueryParameters3D.create(origin, to) query.collision_mask = 1 var space: PhysicsDirectSpaceState3D = get_world_3d().direct_space_state var hit: Dictionary = space.intersect_ray(query) if hit.is_empty(): return var collider: Variant = hit.get("collider") if not _collider_is_walkable(collider): return var hit_pos: Variant = hit.get("position") if hit_pos is Vector3: target_chosen.emit(hit_pos as Vector3) func _collider_is_walkable(collider: Variant) -> bool: var n: Node = collider as Node while n: if n.is_in_group("walkable"): return true n = n.get_parent() return false