A projectile class 'Needle' for a game. It spawns a timed destroy component, optionally creates a trail prefab each update, and handles projectile collisions to either heal teammates or damage enemies based on distance from the owner; it then stops physics and destroys itself.
using Sandbox.Events;
using Sandbox.Utility;
using System;
namespace KOTH;
public sealed class Needle : Projectile, IGameEventHandler<ProjectileCollideEvent>
{
[Property, Group("Healing")] public int MinHealing { get; set; } = 50;
[Property, Group("Healing")] public int MaxHealing { get; set; } = 100;
[Property, Group("Damage")] public int MinDamage { get; set; } = 40;
[Property, Group("Damage")] public int MaxDamage { get; set; } = 90;
[Property, Group("VFX")] public GameObject TrailPrefab { get; set; }
/////////////////////////////////////////////////////////////////////////////////////
//[Sync] private bool IsAttached { get; set; } = false;
//private GameObject AttachedGameObject { get; set; } = null;
//private Vector3 HitLocation;
//private Transform InitArmedWorldTransformAttachedObject;
/////////////////////////////////////////////////////////////////////////////////////
public TimedDestroyComponent DestroyComponent { get; private set; }
private readonly float MaxAliveTime = 30; // seconds
protected override void OnStart()
{
DestroyComponent = Components.Create<TimedDestroyComponent>();
DestroyComponent.Time = MaxAliveTime;
}
protected override void OnUpdate()
{
base.OnUpdate();
//if (IsAttached)
//{
// if (IsProxy)
// {
// return;
// }
// if (!AttachedGameObject.IsValid())
// {
// GameObject.Root.Destroy();
// return;
// }
// Vector3 GameObjectOffset = AttachedGameObject.WorldPosition - InitArmedWorldTransformAttachedObject.Position;
// GameObject.WorldPosition = HitLocation + GameObjectOffset;
//}
//else
{
if (!IsProxy && TimeSinceSpawn < LocalInvisableTime)
{
return;
}
if (TrailPrefab.IsValid())
{
TrailPrefab.Clone(WorldPosition);
}
}
}
const float MaxDistance = 1800f;
public void OnGameEvent(ProjectileCollideEvent EventArgs)
{
var Collision = EventArgs.ProjectileCollision;
var CollidePlayerPawn = Collision.HitObject.Root.Components.Get<PlayerPawn>();
if (CollidePlayerPawn.IsValid())
{
if (!OwnerPlayerPawn.IsValid())
{
Log.Warning($"owner pawn not valid on needle collision {this}");
return;
}
var Distance = OwnerPlayerPawn.WorldPosition.Distance(CollidePlayerPawn.WorldPosition);
float InterpFactor = Distance / MaxDistance;
if (CollidePlayerPawn.Team == OwnerPlayerPawn.Team)
{
var Healing = MathX.Lerp(MinHealing, MaxHealing, InterpFactor).CeilToInt();
FHealingRequest HealingRequest = new()
{
TargetDamageComponent = CollidePlayerPawn.DamageComponent,
TargetPlayerPawn = CollidePlayerPawn,
HealerPlayerPawn = OwnerPlayerPawn,
BaseHealing = Healing,
AllowOverheal = false,
HealingType = EHealingType.OneOff,
};
Scene.Dispatch(new HealingRequestEvent(HealingRequest));
}
else
{
var Damage = MathX.Lerp(MinDamage, MaxDamage, InterpFactor);
FDamageRequest DamageRequest = new()
{
TargetDamageComponent = CollidePlayerPawn.DamageComponent,
AttackerPlayerPawn = OwnerPlayerPawn,
TargetPlayerPawn = CollidePlayerPawn,
DamageOrigin = Collision.HitLocation,
TargetOrigin = CollidePlayerPawn.CenterPosition,
BaseDamage = MaxDamage,
BaseKnockbackStrength = BaseKnockbackStrength,
DirectImpact = true,
DamageType = EDamageType.Projectile,
DamageFalloffType = EDamageFalloffType.Rampup,
MaxDamageImpactDistance = ExplosionRadius,
};
Scene.Dispatch(new DamageRequestEvent(DamageRequest));
}
}
var Rigidbody = GameObject.Root.Components.Get<Rigidbody>();
if (!Rigidbody.IsValid())
{
Log.Warning($"cannot find rigidboy comp on needle {this}");
}
Rigidbody.Velocity = Vector3.Zero;
Rigidbody.MotionEnabled = false;
DestroyGameObject();
//IsAttached = true;
//AttachedGameObject = Collision.HitObject;
//InitArmedWorldTransformAttachedObject = Collision.HitObject.WorldTransform;
//HitLocation = Collision.HitLocation;
}
}