You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
170 lines
5.8 KiB
C#
170 lines
5.8 KiB
C#
3 weeks ago
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using Unity.BossRoom.Gameplay.Actions;
|
||
|
using UnityEngine;
|
||
|
using Action = Unity.BossRoom.Gameplay.Actions.Action;
|
||
|
using Random = UnityEngine.Random;
|
||
|
|
||
|
namespace Unity.BossRoom.Gameplay.GameplayObjects.Character.AI
|
||
|
{
|
||
|
public class AttackAIState : AIState
|
||
|
{
|
||
|
private AIBrain m_Brain;
|
||
|
private ServerActionPlayer m_ServerActionPlayer;
|
||
|
private ServerCharacter m_Foe;
|
||
|
private Action m_CurAttackAction;
|
||
|
|
||
|
List<Action> m_AttackActions;
|
||
|
|
||
|
public AttackAIState(AIBrain brain, ServerActionPlayer serverActionPlayer)
|
||
|
{
|
||
|
m_Brain = brain;
|
||
|
m_ServerActionPlayer = serverActionPlayer;
|
||
|
}
|
||
|
|
||
|
public override bool IsEligible()
|
||
|
{
|
||
|
return m_Foe != null || ChooseFoe() != null;
|
||
|
}
|
||
|
|
||
|
public override void Initialize()
|
||
|
{
|
||
|
m_AttackActions = new List<Action>();
|
||
|
if (m_Brain.CharacterData.Skill1 != null)
|
||
|
{
|
||
|
m_AttackActions.Add(m_Brain.CharacterData.Skill1);
|
||
|
}
|
||
|
if (m_Brain.CharacterData.Skill2 != null)
|
||
|
{
|
||
|
m_AttackActions.Add(m_Brain.CharacterData.Skill2);
|
||
|
}
|
||
|
if (m_Brain.CharacterData.Skill3 != null)
|
||
|
{
|
||
|
m_AttackActions.Add(m_Brain.CharacterData.Skill3);
|
||
|
}
|
||
|
|
||
|
// pick a starting attack action from the possible
|
||
|
m_CurAttackAction = m_AttackActions[Random.Range(0, m_AttackActions.Count)];
|
||
|
|
||
|
// clear any old foe info; we'll choose a new one in Update()
|
||
|
m_Foe = null;
|
||
|
}
|
||
|
|
||
|
public override void Update()
|
||
|
{
|
||
|
if (!m_Brain.IsAppropriateFoe(m_Foe))
|
||
|
{
|
||
|
// time for a new foe!
|
||
|
m_Foe = ChooseFoe();
|
||
|
// whatever we used to be doing, stop that. New plan is coming!
|
||
|
m_ServerActionPlayer.ClearActions(true);
|
||
|
}
|
||
|
|
||
|
// if we're out of foes, stop! IsEligible() will now return false so we'll soon switch to a new state
|
||
|
if (!m_Foe)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// see if we're already chasing or attacking our active foe!
|
||
|
if (m_ServerActionPlayer.GetActiveActionInfo(out var info))
|
||
|
{
|
||
|
if (GameDataSource.Instance.GetActionPrototypeByID(info.ActionID).IsChaseAction)
|
||
|
{
|
||
|
if (info.TargetIds != null && info.TargetIds[0] == m_Foe.NetworkObjectId)
|
||
|
{
|
||
|
// yep we're chasing our foe; all set! (The attack is enqueued after it)
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
else if (info.ActionID == m_CurAttackAction.ActionID)
|
||
|
{
|
||
|
if (info.TargetIds != null && info.TargetIds[0] == m_Foe.NetworkObjectId)
|
||
|
{
|
||
|
// yep we're attacking our foe; all set!
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
else if (GameDataSource.Instance.GetActionPrototypeByID(info.ActionID).IsStunAction)
|
||
|
{
|
||
|
// we can't do anything right now. We're stunned!
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// choose the attack to use
|
||
|
m_CurAttackAction = ChooseAttack();
|
||
|
if (m_CurAttackAction == null)
|
||
|
{
|
||
|
// no actions are usable right now
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// attack!
|
||
|
var attackData = new ActionRequestData
|
||
|
{
|
||
|
ActionID = m_CurAttackAction.ActionID,
|
||
|
TargetIds = new ulong[] { m_Foe.NetworkObjectId },
|
||
|
ShouldClose = true,
|
||
|
Direction = m_Brain.GetMyServerCharacter().physicsWrapper.Transform.forward
|
||
|
};
|
||
|
m_ServerActionPlayer.PlayAction(ref attackData);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Picks the most appropriate foe for us to attack right now, or null if none are appropriate
|
||
|
/// (Currently just chooses the foe closest to us in distance)
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
private ServerCharacter ChooseFoe()
|
||
|
{
|
||
|
Vector3 myPosition = m_Brain.GetMyServerCharacter().physicsWrapper.Transform.position;
|
||
|
|
||
|
float closestDistanceSqr = int.MaxValue;
|
||
|
ServerCharacter closestFoe = null;
|
||
|
foreach (var foe in m_Brain.GetHatedEnemies())
|
||
|
{
|
||
|
float distanceSqr = (myPosition - foe.physicsWrapper.Transform.position).sqrMagnitude;
|
||
|
if (distanceSqr < closestDistanceSqr)
|
||
|
{
|
||
|
closestDistanceSqr = distanceSqr;
|
||
|
closestFoe = foe;
|
||
|
}
|
||
|
}
|
||
|
return closestFoe;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Randomly picks a usable attack. If no actions are usable right now, returns null.
|
||
|
/// </summary>
|
||
|
/// <returns>Action to attack with, or null</returns>
|
||
|
private Action ChooseAttack()
|
||
|
{
|
||
|
// make a random choice
|
||
|
int idx = Random.Range(0, m_AttackActions.Count);
|
||
|
|
||
|
// now iterate through our options to find one that's currently usable
|
||
|
bool anyUsable;
|
||
|
do
|
||
|
{
|
||
|
anyUsable = false;
|
||
|
foreach (var attack in m_AttackActions)
|
||
|
{
|
||
|
if (m_ServerActionPlayer.IsReuseTimeElapsed(attack.ActionID))
|
||
|
{
|
||
|
anyUsable = true;
|
||
|
if (idx == 0)
|
||
|
{
|
||
|
return attack;
|
||
|
}
|
||
|
--idx;
|
||
|
}
|
||
|
}
|
||
|
} while (anyUsable);
|
||
|
|
||
|
// none of our actions are available now
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
}
|