Weapons/Projectiles/Grenade.cs

A Grenade projectile class. It counts up AliveTime, explodes after ExplosionFuse or on collision, applies direct impact damage to a hit player then simulates an explosion trace to apply area damage to nearby damage components, and destroys its GameObject.

NetworkingFile Access
using Sandbox.Events;
using System;

namespace KOTH;

public sealed class Grenade : Projectile, IGameEventHandler<ProjectileCollideEvent>
{
	[Property, Group("Explosion")] public float ExplosionFuse { get; set; } = 3f;
	[Property, Group("Explosion")] public GameObject ExplosionPrefab { get; set; }

	private TimeSince AliveTime = new();

	protected override void OnStart()
	{
		base.OnStart();

		AliveTime = 0;
	}

	protected override void OnUpdate()
	{
		base.OnUpdate();

		// explode on our own accord //
		if (AliveTime >= ExplosionFuse)
		{
			FProjectileCollision ProjectileCollision;
			SimulateExplode(out ProjectileCollision, WorldPosition);

			foreach (var DamageComponent in ProjectileCollision.TracedDamageComponents)
			{
				if (!DamageComponent.IsValid())
				{
					continue;
				}

				FDamageRequest DamageRequest = new()
				{
					TargetDamageComponent = DamageComponent,
					AttackerPlayerPawn = OwnerPlayerPawn,
					DamageOrigin = ProjectileCollision.HitLocation,
					TargetOrigin = DamageComponent.WorldPosition,
					BaseDamage = (BaseDamage * .33f).FloorToInt(),
					BaseKnockbackStrength = BaseKnockbackStrength,
					DamageType = EDamageType.Projectile,
					DamageFalloffType = EDamageFalloffType.Falloff,
					MaxDamageImpactDistance = ExplosionRadius,
				};

				if (DamageComponent.GameObject.GetComponent<PlayerPawn>() is { } PlayerPawn)
				{
					DamageRequest.TargetPlayerPawn = PlayerPawn;
					DamageRequest.TargetOrigin = PlayerPawn.CenterPosition;
				}

				Scene.Dispatch(new DamageRequestEvent(DamageRequest));
			}

			GameObject.Root.Destroy();
		}
	}

	public void OnGameEvent(ProjectileCollideEvent EventArgs)
	{
		var Collision = EventArgs.ProjectileCollision;

		// the guy we hit ////////////////////////////////////////////////////////
		var HitPlayerPawn = Collision.HitObject.Root.Components.Get<PlayerPawn>();
		if (!HitPlayerPawn.IsValid())
		{
			return;
		}
		
		FDamageRequest DirectDamageRequest = new()
		{
			TargetDamageComponent = HitPlayerPawn.DamageComponent,
			AttackerPlayerPawn = OwnerPlayerPawn,
			TargetPlayerPawn = HitPlayerPawn,
			DamageOrigin = Collision.HitLocation,
			TargetOrigin = HitPlayerPawn.CenterPosition,
			BaseDamage = BaseDamage,
			BaseKnockbackStrength = BaseKnockbackStrength,
			DirectImpact = true,
			DamageType = EDamageType.Projectile,
			DamageFalloffType = EDamageFalloffType.None,
			MaxDamageImpactDistance = ExplosionRadius,
		};
		Scene.Dispatch(new DamageRequestEvent(DirectDamageRequest));

		// explode on them //////////////////////
		FProjectileCollision ProjectileCollision;
		SimulateExplode(out ProjectileCollision, Collision.HitLocation);

		foreach (var DamageComponent in ProjectileCollision.TracedDamageComponents)
		{
			if (!DamageComponent.IsValid() || HitPlayerPawn.DamageComponent == DamageComponent)
			{
				continue;
			}

			FDamageRequest DamageRequest = new()
			{
				TargetDamageComponent = DamageComponent,
				AttackerPlayerPawn = OwnerPlayerPawn,
				DamageOrigin = ProjectileCollision.HitLocation,
				TargetOrigin = DamageComponent.WorldPosition,
				BaseDamage = (BaseDamage * .66f).FloorToInt(),
				BaseKnockbackStrength = BaseKnockbackStrength,
				DamageType = EDamageType.Projectile,
				DamageFalloffType = EDamageFalloffType.Falloff,
				MaxDamageImpactDistance = ExplosionRadius,
			};

			if (DamageComponent.GameObject.GetComponent<PlayerPawn>() is { } PlayerPawn)
			{
				DamageRequest.TargetPlayerPawn = PlayerPawn;
				DamageRequest.TargetOrigin = PlayerPawn.CenterPosition;
			}

			Scene.Dispatch(new DamageRequestEvent(DamageRequest));
		}

		GameObject.Root.Destroy();
	}
}