2025-07-04 10:31:23 +02:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Linq;
|
2025-06-19 17:55:23 +02:00
|
|
|
|
using Cirno.Scripts.Controllers;
|
|
|
|
|
|
using Cirno.Scripts.Resources;
|
2026-03-01 19:14:34 +01:00
|
|
|
|
using Cirno.Scripts.Resources.Loot;
|
2025-06-19 17:55:23 +02:00
|
|
|
|
using Cirno.Scripts.Utils;
|
|
|
|
|
|
using Cirno.Scripts.Weapons;
|
|
|
|
|
|
using Godot;
|
|
|
|
|
|
using Godot.Collections;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Cirno.Scripts.Actors;
|
|
|
|
|
|
|
2025-07-04 10:31:23 +02:00
|
|
|
|
[Tool]
|
2025-06-19 17:55:23 +02:00
|
|
|
|
public partial class Destructible3D : StaticBody3D, IDestructible
|
|
|
|
|
|
{
|
2025-08-27 18:17:17 +02:00
|
|
|
|
[Export] public bool Indestructible { get; protected set; }
|
|
|
|
|
|
[Export] public float Health { get; protected set; } = 1f;
|
|
|
|
|
|
|
2025-06-19 17:55:23 +02:00
|
|
|
|
[Export] public PackedScene DebrisScene { get; set; }
|
2025-08-27 18:17:17 +02:00
|
|
|
|
|
2025-06-19 17:55:23 +02:00
|
|
|
|
[Export] public PackedScene ExplosionParticles { get; set; }
|
|
|
|
|
|
[Export] public BulletResource ExplosionData { get; set; }
|
2025-08-27 18:17:17 +02:00
|
|
|
|
|
2025-06-19 17:55:23 +02:00
|
|
|
|
[Export] public BulletOwner BulletGroup { get; set; } = BulletOwner.None;
|
|
|
|
|
|
[Export] public Array<DamageResistance> DamageResistances { get; set; } = [];
|
2025-08-27 18:17:17 +02:00
|
|
|
|
|
2025-06-19 17:55:23 +02:00
|
|
|
|
[ExportCategory("On Death Activation")]
|
|
|
|
|
|
[Export]
|
2025-08-27 18:17:17 +02:00
|
|
|
|
public ActivationType ActivationType { get; protected set; } = ActivationType.Toggle;
|
|
|
|
|
|
|
|
|
|
|
|
[Export] public Node Target { get; private set; }
|
|
|
|
|
|
[Export] public string TargetGroup { get; protected set; }
|
|
|
|
|
|
|
2026-03-01 19:14:34 +01:00
|
|
|
|
[ExportCategory("Loot Drops")]
|
|
|
|
|
|
[Export] public Array<LootDrop> LootDrops { get; set; } = [];
|
|
|
|
|
|
/// <summary>Radius of the circle in which dropped items are scattered uniformly.</summary>
|
|
|
|
|
|
[Export] public float LootScatterRadius { get; set; } = 1.0f;
|
|
|
|
|
|
/// <summary>Initial upward speed applied to each dropped item so it pops up before falling.</summary>
|
|
|
|
|
|
[Export] public float LootLaunchUpSpeed { get; set; } = 3.0f;
|
|
|
|
|
|
|
2025-06-19 17:55:23 +02:00
|
|
|
|
[Signal]
|
|
|
|
|
|
public delegate void ExplodedEventHandler();
|
2025-08-27 18:17:17 +02:00
|
|
|
|
|
2025-06-19 17:55:23 +02:00
|
|
|
|
private float _currentHealth = 0f;
|
|
|
|
|
|
private bool _isDestroyed = false;
|
2026-03-01 19:14:34 +01:00
|
|
|
|
private readonly RandomNumberGenerator _rng = new();
|
2025-08-27 18:17:17 +02:00
|
|
|
|
|
2025-06-19 17:55:23 +02:00
|
|
|
|
public override void _Ready()
|
|
|
|
|
|
{
|
|
|
|
|
|
_currentHealth = Health;
|
|
|
|
|
|
}
|
2025-08-27 18:17:17 +02:00
|
|
|
|
|
|
|
|
|
|
public virtual void _func_godot_apply_properties(Dictionary<string, Variant> props)
|
2025-07-04 10:31:23 +02:00
|
|
|
|
{
|
2025-08-27 18:17:17 +02:00
|
|
|
|
SetProperties(props);
|
2025-07-04 10:31:23 +02:00
|
|
|
|
}
|
2025-08-27 18:17:17 +02:00
|
|
|
|
|
|
|
|
|
|
protected void SetProperties(Dictionary<string, Variant> 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-19 17:55:23 +02:00
|
|
|
|
private void Explode()
|
2025-08-27 18:17:17 +02:00
|
|
|
|
{
|
|
|
|
|
|
//ApplyExplosionDamage();
|
|
|
|
|
|
EmitSignal(SignalName.Exploded, this, _currentHealth);
|
|
|
|
|
|
ActivateOnDeath();
|
|
|
|
|
|
CreateExplosion();
|
|
|
|
|
|
CreateParticles();
|
|
|
|
|
|
CreateDebris();
|
2026-03-01 19:14:34 +01:00
|
|
|
|
DropLoot();
|
2025-08-27 18:17:17 +02:00
|
|
|
|
|
|
|
|
|
|
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<Bullet3D>(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<Destructible3D>(DebrisScene);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void CreateParticles()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ExplosionParticles == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var particle = this.CreateSibling<GpuParticles3D>(ExplosionParticles);
|
|
|
|
|
|
if (particle == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
particle.Emitting = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 19:14:34 +01:00
|
|
|
|
private void DropLoot()
|
|
|
|
|
|
{
|
|
|
|
|
|
LootDropHelper.SpawnDrops(LootDrops, this, GlobalPosition, LootScatterRadius, LootLaunchUpSpeed, _rng);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-27 18:17:17 +02:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
2025-06-19 17:55:23 +02:00
|
|
|
|
}
|