using System; using System.Collections.Generic; using System.Linq; using Animators.Leonid_Animator; using Animators.Leonid_Animator.Bot; using Unity.MLAgents; using Unity.MLAgents.Actuators; using Unity.MLAgents.Sensors; using UnityEngine; [RequireComponent(typeof(MovementController))] [RequireComponent(typeof(BufferSensorComponent))] [RequireComponent(typeof(DecisionRequester))] [RequireComponent(typeof(BotLocomotion))] public class NPC : Agent, ICharacter { private CharacterCondition _condition; private FlagZone _flagZone = null; private INpcBaseState NpcState { get; set; } public INpcBaseBodyState NpcBodyState { get; private set; } [field: HideInInspector] public Character GetCharacter { get; private set; } private NpcDirectPointState _directState; private NpcInCoverState _coverState; private NpcRunningState _runningState; private NpcStandingState _standingState; private NpcCrouchingState _crouchingState; private MovementController _moveController; private BufferSensorComponent _bufferSensor; private AimAssistant _assistant; private BotLocomotion _botLocomotion; private Dictionary _navPointIdDict; public bool IsFiring => _assistant.fireAnimation; #region UnityEvents and ML private void Awake() { _directState = new NpcDirectPointState(); _coverState = new NpcInCoverState(); _runningState = new NpcRunningState(); NpcState = _directState; _crouchingState = new NpcCrouchingState(); _standingState = new NpcStandingState(); NpcBodyState = _standingState; GetCharacter = new Character(); _condition = GetCharacter.Condition; _moveController = gameObject.GetComponent(); _bufferSensor = gameObject.GetComponent(); _assistant = gameObject.GetComponent(); _botLocomotion = gameObject.GetComponent(); _flagZone = GameObject.FindObjectOfType(); if (_flagZone is null) Debug.LogError("Flag Is Not Set"); _navPointIdDict = MapManager.Instance.IDToNavPoint; if (_navPointIdDict is null) Debug.LogError("Cant Find Nav Point Dictionary"); } private void OnDestroy() { Debug.LogWarning("Pooled object was destroyed"); } private void Update() { _botLocomotion.UpdateAnimatorValues(); } public override void OnEpisodeBegin() { if (_navPointIdDict is null) Debug.LogError("Cant Find Nav Point Dictionary"); NpcState = _directState; _flagZone = GameObject.FindObjectOfType(); } public override void CollectObservations(VectorSensor sensor) { Debug.Log("Collect observations called!"); _navPointIdDict = MapManager.Instance.IDToNavPoint; if (_navPointIdDict is null) Debug.LogError("Cant Find Nav Point Dictionary"); var candidates = _moveController.GetPointsCandidate(); //common sensors sensor.AddObservation(GameManager.IsHaveSeenByEnemy(GetCharacter.Team.GetOppositeTeam(), NpcBodyState.GetPointToHit(gameObject)).ToInt()); sensor.AddObservation(GetCharacter.LastTimeHit); sensor.AddObservation((!_flagZone.IsNotOccup).ToInt()); sensor.AddObservation(_condition.GetHealthPointsInQuantile()); sensor.AddObservation(_condition.GetArmourPointsInQuantile()); sensor.AddObservation(candidates.Count); sensor.AddObservation(_moveController.PointStartID); sensor.AddObservation(_moveController.PointEndID); //state sensors sensor.AddObservation((int)NpcState.State); sensor.AddObservation((int)NpcBodyState.State); sensor.AddObservation(GameManager.IsEnemyNearby(gameObject.transform.position, GetCharacter.Team)); sensor.AddObservation(_navPointIdDict[_moveController.PointStartID].DeathAttr); sensor.AddObservation(_navPointIdDict[_moveController.PointEndID].DeathAttr); sensor.AddObservation(_moveController.FlagDistance); //point sensors foreach (var point in candidates) { var position = transform.position; _bufferSensor.AppendObservation(new float[] { point.DeathAttr, (int)point.navType, //4 flagEnemyDistance GameManager.IsCloserToFlagFromNextNavPoint(point, position).ToInt(), //5 EnemyVsNavPointDistance GameManager.IsCloserToEnemyThanToNextNavPoint(point, position, GetCharacter.Team.GetOppositeTeam()).ToInt(), //6 Have been seen by enemy in this point GameManager.IsHaveSeenByEnemy(GetCharacter.Team.GetOppositeTeam(), point.Position).ToInt() }); } ; } public override void OnActionReceived(ActionBuffers actions) { var result = actions.DiscreteActions; print(result[0]); if (result[0] == 0) { if (_navPointIdDict[_moveController.PointStartID].navType != NavPointType.Cover) return; NpcState = _coverState; switch (result[1]) { case 0: Peek(); break; case 1: Cover(); break; case 2: Peek(); _moveController.GoToNextNavPoint(_navPointIdDict[result[2]]); break; case 3: break; default: throw new ArgumentException("Undefined Action received"); } } if (result[0] == 1) { if (_navPointIdDict[_moveController.PointStartID].navType != NavPointType.Direction) { return; } switch (result[1]) { case 0: _moveController.GoToNextNavPoint(_navPointIdDict[result[2]]); NpcState = _runningState; Debug.Log("Go to point " + result[2]);break; case 1: NpcState = _directState; break; case 2: break; case 3: break; default: throw new ArgumentException("Undefined Action received"); } } if (result[0] == 2) { if (_moveController.PointStartID == _moveController.PointEndID && _moveController.PointEndID != -1) return; switch (result[1]) { case 0: _moveController.StopOnPath(); NpcState = _directState; break; case 1: _moveController.ReturnToStartPoint(); NpcState = _runningState; break; case 2: break; case 3: break; default: throw new ArgumentException("Undefined Action received"); } } } #endregion public event Action OnChangePosition; private void Peek() { OnChangePosition?.Invoke(global::NpcBodyState.Standing); NpcBodyState = _standingState; } private void Cover() { OnChangePosition?.Invoke(global::NpcBodyState.Crouching); NpcBodyState = _crouchingState; } public event Action OnDeathEvent; public event Action OnDamageReceived; public void GetDamage(int damage) { GetCharacter.LastTimeHit = TimeManager.Instance.CurrentTime; _condition.GiveHealth(-Mathf.RoundToInt(damage * (1 - _condition.ArmourPoints * 0.5f))); _condition.GiveArmour(-Mathf.RoundToInt(Mathf.Sqrt(damage) * 5)); OnDamageReceived?.Invoke(damage, GetCharacter.Team); if (_condition.HealthPoints < 0) { OnDeathEvent?.Invoke(true); MapManager.AddDeathAttributeToPoints(_moveController.PointStartID, _moveController.PointEndID, _moveController.DistanceToGo, _moveController.RemainingDistance); var pos = gameObject.transform.position; var id = _moveController.PointStartID; CharacterFactory.Instance.ReSpawn(this, ref pos, ref id); } } public void ResetCharacter() { _condition.Reset(); EndEpisode(); } }