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 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(); 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); } /// /// 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) /// /// 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; } /// /// Randomly picks a usable attack. If no actions are usable right now, returns null. /// /// Action to attack with, or null 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; } } }