using System; using System.Linq; using Cirno.Scripts.Controllers; using Cirno.Scripts.Resources; using Cirno.Scripts.Resources.Loot; using Cirno.Scripts.Utils; using Cirno.Scripts.Weapons; using Godot; using Godot.Collections; namespace Cirno.Scripts.Actors; [Tool] public partial class Destructible3D : StaticBody3D, IDestructible { [Export] public bool Indestructible { get; protected set; } [Export] public float Health { get; protected set; } = 1f; [Export] public PackedScene DebrisScene { get; set; } [Export] public PackedScene ExplosionParticles { get; set; } [Export] public BulletResource ExplosionData { get; set; } [Export] public BulletOwner BulletGroup { get; set; } = BulletOwner.None; [Export] public Array DamageResistances { get; set; } = []; [ExportCategory("On Death Activation")] [Export] public ActivationType ActivationType { get; protected set; } = ActivationType.Toggle; [Export] public Node Target { get; private set; } [Export] public string TargetGroup { get; protected set; } [ExportCategory("Loot Drops")] [Export] public Array LootDrops { get; set; } = []; /// Radius of the circle in which dropped items are scattered uniformly. [Export] public float LootScatterRadius { get; set; } = 1.0f; /// Initial upward speed applied to each dropped item so it pops up before falling. [Export] public float LootLaunchUpSpeed { get; set; } = 3.0f; [Signal] public delegate void ExplodedEventHandler(); private float _currentHealth = 0f; private bool _isDestroyed = false; private readonly RandomNumberGenerator _rng = new(); public override void _Ready() { _currentHealth = Health; } public virtual void _func_godot_apply_properties(Dictionary props) { SetProperties(props); } protected void SetProperties(Dictionary props) { if (props.TryGetValue("target", out var target)) { TargetGroup = target.AsString(); } if (props.TryGetValue("activationtype", out var type)) { var stringType = type.AsString(); var t = Enum.TryParse(stringType, true, out ActivationType activationType); if (t) { ActivationType = activationType; } } if (props.TryGetValue("indestructible", out var indestructible)) { var ind = indestructible.AsBool(); Indestructible = ind; } if (props.TryGetValue("health", out var health)) { var h = health.AsSingle(); Health = h; } } private void Explode() { //ApplyExplosionDamage(); EmitSignal(SignalName.Exploded, this, _currentHealth); ActivateOnDeath(); CreateExplosion(); CreateParticles(); CreateDebris(); DropLoot(); QueueFree(); //GameManager.Instance.RebakeNavigation(); //GameManager.Instance.RecalculateTilemap(this.GlobalPosition); } private void ActivateOnDeath() { if (Target is IActivable node) { node.Activate(ActivationType); } if (!string.IsNullOrWhiteSpace(TargetGroup)) { ActivationHelper.UseTargets(this, TargetGroup, ActivationType); } } private void CreateExplosion() { if (ExplosionData == null) return; var explosion = PoolingManager.Instance.SpawnBullet(ExplosionData); explosion.GlobalPosition = this.GlobalPosition; explosion.Speed = 0; explosion.Initialize(ExplosionData.MakeBullet(new Vector2(this.GlobalPosition.X, this.GlobalPosition.Y))); } private void CreateDebris() { if (DebrisScene == null) return; this.CreateSibling(DebrisScene); } private void CreateParticles() { if (ExplosionParticles == null) { return; } var particle = this.CreateSibling(ExplosionParticles); if (particle == null) return; particle.Emitting = true; } private void DropLoot() { LootDropHelper.SpawnDrops(LootDrops, this, GlobalPosition, LootScatterRadius, LootLaunchUpSpeed, _rng); } public void Hit(float damage, DamageType damageType = DamageType.Neutral) { if (_isDestroyed || Indestructible) return; var dmg = DamageResistances.Aggregate(damage, (current, resistance) => current * resistance.CalculateDamage(current, damageType)); _currentHealth -= dmg; if (_currentHealth > 0) return; _isDestroyed = true; Explode(); } public bool IsDestroyed() { return _isDestroyed; } }