diff --git a/GameData/CryoTanks/Plugins/SimpleBoiloff.dll b/GameData/CryoTanks/Plugins/SimpleBoiloff.dll index 003ef03..1f80cbb 100644 Binary files a/GameData/CryoTanks/Plugins/SimpleBoiloff.dll and b/GameData/CryoTanks/Plugins/SimpleBoiloff.dll differ diff --git a/GameData/CryoTanks/Versioning/CryoTanks.version b/GameData/CryoTanks/Versioning/CryoTanks.version index 7610da9..93ab4a7 100644 --- a/GameData/CryoTanks/Versioning/CryoTanks.version +++ b/GameData/CryoTanks/Versioning/CryoTanks.version @@ -6,14 +6,14 @@ { "MAJOR":1, "MINOR":6, - "PATCH":1, + "PATCH":2, "BUILD":0 }, "KSP_VERSION": { "MAJOR":1, "MINOR":12, - "PATCH":1 + "PATCH":2 }, "KSP_VERSION_MIN":{ "MAJOR":1, diff --git a/Source/.vs/SimpleBoiloff/v16/.suo b/Source/.vs/SimpleBoiloff/v16/.suo new file mode 100644 index 0000000..eed0d59 Binary files /dev/null and b/Source/.vs/SimpleBoiloff/v16/.suo differ diff --git a/Source/BoiloffFuel.cs b/Source/BoiloffFuel.cs new file mode 100644 index 0000000..fbd41bd --- /dev/null +++ b/Source/BoiloffFuel.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; + +namespace SimpleBoiloff +{ + /// <summary> + /// Represents a fuel resource on a part that boils off + /// </summary> + [System.Serializable] + public class BoiloffFuel + { + /// <summary> + /// Fuel name + /// </summary> + public string fuelName; + + /// <summary> + /// Boiloff rate in %/hr + /// </summary> + public float boiloffRate; + + /// <summary> + /// Cooling cost in EC/1000u + /// </summary> + public float coolingCost; + + public PartResource resource; + public List<ResourceRatio> outputs; + public float boiloffRateSeconds = 0f; + + public bool fuelPresent = false; + int id = -1; + Part part; + + /// <summary> + /// Constructor + /// </summary> + /// <param name="node"></param> + /// <param name="p"></param> + public BoiloffFuel(ConfigNode node, Part p) + { + part = p; + node.TryGetValue("FuelName", ref fuelName); + node.TryGetValue("BoiloffRate", ref boiloffRate); + node.TryGetValue("CoolingCost", ref coolingCost); + + + ConfigNode[] outNodes = node.GetNodes("OUTPUT_RESOURCE"); + + if (outNodes.Length > 0 || outputs == null) + outputs = new List<ResourceRatio>(); + { + for (int i = 0; i < outNodes.Length; i++) + { + ResourceRatio r = new ResourceRatio(); + r.Load(outNodes[i]); + outputs.Add(r); + } + } + } + + /// <summary> + /// Initialize the fuel + /// </summary> + public void Initialize() + { + id = PartResourceLibrary.Instance.GetDefinition(fuelName).id; + resource = part.Resources.Get(id); + boiloffRateSeconds = boiloffRate / 100f / 3600f; + fuelPresent = true; + } + + /// <summary> + /// Returns the max amount of the fuel on the part + /// </summary> + /// <returns></returns> + public double FuelAmountMax() + { + if (fuelPresent) + return resource.maxAmount; + return 0d; + } + + /// <summary> + /// Returns the amount of the fuel on the part + /// </summary> + /// <returns></returns> + public double FuelAmount() + { + if (fuelPresent) + return resource.amount; + return 0d; + } + + /// <summary> + /// Returns the cost to cool the fuel + /// </summary> + /// <returns></returns> + public float FuelCoolingCost() + { + if (fuelPresent) + return coolingCost; + return 0f; + } + + /// <summary> + /// Returns the cost to cool fuel not considering current contents + /// </summary> + /// <returns></returns> + public float FuelConfiguredCoolingCost() + { + return coolingCost; + } + + /// <summary> + /// Appplies boiloff, given a time interval and a scale + /// </summary> + /// <param name="seconds"></param> + /// <param name="scale"></param> + public void Boiloff(double seconds, double scale) + { + if (fuelPresent) + { + double toBoil = resource.amount * (1.0 - Math.Pow(1.0 - boiloffRateSeconds, seconds)) * scale; + + double boilResult = part.RequestResource(resource.info.id, toBoil, ResourceFlowMode.NO_FLOW); + + /// Generate outputs + if (outputs.Count > 0) + { + for (int i = 0; i < outputs.Count; i++) + { + part.RequestResource(outputs[i].ResourceName, -boilResult * outputs[i].Ratio, outputs[i].FlowMode); + } + } + } + } + } + +} diff --git a/Source/ModuleSimpleBoiloff.cs b/Source/ModuleSimpleBoiloff.cs index 9154a6e..a3965fd 100644 --- a/Source/ModuleSimpleBoiloff.cs +++ b/Source/ModuleSimpleBoiloff.cs @@ -7,14 +7,14 @@ namespace SimpleBoiloff { - public class ModuleCryoTank: PartModule + public class ModuleCryoTank : PartModule { - // Cost to cool off u/s per 1000 u + // Module ID [KSPField(isPersistant = false)] public string moduleID = "CryoModule"; - // Cost to cool off u/s per 1000 u + /// Cost to cool off u/s per 1000 u [KSPField(isPersistant = false)] public float CoolingCost = 0.0f; @@ -22,7 +22,7 @@ public class ModuleCryoTank: PartModule [KSPField(isPersistant = false)] public double currentCoolingCost = 0.0; - // Minimum EC to leave when boiling off + // Minimum EC to leave on the vessel when consuming power [KSPField(isPersistant = false)] public float minResToLeave = 1.0f; @@ -30,15 +30,15 @@ public class ModuleCryoTank: PartModule [KSPField(isPersistant = true)] public double LastUpdateTime = 0; - // Whether active tank refrigeration is allowed + // Whether active tank refrigeration is allowed on the part [KSPField(isPersistant = true)] public bool CoolingAllowed = true; - // Whether active tank refrigeration is occurring + // Whether active tank refrigeration is occurring on the part [KSPField(isPersistant = true)] public bool CoolingEnabled = true; - // Whether the tank is cooled or not + // Whether the tank is CURRENTLY cooled or not [KSPField(isPersistant = true)] public bool BoiloffOccuring = false; @@ -71,118 +71,40 @@ public class ModuleCryoTank: PartModule [KSPField(isPersistant = false)] public double MaximumBoiloffScale = 5f; - public bool HasResource { get { return HasResource; } } + /// <summary> + /// Indicates whether there are any boilable resources currently on the part + /// </summary> + public bool HasAnyBoiloffResource { get; private set; } = false; // PRIVATE private double finalCoolingCost = 0.0; private List<BoiloffFuel> fuels; - private bool hasResource = false; + private double fuelAmount = 0.0; private double maxFuelAmount = 0.0; private double boiloffRateSeconds = 0.0; + + // Thermal private double solarFlux = 1.0; private double planetFlux = 1.0; private double fluxScale = 1.0; - // Represents a fuel that boils off - [System.Serializable] - public class BoiloffFuel - { - // Name of the fuel to boil - public string fuelName; - // Rate at which it boils - public float boiloffRate; - public float coolingCost; - - public PartResource resource; - public List<ResourceRatio> outputs; - public float boiloffRateSeconds = 0f; - - public bool fuelPresent = false; - int id = -1; - Part part; - - public BoiloffFuel(ConfigNode node, Part p) - { - part = p; - node.TryGetValue("FuelName", ref fuelName); - node.TryGetValue("BoiloffRate", ref boiloffRate); - node.TryGetValue("CoolingCost", ref coolingCost); - - outputs = new List<ResourceRatio>(); - ConfigNode[] outNodes = node.GetNodes("OUTPUT_RESOURCE"); - for (int i = 0; i < outNodes.Length; i++) - { - ResourceRatio r = new ResourceRatio(); - r.Load(outNodes[i]); - outputs.Add(r); - } - } - - public void Initialize() - { - //if (id == -1) - id = PartResourceLibrary.Instance.GetDefinition(fuelName).id; - resource = part.Resources.Get(id); - boiloffRateSeconds = boiloffRate/100f/3600f; - fuelPresent = true; - } - - public double FuelAmountMax() - { - if (fuelPresent) - return resource.maxAmount; - return 0d; - } - - public double FuelAmount() - { - if (fuelPresent) - return resource.amount; - return 0d; - } - - public float FuelCoolingCost() - { - - if (fuelPresent) - return coolingCost; - return 0f; - } - - public void Boiloff(double seconds, double scale) - { - if (fuelPresent) - { - double toBoil = resource.amount * (1.0 - Math.Pow(1.0 - boiloffRateSeconds, seconds)) * scale; - resource.amount = Math.Max(resource.amount - toBoil, 0d); - - if (outputs.Count > 0) - { - for (int i = 0; i < outputs.Count; i++) - { - part.RequestResource(outputs[i].ResourceName, -toBoil*outputs[i].Ratio, outputs[i].FlowMode); - } - } - } - } - } // UI FIELDS/ BUTTONS // Status string - [KSPField(isPersistant = false, guiActive = true, guiName = "Boiloff")] + [KSPField(isPersistant = false, guiActive = true, guiName = "#LOC_CryoTanks_ModuleCryoTank_Field_BoiloffStatus")] public string BoiloffStatus = "N/A"; - [KSPField(isPersistant = false, guiActive = false, guiName = "Insulation")] + [KSPField(isPersistant = false, guiActive = false, guiName = "#LOC_CryoTanks_ModuleCryoTank_Field_CoolingStatus")] public string CoolingStatus = "N/A"; - [KSPEvent(guiActive = false, guiName = "Enable Cooling", active = true)] + [KSPEvent(guiActive = false, guiName = "#LOC_CryoTanks_ModuleCryoTank_Event_Enable", active = true)] public void Enable() { CoolingEnabled = true; } - [KSPEvent(guiActive = false, guiName = "Disable Cooling", active = false)] + [KSPEvent(guiActive = false, guiName = "#LOC_CryoTanks_ModuleCryoTank_Event_Disable", active = false)] public void Disable() { CoolingEnabled = false; @@ -209,13 +131,13 @@ public void Disable() public string D_NetRad = "N/A"; // ACTIONS - [KSPAction("Enable Cooling")] + [KSPAction(guiName = "#LOC_CryoTanks_ModuleCryoTank_Action_EnableAction")] public void EnableAction(KSPActionParam param) { Enable(); } - [KSPAction("Disable Cooling")] + [KSPAction(guiName = "#LOC_CryoTanks_ModuleCryoTank_Action_DisableAction")] public void DisableAction(KSPActionParam param) { Disable(); } - [KSPAction("Toggle Cooling")] + [KSPAction(guiName = "#LOC_CryoTanks_ModuleCryoTank_Action_ToggleAction")] public void ToggleAction(KSPActionParam param) { CoolingEnabled = !CoolingEnabled; @@ -226,20 +148,20 @@ public override string GetInfo() { string msg; string fuelDisplayName; - if (IsCoolable()) + if (IsConfiguredAsCoolable()) { string sub = ""; float baseCooling = CoolingCost; - foreach(BoiloffFuel fuel in fuels) + foreach (BoiloffFuel fuel in fuels) { fuelDisplayName = PartResourceLibrary.Instance.GetDefinition(fuel.fuelName).displayName; - sub += Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_PartInfoBoiloff", fuelDisplayName, (fuel.boiloffRate).ToString("F2"), (baseCooling+fuel.coolingCost).ToString("F2")); + sub += Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_PartInfoBoiloff", fuelDisplayName, (fuel.boiloffRate).ToString("F2"), (baseCooling + fuel.coolingCost).ToString("F2")); if (fuel.outputs.Count > 0) { foreach (ResourceRatio output in fuel.outputs) { string outputDisplayName = PartResourceLibrary.Instance.GetDefinition(output.ResourceName).displayName; - sub += Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_PartInfoBoiloffOutput", outputDisplayName, (fuel.boiloffRate*output.Ratio).ToString("F2")); + sub += Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_PartInfoBoiloffOutput", outputDisplayName, (fuel.boiloffRate * output.Ratio).ToString("F2")); } } } @@ -248,10 +170,10 @@ public override string GetInfo() else { msg = Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_PartInfoUncooled"); - foreach(BoiloffFuel fuel in fuels) + foreach (BoiloffFuel fuel in fuels) { fuelDisplayName = PartResourceLibrary.Instance.GetDefinition(fuel.fuelName).displayName; - msg += Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_PartInfoBoiloff", fuelDisplayName, (fuel.boiloffRate).ToString("F2")); + msg += Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_PartInfoBoiloff", fuelDisplayName, (fuel.boiloffRate).ToString("F2")); if (fuel.outputs.Count > 0) { foreach (ResourceRatio output in fuel.outputs) @@ -266,39 +188,23 @@ public override string GetInfo() } - public override void OnStart(StartState state) + public void Start() { - Fields["BoiloffStatus"].guiName = Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_Field_BoiloffStatus"); - Fields["CoolingStatus"].guiName = Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_Field_CoolingStatus"); - - Events["Enable"].guiName = Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_Event_Enable"); - Events["Disable"].guiName = Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_Event_Disable"); - - Actions["EnableAction"].guiName = Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_Action_EnableAction"); - Actions["DisableAction"].guiName = Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_Action_DisableAction"); - Actions["ToggleAction"].guiName = Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_Action_ToggleAction"); - if (HighLogic.LoadedSceneIsFlight || HighLogic.LoadedSceneIsEditor) { ReloadDatabaseNodes(); - if (DebugMode) - { - Fields["D_NetRad"].guiActive = true; - Fields["D_InSolar"].guiActive = true; - Fields["D_InPlanet"].guiActive = true; - Fields["D_Albedo"].guiActive = true; - Fields["D_Emiss"].guiActive = true; - } + SetDebugMode(DebugMode); } if (HighLogic.LoadedSceneIsFlight) { - hasResource = false; - foreach(BoiloffFuel fuel in fuels) + /// Check to see if there's any boiloff resources on this part + HasAnyBoiloffResource = false; + foreach (BoiloffFuel fuel in fuels) { - if (isResourcePresent(fuel.fuelName)) + if (BoiloffUtils.IsPartResourcePresent(fuel.fuelName, part)) { - hasResource = true; + HasAnyBoiloffResource = true; fuel.Initialize(); } else @@ -306,20 +212,23 @@ public override void OnStart(StartState state) fuel.fuelPresent = false; } } - if (!hasResource) + /// if no resources turn off the UI + if (!HasAnyBoiloffResource) { Events["Disable"].guiActive = false; Events["Enable"].guiActive = false; Fields["BoiloffStatus"].guiActive = false; return; } + maxFuelAmount = GetTotalMaxResouceAmount(); + planetFlux = LongwaveFluxBaseline; solarFlux = ShortwaveFluxBaseline; if (GetTotalCoolingCost() > 0.0) { - finalCoolingCost = maxFuelAmount/1000.0 * GetTotalCoolingCost(); + finalCoolingCost = maxFuelAmount / 1000.0 * GetTotalCoolingCost(); Events["Disable"].guiActive = true; Events["Enable"].guiActive = true; Events["Enable"].guiActiveEditor = true; @@ -330,11 +239,25 @@ public override void OnStart(StartState state) } } + /// <summary> + /// Sets the UI debug fields to active + /// </summary> + /// <param name="debug"></param> + void SetDebugMode(bool debug) + { + + Fields["D_NetRad"].guiActive = debug; + Fields["D_InSolar"].guiActive = debug; + Fields["D_InPlanet"].guiActive = debug; + Fields["D_Albedo"].guiActive = debug; + Fields["D_Emiss"].guiActive = debug; + + } void ReloadDatabaseNodes() { if (fuels == null || fuels.Count == 0) { - Debug.Log(String.Format("[ModuleCryoTank]: Reloading ConfigNodes for {0}", part.partInfo.name)); + Utils.Log(String.Format("Reloading ConfigNodes for {0}", part.partInfo.name)); ConfigNode cfg; foreach (UrlDir.UrlConfig pNode in GameDatabase.Instance.GetConfigs("PART")) @@ -348,16 +271,19 @@ void ReloadDatabaseNodes() { ConfigNode node = cryoNodes.Single(n => n.GetValue("moduleID") == moduleID); OnLoad(node); - } catch (InvalidOperationException) + } + catch (InvalidOperationException) { // Thrown if predicate is not fulfilled, ie moduleName is not unqiue - Debug.Log(String.Format("[ModuleCryoTank]: Critical configuration error: Multiple ModuleCryoTank nodes found with identical or no moduleName")); - } catch (ArgumentNullException) + Utils.Log(String.Format("Critical configuration error: Multiple ModuleCryoTank nodes found with identical or no moduleName")); + } + catch (ArgumentNullException) { // Thrown if ModuleCryoTank is not found (a Large Problem (tm)) - Debug.Log(String.Format("[ModuleCryoTank]: Critical configuration error: No ModuleCryoTank nodes found in part")); + Utils.Log(String.Format("Critical configuration error: No ModuleCryoTank nodes found in part")); } - } else + } + else { OnLoad(cryoNodes[0]); } @@ -366,40 +292,58 @@ void ReloadDatabaseNodes() } } + /// <summary> + /// Loads data from config node + /// </summary> + /// <param name="node"></param> public override void OnLoad(ConfigNode node) { base.OnLoad(node); - + ConfigNode[] varNodes = node.GetNodes("BOILOFFCONFIG"); - fuels = new List<BoiloffFuel>(); - for (int i=0; i < varNodes.Length; i++) + + if (fuels == null) + fuels = new List<BoiloffFuel>(); + if (varNodes.Length > 0 ) { - fuels.Add(new BoiloffFuel(varNodes[i], this.part)); + + for (int i = 0; i < varNodes.Length; i++) + { + fuels.Add(new BoiloffFuel(varNodes[i], this.part)); + } } + Utils.Log($"Now have {fuels.Count} fuels"); } + /// <summary> + /// Execute a boiloff catchup operation, which looks at elapsed time to see how long we've been away and subtracts + /// resources appropriately + /// </summary> public void DoCatchup() { if (part.vessel.missionTime > 0.0) { - double currentEC = 0d; - double maxAmount = 0d; - vessel.GetConnectedResourceTotals(PartResourceLibrary.Instance.GetDefinition("ElectricCharge").id, out currentEC, out maxAmount); - // no consumption here anymore, since we know, that there won't be enough EC - if(!CoolingEnabled || (CoolingEnabled && (currentEC - minResToLeave) < (finalCoolingCost * TimeWarp.fixedDeltaTime))) + if (BoiloffOccuring) { double elapsedTime = Planetarium.GetUniversalTime() - LastUpdateTime; - for (int i = 0; i < fuels.Count ; i++) - fuels[i].Boiloff(elapsedTime, 1.0); + if (elapsedTime > 0d) + { + Utils.Log($"Catching up {elapsedTime} s of time on load"); + + for (int i = 0; i < fuels.Count; i++) + { + fuels[i].Boiloff(elapsedTime, 1.0); + } + } } } } public void Update() { - if (HighLogic.LoadedSceneIsFlight && hasResource) + if (HighLogic.LoadedSceneIsFlight && HasAnyBoiloffResource) { - // Show the insulation status field if there is a cooling cost + /// Show the insulation status field if there is a cooling cost if (IsCoolable()) { Fields["CoolingStatus"].guiActive = true; @@ -416,6 +360,7 @@ public void Update() Events["Enable"].active = false; } + /// if there is no more fuel, hide the boiloff status if (fuelAmount == 0.0) { Fields["BoiloffStatus"].guiActive = false; @@ -423,12 +368,13 @@ public void Update() } else if (HighLogic.LoadedSceneIsEditor) { - hasResource = false; - foreach(BoiloffFuel fuel in fuels) + /// Check for the presence of any resource + HasAnyBoiloffResource = false; + foreach (BoiloffFuel fuel in fuels) { - if (isResourcePresent(fuel.fuelName)) + if (BoiloffUtils.IsPartResourcePresent(fuel.fuelName, part)) { - hasResource = true; + HasAnyBoiloffResource = true; fuel.Initialize(); } else @@ -437,14 +383,14 @@ public void Update() } } - if (IsCoolable() && hasResource) + if (IsCoolable() && HasAnyBoiloffResource) { Fields["CoolingStatus"].guiActive = true; Fields["CoolingStatus"].guiActiveEditor = true; double max = GetTotalMaxResouceAmount(); - CoolingStatus = Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_Field_CoolingStatus_Editor", (GetTotalCoolingCost() * (float)(max / 1000.0)).ToString("F2")); + CoolingStatus = Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_Field_CoolingStatus_Editor", (GetTotalCoolingCost() * (float)(max / 1000.0)).ToString("F2")); Events["Disable"].guiActiveEditor = true; Events["Enable"].guiActiveEditor = true; @@ -466,7 +412,7 @@ public void Update() } protected void FixedUpdate() { - if (HighLogic.LoadedSceneIsFlight && hasResource) + if (HighLogic.LoadedSceneIsFlight && HasAnyBoiloffResource) { fluxScale = CalculateRadiativeEffects(); @@ -481,11 +427,11 @@ protected void FixedUpdate() return; } - // If the cooling cost is zero, we must boil off + // If the tank is not coolable we must boil off if (!IsCoolable()) { BoiloffOccuring = true; - BoiloffStatus = FormatRate(GetTotalBoiloffRate() * fuelAmount * fluxScale); + BoiloffStatus = BoiloffUtils.FormatRate(GetTotalBoiloffRate() * fuelAmount * fluxScale); currentCoolingCost = 0.0; } // else check for available power @@ -494,24 +440,24 @@ protected void FixedUpdate() if (!CoolingEnabled) { BoiloffOccuring = true; - BoiloffStatus = FormatRate(GetTotalBoiloffRate() * fuelAmount * fluxScale); + BoiloffStatus = BoiloffUtils.FormatRate(GetTotalBoiloffRate() * fuelAmount * fluxScale); CoolingStatus = Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_Field_CoolingStatus_Disabled"); currentCoolingCost = 0.0; } else { - ConsumeCharge(); + BoiloffOccuring = ConsumeCharge(); currentCoolingCost = finalCoolingCost; } } if (BoiloffOccuring) { - DoBoiloff(); + DoBoiloff(); } if (part.vessel.missionTime > 0.0) { - + } } if (HighLogic.LoadedSceneIsFlight && DebugMode) @@ -519,12 +465,12 @@ protected void FixedUpdate() D_Albedo = String.Format("{0:F4}", Albedo); D_Emiss = String.Format("{0:F4}", part.emissiveConstant); D_InPlanet = String.Format("{0:F4}", planetFlux); - D_InSolar = String.Format("{0:F4}", solarFlux*Albedo); + D_InSolar = String.Format("{0:F4}", solarFlux * Albedo); D_NetRad = String.Format("{0:F4}", fluxScale); } - if (HighLogic.LoadedSceneIsEditor && hasResource) + if (HighLogic.LoadedSceneIsEditor && HasAnyBoiloffResource) { - currentCoolingCost = GetTotalCoolingCost()*GetTotalMaxResouceAmount()/1000d; + currentCoolingCost = GetTotalCoolingCost() * GetTotalMaxResouceAmount() / 1000d; } } @@ -542,6 +488,7 @@ protected double CalculateRadiativeEffects() if (ShortwaveFluxAffectsBoiloff) solarFlux = part.ptd.sunFlux / part.emissiveConstant; } + if (ShortwaveFluxAffectsBoiloff || LongwaveFluxAffectsBoiloff) { fluxScale = Math.Max((planetFlux / LongwaveFluxBaseline + (solarFlux * Albedo) / ShortwaveFluxBaseline) / 2.0f, MinimumBoiloffScale); @@ -558,16 +505,17 @@ protected double CalculateRadiativeEffects() /// </summary> /// <param name="state">Whether boiloff is occuring or not</param> /// <return>The total cost of the boiloff</return> - public double SetBoiloffState(bool state) + public double SetBoiloffState(bool isBoiling) { if (CoolingEnabled && IsCoolable()) { - if (state) + if (isBoiling) { BoiloffOccuring = true; - BoiloffStatus = FormatRate(boiloffRateSeconds * fuelAmount); + BoiloffStatus = BoiloffUtils.FormatRate(GetTotalBoiloffRate() * fuelAmount* fluxScale); CoolingStatus = Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_Field_CoolingStatus_Uncooled"); - } else + } + else { BoiloffOccuring = false; @@ -585,82 +533,54 @@ public override void OnSave(ConfigNode node) base.OnSave(node); } - public void ConsumeCharge() + /// <summary> + /// Consumes electric charge and return whether boiloff happens + /// </summary> + /// <returns></returns> + public bool ConsumeCharge() { + bool boiloff = false; if (CoolingEnabled && IsCoolable()) { double chargeRequest = finalCoolingCost * TimeWarp.fixedDeltaTime; - double currentEC = 0d; - double maxEC = 0d; - vessel.GetConnectedResourceTotals(PartResourceLibrary.Instance.GetDefinition("ElectricCharge").id, out currentEC, out maxEC); + vessel.GetConnectedResourceTotals(PartResourceLibrary.Instance.GetDefinition("ElectricCharge").id, out double currentEC, out double maxEC); // only use EC if there is more then minResToLeave left - double req = 0; - if (currentEC > chargeRequest + minResToLeave) + double consumedEC = 0; + if (currentEC > (chargeRequest + minResToLeave)) { - req = part.RequestResource("ElectricCharge", chargeRequest); + consumedEC = part.RequestResource("ElectricCharge", chargeRequest); } double tolerance = 0.0001; - if (req >= chargeRequest - tolerance) + if (consumedEC >= chargeRequest - tolerance) { - BoiloffOccuring = false; + boiloff = false; BoiloffStatus = Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_Field_BoiloffStatus_Insulated"); CoolingStatus = Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_Field_CoolingStatus_Cooling", finalCoolingCost.ToString("F2")); } else { - BoiloffOccuring = true; - BoiloffStatus = FormatRate(boiloffRateSeconds * fuelAmount); + boiloff = true; + BoiloffStatus = BoiloffUtils.FormatRate(GetTotalBoiloffRate() * fuelAmount * fluxScale); CoolingStatus = Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_Field_CoolingStatus_Uncooled"); } } + return boiloff; } + /// <summary> + /// Iteractes through the fuels and applies boiloff + /// </summary> protected void DoBoiloff() { - // Iterate through fuels and do boiloff - for (int i = 0; i < fuels.Count ; i++) + for (int i = 0; i < fuels.Count; i++) fuels[i].Boiloff(TimeWarp.fixedDeltaTime, fluxScale); } - /// <summary> - /// Produces a friendly string describing boiloff rate - /// </summary> - /// <param name="rate">The rate in seconds to format</param> - /// <return>String describing rate</return> - protected string FormatRate(double rate) - { - double adjRate = rate; - string interval = Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_TimeInterval_Second_Abbrev"); - if (adjRate < 0.01) - { - adjRate = adjRate*60.0; - interval = Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_TimeInterval_Minute_Abbrev"); - } - if (adjRate < 0.01) - { - adjRate = adjRate * 60.0; - interval = Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_TimeInterval_Hour_Abbrev"); - } - return Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_Field_BoiloffStatus_Boiloff", adjRate.ToString("F2"), interval.ToString()); - } - - /// <summary> - /// Determines if a specified resource is present on the part - /// </summary> - /// <param name="string">The name of the resource</param> - /// <return>True if the resouce is present</return> - public bool isResourcePresent(string nm) - { - int id = PartResourceLibrary.Instance.GetDefinition(nm).id; - PartResource res = this.part.Resources.Get(id); - if (res == null) - return false; - return true; - } + /// <summary> /// Get the total fuel amount of the tank (sum of all boilable fuels) @@ -693,7 +613,7 @@ protected double GetTotalMaxResouceAmount() protected double GetTotalBoiloffRate() { double max = 0d; - for (int i = 0; i < fuels.Count ; i++) + for (int i = 0; i < fuels.Count; i++) max += fuels[i].boiloffRateSeconds; return max; } @@ -705,7 +625,7 @@ protected double GetTotalBoiloffRate() protected float GetTotalCoolingCost() { float total = CoolingCost; - for (int i = 0; i < fuels.Count ; i++) + for (int i = 0; i < fuels.Count; i++) total += fuels[i].FuelCoolingCost(); return total; } @@ -719,36 +639,35 @@ protected bool IsCoolable() if (!CoolingAllowed) return false; - for (int i = 0; i < fuels.Count ; i++) + for (int i = 0; i < fuels.Count; i++) + { if (fuels[i].FuelCoolingCost() > 0.0f) return true; - + } if (CoolingCost > 0.0f) return true; + return false; } /// <summary> - /// Gets the current amount of a resource by name + /// Determines if the tank is currently coolable (ie, can use power to stop boiloff) /// </summary> - /// <param name="string">The name of the resource</param> - /// <return>The amount</return> - protected double GetResourceAmount(string nm) + /// <return>True if the tank can be cooled</return> + protected bool IsConfiguredAsCoolable() { - PartResource res = this.part.Resources.Get(PartResourceLibrary.Instance.GetDefinition(nm).id); - return res.amount; - } + if (!CoolingAllowed) + return false; - /// <summary> - /// Gets the maximum amount of a resource by name - /// </summary> - /// <param name="string">The name of the resource</param> - /// <return>The maximum amount</return> - protected double GetMaxResourceAmount(string nm) - { - PartResource res = this.part.Resources.Get(PartResourceLibrary.Instance.GetDefinition(nm).id); - return res.maxAmount; - } + for (int i = 0; i < fuels.Count; i++) + { + if (fuels[i].FuelConfiguredCoolingCost() > 0.0f) + return true; + } + if (CoolingCost > 0.0f) + return true; + return false; + } } } diff --git a/Source/SimpleBoiloff.csproj b/Source/SimpleBoiloff.csproj index 1b8f25a..53a46bb 100644 --- a/Source/SimpleBoiloff.csproj +++ b/Source/SimpleBoiloff.csproj @@ -37,7 +37,7 @@ </PropertyGroup> <ItemGroup> <Reference Include="Assembly-CSharp"> - <HintPath>..\..\..\..\..\..\Games\Steam\steamapps\common\Kerbal Space Program\KSP_x64_Data\Managed\Assembly-CSharp.dll</HintPath> + <HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Kerbal Space Program\KSP_x64_Data\Managed\Assembly-CSharp.dll</HintPath> </Reference> <Reference Include="Assembly-CSharp-firstpass"> <HintPath>..\..\..\..\..\..\Games\Steam\steamapps\common\Kerbal Space Program\KSP_x64_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath> @@ -51,16 +51,21 @@ <Reference Include="UnityEngine"> <HintPath>..\..\..\..\..\..\Games\Steam\steamapps\common\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.dll</HintPath> </Reference> + <Reference Include="UnityEngine.AnimationModule"> + <HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.AnimationModule.dll</HintPath> + </Reference> <Reference Include="UnityEngine.CoreModule"> - <HintPath>..\..\..\..\..\..\Games\Steam\steamapps\common\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.CoreModule.dll</HintPath> + <HintPath>..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.CoreModule.dll</HintPath> </Reference> <Reference Include="UnityEngine.UI"> <HintPath>..\..\..\..\..\..\Games\Steam\steamapps\common\Kerbal Space Program\KSP_x64_Data\Managed\UnityEngine.UI.dll</HintPath> </Reference> </ItemGroup> <ItemGroup> + <Compile Include="BoiloffFuel.cs" /> <Compile Include="ModuleSimpleBoiloff.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="Utils.cs" /> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. diff --git a/Source/Utils.cs b/Source/Utils.cs new file mode 100644 index 0000000..0df334a --- /dev/null +++ b/Source/Utils.cs @@ -0,0 +1,108 @@ +using System; +using UnityEngine; +using KSP.Localization; + +namespace SimpleBoiloff +{ + public enum LogType + { + UI, + Settings, + Modules, + Any + } + public static class Utils + { + public static string logTag = "CryoTanks"; + + + /// <summary> + /// Log a message with the mod name tag prefixed + /// </summary> + /// <param name="str">message string </param> + public static void Log(string str, LogType logType) + { + bool doLog = false; + if (logType == LogType.Any) doLog = true; + + if (doLog) + Debug.Log(String.Format("[{0}]{1}", logTag, str)); + } + + public static void Log(string str) + { + Debug.Log(String.Format("[{0}]{1}", logTag, str)); + } + + public static void LogWarning(string toLog) + { + Debug.LogWarning(String.Format("[{0}]{1}", logTag, toLog)); + } + public static void LogError(string toLog) + { + Debug.LogError(String.Format("[{0}]{1}", logTag, toLog)); + } + } + + public static class BoiloffUtils + { + /// <summary> + /// Determines if a resource is present on a part + /// </summary> + /// <param name="resourceName"></param> + /// <param name="p"></param> + /// <returns></returns> + public static bool IsPartResourcePresent(string resourceName, Part p) + { + int id = PartResourceLibrary.Instance.GetDefinition(resourceName).id; + PartResource res = p.Resources.Get(id); + if (res == null) + return false; + return true; + } + + /// <summary> + /// Gets the current amount of a resource by name on a part + /// </summary> + /// <param name="string">The name of the resource</param> + /// <return>The amount</return> + public static double GetResourceAmount(string nm, Part p) + { + PartResource res = p.Resources.Get(PartResourceLibrary.Instance.GetDefinition(nm).id); + return res.amount; + } + + /// <summary> + /// Gets the maximum amount of a resource by name on a part + /// </summary> + /// <param name="string">The name of the resource</param> + /// <return>The maximum amount</return> + public static double GetMaxResourceAmount(string nm, Part p) + { + PartResource res = p.Resources.Get(PartResourceLibrary.Instance.GetDefinition(nm).id); + return res.maxAmount; + } + + /// <summary> + /// Produces a friendly string describing boiloff rate + /// </summary> + /// <param name="rate">The rate in seconds to format</param> + /// <return>String describing rate</return> + public static string FormatRate(double rate) + { + double adjRate = rate; + string interval = Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_TimeInterval_Second_Abbrev"); + if (adjRate < 0.01) + { + adjRate = adjRate * 60.0; + interval = Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_TimeInterval_Minute_Abbrev"); + } + if (adjRate < 0.01) + { + adjRate = adjRate * 60.0; + interval = Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_TimeInterval_Hour_Abbrev"); + } + return Localizer.Format("#LOC_CryoTanks_ModuleCryoTank_Field_BoiloffStatus_Boiloff", adjRate.ToString("F2"), interval.ToString()); + } + } +} diff --git a/changelog.txt b/changelog.txt index 65b5140..c193814 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,9 @@ +v1.6.2 +------ +- Refactored boiloff code +- Catchup calculation is more robust +- Fixed inconsistency when displaying boiloff rate (was displayed using different units when power was out vs when cooling was disabled) + v1.6.1 ------ - Updated DynamicBatteryStorage to 2.2.4 diff --git a/readme.txt b/readme.txt index 580d497..7a95488 100644 --- a/readme.txt +++ b/readme.txt @@ -1,5 +1,5 @@ ================= -Cryo Tanks v1.6.1 +Cryo Tanks v1.6.2 ================= A mod pack for Kerbal Space Program, specifically supporting my other mods Kerbal Atomics (https://github.com/ChrisAdderley/KerbalAtomics) and Cryogenic Engines (https://github.com/ChrisAdderley/CryoEngines), dealing with cryogenic fuels, their storage and their properties.