A Thing subclass representing a thrown spear. It handles initialization, sizing based on damage, movement with wind, lifetime expiry, collisions with Enemy and Obstacle objects, applying damage and spawning impact particles, and then removal.
using System;
using Sandbox;
public class Spear : Thing
{
[Property] public ModelRenderer Model { get; set; }
public Player Shooter { get; set; }
public float BaseZPos { get; set; }
private List<Thing> _hitThings;
public float Damage { get; set; }
public float DamagePerPierce { get; set; }
public float Lifetime { get; set; }
private bool _sizeDirty;
protected override void OnStart()
{
base.OnStart();
Radius = 5f;
if ( IsProxy )
return;
CollideWithTags.Add( "enemy" );
CollideWithTags.Add( "obstacle" );
_hitThings = new();
_sizeDirty = true;
}
public void Init()
{
}
void DetermineSize()
{
var damage = Damage;
var scale = damage < 30f
? Utils.Map( damage, 0f, 30f, 0.9f, 1f, EasingType.QuadOut )
: Utils.Map( damage, 30f, 150f, 1f, 1.4f, EasingType.QuadIn );
Radius = 5f * scale;
WorldScale = new Vector3( scale );
//_pfakeshadow.Set("Size", 9f * Scale);
_sizeDirty = false;
}
protected override void OnUpdate()
{
base.OnUpdate();
if ( IsProxy )
return;
//Gizmo.Draw.Color = Color.White;
//Gizmo.Draw.Text( $"\n\n\n{Stats[BulletStat.NumPiercing]}", new global::Transform( WorldPosition ) );
if ( _sizeDirty )
DetermineSize();
var zPos = Utils.Map( TimeSinceSpawn, 0f, Lifetime, BaseZPos, 0f, EasingType.QuadIn );
if ( Manager.Instance.IsWindActive )
Velocity += Manager.Instance.GlobalWindForce * 0.75f * Time.Delta;
WorldPosition = (WorldPosition + (Vector3)Velocity * Time.Delta).WithZ( zPos );
if ( TimeSinceSpawn > Lifetime )
{
var scaleMultiplier = Utils.Map( Damage, 1f, 5f, 0.5f, 1f, EasingType.Linear ) * Utils.Map( Damage, 5f, 30f, 1f, 1.5f, EasingType.Linear );
Manager.Instance.SpawnBulletImpactParticlesRpc( WorldPosition.WithZ(10f), Vector3.Up, Color.White, scaleMultiplier );
Remove();
return;
}
}
public override void Colliding( Thing other, float percent, float dt )
{
base.Colliding( other, percent, dt );
if ( !Shooter.IsValid() || _hitThings.Contains( other ) )
return;
if ( other is Enemy enemy )
{
//if ( enemy.IsDying || (enemy.IsSpawning && enemy.SpawnProgress < 0.7f) )
if ( enemy.IsSpawning && enemy.SpawnProgress < 0.7f )
return;
Vector2 dir = Velocity.Normal;
//if ( Stats[BulletStat.Force] > 0f )
// enemy.AddVelocity( Vector2.Lerp( Velocity2D.Normal, dir, 0.3f ) * Stats[BulletStat.Force] * (1f / enemy.Weight) * Utils.Map( dmg, 0f, 5f, 0.1f, 1f ) );
var shouldFlinch = Damage < enemy.MaxHealth * 0.05f ? false : true;
var force = dir * Damage * 0.1f;
enemy.DamageRpc( Damage, Shooter, DamageType.Spear, WorldPosition, force, isCrit: false, shouldFlinch );
// todo: needs to use GetDamageMultiplier
_hitThings.Add( enemy );
Damage += DamagePerPierce;
WorldRotation = WorldRotation.RotateAroundAxis( Vector3.Forward, Game.Random.Float( -40f, -25f ) );
_sizeDirty = true;
}
else if ( other is Obstacle obstacle )
{
var normal = (Position2D - obstacle.Position2D).LengthSquared > Manager.TOUCH_DIST_REQUIRED_SQR
? (Position2D - obstacle.Position2D).Normal
: Utils.GetRandomVector();
var scaleMultiplier = Utils.Map( Damage, 1f, 40f, 0.5f, 1f, EasingType.Linear ) * Utils.Map( Damage, 40f, 100f, 1f, 1.25f, EasingType.Linear );
Manager.Instance.SpawnBulletImpactParticlesRpc( WorldPosition, normal, Color.White, scaleMultiplier );
Remove();
}
}
public void Remove()
{
GameObject.Destroy();
}
}