351 lines
8.7 KiB
C#
351 lines
8.7 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.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<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 (_, { CurrentState: IPlayerKnockdownState }):
|
|
self.TransitionState<KnockedCatch>();
|
|
return;
|
|
case (_, { Size: { y: < 1f } }):
|
|
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 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<Idle>();
|
|
}
|
|
}
|
|
|
|
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<IPlayerMovement>(out _playerMovement);
|
|
switch (self.service.CombatTarget)
|
|
{
|
|
case { } x when x.TryGetComponent<IEntityMovement>(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<Entity>().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<Idle>();
|
|
}
|
|
}
|
|
|
|
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
|
|
{
|
|
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<PhysicsImpact>(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<Idle>();
|
|
}
|
|
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<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;
|
|
}
|
|
}
|
|
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();
|
|
}
|
|
}
|
|
} |