An Enemy subclass representing a stationary turret enemy. It configures stats (health, spawn, movement disabled), aims at the target, spawns visual cloud effect on build, and periodically fires bullets using its owner PlayerCreator to spawn bullets and play sound/particle RPCs.
using System;
using Sandbox;
public class Turret : Enemy
{
public override EnemyType EnemyType => EnemyType.Turret;
public override float GetMaxHealth()
{
return 150f;
}
public override Vector3 SpawnScale => new Vector3( 0.5f, 0.5f, 1f );
//public override float SpawnZPos => -10f;
public override float SpawnTime => 5f;
public override bool ShowHealthbar => true;
public override float HealthbarOffset => 95f;
public override bool CanMove => false;
public override bool CanDamageByTouch => false;
private TimeSince _timeSinceShoot;
private int _numTimesShoot;
public float ShootDelay { get; set; }
private TimeSince _timeSinceBuildCloud;
public int TurretNum { get; private set; }
protected override void OnStart()
{
base.OnStart();
CoinValueMin = 0;
CoinValueMax = 0;
CoinChance = 0f;
PushStrength = 10000f;
Weight = 3.5f;
_personalSpeedScale = 1f;
_personalSpeedFreq = 8f;
_timeSinceBuildCloud = 0f;
if ( IsProxy )
return;
AggroRange = 500f;
DetectTargetRange = 600f;
LoseTargetRange = 700f;
LoseTargetTime = 3f;
DamageTargetDelay = 0.5f;
_personalTurnSpeed = 5f;
Acceleration = 100f;
AccelerationAttacking = 100f;
Deceleration = 3f;
DecelerationAttacking = 3f;
}
protected override void OnUpdate()
{
base.OnUpdate();
//Gizmo.Draw.Color = Color.White;
//Gizmo.Draw.Text( $"{TurretNum}", new global::Transform( WorldPosition ) );
if ( !PlayerCreator.IsValid() )
{
//DieRpc( dir: Vector2.Zero, force: 0f, player: null, damageType: DamageType.Bullet );
return;
}
if ( !PlayerCreator.IsProxy )
{
if ( Manager.Instance.IsGameOver )
return;
if ( IsAttacking )
{
if ( _timeSinceShoot > PlayerCreator.Stats[PlayerStat.TurretShootDelay] )
{
Shoot();
}
}
}
}
protected override void HandleSpawning()
{
base.HandleSpawning();
if( _timeSinceBuildCloud > 0.5f )
{
var pos = WorldPosition.WithZ( 10f );
GameObject.Clone( "prefabs/effects/cloud.prefab", new CloneConfig { StartEnabled = true, Transform = new Transform( pos ) } );
_timeSinceBuildCloud = 0f;
}
if ( IsProxy )
return;
if ( IsSpawning && PlayerCreator.IsValid() )
{
if ( PlayerCreator.IsDead )
{
Remove();
return;
}
var pos = Manager.Instance.ClampPosToBounds( PlayerCreator.Position2D + PlayerCreator.FacingDir * 45f );
WorldPosition = new Vector3( pos.x, pos.y, WorldPosition.z );
}
}
protected override void HandleTarget()
{
base.HandleTarget();
if ( !PlayerCreator.IsValid() )
return;
TargetPos = PlayerCreator.Position2D - PlayerCreator.FacingDir * (TurretNum + 1) * 45f;
}
protected override void HandleRotation()
{
if ( !HasTarget )
return;
var targetFacingDir = ((Vector3)TargetUnit.Position2D - (Vector3)Position2D).Normal.WithZ( 0f );
if ( ShouldRetreatFromTarget )
targetFacingDir *= -1f;
WorldRotation = Rotation.Lerp( WorldRotation, Rotation.LookAt( targetFacingDir ), _personalTurnSpeed * Time.Delta * TimeScale );
}
protected override void HandleMovement()
{
var moveDir = (TargetPos - Position2D).Normal;
float acceleration = (IsAttacking ? AccelerationAttacking : Acceleration);
Velocity += moveDir * acceleration * Time.Delta * TimeScale * Manager.Instance.GlobalMovespeedModifier;
if ( Manager.Instance.IsWindActive )
Velocity += (Manager.Instance.GlobalWindForce / (Weight * 3f)) * Time.Delta;
Velocity *= Math.Max( 1f - Time.Delta * (IsAttacking ? DecelerationAttacking : Deceleration) * Manager.Instance.GlobalFrictionModifier, 0f );
WorldPosition += (Vector3)Velocity * GetMoveSpeedFactor() * Time.Delta;
}
protected override float GetMoveSpeedFactor()
{
return 1f;
}
public override void StartAttacking()
{
base.StartAttacking();
if ( IsProxy )
return;
_timeSinceShoot = 0f;
}
void Shoot()
{
if ( !PlayerCreator.IsValid() || Manager.Instance.IsGameOver )
return;
var dir = (Vector2)WorldRotation.Forward;
var pos = Position2D + Utils.GetPerpendicularVector( dir ) * 4f * (_numTimesShoot % 2 == 0 ? 1f : -1f);
var zPos = 35f;
var damage = PlayerCreator.GetBulletDamage( isFromClip: false, isLastAmmo: false );
PlayerCreator.SpawnBullet( pos, zPos, dir, damage, isFromClip: false );
//public Bullet SpawnBullet( Vector2 pos, float zPos, Vector2 dir, float damage, bool isFromClip = false, BulletType bulletType = BulletType.Normal )
Manager.Instance.PlaySfxNearbyRpc( "player.shoot", Position2D, Game.Random.Float( 1.2f, 1.25f ), volume: 0.75f, maxDist: 300f );
var impactParticlesPos = new Vector3( pos.x, pos.y, zPos ) + (Vector3)dir * 15f;
Manager.Instance.SpawnBulletImpactParticlesRpc( impactParticlesPos, Vector3.Up, Color.White );
_timeSinceShoot = 0f;
_numTimesShoot++;
}
protected override void PlaySpawnAnim()
{
}
protected override void PlayWalkAnim()
{
}
protected override void PlayAttackAnim()
{
}
protected override void PlayFlinchAnim()
{
}
protected override void PlayJumpAnim()
{
}
public override void Colliding( Thing other, float percent, float dt )
{
base.Colliding( other, percent, dt );
if ( other is Player player )
{
if ( IsSpawning )
return;
//if ( !player.IsDead && !Position2D.Equals( player.Position2D ) )
//{
// if ( !(player.IgnorePhysicsAmount > 0) )
// RepelVelocity += (Position2D - player.Position2D).Normal * player.PushStrength * 10f * percent * dt;
//}
}
}
[Rpc.Broadcast]
public void SetTurretNum( int num )
{
TurretNum = num;
}
}