/// <summary> /// Updates the target situation to retreat from an enemy. /// </summary> public static void Retreat(AIController controller, ref AISituation situation, NavMeshAgent agent) { situation.TargetCover = null; situation.TargetPosition = situation.CurrentPosition; var vec = (controller.transform.position - situation.ThreatGroundPosition).normalized; float extra = 0f; while (extra < 20f) { var p = situation.ThreatGroundPosition + vec * (controller.Distances.MinRetreat + extra); var path = new NavMeshPath(); agent.CalculatePath(p, path); if (path.status != NavMeshPathStatus.PathInvalid) { situation.TargetPosition = p; break; } else { extra += 1f; } } }
/// <summary> /// Updates target situation to visit a patrol point. /// </summary> public static void Patrol(AIController controller, ref AISituation situation) { situation.TargetCover = null; situation.TargetPosition = situation.CurrentPosition; if (controller.Waypoints != null && controller.Waypoints.Length > 0) { if (controller.Waypoints.Length == 1) { situation.PatrolPoint = 0; } else { situation.PatrolPoint = situation.PatrolPoint % controller.Waypoints.Length; if (Vector3.Distance(controller.transform.position, controller.Waypoints[situation.PatrolPoint].Position) < 1) { situation.PatrolPoint++; situation.PatrolPoint = situation.PatrolPoint % controller.Waypoints.Length; } } situation.TargetPosition = controller.Waypoints[situation.PatrolPoint].Position; } }
/// <summary> /// Updates the target situation to retreat from an enemy. /// </summary> public static void AvoidGrenade(AIController controller, ref AISituation situation, NavMeshAgent agent) { situation.TargetCover = null; situation.TargetPosition = situation.CurrentPosition; var vec = controller.transform.position - situation.NearestGrenadePosition; vec.y = 0; vec.Normalize(); situation.TargetPosition = situation.NearestGrenadePosition + vec * controller.Grenades.AvoidDistance; }
/// <summary> /// Looks for a position to investigate around the character. /// </summary> public static void FindNewThreatPosition(AIController controller, ref AISituation situation, NavMeshAgent agent, int attempts = 0) { var vec = Quaternion.AngleAxis(UnityEngine.Random.Range(0, 360), Vector3.up) * Vector3.forward; if (!FindNewThreatPositionInDirection(vec, controller, ref situation, agent)) { if (attempts < 3) { FindNewThreatPosition(controller, ref situation, agent, attempts + 1); } } }
/// <summary> /// Updates the target situation to approach an enemy that's in cover. /// </summary> public static void ApproachACovered(AIController controller, ref AISituation situation) { var path = new NavMeshPath(); var corners = new Vector3[16]; var resultPosition = situation.ThreatGroundPosition; var resultDistance = 9999f; var coverAngle = Util.AngleOfVector(situation.ThreatCoverForward); for (int a = 0; a < 18; a++) { var angle = coverAngle - 180f + a * 10; var position = situation.ThreatGroundPosition + new Vector3(Mathf.Cos(angle * Mathf.Deg2Rad), 0, Mathf.Sin(angle * Mathf.Deg2Rad)) * controller.Distances.MinEnemy; NavMeshHit hit; if (NavMesh.SamplePosition(position, out hit, 1.0f, NavMesh.AllAreas) && AIUtil.IsInSight(controller, hit.position, situation.ThreatStandingTopPosition)) { NavMesh.CalculatePath(controller.transform.position, hit.position, NavMesh.AllAreas, path); if (path.status == NavMeshPathStatus.PathComplete && !AIUtil.IsPositionTooCloseToFriends(controller, hit.position)) { var dist = 0f; for (int i = 1; i < path.GetCornersNonAlloc(corners); i++) { dist += Vector3.Distance(corners[i - 1], corners[i]); } if (dist < resultDistance) { resultDistance = dist; resultPosition = position; } } } } situation.TargetPosition = resultPosition; situation.TargetCover = null; }
/// <summary> /// Sets the current state machine state. Resets the state and burst timers and counters. /// </summary> private void setNewState(AIState value, AISituation situation) { _situation = situation; if (value != AIState.reloadInCover && _state != AIState.reloadInCover) { _stateTime = 0; } if (_state != value && value == AIState.fireInCover) { _situation.BurstCount++; } else if (value == AIState.takeCover || value == AIState.retreat || value == AIState.approach) { _situation.BurstCount = 0; } _state = value; _walkingBurstWait = 0; }
/// <summary> /// Checks if this situation as better info than the other. /// </summary> public bool HasBetterThreatInfo(AIController me, ref AISituation other) { if (Threat == null || IsThreatPositionInvestigative) { return(false); } if (other.Threat == null || other.IsThreatPositionInvestigative) { if (LastSeenThreatTime - 1 > other.LastSeenThreatTime) { return(true); } } if (LastSeenThreatTime - 0.005f > other.LastSeenThreatTime) { return(true); } return(false); }
/// <summary> /// Returns true if the current selected state is still valid. /// </summary> private bool isStateStillValid(AIState state, float time, AISituation previous, AISituation next) { var hasEnemyMoved = Vector3.Distance(previous.ThreatGroundPosition, next.ThreatGroundPosition) > 4 || (previous.IsThreatInCover != next.IsThreatInCover); var canUseCover = (next.IsRetreating && Behaviour.IsRetreatingUsingCovers) || (!next.IsRetreating && Behaviour.IsApproachingUsingCovers); var isTargetPositionUseful = next.CanSeeFromTargetPosition || next.IsRetreating; var burst = Behaviour.IsFightingUsingCovers ? CoveredFightingBursts : CoveredApproachBursts; if (next.IsNearGrenade) { return(state == AIState.avoidGrenade); } else { switch (state) { case AIState.avoidGrenade: return(time < 2 || next.IsNearGrenade); case AIState.investigate: return(next.IsAlerted && !next.HasInvestigatedTheLatestAlert && next.Threat == null && Vector3.Distance(next.ThreatGroundPosition, previous.TargetPosition) < Distances.ThreatInvestigation); case AIState.approach: if (next.DidntFindCover && time < 4) { return(true); } return(!next.IsRetreating && (!Behaviour.IsFightingUsingCovers || !Behaviour.IsApproachingUsingCovers) && isTargetPositionUseful); case AIState.fireInCover: return(next.IsAllowedToBeAggressive && canUseCover && !hasEnemyMoved && time <= burst.TotalPeekDuration && next.IsTargetCoverGood && isTargetPositionUseful); case AIState.hideInCover: return(canUseCover && !hasEnemyMoved && time <= burst.Wait && next.IsTargetCoverGood && isTargetPositionUseful); case AIState.retreat: return(next.IsRetreating && Behaviour.IsRetreatingUsingCovers && isTargetPositionUseful); case AIState.reloadInCover: return(!next.IsGunReady); case AIState.none: return(false); case AIState.patrol: { if (!next.IsAlerted && Waypoints != null && Waypoints.Length > 0 && _situation.PatrolPoint < Waypoints.Length) { if (Waypoints[_situation.PatrolPoint].Pause < float.Epsilon && Vector3.Distance(next.TargetPosition, next.CurrentPosition) < 0.5f) { return(false); } else if (Vector3.Distance(transform.position, _situation.TargetPosition) > 1) { return(true); } else { return(false); } } else { return(false); } } case AIState.patrolPause: return(!next.IsAlerted && Waypoints != null && Waypoints.Length > 0 && _situation.PatrolPoint < Waypoints.Length && time <= Waypoints[_situation.PatrolPoint].Pause); case AIState.takeCover: if (next.Threat == null || !next.Threat.IsAttacking) { return(false); } return(canUseCover && !hasEnemyMoved && next.IsTargetCoverGood && isTargetPositionUseful && next.CurrentCover != next.TargetCover); } } return(false); }
/// <summary> /// Sets a new state and takes necessary actions. /// </summary> private void updateSituation(AIState state) { if (_registeredGroup != Group) { if (Group != null) { register(Group); } else { unregister(); } } setNewState(state, _situation); switch (_state) { case AIState.approach: if (_situation.IsThreatInCover) { Positioning.ApproachACovered(this, ref _situation); } else { Positioning.ApproachAFree(this, ref _situation, _agent, Distances.MaxWalkingFight, true); } break; case AIState.retreat: Positioning.Retreat(this, ref _situation, _agent); break; case AIState.avoidGrenade: Positioning.AvoidGrenade(this, ref _situation, _agent); break; case AIState.investigate: _situation.HasInvestigatedTheLatestAlert = false; if (_situation.HasAnInvestigatedAlert) { if (_situation.WasTheLastInvestigatedAlertTheFirst) { if (!Positioning.FindNewThreatPositionInDirection(_situation.DirectionOfInvestigation, this, ref _situation, _agent)) { Positioning.FindNewThreatPosition(this, ref _situation, _agent); } } else { Positioning.FindNewThreatPosition(this, ref _situation, _agent); } } _situation.TargetCover = null; _situation.TargetPosition = _situation.ThreatGroundPosition; _situation.DirectionOfInvestigation = _situation.ThreatGroundPosition - transform.position; _situation.DirectionOfInvestigation.y = 0; _situation.DirectionOfInvestigation.Normalize(); break; case AIState.takeCover: _situation.DidntFindCover = !Positioning.TakeCover(this, ref _situation); if (_situation.DidntFindCover) { updateSituation(AIState.approach); } break; case AIState.reloadInCover: _motor.InputReload(); break; case AIState.patrol: Positioning.Patrol(this, ref _situation); break; } updateAgent(_situation.TargetPosition); _previousSituation = _situation; }
/// <summary> /// Calculates and returns best state to move to depending on the given situation. /// </summary> private AIState calcState(AISituation situation, AIState current, float time) { if (situation.IsAlerted) { if (situation.IsNearGrenade) { return(AIState.avoidGrenade); } var burst = Behaviour.IsFightingUsingCovers ? CoveredFightingBursts : CoveredApproachBursts; var isTargetCoverTooCloseForApproach = !Behaviour.IsFightingUsingCovers && ((situation.CurrentCover != null && !situation.IsNewCover) || Vector3.Distance(situation.TargetPosition, situation.ThreatGroundPosition) < Distances.MaxApproach); var isCloseToFight = !Behaviour.IsFightingUsingCovers && Vector3.Distance(situation.CurrentPosition, situation.ThreatGroundPosition) < Distances.MaxApproach; if (situation.Threat == null) { return(AIState.investigate); } else if (!situation.Threat.IsAttacking) { return(AIState.approach); } else if ((situation.IsRetreating && Behaviour.IsRetreatingUsingCovers) || (!situation.IsRetreating && Behaviour.IsApproachingUsingCovers && !isCloseToFight && !isTargetCoverTooCloseForApproach)) { if (situation.CurrentCover == null || !situation.IsAllowedToBeAggressive || situation.CurrentCover != situation.TargetCover || (!situation.IsRetreating && (!situation.IsTargetCoverGood || !situation.CanSeeFromTargetPosition))) { return(AIState.takeCover); } else { if (!situation.IsGunReady) { return(AIState.reloadInCover); } else if (current == AIState.hideInCover) { if (time <= burst.Wait) { return(AIState.hideInCover); } else if (situation.BurstCount < burst.Count) { return(AIState.fireInCover); } else { return(AIState.takeCover); } } else if (current == AIState.fireInCover) { if (time <= burst.TotalPeekDuration) { return(AIState.fireInCover); } else if (situation.BurstCount < burst.Count) { return(AIState.hideInCover); } else { return(AIState.takeCover); } } else { return(AIState.hideInCover); } } } else { if (situation.IsRetreating || !situation.IsAllowedToBeAggressive) { return(AIState.retreat); } else { return(AIState.approach); } } } else { if (Waypoints != null) { var isAlreadyCloseToTheOnlyOne = false; var hasToWait = false; if (Waypoints.Length == 0) { isAlreadyCloseToTheOnlyOne = true; hasToWait = true; } else { if (_situation.PatrolPoint < Waypoints.Length) { hasToWait = Waypoints[_situation.PatrolPoint].Pause > float.Epsilon; } if (Waypoints.Length == 1) { isAlreadyCloseToTheOnlyOne = Vector3.Distance(transform.position, Waypoints[0].Position) < 0.7f; if (!isAlreadyCloseToTheOnlyOne) { hasToWait = false; } } } if (hasToWait && (current == AIState.patrol || isAlreadyCloseToTheOnlyOne)) { return(AIState.patrolPause); } else { return(AIState.patrol); } } else { return(AIState.patrolPause); } } }
/// <summary> /// Returns an updated situation struct. /// </summary> public void Update(AIController controller, AISituation previous, bool updateInDetail) { if (!IsAlerted) { HasInvestigatedTheLatestAlert = false; } else if (Threat == null & Vector3.Distance(CurrentPosition, ThreatGroundPosition) < controller.Distances.ThreatInvestigation) { MarkInvestigated(); } if (HasAnInvestigatedAlert) { InvestigatedAlertAge += Time.deltaTime; } // Check grenades { IsNearGrenade = false; float minDist = 1000; foreach (var grenade in GrenadeList.All) { var vec = grenade.transform.position - controller.transform.position; var dist = vec.magnitude; if (dist < grenade.ExplosionRadius) { if (!IsNearGrenade || dist < minDist) { minDist = dist; IsNearGrenade = true; NearestGrenadePosition = grenade.transform.position; if (Threat == null) { HasInvestigatedTheLatestAlert = false; HasAnInvestigatedAlert = false; ThreatGroundPosition = grenade.transform.position; } } } } } // Check friends and enemies. if (Threat == null || (!IsAlerted && !IsGettingAlerted)) { var minEnemyInfoTimer = controller.View.EnemySustainTime; foreach (var actor in Actors.All) { if (actor != controller.Actor) { if (actor.Side != controller.Actor.Side) { if (AIUtil.IsInSight(controller, actor.TopPosition)) { IsAlerted = true; ReadEnemyState(actor); break; } } else if (actor.AI != null && actor.AI.IsAlerted) { var vector = actor.transform.position - controller.transform.position; if (vector.magnitude < controller.View.CommunicationDistance) { IsAlerted = true; if (actor.AI.Situation.NoThreatTimer < actor.AI.View.EnemySustainTime && minEnemyInfoTimer > actor.AI.Situation.NoThreatTimer) { TakeEnemyState(actor.AI); } } } } } } // Check friends if they had investigated the same position if (IsAlerted && Threat == null && !HasInvestigatedTheLatestAlert) { foreach (var friend in Actors.All) { if (friend != controller.Actor && friend.Side == controller.Actor.Side && friend.AI.IsAlerted && friend.AI.Situation.HasAnInvestigatedAlert && friend.AI.Situation.InvestigatedAlertAge < 10 && Vector3.Distance(friend.transform.position, controller.transform.position) < controller.View.CommunicationDistance && Vector3.Distance(friend.AI.Situation.InvestigatedThreatPosition, ThreatGroundPosition) < controller.Distances.ThreatInvestigation) { MarkInvestigated(); break; } } } // Check threats if (Threat == null) { var minDist = 100000f; foreach (var alert in Alerts.All) { var dist = Vector3.Distance(controller.transform.position, alert.Position); if (dist < alert.Range) { if (dist < minDist) { minDist = dist; IsAlerted = true; ThreatGroundPosition = alert.Position; HasAnInvestigatedAlert = false; HasInvestigatedTheLatestAlert = false; } } } } // React to grenades if (IsNoticingGrenade) { if (GrenadeReaction < float.Epsilon) { IsNoticingGrenade = false; } else { GrenadeReaction -= Time.deltaTime; IsNearGrenade = false; } } else if (IsNearGrenade && !previous.IsNearGrenade) { GrenadeReaction = controller.Fighting.GrenadeReactionTime; IsNoticingGrenade = true; IsNearGrenade = false; } if (IsNearGrenade) { IsAlerted = true; } // React to being alerted. if (IsGettingAlerted) { if (AlertReaction < float.Epsilon) { IsGettingAlerted = false; IsAlerted = true; } else { AlertReaction -= Time.deltaTime; IsAlerted = false; } } else if (IsAlerted && !previous.IsAlerted) { AlertReaction = controller.Fighting.ReactionTime; IsGettingAlerted = true; IsAlerted = false; } if (previous.TargetCover != null && (controller.Motor.LeftCover == previous.TargetCover || controller.Motor.RightCover == previous.TargetCover || controller.Motor.Cover == previous.TargetCover)) { CurrentCover = previous.TargetCover; } else { CurrentCover = controller.Motor.Cover; } CurrentPosition = controller.transform.position; IsGunReady = controller.Motor.IsGunReady && controller.Motor.Gun.Clip >= controller.Motor.Gun.ClipSize * controller.Fighting.ReloadFraction; if (controller.Health != null && Threat != null && Threat.IsAttacking) { IsRetreating = IsAlerted && controller.Health.Health <= controller.Fighting.MinHealth; } else { IsRetreating = false; } if (IsAlerted) { var couldSeeTheEnemy = CanSeeTheThreat; CanSeeTheThreat = false; if (Threat != null) { if (couldSeeTheEnemy || updateInDetail) { CanSeeTheThreat = AIUtil.IsInSight(controller, Threat.TopPosition); } if (CanSeeTheThreat) { ReadEnemyState(Threat); } else { NoThreatTimer += Time.deltaTime; if (updateInDetail) { // Check friends and enemies. foreach (var friend in Actors.All) { if (friend != controller.Actor && friend.Side == controller.Actor.Side && friend.AI != null && friend.AI.IsAlerted && friend.AI.Situation.CanSeeTheThreat && friend.AI.Situation.Threat == Threat) { var vector = friend.transform.position - controller.transform.position; if (vector.magnitude < controller.View.CommunicationDistance) { TakeEnemyState(friend.AI); } } } } } } if (TargetCover != null && updateInDetail) { var distanceToThreat = Vector3.Distance(TargetPosition, ThreatGroundPosition); IsTargetCoverGood = distanceToThreat >= controller.Distances.MinEnemy && distanceToThreat >= controller.Cover.MinCoverToEnemyDistance && AIUtil.IsGoodAngle(controller, TargetCover, TargetPosition, ThreatGroundPosition, TargetCover.IsTall(controller.Motor.CoverSettings.TallThreshold)) && !AIUtil.IsCoverPositionTooCloseToFriends(TargetCover, controller, TargetPosition); } if (updateInDetail) { if (TargetCover != null && TargetCover.IsTall(controller.Motor.CoverSettings.TallThreshold)) { Vector3 aimPoint; if (TargetDirection < 0) { aimPoint = TargetCover.LeftCorner(0, controller.Motor.CoverSettings.CornerOffset.x); } else { aimPoint = TargetCover.RightCorner(0, controller.Motor.CoverSettings.CornerOffset.x); } CanSeeFromTargetPosition = AIUtil.IsInSight(controller, aimPoint, ThreatStandingTopPosition); } else { CanSeeFromTargetPosition = AIUtil.IsInSight(controller, TargetPosition, ThreatStandingTopPosition); } } } else { if (TargetCover != null && updateInDetail) { IsTargetCoverGood = !AIUtil.IsCoverPositionTooCloseToFriends(TargetCover, controller, TargetPosition); } CanSeeFromTargetPosition = true; } }
/// <summary> /// Updates the target situation to approach an enemy that's not in cover. /// </summary> public static void ApproachAFree(AIController controller, ref AISituation situation, NavMeshAgent agent, float maxDistance, bool approachStanding) { situation.TargetCover = null; situation.TargetPosition = situation.ThreatGroundPosition; var path = new NavMeshPath(); agent.CalculatePath(situation.ThreatGroundPosition, path); var corners = new Vector3[16]; var count = path.GetCornersNonAlloc(corners); if (count < 2) { return; } var i = 0; var p0 = corners[i]; var p1 = corners[i + 1]; var f = 1f; var targetPosition = approachStanding ? situation.ThreatStandingTopPosition : situation.ThreatGroundPosition; { var distLeft = Vector3.Distance(controller.transform.position, situation.ThreatGroundPosition); while (distLeft > maxDistance) { var pd = Vector3.Distance(p0, p1); var left = distLeft - maxDistance; distLeft -= pd; if (pd >= left) { f = left / pd; break; } else { i++; if (i + 1 >= count) { i = 0; break; } else { p0 = corners[i]; p1 = corners[i + 1]; } } } } while (i + 1 < count) { var p = p0 + (p1 - p0) * f; if (AIUtil.IsInSight(controller, p, targetPosition)) { situation.TargetPosition = p; break; } f += 0.2f; if (f >= 1f) { if (AIUtil.IsInSight(controller, p1, targetPosition)) { situation.TargetPosition = p1; break; } f = 0; i++; } } }
/// <summary> /// Updates the target situation to take cover. Returns true if a cover was found. /// </summary> public static bool TakeCover(AIController controller, ref AISituation situation) { var currentVectorToTarget = situation.ThreatGroundPosition - situation.CurrentPosition; var currentDistanceToTarget = currentVectorToTarget.magnitude; Cover result = null; float resultPathDistance = 0; int resultDirection = 0; Vector3 resultPosition = situation.CurrentPosition; var path = new NavMeshPath(); var corners = new Vector3[32]; var isAlreadyTooClose = currentDistanceToTarget <= controller.Distances.MinEnemy || currentDistanceToTarget <= controller.Cover.MinCoverToEnemyDistance; var covers = new List <CoverItem>(); foreach (var collider in Physics.OverlapSphere(controller.transform.position, controller.Cover.MaxDistance, 0x1 << 8, QueryTriggerInteraction.Collide)) { if (!collider.isTrigger) { continue; } var cover = CoverSearch.GetCover(collider.gameObject); if (cover == null || cover == situation.CurrentCover) { continue; } var item = new CoverItem(); item.Cover = cover; item.IsTall = cover.IsTall(controller.Motor.CoverSettings.TallThreshold); if (item.IsTall) { item.Direction = cover.ClosestCornerTo(situation.CurrentPosition, controller.Motor.CoverSettings.CornerAimTriggerDistance, out item.Point); } else { item.Point = cover.ClosestPointTo(situation.CurrentPosition, controller.Motor.CoverSettings.LowSideEnterRadius, 0.3f); } if (float.IsNaN(item.Point.x) || float.IsNaN(item.Point.z)) { continue; } item.Point.y = cover.Bottom; item.Distance = Vector3.Distance(controller.transform.position, item.Point); covers.Add(item); } foreach (var item in covers.OrderBy(o => o.Distance)) { var isTall = item.IsTall; var cover = item.Cover; var point = item.Point; var coverDirection = item.Direction; if (!AIUtil.IsGoodAngle(controller, cover, point, situation.ThreatGroundPosition, isTall)) { continue; } var distanceToTarget = Vector3.Distance(situation.ThreatGroundPosition, point); if (distanceToTarget < controller.Distances.MinEnemy || distanceToTarget < controller.Cover.MinCoverToEnemyDistance) { continue; } if (situation.CurrentCover != null && Vector3.Distance(situation.CurrentPosition, point) < controller.Cover.MinSwitchDistance) { continue; } if ((controller.Health == null || controller.Health.Health > controller.Fighting.MinHealth)) { if (isTall) { Vector3 aimPoint; coverDirection = cover.ClosestCornerTo(point, -controller.Motor.CoverSettings.CornerOffset.x, out aimPoint); if (!AIUtil.IsInSight(controller, aimPoint, situation.ThreatStandingTopPosition)) { continue; } } else if (!AIUtil.IsInSight(controller, point, situation.ThreatStandingTopPosition)) { continue; } } var distanceToOrigin = Vector3.Distance(situation.CurrentPosition, point); if (situation.CurrentCover == null) { if (distanceToOrigin > controller.Cover.MaxDistance) { continue; } } else if (distanceToOrigin > controller.Cover.MaxSwitchDistance) { continue; } var areThereFriends = false; { var hasChangedPosition = false; Vector3 side; if (Vector3.Dot((point - situation.CurrentPosition).normalized, cover.Right) > 0) { side = cover.Right; } else { side = cover.Left; } do { hasChangedPosition = false; if (AIUtil.IsCoverPositionTooCloseToFriends(cover, controller, point)) { var next = point + side * 0.5f; if (cover.IsInFront(next, false)) { point = next; hasChangedPosition = true; } else { areThereFriends = true; } } }while (hasChangedPosition); } if (areThereFriends) { continue; } var isOk = false; NavMesh.CalculatePath(situation.CurrentPosition, point, NavMesh.AllAreas, path); float pathDistance = 0f; var count = path.GetCornersNonAlloc(corners); if (count < 2) { continue; } var isTooCloseToEnemy = false; for (int i = 1; i < count; i++) { pathDistance += Vector3.Distance(corners[i - 1], corners[i]); if (!isAlreadyTooClose) { if (Util.DistanceToSegment(situation.ThreatGroundPosition, corners[i - 1], corners[i]) <= controller.Distances.MinPassing) { isTooCloseToEnemy = true; break; } } } if (isTooCloseToEnemy) { continue; } if (situation.CurrentCover == null) { isOk = result == null || pathDistance < resultPathDistance; } else if (controller.Health == null || controller.Health.Health > controller.Fighting.MinHealth) { isOk = (isAlreadyTooClose || distanceToTarget < currentDistanceToTarget) && (result == null || pathDistance < resultPathDistance); } else { isOk = distanceToTarget > currentDistanceToTarget && (result == null || pathDistance < resultPathDistance); } if (isOk) { result = cover; resultPosition = point; resultPathDistance = pathDistance; resultDirection = coverDirection; break; } } situation.TargetDirection = resultDirection; if (result == null) { if (situation.IsThreatInCover) { ApproachACovered(controller, ref situation); } else { ApproachAFree(controller, ref situation, controller.Agent, controller.Distances.MaxWalkingFight, true); } return(false); } else { situation.IsNewCover = true; situation.TargetCover = result; situation.TargetPosition = resultPosition; return(true); } }
/// <summary> /// Looks for a position to investigate in given direction. /// </summary> public static bool FindNewThreatPositionInDirection(Vector3 direction, AIController controller, ref AISituation situation, NavMeshAgent agent) { var corners = new Vector3[32]; float extra = 0f; while (extra < 20f) { var p = situation.CurrentPosition + direction * (controller.Distances.MinSearch + extra); var path = new NavMeshPath(); agent.CalculatePath(p, path); if (path.status != NavMeshPathStatus.PathInvalid) { int count = path.GetCornersNonAlloc(corners); if (count > 1) { situation.ThreatGroundPosition = corners[count - 1]; return(true); } } extra += 1f; } return(false); }
/// <summary> /// Returns an updated situation struct. /// </summary> public void Update(AIController controller, AISituation previous, bool updateInDetail) { if (!IsAlerted) { HasInvestigatedTheLatestAlert = false; } else if (!HasInvestigatedTheLatestAlert && Vector3.Distance(CurrentPosition, InvestigationPosition) < controller.Distances.ThreatInvestigation) { MarkInvestigated(); } if (HasAnInvestigatedAlert) { InvestigatedAlertAge += Time.deltaTime; } if (IsIrritated) { if (IrritationTime > controller.Fighting.Irritation) { IrritationTime = 0; IsIrritated = false; } else { IrritationTime += Time.deltaTime; } } else { IrritationTime = 0; } // Check grenades { IsNearGrenade = false; float minDist = 1000; foreach (var grenade in GrenadeList.All) { var vec = grenade.transform.position - controller.transform.position; var dist = vec.magnitude; if (dist < grenade.ExplosionRadius) { if (!IsNearGrenade || dist < minDist) { minDist = dist; IsNearGrenade = true; NearestGrenadePosition = grenade.transform.position; if (Threat == null) { HasInvestigatedTheLatestAlert = false; HasAnInvestigatedAlert = false; IsThreatPositionANewAlert = true; SetThreatPosition(grenade.transform.position, true); } } } } } // Check friends and enemies. if (Threat == null || (!IsAlerted && !IsGettingAlerted)) { foreach (var actor in Actors.All) { if (actor != controller.Actor) { if (actor.Side != controller.Actor.Side) { if (AIUtil.IsInSight(controller, actor.TopPosition)) { IsAlerted = true; ReadEnemyState(actor); break; } } else if (actor.AI != null && actor.AI.IsAlerted) { var vector = actor.transform.position - controller.transform.position; if (vector.magnitude < controller.View.CommunicationDistance) { IsAlerted = true; if (actor.AI.State != AIState.investigate) { if (actor.AI.Situation.Threat != null && actor.AI.Situation.HasBetterThreatInfo(actor.AI, ref this)) { TakeEnemyState(actor.AI); break; } } } } } } } // Check friends if they had investigated the same position if (IsAlerted && !HasInvestigatedTheLatestAlert) { foreach (var friend in Actors.All) { if (friend != controller.Actor && friend.Side == controller.Actor.Side && friend.AI != null && friend.AI.IsAlerted && friend.AI.Situation.HasAnInvestigatedAlert && friend.AI.Situation.InvestigatedAlertAge < 4 && Vector3.Distance(friend.transform.position, controller.transform.position) < controller.View.CommunicationDistance && Vector3.Distance(friend.AI.Situation.InvestigatedThreatPosition, InvestigationPosition) < controller.Distances.ThreatInvestigation) { MarkInvestigated(); break; } } } var isCheckingAThreatInCover = Threat != null && IsThreatInCover && !CanSeeTheThreat; // Check threats if (Threat == null || CanSeeThatNoThreatAtLastPosition || isCheckingAThreatInCover) { var minDist = 100000f; foreach (var alert in Alerts.All) { bool isOk; Actor newThreat = null; if (Threat != null) { if (alert.Actor == null) { isOk = NoThreatVisibilityTime > 6; } else if (alert.Actor.Side != controller.Actor.Side) { isOk = true; newThreat = alert.Actor; } else if (alert.Actor.AI != null) { isOk = NoThreatVisibilityTime > 2 && alert.Actor.AI.Situation.NoThreatVisibilityTime < 1; } else { isOk = NoThreatVisibilityTime > 6; } } else { isOk = true; } if (isOk) { var dist = Vector3.Distance(controller.transform.position, alert.Position); if (dist < alert.Range) { if (dist < minDist) { minDist = dist; IsAlerted = true; HasAnInvestigatedAlert = false; HasInvestigatedTheLatestAlert = false; if (newThreat != null) { ReadEnemyState(newThreat); } else { IsThreatPositionANewAlert = true; SetThreatPosition(alert.Position, true); } } } } } } // React to grenades if (IsNoticingGrenade) { if (GrenadeReaction < float.Epsilon) { IsNoticingGrenade = false; } else { GrenadeReaction -= Time.deltaTime; IsNearGrenade = false; } } else if (IsNearGrenade && !previous.IsNearGrenade) { GrenadeReaction = controller.Fighting.GrenadeReactionTime; IsNoticingGrenade = true; IsNearGrenade = false; } if (IsNearGrenade) { IsAlerted = true; } // React to being alerted. if (IsGettingAlerted) { if (AlertReaction < float.Epsilon) { IsGettingAlerted = false; IsAlerted = true; } else { AlertReaction -= Time.deltaTime; IsAlerted = false; } } else if (IsAlerted && !previous.IsAlerted) { AlertReaction = controller.Fighting.ReactionTime; IsGettingAlerted = true; IsAlerted = false; } if (previous.TargetCover != null && (controller.Motor.LeftCover == previous.TargetCover || controller.Motor.RightCover == previous.TargetCover || controller.Motor.Cover == previous.TargetCover)) { CurrentCover = previous.TargetCover; } else { CurrentCover = controller.Motor.Cover; } CurrentPosition = controller.transform.position; IsGunReady = controller.Motor.IsGunReady && controller.Motor.Gun.Clip >= controller.Motor.Gun.ClipSize * controller.Fighting.ReloadFraction; if (Threat == null || Threat.IsAggressive) { WouldLikeToRetreat = controller.Health.Health <= controller.Fighting.MinHealth; } else { WouldLikeToRetreat = false; } if (IsAlerted) { var couldSeeTheEnemy = CanSeeTheThreat; CanSeeTheThreat = false; if (Threat != null) { var noPatience = NoThreatVisibilityTime > controller.Fighting.Patience; if (couldSeeTheEnemy || updateInDetail) { CanSeeTheThreat = AIUtil.IsInSight(controller, Threat.TopPosition); } if (CanSeeTheThreat) { ReadEnemyState(Threat); if (!couldSeeTheEnemy && noPatience) { IsIrritated = true; } } else { if (noPatience || (NoThreatVisibilityTime > 2 && (!IsThreatInCover || Vector3.Dot(ThreatCoverForward, ThreatGroundPosition - CurrentPosition) > 0))) { if (!IsThreatInCover || noPatience || Vector3.Distance(CurrentPosition, ThreatGroundPosition) < controller.Distances.ThreatInvestigation || AIUtil.IsInSight(controller, (ThreatGroundPosition + ThreatTopPosition) * 0.5f)) { CanSeeThatNoThreatAtLastPosition = true; } } NoThreatVisibilityTime += Time.deltaTime; if (updateInDetail) { // Check friends. foreach (var friend in Actors.All) { if (friend != controller.Actor && friend.Side == controller.Actor.Side && friend.AI != null && friend.AI.IsAlerted && friend.AI.State != AIState.investigate && friend.AI.Situation.Threat == Threat && friend.AI.Situation.HasBetterThreatInfo(friend.AI, ref this)) { var vector = friend.transform.position - controller.transform.position; if (vector.magnitude < controller.View.CommunicationDistance) { TakeEnemyState(friend.AI); } } } } } } if (TargetCover != null && updateInDetail) { var distanceToThreat = Vector3.Distance(TargetPosition, ThreatGroundPosition); IsTargetCoverGood = distanceToThreat >= controller.Distances.MinEnemy && distanceToThreat >= controller.Cover.MinCoverToEnemyDistance && AIUtil.IsGoodAngle(controller, TargetCover, TargetPosition, ThreatGroundPosition, TargetCover.IsTall) && !AIUtil.IsCoverPositionTooCloseToFriends(TargetCover, controller, TargetPosition); } if (updateInDetail) { if (IsThreatInCover) { if (CurrentCover != null && CurrentCover.IsTall) { var aimPoint = Vector3.zero; var isGood = true; if (TargetDirection < 0) { isGood = CurrentCover.IsLeft(Util.AngleOfVector(ThreatStandingTopPosition - CurrentCover.LeftCorner(0)), controller.Motor.CoverSettings.Angles.LeftCorner, false); if (isGood) { aimPoint = CurrentCover.LeftCorner(0, controller.Motor.CoverSettings.CornerOffset.x); } } else { isGood = CurrentCover.IsRight(Util.AngleOfVector(ThreatStandingTopPosition - CurrentCover.RightCorner(0)), controller.Motor.CoverSettings.Angles.LeftCorner, false); if (isGood) { aimPoint = CurrentCover.RightCorner(0, controller.Motor.CoverSettings.CornerOffset.x); } } CanSeeFromCurrentPosition = AIUtil.IsInSight(controller, aimPoint, ThreatStandingTopPosition); } else { CanSeeFromCurrentPosition = AIUtil.IsInSight(controller, CurrentPosition, ThreatStandingTopPosition); } } else { CanSeeFromCurrentPosition = CanSeeTheThreat; } } } else { if (TargetCover != null && updateInDetail) { IsTargetCoverGood = !AIUtil.IsCoverPositionTooCloseToFriends(TargetCover, controller, TargetPosition); } } }