From 55d39349b7456df1a85c87533272f790f5cf73ef Mon Sep 17 00:00:00 2001 From: Nicolas Loridon Date: Sun, 24 Aug 2025 20:07:13 +0200 Subject: [PATCH] Implement EC checks for science Transfers Save and use EC per MIT for science transfers - Store the calculated ecPerMit value in the ALH module on loaded vessels. - Retrieve ecPerMit during science transfer (loaded and unloaded) to accurately estimate required ElectricCharge. - Ensures consistent energy checks across loaded and unloaded vessels for efficient science transmission. - Consume EC on loaded vessels when transmissions occurs - Check unloaded vessels has enough EC for transmissions (cannot consume EC on unloaded) --- .../Localization/de_de.cfg | 1 + .../Localization/en_us.cfg | 1 + .../ALH_LabSettings.cs | 64 +++++++ source/AutomaticLabHousekeeper/ALH_Main.cs | 174 +++++++++++++++--- 4 files changed, 210 insertions(+), 30 deletions(-) diff --git a/GameData/AutomaticLabHousekeeper/Localization/de_de.cfg b/GameData/AutomaticLabHousekeeper/Localization/de_de.cfg index 3bb8158..4c59b09 100644 --- a/GameData/AutomaticLabHousekeeper/Localization/de_de.cfg +++ b/GameData/AutomaticLabHousekeeper/Localization/de_de.cfg @@ -7,6 +7,7 @@ Localization //Angezeigte Nachricht #autoLOC_ALH_0003 = F&E : <<1>> Wissenschaft erhalten von <<2>> + #autoLOC_ALH_0031 = F&E: nzureichende elektrische Energie (<<2>>/<<1>>) auf <<3>>. Wird übersprungen… //VAB/SPH Beschreibung #autoLOC_ALH_0004 = Hat die Fähigkeit automatisch Wissenschaft an F&E zu senden.\n diff --git a/GameData/AutomaticLabHousekeeper/Localization/en_us.cfg b/GameData/AutomaticLabHousekeeper/Localization/en_us.cfg index ec52451..70a1d66 100644 --- a/GameData/AutomaticLabHousekeeper/Localization/en_us.cfg +++ b/GameData/AutomaticLabHousekeeper/Localization/en_us.cfg @@ -7,6 +7,7 @@ Localization //Transmission ScreenMessage #autoLOC_ALH_0003 = R&D: Received <<1>> science from <<2>> + #autoLOC_ALH_0031 = R&D: Insufficient EC (<<2>>/<<1>>) on <<3>>. Skipping transfer.. //VAB/SPH Description #autoLOC_ALH_0004 = Has ability to automatically transfer processed science to R&D over time.\n diff --git a/source/AutomaticLabHousekeeper/ALH_LabSettings.cs b/source/AutomaticLabHousekeeper/ALH_LabSettings.cs index ea2d1ea..ddd060b 100644 --- a/source/AutomaticLabHousekeeper/ALH_LabSettings.cs +++ b/source/AutomaticLabHousekeeper/ALH_LabSettings.cs @@ -19,6 +19,9 @@ public class Module_AutomaticLabHousekeeper : PartModule [KSPField(isPersistant = true)] public string selectedExperimentStorageUnit = "None"; + [KSPField(isPersistant = true)] + public string ecPerMit = "-1.0"; //default to -1.0 to indicate uninitialized + private bool selectingStorage = false; private List validStorageParts = new List(); @@ -184,11 +187,72 @@ public override void OnStart(StartState state) base.OnStart(state); if (HighLogic.LoadedSceneIsFlight) { + CalculateAndStoreECPerMit(); Debug.Log($"[AutomaticLabHousekeeper] Lab settings loaded for {part.partInfo.title}"); UpdatePAW(); } } + public override void OnSave(ConfigNode node) + { + base.OnSave(node); + CalculateAndStoreECPerMit(); + } + + private void CalculateAndStoreECPerMit() + { + if (part?.vessel == null || !part.vessel.loaded) + return; + + float totalCombinableBandwidth = 0f; + float totalCombinableEC = 0f; + float lowestNonCombinableECPerMit = float.MaxValue; + bool foundAny = false; + + foreach (Part p in part.vessel.Parts) + { + foreach (PartModule module in p.Modules) + { + if (module.moduleName != "ModuleDataTransmitter") + continue; + + float packetSize = -1f, packetResourceCost = 1f; // Default values, should be overridden by actual values + bool antennaCombinable = false; + + if (float.TryParse(module.Fields.GetValue("packetSize").ToString() ?? "-1f", out packetSize)) + if (float.TryParse(module.Fields.GetValue("packetResourceCost").ToString() ?? "1f", out packetResourceCost)) + if (bool.TryParse(module.Fields.GetValue("antennaCombinable").ToString() ?? "false", out antennaCombinable)) + + if (packetSize <= 0f) continue; + + float ecPerMitValue = packetResourceCost / packetSize; + foundAny = true; + + if (antennaCombinable) + { + totalCombinableBandwidth += packetSize; + totalCombinableEC += packetResourceCost; + } + else + { + if (ecPerMitValue < lowestNonCombinableECPerMit) + lowestNonCombinableECPerMit = ecPerMitValue; + } + } + } + + float combinableECPerMit = totalCombinableBandwidth > 0f ? totalCombinableEC / totalCombinableBandwidth : float.MaxValue; + float best = Mathf.Min(combinableECPerMit, lowestNonCombinableECPerMit); + + // Store in the module's moduleValues safely + ecPerMit = (foundAny && best < float.MaxValue ? best : 1f).ToString("F4"); + + if (!foundAny) + { + Debug.Log($"[AutomaticLabHousekeeper] No ModuleDataTransmitter found on vessel {part.vessel.vesselName}"); + } + } + public void UpdatePAW() { Events["ToggleLabAutomation"].guiName = transmissionAutomationEnabled diff --git a/source/AutomaticLabHousekeeper/ALH_Main.cs b/source/AutomaticLabHousekeeper/ALH_Main.cs index 347bf59..48a7d6f 100644 --- a/source/AutomaticLabHousekeeper/ALH_Main.cs +++ b/source/AutomaticLabHousekeeper/ALH_Main.cs @@ -172,7 +172,7 @@ void ProcessScienceForAllVessels() if (transmissionEnabled) { - TransferScienceFromLab(vessel, part, lab); + TransferScienceFromLab(vessel, part, lab, alhModule); } if (dataPullingEnabled) @@ -197,7 +197,7 @@ void ProcessScienceForAllVessels() if (transmissionEnabled) { SimulateScienceProcessingForUnloadedLab(vessel, protoPart, labModule, converterModule); - TransferScienceFromUnloadedLab(vessel, protoPart, labModule); + TransferScienceFromUnloadedLab(vessel, protoPart, labModule, alhModule); } if (dataPullingEnabled) @@ -236,49 +236,90 @@ bool HasConnectionToKSC(Vessel vessel) return true; } - void TransferScienceFromLab(Vessel vessel, Part part, ModuleScienceLab lab) + void TransferScienceFromLab(Vessel vessel, Part part, ModuleScienceLab lab, PartModule alhModule) { Debug.Log($"[AutomaticLabHousekeeper] Processing science transfer for lab {part.partName} in LOADED vessel {vessel.vesselName}"); - - if (lab.storedScience >= 1) + float ecPerMit = GetECPerMit(alhModule); + if (ecPerMit < 0) { - float wholeScience = Mathf.Floor(lab.storedScience); - float remainingScience = lab.storedScience - wholeScience; - - Debug.Log($"[AutomaticLabHousekeeper] Transferring {wholeScience} science from {vessel.vesselName} to R&D, keeping {remainingScience} science in the lab"); - - ResearchAndDevelopment.Instance.AddScience(wholeScience, TransactionReasons.ScienceTransmission); - lab.storedScience = remainingScience; - - ScreenMessages.PostScreenMessage(Localizer.Format("#autoLOC_ALH_0003", wholeScience, vessel.vesselName), 10f, ScreenMessageStyle.UPPER_CENTER); + Debug.LogWarning($"[AutomaticLabHousekeeper] Invalid ecPerMit value ({ecPerMit}) in ALH module on {part.partName}. Skipping science transfer."); } else { - Debug.Log("[AutomaticLabHousekeeper] Not enough storedScience"); + if (lab.storedScience >= 1) + { + float wholeScience = Mathf.Floor(lab.storedScience); + float remainingScience = lab.storedScience - wholeScience; + float requiredEC = wholeScience * GetMitPerScience() * ecPerMit; + double ecAvailable = getAvailableEC(vessel); + + if (ecAvailable < requiredEC) + { + Debug.Log($"[AutomaticLabHousekeeper] Not enough EC ({ecAvailable:F1} / {requiredEC:F1}) for science transfer on {vessel.vesselName}"); + ScreenMessages.PostScreenMessage(Localizer.Format("#autoLOC_ALH_0031", requiredEC, ecAvailable, vessel.vesselName), 10f, ScreenMessageStyle.UPPER_CENTER); + } + else + { + DebugLog($"[AutomaticLabHousekeeper] Sufficient EC ({ecAvailable:F1} / {requiredEC:F1}) for science transfer on {vessel.vesselName}"); + // Consume EC + double ecTaken = vessel.rootPart.RequestResource("ElectricCharge", (double)requiredEC); + DebugLog($"[AutomaticLabHousekeeper] Consumed {ecTaken:F2} EC for transmitting {wholeScience} science from {vessel.vesselName}"); + DebugLog($"[AutomaticLabHousekeeper] Transferring {wholeScience} science from {vessel.vesselName} to R&D, keeping {remainingScience} science in the lab"); + // Add science to R&D and update lab storage + ResearchAndDevelopment.Instance.AddScience(wholeScience, TransactionReasons.ScienceTransmission); + lab.storedScience = remainingScience; + + ScreenMessages.PostScreenMessage(Localizer.Format("#autoLOC_ALH_0003", wholeScience, vessel.vesselName), 10f, ScreenMessageStyle.UPPER_CENTER); + } + + + } + else + { + Debug.Log("[AutomaticLabHousekeeper] Not enough storedScience"); + } } } - void TransferScienceFromUnloadedLab(Vessel vessel, ProtoPartSnapshot protoPart, ProtoPartModuleSnapshot labModule) + void TransferScienceFromUnloadedLab(Vessel vessel, ProtoPartSnapshot protoPart, ProtoPartModuleSnapshot labModule, ProtoPartModuleSnapshot alhModule) { Debug.Log($"[AutomaticLabHousekeeper] Processing science transfer for lab {protoPart.partName} in UNLOADED vessel {vessel.vesselName}"); - - float storedScience = float.Parse(labModule.moduleValues.GetValue("storedScience")); - - if (storedScience >= 1) + float ecPerMit = GetECPerMit(alhModule); + if (ecPerMit < 0) { - float wholeScience = Mathf.Floor(storedScience); - float remainingScience = storedScience - wholeScience; - - Debug.Log($"[AutomaticLabHousekeeper] Transferring {wholeScience} science from {vessel.vesselName} to R&D, keeping {remainingScience} science in the lab"); - - ResearchAndDevelopment.Instance.AddScience(wholeScience, TransactionReasons.ScienceTransmission); - labModule.moduleValues.SetValue("storedScience", remainingScience.ToString("F2")); - - ScreenMessages.PostScreenMessage(Localizer.Format("#autoLOC_ALH_0003", wholeScience, vessel.vesselName), 10f, ScreenMessageStyle.UPPER_CENTER); + Debug.LogWarning($"[AutomaticLabHousekeeper] Invalid ecPerMit value ({ecPerMit}) in ALH module on {protoPart.partName}. Skipping science transfer."); } else { - Debug.Log("[AutomaticLabHousekeeper] Not enough storedScience"); + float storedScience = float.Parse(labModule.moduleValues.GetValue("storedScience")); + if (storedScience >= 1) + { + float wholeScience = Mathf.Floor(storedScience); + float remainingScience = storedScience - wholeScience; + float requiredEC = wholeScience * GetMitPerScience() * ecPerMit; + double ecAvailable = getAvailableEC(vessel); + + if (ecAvailable < requiredEC) + { + Debug.Log($"[AutomaticLabHousekeeper] Not enough EC ({ecAvailable:F1} / {requiredEC:F1}) for science transfer on {vessel.vesselName} (UNLOADED)"); + ScreenMessages.PostScreenMessage(Localizer.Format("#autoLOC_ALH_0031", requiredEC, ecAvailable, vessel.vesselName), 10f, ScreenMessageStyle.UPPER_CENTER); + } + else + { + DebugLog($"[AutomaticLabHousekeeper] Sufficient EC ({ecAvailable:F1} / {requiredEC:F1}) for science transfer on {vessel.vesselName} (UNLOADED)"); + DebugLog($"[AutomaticLabHousekeeper] Transferring {wholeScience} science from {vessel.vesselName} to R&D, keeping {remainingScience} science in the lab"); + // For unloaded, we cannot request EC, so we just check + // Add science to R&D and update lab storage + ResearchAndDevelopment.Instance.AddScience(wholeScience, TransactionReasons.ScienceTransmission); + labModule.moduleValues.SetValue("storedScience", remainingScience.ToString("F2")); + + ScreenMessages.PostScreenMessage(Localizer.Format("#autoLOC_ALH_0003", wholeScience, vessel.vesselName), 10f, ScreenMessageStyle.UPPER_CENTER); + } + } + else + { + Debug.Log("[AutomaticLabHousekeeper] Not enough storedScience"); + } } } @@ -610,5 +651,78 @@ void DebugLog(string message) Debug.Log($"{message}"); } } + + // Helper method to get mitPerScience (inverse of science per MB) + private float GetMitPerScience() + { + return 1f; // Vanilla KSP uses 1 Mit per Science by default + //TODO: Add support for mods that change this ratio + } + + private double getAvailableEC(Vessel vessel) + { + if (vessel == null) + { + Debug.LogError("[AutomaticLabHousekeeper] getAvailableEC called with null vessel."); + return -1; + } + + double ecAvailable = 0; + if (vessel.loaded) + { + foreach (Part p in vessel.Parts) + { + foreach (PartResource r in p.Resources) + { + if (r.resourceName == "ElectricCharge") + ecAvailable += r.amount; + } + + } + } + else + { + foreach (var PartSnap in vessel.protoVessel.protoPartSnapshots) + { + foreach (var res in PartSnap.resources) + { + if (res.resourceName == "ElectricCharge") + ecAvailable += double.Parse(res.amount.ToString("F2")); + } + } + } + + return ecAvailable; + } + + private float GetECPerMit(object alhModule) + { + float ecPerMit = -1f; // fallback default + string value = null; + switch (alhModule) + { + case Module_AutomaticLabHousekeeper loadedModule: + value = loadedModule.ecPerMit; + break; + case ProtoPartModuleSnapshot protoModule when protoModule.moduleValues.HasValue("ecPerMit"): + value = protoModule.moduleValues.GetValue("ecPerMit"); + break; + default: + Debug.LogWarning($"[AutomaticLabHousekeeper] GetECPerMit called with unknown alhModule type. Using fallback EC per Mit = {ecPerMit}"); + return ecPerMit; + + } + + if (!string.IsNullOrEmpty(value) && float.TryParse(value, out float ParsedEcPerMit)) + return ParsedEcPerMit; + else + { + DebugLog($"[AutomaticLabHousekeeper] Failed to parse ecPerMit ('{value}'), using fallback -1"); + return ecPerMit; + } + } } } + + +