Weapons/SatchelChargeThrown.cs
public sealed class SatchelChargeThrown : Component, Component.IDamageable, IKillIcon, IInstigator
{
	[Sync]
	public PlayerData Instigator { get; set; }

	/// <summary>
	/// Damage radius
	/// </summary>
	[Property]
	public float Radius { get; set; }

	/// <summary>
	/// Max damage, respecting <see cref="DamageFalloff"/>
	/// </summary>
	[Property]
	public float MaxDamage { get; set; } = 125;

	/// <summary>
	/// A falloff curve to dictate damage inflicted to damageables in the area.
	/// </summary>
	[Property]
	public Curve DamageFalloff { get; set; } = new Curve( new Curve.Frame( 1.0f, 1.0f ), new Curve.Frame( 0.0f, 0.0f ) );

	[Property]
	Texture IKillIcon.DisplayIcon { get; set; }

	[Rpc.Broadcast]
	private void CreateEffects()
	{
		if ( Application.IsDedicatedServer ) return;

		Effects.SpawnExplosion( WorldPosition );
		Effects.SpawnScorch( WorldPosition, Vector3.Up, GameObject );
	}

	public void Explode( PlayerData instigator = default, GameObject weapon = default )
	{
		var actualInstigator = instigator ?? Instigator;
		Damage.Radius( WorldPosition, Radius, MaxDamage, [DamageTags.Explosion], GameObject, weapon.IsValid() ? weapon : GameObject, DamageFalloff, actualInstigator );

		CreateEffects();
		GameObject.Destroy();
	}

	void IDamageable.OnDamage( in DamageInfo damage )
	{
		DestroyGameObject();
	}
}