using Godot; namespace AceFieldNewHorizon.Scripts.Entities; public partial class Enemy : CharacterBody2D { public const string EnemyGroupName = "Enemy"; [Export] public float MoveSpeed = 150.0f; [Export] public float DetectionRadius = 300.0f; [Export] public float AttackRange = 50.0f; [Export] public int Damage = 10; [Export] public float AttackCooldown = 1.0f; [Export] public int MaxHealth = 100; [Export] public bool ShowDamageNumbers = true; private Player _player; private float _attackTimer = 0; private bool _isPlayerInRange = false; private Area2D _detectionArea; private int _currentHealth; private ProgressBar _healthBar; public int CurrentHealth { get => _currentHealth; private set { _currentHealth = Mathf.Clamp(value, 0, MaxHealth); UpdateHealthBar(); if (_currentHealth <= 0) { Die(); } } } public bool IsDead => _currentHealth <= 0; public override void _Ready() { _currentHealth = MaxHealth; // Create health bar _healthBar = new ProgressBar { MaxValue = MaxHealth, Value = _currentHealth, Size = new Vector2(40, 4), ShowPercentage = false, Visible = false }; var healthBarContainer = new Control(); healthBarContainer.AddChild(_healthBar); AddChild(healthBarContainer); healthBarContainer.Position = new Vector2(-20, -20); // Adjust position as needed // Create detection area _detectionArea = new Area2D(); var collisionShape = new CollisionShape2D(); var shape = new CircleShape2D(); shape.Radius = DetectionRadius; collisionShape.Shape = shape; _detectionArea.AddChild(collisionShape); AddChild(_detectionArea); // Connect signals _detectionArea.BodyEntered += OnBodyEnteredDetection; _detectionArea.BodyExited += OnBodyExitedDetection; AddToGroup(EnemyGroupName); } public override void _Process(double delta) { if (IsDead) return; if (_player != null && _isPlayerInRange) { // Face the player LookAt(_player.GlobalPosition); // Move towards player if not in attack range var direction = GlobalPosition.DirectionTo(_player.GlobalPosition); var distance = GlobalPosition.DistanceTo(_player.GlobalPosition); if (distance > AttackRange) { Velocity = direction * MoveSpeed; } else { Velocity = Vector2.Zero; TryAttackPlayer(delta); } MoveAndSlide(); } } public void TakeDamage(int damage, Vector2? hitPosition = null) { if (IsDead) return; CurrentHealth -= damage; // Show damage number (optional) if (ShowDamageNumbers) { var damageLabel = new Label { Text = $"-{damage}", Position = hitPosition ?? GlobalPosition, ZIndex = 1000 }; GetTree().CurrentScene.AddChild(damageLabel); // Animate and remove damage number var tween = CreateTween(); tween.TweenProperty(damageLabel, "position:y", damageLabel.Position.Y - 30, 0.5f); tween.TweenCallback(Callable.From(() => damageLabel.QueueFree())).SetDelay(0.5f); } // Visual feedback var originalModulate = Modulate; Modulate = new Color(1, 0.5f, 0.5f); // Flash red var tweenFlash = CreateTween(); tweenFlash.TweenProperty(this, "modulate", originalModulate, 0.2f); } private void UpdateHealthBar() { if (_healthBar != null) { _healthBar.Value = _currentHealth; _healthBar.Visible = _currentHealth < MaxHealth; // Only show when damaged } } private void Die() { // Play death animation/sound // You can add a death animation here // Disable collisions and hide SetProcess(false); SetPhysicsProcess(false); Hide(); // Queue free after a delay (for any death animation/sound to play) var timer = new Timer(); AddChild(timer); timer.Timeout += () => QueueFree(); timer.Start(0.5f); } private void TryAttackPlayer(double delta) { if (IsDead) return; _attackTimer += (float)delta; if (_attackTimer >= AttackCooldown) { _attackTimer = 0; // Here you can implement the attack logic // For example: _player.TakeDamage(Damage); GD.Print("Enemy attacks player!"); } } private void OnBodyEnteredDetection(Node2D body) { if (body is Player player) { _player = player; _isPlayerInRange = true; } } private void OnBodyExitedDetection(Node2D body) { if (body == _player) { _isPlayerInRange = false; Velocity = Vector2.Zero; } } }