things/enemies/Enemy.cs
using Sandbox.Diagnostics;
using static Manager;
public abstract class Enemy : Thing
{
public float Health { get; set; }
public bool FlipX { get; set; }
public float MoveTimeOffset { get; set; }
private float _flashTimer;
public bool IsFlashing { get; private set; }
public float MaxHealth { get; set; }
public bool IsSpawning { get; protected set; }
public TimeSince TimeSinceSpawn { get; protected set; }
public bool IsDying { get; private set; }
public float DeathTimeElapsed { get; private set; }
public float DeathTime { get; protected set; }
public float DeathProgress { get; private set; }
private Vector3 _deathScale;
[Sync] public bool IsAttacking { get; protected set; }
private float _aggroTimer;
public bool CanAttack { get; set; }
public bool CanAttackAnim { get; set; }
public bool CanTurn { get; set; }
public bool DontChangeAnimSpeed { get; set; }
public virtual bool CanBleed => true;
public float AggroRange { get; protected set; }
protected const float AGGRO_START_TIME = 0.2f;
protected const float AGGRO_LOSE_TIME = 0.4f;
public float DamageToPlayer { get; protected set; }
public float ScaleFactor { get; protected set; }
public float PushStrength { get; protected set; }
public float SpawnTime { get; protected set; }
public float ShadowFullOpacity { get; protected set; }
public virtual float FullOpacity => 1f;
public string AnimSpawnPath { get; protected set; }
public string AnimIdlePath { get; protected set; }
public string AnimAttackPath { get; protected set; }
public string AnimDiePath { get; protected set; }
public float Deceleration { get; protected set; }
public float DecelerationAttacking { get; protected set; }
protected TimeSince _spawnCloudTime;
public Dictionary<TypeDescription, EnemyStatus> EnemyStatuses = new Dictionary<TypeDescription, EnemyStatus>();
public BurningVfx BurningVfx { get; protected set; }
private FrozenVfx _frozenVfx;
private FearVfx _fearVfx;
private CharmVfx _charmVfx;
public float VfxOpacity { get; set; }
public bool IsFrozen { get; set; }
public bool IsFeared { get; set; }
private float _animSpeed;
public float AnimSpeed { get { return _animSpeed; } set { _animSpeed = value; Sprite.PlaybackSpeed = _animSpeed * _animSpeedModifier; } }
private float _animSpeedModifier;
public float AnimSpeedModifier { get { return _animSpeedModifier; } set { _animSpeedModifier = value; Sprite.PlaybackSpeed = _animSpeed * _animSpeedModifier; } }
public int CoinValueMin { get; protected set; }
public int CoinValueMax { get; protected set; }
public float CoinChance { get; protected set; }
public virtual float HeightVariance => 0f;
public virtual float WidthVariance => 0f;
public Thing Target { get; set; }
public Color TintFullHp { get; set; }
public Color TintZeroHp { get; set; }
public Color TintDeath { get; set; }
public bool IgnoreCollision { get; set; }
public bool IsCharmed { get; set; }
private TimeSince _timeSinceLookForEnemyTarget;
private float _nextEnemyTargetDelay;
private const float ENEMY_TARGET_DELAY_MIN = 0.3f;
private const float ENEMY_TARGET_DELAY_MAX = 0.8f;
public float CharmDamageTakenMultiplier { get; set; }
public float CharmDamageDealtMultiplier { get; set; }
public bool ShouldUpdateAfterGameOver { get; set; }
public TimeSince TimeSinceTakeInfightingDamage { get; set; }
public int EnemyIdNum { get; set; }
public TimeSince TimeSinceBurn { get; set; }
protected override void OnAwake()
{
base.OnAwake();
Sprite.LocalScale = new Vector3( Scale * Game.Random.Float( 1f - HeightVariance, 1f + HeightVariance ), Scale * Game.Random.Float( 1f - WidthVariance, 1f + WidthVariance ), 1f ) * Globals.SPRITE_SCALE;
AnimSpawnPath = "spawn";
AnimIdlePath = "walk";
AnimAttackPath = "attack";
AnimDiePath = "die";
_animSpeed = 1f;
_animSpeedModifier = 1f;
SpawnShadow( ShadowScale, ShadowOpacity );
IsSpawning = true;
TimeSinceSpawn = 0f;
SpawnTime = 1.75f;
MoveTimeOffset = Game.Random.Float( 0f, 5f );
Deceleration = 1.47f;
DecelerationAttacking = 1.33f;
DeathTime = 0.3f;
AggroRange = 1.4f;
CanAttack = true;
CanAttackAnim = true;
CanTurn = true;
CoinValueMin = 1;
CoinValueMax = 1;
CoinChance = 0.55f;
TintFullHp = Color.White;
TintZeroHp = new Color( 0.3f, 0.3f, 0.3f );
TintDeath = new Color( 0.5f, 0.5f, 0.5f );
VfxOpacity = 0f;
Sprite.PlayAnimation( AnimSpawnPath );
}
//protected override void OnStart()
//{
// base.OnStart();
// Sprite.Tint = TintFullHp;
//}
protected override void OnUpdate()
{
//Gizmo.Draw.Color = Color.White;
//Gizmo.Draw.Text( $"Anim: {Sprite.CurrentAnimation.Name}", new global::Transform( (Vector3)Position2D + new Vector3( 0f, -0.7f, 0f ) ) );
//Gizmo.Draw.Color = Color.White.WithAlpha( 0.05f );
//Gizmo.Draw.LineSphere( (Vector3)Position2D, Radius );
//Gizmo.Draw.Color = Color.Black.WithAlpha( 0.2f );
//Gizmo.Draw.Text( $"{Health}/{MaxHealth}", new global::Transform( (Vector3)Position2D + new Vector3( 0f, -0.2f, 0f ) ) );
float dt = Time.Delta;
//HandleFlashing( dt );
HandleFlashing( RealTime.Delta );
if ( !Manager.Instance.ShouldUpdateThings && !ShouldUpdateAfterGameOver )
return;
base.OnUpdate();
if ( !Target.IsValid() )
{
if ( !IsCharmed )
Target = Manager.Instance.Player;
}
UpdateSprite( Target );
if ( IsSpawning )
{
HandleSpawning();
return;
}
if ( IsDying )
{
HandleDying( dt );
return;
}
Sprite.SpriteFlags = FlipX ? SpriteFlags.HorizontalFlip : SpriteFlags.None;
if ( IsProxy )
return;
HandleStatuses( dt );
float timeScale = this is Boss ? Utils.Map( TimeScale, 1f, 0f, 1f, 0.3f ) : TimeScale;
UpdatePosition( dt * timeScale );
WorldPosition = WorldPosition.WithZ( Globals.GetZPos( Position2D.y ) );
ClampToBounds();
//Depth = -Position.y * 10f;
if ( !IgnoreCollision )
{
CheckCollisions( dt );
HandleAttacking( Target, dt * timeScale );
}
HandleDeceleration( dt );
TempWeight *= (1f - dt * 4.7f);
if ( Target.IsValid() && Target is Enemy enemy )
{
if ( enemy.IsDying || enemy.IsCharmed == IsCharmed )
{
Target = IsCharmed ? null : Manager.Instance.Player;
}
}
if ( Manager.Instance.CharmedEnemyCount > 0 )
{
//if ( Target.IsValid() && !IsCharmed && Target is Enemy )
//{
// Gizmo.Draw.Color = Color.Yellow.WithAlpha( 0.3f );
// Gizmo.Draw.LineThickness = 5f;
// Gizmo.Draw.Line( WorldPosition, Target.WorldPosition + new Vector3( 0f, 0.05f, 0f ) );
//}
//if ( Target.IsValid() && IsCharmed )
//{
// Gizmo.Draw.Color = Color.Red;
// Gizmo.Draw.LineThickness = 1f;
// Gizmo.Draw.Line( WorldPosition, Target.WorldPosition );
//}
if ( _timeSinceLookForEnemyTarget > _nextEnemyTargetDelay )
LookForEnemyTarget();
}
}
//protected override void OnFixedUpdate()
//{
// base.OnFixedUpdate();
// if ( !IgnoreCollision )
// {
// CheckCollisions( Time.Delta );
// }
//}
protected virtual void HandleStatuses( float dt )
{
for ( int i = EnemyStatuses.Count - 1; i >= 0; i-- )
{
var status = EnemyStatuses.Values.ElementAt( i );
if ( status.ShouldUpdate )
status.Update( dt );
}
}
protected virtual void HandleDeceleration( float dt )
{
//var oldVel = Velocity;
Velocity *= Math.Max( 1f - dt * (IsAttacking ? DecelerationAttacking : Deceleration), 0f );
//if(this is Zombie)
// Log.Info( $"decel - dt: {dt}, oldVel: {oldVel.Length} newVel: {Velocity.Length} now: {Time.Now} {(dt > 0.1f ? "----------------------" : "")}" );
}
protected virtual void HandleAttacking( Thing target, float dt )
{
if ( !target.IsValid() )
{
IsAttacking = false;
return;
}
float dist_sqr = (target.Position2D - Position2D).LengthSquared;
float attack_dist_sqr = MathF.Pow( AggroRange, 2f );
if ( !IsAttacking )
{
if ( CanAttack )
{
if ( dist_sqr < attack_dist_sqr )
{
_aggroTimer += dt;
if ( _aggroTimer > AGGRO_START_TIME )
{
StartAttacking();
_aggroTimer = 0f;
}
}
else
{
_aggroTimer = 0f;
}
}
}
else
{
if ( dist_sqr > attack_dist_sqr )
{
_aggroTimer += dt;
if ( _aggroTimer > AGGRO_LOSE_TIME )
{
IsAttacking = false;
}
}
else
{
if ( !DontChangeAnimSpeed )
AnimSpeed = Utils.Map( dist_sqr, attack_dist_sqr, 0f, 1f, 4f, EasingType.Linear );
_aggroTimer = 0f;
}
}
}
public virtual void StartAttacking()
{
IsAttacking = true;
}
protected virtual void UpdateSprite( Thing target )
{
if ( IsDying )
return;
if ( !IsAttacking || !target.IsValid() || IsSpawning )
{
if ( IsSpawning )
Sprite.PlayAnimation( AnimSpawnPath );
else
Sprite.PlayAnimation( AnimIdlePath );
if ( !DontChangeAnimSpeed )
AnimSpeed = Utils.Map( Utils.FastSin( MoveTimeOffset + Time.Now * 7.5f ), -1f, 1f, 0.75f, 3f, EasingType.ExpoIn );
if ( CanTurn && !IsFrozen )
{
if ( MathF.Abs( Velocity.x ) > 0.175f )
FlipX = Velocity.x > 0f;
}
}
else
{
Sprite.PlayAnimation( AnimAttackPath );
if ( !DontChangeAnimSpeed )
{
float dist_sqr = (target.Position2D - Position2D).LengthSquared;
float attack_dist_sqr = MathF.Pow( AggroRange, 2f );
AnimSpeed = Utils.Map( dist_sqr, attack_dist_sqr, 0f, 1f, 4f, EasingType.Linear );
}
if ( !IsProxy && CanTurn && !IsFrozen )
{
if ( IsFeared )
FlipX = target.Position2D.x < Position2D.x;
else
FlipX = target.Position2D.x > Position2D.x;
}
}
}
void HandleFlashing( float dt )
{
if ( IsFlashing )
{
_flashTimer -= dt;
if ( _flashTimer < 0f )
{
IsFlashing = false;
Sprite.FlashTint = IsCharmed ? Color.Magenta.WithAlpha( 0.4f ) : Color.White.WithAlpha( 0f );
Sprite.Tint = Color.Lerp( TintFullHp, TintZeroHp, Utils.Map( Health, MaxHealth, 0f, 0f, 1f ) ).WithAlpha( FullOpacity );
//if ( IsCharmed )
// Sprite.Tint = Color.Lerp( Sprite.Tint, Color.Magenta, 0.9f );
}
}
}
void HandleDying( float dt )
{
DeathTimeElapsed += dt;
Sprite.LocalScale = _deathScale * Utils.Map( DeathTimeElapsed, 0f, DeathTime, 1f, 1.2f );
if ( DeathTimeElapsed > DeathTime )
{
DeathProgress = 1f;
FinishDying();
}
else
{
DeathProgress = Utils.Map( DeathTimeElapsed, 0f, DeathTime, 0f, 1f );
ShadowOpacity = Utils.Map( DeathProgress, 0f, 1f, ShadowFullOpacity, 0f, EasingType.QuadIn );
ShadowSpriteDirty = true;
}
}
void HandleSpawning()
{
if ( TimeSinceSpawn > SpawnTime )
{
FinishSpawning();
}
else
{
//Sprite.PlayAnimation( AnimSpawnPath );
if ( _spawnCloudTime > (0.3f / TimeScale) )
{
var cloud = Manager.Instance.SpawnCloud( Position2D + new Vector2( 0f, 0.05f ) );
cloud.Velocity = new Vector2( Game.Random.Float( -1f, 1f ), Game.Random.Float( -1f, 1f ) ).Normal * Game.Random.Float( 0.2f, 0.6f );
_spawnCloudTime = Game.Random.Float( 0f, 0.15f );
}
ShadowOpacity = Utils.Map( TimeSinceSpawn, 0f, SpawnTime, 0f, ShadowFullOpacity );
VfxOpacity = Utils.Map( TimeSinceSpawn, 0f, SpawnTime, 0f, 1f );
}
ShadowSprite.Tint = Color.Black.WithAlpha( ShadowOpacity );
}
protected virtual void FinishSpawning()
{
IsSpawning = false;
ShadowOpacity = ShadowFullOpacity;
ShadowSprite.Tint = Color.Black.WithAlpha( ShadowOpacity );
VfxOpacity = 1f;
}
void ClampToBounds()
{
var x_min = Manager.Instance.BOUNDS_MIN.x + Radius;
var x_max = Manager.Instance.BOUNDS_MAX.x - Radius;
var y_min = Manager.Instance.BOUNDS_MIN.y;
var y_max = Manager.Instance.BOUNDS_MAX.y - Radius;
Position2D = new Vector2( MathX.Clamp( Position2D.x, x_min, x_max ), MathX.Clamp( Position2D.y, y_min, y_max ) );
}
protected virtual void UpdatePosition( float dt )
{
}
public virtual void Damage( float damage, Player player, Vector2 addVel, float addTempWeight, bool isCrit = false, DamageType damageType = DamageType.PlayerBullet )
{
if ( IsDying )
return;
if ( player != null )
{
if ( IsFeared )
{
damage *= player.Stats[PlayerStat.FearDamageMultiplier];
}
}
Flash( 0.12f );
if ( Manager.Instance.Difficulty >= 0 && !Manager.Instance.UnlockedBulletDmgAchievement && damage > 500f )
{
Sandbox.Services.Achievements.Unlock( "big_bullet" );
Manager.Instance.UnlockedBulletDmgAchievement = true;
}
if ( IsCharmed )
damage *= CharmDamageTakenMultiplier;
//DamageNumbers.Add( (int)damage, Position2D + Vector2.Up * Radius * 3f + new Vector2( Game.Random.Float( -1f, 1f ), Game.Random.Float( -1f, 1f ) ) * 0.2f, color: isCrit ? Color.Yellow : Color.White );
var color = IsCharmed ? new Color( 1f, 0.05f, 1f ) : (isCrit ? new Color( 1f, 1f, 0.02f ) : Color.White);
//DamageNumbersLegacy.Create( damage, Position2D + new Vector2( 0.4f + Game.Random.Float( -0.1f, 0.1f ), Radius * 3f + Game.Random.Float( -0.2f, 0.3f ) ), color );
var pos = Position2D + new Vector2( Game.Random.Float( -0.1f, 0.1f ), Radius * 3f + Game.Random.Float( -0.2f, 0.3f ) );
float size;
if ( damage < 5f ) size = Utils.Map( damage, 1f, 5f, 1.05f, 1.3f, EasingType.QuadOut );
else if ( damage < 20f ) size = Utils.Map( damage, 5f, 20f, 1.3f, 1.6f, EasingType.Linear );
else size = Utils.Map( damage, 20f, 100f, 1.6f, 1.9f, EasingType.Linear );
Manager.Instance.SpawnDamageNumber( pos, damage, color, size );
if ( IsProxy )
return;
if ( player != null )
{
if ( IsFeared )
{
if ( player.Stats[PlayerStat.FearDrainPercent] > 0f )
{
if ( player.Health < player.Stats[PlayerStat.MaxHp] )
player.TimeSinceChangeHP = 0f;
//Log.Info( $"damage: {damage}, Health: {Health}" );
float damageToDrainFrom = damage * (damageType == DamageType.FearPain ? 0.2f : 1f);
player.RegenHealth( Math.Min( damageToDrainFrom, Math.Max( Health, 0f ) ) * player.Stats[PlayerStat.FearDrainPercent] );
}
}
}
Velocity += addVel;
TempWeight += addTempWeight;
Health -= damage;
if ( Health <= 0f )
StartDying( player );
}
public virtual void DamageFire( float damage, Player player )
{
damage *= player.GetDamageMultiplier();
if ( IsFrozen )
damage *= player?.Stats[PlayerStat.FreezeFireDamageMultiplier] ?? 1f;
Damage( damage, player, addVel: Vector2.Zero, addTempWeight: 0f );
}
public virtual void StartDying( Player player )
{
IsDying = true;
DeathProgress = 0f;
DeathTimeElapsed = 0f;
Sprite.PlayAnimation( AnimDiePath );
AnimSpeed = Game.Random.Float( 6.5f, 9f );
IsFlashing = false;
Sprite.Tint = TintDeath;
Sprite.FlashTint = Color.White.WithAlpha( 0f );
_deathScale = Sprite.LocalScale;
if ( CanBleed )
Manager.Instance.SpawnBloodSplatter( Position2D );
Manager.Instance.PlayEnemyDeathSfxLocal( Position2D );
if ( IsProxy )
return;
if ( Manager.Instance.Difficulty >= 0 )
Sandbox.Services.Stats.Increment( "zombies_killed", 1 );
if ( player is not null )
{
player.ForEachStatus( status => status.OnKill( this ) );
//if ( this is not Crate )
//{
// Sandbox.Services.Stats.Increment( player.Client, "kills", 1, $"{GetType().Name.ToLowerInvariant()}" );
//}
//else
//{
// Sandbox.Services.Stats.Increment( player.Client, "crates", 1 );
//}
}
DropLoot( player );
for ( int i = EnemyStatuses.Count - 1; i >= 0; i-- )
EnemyStatuses.Values.ElementAt( i ).StartDying();
}
public virtual void DropLoot( Player player )
{
//var coin_chance = player != null ? Utils.Map( player.Stats[PlayerStat.Luck], 0f, 10f, 0.5f, 1f ) : 0.5f;
var coin_chance = player == null || Manager.Instance.ElapsedTime > 60f
? CoinChance
: Utils.Map( player.Level, 0, 3, MathX.Lerp( CoinChance, 1f, 0.55f ), CoinChance );
if ( Manager.Instance.Difficulty < 0 )
coin_chance *= 1.25f;
//Log.Info( $"{this.GetType()} coin_chance: {coin_chance} CoinValueMin: {CoinValueMin}" );
if ( Game.Random.Float( 0f, 1f ) < coin_chance )
{
Manager.Instance.SpawnCoin( Position2D, vel: Vector2.Zero, value: Game.Random.Int( CoinValueMin, CoinValueMax ), force: coin_chance >= 0.95f );
}
else
{
var lowest_hp_percent = 1f;
foreach ( Player p in Manager.Instance.GetPlayers() )
lowest_hp_percent = MathF.Min( lowest_hp_percent, p.Health / p.Stats[PlayerStat.MaxHp] );
var healthPackCount = Scene.GetAllComponents<HealthPack>().Count();
var health_pack_chance = Utils.Map( lowest_hp_percent, 1f, 0f, 0f, 0.1f ) * Utils.Map( healthPackCount, 0, 5, 1f, 0f, EasingType.SineOut );
if ( Manager.Instance.Difficulty < 0 )
health_pack_chance *= 1.5f;
else if ( Manager.Instance.Difficulty >= 3 )
health_pack_chance *= 0.2f;
if ( Game.Random.Float( 0f, 1f ) < health_pack_chance )
{
Manager.Instance.SpawnHealthPack( Position2D, vel: Vector2.Zero );
}
}
}
public virtual void FinishDying()
{
Remove();
}
public override void Remove()
{
if ( !IsProxy )
{
for ( int i = EnemyStatuses.Count - 1; i >= 0; i-- )
EnemyStatuses.Values.ElementAt( i ).Remove();
EnemyStatuses.Clear();
}
base.Remove();
}
public void Flash( float time )
{
if ( IsFlashing )
return;
Sprite.Tint = Color.White.WithAlpha( 1f );
Sprite.FlashTint = Color.White.WithAlpha( 1f );
IsFlashing = true;
_flashTimer = time;
}
public void Flash( float time, Color color )
{
if ( IsFlashing )
return;
Sprite.Tint = color;
Sprite.FlashTint = color;
IsFlashing = true;
_flashTimer = time;
}
void CheckCollisions( float dt )
{
for ( int dx = -1; dx <= 1; dx++ )
{
for ( int dy = -1; dy <= 1; dy++ )
{
Manager.Instance.HandleThingCollisionForGridSquare( this, new GridSquare( GridPos.x + dx, GridPos.y + dy ), dt );
}
}
}
public override void Colliding( Thing other, float percent, float dt )
{
for ( int i = EnemyStatuses.Count - 1; i >= 0; i-- )
EnemyStatuses.Values.ElementAt( i ).Colliding( other, percent, dt );
}
public TStatus AddEnemyStatus<TStatus>()
where TStatus : EnemyStatus
{
var type = TypeLibrary.GetType<TStatus>();
if ( EnemyStatuses.TryGetValue( type, out var status ) )
{
status.Refresh();
return (TStatus)status;
}
else
{
status = type.Create<EnemyStatus>();
EnemyStatuses.Add( type, status );
status.Init( this );
return (TStatus)status;
}
}
public void RemoveEnemyStatus<TStatus>( TStatus status )
where TStatus : EnemyStatus
{
if ( EnemyStatuses.Remove( TypeLibrary.GetType<TStatus>(), out var existing ) )
{
Assert.AreEqual( existing, status );
status.Remove();
}
}
private void RemoveEnemyStatus<TStatus>()
where TStatus : EnemyStatus
{
if ( EnemyStatuses.Remove( TypeLibrary.GetType<TStatus>(), out var status ) )
{
status.Remove();
}
}
public TStatus GetEnemyStatus<TStatus>()
where TStatus : EnemyStatus
{
return EnemyStatuses.TryGetValue( TypeLibrary.GetType<TStatus>(), out var status )
? (TStatus)status
: null;
}
public bool HasEnemyStatus<TStatus>( TStatus status )
where TStatus : EnemyStatus
{
return EnemyStatuses.TryGetValue( TypeLibrary.GetType<TStatus>(), out var existing ) && existing == status;
}
public bool HasEnemyStatus<TStatus>()
where TStatus : EnemyStatus
{
return EnemyStatuses.ContainsKey( TypeLibrary.GetType<TStatus>() );
}
public void CreateBurningVfx()
{
var obj = Manager.Instance.BurningVfxPrefab.Clone( WorldPosition );
obj.Parent = GameObject;
obj.LocalPosition = new Vector3( 0f, 0f, 1f );
BurningVfx = obj.Components.Get<BurningVfx>();
BurningVfx.Enemy = this;
}
public void RemoveBurningVfx()
{
if ( BurningVfx != null )
{
BurningVfx.GameObject.Destroy();
BurningVfx = null;
}
}
public void CreateFrozenVfx()
{
var obj = Manager.Instance.FrozenVfxPrefab.Clone( WorldPosition );
obj.Parent = GameObject;
obj.LocalPosition = new Vector3( 0f, 0f, 2f );
_frozenVfx = obj.Components.Get<FrozenVfx>();
_frozenVfx.Enemy = this;
}
public void RemoveFrozenVfx()
{
if ( _frozenVfx != null )
{
_frozenVfx.GameObject.Destroy();
_frozenVfx = null;
}
}
public void CreateFearVfx()
{
var obj = Manager.Instance.FearVfxPrefab.Clone( WorldPosition );
obj.Parent = GameObject;
obj.LocalPosition = new Vector3( 0f, 0f, 3f );
_fearVfx = obj.Components.Get<FearVfx>();
_fearVfx.Enemy = this;
}
public void RemoveFearVfx()
{
if ( _fearVfx != null )
{
_fearVfx.GameObject.Destroy();
_fearVfx = null;
}
}
public void CreateCharmVfx()
{
var obj = Manager.Instance.CharmVfxPrefab.Clone( WorldPosition );
obj.Parent = GameObject;
obj.LocalPosition = new Vector3( 0f, 0f, 2f );
_charmVfx = obj.Components.Get<CharmVfx>();
_charmVfx.Enemy = this;
}
public void RemoveCharmVfx()
{
if ( _charmVfx != null )
{
_charmVfx.GameObject.Destroy();
_charmVfx = null;
}
}
public void Burn( Player player, float damage, float lifetime, float spreadChance )
{
var burning = AddEnemyStatus<BurningEnemyStatus>();
burning.Player = player;
burning.Damage = damage;
burning.Lifetime = lifetime;
burning.SpreadChance = spreadChance;
//if ( player != null )
// player.ForEachStatus( status => status.OnBurn( this ) );
}
public void Freeze( Player player )
{
if ( IsDying )
return;
var frozen = AddEnemyStatus<FrozenEnemyStatus>();
frozen.Player = player;
frozen.SetLifetime( player.Stats[PlayerStat.FreezeLifetime] );
frozen.SetTimeScale( player.Stats[PlayerStat.FreezeTimeScale] );
//if ( player != null )
// player.ForEachStatus( status => status.OnFreeze( this ) );
}
public void Fear( Player player )
{
if ( IsDying || this is Crate )
return;
var fear = AddEnemyStatus<FearEnemyStatus>();
fear.Player = player;
fear.SetLifetime( player?.Stats[PlayerStat.FearLifetime] ?? 4f );
if ( player != null )
{
Log.Info( $"player.Stats[PlayerStat.FearPainPercent]: {player.Stats[PlayerStat.FearPainPercent]}" );
if ( player.Stats[PlayerStat.FearPainPercent] > 0f )
{
if ( this is Boss )
fear.PainPercent = player.Stats[PlayerStat.FearPainPercent] * 0.1f;
else
fear.PainPercent = player.Stats[PlayerStat.FearPainPercent];
}
//player.ForEachStatus( status => status.OnFear( this ) );
}
}
protected virtual void OnDamagePlayer( Player player, float damage )
{
if ( player.Stats[PlayerStat.ThornsPercent] > 0f )
Damage( damage * player.Stats[PlayerStat.ThornsPercent] * player.GetDamageMultiplier(), player, addVel: Vector2.Zero, addTempWeight: 0f, isCrit: false );
if ( Game.Random.Float( 0f, 1f ) < player.Stats[PlayerStat.FreezeOnMeleeChance] )
{
if ( !HasEnemyStatus<FrozenEnemyStatus>() )
Manager.Instance.PlaySfxNearby( "frozen", Position2D, pitch: Game.Random.Float( 1.1f, 1.2f ), volume: 1.3f, maxDist: 5f );
Freeze( player );
}
if ( Game.Random.Float( 0f, 1f ) < player.Stats[PlayerStat.FearOnMeleeChance] )
{
if ( !HasEnemyStatus<FearEnemyStatus>() )
Manager.Instance.PlaySfxNearby( "fear", Position2D, pitch: Game.Random.Float( 0.95f, 1.05f ), volume: 0.6f, maxDist: 5f );
Fear( player );
}
}
public void Charm()
{
if ( IsCharmed || this is Crate || this is Boss )
return;
var charmed = AddEnemyStatus<CharmedEnemyStatus>();
charmed.Lifetime = 999f;
CharmDamageTakenMultiplier = Manager.Instance.Player.Stats[PlayerStat.CharmedEnemyDmgTakenMultiplier];
CharmDamageDealtMultiplier = Manager.Instance.Player.Stats[PlayerStat.CharmedEnemyDmgDealtMultiplier];
}
public void LookForEnemyTarget()
{
if ( Manager.Instance.CharmedEnemyCount == 0 )
return;
if ( !IsValid || IsRemoved || IsDying || this is Crate )
return;
Enemy closestTarget = null;
var closestDistSqr = float.MaxValue;
for ( int dx = -2; dx <= 2; dx++ )
{
for ( int dy = -2; dy <= 2; dy++ )
{
var gridSquare = new GridSquare( GridPos.x + dx, GridPos.y + dy );
if ( !Manager.Instance.ThingGridPositions.ContainsKey( gridSquare ) )
continue;
var things = Manager.Instance.ThingGridPositions[gridSquare];
if ( things.Count == 0 )
continue;
for ( int i = things.Count - 1; i >= 0; i-- )
{
if ( i >= things.Count )
continue;
var other = things[i];
if ( other == this || other.IsRemoved || !other.IsValid() )
continue;
if ( other is not Enemy enemy )
continue;
if ( IsCharmed == enemy.IsCharmed )
continue;
if ( enemy is Crate || enemy.IsDying || enemy.IgnoreCollision )
continue;
var dist_sqr = (Position2D - other.Position2D).LengthSquared;
if ( dist_sqr < closestDistSqr )
{
closestDistSqr = dist_sqr;
closestTarget = enemy;
}
}
}
}
Target = closestTarget;
if ( !IsCharmed )
{
float player_dist_sqr = (Manager.Instance.Player.Position2D - Position2D).LengthSquared;
if ( player_dist_sqr < closestDistSqr )
{
Target = Manager.Instance.Player;
}
}
_nextEnemyTargetDelay = Game.Random.Float( ENEMY_TARGET_DELAY_MIN, ENEMY_TARGET_DELAY_MAX );
_timeSinceLookForEnemyTarget = 0f;
}
public virtual void Celebrate()
{
if ( IsSpawning )
FinishSpawning();
}
}