Files
Station-0/scripts/enemies/anchor.gd
T
Station 0 Dev 342175f4c3 Initial commit — Station 0 vertical slice in progress
Includes full design documentation (ARCHITECTURE, DESIGN_NOTES, CONTENT_SPEC, PRODUCTION_NOTES)
and active Godot 4 / GDScript codebase. Core systems built: player, floor generation,
room system, enemy base, body part upgrades, run manager, save system, HUD, EventBus.
2026-03-04 12:33:05 -05:00

137 lines
4.4 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
extends CharacterBody2D
# ---------------------------------------------------------------------------
# ANCHOR — Structural integrity corruption
# Pathfinds to nearest wall, locks in. Fires slow homing projectiles.
# Does not move once anchored.
# ---------------------------------------------------------------------------
const ENEMY_PROJECTILE = preload("res://scenes/enemies/enemy_projectile.tscn")
enum State { SEEKING, ANCHORED }
var max_health: float = 40.0
var current_health: float = 40.0
var speed: float = 95.0
var contact_damage: float = 0.5
var contact_cooldown: float = 0.8
const SHOT_COOLDOWN := 2.2
# Room interior half-extents (960×540 room, 32px walls, 16px gap buffer)
const INNER_HALF_X := 432.0
const INNER_HALF_Y := 222.0
var _player: Node2D = null
var _state: State = State.SEEKING
var _target_wall: Vector2
var _shot_timer: float = 1.0 # initial fire delay
var _contact_timer: float = 0.0
var _flash_timer: float = 0.0
@onready var visual: Node2D = $Visual
func _ready() -> void:
add_to_group("enemies")
$ContactArea.body_entered.connect(_on_contact_entered)
_compute_wall_target()
# ---------------------------------------------------------------------------
# Find the nearest wall face and set it as the target
# ---------------------------------------------------------------------------
func _compute_wall_target() -> void:
var room_center: Vector2 = (get_parent() as Node2D).global_position
var lp: Vector2 = global_position - room_center
var d_left: float = lp.x - (-INNER_HALF_X)
var d_right: float = INNER_HALF_X - lp.x
var d_up: float = lp.y - (-INNER_HALF_Y)
var d_down: float = INNER_HALF_Y - lp.y
var md: float = minf(minf(d_left, d_right), minf(d_up, d_down))
if md == d_left:
_target_wall = room_center + Vector2(-INNER_HALF_X, lp.y)
elif md == d_right:
_target_wall = room_center + Vector2( INNER_HALF_X, lp.y)
elif md == d_up:
_target_wall = room_center + Vector2(lp.x, -INNER_HALF_Y)
else:
_target_wall = room_center + Vector2(lp.x, INNER_HALF_Y)
func _physics_process(delta: float) -> void:
_find_player()
_tick_contact(delta)
_tick_flash(delta)
match _state:
State.SEEKING:
_do_seek()
State.ANCHORED:
_do_anchored(delta)
move_and_slide()
func _find_player() -> void:
if _player == null or not is_instance_valid(_player):
_player = get_tree().get_first_node_in_group("player")
# ---------------------------------------------------------------------------
# Move toward the nearest wall, then lock in
# ---------------------------------------------------------------------------
func _do_seek() -> void:
var diff := _target_wall - global_position
if diff.length() < 12.0:
velocity = Vector2.ZERO
_state = State.ANCHORED
else:
velocity = diff.normalized() * speed
# ---------------------------------------------------------------------------
# Stationary — fire slow homing projectiles at the player
# ---------------------------------------------------------------------------
func _do_anchored(delta: float) -> void:
velocity = Vector2.ZERO
_shot_timer -= delta
if _shot_timer <= 0.0 and _player != null:
_fire_at_player()
_shot_timer = SHOT_COOLDOWN
func _fire_at_player() -> void:
var dir := (_player.global_position - global_position).normalized()
var proj = ENEMY_PROJECTILE.instantiate()
proj.position = (get_parent() as Node2D).to_local(global_position)
proj.direction = dir
proj.speed = 120.0
proj.damage = 1.0
proj.max_range = 720.0
proj.homing = true
proj.homing_target = _player
get_parent().call_deferred("add_child", proj)
func take_damage(amount: float) -> void:
current_health -= amount
_flash_timer = 0.1
if current_health <= 0.0:
_die()
func _die() -> void:
_spawn_drop()
queue_free()
func _spawn_drop() -> void:
var orb = preload("res://scenes/enemies/currency_orb.tscn").instantiate()
orb.position = (get_parent() as Node2D).to_local(global_position)
get_parent().call_deferred("add_child", orb)
func _on_contact_entered(body: Node) -> void:
if body.is_in_group("player") and _contact_timer == 0.0:
body.take_damage(contact_damage)
_contact_timer = contact_cooldown
func _tick_contact(delta: float) -> void:
_contact_timer = maxf(_contact_timer - delta, 0.0)
func _tick_flash(delta: float) -> void:
if _flash_timer > 0.0:
_flash_timer -= delta
visual.modulate = Color(2.5, 2.5, 2.5)
else:
visual.modulate = Color(1.0, 1.0, 1.0)