extends Control const DOT_RADIUS := 3.0 const SCAN_RING_RADIUS := 48.0 var player: CharacterBody2D var targets: Array[Node2D] = [] var _elapsed := 0.0 var _previous_player_position := Vector2.ZERO var _player_position := Vector2.ZERO var _positions: Dictionary = {} func _ready() -> void: mouse_filter = Control.MOUSE_FILTER_IGNORE process_physics_priority = 10 _player_position = player.global_position _previous_player_position = _player_position func _process(delta: float) -> void: _elapsed += delta queue_redraw() func _physics_process(_delta: float) -> void: if not is_instance_valid(player): return _previous_player_position = _player_position _player_position = player.global_position if _previous_player_position.distance_to(_player_position) > 600.0: _previous_player_position = _player_position targets.clear() var next_positions: Dictionary = {} for group in ["enemies", "exploration_pois", "scanner_planets"]: for target in get_tree().get_nodes_in_group(group): if player.get_parent().is_ancestor_of(target) and not target.is_queued_for_deletion() and is_target_in_range(target): if group == "scanner_planets" and (not bool(target.get("is_protected")) or not bool(player.get_parent().get("system_cleared"))): continue targets.append(target) var current: Vector2 = target.global_position var previous: Vector2 = _positions.get(target.get_instance_id(), [current, current])[1] if previous.distance_to(current) > 600.0: previous = current next_positions[target.get_instance_id()] = [previous, current] _positions = next_positions func get_scan_range() -> float: var transform := get_viewport().get_canvas_transform() var half_screen := get_viewport_rect().size * 0.5 var visible_radius := Vector2(half_screen.x / transform.x.length(), half_screen.y / transform.y.length()).length() return maxf(float(player.get("base_scanner_range")), visible_radius + 150.0) + float(player.get("scanner_range")) - float(player.get("base_scanner_range")) func is_target_in_range(target: Node2D) -> bool: return player.global_position.distance_to(target.global_position) <= get_scan_range() func _draw() -> void: if not is_instance_valid(player) or bool(player.get("_is_destroyed")) or bool(player.get("is_docked")): return var canvas := get_viewport().get_canvas_transform() var to_local := get_global_transform().affine_inverse() # Match the rendered ship and targets, rather than their discrete physics positions. var fraction := Engine.get_physics_interpolation_fraction() var origin: Vector2 = to_local * (canvas * _previous_player_position.lerp(_player_position, fraction)) var radius := SCAN_RING_RADIUS * canvas.x.length() var pulse := (sin(_elapsed * TAU * 0.9) + 1.0) * 0.5 for target in targets: if not is_instance_valid(target) or target.is_queued_for_deletion(): continue var color := Color("ff5555") if target.is_in_group("enemies"): if float(target.get("hull")) <= 0: continue elif target.is_in_group("scanner_planets"): color = Color("b68aff") else: if bool(target.get("_claimed")): continue match str(target.get("kind")): "asteroid": color = Color("a8adb5") "wreck": color = Color("b98050") "ruin": color = Color("6be58b") _: continue var positions: Array = _positions[target.get_instance_id()] var target_position: Vector2 = positions[0].lerp(positions[1], fraction) var offset: Vector2 = to_local * (canvas * target_position) - origin if offset.length_squared() < 0.01: continue var point := origin + offset.normalized() * radius draw_circle(point, DOT_RADIUS + 3.0 + pulse * 2.0, Color(color, 0.08 + pulse * 0.12), true, -1.0, true) draw_circle(point, DOT_RADIUS, Color(color, 0.55 + pulse * 0.45), true, -1.0, true)