namespace Sandbox; /// /// Applies damage in a radius, with physics force, and optional occlusion /// [Category( "Game" ), Icon( "flare" ), EditorHandle( Icon = "💥" )] public sealed class RadiusDamage : Component { /// /// Should the world shield victims from damage? /// [Property] public float Radius { get; set; } = 512; /// /// How much physics force should be applied on explosion? /// [Property] public float PhysicsForceScale { get; set; } = 1; /// /// If enabled we'll apply damage once as soon as enabled /// [Property] public bool DamageOnEnabled { get; set; } = true; /// /// Should the world shield victims from damage? /// [Property] public bool Occlusion { get; set; } = true; /// /// The amount of damage inflicted /// [Property] public float DamageAmount { get; set; } = 100; /// /// Tags to apply to the damage /// [Property] public TagSet DamageTags { get; set; } = new TagSet(); /// /// Who should we credit with this attack? /// [Property] public GameObject Attacker { get; set; } protected override void OnEnabled() { base.OnEnabled(); if ( DamageOnEnabled ) { Apply(); } } protected override void DrawGizmos() { if ( !Gizmo.IsSelected ) return; Gizmo.Draw.LineSphere( new Sphere( 0, Radius ), 16 ); } /// /// Apply the damage now /// public void Apply() { var sphere = new Sphere( WorldPosition, Radius ); var dmg = new DamageInfo(); dmg.Weapon = GameObject; dmg.Damage = DamageAmount; dmg.Tags.Add( DamageTags ); dmg.Attacker = Attacker; ApplyDamage( sphere, dmg, PhysicsForceScale ); } public static void ApplyDamage( Sphere sphere, DamageInfo damage, float physicsForce = 1, GameObject ignore = null ) { var scene = Game.ActiveScene; if ( !scene.IsValid() ) return; var point = sphere.Center; var damageAmount = damage.Damage; var objectsInArea = scene.FindInPhysics( sphere ); var losTrace = scene.Trace.WithTag( "map" ).WithoutTags( "trigger", "gib", "debris", "player" ); foreach ( var rb in objectsInArea.SelectMany( x => x.GetComponents() ).Distinct() ) { if ( rb.IsProxy ) continue; if ( !rb.MotionEnabled ) continue; if ( ignore.IsValid() && ignore.IsDescendant( rb.GameObject ) ) continue; // If the object isn't in line of sight, fuck it off var tr = losTrace.Ray( point, rb.WorldPosition ).Run(); if ( tr.Hit && tr.GameObject.IsValid() ) { if ( !rb.GameObject.Root.IsDescendant( tr.GameObject ) ) continue; } var dir = (rb.WorldPosition - point).Normal; var distance = rb.WorldPosition.Distance( sphere.Center ); var forceMagnitude = Math.Clamp( 10000000000f / (distance * distance + 1), 0, 10000000000f ); forceMagnitude += physicsForce * (1 - (distance / sphere.Radius)); rb.ApplyForceAt( point, dir * forceMagnitude ); } foreach ( var damageable in objectsInArea.SelectMany( x => x.GetComponentsInParent().Distinct() ) ) { // no proxy checks needed, it's up to the OnDamage call to filter var target = damageable as Component; if ( ignore.IsValid() && ignore.IsDescendant( target.GameObject ) ) continue; // If the object isn't in line of sight, fuck it off var tr = losTrace.Ray( point, target.WorldPosition ).Run(); if ( tr.Hit && tr.GameObject.IsValid() ) { if ( !target.GameObject.Root.IsDescendant( tr.GameObject ) ) continue; } var distance = target.WorldPosition.Distance( point ); var distanceLinear = distance / sphere.Radius; distanceLinear = distanceLinear.Clamp( 0, 1 ); damage.Damage = damageAmount * distanceLinear; var direction = (target.WorldPosition - point).Normal; var force = direction * distance * 50f; damage.Origin = sphere.Center; damage.Position = tr.HitPosition; damageable.OnDamage( damage ); } damage.Damage = damageAmount; } }