void AddTank(FuelTank tank) { GUILayout.Label(addLabelCache[tank.name], GUILayout.Width(150)); if (GUILayout.Button("Add", GUILayout.Width(120))) { tank.maxAmount = tank_module.AvailableVolume * tank.utilization; tank.amount = tank.fillable ? tank.maxAmount : 0; tank.maxAmountExpression = tank.maxAmount.ToString(); GameEvents.onEditorShipModified.Fire(EditorLogic.fetch.ship); tank_module.MarkWindowDirty(); //Debug.LogWarning ("[MFT] Adding tank " + tank.name + " maxAmount: " + tank.maxAmountExpression ?? "null"); } }
protected void ChangeResources(double volumeRatio, bool propagate = false) { // The used volume will rescale automatically when setting maxAmount for (int i = 0; i < tankList.Count; i++) { FuelTank tank = tankList[i]; bool save_propagate = tank.propagate; tank.propagate = propagate; tank.maxAmount *= volumeRatio; tank.propagate = save_propagate; } }
void AddTank(FuelTank tank) { string extraData = "Max: " + (tank_module.AvailableVolume * tank.utilization).ToStringExt("S3") + "L (+" + ModuleFuelTanks.FormatMass((float)(tank_module.AvailableVolume * tank.mass)) + " )"; GUILayout.Label(extraData, GUILayout.Width(150)); if (GUILayout.Button("Add", GUILayout.Width(120))) { tank.maxAmount = tank_module.AvailableVolume * tank.utilization; tank.amount = tank.fillable ? tank.maxAmount : 0; tank.maxAmountExpression = tank.maxAmount.ToString(); //Debug.LogWarning ("[MFT] Adding tank " + tank.name + " maxAmount: " + tank.maxAmountExpression ?? "null"); } }
private bool CalculateLowestTankTemperature() { bool result = false; lowestTankTemperature = 300; for (int i = tankList.Count - 1; i >= 0; --i) { FuelTank tank = tankList[i]; if (tank.maxAmount > 0d && (tank.vsp > 0.0 || tank.loss_rate > 0d)) { lowestTankTemperature = Math.Min(lowestTankTemperature, tank.temperature); result = true; } } return(result); }
void RecordTankTypeResources(HashSet <string> resources, string type) { TankDefinition def; if (!MFSSettings.tankDefinitions.Contains(type)) { return; } def = MFSSettings.tankDefinitions[type]; for (int i = 0; i < def.tankList.Count; i++) { FuelTank tank = def.tankList[i]; resources.Add(tank.name); } }
partial void OnStartRF(StartState state) { base.OnStart(state); GameEvents.onVesselWasModified.Add(OnVesselWasModified); GameEvents.onPartDestroyed.Add(OnPartDestroyed); if (HighLogic.LoadedSceneIsFlight) { for (int i = 0; i < vessel.vesselModules.Count; i++) { if (vessel.vesselModules[i] is FlightIntegrator) { _flightIntegrator = vessel.vesselModules[i] as FlightIntegrator; } } } CalculateTankArea(out totalTankArea); if (outerInsulationFactor > 0.0) { // TODO Deprecated! Leave in place for legacy purposes but this is moving back to part.skinInternalConductionMult based on calculated Lockheed MLI equations // changed from skin-internal to part.heatConductivity which affects only skin-internal // part.heatConductivity = Math.Min(part.heatConductivity, outerInsulationFactor); // affects how fast internal temperatures change during analytic mode // part.analyticInternalInsulationFactor *= outerInsulationFactor; } for (int i = tankList.Count - 1; i >= 0; --i) { FuelTank tank = tankList[i]; if (tank.maxAmount > 0.0 && (tank.vsp > 0.0 || tank.loss_rate > 0.0)) { supportsBoiloff = true; break; } } Fields[nameof(debug0Display)].guiActive = RFSettings.Instance.debugBoilOff && this.supportsBoiloff; Fields[nameof(debug1Display)].guiActive = RFSettings.Instance.debugBoilOff && this.supportsBoiloff; Fields[nameof(debug2Display)].guiActive = RFSettings.Instance.debugBoilOff && this.supportsBoiloff; //numberOfAddedMLILayers = Mathf.Round(numberOfAddedMLILayers); CalculateInsulation(); }
public void CalculateTankArea(out float totalTankArea) { totalTankArea = 0f; for (int i = 0; i < 6; ++i) { totalTankArea += part.DragCubes.WeightedArea[i]; } #if DEBUG Debug.Log("[RF] Part WeightedArea: " + part.name + " = " + totalTankArea.ToString("F2")); Debug.Log("[RF] Part Area: " + part.name + " = " + part.DragCubes.Area.ToString("F2")); #endif // This allows a rough guess as to individual tank surface area based on ratio of tank volume to total volume but it breaks down at very small fractions // So use greater of spherical calculation and tank ratio of total area. if (totalTankArea > 0.0) { double tankMaxAmount; for (int i = tankList.Count - 1; i >= 0; --i) { FuelTank tank = tankList[i]; if (tank.maxAmount > 0.0) { tankMaxAmount = tank.maxAmount; if (tank.utilization > 1.0) { tankMaxAmount /= utilization; } tank.tankRatio = tankMaxAmount / volume; tank.totalArea = Math.Max(Math.Pow(Math.PI, 1.0 / 3.0) * Math.Pow((tankMaxAmount / 1000.0) * 6, 2.0 / 3.0), tank.totalArea = totalTankArea * tank.tankRatio); if (RFSettings.Instance.debugBoilOff) { Debug.Log("[RF] " + tank.name + ".tankRatio = " + tank.tankRatio.ToString()); Debug.Log("[RF] " + tank.name + ".maxAmount = " + tankMaxAmount.ToString()); Debug.Log("[RF] " + part.name + ".totalTankArea = " + totalTankArea.ToString()); Debug.Log("[RF] Tank surface area = " + tank.totalArea.ToString()); } } } } }
internal FuelTank CreateCopy(ModuleFuelTanks toModule, ConfigNode overNode, bool initializeAmounts) { FuelTank clone = (FuelTank)MemberwiseClone(); clone.module = toModule; if (overNode != null) { clone.Load(overNode); } if (initializeAmounts) { clone.InitializeAmounts(); } else { clone.amountExpression = clone.maxAmountExpression = null; } return(clone); }
public override string GetInfo() { if (!compatible) { return(""); } UpdateTankType(); StringBuilder info = new StringBuilder(); info.AppendLine("Modular Fuel Tank:"); info.Append(" Max Volume: ").AppendLine(KSPUtil.PrintSI(volume, MFSSettings.unitLabel)); info.AppendLine(" Tank can hold:"); for (int i = 0; i < tankList.Count; i++) { FuelTank tank = tankList[i]; info.Append(" ").Append(tank).Append(" ").AppendLine(tank.note); } return(info.ToString()); }
void EditTank(FuelTank tank) { GUILayout.Label(" ", GUILayout.Width(5)); GUIStyle style = unchanged; if (tank.maxAmountExpression == null) { tank.maxAmountExpression = tank.maxAmount.ToString(); //Debug.LogWarning ("[MFT] Adding tank from API " + tank.name + " amount: " + tank.maxAmountExpression ?? "null"); } else if (tank.maxAmountExpression.Length > 0 && tank.maxAmountExpression != tank.maxAmount.ToString()) { style = changed; } tank.maxAmountExpression = GUILayout.TextField(tank.maxAmountExpression, style, GUILayout.Width(127)); UpdateTank(tank); RemoveTank(tank); }
private void CalculateTankLossFunction(double deltaTime) { for (int i = 0; i < tankList.Count; i++) { FuelTank tank = tankList[i]; if (tank.loss_rate > 0 && tank.amount > 0) { double deltaTemp = part.temperature - tank.temperature; if (deltaTemp > 0) { double loss = tank.maxAmount * tank.loss_rate * deltaTemp * deltaTime; // loss_rate is calibrated to 300 degrees. if (loss > tank.amount) { tank.amount = 0; } else { tank.amount -= loss; } } } } }
partial void OnStartRF(StartState state) { base.OnStart(state); if (HighLogic.LoadedSceneIsFlight) { for (int i = 0; i < vessel.vesselModules.Count; i++) { if (vessel.vesselModules[i] is FlightIntegrator) { _flightIntegrator = vessel.vesselModules[i] as FlightIntegrator; } } } CalculateTankArea(out tankArea); // changed from skin-internal to part.heatConductivity which affects part.heatConductivity = Math.Min(part.heatConductivity, outerInsulationFactor); // affects how fast internal temperatures change during analytic mode part.analyticInternalInsulationFactor *= outerInsulationFactor; for (int i = tankList.Count - 1; i >= 0; --i) { FuelTank tank = tankList[i]; if (tank.maxAmount > 0.0 && (tank.vsp > 0.0 || tank.loss_rate > 0.0)) { supportsBoiloff = true; break; } } Fields[nameof(debug0Display)].guiActive = RFSettings.Instance.debugBoilOff && this.supportsBoiloff; Fields[nameof(debug1Display)].guiActive = RFSettings.Instance.debugBoilOff && this.supportsBoiloff; Fields[nameof(debug2Display)].guiActive = RFSettings.Instance.debugBoilOff && this.supportsBoiloff; }
private void UpdateTankType(bool initializeAmounts = true) { if (oldType == type || type == null) { return; } // Copy the tank list from the tank definitiion TankDefinition def; if (!MFSSettings.tankDefinitions.Contains(type)) { Debug.LogError("Unable to find tank definition for type \"" + type + "\" reverting."); type = oldType; return; } def = MFSSettings.tankDefinitions[type]; if (!def.canHave) { type = oldType; if (oldType != null && oldType != "") // we have an old type { def = MFSSettings.tankDefinitions[type]; if (def.canHave) { return; // go back to old type } } // else find one that does work foreach (TankDefinition newDef in MFSSettings.tankDefinitions) { if (newDef.canHave) { def = newDef; type = newDef.name; break; } } if (type == oldType) // if we didn't find a new one { Debug.LogError("Unable to find a type that is tech-available for part " + part.name); return; } } oldType = type; // Build the new tank list. tankList = new FuelTankList(); for (int i = 0; i < def.tankList.Count; i++) { FuelTank tank = def.tankList[i]; // Pull the override from the list of overrides ConfigNode overNode = MFSSettings.GetOverrideList(part).FirstOrDefault(n => n.GetValue("name") == tank.name); tankList.Add(tank.CreateCopy(this, overNode, initializeAmounts)); } tankList.TechAmounts(); // update for current techs // Destroy any managed resources that are not in the new type. HashSet <string> managed = MFSSettings.managedResources[part.name]; // if this throws, we have some big fish to fry bool needsMesage = false; for (int i = part.Resources.Count - 1; i >= 0; --i) { PartResource partResource = part.Resources[i]; string resname = partResource.resourceName; if (!managed.Contains(resname) || tankList.Contains(resname)) { continue; } part.Resources.Remove(partResource.info.id); needsMesage = true; } if (needsMesage) { RaiseResourceListChanged(); } if (!basemassOverride) { ParseBaseMass(def.basemass); } if (!baseCostOverride) { ParseBaseCost(def.baseCost); } if (!isDatabaseLoad) { // being called in the SpaceCenter scene is assumed to be a database reload //FIXME is this really needed? massDirty = true; } UpdateTankTypeRF(def); UpdateTestFlight(); }
void UpdateTank(FuelTank tank) { if (GUILayout.Button ("Update", GUILayout.Width (53))) { string trimmed = tank.maxAmountExpression.Trim (); if (trimmed == "") { tank.maxAmount = 0; //Debug.LogWarning ("[MFT] Removing tank as empty input " + tank.name + " amount: " + tank.maxAmountExpression ?? "null"); } else { double tmp; if (MathUtils.TryParseExt (trimmed, out tmp)) { tank.maxAmount = tmp; if (tmp != 0) { tank.amount = tank.fillable ? tank.maxAmount : 0; // Need to round-trip the value tank.maxAmountExpression = tank.maxAmount.ToString (); //Debug.LogWarning ("[MFT] Updating maxAmount " + tank.name + " amount: " + tank.maxAmountExpression ?? "null"); } } } } }
void TankLine(FuelTank tank) { GUILayout.BeginHorizontal (); GUILayout.Label (" " + tank, GUILayout.Width (115)); // So our states here are: // Not being edited currently (empty): maxAmountExpression = null, maxAmount = 0 // Have updated the field, no user edit: maxAmountExpression == maxAmount.ToStringExt // Other non UI updated maxAmount: maxAmountExpression = null (set), maxAmount = non-zero // User has updated the field: maxAmountExpression != null, maxAmountExpression != maxAmount.ToStringExt if (tank_module.part.Resources.Contains (tank.name) && tank_module.part.Resources[tank.name].maxAmount > 0) { EditTank (tank); } else if (tank_module.AvailableVolume >= 0.001) { AddTank (tank); } else { NoRoom (); } GUILayout.EndHorizontal (); }
private IEnumerator CalculateTankLossFunction(double deltaTime, bool analyticalMode = false) { // Need to ensure that all heat compensation (radiators, heat pumps, etc) run first. if (!analyticalMode) { yield return(new WaitForFixedUpdate()); } boiloffMass = 0d; previewInternalFluxAdjust = 0; for (int i = tankList.Count - 1; i >= 0; --i) { FuelTank tank = tankList[i]; if (tank.amount > 0d && (tank.vsp > 0.0 || tank.loss_rate > 0d)) { lowestTankTemperature = Math.Min(lowestTankTemperature, tank.temperature); } } if (tankList.Count > 0 && lowestTankTemperature < 300d && MFSSettings.radiatorMinTempMult >= 0d) { part.radiatorMax = (lowestTankTemperature * MFSSettings.radiatorMinTempMult) / part.maxTemp; } if (vessel != null && vessel.situation == Vessel.Situations.PRELAUNCH) { part.temperature = lowestTankTemperature; part.skinTemperature = lowestTankTemperature; } else { partPrevTemperature = part.temperature; double deltaTimeRecip = 1d / deltaTime; //Debug.Log("internalFlux = " + part.thermalInternalFlux.ToString() + ", thermalInternalFluxPrevious =" + part.thermalInternalFluxPrevious.ToString() + ", analytic internal flux = " + previewInternalFluxAdjust.ToString()); double cooling = analyticalMode ? Math.Max(0, part.thermalInternalFluxPrevious) : 0; for (int i = tankList.Count - 1; i >= 0; --i) { FuelTank tank = tankList[i]; if (tank.amount > 0d) { if (tank.vsp > 0.0) { // Opposite of original boil off code. Finds massLost first. double massLost = 0.0; double deltaTemp; double hotTemp = part.temperature - (cooling * part.thermalMassReciprocal); double tankRatio = tank.maxAmount / volume; if (RFSettings.Instance.ferociousBoilOff) { hotTemp = Math.Max(((hotTemp * part.thermalMass) - (tank.temperature * part.resourceThermalMass)) / (part.thermalMass - part.resourceThermalMass), part.temperature); } deltaTemp = hotTemp - tank.temperature; if (RFSettings.Instance.debugBoilOff) { if (debug2Display != "") { debug2Display += " / "; } if (debug1Display != "") { debug1Display += " / "; } if (debug0Display != "") { debug0Display += " / "; } } if (RFSettings.Instance.debugBoilOff) { debug0Display += hotTemp.ToString("F6"); } if (deltaTemp > 0) { double wettedArea = tank.totalArea * (tank.amount / tank.maxAmount); double Q = deltaTemp / ((tank.wallThickness / (tank.wallConduction * wettedArea)) + (tank.insulationThickness / (tank.insulationConduction * wettedArea)) + (tank.resourceConductivity > 0 ? (0.01 / (tank.resourceConductivity * wettedArea)) : 0)); Q *= 0.001d; // convert to kilowatts massLost = Q / tank.vsp; if (RFSettings.Instance.debugBoilOff) { // Only do debugging displays if debugging enabled in RFSettings debug1Display += Utilities.FormatFlux(Q); debug2Display += (massLost * 1000 * 3600).ToString("F4") + "kg/hr"; } massLost *= deltaTime; // Frame scaling } double lossAmount = massLost / tank.density; if (double.IsNaN(lossAmount)) { print("[RF] " + tank.name + " lossAmount is NaN!"); } else { double heatLost = 0d; if (lossAmount > tank.amount) { tank.amount = 0d; } else { tank.amount -= lossAmount; heatLost = -massLost * tank.vsp; heatLost *= ConductionFactors; // See if there is boiloff byproduct and see if any other parts want to accept it. if (tank.boiloffProductResource != null) { double boiloffProductAmount = -(massLost / tank.boiloffProductResource.density); double retainedAmount = part.RequestResource(tank.boiloffProductResource.id, boiloffProductAmount, ResourceFlowMode.STAGE_PRIORITY_FLOW); massLost -= retainedAmount * tank.boiloffProductResource.density; } boiloffMass += massLost; } // subtract heat from boiloff // subtracting heat in analytic mode is tricky: Analytic flux handling is 'cheaty' and tricky to predict. if (!analyticalMode) { part.AddThermalFlux(heatLost * deltaTimeRecip * 2.0d); // double because there is a bug in FlightIntegrator that cuts internal flux in half } else { analyticInternalTemp = analyticInternalTemp + (heatLost * part.thermalMassReciprocal); previewInternalFluxAdjust -= heatLost * deltaTimeRecip * 2d; if (deltaTime > 0) { print(part.name + " deltaTime = " + deltaTime + ", heat lost = " + heatLost + ", thermalMassReciprocal = " + part.thermalMassReciprocal); } } } } else if (tank.loss_rate > 0 && tank.amount > 0) { double deltaTemp = part.temperature - tank.temperature; if (deltaTemp > 0) { double lossAmount = tank.maxAmount * tank.loss_rate * deltaTemp * deltaTime; if (lossAmount > tank.amount) { lossAmount = -tank.amount; tank.amount = 0d; } else { lossAmount = -lossAmount; tank.amount += lossAmount; } double massLost = tank.density * lossAmount; boiloffMass += massLost; } } } } } }
public void CalculateTankArea() { // TODO: Codify a more accurate tank area calculator. // Thought: cube YN/YP can be used to find the part diameter / circumference... X or Z finds the length // Also should try to determine if tank has a common bulkhead - and adjust heat flux into individual tanks accordingly #if DEBUG print("CalculateTankArea() running"); #endif if (HighLogic.LoadedSceneIsEditor) { if (!this.part.DragCubes.None && this.oldTotalVolume != this.totalVolume) { if (this.IsProcedural()) { bool origProceduralValue = this.part.DragCubes.Procedural; this.part.DragCubes.Procedural = true; this.part.DragCubes.ForceUpdate(true, true, true); this.part.DragCubes.SetDragWeights(); this.part.DragCubes.RequestOcclusionUpdate(); this.part.DragCubes.SetPartOcclusion(); this.part.DragCubes.Procedural = origProceduralValue; this.oldTotalVolume = this.totalVolume; } } } totalTankArea = 0f; for (int i = 0; i < 6; ++i) { totalTankArea += part.DragCubes.WeightedArea[i]; } #if DEBUG Debug.Log("[RealFuels.ModuleFuelTankRF] Part WeightedArea: " + part.name + " = " + totalTankArea.ToString("F2")); Debug.Log("[RealFuels.ModuleFuelTankRF] Part Area: " + part.name + " = " + part.DragCubes.Area.ToString("F2")); #endif // This allows a rough guess as to individual tank surface area based on ratio of tank volume to total volume but it breaks down at very small fractions // So use greater of spherical calculation and tank ratio of total area. // if for any reason our totalTankArea is still 0 (no drag cubes available yet or analytic temp routines executed first) // then we're going to be defaulting to spherical calculation double tankMaxAmount; double tempTotal = 0; if (RFSettings.Instance.debugBoilOff) { Debug.Log("[RealFuels.ModuleFuelTankRF] Initializing " + part.name + ".totalTankArea as " + totalTankArea.ToString()); } for (int i = tankList.Count - 1; i >= 0; --i) { FuelTank tank = tankList[i]; if (tank.maxAmount > 0.0) { tankMaxAmount = tank.maxAmount; if (tank.utilization > 1.0) { tankMaxAmount /= tank.utilization; } tank.tankRatio = tankMaxAmount / volume; tank.totalArea = Math.Max(Math.Pow(Math.PI, 1.0 / 3.0) * Math.Pow((tankMaxAmount / 1000.0) * 6, 2.0 / 3.0), totalTankArea * tank.tankRatio); tempTotal += tank.totalArea; if (RFSettings.Instance.debugBoilOff) { Debug.Log("[RealFuels.ModuleFuelTankRF] " + tank.name + ".tankRatio = " + tank.tankRatio.ToString()); Debug.Log("[RealFuels.ModuleFuelTankRF] " + tank.name + ".maxAmount = " + tankMaxAmount.ToString()); Debug.Log("[RealFuels.ModuleFuelTankRF] Tank surface area = " + tank.totalArea.ToString()); DebugLog("tank Dewar status = " + tank.isDewar.ToString()); } } } if (!(totalTankArea > 0) || tempTotal > totalTankArea) { totalTankArea = tempTotal; } if (RFSettings.Instance.debugBoilOff) { Debug.Log("[RealFuels.ModuleFuelTankRF] " + part.name + ".totalTankArea = " + totalTankArea.ToString()); Debug.Log("[RealFuels.ModuleFuelTankRF] " + part.name + ".GetModuleSize()" + part.GetModuleSize(Vector3.zero).ToString("F2")); } }
partial void OnStartRF(StartState state) { GameEvents.onVesselWasModified.Add(OnVesselWasModified); GameEvents.onEditorShipModified.Add(OnEditorShipModified); GameEvents.onPartDestroyed.Add(OnPartDestroyed); if (HighLogic.LoadedSceneIsFlight) { for (int i = 0; i < vessel.vesselModules.Count; i++) { if (vessel.vesselModules[i] is FlightIntegrator) { _flightIntegrator = vessel.vesselModules[i] as FlightIntegrator; } } } // Wait to calculate tank area because it depends on drag cubes // MLI depends on tank area so mass will also be recalculated IEnumerator WaitAndRecalculateMass() { yield return(null); yield return(null); yield return(null); CalculateTankArea(); massDirty = true; CalculateMass(); } if (HighLogic.LoadedSceneIsFlight) { StartCoroutine(WaitAndRecalculateMass()); } for (int i = tankList.Count - 1; i >= 0; --i) { FuelTank tank = tankList[i]; if (tank.maxAmount > 0.0 && (tank.vsp > 0.0 || tank.loss_rate > 0.0)) { supportsBoiloff = true; break; } } if (state == StartState.Editor) { if (maxMLILayers > 0) { ((UI_FloatRange)Fields[nameof(_numberOfAddedMLILayers)].uiControlEditor).maxValue = maxMLILayers; } else { Fields[nameof(_numberOfAddedMLILayers)].guiActiveEditor = false; } Fields[nameof(_numberOfAddedMLILayers)].uiControlEditor.onFieldChanged = delegate(BaseField field, object value) { massDirty = true; CalculateMass(); }; } Fields[nameof(debug0Display)].guiActive = this.supportsBoiloff && (RFSettings.Instance.debugBoilOff || RFSettings.Instance.debugBoilOffPAW); Fields[nameof(debug1Display)].guiActive = this.supportsBoiloff && (RFSettings.Instance.debugBoilOff || RFSettings.Instance.debugBoilOffPAW); Fields[nameof(debug2Display)].guiActive = this.supportsBoiloff && (RFSettings.Instance.debugBoilOff || RFSettings.Instance.debugBoilOffPAW); //numberOfAddedMLILayers = Mathf.Round(numberOfAddedMLILayers); //CalculateInsulation(); }
private void FillAttachedTanks(double deltaTime) { // sanity check if (deltaTime <= 0) { return; } // now, let's look at what we're connected to. foreach (Part p in vessel.parts) // look through all parts { Tanks.ModuleFuelTanks m = p.FindModuleImplementing <Tanks.ModuleFuelTanks>(); if (m != null) { m.fueledByLaunchClamp = true; // look through all tanks inside this part for (int j = m.tankList.Count - 1; j >= 0; --j) { Tanks.FuelTank tank = m.tankList[j]; // if a tank isn't full, start filling it. if (tank.maxAmount <= 0) { continue; } PartResource r = tank.resource; if (r == null) { continue; } PartResourceDefinition d = PartResourceLibrary.Instance.GetDefinition(r.resourceName); if (d == null) { continue; } if (tank.amount < tank.maxAmount && tank.fillable && r.flowMode != PartResource.FlowMode.None && d.resourceTransferMode == ResourceTransferMode.PUMP && r.flowState) { double amount = Math.Min(deltaTime * pump_rate * tank.utilization, tank.maxAmount - tank.amount); var game = HighLogic.CurrentGame; if (d.unitCost > 0 && game.Mode == Game.Modes.CAREER && Funding.Instance != null) { double funds = Funding.Instance.Funds; double cost = amount * d.unitCost; if (cost > funds) { amount = funds / d.unitCost; cost = funds; } Funding.Instance.AddFunds(-cost, TransactionReasons.VesselRollout); } //tank.amount = tank.amount + amount; p.TransferResource(r, amount, this.part); } } } else { for (int j = p.Resources.Count - 1; j >= 0; --j) { PartResource partResource = p.Resources[j]; if (partResource.info.name == "ElectricCharge") { if (partResource.flowMode != PartResource.FlowMode.None && partResource.info.resourceTransferMode == ResourceTransferMode.PUMP && partResource.flowState) { double amount = deltaTime * pump_rate; amount = Math.Min(amount, partResource.maxAmount - partResource.amount); p.TransferResource(partResource, amount, this.part); } } } } } }
private IEnumerator CalculateTankBoiloff(double deltaTime, bool analyticalMode = false) { // Need to ensure that all heat compensation (radiators, heat pumps, etc) run first. if (totalTankArea <= 0) { CalculateTankArea(out totalTankArea); } if (!analyticalMode) { yield return(new WaitForFixedUpdate()); } boiloffMass = 0d; previewInternalFluxAdjust = 0; bool hasCryoFuels = CalculateLowestTankTemperature(); if (tankList.Count > 0 && lowestTankTemperature < 300d && MFSSettings.radiatorMinTempMult >= 0d) { part.radiatorMax = (lowestTankTemperature * MFSSettings.radiatorMinTempMult) / part.maxTemp; } if (fueledByLaunchClamp) { if (hasCryoFuels) { part.temperature = lowestTankTemperature; part.skinTemperature = lowestTankTemperature; } fueledByLaunchClamp = false; yield break; } partPrevTemperature = part.temperature; double deltaTimeRecip = 1d / deltaTime; //Debug.Log("internalFlux = " + part.thermalInternalFlux.ToString() + ", thermalInternalFluxPrevious =" + part.thermalInternalFluxPrevious.ToString() + ", analytic internal flux = " + previewInternalFluxAdjust.ToString()); double cooling = analyticalMode ? Math.Min(0, part.thermalInternalFluxPrevious) : 0; if (RFSettings.Instance.debugBoilOff) { debug0Display = part.temperature.ToString("F4") + "(" + GetMLITransferRate(part.skinTemperature, part.temperature).ToString("F4") + " * " + (part.radiativeArea * part.skinExposedAreaFrac).ToString("F2") + "m2)"; } for (int i = tankList.Count - 1; i >= 0; --i) { FuelTank tank = tankList[i]; if (tank.amount <= 0) { continue; } if (tank.vsp > 0.0 && tank.totalArea > 0) { // Opposite of original boil off code. Finds massLost first. double massLost = 0.0; double deltaTemp; double hotTemp = part.temperature; double tankRatio = tank.maxAmount / volume; if (RFSettings.Instance.ferociousBoilOff) { hotTemp = Math.Max(((hotTemp * part.thermalMass) - (tank.temperature * part.resourceThermalMass)) / (part.thermalMass - part.resourceThermalMass), part.temperature); } deltaTemp = hotTemp - tank.temperature; if (RFSettings.Instance.debugBoilOff) { if (debug2Display != "") { debug2Display += " / "; } if (debug1Display != "") { debug1Display += " / "; } } if (deltaTemp > 0) { #if DEBUG if (analyticalMode) { print("Tank " + tank.name + " surface area = " + tank.totalArea); } #endif double wettedArea = tank.totalArea;// disabled until proper wetted vs ullage conduction can be done (tank.amount / tank.maxAmount); double Q = 0; if (tank.isDewar) { Q = GetDewarTransferRate(hotTemp, tank.temperature, tank.totalArea); } else { Q = deltaTemp / ((tank.wallThickness / (tank.wallConduction * wettedArea)) + (tank.insulationThickness / (tank.insulationConduction * wettedArea)) + (tank.resourceConductivity > 0 ? (0.01 / (tank.resourceConductivity * wettedArea)) : 0)); } Q *= 0.001d; // convert to kilowatts massLost = Q / tank.vsp; if (RFSettings.Instance.debugBoilOff) { // Only do debugging displays if debugging enabled in RFSettings debug1Display += Utilities.FormatFlux(Q); debug2Display += (massLost * 1000 * 3600).ToString("F4") + "kg/hr"; } massLost *= deltaTime; // Frame scaling } double lossAmount = massLost / tank.density; if (double.IsNaN(lossAmount)) { print("[RF] " + tank.name + " lossAmount is NaN!"); } else { double heatLost = 0d; if (lossAmount > tank.amount) { tank.amount = 0d; } else { tank.amount -= lossAmount; heatLost = -massLost * tank.vsp; // See if there is boiloff byproduct and see if any other parts want to accept it. if (tank.boiloffProductResource != null) { double boiloffProductAmount = -(massLost / tank.boiloffProductResource.density); double retainedAmount = part.RequestResource(tank.boiloffProductResource.id, boiloffProductAmount, ResourceFlowMode.STAGE_PRIORITY_FLOW); massLost -= retainedAmount * tank.boiloffProductResource.density; } boiloffMass += massLost; } // subtract heat from boiloff // subtracting heat in analytic mode is tricky: Analytic flux handling is 'cheaty' and tricky to predict. // scratch sheet: example // [RealFuels.ModuleFuelTankRF] proceduralTankRealFuels Analytic Temp = 256.679360297684, Analytic Internal = 256.679360297684, Analytic Skin = 256.679360297684 // [RealFuels.ModuleFuelTankRF] proceduralTankRealFuels deltaTime = 17306955.5092776, heat lost = 6638604.21227684, thermalMassReciprocal = 0.00444787360733243 if (!analyticalMode) { heatLost *= ConductionFactors; part.AddThermalFlux(heatLost * deltaTimeRecip); } else { analyticInternalTemp = analyticInternalTemp + (heatLost * part.thermalMassReciprocal); previewInternalFluxAdjust += heatLost * deltaTimeRecip; #if DEBUG if (deltaTime > 0) { print(part.name + " deltaTime = " + deltaTime + ", heat lost = " + heatLost + ", thermalMassReciprocal = " + part.thermalMassReciprocal); } #endif } } } else if (tank.loss_rate > 0 && tank.amount > 0) { double deltaTemp = part.temperature - tank.temperature; if (deltaTemp > 0) { double lossAmount = tank.maxAmount * tank.loss_rate * deltaTemp * deltaTime; if (lossAmount > tank.amount) { lossAmount = -tank.amount; tank.amount = 0d; } else { lossAmount = -lossAmount; tank.amount += lossAmount; } double massLost = tank.density * lossAmount; boiloffMass += massLost; } } } }
private IEnumerator CalculateTankBoiloff(double deltaTime, bool analyticalMode = false) { // Need to ensure that all heat compensation (radiators, heat pumps, etc) run first. if (totalTankArea <= 0) { CalculateTankArea(); } if (!analyticalMode) { yield return(new WaitForFixedUpdate()); } boiloffMass = 0d; previewInternalFluxAdjust = 0; bool hasCryoFuels = CalculateLowestTankTemperature(); if (tankList.Count > 0 && lowestTankTemperature < 300d && MFSSettings.radiatorMinTempMult >= 0d) { part.radiatorMax = (lowestTankTemperature * MFSSettings.radiatorMinTempMult) / part.maxTemp; } if (fueledByLaunchClamp) { if (hasCryoFuels) { part.temperature = lowestTankTemperature; part.skinTemperature = lowestTankTemperature; } fueledByLaunchClamp = false; yield break; } if (!double.IsNaN(part.temperature)) { partPrevTemperature = part.temperature; } else { part.temperature = partPrevTemperature; } if (deltaTime > 0) { double deltaTimeRecip = 1d / deltaTime; //Debug.Log("internalFlux = " + part.thermalInternalFlux.ToString() + ", thermalInternalFluxPrevious =" + part.thermalInternalFluxPrevious.ToString() + ", analytic internal flux = " + previewInternalFluxAdjust.ToString()); if (RFSettings.Instance.debugBoilOff || RFSettings.Instance.debugBoilOffPAW) { string MLIText = totalMLILayers > 0 ? GetMLITransferRate(part.skinTemperature, part.temperature).ToString("F4") : "No MLI"; debug0Display = part.temperature.ToString("F4") + "(" + MLIText + " * " + (part.radiativeArea * part.skinExposedAreaFrac).ToString("F2") + "m2)"; } double cooling = 0; if (analyticalMode) { if (part.thermalInternalFlux < 0) { cooling = part.thermalInternalFlux; } else if (part.thermalInternalFluxPrevious < 0) { cooling = part.thermalInternalFluxPrevious; } if (cooling < 0) { // in analytic mode, MFTRF interprets this as an attempt to cool the tanks analyticInternalTemp += cooling * part.thermalMassReciprocal * deltaTime; // because of what we're doing in CalculateAnalyticInsulationFactor(), it will take too much time to reach that temperature so part.temperature += cooling * part.thermalMassReciprocal * deltaTime; } } debug3Display = Utilities.FormatFlux(cooling); for (int i = tankList.Count - 1; i >= 0; --i) { FuelTank tank = tankList[i]; if (tank.amount <= 0) { continue; } if (tank.vsp > 0.0 && tank.totalArea > 0) { // Opposite of original boil off code. Finds massLost first. double massLost = 0.0; double deltaTemp; double hotTemp = part.temperature; double tankRatio = tank.maxAmount / volume; if (RFSettings.Instance.ferociousBoilOff) { hotTemp = Math.Max(((hotTemp * part.thermalMass) - (tank.temperature * part.resourceThermalMass)) / (part.thermalMass - part.resourceThermalMass), part.temperature); } deltaTemp = hotTemp - tank.temperature; if (RFSettings.Instance.debugBoilOff || RFSettings.Instance.debugBoilOffPAW) { if (debug2Display != "") { debug2Display += " / "; } if (debug1Display != "") { debug1Display += " / "; } } double Q = 0; if (deltaTemp > 0) { #if DEBUG if (analyticalMode) { print("Tank " + tank.name + " surface area = " + tank.totalArea); } #endif double wettedArea = tank.totalArea; // disabled until proper wetted vs ullage conduction can be done (tank.amount / tank.maxAmount); if (tank.isDewar) { Q = GetDewarTransferRate(hotTemp, tank.temperature, tank.totalArea); } else { Q = deltaTemp / ((tank.wallThickness / (tank.wallConduction * wettedArea)) + (tank.insulationThickness / (tank.insulationConduction * wettedArea)) + (tank.resourceConductivity > 0 ? (0.01 / (tank.resourceConductivity * wettedArea)) : 0)); } Q *= 0.001d; // convert to kilowatts if (!double.IsNaN(Q)) { massLost = Q / tank.vsp; } else { DebugLog("Q = NaN! W - T - F!!!"); } if (RFSettings.Instance.debugBoilOff || RFSettings.Instance.debugBoilOffPAW) { // Only do debugging displays if debugging enabled in RFSettings debug1Display += Utilities.FormatFlux(Q); debug2Display += (massLost * 1000 * 3600).ToString("F4") + "kg/hr"; } massLost *= deltaTime; // Frame scaling } double lossAmount = massLost / tank.density; if (double.IsNaN(lossAmount)) { print(tank.name + " lossAmount is NaN!"); } else { if (lossAmount > tank.amount) { if (!CheatOptions.InfinitePropellant) { tank.amount = 0d; } } else { if (!CheatOptions.InfinitePropellant) { tank.amount -= lossAmount; } // See if there is boiloff byproduct and see if any other parts want to accept it. if (tank.boiloffProductResource != null) { double boiloffProductAmount = -(massLost / tank.boiloffProductResource.density); double retainedAmount = part.RequestResource(tank.boiloffProductResource.id, boiloffProductAmount, ResourceFlowMode.STAGE_PRIORITY_FLOW); massLost -= retainedAmount * tank.boiloffProductResource.density; } boiloffMass += massLost; } // subtract heat from boiloff // subtracting heat in analytic mode is tricky: Analytic flux handling is 'cheaty' and tricky to predict. // scratch sheet: example // [RealFuels.ModuleFuelTankRF] proceduralTankRealFuels Analytic Temp = 256.679360297684, Analytic Internal = 256.679360297684, Analytic Skin = 256.679360297684 // [RealFuels.ModuleFuelTankRF] proceduralTankRealFuels deltaTime = 17306955.5092776, heat lost = 6638604.21227684, thermalMassReciprocal = 0.00444787360733243 if (!double.IsNaN(Q)) { double heatLost = -Q; if (!analyticalMode) { part.AddThermalFlux(heatLost); } else { analyticInternalTemp = analyticInternalTemp + (heatLost * part.thermalMassReciprocal * deltaTime); // Don't try to adjust flux if significant time has passed; it never works out. // Analytic mode flux only gets applied if timewarping AND analytic mode was set. if (TimeWarp.CurrentRate > 1) { previewInternalFluxAdjust += heatLost; } else { print("boiloff function running with delta time of " + deltaTime.ToString() + "(vessel.lastUT =" + (Planetarium.GetUniversalTime() - vessel.lastUT).ToString("F4") + " seconds ago)"); } #if DEBUG if (deltaTime > 0) { print(part.name + " deltaTime = " + deltaTime + ", heat lost = " + heatLost + ", thermalMassReciprocal = " + part.thermalMassReciprocal); } #endif } } else { DebugLog("WHO WOULD WIN? Some Well Written Code or One Misplaced NaN?"); DebugLog("heatLost = " + Q.ToString()); DebugLog("deltaTime = " + deltaTime.ToString()); DebugLog("deltaTimeRecip = " + deltaTimeRecip.ToString()); DebugLog("massLost = " + massLost.ToString()); DebugLog("tank.vsp = " + tank.vsp.ToString()); } } } else if (tank.loss_rate > 0 && tank.amount > 0) { double deltaTemp = part.temperature - tank.temperature; if (deltaTemp > 0) { double lossAmount = tank.maxAmount * tank.loss_rate * deltaTemp * deltaTime; if (lossAmount > tank.amount) { lossAmount = -tank.amount; tank.amount = 0d; } else { lossAmount = -lossAmount; tank.amount += lossAmount; } double massLost = tank.density * lossAmount; boiloffMass += massLost; } } } } }
private void UpdateTankType(bool initializeAmounts = true) { if (oldType == type || type == null) { return; } // Copy the tank list from the tank definitiion TankDefinition def; if (!MFSSettings.tankDefinitions.Contains(type)) { Debug.LogError("Unable to find tank definition for type \"" + type + "\" reverting."); type = oldType; return; } def = MFSSettings.tankDefinitions[type]; oldType = type; // Build the new tank list. tankList = new FuelTankList(); for (int i = 0; i < def.tankList.Count; i++) { FuelTank tank = def.tankList[i]; // Pull the override from the list of overrides ConfigNode overNode = overrideListNodes.FirstOrDefault(n => n.GetValue("name") == tank.name); tankList.Add(tank.CreateCopy(this, overNode, initializeAmounts)); } // Destroy any managed resources that are not in the new type. HashSet <string> managed = MFSSettings.managedResources[part.name]; // if this throws, we have some big fish to fry bool needsMesage = false; for (int i = part.Resources.Count - 1; i >= 0; --i) { PartResource partResource = part.Resources[i]; string resname = partResource.resourceName; if (!managed.Contains(resname) || tankList.Contains(resname)) { continue; } part.Resources.list.RemoveAt(i); DestroyImmediate(partResource); needsMesage = true; } if (needsMesage) { RaiseResourceListChanged(); } if (!basemassOverride) { ParseBaseMass(def.basemass); } if (!baseCostOverride) { ParseBaseCost(def.baseCost); } if (isDatabaseLoad) { // being called in the SpaceCenter scene is assumed to be a database reload //FIXME is this really needed? return; } UpdateEngineIgnitor(def); massDirty = true; }
void AddTank(FuelTank tank) { string extraData = "Max: " + (tank_module.AvailableVolume * tank.utilization).ToStringExt ("S3") + "L (+" + ModuleFuelTanks.FormatMass ((float) (tank_module.AvailableVolume * tank.mass)) + " )"; GUILayout.Label (extraData, GUILayout.Width (150)); if (GUILayout.Button ("Add", GUILayout.Width (120))) { tank.maxAmount = tank_module.AvailableVolume * tank.utilization; tank.amount = tank.fillable ? tank.maxAmount : 0; tank.maxAmountExpression = tank.maxAmount.ToString (); //Debug.LogWarning ("[MFT] Adding tank " + tank.name + " maxAmount: " + tank.maxAmountExpression ?? "null"); } }
void RemoveTank(FuelTank tank) { if (GUILayout.Button ("Remove", GUILayout.Width (58))) { tank.maxAmount = 0; GameEvents.onEditorShipModified.Fire (EditorLogic.fetch.ship); //Debug.LogWarning ("[MFT] Removing tank from button " + tank.name + " amount: " + tank.maxAmountExpression ?? "null"); } }
private void FillAttachedTanks(double deltaTime) { // sanity check if (deltaTime <= 0) { return; } // now, let's look at what we're connected to. for (int i = vessel.parts.Count - 1; i >= 0; --i) // look through all parts { Part p = vessel.parts[i]; if (p.Modules.Contains("ModuleFuelTanks")) { Tanks.ModuleFuelTanks m = (Tanks.ModuleFuelTanks)p.Modules["ModuleFuelTanks"]; double minTemp = p.temperature; // look through all tanks inside this part for (int j = m.tankList.Count - 1; j >= 0; --j) { Tanks.FuelTank tank = m.tankList[j]; // if a tank isn't full, start filling it. PartResource r = tank.resource; if (r == null) { continue; } PartResourceDefinition d = PartResourceLibrary.Instance.GetDefinition(r.resourceName); if (d == null) { continue; } if (tank.maxAmount > 0d) { if (tank.loss_rate > 0d) { minTemp = Math.Min(p.temperature, tank.temperature); } if (tank.amount < tank.maxAmount && tank.fillable && r.flowMode != PartResource.FlowMode.None && d.resourceTransferMode == ResourceTransferMode.PUMP && r.flowState) { double amount = Math.Min(deltaTime * pump_rate * tank.utilization, tank.maxAmount - tank.amount); var game = HighLogic.CurrentGame; if (d.unitCost > 0 && game.Mode == Game.Modes.CAREER && Funding.Instance != null) { double funds = Funding.Instance.Funds; double cost = amount * d.unitCost; if (cost > funds) { amount = funds / d.unitCost; cost = funds; } Funding.Instance.AddFunds(-cost, TransactionReasons.VesselRollout); } //tank.amount = tank.amount + amount; p.TransferResource(r, amount, this.part); } } } p.temperature = minTemp; } else { for (int j = p.Resources.Count - 1; j >= 0; --j) { PartResource r = p.Resources[j]; if (r.info.name == "ElectricCharge") { if (r.flowMode != PartResource.FlowMode.None && r.info.resourceTransferMode == ResourceTransferMode.PUMP && r.flowState) { double amount = deltaTime * pump_rate; amount = Math.Min(amount, r.maxAmount - r.amount); p.TransferResource(r, amount, this.part); } } } } } }
void EditTank(FuelTank tank) { GUILayout.Label (" ", GUILayout.Width (5)); GUIStyle style = unchanged; if (tank.maxAmountExpression == null) { tank.maxAmountExpression = tank.maxAmount.ToString (); //Debug.LogWarning ("[MFT] Adding tank from API " + tank.name + " amount: " + tank.maxAmountExpression ?? "null"); } else if (tank.maxAmountExpression.Length > 0 && tank.maxAmountExpression != tank.maxAmount.ToString ()) { style = changed; } tank.maxAmountExpression = GUILayout.TextField (tank.maxAmountExpression, style, GUILayout.Width (127)); UpdateTank (tank); RemoveTank (tank); }