2026-03-16 16:24:32 +11:00
|
|
|
## player.gd
|
2026-03-20 20:14:21 +11:00
|
|
|
# Player character script. Handles input, pathfinding, and turn-based movement.
|
2026-03-16 16:24:32 +11:00
|
|
|
class_name Player
|
|
|
|
|
extends Character
|
|
|
|
|
|
|
|
|
|
# Movement speed in pixels per second.
|
|
|
|
|
const SPEED: float = 250.0
|
|
|
|
|
# Distance threshold for snapping to a tile (prevents jitter).
|
|
|
|
|
const ARRIVAL_THRESHOLD: float = 4.0
|
|
|
|
|
|
2026-03-20 20:14:21 +11:00
|
|
|
# The queued path (tile coordinates) the player will walk along.
|
2026-03-16 16:24:32 +11:00
|
|
|
var _path: Array[Vector2i] = []
|
2026-03-20 20:14:21 +11:00
|
|
|
# True when the TurnManager is waiting for the player to act.
|
|
|
|
|
var _awaiting_input: bool = false
|
|
|
|
|
# True while the player is animating toward _move_target.
|
|
|
|
|
var _is_moving: bool = false
|
|
|
|
|
# World position of the tile the player is currently stepping toward.
|
|
|
|
|
var _move_target: Vector2 = Vector2.ZERO
|
2026-03-16 16:24:32 +11:00
|
|
|
|
|
|
|
|
|
2026-03-20 20:14:21 +11:00
|
|
|
# Called by TurnManager at the start of the player's turn.
|
|
|
|
|
func take_turn() -> void:
|
|
|
|
|
if _path.is_empty():
|
|
|
|
|
_awaiting_input = true
|
|
|
|
|
else:
|
|
|
|
|
_start_step()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Handles mouse input for movement. Only active while awaiting input.
|
2026-03-16 16:24:32 +11:00
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
2026-03-20 20:14:21 +11:00
|
|
|
if not _awaiting_input:
|
|
|
|
|
return
|
2026-03-16 16:24:32 +11:00
|
|
|
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
|
|
|
|
|
var clicked_tile: Vector2i = LevelManager.world_to_tile(get_global_mouse_position())
|
|
|
|
|
var current_tile: Vector2i = LevelManager.world_to_tile(position)
|
|
|
|
|
_path = LevelManager.find_path(current_tile, clicked_tile)
|
|
|
|
|
# Drop the first entry — it's the tile the player is already on.
|
|
|
|
|
if not _path.is_empty():
|
|
|
|
|
_path.remove_at(0)
|
2026-03-20 20:14:21 +11:00
|
|
|
if not _path.is_empty():
|
|
|
|
|
_start_step()
|
2026-03-16 16:24:32 +11:00
|
|
|
|
2026-03-20 20:14:21 +11:00
|
|
|
|
|
|
|
|
# Begins movement toward the next tile in the queued path.
|
|
|
|
|
func _start_step() -> void:
|
|
|
|
|
_awaiting_input = false
|
|
|
|
|
_is_moving = true
|
|
|
|
|
var next_tile: Vector2i = _path.pop_front()
|
|
|
|
|
_move_target = LevelManager.tile_to_world(next_tile)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Moves the player toward _move_target each frame. Emits turn_finished on arrival.
|
2026-03-16 16:24:32 +11:00
|
|
|
func _physics_process(_delta: float) -> void:
|
2026-03-20 20:14:21 +11:00
|
|
|
if not _is_moving:
|
2026-03-16 16:24:32 +11:00
|
|
|
velocity = Vector2.ZERO
|
|
|
|
|
move_and_slide()
|
|
|
|
|
return
|
|
|
|
|
|
2026-03-20 20:14:21 +11:00
|
|
|
var to_target: Vector2 = _move_target - position
|
2026-03-16 16:24:32 +11:00
|
|
|
|
|
|
|
|
if to_target.length() < ARRIVAL_THRESHOLD:
|
2026-03-20 20:14:21 +11:00
|
|
|
position = _move_target
|
|
|
|
|
velocity = Vector2.ZERO
|
|
|
|
|
move_and_slide()
|
|
|
|
|
_is_moving = false
|
|
|
|
|
turn_finished.emit()
|
2026-03-16 16:24:32 +11:00
|
|
|
else:
|
|
|
|
|
velocity = to_target.normalized() * SPEED
|
|
|
|
|
move_and_slide()
|