Enemy subclass representing a Zombie NPC. It sets stats, randomizes appearance and animation parameters, controls movement/attack/flinch/jump animations, and computes a walk speed factor based on animation progress.
using System;
using Sandbox;
public class Zombie : Enemy
{
public override EnemyType EnemyType => EnemyType.Zombie;
public override float MeleeForce => 20f;
public override float MeleeRagdollForce => Game.Random.Float( 0.4f, 1.25f );
public override float MeleeUpwardForceAmount => Game.Random.Float( 0f, 0.3f );
private int _bodyGroupIndex;
protected override bool HasLeftArm => _bodyGroupIndex != 4;
public override float GetMaxHealth()
{
switch( Manager.Instance.Difficulty )
{
case 0: default: return 30f;
case 1: return 35f;
case 2: return 37f;
}
}
public override Vector3 SpawnScale => new Vector3( 1f );
//public override float SpawnZPos => -10f;
protected override void OnStart()
{
base.OnStart();
CoinValueMin = 1;
CoinValueMax = 1;
CoinChance = 0.55f;
PushStrength = 5000f;
_personalSpeedScale = Game.Random.Float( 0.85f, 1.25f );
_personalSpeedFreq = Game.Random.Float( 6f, 12f );
_fullMeleeAttackAnimSpeed = 2.5f;
_bodyGroupIndex = Game.Random.Int( 0, 4 );
ModelRenderer.SetBodyGroup( 0, _bodyGroupIndex ); // value for part 0: 3 has right eye hanging out, 4 has no left arm
var blueTintAmount = this is ZombieTemporary ? 0f : Game.Random.Float( 0f, 0.2f );
TintFullHp = new Color( TintFullHp.r - blueTintAmount, TintFullHp.g - blueTintAmount, TintFullHp.b );
TintZeroHp = Color.Lerp( TintZeroHp, TintZeroHp.WithBlue( TintZeroHp.b + blueTintAmount ), 0.25f );
ModelRenderer.Tint = TintFullHp.WithAlpha( ModelRenderer.Tint.a );
if ( IsProxy )
return;
AggroRange = 100f;
DetectTargetRange = 250f;
LoseTargetRange = 700f;
LoseTargetTime = 3f;
MeleeDamage = Utils.Select( Manager.Instance.Difficulty, 5f, 7f, 8f );
DamageTargetDelay = 0.5f;
//_personalTurnSpeed = Game.Random.Float( 1.5f, 4f );
_personalTurnSpeed = Game.Random.Float( 3.5f, 5f );
//Acceleration = 110f;
//AccelerationAttacking = 130f;
Acceleration = 350f * Utils.Select( Manager.Instance.Difficulty, 0.9f, 1f, 1.1f );
AccelerationAttacking = 600f * Utils.Select( Manager.Instance.Difficulty, 0.9f, 1f, 1.25f );
Deceleration = 1.95f;
DecelerationAttacking = 1.75f;
}
protected override void OnUpdate()
{
base.OnUpdate();
//Gizmo.Draw.Color = Color.White;
//Gizmo.Draw.Text( $"CanAnimate: {CanAnimate} - {ModelRenderer.PlaybackRate.ToString("N2")}", new global::Transform( WorldPosition ) );
//Gizmo.Draw.Text( $"{ModelRenderer.SceneModel.CurrentSequence.Name}\nspeed: {ModelRenderer.SceneModel.PlaybackRate}\n_hitstopOldPlaybackSpeed: {_hitstopOldPlaybackSpeed}", new global::Transform( WorldPosition ) );
//Gizmo.Draw.Text( $"{ModelRenderer.SceneModel.CurrentSequence.TimeNormalized.ToString("N2")}", new global::Transform( WorldPosition ) );
//var progress = ModelRenderer.SceneModel.CurrentSequence.TimeNormalized;
//var animName = "Hurt";
//if ( progress > 0.3f && progress < 0.6f )
// animName = "Hurt_Left";
//else if ( progress < 0.2f || progress > 0.7f )
// animName = "Hurt_Right";
//Gizmo.Draw.Text( $"{ModelRenderer.SceneModel.CurrentSequence.TimeNormalized.ToString( "N2" )}\n{animName}", new global::Transform( WorldPosition ) );
}
protected override float GetMoveSpeedFactor()
{
var progress = ModelRenderer.SceneModel.CurrentSequence.TimeNormalized;
return Zombie.GetZombieMoveSpeedFactor( progress );
}
public static float GetZombieMoveSpeedFactor( float animProgress )
{
var leftFootStart = 0.18f;
var leftFootEnd = 0.28f;
var rightFootStart = 0.66f;
var rightFootEnd = 0.8f;
//if ( progress > leftFootStart && progress < leftFootEnd )
// return Utils.MapReturn( progress, leftFootStart, leftFootEnd, 0f, 1f, EasingType.Linear );
//else if( progress > rightFootStart && progress < rightFootEnd )
// return Utils.MapReturn( progress, rightFootStart, rightFootEnd, 0f, 1f, EasingType.Linear );
if ( animProgress > leftFootStart && animProgress < leftFootEnd )
return Utils.Map( animProgress, leftFootStart, leftFootEnd, 0f, 1f, EasingType.QuadOut );
else if ( animProgress > rightFootStart && animProgress < rightFootEnd )
return Utils.Map( animProgress, rightFootStart, rightFootEnd, 0f, 1f, EasingType.QuadOut );
return 0f;
//return (0.5f + Utils.FastSin( MoveTimeOffset + Manager.Instance.ElapsedTime * _personalSpeedFreq * (IsAttacking ? 2f : 1f) ) * 0.5f) * _personalSpeedScale; // todo: should "(IsAttacking ? 2f : 1f)" be there?
}
protected override void PlayWalkAnim()
{
//SetPlaybackRate( Game.Random.Float( 0.8f, 1.2f ) );
if( !IsStunned )
SetPlaybackRate( _personalSpeedScale );
SetAnim( "Walk" );
}
protected override void PlayAttackAnim()
{
SetPlaybackRate( _personalSpeedScale );
SetAnim( "Attack" );
}
protected override void PlayFlinchAnim()
{
var progress = ModelRenderer.SceneModel.CurrentSequence.TimeNormalized;
var currAnimName = ModelRenderer.SceneModel.CurrentSequence?.Name ?? string.Empty;
string animName;
if ( !string.IsNullOrEmpty( currAnimName ) && currAnimName.Contains( "Hurt" ) )
{
animName = ModelRenderer.SceneModel.CurrentSequence.Name;
}
else
{
if ( progress > 0.3f && progress < 0.6f )
animName = "Hurt_Left";
else if ( progress < 0.2f || progress > 0.7f )
animName = "Hurt_Right";
else
animName = "Hurt";
}
SetAnim( animName, forceRestart: true );
//SetPlaybackRate( 1f );
}
protected override void PlayJumpAnim()
{
SetAnim( "Jump" );
//SetPlaybackRate( Game.Random.Float(0.2f, 0.8f) );
}
//protected override void PlayCelebrateAnim( bool victory )
//{
// if ( victory )
// {
// //SetPlaybackRate( Game.Random.Float( 0.5f, 0.9f ) );
// SetAnim( "HeadPokeLoop" );
// }
// else
// {
// //SetPlaybackRate( Game.Random.Float( 0.8f, 1.4f ) );
// SetAnim( "Attack3" );
// }
//}
}