things/enemies/Turret.cs

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.

NetworkingFile Access
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;
	}
}