things/enemies/Spitter.cs
using Sandbox;
public class Spitter : Enemy
{
private TimeSince _damageTime;
private const float DAMAGE_TIME = 0.75f;
private float _shootDelayTimer;
protected float SHOOT_DELAY_MIN = 2f;
protected float SHOOT_DELAY_MAX = 4f;
public bool IsShooting { get; private set; }
protected bool _hasShot;
private TimeSince _prepareShootTime;
public override float HeightVariance => 0.04f;
public override float WidthVariance => 0.02f;
public float ShootRange { get; protected set; }
protected override void OnAwake()
{
//OffsetY = -0.45f;
ShadowScale = 1.1f;
ShadowFullOpacity = 0.8f;
ShadowOpacity = 0f;
Scale = 1f;
base.OnAwake();
//Sprite.Texture = Texture.Load("textures/sprites/spitter.vtex");
//Sprite.Size = new Vector2( 1f, 1f ) * Scale;
PushStrength = 8f;
Radius = 0.25f;
Health = 50f;
if ( Manager.Instance.Difficulty < 0 )
Health = 40f;
MaxHealth = Health;
DamageToPlayer = 10f;
CoinValueMin = 2;
CoinValueMax = 3;
CoinChance = 0.65f;
Sprite.PlayAnimation( AnimSpawnPath );
if ( IsProxy )
return;
CollideWith.Add( typeof( Enemy ) );
CollideWith.Add( typeof( Player ) );
_damageTime = DAMAGE_TIME;
_shootDelayTimer = Game.Random.Float( SHOOT_DELAY_MIN, SHOOT_DELAY_MAX );
ShootRange = 5f;
}
protected override void UpdatePosition( float dt )
{
//Gizmo.Draw.Color = Color.White;
//Gizmo.Draw.Text( $"Sprite.PlaybackSpeed: {Sprite.PlaybackSpeed}", new global::Transform( (Vector3)Position2D + new Vector3( 0f, -0.7f, 0f ) ) );
base.UpdatePosition( dt );
if ( IsProxy )
return;
var targetPos = GetTargetPos();
if ( IsShooting )
{
if ( !_hasShot && _prepareShootTime > 1.0f )
Shoot();
if ( _prepareShootTime > 1.6f )
FinishShooting();
return;
}
else
{
Velocity += (targetPos - Position2D).Normal * 1.0f * dt * (IsFeared ? -1f : 1f);
}
float speed = 0.9f * (IsAttacking ? 1.3f : 0.7f) + Utils.FastSin( MoveTimeOffset + Time.Now * (IsAttacking ? 15f : 7.5f) ) * (IsAttacking ? 0.66f : 0.35f);
if ( Manager.Instance.Difficulty < 0 )
speed *= 0.85f;
WorldPosition += (Vector3)Velocity * speed * dt;
if( Target.IsValid() )
{
var target_dist_sqr = (Target.Position2D - Position2D).LengthSquared;
var rangeSqr = ShootRange * ShootRange;
if ( Manager.Instance.Difficulty < 0 )
rangeSqr *= 0.75f;
//Gizmo.Draw.Color = Color.White;
//Gizmo.Draw.Text( $"rangeSqr: {rangeSqr} ShootRange: {ShootRange} target_dist_sqr: {target_dist_sqr}", new global::Transform( (Vector3)Position2D + new Vector3( 0f, -0.7f, 0f ) ) );
if ( !IsShooting && (!IsAttacking || IsCharmed) && target_dist_sqr < rangeSqr && !Manager.Instance.IsGameOver )
{
_shootDelayTimer -= dt;
if ( _shootDelayTimer < 0f )
{
PrepareToShoot();
}
}
}
}
protected virtual Vector2 GetTargetPos()
{
return Target.IsValid() ? Target.Position2D : (IsCharmed ? Manager.Instance.Player.Position2D : Position2D);
}
protected override void UpdateSprite( Thing target )
{
if ( Sprite.CurrentAnimation.Name.Contains( "shoot" ) ) return;
base.UpdateSprite( target );
}
public void PrepareToShoot()
{
if ( Manager.Instance.IsGameOver )
return;
_prepareShootTime = 0f;
IsShooting = true;
_hasShot = false;
DontChangeAnimSpeed = true;
AnimSpeed = 1f;
Sprite.PlayAnimation( "shoot" );
Manager.Instance.PlaySfxNearby( "spitter.prepare", Position2D, pitch: Game.Random.Float( 1f, 1.1f ), volume: 0.6f, maxDist: 2.75f );
CanAttack = false;
CanAttackAnim = false;
}
public virtual void Shoot()
{
if ( IsProxy ) return;
var target_pos = Target.IsValid() ? Target.Position2D + Target.Velocity * Game.Random.Float( 0.5f, 1.5f ) : Position2D + Utils.GetRandomVector() * 5f;
var dir = Utils.RotateVector( (target_pos - Position2D).Normal, Game.Random.Float( -10f, 10f ) );
//Manager.Instance.SpawnEnemyBullet( Position2D + new Vector2( 0f, 0.45f ) + dir * 0.05f, dir, speed: 2f );
var bullet = Manager.Instance.SpawnEnemyBullet( Position2D + dir * 0.05f, dir, speed: 1.8f );
if(IsCharmed)
{
bullet.Creator = this;
bullet.BecomeCharmed();
}
Velocity *= 0.25f;
_hasShot = true;
Manager.Instance.PlaySfxNearby( "spitter.shoot", Position2D, pitch: Game.Random.Float( 0.8f, 0.9f ), volume: 0.9f, maxDist: 5f );
Sprite.PlayAnimation( "shoot_reverse" );
}
public void FinishShooting()
{
Sprite.PlayAnimation( AnimIdlePath );
CanAttack = true;
CanAttackAnim = true;
_shootDelayTimer = Game.Random.Float( SHOOT_DELAY_MIN, SHOOT_DELAY_MAX ) * (Manager.Instance.Difficulty < 0 ? 1.5f : 1f);
IsShooting = false;
DontChangeAnimSpeed = false;
}
public override void Colliding( Thing other, float percent, float dt )
{
base.Colliding( other, percent, dt );
if ( other is Enemy enemy && !enemy.IsDying )
{
var spawnFactor = Utils.Map( enemy.TimeSinceSpawn, 0f, enemy.SpawnTime, 0f, 1f, EasingType.QuadIn );
Velocity += (Position2D - enemy.Position2D).Normal * Utils.Map( percent, 0f, 1f, 0f, 1f ) * enemy.PushStrength * (1f + enemy.TempWeight) * spawnFactor * dt;
if ( IsAttacking && IsCharmed != enemy.IsCharmed && _damageTime > (DAMAGE_TIME / TimeScale) )
{
var dmg = DamageToPlayer;
if ( IsCharmed )
dmg *= CharmDamageDealtMultiplier;
enemy.Damage( dmg, null, addVel: Vector2.Zero, addTempWeight: 0f, isCrit: false, DamageType.Melee );
enemy.Target = this;
_damageTime = 0f;
Manager.Instance.PlaySfxNearby( "zombie.attack.player", Position2D, pitch: Utils.Map( enemy.Health, enemy.MaxHealth, 0f, 0.95f, 1.15f, EasingType.QuadIn ), volume: 0.6f, maxDist: 4.5f );
}
}
else if ( other is Player player )
{
if ( !player.IsDead )
{
Velocity += (Position2D - player.Position2D).Normal * Utils.Map( percent, 0f, 1f, 0f, 1f ) * player.Stats[PlayerStat.PushStrength] * (1f + player.TempWeight) * dt;
if ( IsAttacking && _damageTime > (DAMAGE_TIME / TimeScale) && !IsCharmed )
{
float dmg = player.CheckDamageAmount( DamageToPlayer, DamageType.Melee );
if ( !player.IsInvulnerable && !player.IsTimePausedForChoosing )
{
Manager.Instance.PlaySfxNearby( "zombie.attack.player", Position2D, pitch: Utils.Map( player.Health, player.Stats[PlayerStat.MaxHp], 0f, 0.95f, 1.15f, EasingType.QuadIn ), volume: 1f, maxDist: 5.5f );
player.Damage( dmg );
if ( dmg > 0f )
OnDamagePlayer( player, dmg );
}
_damageTime = 0f;
}
}
}
}
public override void Celebrate()
{
base.Celebrate();
CelebrateAsync();
}
async void CelebrateAsync()
{
await Task.Delay( Game.Random.Int( 0, 500 ) );
Sprite.PlaybackSpeed = Game.Random.Float( 1f, 3f );
Sprite.PlayAnimation( "cheer_start" );
await Task.Delay( Game.Random.Int( 200, 400 ) );
Sprite.PlayAnimation( "cheer" );
}
}