using Cirno.Scripts.Actors; using Cirno.Scripts.Components; using Cirno.Scripts.Enums; using Cirno.Scripts.Utils; using Godot; namespace Cirno.Scripts.AttackPatterns; [GlobalClass] [Tool] public partial class LaserPattern : ShootingPattern3D { [ExportGroup("Laser")][Export] public float SpawnDelay { get; set; } = 0.3f; // Delay before beam appears [ExportGroup("Laser")][Export] public float PreFireTime { get; set; } = 0.5f; // Time before laser becomes lethal [ExportGroup("Laser")][Export] public float LethalTime { get; set; } = 1.5f; // Time laser remains lethal [ExportGroup("Laser")][Export] public Color PreFireColor { get; set; } = new Color(1, 0, 0, 0.5f); // Thin red beam [ExportGroup("Laser")][Export] public Color LethalColor { get; set; } = Colors.Red; // Thicker beam protected override BulletInfo MakeBullet(Vector2 position, int count = 1, float spread = 0f, float rotationOffset = 0f) { var bf = base.MakeBullet(position, count, spread, rotationOffset); bf.IsLaser = true; bf.PreFireTime = PreFireTime; bf.LethalTime = LethalTime; bf.PreFireColor = PreFireColor; bf.LethalColor = LethalColor; bf.SpawnDelay = SpawnDelay; return bf; } public override IPatternMachine MakeMachine(Node parent) { return new LaserPatternMachine(this, parent); } public class LaserPatternMachine(LaserPattern pattern, Node parent) : IPatternMachine { public Node Parent => parent; public IScriptHost3D ScriptHost { get; private set; } private double _timer; private double _burstTimer; private BulletSpawner3D _spawner; private ShootStatus _state = ShootStatus.Idle; private int _burstBullets; private int _currentBurstOffset; public void Start() { ScriptHost = Parent as IScriptHost3D; _timer = 0; _burstBullets = pattern.ShotsPerBurst; _burstTimer = pattern.burstInterval; _spawner = parent.GetNode("BulletSpawner3D"); _state = pattern.Delay == 0 ? ShootStatus.Shooting : ShootStatus.Idle; } public void UpdatePattern(double delta) { switch (_state) { case ShootStatus.Idle: IdleUpdate(delta); break; case ShootStatus.Done: return; case ShootStatus.Shooting: ShootingUpdate(delta); break; case ShootStatus.WaitingBurst: WaitingBurstUpdate(delta); break; case ShootStatus.WaitingReload: WaitingReloadUpdate(delta); break; } if (pattern.duration > -1 && _timer >= pattern.duration) { _state = ShootStatus.Done; } } private void IdleUpdate(double delta) { _timer += delta; if (_timer >= pattern.Delay) { _state = ShootStatus.Shooting; } } private void WaitingBurstUpdate(double delta) { _timer += delta; _burstTimer += delta; if (_burstTimer >= pattern.burstInterval) { _state = ShootStatus.Shooting; } } private void WaitingReloadUpdate(double delta) { _timer += delta; _burstTimer += delta; if (_burstTimer >= pattern.BurstRate) { _burstBullets = pattern.ShotsPerBurst; _state = ShootStatus.Shooting; } } private void ShootingUpdate(double delta) { _timer += delta; _burstTimer = 0; Shoot(); _burstBullets--; if (_burstBullets <= 0) { if (pattern.LoopType == LoopType.PlayOnce) { _state = ShootStatus.Done; return; } _state = ShootStatus.WaitingReload; _currentBurstOffset++; } else { _state = ShootStatus.WaitingBurst; } } private void Shoot() { if (pattern.BulletResource == null) { GD.PushError("LaserPattern: BulletResource is null! Cannot spawn laser."); _state = ShootStatus.Done; return; } float angleOffset = pattern._rotationOffset + (float)(pattern.rotationSpeed * _timer) + (float)pattern.BurstRotationSpeed * _currentBurstOffset; Vector2 direction = pattern.BulletResource.Direction; // Rotate with parent rotation if (pattern.UseParentRotationOffset) { direction = direction.Rotated(-_spawner.GlobalRotation.Y + Mathf.DegToRad(90)); } // Handle player targeting for 3D if (pattern._targetPlayer && GameController.Instance.PlayerPosition.HasValue) { if (pattern._predictPlayer && GameController.Instance.PlayerVelocity.HasValue) { var predictedDirection = MathFunctions.PredictInterceptPosition( _spawner.GlobalPosition.ToVector2(), GameController.Instance.PlayerPosition.Value.ToVector2(), GameController.Instance.PlayerVelocity.Value.ToVector2(), pattern.BulletResource.BulletSpeed); if (predictedDirection.HasValue) { direction = (predictedDirection.Value - _spawner.GlobalPosition.ToVector2()).Normalized(); } } else { direction = (GameController.Instance.PlayerPosition.Value.ToVector2() - _spawner.GlobalPosition.ToVector2()).Normalized(); } } var spawnPosition = _spawner.GlobalPosition + pattern.EmitterOffset; // Create laser bullet with laser-specific properties var bullet = pattern.MakeBullet( spawnPosition.ToVector2(), pattern.bulletCount, pattern.spread, angleOffset); bullet.Direction = direction; _spawner.SpawnBullet(bullet, spawnPosition); } public bool IsComplete() { if (!pattern.WaitForCompletion) return _state is ShootStatus.Done; if (_state is not ShootStatus.Done) return false; if (pattern.duration > -1) { return (_timer >= pattern.duration); } return true; } private enum ShootStatus { Idle, Shooting, WaitingBurst, WaitingReload, Done } } }