using Cirno.Scripts.Actors; using Cirno.Scripts.Components; using Cirno.Scripts.Resources; using Cirno.Scripts.Utils; using Godot; using Godot.Collections; namespace Cirno.Scripts.AttackPatterns; [GlobalClass] [Tool] public partial class ShootingPattern3D : AttackPattern { [Export] public BulletResource BulletResource { get; set; } [Export] public Vector3 EmitterOffset { get; set; } = Vector3.Zero; [Export] public int bulletCount = 16; [Export] public float rotationSpeed = 0f; [Export] public bool UseParentRotationOffset { get; set; } = false; [Export] public float _rotationOffset = 0f; [Export] public float duration = 5f; [Export] public float spread = 360f; [Export] public float burstInterval = 0.5f; [ExportCategory("Burst")] [Export] public int ShotsPerBurst { get; private set; } = 100; [Export] public float BurstRate { get; private set; } = 0f; [ExportCategory("Other")] [Export] public bool _targetPlayer = false; [ExportCategory("Other")] [Export] public bool _predictPlayer = false; [ExportCategory("Overrides")] [ExportGroup("Override Owner")] [Export(PropertyHint.GroupEnable)] public bool OverrideOwner { get; private set; } = false; [Export] public BulletOwner Owner { get; private set; } = BulletOwner.None; [ExportGroup("Override Damage Type")] [Export(PropertyHint.GroupEnable)] public bool OverrideDamageType { get; private set; } = false; [Export] public DamageType DamageType { get; private set; } = DamageType.Neutral; [ExportGroup("Override Controllable")] [Export(PropertyHint.GroupEnable)] public bool OverrideControllable { get; private set; } = false; [Export] public bool Controllable { get; private set; } = false; [ExportCategory("Extra Modifiers")] [ExportGroup("Override Creation Modifier")] [Export(PropertyHint.GroupEnable)] public bool OverrideCreationModifier { get; private set; } = false; [Export] public BulletCreationModifier Modifier; [ExportGroup("Time Modifiers")] [Export] public Array TimeModifiers = []; protected virtual BulletInfo MakeBullet(Vector2 position, int count = 1, float spread = 0f, float rotationOffset = 0f) { var bullet = this.BulletResource.MakeBullet(position, count, spread, rotationOffset); if (OverrideOwner) { bullet.Owner = this.Owner; } if (OverrideDamageType) { bullet.DamageType = DamageType; } if (OverrideControllable) { bullet.Controllabe = Controllable; } if (OverrideCreationModifier) { bullet.Modifier = this.Modifier; } if (TimeModifiers.Count != 0) { bullet.TimeModifiers.AddRange(TimeModifiers); } return bullet; } public override IPatternMachine MakeMachine(Node parent) { return new SpiralPatternMachine(this, parent); } public class SpiralPatternMachine(ShootingPattern3D pattern, Node parent) : IPatternMachine { public Node Parent => parent; public IScriptHost3D ScriptHost { get; private set; } private double timer; private double burstTimer; //private double _burstRateTimer; private BulletSpawner3D spawner; private ShootStatus _state = ShootStatus.Idle; private int _burstBullets; public void Start() { ScriptHost = Parent as IScriptHost3D; timer = 0; _burstBullets = pattern.ShotsPerBurst; burstTimer = pattern.burstInterval; // start immediately spawner = parent.GetNode("BulletSpawner3D"); _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) { _state = ShootStatus.WaitingReload; } else { _state = ShootStatus.WaitingBurst; } } public void UpdatePattern(double delta) { switch (_state) { case ShootStatus.Idle: case ShootStatus.Done: return; case ShootStatus.Shooting: ShootingUpdate(delta); break; case ShootStatus.WaitingBurst: WaitingBurstUpdate(delta); break; case ShootStatus.WaitingReload: WaitingReloadUpdate(delta); break; } if (timer >= pattern.duration) { _state = ShootStatus.Done; } } private void Shoot() { float angleOffset = pattern._rotationOffset + (float)(pattern.rotationSpeed * timer); Vector2 direction = pattern.BulletResource.Direction; // Rotate with parent rotation if (pattern.UseParentRotationOffset) { direction = direction.Rotated(-ScriptHost.ParentObject.GlobalRotation.Y + Mathf.DegToRad(90)); } // TODO: Fix player aiming for 3D if (pattern._targetPlayer && GameController.Instance.PlayerPosition.HasValue) { if (pattern._predictPlayer && GameController.Instance.PlayerVelocity.HasValue) { var predictedDirection = MathFunctions.PredictInterceptPosition(ScriptHost.ParentObject.GlobalPosition.ToVector2(), GameController.Instance.PlayerPosition.Value.ToVector2(), GameController.Instance.PlayerVelocity.Value.ToVector2(), pattern.BulletResource.BulletSpeed); if (predictedDirection.HasValue) { direction = (predictedDirection.Value - ScriptHost.ParentObject.GlobalPosition.ToVector2()).Normalized(); } } else { direction = (GameController.Instance.PlayerPosition.Value.ToVector2() - ScriptHost.ParentObject.GlobalPosition.ToVector2()).Normalized(); } } var spawnPosition = ScriptHost.ParentObject.GlobalPosition + pattern.EmitterOffset; var bullet = pattern.MakeBullet(spawnPosition.ToVector2(), pattern.bulletCount, pattern.spread, angleOffset); bullet.Direction = direction; //spawner.SpawnBullet(MakeBullet(Boss.GlobalPosition, direction, angleOffset)); spawner.SpawnBullet(bullet, spawnPosition); } public bool IsComplete() { //return timer >= pattern.duration; return _state is ShootStatus.Done; } private enum ShootStatus { Idle, Shooting, WaitingBurst, WaitingReload, Done } } }