using System;
using System.Collections.Generic;
using Unity.BossRoom.Gameplay.Configuration;
using Unity.BossRoom.Gameplay.Actions;
using UnityEngine;
namespace Unity.BossRoom.Gameplay.GameplayObjects.Character.AI
{
///
/// Handles enemy AI. Contains AIStateLogics that handle some of the details,
/// and has various utility functions that are called by those AIStateLogics
///
public class AIBrain
{
private enum AIStateType
{
ATTACK,
//WANDER,
IDLE,
}
static readonly AIStateType[] k_AIStates = (AIStateType[])Enum.GetValues(typeof(AIStateType));
private ServerCharacter m_ServerCharacter;
private ServerActionPlayer m_ServerActionPlayer;
private AIStateType m_CurrentState;
private Dictionary m_Logics;
private List m_HatedEnemies;
///
/// If we are created by a spawner, the spawner might override our detection radius
/// -1 is a sentinel value meaning "no override"
///
private float m_DetectRangeOverride = -1;
public AIBrain(ServerCharacter me, ServerActionPlayer myServerActionPlayer)
{
m_ServerCharacter = me;
m_ServerActionPlayer = myServerActionPlayer;
m_Logics = new Dictionary
{
[AIStateType.IDLE] = new IdleAIState(this),
//[ AIStateType.WANDER ] = new WanderAIState(this), // not written yet
[AIStateType.ATTACK] = new AttackAIState(this, m_ServerActionPlayer),
};
m_HatedEnemies = new List();
m_CurrentState = AIStateType.IDLE;
}
///
/// Should be called by the AIBrain's owner each Update()
///
public void Update()
{
AIStateType newState = FindBestEligibleAIState();
if (m_CurrentState != newState)
{
m_Logics[newState].Initialize();
}
m_CurrentState = newState;
m_Logics[m_CurrentState].Update();
}
///
/// Called when we received some HP. Positive HP is healing, negative is damage.
///
/// The person who hurt or healed us. May be null.
/// The amount of HP received. Negative is damage.
public void ReceiveHP(ServerCharacter inflicter, int amount)
{
if (inflicter != null && amount < 0)
{
Hate(inflicter);
}
}
private AIStateType FindBestEligibleAIState()
{
// for now we assume the AI states are in order of appropriateness,
// which may be nonsensical when there are more states
foreach (AIStateType aiStateType in k_AIStates)
{
if (m_Logics[aiStateType].IsEligible())
{
return aiStateType;
}
}
Debug.LogError("No AI states are valid!?!");
return AIStateType.IDLE;
}
///
/// Returns true if it be appropriate for us to murder this character, starting right now!
///
public bool IsAppropriateFoe(ServerCharacter potentialFoe)
{
if (potentialFoe == null ||
potentialFoe.IsNpc ||
potentialFoe.LifeState != LifeState.Alive ||
potentialFoe.IsStealthy.Value)
{
return false;
}
// Also, we could use NavMesh.Raycast() to see if we have line of sight to foe?
return true;
}
///
/// Notify the AIBrain that we should consider this character an enemy.
///
///
public void Hate(ServerCharacter character)
{
if (!m_HatedEnemies.Contains(character))
{
m_HatedEnemies.Add(character);
}
}
///
/// Return the raw list of hated enemies -- treat as read-only!
///
public List GetHatedEnemies()
{
// first we clean the list -- remove any enemies that have disappeared (became null), are dead, etc.
for (int i = m_HatedEnemies.Count - 1; i >= 0; i--)
{
if (!IsAppropriateFoe(m_HatedEnemies[i]))
{
m_HatedEnemies.RemoveAt(i);
}
}
return m_HatedEnemies;
}
///
/// Retrieve info about who we are. Treat as read-only!
///
///
public ServerCharacter GetMyServerCharacter()
{
return m_ServerCharacter;
}
///
/// Convenience getter that returns the CharacterData associated with this creature.
///
public CharacterClass CharacterData
{
get
{
return GameDataSource.Instance.CharacterDataByType[m_ServerCharacter.CharacterType];
}
}
///
/// The range at which this character can detect enemies, in meters.
/// This is usually the same value as is indicated by our game data, but it
/// can be dynamically overridden.
///
public float DetectRange
{
get
{
return (m_DetectRangeOverride == -1) ? CharacterData.DetectRange : m_DetectRangeOverride;
}
set
{
m_DetectRangeOverride = value;
}
}
}
}