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.Entities.Physics; using BITKit.StateMachine; using Cysharp.Threading.Tasks; using Unity.Mathematics; using UnityEditor; 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] protected AnimationClip[] clips; public override async void OnStateEntry(IState old) { base.OnStateEntry(old); var state = animancerComponent.Play(clips.Random(),0.2f); self.entityOverride.AddOverride(this); await state; if (Enabled is false || state.IsValid is false) return; self.TransitionState(); } 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().transform.position, self.transform.position); if (distance <= attackDistance) { self.service.CombatTarget.TryGetComponent(out var movement); switch (Random.Range(0, 4), movement) { case (_, { CurrentState: IPlayerKnockdownState }): self.TransitionState(); return; case (_, { Size: { y: < 1f } }): self.TransitionState(); return; default: self.TransitionState(); 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(out var collider)) { _combat.SetConfig(new MeleeConfig { Position = position, Rotation = Quaternion.LookRotation(collider.bounds.center - position) }); } } } [Serializable] public sealed class KnockedCatch : AIZombieActionState { [Inject] private IMeleeCombat _combat; public sealed class Component { [Inject]public IEntityMovement _targetMovement; [Inject]public IHealth _targetHealth; } [SerializeField] private AnimationClip entryClip; [SerializeField] private AnimationClip loopClip; [SerializeField] private AnimationClip exitClip; private bool isExit; private readonly Component component=new(); private MeleeConfig _meleeConfig; public override async void OnStateEntry(IState old) { isExit = false; self.service.CombatTarget.Inject(component); self.entityOverride.AddOverride(this); await animancerComponent.Play(entryClip); if(!Enabled)return; animancerComponent.Play(loopClip); component._targetMovement.ExecuteCommand(new PlayerDisableMovementCommand() { Disable = true, LockFile = this }); _meleeConfig.Position = self.transform.position + Vector3.up; _meleeConfig.Rotation = self.transform.rotation; } public override async void OnStateUpdate(float deltaTime) { base.OnStateUpdate(deltaTime); if (isExit) return; _combat.SetConfig(_meleeConfig); if(component._targetMovement.CurrentState is not IPlayerKnockdownState || component._targetHealth.IsAlive is false) { isExit = true; await animancerComponent.Play(exitClip); self.TransitionState(); } } public override void OnStateExit(IState old, IState newState) { self.entityOverride.RemoveOverride(this); component._targetMovement.ExecuteCommand(new PlayerDisableMovementCommand() { Disable = false, LockFile = this }); } } [Serializable] public sealed class Catch : AIZombieActionState { private IEntityMovement _targetMovement; private IPlayerMovement _playerMovement; [Inject]private IEntityMovement _movement; public override void OnStateEntry(IState old) { self.service.CombatTarget.TryGetComponent(out _playerMovement); switch (self.service.CombatTarget) { case { } x when x.TryGetComponent(out _targetMovement): _targetMovement.ExecuteCommand(new PlayerDisableMovementCommand() { Disable = true, Duration = 5, LockFile = this }); var direction = (_targetMovement.Position - self.transform.position).normalized; direction=Vector3.ProjectOnPlane(direction,Vector3.up); _movement.Rotation = Quaternion.LookRotation(direction); self.service.CombatTarget.As().Invoke(new DamageMessage() { Damage = 0 }); break; } base.OnStateEntry(old); } public override void OnStateUpdate(float deltaTime) { base.OnStateUpdate(deltaTime); if (_playerMovement is{AllowMovement:true}) { self.TransitionState(); } } 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(); break; case false when self.CurrentState is not Death: self.TransitionState(); break; } } } [Serializable] public sealed class Death : AIZombieActionState { private PhysicsImpact[] impacts; [Inject] private IEntityPhysics _physics; private bool _shouldRelease; public override async void OnStateEntry(IState old) { _shouldRelease = true; var state = animancerComponent.Play(clips.Random(),0.2f); self.entityOverride.AddOverride(this); self.health.OnDamageVoid += OnDamageVoid; self.health.OnSetAlive += OnSetAlive; impacts = self.GetComponentsInChildren(true); foreach (var x in impacts) { x.OnPhysicsCollisionEnter += OnPhysicsCollisionEnter; } await state; _physics.DisablePhysics.AddElement(this); _shouldRelease = false; self.entityOverride.RemoveOverride(this); } private void OnSetAlive(bool obj) { if(obj)self.TransitionState(); } public override void OnStateExit(IState old, IState newState) { base.OnStateExit(old, newState); self.health.OnDamageVoid -= OnDamageVoid; self.health.OnSetAlive -= OnSetAlive; _physics.DisablePhysics.RemoveElement(this); foreach (var x in impacts) { x.OnPhysicsCollisionEnter -= OnPhysicsCollisionEnter; } } private void OnDamageVoid(DamageMessage obj) { if (_shouldRelease) Exit(); } private async void Exit() { _physics.DisablePhysics.RemoveElement(this); _shouldRelease = false; var velocity = self.animancerComponent.Animator.velocity; self.entityOverride.RemoveOverride(this); await UniTask.Delay(50); if (self.destroyCancellationToken.IsCancellationRequested) return; foreach (var x in self.GetComponentsInChildren()) { if(x.isKinematic) { //BIT4Log.Warning($"{x.gameObject.name} is kinematic"); } else { //BIT4Log.Log($"Setting {x.gameObject.name} velocity to {velocity}"); x.velocity = velocity; } } animancerComponent.Stop(); } private void OnPhysicsCollisionEnter(Collision obj) { if (!_shouldRelease) return; var startPos = self.transform.position; var endPos = startPos; startPos.y += 0.2f; endPos.y -= 0.2f; if(Physics.Linecast(startPos,endPos,out var hit,LayerMask.GetMask("Default"))) { if (obj.collider == hit.collider) return; } Exit(); } } }