extends Node2D @export var default_attack_time: float = 0.5 @export var player: Player = null @onready var trigger_area: Area2D = $TriggerArea @onready var trigger_collision: CollisionShape2D = $TriggerArea/TriggerCollision @onready var timer: Timer = $Timer @onready var attack_path: Path2D = $AttackPath @onready var path_follow_2d: PathFollow2D = $AttackPath/PathFollow2D @onready var attack_area: Area2D = $AttackPath/PathFollow2D/Sprite2D/AttackArea var base_damage: float = 5.0 var current_target: Node2D = null var current_progress: float = 0.0 var damaged_this_attack: Array = [] var is_attacking: bool = false func _ready() -> void: attack_path.visible = false func _process(delta: float) -> void: if timer.is_stopped() and current_progress == 0.0: timer.start() if current_progress > 0.95: reset_attack() if ( current_target and is_instance_valid(current_target) and not current_target.is_queued_for_deletion() ): track_target(current_target) is_attacking = true attack_path.visible = true if is_attacking: # Do attack animation current_progress += delta / default_attack_time current_progress = clampf(current_progress, 0.0, 1.0) path_follow_2d.progress_ratio = current_progress func reset_attack() -> void: current_target = null attack_path.visible = false current_progress = 0.0 damaged_this_attack = [] is_attacking = false position = Vector2.ZERO rotation = 0.0 func set_target(body: Node2D): current_target = body is_attacking = true func track_target(body: Node2D): var mid_distance = attack_path.curve.get_baked_length() / 2 var mid_point: Vector2 = attack_path.curve.sample_baked(mid_distance) var offset = body.global_position - to_global(mid_point) var desired_dir = (body.global_position - to_global(mid_point)).normalized() var start_point_global = attack_path.to_global(attack_path.curve.sample_baked(0)) var end_point_global = attack_path.to_global( attack_path.curve.sample_baked(attack_path.curve.get_baked_length()) ) var curve_dir = (start_point_global - end_point_global).normalized() var angle_diff = curve_dir.angle_to(desired_dir) if rotation == 0.0: rotation = curve_dir.angle_to(desired_dir) position += offset func _on_timer_timeout() -> void: if current_target: if trigger_area.has_overlapping_areas(): if current_target not in trigger_area.get_overlapping_bodies(): current_target = null return if trigger_area.has_overlapping_bodies(): for body in trigger_area.get_overlapping_bodies(): if body.is_in_group(GlobalConst.GROUP_ENEMY): set_target(body) func _on_attack_area_body_entered(body: Node2D) -> void: if not attack_path.visible: return if body in damaged_this_attack: return if body.is_in_group(GlobalConst.GROUP_ENEMY) and body.is_in_group(GlobalConst.GROUP_DAMAGEABLE): var crit_chance = player.player_stats.get_final("crit_chance", player.modifiers) var damage_dealt = base_damage var is_crit = randf() >= 1 - crit_chance if is_crit: damage_dealt *= player.player_stats.get_final("crit_multiplier", player.modifiers) body.take_damage(damage_dealt, is_crit) damaged_this_attack.append(body)