A Thing subclass representing a spiking head hazard. It handles lifetime, plays a sound shortly after spawn, configures collision with players, and applies damage/force to players it touches during the active phase.
using System;
using Sandbox;
public class SpikerHead : Thing
{
[Property] public SkinnedModelRenderer ModelRenderer { get; set; }
public float Damage { get; set; }
public float Lifetime { get; set; }
public Enemy Shooter { get; set; }
public float BaseZPos { get; set; }
public override Vector3 SpawnScale => new Vector3( 1.25f );
private List<Unit> _hitUnits = new();
private protected float _hitForce;
private protected float _depthBottom;
private protected float _depthTop;
private bool _hasPlayedSfx;
// todo: TimeScale can apply to this if shooter is frozen?
protected override void OnStart()
{
base.OnStart();
Lifetime = 1.7f;
if ( IsProxy )
return;
CollideWithTags.Add( "player" );
_hitForce = 240f;
_depthBottom = -150f;
_depthTop = -120f;
Damage = Utils.Select( Manager.Instance.Difficulty, 7f, 9f, 12f );
}
protected override void OnUpdate()
{
base.OnUpdate();
//Gizmo.Draw.Color = Color.White;
//Gizmo.Draw.LineSphere( WorldPosition.WithZ( 0f ), (Collider as SphereCollider).Radius * WorldScale.x );
if (!_hasPlayedSfx && TimeSinceSpawn > 0.4f)
{
Manager.Instance.PlaySfxNearby( "spike.thrust", Position2D, pitch: Game.Random.Float( 1.15f, 1.3f ), volume: 1f, maxDist: 350f );
_hasPlayedSfx = true;
}
if ( IsProxy )
return;
//Gizmo.Draw.Color = Color.White;
//Gizmo.Draw.Text( $"Touching: {Touching.Count}", new global::Transform( WorldPosition ) );
//var zPos = WorldPosition.z;
//if ( TimeSinceSpawn < Lifetime * 0.4f )
// zPos = Utils.Map( TimeSinceSpawn, 0f, Lifetime * 0.4f, _depthBottom, _depthTop, EasingType.QuadIn );
//else if ( TimeSinceSpawn > Lifetime * 0.6f )
// zPos = Utils.Map( TimeSinceSpawn, Lifetime * 0.6f, Lifetime, _depthTop, _depthBottom, EasingType.QuadOut );
//WorldPosition = WorldPosition.WithZ( zPos );
//if( TimeSinceSpawn > Lifetime * 0.4f && TimeSinceSpawn < Lifetime * 0.6f )
//{
// WorldScale = SpawnScale * Utils.MapReturn( TimeSinceSpawn, Lifetime * 0.4f, Lifetime * 0.6f, 1f, 1.2f );
//}
//else
//{
// WorldScale = SpawnScale;
//}
if ( TimeSinceSpawn > Lifetime )
GameObject.Destroy();
}
public override void Colliding( Thing other, float percent, float dt )
{
base.Colliding( other, percent, dt );
if ( TimeSinceSpawn < Lifetime * 0.35f || TimeSinceSpawn > Lifetime * 0.65f )
return;
if ( _hitUnits.Contains( other ) )
return;
if ( other is Player player )
{
if ( !player.IsDead )
{
var dir = (player.Position2D - Position2D).Normal;
var hitPos = player.Position2D - dir * player.Radius;
player.DamageRpc( Damage, damageType: DamageType.SpikerHead, hitPos, dir, upwardAmount: Game.Random.Float(0.5f, 1.5f), _hitForce, ragdollForce: _hitForce * 0.01f, Shooter, enemyType: EnemyType.Spiker );
_hitUnits.Add( player );
}
}
}
}