/** * Copyright 2019 The Knights Of Unity, created by Piotr Stoch * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System.Collections.Generic; using System.Linq; using DemoGame.Scripts.Gameplay.NetworkCommunication; using DemoGame.Scripts.Gameplay.NetworkCommunication.MatchStates; using DemoGame.Scripts.Gameplay.Nodes; using UnityEngine; namespace DemoGame.Scripts.Gameplay.Units { /// /// Base class for unit AI /// public class UnitAI : MonoBehaviour { public struct UnitMove { public readonly Unit Unit; public readonly Node Node; public UnitMove(Unit unit, Node node) { Unit = unit; Node = node; } } [SerializeField] protected Unit _unit; /// /// Current enemy /// protected Unit _enemy; #region MONO /// /// Movement and attack loop /// protected virtual void Update() { if (MatchCommunicationManager.Instance.IsHost && _unit.IsDestroyed == false) { if (_enemy) { if (_unit.CanAttack) { if (_unit.CurrentNode.ConnectedNodes.ContainsKey(_enemy.CurrentNode)) { SendAttackRequest(_enemy, _unit.Damage, _unit.AttackType); } } if (_unit.CanMove) { FindAndSetEnemy(); if (!_unit.CurrentNode.ConnectedNodes.ContainsKey(_enemy.CurrentNode)) { Node nextNode = SelectNextNode(); if (nextNode) { SendMoveRequest(nextNode); } } } } else { FindAndSetEnemy(); } } } protected virtual void OnDestroy() { if (_enemy) { _enemy.OnDestroyed -= FindAndSetEnemy; } } #endregion #region PUBLIC METHODS /// /// Search for nearest enemy and moves towards it /// public virtual void FindAndSetEnemy() { if (_enemy) { _enemy.OnDestroyed -= FindAndSetEnemy; } _enemy = UnitsManager.Instance.GetNearestEnemyUnit(_unit.OwnerColor, _unit); if (_enemy) { _enemy.OnDestroyed += FindAndSetEnemy; } } /// /// Returns true if unit can move to other node and still be in contact with current enemy /// /// /// public virtual bool RearrangeIfCan(Stack unitsMovesStack) { Node newNode = SelectEnemyNeighbourNode(unitsMovesStack); if (newNode) { SendMoveRequest(newNode); return true; } else { return false; } } #endregion #region PROTECTED METHODS /// /// Returns next node towards enemy /// /// protected virtual Node SelectNextNode() { Node neighbourNode = SelectEnemyNeighbourNode(); if (neighbourNode) { return neighbourNode; } return _unit.CurrentNode.GetNeighbourNodeWithNearestAngle(Vector3.SignedAngle(Vector3.forward, _enemy.transform.position - transform.position, Vector3.up)); } /// /// Returns free node connected with enemy and current node if it exists. /// Otherwise try to push friendly units in way that they still are in contact with the same enemy. /// If it not ends with success returns null. /// /// /// protected virtual Node SelectEnemyNeighbourNode(Stack previousUnitsMoves = null) { if (previousUnitsMoves == null) { previousUnitsMoves = new Stack(); } var enemyNeighbourNodes = _unit.CurrentNode.ConnectedNodes.Keys.Where(node => node.ConnectedNodes.Keys.Contains(_enemy.CurrentNode)); if (enemyNeighbourNodes.Count() > 0) { var enemyNeighbourFreeNodes = enemyNeighbourNodes.Where(node => !node.Occupied); if (enemyNeighbourFreeNodes.Count() > 0) { return enemyNeighbourFreeNodes.ElementAt(UnityEngine.Random.Range(0, enemyNeighbourFreeNodes.Count())); } else { foreach (Node node in enemyNeighbourNodes) { if (!node.Unit.CanMove || node.Unit.OwnerColor != _unit.OwnerColor || previousUnitsMoves.Any(move => move.Node == node)) { continue; } UnitMove unitMove = new UnitMove(_unit, node); previousUnitsMoves.Push(unitMove); bool canRearrange = node.Unit.UnitAI.RearrangeIfCan(previousUnitsMoves); if (canRearrange) { return node; } } } } return null; } /// /// Sends move request to other players and self. /// Use only on host! /// /// protected virtual void SendMoveRequest(Node node) { MatchMessageUnitMoved message = new MatchMessageUnitMoved(node.Position.x, node.Position.y, _unit.Id, _unit.OwnerId); MatchCommunicationManager.Instance.SendMatchStateMessage(MatchMessageType.UnitMoved, message); MatchCommunicationManager.Instance.SendMatchStateMessageSelf(MatchMessageType.UnitMoved, message); } /// /// Sends attack request to other players and self. /// Use only on host! /// protected virtual void SendAttackRequest(Unit enemy, int damage, AttackType attackType) { MatchMessageUnitAttacked message = new MatchMessageUnitAttacked(_unit.Id, _unit.OwnerId, enemy.Id, _unit.Damage, _unit.AttackType); MatchCommunicationManager.Instance.SendMatchStateMessage(MatchMessageType.UnitAttacked, message); MatchCommunicationManager.Instance.SendMatchStateMessageSelf(MatchMessageType.UnitAttacked, message); } #endregion } }