BITFALL/Assets/Artists/Scripts/Entities/AI/AIZombieStates.cs

351 lines
8.7 KiB
C#
Raw Normal View History

2024-04-19 00:40:34 +08:00
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;
2024-05-19 17:29:41 +08:00
using BITKit.Entities.Physics;
2024-04-19 00:40:34 +08:00
using BITKit.StateMachine;
using Cysharp.Threading.Tasks;
using Unity.Mathematics;
2024-05-19 17:29:41 +08:00
using UnityEditor;
2024-04-19 00:40:34 +08:00
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
{
2024-05-19 17:29:41 +08:00
[SerializeField] protected AnimationClip[] clips;
2024-04-19 00:40:34 +08:00
public override async void OnStateEntry(IState old)
{
base.OnStateEntry(old);
2024-04-26 03:55:43 +08:00
var state = animancerComponent.Play(clips.Random(),0.2f);
2024-04-19 00:40:34 +08:00
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 }):
2024-05-19 17:29:41 +08:00
self.TransitionState<KnockedCatch>();
return;
case (_, { Size: { y: < 1f } }):
2024-04-19 00:40:34 +08:00
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)
});
}
}
}
2024-05-19 17:29:41 +08:00
[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
});
}
}
2024-04-19 00:40:34 +08:00
[Serializable]
public sealed class Catch : AIZombieActionState
{
private IEntityMovement _targetMovement;
2024-05-19 17:29:41 +08:00
private IPlayerMovement _playerMovement;
[Inject]private IEntityMovement _movement;
public override void OnStateEntry(IState old)
2024-04-19 00:40:34 +08:00
{
2024-05-19 17:29:41 +08:00
self.service.CombatTarget.TryGetComponent<IPlayerMovement>(out _playerMovement);
2024-04-19 00:40:34 +08:00
switch (self.service.CombatTarget)
{
case { } x when x.TryGetComponent<IEntityMovement>(out _targetMovement):
_targetMovement.ExecuteCommand(new PlayerDisableMovementCommand()
{
Disable = true,
Duration = 5,
LockFile = this
});
2024-05-19 17:29:41 +08:00
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
});
2024-04-19 00:40:34 +08:00
break;
}
2024-05-19 17:29:41 +08:00
2024-04-19 00:40:34 +08:00
base.OnStateEntry(old);
}
2024-05-19 17:29:41 +08:00
public override void OnStateUpdate(float deltaTime)
{
base.OnStateUpdate(deltaTime);
if (_playerMovement is{AllowMovement:true})
{
self.TransitionState<Idle>();
}
}
2024-04-19 00:40:34 +08:00
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
{
2024-04-26 03:55:43 +08:00
private PhysicsImpact[] impacts;
[Inject] private IEntityPhysics _physics;
private bool _shouldRelease;
2024-05-19 17:29:41 +08:00
public override async void OnStateEntry(IState old)
2024-04-19 00:40:34 +08:00
{
_shouldRelease = true;
var state = animancerComponent.Play(clips.Random(),0.2f);
2024-05-19 17:29:41 +08:00
self.entityOverride.AddOverride(this);
2024-04-19 00:40:34 +08:00
self.health.OnDamageVoid += OnDamageVoid;
2024-05-19 17:29:41 +08:00
self.health.OnSetAlive += OnSetAlive;
2024-04-26 03:55:43 +08:00
impacts = self.GetComponentsInChildren<PhysicsImpact>(true);
foreach (var x in impacts)
{
x.OnPhysicsCollisionEnter += OnPhysicsCollisionEnter;
}
2024-05-19 17:29:41 +08:00
await state;
_physics.DisablePhysics.AddElement(this);
2024-05-19 17:29:41 +08:00
_shouldRelease = false;
self.entityOverride.RemoveOverride(this);
2024-05-19 17:29:41 +08:00
}
private void OnSetAlive(bool obj)
{
if(obj)self.TransitionState<Idle>();
2024-04-19 00:40:34 +08:00
}
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;
}
2024-04-19 00:40:34 +08:00
}
2024-04-26 03:55:43 +08:00
private void OnDamageVoid(DamageMessage obj)
{
if (_shouldRelease)
2024-05-19 17:29:41 +08:00
Exit();
2024-04-26 03:55:43 +08:00
}
private async void Exit()
2024-04-19 00:40:34 +08:00
{
_physics.DisablePhysics.RemoveElement(this); _shouldRelease = false;
var velocity = self.animancerComponent.Animator.velocity;
2024-05-19 17:29:41 +08:00
self.entityOverride.RemoveOverride(this);
await UniTask.Delay(50);
if (self.destroyCancellationToken.IsCancellationRequested) return;
foreach (var x in self.GetComponentsInChildren<Rigidbody>())
2024-08-11 12:46:15 +08:00
{
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;
}
2024-08-11 12:46:15 +08:00
}
animancerComponent.Stop();
2024-05-19 17:29:41 +08:00
2024-04-19 00:40:34 +08:00
}
2024-04-26 03:55:43 +08:00
private void OnPhysicsCollisionEnter(Collision obj)
{
if (!_shouldRelease) return;
2024-04-26 03:55:43 +08:00
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();
}
2024-04-19 00:40:34 +08:00
}
}