An Enemy subclass representing a falling sword hazard. It controls spawn behavior, movement while spawning, despawn/shake effects, spawn/death sounds, and optional gib spawning; it disables normal enemy interactions like attacking or being targeted.
using System;
using System.Numerics;
using Sandbox;
public class Sword : Enemy
{
public override EnemyType EnemyType => EnemyType.Sword;
public override float GetMaxHealth()
{
return 3000f;
}
public override Vector3 SpawnScale => new Vector3( 3f );
public override float SpawnTime => 0.25f + SPAWN_AIM_TIME;
public override float SpawnZPos => 750f;
public override float GroundZPos => 10f;
//public override bool CanHaveTarget => false;
public override bool CanAttack => false;
public override bool CanTurn => false;
public override bool CanBeBackstabbed => false;
public override bool CountsAsKill => false;
public override bool CanMove => false;
public override bool IsInanimate => true;
public override bool CanBeTargeted => false;
public override bool ShouldCreateSpawnClouds => false;
public override bool CanDamageByTouch => false;
public override bool CanCombust => false;
public bool IsInSpawnDamagePhase => (IsSpawning && TimeSinceSpawn > SPAWN_AIM_TIME) || _timeSinceFinishSpawn < 0.2f;
protected string _debrisName;
private bool _isDespawning;
private TimeSince _timeSinceDespawn;
private bool _hasShaked;
private float _leaveTime;
private GameObject _warningSwordEffect;
private const float SPAWN_AIM_TIME = 1.5f;
private TimeSince _timeSinceFinishSpawn;
private float _spawnSpeed;
private float _spawnSpeedFactorClose;
private float _spawnSpeedFactorFar;
private float _spawnDeceleration;
private bool _hasPlayedSpawnSfx;
protected override void OnStart()
{
base.OnStart();
CoinValueMin = 0;
CoinValueMax = 0;
CoinChance = 0f;
PushStrength = 20000f;
Weight = 17f;
_debrisName = "barrel_debris";
MeleeDamage = Utils.Select( Manager.Instance.Difficulty, 9f, 13f, 14f );
DamageTargetDelay = 0.3f;
DetectTargetRange = 2000f;
LoseTargetRange = 3000f;
_warningSwordEffect = GameObject.Clone( "prefabs/effects/warning_sword.prefab", new CloneConfig { StartEnabled = true, Transform = new Transform( new Vector3( Position2D.x, Position2D.y, 5f ) ) } );
if ( IsProxy )
return;
_spawnSpeed = Game.Random.Float( 80f, 500f );
_spawnSpeedFactorClose = Game.Random.Float( 0.1f, 0.7f );
_spawnSpeedFactorFar = Game.Random.Float( 0.5f, 1.5f );
_spawnDeceleration = Game.Random.Float( 0.5f, 1.2f );
Deceleration = 4.2f;
_leaveTime = Game.Random.Float( 4.5f, 5f );
}
protected override void HandleSpawning()
{
if ( TimeSinceSpawn > SpawnTime )
{
FinishSpawning();
}
else
{
SpawnProgress = Utils.Map( TimeSinceSpawn, 0f, SpawnTime, 0f, 1f );
if ( TimeSinceSpawn < SPAWN_AIM_TIME )
{
ModelRenderer.Tint = Color.White.WithAlpha( Utils.Map( TimeSinceSpawn, 0f, 0.5f, 0f, 1f ) );
if ( !IsProxy && !HasTarget )
CheckForTarget();
if ( !IsProxy && HasTarget )
{
Velocity += (TargetUnit.Position2D - Position2D).Normal * _spawnSpeed * Time.Delta * Utils.Map( (TargetUnit.Position2D - Position2D).LengthSquared, 0f, 200f * 200f, _spawnSpeedFactorClose, _spawnSpeedFactorFar );
Velocity *= Math.Max( 1f - Time.Delta * _spawnDeceleration, 0f );
Position2D += Velocity * Time.Delta;
//WorldPosition = new Vector3( TargetUnit.Position2D.x, TargetUnit.Position2D.y, WorldPosition.z );
}
_warningSwordEffect.WorldPosition = new Vector3( WorldPosition.x, WorldPosition.y, _warningSwordEffect.WorldPosition.z );
}
else
{
Velocity = Vector2.Zero;
WorldPosition = WorldPosition.WithZ( Utils.Map( TimeSinceSpawn, SPAWN_AIM_TIME, SpawnTime, SpawnZPos, GroundZPos, EasingType.ExpoIn ) );
if( !_hasPlayedSpawnSfx )
{
Manager.Instance.PlaySfxNearby( "sword_fall", Position2D, pitch: Game.Random.Float(1f, 1.1f), volume: 1f, maxDist: 600f );
_hasPlayedSpawnSfx = true;
}
}
}
}
protected override void OnUpdate()
{
base.OnUpdate();
//Gizmo.Draw.Color = Color.White;
//Gizmo.Draw.Text( $"{WorldPosition.z}", new global::Transform( WorldPosition ) );
if ( IsSpawning )
return;
if ( IsProxy )
return;
Velocity *= Math.Max( 1f - Time.Delta * Deceleration * Manager.Instance.GlobalFrictionModifier, 0f );
WorldPosition += (Vector3)Velocity * Time.Delta;
if( _isDespawning )
{
var despawnTime = 0.45f;
if( _timeSinceDespawn > despawnTime )
{
Remove();
}
else
{
WorldPosition = WorldPosition.WithZ( Utils.Map( _timeSinceDespawn, 0f, despawnTime, GroundZPos, 1500f, EasingType.QuadOut) );
ModelRenderer.Tint = Color.White.WithAlpha( Utils.Map( _timeSinceDespawn, 0f, despawnTime, 1f, 0f, EasingType.QuadIn ) );
}
}
else
{
var leaveTime = _leaveTime;
if( TimeSinceSpawn > leaveTime )
{
_isDespawning = true;
_timeSinceDespawn = 0f;
Manager.Instance.ShakeCamsNearby( Position2D, radius: 300f, maxStrength: Game.Random.Float( 4f, 4.5f ), time: 0.1f );
}
var shakeTime = 0.3f;
if (!_hasShaked && TimeSinceSpawn > leaveTime - shakeTime )
{
_hasShaked = true;
ShakeRpc( startStrength: 0f, endStrength: 1f, time: shakeTime, easingType: EasingType.SineInOut );
}
}
}
public override void Colliding( Thing other, float percent, float dt )
{
base.Colliding( other, percent, dt );
}
public override void Flinch( float time, Vector2 dir )
{
base.Flinch( time, dir );
ModelRenderer.LocalRotation = new Angles(Game.Random.Float(-3f, 3f), 0f, 180f + Game.Random.Float(-3f, 3f));
}
public override void StopFlinching()
{
base.StopFlinching();
ModelRenderer.LocalRotation = new Angles(0f, 0f, 180f );
}
public override void SetAnim( string name, bool forceRestart = false )
{
return;
}
protected override void DropLoot( Player player )
{
// do nothing
}
protected override void SpawnGibs( Vector2 dir, float force, DamageType damageType )
{
// todo:
//GameObject.Clone( $"prefabs/effects/{_debrisName}.prefab", new CloneConfig { StartEnabled = true, Transform = new Transform( WorldPosition.WithZ( Game.Random.Float( 40f, 60f ) ), Rotation.Identity ) } );
//force = Game.Random.Float( 1f, 1.3f );
//for ( int i = 0; i < 3; i++ )
//{
// var offsetDir = Utils.GetRandomVector();
// var pos = WorldPosition + (Vector3)offsetDir * Radius;
// pos = pos.WithZ( Game.Random.Float( 15f, 30f ) );
// var velocity = (pos - WorldPosition).Normal * Game.Random.Float( 100f, 250f );
// SpawnGib( "gib_barrel_01", pos.WithZ( Game.Random.Float( 25f, 40f ) ), velocity + new Vector3( dir.x * Game.Random.Float( -5f, 25f ), dir.y * Game.Random.Float( -5f, 25f ), Game.Random.Float( 30f, 150f ) ), force, chance: 0.5f );
//}
//for ( int i = 0; i < 5; i++ )
//{
// var offsetDir = Utils.GetRandomVector();
// var pos = WorldPosition + (Vector3)offsetDir * Radius;
// pos = pos.WithZ( Game.Random.Float( 15f, 30f ) );
// var velocity = (pos - WorldPosition).Normal * Game.Random.Float( 100f, 250f );
// SpawnGib( "gib_barrel_12", pos.WithZ( Game.Random.Float( 25f, 40f ) ), velocity + new Vector3( dir.x * Game.Random.Float( -5f, 25f ), dir.y * Game.Random.Float( -5f, 25f ), Game.Random.Float( 30f, 150f ) ), force, chance: 0.5f );
//}
}
protected void SpawnGib( string name, Vector3 pos, Vector3 velocity, float force, float chance = 1f )
{
chance *= Utils.Map( Manager.Instance.NumGibs, 0, 100, 1f, 0f, EasingType.SineIn );
if ( Game.Random.Float( 0f, 1f ) > chance )
return;
var gib = GameObject.Clone( $"prefabs/gibs/barrel/{name}.prefab", new global::Transform( pos, new Angles( Game.Random.Float( -5f, 5f ), Game.Random.Float(0f, 360f), Game.Random.Float( -5f, 5f ) ) ) );
var rigidBody = gib.GetComponent<Rigidbody>();
var horizontalForceMultiplier = IsExploding ? 2f : 1f;
var verticalForceMultiplier = IsExploding ? 0.5f : 1f;
rigidBody.Velocity = new Vector3( velocity.x * horizontalForceMultiplier, velocity.y * horizontalForceMultiplier, velocity.z * verticalForceMultiplier ) * force;
rigidBody.AngularVelocity = new Vector3( Game.Random.Float( -15f, 15f ), Game.Random.Float( -15f, 15f ), Game.Random.Float( -15f, 15f ) ) * force;
var gibFader = gib.GetComponent<GibFader>();
gibFader.Color = Color.White;
gibFader.Lifetime = Game.Random.Float( 0.5f, 3.5f );
}
protected override void PlayDeathSfx( Vector2 pos )
{
Manager.Instance.PlaySfxNearby( "chest.break", Position2D, pitch: Game.Random.Float( 1.15f, 1.2f ), volume: 1.4f, maxDist: 450f );
}
protected override void FinishSpawning()
{
base.FinishSpawning();
SpawnLandingClouds();
Manager.Instance.PlaySfxNearby( "slam", Position2D, pitch: Game.Random.Float( 1.8f, 2.2f ), volume: 1f, maxDist: 600f );
Manager.Instance.ShakeCamsNearby( Position2D, radius: 400f, maxStrength: Game.Random.Float( 5f, 6f ), time: Game.Random.Float( 0.2f, 0.25f ) );
if ( !IsProxy )
Manager.Instance.RequestTimeScale( startTimeScale: 0f, endTimeScale: 1f, duration: 0.15f, priority: 10 );
_timeSinceFinishSpawn = 0f;
}
public void SpawnLandingClouds()
{
int numClouds = Game.Random.Int( 3, 7 );
var startAngle = Game.Random.Float( 0f, 360f );
var increment = 360f / numClouds;
for ( int i = 0; i < numClouds; i++ )
{
var offsetDir = Utils.GetVector2FromAngleDegrees( startAngle );
var pos = WorldPosition + (Vector3)offsetDir * Game.Random.Float( 0.5f, 1f );
pos = pos.WithZ( Game.Random.Float( 8f, 13f ) );
Vector2 velocity = ((Vector2)pos - Position2D).Normal * Game.Random.Float( 500f, 800f );
var deceleration = Game.Random.Float( 7f, 10f );
Manager.Instance.SpawnCloud( pos, velocity, deceleration, lifetime: Game.Random.Float( 1f, 2f ), bright: true );
startAngle += increment * Game.Random.Float( 0.8f, 1.2f );
}
}
}