using System.Linq; using AceFieldNewHorizon.Scripts.Entities; using Godot; namespace AceFieldNewHorizon.Scripts.Tiles; public partial class TurretTile : BaseTile { [Export] public PackedScene BulletScene; [Export] public float RotationSpeed = 2.0f; // Radians per second [Export] public float AttackRange = 300.0f; [Export] public float AttackCooldown = 0.5f; [Export] public int Damage = 10; [Export] public float BulletSpeed = 400.0f; [Export] public NodePath BarrelTipPath; private Node2D _spriteBarrel; private Node2D _barrelTip; private float _attackTimer = 0; private bool _hasTarget = false; public override void _Ready() { base._Ready(); _spriteBarrel = GetNodeOrNull("Barrel"); _barrelTip = GetNodeOrNull(BarrelTipPath); if (_barrelTip == null && _spriteBarrel != null) { // If no barrel tip is specified, use the end of the barrel sprite _barrelTip = _spriteBarrel.GetNodeOrNull("BarrelTip"); } } public override void SetGhostMode(bool canPlace) { base.SetGhostMode(canPlace); if (_spriteBarrel != null) _spriteBarrel.Modulate = canPlace ? new Color(0, 1, 0, 0.5f) : new Color(1, 0, 0, 0.5f); } public override void FinalizePlacement() { base.FinalizePlacement(); if (_spriteBarrel != null) _spriteBarrel.Modulate = Colors.White; } public override void _Process(double delta) { if (!IsConstructed) return; // Update attack cooldown if (_attackTimer > 0) { _attackTimer -= (float)delta; } // Find the nearest enemy var enemies = GetTree().GetNodesInGroup(Enemy.EnemyGroupName) .OfType() .Where(e => e.GlobalPosition.DistanceTo(GlobalPosition) <= AttackRange) .OrderBy(e => e.GlobalPosition.DistanceTo(GlobalPosition)) .ToList(); if (enemies.Count > 0) { var nearestEnemy = enemies[0]; _hasTarget = true; // Calculate target angle var direction = (nearestEnemy.GlobalPosition - _spriteBarrel.GlobalPosition).Normalized(); var targetAngle = Mathf.Atan2(direction.Y, direction.X); // Smoothly rotate towards target _spriteBarrel.Rotation = Mathf.LerpAngle(_spriteBarrel.Rotation, targetAngle, (float)delta * RotationSpeed); // Check if we're facing the target and can attack if (Mathf.Abs(Mathf.Wrap(targetAngle - _spriteBarrel.Rotation, -Mathf.Pi, Mathf.Pi)) < 0.1f) TryAttack(nearestEnemy.GlobalPosition); } else { _hasTarget = false; } } private void TryAttack(Vector2 targetPosition) { if (_attackTimer <= 0 && BulletScene != null && _barrelTip != null) { // Create bullet instance var bullet = BulletScene.Instantiate(); // Calculate direction and rotation var direction = (targetPosition - _barrelTip.GlobalPosition).Normalized(); var bulletRotation = direction.Angle(); // Set bullet position and rotation GetTree().CurrentScene.AddChild(bullet); bullet.GlobalPosition = _barrelTip.GlobalPosition; bullet.Rotation = bulletRotation; // Use the calculated rotation // Initialize bullet with direction and damage bullet.Initialize( direction, bullet.GlobalPosition, bulletRotation, // Pass the calculated rotation 1 // Pass the turret's collision layer to ignore ); bullet.Damage = Damage; bullet.Speed = BulletSpeed; bullet.MaxDistance = AttackRange * 1.5f; // Bullets can travel slightly further than attack range // Reset attack cooldown _attackTimer = AttackCooldown; GD.Print("Turret attacking enemy!"); } } }