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

266 lines
7.0 KiB
C#

using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data.Odbc;
using System.Linq;
using BITKit;
using BITKit.AI.States;
using BITKit.Entities;
using BITKit.Sensors;
using BITKit.StateMachine;
using UnityEngine;
using UnityEngine.AI;
namespace BITFALL.AI.States
{
public abstract class AIRuntimeState : AIState
{
[Inject,HideInInspector] public AIService Service;
private readonly ConcurrentDictionary<int, bool> _friends = new();
private readonly ConcurrentDictionary<int, bool> _enemies = new();
protected readonly HashSet<int> ignoreNoises = new();
protected readonly CacheList<IEntity> detectedEnemies = new();
protected readonly CacheList<IEntity> hearEnemies = new();
public bool IsFriend(IEntity target)
{
return _friends.GetOrAdd(target.Id, IsFriendInternal);
}
public bool IsEnemy(IEntity target)
{
return _enemies.GetOrAdd(target.Id, IsEnemyInternal);
}
public override void OnStateUpdate(float deltaTime)
{
base.OnStateUpdate(deltaTime);
detectedEnemies.Clear();
foreach (var x in Service.RangeSensor.Get())
{
if (x.TryGetComponent<IEntity>(out var entity) is false) continue;
if (IsEnemy(entity) is false)
{
foreach (var _collider in entity.As<MonoBehaviour>().GetComponents<Collider>())
{
Service.RangeSensor.Ignores.Add(_collider.GetInstanceID());
; }
continue;
}
detectedEnemies.Add(entity);
}
hearEnemies.Clear();
foreach (var x in Service.AudioSensor.Get())
{
if (x.TryGetComponent<IEntity>(out var entity) is false) continue;
if (IsEnemy(entity) is false)
{
foreach (var _collider in entity.As<MonoBehaviour>().GetComponents<Collider>())
{
Service.AudioSensor.Ignores.Add(_collider.GetInstanceID());
}
continue;
}
hearEnemies.Add(entity);
}
}
private bool IsEnemyInternal(int id)
{
var target = UnityEntitiesService.Get(id);
var tags = target.As<MonoBehaviour>().GetComponent<ITag>().GetTags();
foreach (var x in Service.EnemyTag.GetTags())
{
if (tags.Contains(x)) return true;
}
return false;
}
private bool IsFriendInternal(int id)
{
var target = UnityEntitiesService.Get(id);
var tags = target.As<MonoBehaviour>().GetComponent<ITag>().GetTags();
foreach (var x in Service.FriendTag.GetTags())
{
if (tags.Contains(x)) return true;
}
return false;
}
}
[Serializable]
[CustomType(typeof(AI_Idle))]
public sealed class Idle : AIRuntimeState, AI_Idle
{
public override void OnStateEntry(IState old)
{
base.OnStateEntry(old);
}
public override void OnStateUpdate(float deltaTime)
{
base.OnStateUpdate(deltaTime);
//foreach (var x in Service.AudioSensor.Get().Union(Service.RangeSensor.Get()))
foreach (var entity in detectedEnemies)
{
Service.CombatTarget = entity;
Service.TransitionState<Alert>();
return;
}
foreach (var entity in hearEnemies)
{
Service.CombatTarget = entity;
Service.TransitionState<Alert>();
return;
}
foreach (var x in Service.AudioSensor.Noises)
{
if(ignoreNoises.Add(x.Id) is false)continue;
var distance = Vector3.Distance(Service.transform.position, x.Position);
//Debug.Log($"{x.Radius}:{distance}");
if (distance <= x.Radius && NavMesh.SamplePosition(x.Position,out var hit,1,NavMesh.AllAreas))
{
Service.Agent.SetDestination(hit.position);
}
return;
}
}
}
[Serializable]
[CustomType(typeof(AI_Idle))]
public sealed class Alert : AIRuntimeState, AI_Alert
{
public int AlertLevel { get; set; }
public float RemainingAlertTime { get; set; }
[SerializeField] private int initialAlertTime;
[SerializeField] private int immediatelyDetectDistance = 5;
[SerializeField,ReadOnly]private float detectedWeight;
public override void OnStateEntry(IState old)
{
base.OnStateEntry(old);
RemainingAlertTime = initialAlertTime;
detectedWeight = old switch
{
AI_Combat=>0.5f,
_ => 0
};
}
public override void OnStateUpdate(float deltaTime)
{
base.OnStateUpdate(deltaTime);
if (detectedEnemies.Count is 0)
{
foreach (var entity in hearEnemies)
{
Service.Agent.SetDestination(entity.As<MonoBehaviour>().transform.position);
return;
}
detectedWeight -= deltaTime;
}
else
{
foreach (var entity in detectedEnemies)
{
var distance= Vector3.Distance(Service.transform.position, entity.As<MonoBehaviour>().transform.position);
entity.TryGetComponent<ISensorTarget>(out var sensorTarget);
if(distance<=immediatelyDetectDistance)
{
Service.CombatTarget = entity;
Service.TransitionState<Combat>();
sensorTarget?.Detected(1,Service.RangeSensor,Service.transform);
return;
}
detectedWeight += deltaTime;
sensorTarget?.Detected(detectedWeight,Service.RangeSensor,Service.transform);
if (detectedWeight >= 1)
{
Service.CombatTarget = entity;
Service.TransitionState<Combat>();
return;
}
}
}
RemainingAlertTime -= deltaTime;
if(RemainingAlertTime<=0)
{
Service.TransitionState<Idle>();
return;
}
}
}
[Serializable]
[CustomType(typeof(AI_Idle))]
public sealed class Combat : AIRuntimeState, AI_Combat
{
public AITarget CurrentTarget => _currentTarget;
private AITarget _currentTarget;
[SerializeField] private float lostTargetTime;
private readonly IntervalUpdate reTargetInterval = new(2);
[SerializeField, ReadOnly] private float currentLostTargetTime;
public override void OnStateEntry(IState old)
{
base.OnStateEntry(old);
Service.RangeSensor.DetectedHeightWeight = 1;
currentLostTargetTime = lostTargetTime;
reTargetInterval.Reset();
Service.CombatTarget.TryGetComponent<AITarget>(out _currentTarget);
Service.Agent.isStopped = false;
}
public override void OnStateExit(IState old, IState newState)
{
base.OnStateExit(old, newState);
Service.RangeSensor.DetectedHeightWeight = 0.5f;
Service.RangeSensor.transform.localRotation = Quaternion.identity;
}
public override void OnStateUpdate(float deltaTime)
{
base.OnStateUpdate(deltaTime);
var position = Service.CombatTarget.As<MonoBehaviour>().transform.position;
var direction = position - Service.transform.position;
direction = Vector3.ProjectOnPlane(direction, Vector3.up);
if (direction.magnitude > 0.01)
{
Service.RangeSensor.transform.rotation = Quaternion.LookRotation(direction);
}
if (NavMesh.SamplePosition(position, out var hit, 1, NavMesh.AllAreas))
{
Service.Agent.SetDestination(hit.position);
}
foreach (var entity in detectedEnemies)
{
currentLostTargetTime = lostTargetTime;
if (reTargetInterval.AllowUpdate)
{
Service.CombatTarget = entity;
Service.CombatTarget.TryGetComponent<AITarget>(out _currentTarget);
}
return;
}
foreach (var entity in hearEnemies)
{
currentLostTargetTime = lostTargetTime;
}
currentLostTargetTime -= deltaTime;
if (currentLostTargetTime <= 0)
{
Service.TransitionState<Alert>();
}
}
}
}