Enemy subclass representing a stationary turret enemy. It configures turret stats, spawning behavior, targeting, rotation, movement (mostly inert), and shooting logic which spawns bullets and plays SFX/particles via manager 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;
}
}