/// <summary> /// Get the vessel's acceleration ability, in m/s2 /// </summary> /// <param name="propellantsConsumed">All the propellants consumed, in tons/second for each one</param> /// <param name="totalThrust">The total thrust produced, in kilonewtons</param> private void GetThrustInfo( Tally propellantsConsumed, out double totalThrust) { // Add up all the thrust for all the active engines on the vessel. // We do this as a vector because the engines might not be parallel to each other. Vector3 totalThrustVector = Vector3.zero; totalThrust = 0.0F; propellantsConsumed.Zero(); Tally availableResources = vessel.AvailableResources; int engineCount = 0; for (int engineIndex = 0; engineIndex < vessel.ActiveEngines.Count; ++engineIndex) { ModuleEngines engine = vessel.ActiveEngines[engineIndex]; if (engine.thrustPercentage > 0) { double engineKilonewtons = engine.ThrustLimit(); if (!CheatOptions.InfinitePropellant) { // Possible future consideraiton: // Get the vacuum Isp from engine.atmosphereCurve.Evaluate(0), rather than ask // for engine.realIsp, because there may be mods that tinker with the atmosphere // curve, which changes the actual Isp that the game uses for vacuum without // updating engine.realIsp. double engineIsp = engine.realIsp; double engineTotalFuelConsumption = engineKilonewtons / (KERBIN_GRAVITY * engineIsp); // tons/sec double ratioSum = 0.0; bool isStarved = false; for (int propellantIndex = 0; propellantIndex < engine.propellants.Count; ++propellantIndex) { Propellant propellant = engine.propellants[propellantIndex]; if (!ShouldIgnore(propellant.name)) { if (!availableResources.Has(propellant.name)) { isStarved = true; break; } ratioSum += propellant.ratio; } } if (isStarved) { continue; } if (ratioSum > 0) { double ratio = 1.0 / ratioSum; for (int propellantIndex = 0; propellantIndex < engine.propellants.Count; ++propellantIndex) { Propellant propellant = engine.propellants[propellantIndex]; if (!ShouldIgnore(propellant.name)) { double consumptionRate = ratio * propellant.ratio * engineTotalFuelConsumption; // tons/sec propellantsConsumed.Add(propellant.name, consumptionRate); } } } } // if we need to worry about fuel ++engineCount; totalThrustVector += engine.Forward() * (float)engineKilonewtons; } // if the engine is operational } // for each engine module on the part totalThrust = totalThrustVector.magnitude; if (engineCount != lastEngineCount) { lastEngineCount = engineCount; Logging.Log(engineCount.ToString("Active engines: ##0")); } }
public void LateUpdate() { logFuelCheatActivation(); try { if (!BurnInfo.IsInitialized) return; // can't do anything string customDescription = null; double dVrequired = ImpactTracker.ImpactSpeed; int timeUntil = -1; if (double.IsNaN(dVrequired)) { // No impact info is available. Do we have closest-approach info? dVrequired = ClosestApproachTracker.Velocity; if (double.IsNaN(dVrequired)) { // No closest-approach info available either, use the maneuver dV remaining. dVrequired = BurnInfo.DvRemaining; timeUntil = SecondsUntilNode(); } else { // We have closest-approach info, use the description from that. customDescription = ClosestApproachTracker.Description; timeUntil = ClosestApproachTracker.TimeUntil; } } else { // We have impact info, use the description from that. customDescription = ImpactTracker.Description; // TODO: enable countdown to retro-burn, not doing it now 'coz it needs more math & logic // timeUntil = ImpactTracker.TimeUntil; } // At this point, either we have a dVrequired or not. If we have one, we might // have a description (meaning it's one of our custom trackers from this mod) // or we might not (meaning "leave it alone at let the stock game decide what to say"). if (double.IsNaN(dVrequired)) return; if (FlightGlobals.ActiveVessel == null) return; if (FlightGlobals.ActiveVessel.IsEvaKerbal()) { // it's a kerbal on EVA BurnInfo.Duration = EVA_KERBAL_LABEL; BurnInfo.Countdown = string.Empty; } else { // it's a ship, not an EVA kerbal vessel.Refresh(); propellantsConsumed = new Tally(); bool isInsufficientFuel; double floatBurnSeconds = GetBurnTime(dVrequired, out isInsufficientFuel); int burnSeconds = double.IsInfinity(floatBurnSeconds) ? -1 : (int)(0.5 + floatBurnSeconds); if (burnSeconds != lastBurnTime) { lastBurnTime = burnSeconds; if (isInsufficientFuel) { lastUpdateText = ESTIMATED_BURN_LABEL + TimeFormatter.Default.warn(burnSeconds); } else { lastUpdateText = ESTIMATED_BURN_LABEL + TimeFormatter.Default.format(burnSeconds); } } BurnInfo.Duration = lastUpdateText; int timeUntilBurnStart = timeUntil - burnSeconds / 2; if (timeUntilBurnStart != lastTimeUntilBurnStart) { lastTimeUntilBurnStart = timeUntilBurnStart; BurnInfo.Countdown = Countdown.ForSeconds(timeUntilBurnStart); } } if (customDescription == null) { // No custom description available, turn off the alternate display BurnInfo.AlternateDisplayEnabled = false; } else { // We have alternate info to show BurnInfo.TimeUntil = customDescription; BurnInfo.AlternateDisplayEnabled = true; } } catch (Exception e) { Logging.Exception(e); BurnInfo.Duration = e.GetType().Name + ": " + e.Message + " -> " + e.StackTrace; } }
/// <summary> /// Get the vessel's acceleration ability, in m/s2 /// </summary> /// <param name="propellantsConsumed">All the propellants consumed, in tons/second for each one</param> /// <param name="totalThrust">The total thrust produced, in kilonewtons</param> private void GetThrustInfo( Tally propellantsConsumed, out double totalThrust) { // Add up all the thrust for all the active engines on the vessel. // We do this as a vector because the engines might not be parallel to each other. Vector3 totalThrustVector = Vector3.zero; totalThrust = 0.0F; propellantsConsumed.Zero(); Tally availableResources = vessel.AvailableResources; int engineCount = 0; for (int engineIndex = 0; engineIndex < vessel.ActiveEngines.Count; ++engineIndex) { ModuleEngines engine = vessel.ActiveEngines[engineIndex]; if (engine.thrustPercentage > 0) { double engineKilonewtons = engine.ThrustLimit(); if (!CheatOptions.InfinitePropellant) { // Possible future consideraiton: // Get the vacuum Isp from engine.atmosphereCurve.Evaluate(0), rather than ask // for engine.realIsp, because there may be mods that tinker with the atmosphere // curve, which changes the actual Isp that the game uses for vacuum without // updating engine.realIsp. double engineIsp = engine.realIsp; double engineTotalFuelConsumption = engineKilonewtons / (KERBIN_GRAVITY * engineIsp); // tons/sec double ratioSum = 0.0; bool isStarved = false; for (int propellantIndex = 0; propellantIndex < engine.propellants.Count; ++propellantIndex) { Propellant propellant = engine.propellants[propellantIndex]; if (!ShouldIgnore(propellant.name)) { if (!availableResources.Has(propellant.name)) { isStarved = true; break; } ratioSum += propellant.ratio; } } if (isStarved) continue; if (ratioSum > 0) { double ratio = 1.0 / ratioSum; for (int propellantIndex = 0; propellantIndex < engine.propellants.Count; ++propellantIndex) { Propellant propellant = engine.propellants[propellantIndex]; if (!ShouldIgnore(propellant.name)) { double consumptionRate = ratio * propellant.ratio * engineTotalFuelConsumption; // tons/sec propellantsConsumed.Add(propellant.name, consumptionRate); } } } } // if we need to worry about fuel ++engineCount; totalThrustVector += engine.Forward() * (float)engineKilonewtons; } // if the engine is operational } // for each engine module on the part totalThrust = totalThrustVector.magnitude; if (engineCount != lastEngineCount) { lastEngineCount = engineCount; Logging.Log(engineCount.ToString("Active engines: ##0")); } }
/// <summary> /// Calculate how long we can burn at full throttle until something important runs out. /// </summary> /// <param name="vessel"></param> /// <param name="propellantsConsumed"></param> /// <param name="propellantsAvailable"></param> /// <param name="maxBurnTime"></param> private static double CalculateMaxBurnTime(ShipState vessel, Tally propellantsConsumed) { double maxBurnTime = double.PositiveInfinity; Tally availableResources = vessel.AvailableResources; foreach (string resourceName in propellantsConsumed.Keys) { if (ShouldIgnore(resourceName)) { // ignore this for burn time, it's replenishable continue; } if (!availableResources.Has(resourceName)) { // we're all out! return 0.0; } double availableAmount = availableResources[resourceName]; double rate = propellantsConsumed[resourceName]; double burnTime = availableAmount / rate; if (burnTime < maxBurnTime) maxBurnTime = burnTime; } return maxBurnTime; }
public void LateUpdate() { logFuelCheatActivation(); try { BetterBurnTimeData api = BetterBurnTimeData.Current; if (api != null) { api.Reset(); } if (!BurnInfo.IsInitialized) { return; // can't do anything } BetterBurnTimeData.BurnType burnType = BetterBurnTimeData.BurnType.None; string customDescription = null; double dVrequired = ImpactTracker.ImpactSpeed; double timeUntil = double.NaN; bool shouldDisplayCountdown = false; if (double.IsNaN(dVrequired)) { // No impact info is available. Do we have closest-approach info? dVrequired = ClosestApproachTracker.Velocity; if (double.IsNaN(dVrequired)) { // No closest-approach info is available either. At this point, we've exhausted all // of our special-tracking-burn-information options (impact, closest approach), so // we won't show any burn information or countdown. (Before KSP 1.5, we would have // checked for a maneuver node, and if present, we would have shown a countdown // and our revised burn time estimate. KSP 1.5, however, introduced drastically improved // burn-time indication in stock, so there's no point in trying to compete with that-- // it's good enough, so leave it alone. shouldDisplayCountdown = false; // Do we have a maneuver node? dVrequired = BurnInfo.DvRemaining; if (double.IsNaN(dVrequired)) { // No, there's no maneuver node. burnType = BetterBurnTimeData.BurnType.None; // If we've got an upcoming atmosphere transition, include that info. customDescription = AtmosphereTracker.Description; if (customDescription == null) { customDescription = GeosyncTracker.Description; timeUntil = 0; } else { timeUntil = AtmosphereTracker.TimeUntil; } } else { // Yep, there's a maneuver node. timeUntil = SecondsUntilNode(); burnType = BetterBurnTimeData.BurnType.Maneuver; } } else { // We have closest-approach info, use the description from that. customDescription = ClosestApproachTracker.Description; timeUntil = ClosestApproachTracker.TimeUntil; shouldDisplayCountdown = true; burnType = BetterBurnTimeData.BurnType.Rendezvous; } } else { // We have impact info, use the description from that. customDescription = ImpactTracker.Description; timeUntil = ImpactTracker.TimeUntil; // TODO: enable countdown to retro-burn, not doing it now 'coz it needs more math & logic // shouldDisplayCountdown = true burnType = BetterBurnTimeData.BurnType.Impact; } if (FlightGlobals.ActiveVessel == null) { BurnInfo.Countdown = string.Empty; if (customDescription == null) { BurnInfo.AlternateDisplayEnabled = false; } return; } if (double.IsNaN(dVrequired) && !double.IsNaN(timeUntil)) { // Special case of knowing a time until an event, but there's no dV associated // with it and therefore no burn display. BurnInfo.Duration = string.Empty; BurnInfo.TimeUntil = customDescription; BurnInfo.AlternateDisplayEnabled = true; BurnInfo.Countdown = string.Empty; return; } // At this point, either we have a dVrequired or not. If we have one, we might // have a description (meaning it's one of our custom trackers from this mod) // or we might not (meaning "leave it alone at let the stock game decide what to say"). if ((burnType == BetterBurnTimeData.BurnType.Maneuver) || double.IsNaN(dVrequired) || (FlightGlobals.ActiveVessel == null)) { BurnInfo.Countdown = string.Empty; if (customDescription == null) { BurnInfo.AlternateDisplayEnabled = false; } return; } if (FlightGlobals.ActiveVessel.IsEvaKerbal()) { // it's a kerbal on EVA BurnInfo.Duration = EVA_KERBAL_LABEL; BurnInfo.Countdown = string.Empty; } else { // it's a ship, not an EVA kerbal vessel.Refresh(); propellantsConsumed = new Tally(); bool isInsufficientFuel; double floatBurnSeconds = GetBurnTime(dVrequired, out isInsufficientFuel); int burnSeconds = double.IsInfinity(floatBurnSeconds) ? -1 : (int)(0.5 + floatBurnSeconds); if (burnSeconds != lastBurnTime) { lastBurnTime = burnSeconds; if (isInsufficientFuel) { lastUpdateText = ESTIMATED_BURN_LABEL + TimeFormatter.Default.warn(burnSeconds); } else { lastUpdateText = ESTIMATED_BURN_LABEL + TimeFormatter.Default.format(burnSeconds); } } BurnInfo.Duration = lastUpdateText; int intTimeUntil = (double.IsNaN(timeUntil) || !shouldDisplayCountdown) ? -1 : (int)timeUntil; int timeUntilBurnStart = intTimeUntil - burnSeconds / 2; if (timeUntilBurnStart != lastTimeUntilBurnStart) { lastTimeUntilBurnStart = timeUntilBurnStart; BurnInfo.Countdown = Countdown.ForSeconds(timeUntilBurnStart); } if (api != null) { api.burnType = burnType; api.burnTime = floatBurnSeconds; api.dV = dVrequired; api.timeUntil = timeUntil; api.isInsufficientFuel = isInsufficientFuel; } } if (customDescription == null) { // No custom description available, turn off the alternate display BurnInfo.AlternateDisplayEnabled = false; } else { // We have alternate info to show BurnInfo.TimeUntil = customDescription; BurnInfo.AlternateDisplayEnabled = true; } } catch (Exception e) { Logging.Exception(e); BurnInfo.Duration = e.GetType().Name + ": " + e.Message + " -> " + e.StackTrace; } }
/// <summary> /// Refresh cached information. /// </summary> public void Refresh() { if (!NeedUpdate()) { return; } lastUpdateTime = DateTime.Now; Vessel vessel = FlightGlobals.ActiveVessel; if (vessel == null) { return; } vesselId = vessel.id; vesselPartCount = vessel.parts.Count; totalMass = vessel.GetTotalMass(); activeEngines.Clear(); for (int engineIndex = 0; engineIndex < Propulsion.Engines.Count; ++engineIndex) { Part part = Propulsion.Engines[engineIndex]; for (int moduleIndex = 0; moduleIndex < part.Modules.Count; ++moduleIndex) { ModuleEngines engine = part.Modules[moduleIndex] as ModuleEngines; if (engine == null) { continue; } if (!engine.isOperational) { continue; } if (!CheatOptions.InfinitePropellant) { bool isDeprived = false; for (int propellantIndex = 0; propellantIndex < engine.propellants.Count; ++propellantIndex) { Propellant propellant = engine.propellants[propellantIndex]; if (propellant.isDeprived && !propellant.ignoreForIsp) { isDeprived = true; // out of fuel! break; } } if (isDeprived) { continue; // skip this engine } } activeEngines.Add(engine); } } availableResources = new Tally(); for (int tankIndex = 0; tankIndex < Propulsion.Tanks.Count; ++tankIndex) { Part part = Propulsion.Tanks[tankIndex]; for (int resourceIndex = 0; resourceIndex < part.Resources.Count; ++resourceIndex) { PartResource resource = part.Resources[resourceIndex]; if (resource.flowState) { availableResources.Add(resource.resourceName, resource.amount * resource.info.density); } } } }