/// <summary> /// Changes the direction the ship is headed in normalized world space coordinates. /// </summary> /// <param name="newHeading">The new direction in world coordinates, normalized.</param> /// <param name="isManualOverride">if set to <c>true</c> [is manual override].</param> /// <returns> /// <c>true</c> if the command was accepted, <c>false</c> if the command is a duplicate. /// </returns> public bool ChangeHeading(Vector3 newHeading, bool isManualOverride = true) { if (DebugSettings.Instance.StopShipMovement) { Disengage(); return false; } if (isManualOverride) { Disengage(); } newHeading.ValidateNormalized(); if (newHeading.IsSameDirection(Data.RequestedHeading, 0.1F)) { D.Warn("Duplicate ChangeHeading Command to {0} on {1}.", newHeading, Data.Name); return false; } Data.RequestedHeading = newHeading; if (_headingJob != null && _headingJob.IsRunning) { _headingJob.Kill(); } _headingJob = new Job(ExecuteHeadingChange(), toStart: true, onJobComplete: (wasKilled) => { if (wasKilled) { D.Warn("{0} turn command cancelled. Current Heading is {1}.", Data.Name, Data.CurrentHeading); } else { D.Log("Turn complete. {0} current heading is {1}.", Data.Name, Data.CurrentHeading); } }); return true; }
/// <summary> /// Changes the direction the ship is headed in normalized world space coordinates. /// </summary> /// <param name="newHeading">The new direction in world coordinates, normalized.</param> /// <returns><c>true</c> if the command was accepted, <c>false</c> if the command is a duplicate.</returns> public bool ChangeHeading(Vector3 newHeading) { newHeading.ValidateNormalized(); if (Mathfx.Approx(newHeading, _data.RequestedHeading, 0.01F)) { //if (newHeading.IsSameDirection(_data.RequestedHeading)) { // too precise D.Warn("Duplicate ChangeHeading Command to {0} on {1}.", newHeading, _data.Name); return false; } _data.RequestedHeading = newHeading; if (_headingJob != null && _headingJob.IsRunning) { _headingJob.Kill(); } _headingJob = new Job(ExecuteHeadingChange(), toStart: true, onJobComplete: (wasKilled) => { string message = "Turn complete. {0} current heading is {1}."; if (wasKilled) { message = "{0} turn command cancelled. Current Heading is {1}."; } D.Log(message, _data.Name, _data.CurrentHeading); }); return true; }
private IEnumerator OperateCollisionAvoidancePropulsionIn(Vector3 worldSpaceDirectionToAvoidCollision, GameDate errorDate) { worldSpaceDirectionToAvoidCollision.ValidateNormalized(); GameDate currentDate; while (true) { ApplyCollisionAvoidancePropulsionIn(worldSpaceDirectionToAvoidCollision); currentDate = _gameTime.CurrentDate; if (currentDate > errorDate) { D.Warn("{0}: CurrentDate {1} > ErrorDate {2} while avoiding collision.", DebugName, currentDate, errorDate); } yield return Yielders.WaitForFixedUpdate; } }
/// <summary> /// Changes the direction the ship is headed in normalized world space coordinates. /// </summary> /// <param name="newHeading">The new direction in world coordinates, normalized.</param> /// <returns><c>true</c> if the heading change was accepted.</returns> private bool ChangeHeading(Vector3 newHeading) { if (DebugSettings.Instance.StopShipMovement) { DisengageAutoPilot(); return false; } newHeading.ValidateNormalized(); if (newHeading.IsSameDirection(_data.RequestedHeading, 0.1F)) { D.Warn("{0} received a duplicate ChangeHeading Command to {1}.", _ship.FullName, newHeading); return false; } if (_headingJob != null && _headingJob.IsRunning) { _headingJob.Kill(); } D.Log("{0} changing heading to {1}.", _ship.FullName, newHeading); _data.RequestedHeading = newHeading; IsBearingConfirmed = false; _headingJob = new Job(ExecuteHeadingChange(), toStart: true, onJobComplete: (jobWasKilled) => { if (!_isDisposing) { if (jobWasKilled) { D.Log("{0}'s turn order to {1} has been cancelled.", _ship.FullName, _data.RequestedHeading); } else { IsBearingConfirmed = true; D.Log("{0}'s turn to {1} is complete. Heading deviation is {2:0.00}.", _ship.FullName, _data.RequestedHeading, Vector3.Angle(_data.CurrentHeading, _data.RequestedHeading)); } // ExecuteHeadingChange() appeared to generate angular velocity which continued to turn the ship after the Job was complete. // The actual culprit was the physics engine which when started, found Creators had placed the non-kinematic ships at the same // location, relying on the formation generator to properly separate them later. The physics engine came on before the formation // had been deployed, resulting in both velocity and angular velocity from the collisions. The fix was to make the ship rigidbodies // kinematic until the formation had been deployed. //_rigidbody.angularVelocity = Vector3.zero; } }); return true; }
/// <summary> /// Changes the direction the ship is headed. /// </summary> /// <param name="newHeading">The new direction in world coordinates, normalized.</param> /// <param name="headingConfirmed">Delegate that fires when the ship gets to the new heading.</param> private void ChangeHeading_Internal(Vector3 newHeading, Action headingConfirmed = null) { newHeading.ValidateNormalized(); //D.Log(ShowDebugLog, "{0} received ChangeHeading to (local){1}.", DebugName, _ship.transform.InverseTransformDirection(newHeading)); // Warning: Don't test for same direction here. Instead, if same direction, let the coroutine respond one frame // later. Reasoning: If previous Job was just killed, next frame it will assert that the autoPilot isn't engaged. // However, if same direction is determined here, then onHeadingConfirmed will be // executed before that assert test occurs. The execution of onHeadingConfirmed() could initiate a new autopilot order // in which case the assert would fail the next frame. By allowing the coroutine to respond, that response occurs one frame later, // allowing the assert to successfully pass before the execution of onHeadingConfirmed can initiate a new autopilot order. if (IsTurnUnderway) { // 5.8.16 allowing heading changes to kill existing heading jobs so course corrections don't get skipped if job running //D.Log(ShowDebugLog, "{0} is killing existing change heading job and starting another. Frame: {1}.", DebugName, Time.frameCount); KillChgHeadingJob(); } _shipData.IntendedHeading = newHeading; _engineRoom.HandleTurnBeginning(); string jobName = "{0}.ChgHeadingJob".Inject(DebugName); _chgHeadingJob = _jobMgr.StartGameplayJob(ChangeHeading(newHeading), jobName, isPausable: true, jobCompleted: (jobWasKilled) => { if (jobWasKilled) { // 5.8.16 Killed scenarios better understood: 1) External ChangeHeading call while in AutoPilot, // 2) sequential external ChangeHeading calls, 3) AutoPilot detouring around an obstacle, // 4) AutoPilot resuming course to Target after detour, 5) AutoPilot course correction, and // 6) 12.9.16 JobManager kill at beginning of scene change. // Thoughts: All Killed scenarios will result in an immediate call to this ChangeHeading_Internal method. Responding now // (a frame later) with either onHeadingConfirmed or changing _ship.IsHeadingConfirmed is unnecessary and potentially // wrong. It is unnecessary since the new ChangeHeading_Internal call will set IsHeadingConfirmed correctly and respond // with onHeadingConfirmed() as soon as the new ChangeHeading Job properly finishes. // UNCLEAR Thoughts on potentially wrong: Which onHeadingConfirmed delegate would be executed? 1) the previous source of the // ChangeHeading order which is probably not listening (the autopilot navigation Job has been killed and may be about // to be replaced by a new one) or 2) the new source that generated the kill? If it goes to the new source, // that is going to be accomplished anyhow as soon as the ChangeHeading Job launched by the new source determines // that the heading is confirmed so a response here would be a duplicate. // 12.7.16 Almost certainly 1) as the delegate creates another complete class to hold all the values that // need to be executed when fired. // 12.12.16 An AssertNull(_jobRef) here can fail as the reference can refer to a new Job, created // right after the old one was killed due to the 1 frame delay in execution of jobCompleted(). My attempts at allowing // the AssertNull to occur failed. I believe this is OK as _jobRef is nulled from KillXXXJob() and, if // the reference is replaced by a new Job, then the old Job is no longer referenced which is the objective. Jobs Kill()ed // centrally by JobManager won't null the reference, but this only occurs during scene transitions. } else { _chgHeadingJob = null; //D.Log(ShowDebugLog, "{0}'s turn to {1} complete. Deviation = {2:0.00} degrees.", //DebugName, _ship.Data.IntendedHeading, Vector3.Angle(_ship.Data.CurrentHeading, _ship.Data.IntendedHeading)); _engineRoom.HandleTurnCompleted(); if (headingConfirmed != null) { headingConfirmed(); } } }); }