public void Update() { if (!HighLogic.LoadedSceneIsEditor) { return; } SetAnimationRatio(0, throttleAnimationState); foreach (var engine in moduleEngines) { currentEngine = engine; UpdateFX(); } }
// Finds the active moduleEngine module from the MultiModeEngine partModule private void FetchActiveMode() { for (var i = 0; i < moduleEngines.Length; i++) { var persistentEngineModule = moduleEngines[i]; persistentEngineModule.powerEffectName = powerEffectNameList[i]; persistentEngineModule.runningEffectName = runningEffectNameList[i]; ApplyEffect(persistentEngineModule.powerEffectName, 0); ApplyEffect(persistentEngineModule.runningEffectName, 0); } if (!isMultiMode) { return; } currentEngine = moduleEngines.FirstOrDefault(m => m.engine == (multiModeEngine.runningPrimary ? multiModeEngine.PrimaryEngine : multiModeEngine.SecondaryEngine)); }
// Physics update public void FixedUpdate() // FixedUpdate is also called while not staged { if (this.vessel is null || currentEngine.engine is null || !isEnabled) { return; } fixedUpdateCount++; var universalTime = Planetarium.GetUniversalTime(); // restore heading at load if (HasPersistentHeadingEnabled && fixedUpdateCount <= 60 && vesselAlignmentWithAutopilotMode > 0.995) { vessel.Autopilot.SetMode(persistentAutopilotMode); vessel.PersistHeading(TimeWarp.fixedDeltaTime, headingTolerance, vesselChangedSoiCountdown > 0); } else { persistentAutopilotMode = vessel.Autopilot.Mode; } //vesselHeadingVersusManeuver = vessel.VesselOrbitHeadingVersusManeuverVector(); //vesselHeadingVersusManeuverInDegrees = Math.Acos(Math.Max(-1, Math.Min(1, vesselHeadingVersusManeuver))) * Rad2Deg; _kerbalismResourceChangeRequest.Clear(); if (vesselChangedSoiCountdown > 0) { vesselChangedSoiCountdown--; } // Checks if moduleEngine mode wasn't switched FetchActiveMode(); var processedEngines = isMultiMode ? new[] { currentEngine } : moduleEngines; // Realtime mode if (!vessel.packed) { vesselAlignmentWithAutopilotMode = vessel.HeadingVersusAutopilotVector(universalTime); // Update persistent thrust throttle if NOT transitioning from warp to realtime if (!warpToReal) { UpdatePersistentThrottle(); } for (var i = 0; i < processedEngines.Length; i++) { currentEngine = processedEngines[i]; ResetMonitoringVariables(); // Update persistent thrust isp if NOT transitioning from warp to realtime if (!warpToReal) { UpdatePersistentIsp(); } currentEngine.engineHasAnyMassLessPropellants = currentEngine.engine.propellants.Any(m => m.resourceDef.density == 0); if (processMasslessSeparately && currentEngine.engineHasAnyMassLessPropellants) { ReloadPropellantsWithoutMasslessPropellants(); } //if (vesselHeadingVersusManeuverInDegrees > maneuverTolerance) //{ // moduleEngine.maxFuelFlow = 1e-10f; // finalThrust = 0; //} //else if (!currentEngine.engine.getIgnitionState) { currentEngine.finalThrust = 0; // restore maximum flow RestoreMaxFuelFlow(); } else if (!currentEngine.engineHasAnyMassLessPropellants && currentEngine.engine.propellantReqMet > 0) { // Mass flow rate var massFlowRate = currentEngine.persistentIsp > 0 ? currentEngine.engine.currentThrottle * currentEngine.engine.maxThrust / (currentEngine.persistentIsp * PhysicsGlobals.GravitationalAcceleration) : 0; // Change in mass over time interval dT var deltaMass = massFlowRate * TimeWarp.fixedDeltaTime; // Resource demand from propellants with mass currentEngine.demandMass = currentEngine.averageDensity > 0 ? deltaMass / currentEngine.averageDensity : 0; // Calculate resource demands currentEngine.fuelDemands = CalculateDemands(currentEngine.demandMass); // Apply resource demands & test for resource depletion ApplyDemands(currentEngine.fuelDemands, ref currentEngine.propellantReqMetFactor); // calculate maximum flow var maxFuelFlow = currentEngine.persistentIsp > 0 ? currentEngine.engine.maxThrust / (currentEngine.persistentIsp * PhysicsGlobals.GravitationalAcceleration) : 0; // adjust fuel flow currentEngine.engine.maxFuelFlow = maxFuelFlow > 0 && currentEngine.propellantReqMetFactor > 0 ? (float)(maxFuelFlow * currentEngine.propellantReqMetFactor) : 1e-10f; // update displayed thrust and fx currentEngine.finalThrust = currentEngine.engine.currentThrottle * currentEngine.engine.maxThrust * Math.Min(currentEngine.propellantReqMetFactor, currentEngine.engine.propellantReqMet * 0.01f); } else { // restore maximum flow RestoreMaxFuelFlow(); currentEngine.propellantReqMetFactor = currentEngine.engine.propellantReqMet * 0.01f; currentEngine.finalThrust = currentEngine.engine.GetCurrentThrust(); } UpdatePropellantReqMetFactorQueue(); UpdateFX(); SetThrottleAnimation(); UpdateBuffers(); } } else { for (var i = 0; i < processedEngines.Length; i++) { currentEngine = processedEngines[i]; ResetMonitoringVariables(); // restore maximum flow RestoreMaxFuelFlow(); if (persistentThrottle > 0 && currentEngine.persistentIsp > 0 && isPersistentEngine && HasPersistentThrust) { if (TimeWarp.CurrentRateIndex == 0) { warpToReal = true; // Set to true for transition to realtime } // Calculated requested thrust //var requestedThrust = vesselHeadingVersusManeuverInDegrees <= maneuverTolerance ? moduleEngine.thrustPercentage * 0.01f * persistentThrottle * moduleEngine.maxThrust : 0; var requestedThrust = currentEngine.engine.thrustPercentage * 0.01f * persistentThrottle * currentEngine.engine.maxThrust; var thrustVector = part.transform.up; // Thrust direction unit vector // Calculate deltaV vector & resource demand from propellants with mass var deltaVVector = CalculateDeltaVVector(currentEngine.averageDensity, vessel.totalMass, TimeWarp.fixedDeltaTime, requestedThrust, currentEngine.persistentIsp, thrustVector, out currentEngine.demandMass); // Calculate resource demands currentEngine.fuelDemands = CalculateDemands(currentEngine.demandMass); // Apply resource demands & test for resource depletion ApplyDemands(currentEngine.fuelDemands, ref currentEngine.propellantReqMetFactor); // Apply deltaV vector at UT & dT to orbit if resources not depleted if (currentEngine.propellantReqMetFactor > 0) { currentEngine.finalThrust = requestedThrust * currentEngine.propellantReqMetFactor; vessel.orbit.Perturb(deltaVVector * currentEngine.propellantReqMetFactor, universalTime); } // Otherwise log warning and drop out of TimeWarp if throttle on & depleted else if (persistentThrottle > 0) { currentEngine.finalThrust = 0; Debug.Log("[PersistentThrust]: Thrust warp stopped - propellant depleted"); ScreenMessages.PostScreenMessage(Localizer.Format("#LOC_PT_StoppedDepleted"), 5.0f, ScreenMessageStyle.UPPER_CENTER); // Return to realtime TimeWarp.SetRate(0, true); if (!vessel.IsControllable) { persistentThrottle = 0; vessel.ctrlState.mainThrottle = 0; } } else { currentEngine.finalThrust = 0; } SetThrottleAnimation(); UpdateFX(); } else { currentEngine.finalThrust = 0; SetThrottleAnimation(); UpdateFX(); UpdateBuffers(); } } if (vessel.IsControllable && HasPersistentHeadingEnabled) { vesselAlignmentWithAutopilotMode = vessel.PersistHeading(TimeWarp.fixedDeltaTime, headingTolerance, vesselChangedSoiCountdown > 0, vesselAlignmentWithAutopilotMode == 1); } } // display final thrust in a user friendly way thrustTxt = Utils.FormatThrust(moduleEngines.Sum(m => m.finalThrust)); previousFixedDeltaTime = TimeWarp.fixedDeltaTime; UpdateMasslessConsumption(); }
// Update is called during refresh frame, which can be less frequent than FixedUpdate which is called every processing frame public override void OnUpdate() { var processedEngines = isMultiMode ? new[] { currentEngine } : moduleEngines; for (var i = 0; i < processedEngines.Length; i++) { currentEngine = processedEngines[i]; if (currentEngine.engine == null) { continue; } // hide stock thrust currentEngine.engine.Fields["finalThrust"].guiActive = false; currentEngine.engine.Fields["realIsp"].guiActive = false; currentEngine.engine.Fields["propellantReqMet"].guiActive = false; } var averagePropellantReqMetFactor = isMultiMode ? currentEngine.propellantReqMetFactor : moduleEngines.Average(m => m.propellantReqMetFactor); propellantReqMet = averagePropellantReqMetFactor * 100; var anyMasslessPropellants = isMultiMode ? currentEngine.engine.propellants.Any(m => m.resourceDef.density == 0) : moduleEngines.SelectMany(m => m.engine.propellants.Where(p => p.resourceDef.density == 0)).Any(); var anyAutoMaximizePersistentIsp = isMultiMode ? currentEngine.autoMaximizePersistentIsp : moduleEngines.Any(m => m.autoMaximizePersistentIsp); realIsp = !vessel.packed && !anyMasslessPropellants ? currentEngine.engine.realIsp : vessel.packed && (MaximizePersistentIsp || anyAutoMaximizePersistentIsp) || persistentThrottle == 0 ? currentEngine.persistentIsp : currentEngine.persistentIsp * averagePropellantReqMetFactor; if (!isPersistentEngine || !HasPersistentThrust) { return; } // When transitioning from TimeWarp to real update throttle if (warpToReal) { SetThrottle(persistentThrottle, true); warpToReal = false; } if (vessel.packed) { // maintain thrust setting during TimeWarp vessel.ctrlState.mainThrottle = persistentThrottle; // stop engines when X pressed if (Input.GetKeyDown(KeyCode.X)) { SetThrottle(0, returnToRealtimeAfterKeyPressed); } // full throttle when Z pressed else if (Input.GetKeyDown(KeyCode.Z)) { SetThrottle(1, returnToRealtimeAfterKeyPressed); } // increase throttle when Shift pressed else if (Input.GetKeyDown(KeyCode.LeftShift)) { SetThrottle(Mathf.Min(1, persistentThrottle + 0.01f), returnToRealtimeAfterKeyPressed); } // decrease throttle when Ctrl pressed else if (Input.GetKeyDown(KeyCode.LeftControl)) { SetThrottle(Mathf.Max(0, persistentThrottle - 0.01f), returnToRealtimeAfterKeyPressed); } } else { TimeWarp.GThreshold = 12; } }
// Make "moduleEngine" and "moduleEngineFx" fields refer to the ModuleEngines and ModuleEnginesFX modules in part.Modules private void FindModuleEngines() { var moduleEnginesCount = 0; var moduleEnginesList = new List <PersistentEngineModule>(); foreach (var partModule in part.Modules) { if (partModule is MultiModeEngine multiMode) { multiModeEngine = multiMode; } else if (partModule is ModuleEngines engine) { currentEngine = new PersistentEngineModule { engine = engine }; if (engine is ModuleEnginesFX engineFx) { powerEffectNameList.Add(engineFx.powerEffectName); currentEngine.powerEffectName = engineFx.powerEffectName; ApplyEffect(currentEngine.powerEffectName, 0); runningEffectNameList.Add(engineFx.runningEffectName); currentEngine.runningEffectName = engineFx.runningEffectName; ApplyEffect(currentEngine.runningEffectName, 0); } moduleEnginesList.Add(currentEngine); moduleEnginesCount++; } } moduleEngines = moduleEnginesList.ToArray(); if (moduleEnginesCount == 1 && multiModeEngine == null) { Debug.Log("[PersistentThrust]: enabled for " + part.partInfo.title + " " + part.persistentId); isPersistentEngine = true; } else if (moduleEnginesCount == 0) { Debug.LogError("[PersistentThrust]: found no compatible engines, disabling PersistentThrust for " + part.partInfo.title + " " + part.persistentId); isPersistentEngine = false; } else if (moduleEnginesCount == 1 && multiModeEngine != null) { Debug.LogWarning("[PersistentThrust]: found Insufficient engines for MultiMode, using single engine mode PersistentThrust for " + part.partInfo.title + " " + part.persistentId); isPersistentEngine = false; } else if (moduleEnginesCount > 1 && multiModeEngine == null) { Debug.LogWarning("[PersistentThrust]: found multiple engines but no MultiMode PartModule, enabled multi engine PersistentThrust for " + part.partInfo.title + " " + part.persistentId); isPersistentEngine = true; } else if (multiModeEngine != null && moduleEnginesCount == 2) { Debug.Log("[PersistentThrust]: enabled MultiMode for " + part.partInfo.title + " " + part.persistentId); isPersistentEngine = true; isMultiMode = true; } else { Debug.LogError("[PersistentThrust]: failed to initialize for " + part.partInfo.title + " " + part.persistentId); isPersistentEngine = false; } if (!isPersistentEngine) { return; } var engineFxList = part.FindModulesImplementing <ModuleEnginesFX>(); foreach (var engineFx in engineFxList) { engineFx.powerEffectName = string.Empty; engineFx.runningEffectName = string.Empty; } }