class_name PlayerController extends Node const SPEED = 192.0 @export var dl_map: CombatMap signal combat_requested(attacker: DeployedUnit, defender: DeployedUnit) signal mouse_grid_changed(coords: Vector2i) signal camera_drag(delta: Vector2) var input_disabled := false var _selected_unit: DeployedUnit = null var _target_pos: Vector2 var _goal_pos: Vector2 var _moving := false var _left_pending := false var _drag_start := Vector2.ZERO var _dragging := false var _current_grid_coords := Vector2i(-99999, -99999) const DRAG_THRESHOLD := 8.0 func _ready() -> void: for deployed: DeployedUnit in get_tree().get_nodes_in_group("deployed_units"): deployed.unit_died.connect(_on_unit_died) get_tree().node_added.connect(_on_node_added) func _on_node_added(node: Node) -> void: if node is DeployedUnit and node.is_in_group("deployed_units"): if not node.unit_died.is_connected(_on_unit_died): node.unit_died.connect(_on_unit_died) func _on_unit_died(deployed: DeployedUnit) -> void: if _selected_unit == deployed: _selected_unit = null _moving = false func _process(_delta: float) -> void: if input_disabled: return var mouse_pos := get_viewport().get_canvas_transform().affine_inverse() * get_viewport().get_mouse_position() var coords := BattleMapHelper.world_to_coords(mouse_pos) if coords != _current_grid_coords: _current_grid_coords = coords mouse_grid_changed.emit(coords) func _unhandled_input(event: InputEvent) -> void: if input_disabled: return if event is InputEventMouseButton: match event.button_index: MOUSE_BUTTON_LEFT: if event.pressed: _left_pending = true _drag_start = event.position else: if _dragging: _dragging = false _left_pending = false Input.set_default_cursor_shape(Input.CURSOR_ARROW) get_viewport().set_input_as_handled() else: _left_pending = false _handle_left_click(event.position) MOUSE_BUTTON_MIDDLE: if event.pressed: _dragging = true _drag_start = event.position Input.set_default_cursor_shape(Input.CURSOR_DRAG) else: _dragging = false Input.set_default_cursor_shape(Input.CURSOR_ARROW) get_viewport().set_input_as_handled() elif event is InputEventMouseMotion: if _left_pending and not _dragging: if event.position.distance_to(_drag_start) >= DRAG_THRESHOLD: _dragging = true _left_pending = false Input.set_default_cursor_shape(Input.CURSOR_DRAG) if _dragging: var delta: Vector2 = _drag_start - event.position _drag_start = event.position camera_drag.emit(delta) get_viewport().set_input_as_handled() func _physics_process(delta: float) -> void: if not _selected_unit or not _selected_unit.is_alive(): return if _moving: var remaining := _target_pos - _selected_unit.position var step := SPEED * delta if remaining.length() <= step: _selected_unit.position = _target_pos _moving = false else: _selected_unit.position += remaining.normalized() * step return if _selected_unit.position != _goal_pos: var diff := _goal_pos - _selected_unit.position var dir: Vector2 if absf(diff.x) >= absf(diff.y): dir = Vector2(signf(diff.x), 0) else: dir = Vector2(0, signf(diff.y)) var next_pos := _selected_unit.position + dir * BattleMapHelper.TILE_SIZE var grid_coords := BattleMapHelper.world_to_coords(next_pos) var current_coords := BattleMapHelper.world_to_coords(_selected_unit.position) if not dl_map.is_tile_passable(current_coords, grid_coords): _goal_pos = _selected_unit.position return _target_pos = next_pos _moving = true func _handle_left_click(screen_pos: Vector2) -> void: var world_pos: Vector2 = get_viewport().get_canvas_transform().affine_inverse() * screen_pos var clicked_unit := _get_unit_at(world_pos) if clicked_unit: if _selected_unit and clicked_unit != _selected_unit and _selected_unit.is_alive() and clicked_unit.is_alive(): combat_requested.emit(_selected_unit, clicked_unit) else: _select_unit(clicked_unit) get_viewport().set_input_as_handled() elif _selected_unit: var snapped_pos := BattleMapHelper.snap_to_grid(world_pos) var grid_coords := BattleMapHelper.world_to_coords(world_pos) if not dl_map.is_tile_valid(grid_coords): return _goal_pos = snapped_pos get_viewport().set_input_as_handled() func _select_unit(deployed: DeployedUnit) -> void: if _selected_unit: _selected_unit.set_selected(false) _selected_unit = deployed _selected_unit.set_selected(true) _goal_pos = _selected_unit.position _target_pos = _selected_unit.position _moving = false func _get_unit_at(world_pos: Vector2) -> DeployedUnit: var snapped_coords := BattleMapHelper.snap_to_grid(world_pos) for deployed: DeployedUnit in get_tree().get_nodes_in_group("deployed_units"): if not deployed.is_alive(): continue var unit_snapped := BattleMapHelper.snap_to_grid(deployed.global_position) if unit_snapped == snapped_coords: return deployed return null