201 lines
4.9 KiB
C#
201 lines
4.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Animancer;
|
|
using BITFALL.Combat;
|
|
using BITFALL.Player.Movement;
|
|
using BITKit;
|
|
using BITKit.AI.States;
|
|
using BITKit.Entities;
|
|
using BITKit.Entities.Melee;
|
|
using BITKit.StateMachine;
|
|
using Cysharp.Threading.Tasks;
|
|
using Unity.Mathematics;
|
|
using UnityEngine;
|
|
using Random = UnityEngine.Random;
|
|
|
|
namespace BITFALL.AI.Zombie.States
|
|
{
|
|
[Serializable]
|
|
public abstract class AIZombieState : IState
|
|
{
|
|
[Inject] protected AIZombie self;
|
|
protected AnimancerComponent animancerComponent => self.animancerComponent;
|
|
public virtual bool Enabled { get; set; }
|
|
|
|
public virtual void Initialize()
|
|
{
|
|
|
|
}
|
|
|
|
public virtual void OnStateEntry(IState old)
|
|
{
|
|
|
|
}
|
|
|
|
public virtual void OnStateUpdate(float deltaTime)
|
|
{
|
|
}
|
|
|
|
public virtual void OnStateExit(IState old, IState newState)
|
|
{
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
public abstract class AIZombieActionState : AIZombieState
|
|
{
|
|
[SerializeField] private AnimationClip[] clips;
|
|
|
|
public override async void OnStateEntry(IState old)
|
|
{
|
|
base.OnStateEntry(old);
|
|
var state = animancerComponent.Layers[1].Play(clips.Random());
|
|
self.entityOverride.AddOverride(this);
|
|
await state;
|
|
if (Enabled is false || state.IsValid is false) return;
|
|
state.Stop();
|
|
self.TransitionState<Idle>();
|
|
}
|
|
public override void OnStateExit(IState old, IState newState)
|
|
{
|
|
base.OnStateExit(old, newState);
|
|
self.entityOverride.RemoveOverride(this);
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
public sealed class Idle : AIZombieState
|
|
{
|
|
[SerializeField] private float attackDistance;
|
|
|
|
public override void OnStateUpdate(float deltaTime)
|
|
{
|
|
base.OnStateUpdate(deltaTime);
|
|
switch (self.service.CurrentState)
|
|
{
|
|
case AI_Combat when self.service.CombatTarget is not null:
|
|
var distance = Vector3.Distance(self.service.CombatTarget.As<MonoBehaviour>().transform.position,
|
|
self.transform.position);
|
|
if (distance <= attackDistance)
|
|
{
|
|
self.service.CombatTarget.TryGetComponent<IEntityMovement>(out var movement);
|
|
switch (Random.Range(0, 4), movement)
|
|
{
|
|
case (_, { Size: { y: < 1f } }):
|
|
case (_, { CurrentState: IPlayerKnockdownState }):
|
|
self.TransitionState<Attack>();
|
|
return;
|
|
default:
|
|
self.TransitionState<Catch>();
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
public sealed class Attack : AIZombieActionState
|
|
{
|
|
[Inject] private IMeleeCombat _combat;
|
|
|
|
public override void OnStateEntry(IState old)
|
|
{
|
|
base.OnStateEntry(old);
|
|
var position = self.transform.position+Vector3.up;
|
|
if (self.service.CombatTarget is MonoBehaviour monoBehaviour &&
|
|
monoBehaviour.TryGetComponent<CapsuleCollider>(out var collider))
|
|
{
|
|
_combat.SetConfig(new MeleeConfig
|
|
{
|
|
Position = position,
|
|
Rotation = Quaternion.LookRotation(collider.bounds.center - position)
|
|
});
|
|
}
|
|
}
|
|
}
|
|
[Serializable]
|
|
public sealed class Catch : AIZombieActionState
|
|
{
|
|
private IEntityMovement _targetMovement;
|
|
public override async void OnStateEntry(IState old)
|
|
{
|
|
switch (self.service.CombatTarget)
|
|
{
|
|
case { } x when x.TryGetComponent<IEntityMovement>(out _targetMovement):
|
|
_targetMovement.ExecuteCommand(new PlayerDisableMovementCommand()
|
|
{
|
|
Disable = true,
|
|
Duration = 5,
|
|
LockFile = this
|
|
});
|
|
break;
|
|
}
|
|
base.OnStateEntry(old);
|
|
}
|
|
public override void OnStateExit(IState old, IState newState)
|
|
{
|
|
base.OnStateExit(old, newState);
|
|
_targetMovement.ExecuteCommand(new PlayerDisableMovementCommand()
|
|
{
|
|
Disable = false,
|
|
LockFile = this
|
|
});
|
|
}
|
|
}
|
|
[Serializable]
|
|
public sealed class Damaged : AIZombieActionState
|
|
{
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
self.health.OnDamageRelease += OnDamageRelease;
|
|
}
|
|
private void OnDamageRelease(DamageMessage obj)
|
|
{
|
|
switch (self.health.IsAlive)
|
|
{
|
|
case true when self.CurrentState is not Damaged:
|
|
self.TransitionState<Damaged>();
|
|
break;
|
|
case false when self.CurrentState is not Death:
|
|
self.TransitionState<Death>();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
[Serializable]
|
|
public sealed class Death : AIZombieActionState
|
|
{
|
|
public override void OnStateEntry(IState old)
|
|
{
|
|
base.OnStateEntry(old);
|
|
self.health.OnDamageVoid += OnDamageVoid;
|
|
}
|
|
public override void OnStateExit(IState old, IState newState)
|
|
{
|
|
base.OnStateExit(old, newState);
|
|
self.health.OnDamageVoid -= OnDamageVoid;
|
|
}
|
|
private async void OnDamageVoid(DamageMessage obj)
|
|
{
|
|
var velocity = self.animancerComponent.Animator.velocity;
|
|
animancerComponent.Stop();
|
|
await UniTask.Delay(50);
|
|
if (self.destroyCancellationToken.IsCancellationRequested) return;
|
|
foreach (var x in self.GetComponentsInChildren<Rigidbody>())
|
|
{
|
|
if(x.isKinematic)
|
|
{
|
|
//BIT4Log.Warning<AIZombieState>($"{x.gameObject.name} is kinematic");
|
|
}
|
|
else
|
|
{
|
|
//BIT4Log.Log<AIZombieState>($"Setting {x.gameObject.name} velocity to {velocity}");
|
|
x.velocity = velocity;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |