using Godot; using testing.interfaces; // Match your namespace public partial class Enemy : CharacterBody2D, IDamageable { [Export] public float MoveSpeed { get; set; } = 150.0f; [Export] public float MaxHealth { get; set; } = 30.0f; [Export] public float Damage { get; set; } = 50.0f; [Export] public float AttackRange = 40.0f; // Distance to trigger swing [Export] public float AttackCooldown = 1.2f; [Export] public float SteeringAcceleration { get; set; } = 12.0f; // Higher = tighter turns, lower = smoother turns [Export] public float SwingAngle = 150.0f; // The parent node holding the weapon private NavigationAgent2D _nav; [Export] public Node2D WeaponPivot; // The parent node holding the weapon [Export] public Area2D WeaponHitbox; private bool _isAttacking = false; private bool _canAttack = true; private Node2D _player; private Vector2 _lastPlayerPos; public override void _Ready() { _nav = GetNode("NavigationAgent2D"); _player = GetTree().GetFirstNodeInGroup("player") as CharacterBody2D; _lastPlayerPos = _player.GlobalPosition; Callable.From(ActorSetup).CallDeferred(); _nav.AvoidanceEnabled = true; // Connect the VelocityComputed signal to receive safe avoidance vectors _nav.VelocityComputed += OnVelocityComputed; if (WeaponHitbox != null) { // Keep hitbox turned off when not swinging WeaponHitbox.Monitoring = false; WeaponHitbox.BodyEntered += OnWeaponHitboxBodyEntered; } } private async void ActorSetup() { await ToSignal(GetTree(), SceneTree.SignalName.PhysicsFrame); SetMovementTarget(_player.Position); } public void SetMovementTarget(Vector2 movementTarget) { _nav.TargetPosition = movementTarget; } public override void _PhysicsProcess(double delta) { if (!GodotObject.IsInstanceValid(_player) || _isAttacking) return; // Calculate direction vector pointing from the enemy to the player // if (GlobalPosition.DistanceSquaredTo(_lastPlayerPos) > 256.0f) // { // _lastPlayerPos = _player.GlobalPosition; // _nav.TargetPosition = _lastPlayerPos; // } _nav.TargetPosition = _player.GlobalPosition; float distanceToPlayer = GlobalPosition.DistanceTo(_player.GlobalPosition); if (distanceToPlayer <= AttackRange) { if (_canAttack) { PerformSwingAttack(); return; } } if (_nav.IsNavigationFinished()) { _nav.Velocity = Vector2.Zero; Velocity = Velocity.MoveToward(Vector2.Zero, (float)delta * MoveSpeed * SteeringAcceleration); MoveAndSlide(); return; } Vector2 nextPathPosition = _nav.GetNextPathPosition(); Vector2 desiredDirection = GlobalPosition.DirectionTo(nextPathPosition); Vector2 targetVelocity = desiredDirection * MoveSpeed; WeaponPivot.Rotation = desiredDirection.Angle(); // 5. Pass target velocity to the NavigationAgent for processing if (_nav.AvoidanceEnabled) { // Triggers OnVelocityComputed signal on the next frame calculation _nav.Velocity = targetVelocity; } else { // Fallback smooth steering without avoidance ApplySmoothSteering(targetVelocity, (float)delta); } } private async void PerformSwingAttack(){ _isAttacking = true; _canAttack = false; Velocity = Vector2.Zero; // Stop moving while swinging // 1. Calculate the angle pointing toward the player float aimAngle = (_player.GlobalPosition - GlobalPosition).Angle(); // Define swing arc (-60 degrees back to +60 degrees forward) float startAngle = aimAngle - Mathf.DegToRad(SwingAngle/2); float endAngle = aimAngle + Mathf.DegToRad(SwingAngle/2); // 2. Set starting position for the swing Tween prepTween = CreateTween(); // 3. Create a Tween to animate the rotation prepTween.TweenProperty(WeaponPivot, "rotation", startAngle, 0.25f) .SetTrans(Tween.TransitionType.Quad) .SetEase(Tween.EaseType.Out); await ToSignal(prepTween, Tween.SignalName.Finished); WeaponPivot.Rotation = startAngle; WeaponHitbox.Monitoring = true; // Turn on collision check Tween swingTween = CreateTween(); // Rotate from startAngle to endAngle over 0.25 seconds with a smooth curve swingTween.TweenProperty(WeaponPivot, "rotation", endAngle, 0.25f) .SetTrans(Tween.TransitionType.Quad) .SetEase(Tween.EaseType.Out); // Wait for the swing movement to complete await ToSignal(swingTween, Tween.SignalName.Finished); // 4. Disable hitbox as soon as swing finishes WeaponHitbox.Monitoring = false; // 5. Retract weapon back to neutral position smoothly Tween resetTween = CreateTween(); resetTween.TweenProperty(WeaponPivot, "rotation", aimAngle, 0.15f); await ToSignal(resetTween, Tween.SignalName.Finished); _isAttacking = false; // Cooldown timer before the enemy can swing again await ToSignal(GetTree().CreateTimer(AttackCooldown), SceneTreeTimer.SignalName.Timeout); _canAttack = true; } private void OnWeaponHitboxBodyEntered(Node2D body) { // Deal damage when the weapon hits a damageable target if (body is IDamageable target) { target.TakeDamage(Damage); } } private void OnVelocityComputed(Vector2 safeVelocity) { Velocity = safeVelocity; MoveAndSlide(); } private void ApplySmoothSteering(Vector2 targetVelocity, float delta) { Velocity = Velocity.Lerp(targetVelocity, delta * SteeringAcceleration); MoveAndSlide(); } public void TakeDamage(float amount) { MaxHealth -= amount; GD.Print($"Enemy took {amount} damage! Health remaining: {MaxHealth}"); if (MaxHealth <= 0) { GD.Print("Enemy defeated!"); QueueFree(); // Removes the enemy from the game } } }