/// <summary> /// Refreshes the course. /// </summary> /// <param name="mode">The mode.</param> /// <param name="wayPtProxy">The optional waypoint. When not null, this is always a StationaryLocation detour to avoid an obstacle.</param> /// <exception cref="System.NotImplementedException"></exception> private void RefreshCourse(CourseRefreshMode mode, AutoPilotDestinationProxy wayPtProxy = null) { //D.Log(ShowDebugLog, "{0}.RefreshCourse() called. Mode = {1}. CourseCountBefore = {2}.", DebugName, mode.GetValueName(), AutoPilotCourse.Count); switch (mode) { case CourseRefreshMode.NewCourse: D.AssertNull(wayPtProxy); ApCourse.Clear(); ApCourse.Add(_ship); IShipNavigable courseTgt; if (ApTargetProxy.IsMobile) { courseTgt = new MobileLocation(new Reference<Vector3>(() => ApTargetProxy.Position)); } else { courseTgt = new StationaryLocation(ApTargetProxy.Position); } ApCourse.Add(courseTgt); // includes fstOffset break; case CourseRefreshMode.AddWaypoint: ApCourse.Insert(ApCourse.Count - 1, new StationaryLocation(wayPtProxy.Position)); // changes Course.Count break; case CourseRefreshMode.ReplaceObstacleDetour: D.AssertEqual(3, ApCourse.Count); ApCourse.RemoveAt(ApCourse.Count - 2); // changes Course.Count ApCourse.Insert(ApCourse.Count - 1, new StationaryLocation(wayPtProxy.Position)); // changes Course.Count break; case CourseRefreshMode.RemoveWaypoint: D.AssertEqual(3, ApCourse.Count); bool isRemoved = ApCourse.Remove(new StationaryLocation(wayPtProxy.Position)); // Course.RemoveAt(Course.Count - 2); // changes Course.Count D.Assert(isRemoved); break; case CourseRefreshMode.ClearCourse: D.AssertNull(wayPtProxy); ApCourse.Clear(); break; default: throw new NotImplementedException(ErrorMessages.UnanticipatedSwitchValue.Inject(mode)); } //D.Log(ShowDebugLog, "CourseCountAfter = {0}.", Course.Count); HandleCourseChanged(); }
/// <summary> /// Checks for an obstacle en-route to the provided <c>destination</c>. Returns true if one /// is found that requires immediate action and provides the detour to avoid it, false otherwise. /// </summary> /// <param name="destProxy">The current destination. May be the AutoPilotTarget or an obstacle detour.</param> /// <param name="castingDistanceSubtractor">The distance to subtract from the casted Ray length to avoid /// detecting any ObstacleZoneCollider around the destination.</param> /// <param name="detourProxy">The obstacle detour.</param> /// <param name="destinationOffset">The offset from destination.Position that is our destinationPoint.</param> /// <returns> /// <c>true</c> if an obstacle was found and a detour generated, false if the way is effectively clear. /// </returns> private bool TryCheckForObstacleEnrouteTo(AutoPilotDestinationProxy destProxy, out AutoPilotDestinationProxy detourProxy) { D.AssertNotNull(destProxy, DebugName); // 12.15.16 Got null ref in TryCheckForObstacleEnrouteTo() Profiler.BeginSample("Ship TryCheckForObstacleEnrouteTo Execution", _ship); int iterationCount = Constants.Zero; bool hasDetour = TryCheckForObstacleEnrouteTo(destProxy, out detourProxy, ref iterationCount); Profiler.EndSample(); return hasDetour; }
private bool TryCheckForObstacleEnrouteTo(AutoPilotDestinationProxy destProxy, out AutoPilotDestinationProxy detourProxy, ref int iterationCount) { D.AssertNotNull(destProxy, DebugName); // 12.15.16 Got null ref D.AssertException(iterationCount++ < 10); detourProxy = null; Vector3 destBearing = (destProxy.Position - Position).normalized; float rayLength = destProxy.GetObstacleCheckRayLength(Position); Ray ray = new Ray(Position, destBearing); bool isDetourGenerated = false; RaycastHit hitInfo; if (Physics.Raycast(ray, out hitInfo, rayLength, AvoidableObstacleZoneOnlyLayerMask.value)) { // there is an AvoidableObstacleZone in the way. Warning: hitInfo.transform returns the rigidbody parent since // the obstacleZone trigger collider is static. UNCLEAR if this means it forms a compound collider as this is a raycast var obstacleZoneGo = hitInfo.collider.gameObject; var obstacleZoneHitDistance = hitInfo.distance; IAvoidableObstacle obstacle = obstacleZoneGo.GetSafeFirstInterfaceInParents<IAvoidableObstacle>(excludeSelf: true); if (obstacle == destProxy.Destination) { D.LogBold(ShowDebugLog, "{0} encountered obstacle {1} which is the destination. \nRay length = {2:0.00}, DistanceToHit = {3:0.00}.", DebugName, obstacle.DebugName, rayLength, obstacleZoneHitDistance); HandleObstacleFoundIsTarget(obstacle); } else { D.Log(ShowDebugLog, "{0} encountered obstacle {1} at {2} when checking approach to {3}. \nRay length = {4:0.#}, DistanceToHit = {5:0.#}.", DebugName, obstacle.DebugName, obstacle.Position, destProxy.DebugName, rayLength, obstacleZoneHitDistance); if (TryGenerateDetourAroundObstacle(obstacle, hitInfo, out detourProxy)) { AutoPilotDestinationProxy newDetourProxy; if (TryCheckForObstacleEnrouteTo(detourProxy, out newDetourProxy, ref iterationCount)) { D.Log(ShowDebugLog, "{0} found another obstacle on the way to detour {1}.", DebugName, detourProxy.DebugName); detourProxy = newDetourProxy; } isDetourGenerated = true; } } } return isDetourGenerated; }
private void InitiateObstacleCheckingEnrouteTo(AutoPilotDestinationProxy destProxy, CourseRefreshMode courseRefreshMode) { D.AssertNotNull(destProxy, DebugName); // 12.15.16 Got null ref in TryCheckForObstacleEnrouteTo() D.AssertNull(_apObstacleCheckJob, DebugName); _apObstacleCheckPeriod = __GenerateObstacleCheckPeriod(); AutoPilotDestinationProxy detourProxy; string jobName = "{0}.ApObstacleCheckJob".Inject(DebugName); _apObstacleCheckJob = _jobMgr.RecurringWaitForHours(new Reference<GameTimeDuration>(() => _apObstacleCheckPeriod), jobName, waitMilestone: () => { Profiler.BeginSample("Ship ApObstacleCheckJob Execution", _ship); if (TryCheckForObstacleEnrouteTo(destProxy, out detourProxy)) { KillApObstacleCheckJob(); RefreshCourse(courseRefreshMode, detourProxy); Profiler.EndSample(); ContinueCourseToTargetVia(detourProxy); return; } if (_doesApObstacleCheckPeriodNeedRefresh) { _apObstacleCheckPeriod = __GenerateObstacleCheckPeriod(); _doesApObstacleCheckPeriodNeedRefresh = false; } Profiler.EndSample(); }); }
/// <summary> /// Tries to generate a detour around the provided obstacle. Returns <c>true</c> if a detour /// was generated, <c>false</c> otherwise. /// <remarks>A detour can always be generated around an obstacle. However, this algorithm considers other factors /// before initiating a heading change to redirect to a detour. E.g. moving obstacles that are far away /// and/or require only a small change in heading may not necessitate a diversion to a detour yet. /// </remarks> /// </summary> /// <param name="obstacle">The obstacle.</param> /// <param name="zoneHitInfo">The zone hit information.</param> /// <param name="detourProxy">The resulting detour.</param> /// <returns></returns> private bool TryGenerateDetourAroundObstacle(IAvoidableObstacle obstacle, RaycastHit zoneHitInfo, out AutoPilotDestinationProxy detourProxy) { detourProxy = GenerateDetourAroundObstacle(obstacle, zoneHitInfo, _ship.Command.UnitMaxFormationRadius); bool useDetour = true; Vector3 detourBearing = (detourProxy.Position - Position).normalized; float reqdTurnAngleToDetour = Vector3.Angle(_ship.CurrentHeading, detourBearing); if (obstacle.IsMobile) { if (reqdTurnAngleToDetour < DetourTurnAngleThreshold) { useDetour = false; // angle is still shallow but short remaining distance might require use of a detour float maxDistanceTraveledBeforeNextObstacleCheck = _engineRoom.IntendedCurrentSpeedValue * _apObstacleCheckPeriod.TotalInHours; float obstacleDistanceThresholdRequiringDetour = maxDistanceTraveledBeforeNextObstacleCheck * 2F; // HACK float distanceToObstacleZone = zoneHitInfo.distance; if (distanceToObstacleZone <= obstacleDistanceThresholdRequiringDetour) { useDetour = true; } } } if (useDetour) { D.Log(ShowDebugLog, "{0} has generated detour {1} to get by obstacle {2}. Reqd Turn = {3:0.#} degrees.", DebugName, detourProxy.DebugName, obstacle.DebugName, reqdTurnAngleToDetour); } else { D.Log(ShowDebugLog, "{0} has declined to generate a detour to get by mobile obstacle {1}. Reqd Turn = {2:0.#} degrees.", DebugName, obstacle.DebugName, reqdTurnAngleToDetour); } return useDetour; }
private void InitiateNavigationTo(AutoPilotDestinationProxy destProxy, Action hasArrived = null) { if (!_engineRoom.IsPropulsionEngaged) { D.Error("{0}.InitiateNavigationTo({1}) called without propulsion engaged. AutoPilotSpeed: {2}", DebugName, destProxy.DebugName, ApSpeed.GetValueName()); } D.AssertNull(_apNavJob, DebugName); bool isDestinationADetour = destProxy != ApTargetProxy; bool isDestFastMover = destProxy.IsFastMover; bool isIncreaseAboveApSpeedAllowed = isDestinationADetour || isDestFastMover; GameTimeDuration progressCheckPeriod = default(GameTimeDuration); Speed correctedSpeed; float distanceToArrival; Vector3 directionToArrival; #pragma warning disable 0219 bool isArrived = false; #pragma warning restore 0219 if (isArrived = !destProxy.TryGetArrivalDistanceAndDirection(Position, out directionToArrival, out distanceToArrival)) { // arrived if (hasArrived != null) { hasArrived(); } return; } else { //D.Log(ShowDebugLog, "{0} powering up. Distance to arrival at {1} = {2:0.0}.", DebugName, destination.DebugName, distanceToArrival); progressCheckPeriod = GenerateProgressCheckPeriod(distanceToArrival, out correctedSpeed); if (correctedSpeed != default(Speed)) { //D.Log(ShowDebugLog, "{0} is correcting its speed to {1} to get a minimum of 5 progress checks.", DebugName, correctedSpeed.GetValueName()); ChangeSpeed_Internal(correctedSpeed, _isApCurrentSpeedFleetwide); } //D.Log(ShowDebugLog, "{0} initial progress check period set to {1}.", DebugName, progressCheckPeriod); } int minFrameWaitBetweenAttemptedCourseCorrectionChecks = 0; int previousFrameCourseWasCorrected = 0; float halfArrivalWindowDepth = destProxy.ArrivalWindowDepth / 2F; string jobName = "{0}.ApNavJob".Inject(DebugName); _apNavJob = _jobMgr.RecurringWaitForHours(new Reference<GameTimeDuration>(() => progressCheckPeriod), jobName, waitMilestone: () => { //D.Log(ShowDebugLog, "{0} making ApNav progress check on Date: {1}, Frame: {2}. CheckPeriod = {3}.", DebugName, _gameTime.CurrentDate, Time.frameCount, progressCheckPeriod); Profiler.BeginSample("Ship ApNav Job Execution", _ship); if (isArrived = !destProxy.TryGetArrivalDistanceAndDirection(Position, out directionToArrival, out distanceToArrival)) { KillApNavJob(); if (hasArrived != null) { hasArrived(); } Profiler.EndSample(); return; } //D.Log(ShowDebugLog, "{0} beginning progress check on Date: {1}.", DebugName, _gameTime.CurrentDate); if (CheckForCourseCorrection(directionToArrival, ref previousFrameCourseWasCorrected, ref minFrameWaitBetweenAttemptedCourseCorrectionChecks)) { //D.Log(ShowDebugLog, "{0} is making a mid course correction of {1:0.00} degrees. Frame = {2}.", //DebugName, Vector3.Angle(directionToArrival, _ship.Data.IntendedHeading), Time.frameCount); Profiler.BeginSample("ChangeHeading_Internal", _ship); ChangeHeading_Internal(directionToArrival); _ship.UpdateDebugCoursePlot(); // 5.7.16 added to keep plots current with moving targets Profiler.EndSample(); } Profiler.BeginSample("TryCheckForPeriodOrSpeedCorrection", _ship); GameTimeDuration correctedPeriod; if (TryCheckForPeriodOrSpeedCorrection(distanceToArrival, isIncreaseAboveApSpeedAllowed, halfArrivalWindowDepth, progressCheckPeriod, out correctedPeriod, out correctedSpeed)) { if (correctedPeriod != default(GameTimeDuration)) { D.AssertDefault((int)correctedSpeed); //D.Log(ShowDebugLog, "{0} is correcting progress check period from {1} to {2} en-route to {3}, Distance to arrival = {4:0.0}.", //Name, progressCheckPeriod, correctedPeriod, destination.DebugName, distanceToArrival); progressCheckPeriod = correctedPeriod; } else { D.AssertNotDefault((int)correctedSpeed); //D.Log(ShowDebugLog, "{0} is correcting speed from {1} to {2} en-route to {3}, Distance to arrival = {4:0.0}.", //Name, CurrentSpeed.GetValueName(), correctedSpeed.GetValueName(), destination.DebugName, distanceToArrival); Profiler.BeginSample("ChangeSpeed_Internal", _ship); ChangeSpeed_Internal(correctedSpeed, _isApCurrentSpeedFleetwide); Profiler.EndSample(); } } Profiler.EndSample(); //D.Log(ShowDebugLog, "{0} completed progress check on Date: {1}, NextProgressCheckPeriod: {2}.", DebugName, _gameTime.CurrentDate, progressCheckPeriod); //D.Log(ShowDebugLog, "{0} not yet arrived. DistanceToArrival = {1:0.0}.", DebugName, distanceToArrival); Profiler.EndSample(); }); }
/// <summary> /// Continues the course to target via the provided obstacleDetour. Called while underway upon encountering an obstacle. /// </summary> /// <param name="obstacleDetour">The obstacle detour's proxy.</param> private void ContinueCourseToTargetVia(AutoPilotDestinationProxy obstacleDetour) { CleanupAnyRemainingJobs(); // always called while already engaged //D.Log(ShowDebugLog, "{0} continuing course to target {1} via obstacle detour {2}. Distance to detour = {3:0.0}.", // DebugName, ApTargetFullName, obstacleDetour.DebugName, Vector3.Distance(Position, obstacleDetour.Position)); ResumeApSpeed(); // Uses ShipSpeed to catchup as we must go through this detour Vector3 newHeading = (obstacleDetour.Position - Position).normalized; ChangeHeading_Internal(newHeading, headingConfirmed: () => { //D.Log(ShowDebugLog, "{0} is now on heading to reach obstacle detour {1}.", DebugName, obstacleDetour.DebugName); InitiateNavigationTo(obstacleDetour, hasArrived: () => { // even if this is an obstacle that has appeared on the way to another obstacle detour, go around it, then direct to target RefreshCourse(CourseRefreshMode.RemoveWaypoint, obstacleDetour); ResumeDirectCourseToTarget(); }); InitiateObstacleCheckingEnrouteTo(obstacleDetour, CourseRefreshMode.ReplaceObstacleDetour); }); }
/// <summary> /// Initiates a course to the target after first going to <c>obstacleDetour</c>. This 'Initiate' version includes 2 responsibilities /// not present in the 'Continue' version. 1) It waits for the fleet to align before departure, and 2) engages the engines. /// </summary> /// <param name="obstacleDetour">The proxy for the obstacle detour.</param> private void InitiateCourseToTargetVia(AutoPilotDestinationProxy obstacleDetour) { D.AssertNull(_apNavJob); D.AssertNull(_apObstacleCheckJob); D.AssertNull(_apActionToExecuteWhenFleetIsAligned); //D.Log(ShowDebugLog, "{0} initiating course to target {1} at {2} via obstacle detour {3}. Distance to detour = {4:0.0}.", //Name, TargetFullName, ApTargetProxy.Position, obstacleDetour.DebugName, Vector3.Distance(Position, obstacleDetour.Position)); Vector3 newHeading = (obstacleDetour.Position - Position).normalized; if (newHeading.IsSameAs(Vector3.zero)) { D.Error("{0}: ObstacleDetour and current location shouldn't be able to be the same.", DebugName); } if (_isApFleetwideMove) { ChangeHeading_Internal(newHeading); _apActionToExecuteWhenFleetIsAligned = () => { //D.Log(ShowDebugLog, "{0} reports fleet {1} is aligned. Initiating departure for detour {2}.", //Name, _ship.Command.DisplayName, obstacleDetour.DebugName); _apActionToExecuteWhenFleetIsAligned = null; EngageEnginesAtApSpeed(isFleetSpeed: false); // this is a detour so catch up // even if this is an obstacle that has appeared on the way to another obstacle detour, go around it, then try direct to target InitiateNavigationTo(obstacleDetour, hasArrived: () => { RefreshCourse(CourseRefreshMode.RemoveWaypoint, obstacleDetour); ResumeDirectCourseToTarget(); }); InitiateObstacleCheckingEnrouteTo(obstacleDetour, CourseRefreshMode.ReplaceObstacleDetour); }; //D.Log(ShowDebugLog, "{0} starting wait for fleet to align, actual speed = {1:0.##}.", DebugName, ActualSpeedValue); _ship.Command.WaitForFleetToAlign(_apActionToExecuteWhenFleetIsAligned, _ship); } else { ChangeHeading_Internal(newHeading, headingConfirmed: () => { EngageEnginesAtApSpeed(isFleetSpeed: false); // this is a detour so catch up // even if this is an obstacle that has appeared on the way to another obstacle detour, go around it, then try direct to target InitiateNavigationTo(obstacleDetour, hasArrived: () => { RefreshCourse(CourseRefreshMode.RemoveWaypoint, obstacleDetour); ResumeDirectCourseToTarget(); }); InitiateObstacleCheckingEnrouteTo(obstacleDetour, CourseRefreshMode.ReplaceObstacleDetour); }); } }
/// <summary> /// Engages the pilot to pursue the target using the provided proxy. "Pursuit" here /// entails continuously adjusting speed and heading to stay within the arrival window /// provided by the proxy. There is no 'notification' to the ship as the pursuit never /// terminates until the pilot is disengaged by the ship. /// </summary> /// <param name="apTgtProxy">The proxy for the target this Pilot is being engaged to pursue.</param> /// <param name="apSpeed">The initial speed used by the pilot.</param> internal void EngagePilotToPursue(AutoPilotDestinationProxy apTgtProxy, Speed apSpeed) { Utility.ValidateNotNull(apTgtProxy); ApTargetProxy = apTgtProxy; ApSpeed = apSpeed; _isApFleetwideMove = false; _isApCurrentSpeedFleetwide = false; _isApInPursuit = true; RefreshCourse(CourseRefreshMode.NewCourse); EngagePilot(); }
/// <summary> /// Engages the pilot to move to the target using the provided proxy. It will notify the ship /// when it arrives via Ship.HandleTargetReached. /// </summary> /// <param name="apTgtProxy">The proxy for the target this Pilot is being engaged to reach.</param> /// <param name="speed">The initial speed the pilot should travel at.</param> /// <param name="isFleetwideMove">if set to <c>true</c> [is fleetwide move].</param> internal void EngagePilotToMoveTo(AutoPilotDestinationProxy apTgtProxy, Speed speed, bool isFleetwideMove) { Utility.ValidateNotNull(apTgtProxy); D.Assert(!InvalidApSpeeds.Contains(speed), speed.GetValueName()); ApTargetProxy = apTgtProxy; ApSpeed = speed; _isApFleetwideMove = isFleetwideMove; _isApCurrentSpeedFleetwide = isFleetwideMove; _isApInPursuit = false; RefreshCourse(CourseRefreshMode.NewCourse); EngagePilot(); }