/// <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> /// 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> /// 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); } } }