extends CharacterBody2D const DAMAGE_POPUP_SCENE := preload("res://scenes/game/damage_popup.tscn") const IMPACT_EFFECT_SCENE := preload("res://scenes/game/impact_effect.tscn") const PLASMA_PROJECTILE_SCENE := preload("res://scenes/game/enemy_plasma_projectile.tscn") const WEAPON_AUDIO_SCRIPT := preload("res://scripts/game/weapon_audio.gd") @export var max_hull := 50.0 @export var activation_range := 760.0 @export var approach_speed := 62.0 @export var approach_acceleration := 85.0 @export var turn_speed := 1.8 @export var weapon_range := 680.0 @export var weapon_cooldown := 1.5 @export var weapon_damage := 8.0 var hull := 50.0 var _player: CharacterBody2D var _activated := false var _sprite: Sprite2D var _weapon_timer := 0.0 func _ready() -> void: hull = max_hull _sprite = $Sprite2D add_to_group("enemies") _weapon_timer = randf_range(0.2, weapon_cooldown) func configure(player: CharacterBody2D, ship_texture: Texture2D) -> void: _player = player _sprite = $Sprite2D _sprite.texture = ship_texture var largest_dimension := maxf(ship_texture.get_width(), ship_texture.get_height()) _sprite.scale = Vector2.ONE * (56.0 / largest_dimension) func _physics_process(delta: float) -> void: if not is_instance_valid(_player): velocity = velocity.move_toward(Vector2.ZERO, approach_acceleration * delta) move_and_slide() return var to_player := _player.global_position - global_position if to_player.length() <= activation_range: _activated = true if _activated and to_player.length_squared() > 1.0: var direction := to_player.normalized() var desired_rotation := direction.angle() + PI * 0.5 rotation = rotate_toward(rotation, desired_rotation, turn_speed * delta) velocity = velocity.move_toward(direction * approach_speed, approach_acceleration * delta) _weapon_timer = maxf(0.0, _weapon_timer - delta) if to_player.length() <= weapon_range and _weapon_timer <= 0.0: _fire_plasma(direction) _weapon_timer = weapon_cooldown else: velocity = velocity.move_toward(Vector2.ZERO, approach_acceleration * delta) move_and_slide() _check_player_collision() func _check_player_collision() -> void: for collision_index in get_slide_collision_count(): var collision := get_slide_collision(collision_index) if collision.get_collider() == _player and _player.has_method("register_enemy_collision"): _player.call("register_enemy_collision", global_position) func _fire_plasma(direction: Vector2) -> void: var projectile := PLASMA_PROJECTILE_SCENE.instantiate() as Area2D projectile.call("configure", global_position + direction * 28.0, direction, weapon_damage) get_parent().add_child(projectile) WEAPON_AUDIO_SCRIPT.play(get_parent(), global_position, ItemDefinition.WeaponKind.PLASMA, -10.0) func take_damage(amount: float) -> void: hull = maxf(0.0, hull - amount) _show_damage_popup(amount) if hull <= 0.0: _spawn_destruction_effect() queue_free() func _spawn_destruction_effect() -> void: var effect := IMPACT_EFFECT_SCENE.instantiate() as Node2D effect.global_position = global_position get_parent().add_child(effect) effect.call("configure", true, Color(1.0, 0.36, 0.18)) func _show_damage_popup(amount: float) -> void: var popup := DAMAGE_POPUP_SCENE.instantiate() as Label popup.text = "-%s DMG" % _format_damage(amount) popup.global_position = global_position + Vector2(randf_range(-16.0, 16.0), -28.0) get_parent().add_child(popup) func _format_damage(amount: float) -> String: if is_equal_approx(amount, roundf(amount)): return str(int(roundf(amount))) return "%.1f" % amount