From f10c43068eb172ec6266b9da8bafa81a50519663 Mon Sep 17 00:00:00 2001 From: BREAN Maxime Date: Sat, 24 Mar 2018 14:08:26 +0100 Subject: [PATCH 01/15] Add agent title for 'Umbra Space Industries' --- FOR_RELEASE/GameData/000_USITools/Agency/USIAgency.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/FOR_RELEASE/GameData/000_USITools/Agency/USIAgency.cfg b/FOR_RELEASE/GameData/000_USITools/Agency/USIAgency.cfg index ec6401a..63010d2 100644 --- a/FOR_RELEASE/GameData/000_USITools/Agency/USIAgency.cfg +++ b/FOR_RELEASE/GameData/000_USITools/Agency/USIAgency.cfg @@ -1,6 +1,7 @@ AGENT { name = Umbra Space Industries + title = Umbra Space Industries description = A world leader in corrugated paper products, Umbra Space Industries is now applying their formidable boxing and packaging skills into a a series of products for colonization, exploration, and resource exploitation! From 2445afa43eb01ef2d8f05339bc93f2cf91791bc7 Mon Sep 17 00:00:00 2001 From: doktorkrogg Date: Sat, 29 Sep 2018 09:45:16 -0400 Subject: [PATCH 02/15] Multi-bay swappable converters Extend ModuleSwapControllerNew, ModuleSwapOption and ModuleResourceConverter_USI to support ModuleEfficiencyPart from MKS --- USITools/USITools/ModuleResourceConverter_USI.cs | 6 ++++++ USITools/USITools/ModuleSwapControllerNew.cs | 3 +++ USITools/USITools/ModuleSwapOption.cs | 9 +++++++++ 3 files changed, 18 insertions(+) diff --git a/USITools/USITools/ModuleResourceConverter_USI.cs b/USITools/USITools/ModuleResourceConverter_USI.cs index fe5782f..b251132 100644 --- a/USITools/USITools/ModuleResourceConverter_USI.cs +++ b/USITools/USITools/ModuleResourceConverter_USI.cs @@ -11,6 +11,12 @@ public class ModuleResourceConverter_USI : ModuleResourceConverter, IEfficiencyB [KSPField] public bool UseBonus = true; + [KSPField] + public double eMultiplier = 1d; + + [KSPField] + public string eTag = ""; + public Dictionary BonusList { get diff --git a/USITools/USITools/ModuleSwapControllerNew.cs b/USITools/USITools/ModuleSwapControllerNew.cs index b6096bc..bad885c 100644 --- a/USITools/USITools/ModuleSwapControllerNew.cs +++ b/USITools/USITools/ModuleSwapControllerNew.cs @@ -82,6 +82,9 @@ private void ApplyConverterChanges(ModuleResourceConverter_USI converter, Module converter.UseSpecialistBonus = loadout.UseSpecialistBonus; if (converter.UseSpecialistBonus) converter.ExperienceEffect = loadout.ExperienceEffect; + converter.UseBonus = loadout.UseBonus; + converter.eMultiplier = loadout.eMultiplier; + converter.eTag = loadout.eTag; converter.inputList.Clear(); converter.outputList.Clear(); diff --git a/USITools/USITools/ModuleSwapOption.cs b/USITools/USITools/ModuleSwapOption.cs index bf14a11..ae9aa5c 100644 --- a/USITools/USITools/ModuleSwapOption.cs +++ b/USITools/USITools/ModuleSwapOption.cs @@ -26,6 +26,15 @@ public class ModuleSwapOption : PartModule [KSPField] public string ExperienceEffect = ""; + [KSPField] + public bool UseBonus = true; + + [KSPField] + public double eMultiplier = 1d; + + [KSPField] + public string eTag = ""; + public List inputList; public List outputList; public List reqList; From a027e622a38945b10f35c5be20c2e171fc8ef1ba Mon Sep 17 00:00:00 2001 From: doktorkrogg Date: Sat, 29 Sep 2018 22:08:24 -0400 Subject: [PATCH 03/15] Refactor swappable converters Added additional abstraction to remove the necessity for the swap controller to know what kind of converter(s) it's working with; swap options are now responsible for changing the loadout of a converter --- .../AbstractSwapOption.cs} | 38 ++- .../{ => Converters}/ConverterPart.cs | 4 +- .../Interfaces}/IEfficiencyBonusConsumer.cs | 7 +- .../Interfaces/ISwappableConverter.cs | 7 + .../USITools/{ => Converters}/LoadoutInfo.cs | 3 + .../Converters/ModuleConverterSwapOption.cs | 50 ++++ .../ModuleEfficiencyPartSwapOption.cs | 19 ++ .../Converters/ModuleHarvesterSwapOption.cs | 19 ++ .../ModuleResourceConverter_USI.cs | 93 +++--- .../Converters/ModuleResourceHarvester_USI.cs | 95 +++++++ .../Converters/ModuleSwapController.cs | 68 +++++ .../ModuleSwapControllerNew.cs | 6 +- .../USITools/Converters/ModuleSwappableBay.cs | 266 ++++++++++++++++++ .../ModuleSwappableConverter.cs | 1 + .../ModuleSwappableConverterNew.cs | 1 + .../USITools/ModuleResourceHarvester_USI.cs | 79 ------ USITools/USITools/USITools.csproj | 24 +- 17 files changed, 626 insertions(+), 154 deletions(-) rename USITools/USITools/{ModuleSwapOption.cs => Converters/AbstractSwapOption.cs} (70%) rename USITools/USITools/{ => Converters}/ConverterPart.cs (90%) rename USITools/USITools/{ => Converters/Interfaces}/IEfficiencyBonusConsumer.cs (77%) create mode 100644 USITools/USITools/Converters/Interfaces/ISwappableConverter.cs rename USITools/USITools/{ => Converters}/LoadoutInfo.cs (74%) create mode 100644 USITools/USITools/Converters/ModuleConverterSwapOption.cs create mode 100644 USITools/USITools/Converters/ModuleEfficiencyPartSwapOption.cs create mode 100644 USITools/USITools/Converters/ModuleHarvesterSwapOption.cs rename USITools/USITools/{ => Converters}/ModuleResourceConverter_USI.cs (55%) create mode 100644 USITools/USITools/Converters/ModuleResourceHarvester_USI.cs create mode 100644 USITools/USITools/Converters/ModuleSwapController.cs rename USITools/USITools/{ => Converters}/ModuleSwapControllerNew.cs (95%) create mode 100644 USITools/USITools/Converters/ModuleSwappableBay.cs rename USITools/USITools/{ => Converters}/ModuleSwappableConverter.cs (99%) rename USITools/USITools/{ => Converters}/ModuleSwappableConverterNew.cs (99%) delete mode 100644 USITools/USITools/ModuleResourceHarvester_USI.cs diff --git a/USITools/USITools/ModuleSwapOption.cs b/USITools/USITools/Converters/AbstractSwapOption.cs similarity index 70% rename from USITools/USITools/ModuleSwapOption.cs rename to USITools/USITools/Converters/AbstractSwapOption.cs index ae9aa5c..7ecae0b 100644 --- a/USITools/USITools/ModuleSwapOption.cs +++ b/USITools/USITools/Converters/AbstractSwapOption.cs @@ -1,9 +1,23 @@ -using System.Collections.Generic; +using System.Collections.Generic; using UnityEngine; namespace USITools { - public class ModuleSwapOption : PartModule + public abstract class AbstractSwapOption : AbstractSwapOption + where T : BaseConverter + { + public virtual void ApplyConverterChanges(T converter) + { + converter.ConverterName = ConverterName; + converter.StartActionName = StartActionName; + converter.UseSpecialistBonus = UseSpecialistBonus; + converter.ExperienceEffect = ExperienceEffect; + + MonoUtilities.RefreshContextWindows(part); + } + } + + public abstract class AbstractSwapOption : PartModule { [KSPField] public float Efficiency = 1; @@ -29,15 +43,14 @@ public class ModuleSwapOption : PartModule [KSPField] public bool UseBonus = true; - [KSPField] - public double eMultiplier = 1d; - - [KSPField] - public string eTag = ""; + public List inputList { get; set; } + public List outputList { get; set; } + public List reqList { get; set; } - public List inputList; - public List outputList; - public List reqList; + public virtual ConversionRecipe PrepareRecipe(ConversionRecipe recipe) + { + return recipe; + } public override void OnLoad(ConfigNode node) { @@ -76,9 +89,6 @@ public override void OnLoad(ConfigNode node) break; } } - } } - - -} \ No newline at end of file +} diff --git a/USITools/USITools/ConverterPart.cs b/USITools/USITools/Converters/ConverterPart.cs similarity index 90% rename from USITools/USITools/ConverterPart.cs rename to USITools/USITools/Converters/ConverterPart.cs index da11f32..013ed1a 100644 --- a/USITools/USITools/ConverterPart.cs +++ b/USITools/USITools/Converters/ConverterPart.cs @@ -1,7 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace USITools { + [Obsolete] public class ConverterPart { public Part HostPart { get; set; } diff --git a/USITools/USITools/IEfficiencyBonusConsumer.cs b/USITools/USITools/Converters/Interfaces/IEfficiencyBonusConsumer.cs similarity index 77% rename from USITools/USITools/IEfficiencyBonusConsumer.cs rename to USITools/USITools/Converters/Interfaces/IEfficiencyBonusConsumer.cs index fdabb83..d1f7ed2 100644 --- a/USITools/USITools/IEfficiencyBonusConsumer.cs +++ b/USITools/USITools/Converters/Interfaces/IEfficiencyBonusConsumer.cs @@ -5,12 +5,9 @@ public interface IEfficiencyBonusConsumer float GetEfficiencyBonus(); void SetEfficiencyBonus(string bonName, float bonValue); - bool useEfficiencyBonus - { - get; - } + bool useEfficiencyBonus { get; } void EnableConsumer(); void DisableConsumer(); } -} \ No newline at end of file +} diff --git a/USITools/USITools/Converters/Interfaces/ISwappableConverter.cs b/USITools/USITools/Converters/Interfaces/ISwappableConverter.cs new file mode 100644 index 0000000..80249d4 --- /dev/null +++ b/USITools/USITools/Converters/Interfaces/ISwappableConverter.cs @@ -0,0 +1,7 @@ +namespace USITools +{ + public interface ISwappableConverter + { + void Swap(AbstractSwapOption swapOption); + } +} diff --git a/USITools/USITools/LoadoutInfo.cs b/USITools/USITools/Converters/LoadoutInfo.cs similarity index 74% rename from USITools/USITools/LoadoutInfo.cs rename to USITools/USITools/Converters/LoadoutInfo.cs index 2a312cc..cee99ad 100644 --- a/USITools/USITools/LoadoutInfo.cs +++ b/USITools/USITools/Converters/LoadoutInfo.cs @@ -1,5 +1,8 @@ +using System; + namespace USITools { + [Obsolete("Use classes derived from AbstractSwapOption instead.")] public class LoadoutInfo { public int ModuleId { get; set; } diff --git a/USITools/USITools/Converters/ModuleConverterSwapOption.cs b/USITools/USITools/Converters/ModuleConverterSwapOption.cs new file mode 100644 index 0000000..5dcda30 --- /dev/null +++ b/USITools/USITools/Converters/ModuleConverterSwapOption.cs @@ -0,0 +1,50 @@ +using USITools.KolonyTools; + +namespace USITools +{ + public class ModuleConverterSwapOption + : AbstractSwapOption + { + public override void ApplyConverterChanges(ModuleResourceConverter_USI converter) + { + converter.inputList.Clear(); + converter.outputList.Clear(); + converter.reqList.Clear(); + + converter.Recipe.Inputs.Clear(); + converter.Recipe.Outputs.Clear(); + converter.Recipe.Requirements.Clear(); + + converter.inputList.AddRange(inputList); + converter.outputList.AddRange(outputList); + converter.reqList.AddRange(reqList); + + converter.Recipe.Inputs.AddRange(inputList); + converter.Recipe.Outputs.AddRange(outputList); + converter.Recipe.Requirements.AddRange(reqList); + + base.ApplyConverterChanges(converter); + } + + public override ConversionRecipe PrepareRecipe(ConversionRecipe recipe) + { + if (!USI_DifficultyOptions.ConsumeMachineryEnabled && recipe != null) + { + for (int i = recipe.Inputs.Count; i-- > 0;) + { + var input = recipe.Inputs[i]; + if (input.ResourceName == "Machinery") + recipe.Inputs.Remove(input); + } + for (int o = recipe.Outputs.Count; o-- > 0;) + { + var output = recipe.Outputs[o]; + if (output.ResourceName == "Recyclables") + recipe.Inputs.Remove(output); + } + } + + return recipe; + } + } +} diff --git a/USITools/USITools/Converters/ModuleEfficiencyPartSwapOption.cs b/USITools/USITools/Converters/ModuleEfficiencyPartSwapOption.cs new file mode 100644 index 0000000..34399b1 --- /dev/null +++ b/USITools/USITools/Converters/ModuleEfficiencyPartSwapOption.cs @@ -0,0 +1,19 @@ +namespace USITools +{ + public class ModuleEfficiencyPartSwapOption : ModuleConverterSwapOption + { + [KSPField] + public double eMultiplier = 1d; + + [KSPField] + public string eTag = ""; + + public override void ApplyConverterChanges(ModuleResourceConverter_USI converter) + { + converter.eMultiplier = eMultiplier; + converter.eTag = eTag; + + base.ApplyConverterChanges(converter); + } + } +} diff --git a/USITools/USITools/Converters/ModuleHarvesterSwapOption.cs b/USITools/USITools/Converters/ModuleHarvesterSwapOption.cs new file mode 100644 index 0000000..21d8020 --- /dev/null +++ b/USITools/USITools/Converters/ModuleHarvesterSwapOption.cs @@ -0,0 +1,19 @@ +using System; + +namespace USITools +{ + [Obsolete("Use a class derived from AbstractSwapOption instead.")] + public class ModuleSwapOption : ModuleHarvesterSwapOption { } + + public class ModuleHarvesterSwapOption + : AbstractSwapOption + { + public override void ApplyConverterChanges(ModuleResourceHarvester_USI converter) + { + converter.Efficiency = Efficiency; + converter.ResourceName = ResourceName; + + base.ApplyConverterChanges(converter); + } + } +} diff --git a/USITools/USITools/ModuleResourceConverter_USI.cs b/USITools/USITools/Converters/ModuleResourceConverter_USI.cs similarity index 55% rename from USITools/USITools/ModuleResourceConverter_USI.cs rename to USITools/USITools/Converters/ModuleResourceConverter_USI.cs index b251132..25ad3e6 100644 --- a/USITools/USITools/ModuleResourceConverter_USI.cs +++ b/USITools/USITools/Converters/ModuleResourceConverter_USI.cs @@ -1,30 +1,64 @@ -using System; using System.Collections.Generic; using USITools.KolonyTools; namespace USITools { - public class ModuleResourceConverter_USI : ModuleResourceConverter, IEfficiencyBonusConsumer + public class ModuleResourceConverter_USI : + ModuleResourceConverter, + IEfficiencyBonusConsumer, + ISwappableConverter { - private Dictionary _bonusList; - - [KSPField] - public bool UseBonus = true; - + #region Fields and properties [KSPField] public double eMultiplier = 1d; [KSPField] public string eTag = ""; - public Dictionary BonusList + public Dictionary BonusList { get; private set; } = + new Dictionary(); + + public bool useEfficiencyBonus { get { - if (_bonusList == null) - _bonusList = new Dictionary(); - return _bonusList; + if (_swapOption != null) + return _swapOption.UseBonus; + else + return false; + } + } + + private AbstractSwapOption _swapOption; + #endregion + + public void Swap(AbstractSwapOption swapOption) + { + Swap(swapOption as AbstractSwapOption); + } + + public void Swap(AbstractSwapOption swapOption) + { + _swapOption = swapOption; + _swapOption.ApplyConverterChanges(this); + } + + public float GetEfficiencyBonus() + { + var totalBonus = 1f; + foreach (var bonus in BonusList) + { + totalBonus *= bonus.Value; } + return totalBonus; + } + + public void SetEfficiencyBonus(string name, float value) + { + if (!BonusList.ContainsKey(name)) + BonusList.Add(name, value); + else + BonusList[name] = value; } protected override void PreProcessing() @@ -38,17 +72,15 @@ protected override ConversionRecipe PrepareRecipe(double deltatime) var recipe = base.PrepareRecipe(deltatime); if (!USI_DifficultyOptions.ConsumeMachineryEnabled && recipe != null) { - var iCount = recipe.Inputs.Count; - var oCount = recipe.Outputs.Count; - for (int i = iCount; i-- > 0;) + for (int i = recipe.Inputs.Count; i-- > 0;) { - var ip = recipe.Inputs[i]; - if (ip.ResourceName == "Machinery") - recipe.Inputs.Remove(ip); + var input = recipe.Inputs[i]; + if (input.ResourceName == "Machinery") + recipe.Inputs.Remove(input); } - for (int o = oCount; o-- > 0;) + for (int output = recipe.Outputs.Count; output-- > 0;) { - var op = recipe.Outputs[o]; + var op = recipe.Outputs[output]; if (op.ResourceName == "Recyclables") recipe.Inputs.Remove(op); } @@ -72,25 +104,6 @@ protected override void PostProcess(ConverterResults result, double deltaTime) statusPercent = 0d; //Force a reset of the load display. } } - public float GetEfficiencyBonus() - { - var finBonus = 1f; - foreach (var b in BonusList) - { - finBonus *= b.Value; - } - return finBonus; - } - - public void SetEfficiencyBonus(string bonName, float bonVal) - { - if (!BonusList.ContainsKey(bonName)) - BonusList.Add(bonName, bonVal); - else - BonusList[bonName] = bonVal; - } - - public bool useEfficiencyBonus => UseBonus; public override string GetModuleDisplayName() { @@ -110,7 +123,5 @@ public void DisableConsumer() isEnabled = false; MonoUtilities.RefreshContextWindows(part); } - - } -} \ No newline at end of file +} diff --git a/USITools/USITools/Converters/ModuleResourceHarvester_USI.cs b/USITools/USITools/Converters/ModuleResourceHarvester_USI.cs new file mode 100644 index 0000000..ec1bb9e --- /dev/null +++ b/USITools/USITools/Converters/ModuleResourceHarvester_USI.cs @@ -0,0 +1,95 @@ +using System.Collections.Generic; + +namespace USITools +{ + public class ModuleResourceHarvester_USI : + ModuleResourceHarvester, + IEfficiencyBonusConsumer, + ISwappableConverter + { + #region Fields and properties + public Dictionary BonusList { get; private set; } = + new Dictionary(); + + public bool useEfficiencyBonus + { + get + { + if (_swapOption != null) + return _swapOption.UseBonus; + else + return false; + } + } + + private AbstractSwapOption _swapOption; + #endregion + + public void Swap(AbstractSwapOption swapOption) + { + Swap(swapOption as AbstractSwapOption); + } + + public void Swap(AbstractSwapOption swapOption) + { + _swapOption = swapOption; + _swapOption.ApplyConverterChanges(this); + } + + public float GetEfficiencyBonus() + { + var totalBonus = 1f; + foreach (var bonus in BonusList) + { + totalBonus *= bonus.Value; + } + return totalBonus; + } + + public void SetEfficiencyBonus(string name, float value) + { + if (!BonusList.ContainsKey(name)) + BonusList.Add(name, value); + else + BonusList[name] = value; + } + + protected override void PreProcessing() + { + base.PreProcessing(); + EfficiencyBonus = GetEfficiencyBonus(); + } + + protected override void PostProcess(ConverterResults result, double deltaTime) + { + base.PostProcess(result, deltaTime); + + var hasLoad = false; + if (status != null) + { + hasLoad = status.EndsWith("Load"); + } + + + if (result.TimeFactor >= ResourceUtilities.FLOAT_TOLERANCE + && !hasLoad) + { + statusPercent = 0d; //Force a reset of the load display. + } + } + + public void EnableConsumer() + { + base.EnableModule(); + isEnabled = true; + MonoUtilities.RefreshContextWindows(part); + } + + public void DisableConsumer() + { + DisableModule(); + isEnabled = false; + MonoUtilities.RefreshContextWindows(part); + } + } +} \ No newline at end of file diff --git a/USITools/USITools/Converters/ModuleSwapController.cs b/USITools/USITools/Converters/ModuleSwapController.cs new file mode 100644 index 0000000..3138f4c --- /dev/null +++ b/USITools/USITools/Converters/ModuleSwapController.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.Text; + +namespace USITools +{ + public class ModuleSwapController : PartModule + { + [KSPField] + public string ResourceCosts = ""; + + [KSPField] + public string typeName = "Loadout"; + + public List ResourceCostRatios = new List(); + public List Loadouts; + + private List _converters; + private double lastCheck; + private double checkInterval = 5d; + + public override void OnStart(StartState state) + { + Loadouts = part.FindModulesImplementing(); + _converters = part.FindModulesImplementing(); + SetupResourceCosts(); + } + + private void SetupResourceCosts() + { + ResourceCostRatios.Clear(); + + if (string.IsNullOrEmpty(ResourceCosts)) + return; + + var resources = ResourceCosts.Split(','); + for (int i = 0; i < resources.Length; i += 2) + { + ResourceCostRatios.Add(new ResourceRatio + { + ResourceName = resources[i], + Ratio = double.Parse(resources[i + 1]) + }); + } + } + + public override string GetInfo() + { + if (string.IsNullOrEmpty(ResourceCosts)) + return string.Empty; + + var output = new StringBuilder("Resource Cost:\n\n"); + var resources = ResourceCosts.Split(','); + for (int i = 0; i < resources.Length; i += 2) + { + output.Append(string.Format("{0} {1}\n", double.Parse(resources[i + 1]), resources[i])); + } + return output.ToString(); + } + + public void ApplyLoadout(int loadoutIndex, int converterIndex) + { + var loadout = Loadouts[loadoutIndex]; + var converter = _converters[converterIndex]; + + converter.Swap(loadout); + } + } +} diff --git a/USITools/USITools/ModuleSwapControllerNew.cs b/USITools/USITools/Converters/ModuleSwapControllerNew.cs similarity index 95% rename from USITools/USITools/ModuleSwapControllerNew.cs rename to USITools/USITools/Converters/ModuleSwapControllerNew.cs index bad885c..24c6919 100644 --- a/USITools/USITools/ModuleSwapControllerNew.cs +++ b/USITools/USITools/Converters/ModuleSwapControllerNew.cs @@ -1,11 +1,10 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Text; -using UnityEngine; namespace USITools { + [Obsolete("Use a class derived from AbstractSwapController instead.")] public class ModuleSwapControllerNew : PartModule { [KSPField] @@ -82,9 +81,6 @@ private void ApplyConverterChanges(ModuleResourceConverter_USI converter, Module converter.UseSpecialistBonus = loadout.UseSpecialistBonus; if (converter.UseSpecialistBonus) converter.ExperienceEffect = loadout.ExperienceEffect; - converter.UseBonus = loadout.UseBonus; - converter.eMultiplier = loadout.eMultiplier; - converter.eTag = loadout.eTag; converter.inputList.Clear(); converter.outputList.Clear(); diff --git a/USITools/USITools/Converters/ModuleSwappableBay.cs b/USITools/USITools/Converters/ModuleSwappableBay.cs new file mode 100644 index 0000000..7615a11 --- /dev/null +++ b/USITools/USITools/Converters/ModuleSwappableBay.cs @@ -0,0 +1,266 @@ +namespace USITools.Converters +{ + public class ModuleSwappableBay : PartModule + { + #region KSP Fields and Events + [KSPField] + public bool autoActivate = true; + + [KSPField] + public string bayName = ""; + + [KSPField] + public bool isConverter = false; + + [KSPField] + public int moduleIndex = 0; + + [KSPField(isPersistant = true)] + public int currentLoadout = 0; + + [KSPField(guiActive = true, guiActiveEditor = true, guiName = "Active: ")] + public string curTemplate = "???"; + + [KSPEvent(active = true, guiActiveEditor = true, guiActiveUnfocused = true, externalToEVAOnly = true, guiName = "B1: Install [None]", unfocusedRange = 10f)] + public void LoadSetup() + { + if (!CheckResources()) + return; + + var oldTemplate = curTemplate; + currentLoadout = displayLoadout; + NextSetup(); + + ScreenMessages.PostScreenMessage("Reconfiguration from " + oldTemplate + " to " + curTemplate + " completed.", 5f, + ScreenMessageStyle.UPPER_CENTER); + + ConfigureLoadout(); + } + + [KSPEvent(active = true, guiActiveEditor = true, guiActiveUnfocused = true, externalToEVAOnly = true, guiName = "B1: Next Loadout", unfocusedRange = 10f)] + public void NextSetup() + { + if (_controller.Loadouts.Count < 2) + return; + + displayLoadout++; + if (displayLoadout >= _controller.Loadouts.Count) + { + displayLoadout = 0; + } + if (displayLoadout == currentLoadout) + { + NextSetup(); + } + + ChangeMenu(); + } + + [KSPEvent(active = true, guiActiveEditor = true, guiActiveUnfocused = true, externalToEVAOnly = true, guiName = "B1: Prev. Loadout", unfocusedRange = 10f)] + public void PrevSetup() + { + if (_controller.Loadouts.Count < 2) + return; + + displayLoadout--; + if (displayLoadout < 0) + { + displayLoadout = _controller.Loadouts.Count - 1; + } + if (displayLoadout == currentLoadout) + { + PrevSetup(); + } + + ChangeMenu(); + } + #endregion + + #region Fields and properties + private bool _postLoad = false; + private int displayLoadout; + private ModuleSwapController _controller; + #endregion + + public override void OnStart(StartState state) + { + _controller = part.FindModuleImplementing(); + GameEvents.OnAnimationGroupStateChanged.Add(SetModuleState); + displayLoadout = currentLoadout; + ConfigureLoadout(); + } + + private void SetModuleState(ModuleAnimationGroup module, bool enable) + { + if (module != null && module.part != part) + return; + + if (HighLogic.LoadedSceneIsFlight) + { + EnableMenus(enable); + } + } + + private void EnableMenus(bool enable) + { + Events["NextSetup"].active = enable; + Events["PrevSetup"].active = enable; + Events["LoadSetup"].active = enable; + MonoUtilities.RefreshContextWindows(part); + } + + private void ConfigureLoadout() + { + _controller.ApplyLoadout(currentLoadout, moduleIndex); + } + + public void Update() + { + if (!_postLoad) + { + if (_controller.Loadouts.Count > 2) + { + _postLoad = true; + NextSetup(); + } + } + } + + public void ChangeMenu() + { + Events["NextSetup"].guiName = (bayName + " Next " + _controller.typeName).Trim(); + Events["PrevSetup"].guiName = (bayName + " Prev. " + _controller.typeName).Trim(); + Fields["curTemplate"].guiName = (bayName + " Active " + _controller.typeName).Trim(); + curTemplate = _controller.Loadouts[currentLoadout].ConverterName; + Events["LoadSetup"].guiName = + (bayName + " " + curTemplate + "=>" + _controller.Loadouts[displayLoadout].ConverterName).Trim(); + + MonoUtilities.RefreshContextWindows(part); + } + + private bool CheckResources() + { + if (HighLogic.LoadedSceneIsEditor) + return true; + var kerbal = FlightGlobals.ActiveVessel.rootPart.protoModuleCrew[0]; + if (!kerbal.HasEffect("RepairSkill")) + { + ScreenMessages.PostScreenMessage("Only Kerbals with repair skills (engineers, mechanics) can reconfigure modules!", 5f, + ScreenMessageStyle.UPPER_CENTER); + return false; + } + + var allResources = true; + var missingResources = ""; + //Check that we have everything we need. + var count = _controller.ResourceCostRatios.Count; + for (int i = 0; i < count; ++i) + { + var r = _controller.ResourceCostRatios[i]; + if (!HasResource(r)) + { + allResources = false; + missingResources += "\n" + r.Ratio + " " + r.ResourceName; + } + } + if (!allResources) + { + ScreenMessages.PostScreenMessage("Missing resources to change module:" + missingResources, 5f, + ScreenMessageStyle.UPPER_CENTER); + return false; + } + //Since everything is here... + for (int i = 0; i < count; ++i) + { + var r = _controller.ResourceCostRatios[i]; + TakeResources(r); + } + return true; + } + + private bool HasResource(ResourceRatio resInfo) + { + var resourceName = resInfo.ResourceName; + var needed = resInfo.Ratio; + var whpList = LogisticsTools.GetRegionalWarehouses(vessel, "USI_ModuleResourceWarehouse"); + //EC we're a lot less picky... + if (resInfo.ResourceName == "ElectricCharge") + { + whpList.AddRange(part.vessel.parts); + } + var count = whpList.Count; + for (int i = 0; i < count; ++i) + { + var whp = whpList[i]; + if (whp == part) + continue; + + if (resInfo.ResourceName != "ElectricCharge") + { + var wh = whp.FindModuleImplementing(); + if (wh != null) + { + if (!wh.localTransferEnabled) + continue; + } + } + if (whp.Resources.Contains(resourceName)) + { + var res = whp.Resources[resourceName]; + if (res.amount >= needed) + { + needed = 0; + break; + } + else + { + needed -= res.amount; + } + } + } + return (needed < ResourceUtilities.FLOAT_TOLERANCE); + } + + private void TakeResources(ResourceRatio resInfo) + { + var resourceName = resInfo.ResourceName; + var needed = resInfo.Ratio; + //Pull in from warehouses + + var whpList = LogisticsTools.GetRegionalWarehouses(vessel, "USI_ModuleResourceWarehouse"); + var count = whpList.Count; + for (int i = 0; i < count; ++i) + { + var whp = whpList[i]; + if (whp == part) + continue; + var wh = whp.FindModuleImplementing(); + if (wh != null) + { + if (!wh.localTransferEnabled) + continue; + } + if (whp.Resources.Contains(resourceName)) + { + var res = whp.Resources[resourceName]; + if (res.amount >= needed) + { + res.amount -= needed; + needed = 0; + break; + } + else + { + needed -= res.amount; + res.amount = 0; + } + } + } + } + + public void OnDestroy() + { + GameEvents.OnAnimationGroupStateChanged.Remove(SetModuleState); + } + } +} diff --git a/USITools/USITools/ModuleSwappableConverter.cs b/USITools/USITools/Converters/ModuleSwappableConverter.cs similarity index 99% rename from USITools/USITools/ModuleSwappableConverter.cs rename to USITools/USITools/Converters/ModuleSwappableConverter.cs index a965fba..ed0f48c 100644 --- a/USITools/USITools/ModuleSwappableConverter.cs +++ b/USITools/USITools/Converters/ModuleSwappableConverter.cs @@ -4,6 +4,7 @@ namespace USITools { + [Obsolete("Use a class derived from AbstractSwap")] public class ModuleSwappableConverter : PartModule { [KSPField] diff --git a/USITools/USITools/ModuleSwappableConverterNew.cs b/USITools/USITools/Converters/ModuleSwappableConverterNew.cs similarity index 99% rename from USITools/USITools/ModuleSwappableConverterNew.cs rename to USITools/USITools/Converters/ModuleSwappableConverterNew.cs index c415373..f3cce27 100644 --- a/USITools/USITools/ModuleSwappableConverterNew.cs +++ b/USITools/USITools/Converters/ModuleSwappableConverterNew.cs @@ -4,6 +4,7 @@ namespace USITools { + [Obsolete("Use ModuleSwappableBay instead.")] public class ModuleSwappableConverterNew : PartModule { [KSPField] diff --git a/USITools/USITools/ModuleResourceHarvester_USI.cs b/USITools/USITools/ModuleResourceHarvester_USI.cs deleted file mode 100644 index 6342525..0000000 --- a/USITools/USITools/ModuleResourceHarvester_USI.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System.Collections.Generic; - -namespace USITools -{ - public class ModuleResourceHarvester_USI : ModuleResourceHarvester, IEfficiencyBonusConsumer - { - private Dictionary _bonusList; - - [KSPField] - public bool UseBonus = true; - - public Dictionary BonusList - { - get - { - if (_bonusList == null) - _bonusList = new Dictionary(); - return _bonusList; - } - } - - protected override void PreProcessing() - { - base.PreProcessing(); - EfficiencyBonus = GetEfficiencyBonus(); - } - - protected override void PostProcess(ConverterResults result, double deltaTime) - { - base.PostProcess(result, deltaTime); - - var hasLoad = false; - if (status != null) - { - hasLoad = status.EndsWith("Load"); - } - - - if (result.TimeFactor >= ResourceUtilities.FLOAT_TOLERANCE - && !hasLoad) - { - statusPercent = 0d; //Force a reset of the load display. - } - } - - public float GetEfficiencyBonus() - { - var finBonus = 1f; //Harvesters already have a Crew Bonus - foreach (var b in BonusList) - { - finBonus *= b.Value; - } - return finBonus; - } - - public void SetEfficiencyBonus(string bonName, float bonVal) - { - if (!BonusList.ContainsKey(bonName)) - BonusList.Add(bonName, bonVal); - else - BonusList[bonName] = bonVal; - } - - public bool useEfficiencyBonus => UseBonus; - public void EnableConsumer() - { - base.EnableModule(); - isEnabled = true; - MonoUtilities.RefreshContextWindows(part); - } - - public void DisableConsumer() - { - DisableModule(); - isEnabled = false; - MonoUtilities.RefreshContextWindows(part); - } - } -} \ No newline at end of file diff --git a/USITools/USITools/USITools.csproj b/USITools/USITools/USITools.csproj index 57f67c6..a85567f 100644 --- a/USITools/USITools/USITools.csproj +++ b/USITools/USITools/USITools.csproj @@ -55,11 +55,17 @@ - + + + + + + - - - + + + + @@ -77,7 +83,7 @@ - + @@ -86,12 +92,12 @@ - - + + - - + + From f8ea7e2c72dc79a11bf13e347e2e8f5447bb14d9 Mon Sep 17 00:00:00 2001 From: doktorkrogg Date: Sun, 30 Sep 2018 11:40:43 -0400 Subject: [PATCH 04/15] Code cleanup and bug fixes Removed some unused fields and methods; moved addtional code out of KolonyTools.ModuleEfficiencyPart into USITools.ModuleResourceConverter; AbstractSwapOption wasn't setting the StopActionName when converters were swapped; AbstractSwapOption input/output lists changed (back) to fields instead of properties to make KSP happy; changed ModuleEfficiencyPartSwapOption to always set UseBonus to false regardless of config file setting --- .../USITools/Converters/AbstractSwapOption.cs | 15 ++++-- .../Interfaces/IEfficiencyBonusConsumer.cs | 6 +-- .../Converters/ModuleConverterSwapOption.cs | 21 --------- .../ModuleEfficiencyPartSwapOption.cs | 7 +++ .../Converters/ModuleResourceConverter_USI.cs | 46 +++++++++++++------ .../Converters/ModuleResourceHarvester_USI.cs | 18 +------- .../USITools/Converters/ModuleSwappableBay.cs | 3 -- 7 files changed, 53 insertions(+), 63 deletions(-) diff --git a/USITools/USITools/Converters/AbstractSwapOption.cs b/USITools/USITools/Converters/AbstractSwapOption.cs index 7ecae0b..3f79f11 100644 --- a/USITools/USITools/Converters/AbstractSwapOption.cs +++ b/USITools/USITools/Converters/AbstractSwapOption.cs @@ -10,11 +10,17 @@ public virtual void ApplyConverterChanges(T converter) { converter.ConverterName = ConverterName; converter.StartActionName = StartActionName; + converter.StopActionName = StopActionName; converter.UseSpecialistBonus = UseSpecialistBonus; converter.ExperienceEffect = ExperienceEffect; MonoUtilities.RefreshContextWindows(part); } + + public virtual void PostProcess(T Converter, ConverterResults result, double deltaTime) + { + PostProcess(result, deltaTime); + } } public abstract class AbstractSwapOption : PartModule @@ -43,13 +49,12 @@ public abstract class AbstractSwapOption : PartModule [KSPField] public bool UseBonus = true; - public List inputList { get; set; } - public List outputList { get; set; } - public List reqList { get; set; } + public List inputList; + public List outputList; + public List reqList; - public virtual ConversionRecipe PrepareRecipe(ConversionRecipe recipe) + public virtual void PostProcess(ConverterResults result, double deltaTime) { - return recipe; } public override void OnLoad(ConfigNode node) diff --git a/USITools/USITools/Converters/Interfaces/IEfficiencyBonusConsumer.cs b/USITools/USITools/Converters/Interfaces/IEfficiencyBonusConsumer.cs index d1f7ed2..b9b2383 100644 --- a/USITools/USITools/Converters/Interfaces/IEfficiencyBonusConsumer.cs +++ b/USITools/USITools/Converters/Interfaces/IEfficiencyBonusConsumer.cs @@ -4,10 +4,6 @@ public interface IEfficiencyBonusConsumer { float GetEfficiencyBonus(); void SetEfficiencyBonus(string bonName, float bonValue); - - bool useEfficiencyBonus { get; } - - void EnableConsumer(); - void DisableConsumer(); + bool UseEfficiencyBonus { get; } } } diff --git a/USITools/USITools/Converters/ModuleConverterSwapOption.cs b/USITools/USITools/Converters/ModuleConverterSwapOption.cs index 5dcda30..47e0255 100644 --- a/USITools/USITools/Converters/ModuleConverterSwapOption.cs +++ b/USITools/USITools/Converters/ModuleConverterSwapOption.cs @@ -25,26 +25,5 @@ public override void ApplyConverterChanges(ModuleResourceConverter_USI converter base.ApplyConverterChanges(converter); } - - public override ConversionRecipe PrepareRecipe(ConversionRecipe recipe) - { - if (!USI_DifficultyOptions.ConsumeMachineryEnabled && recipe != null) - { - for (int i = recipe.Inputs.Count; i-- > 0;) - { - var input = recipe.Inputs[i]; - if (input.ResourceName == "Machinery") - recipe.Inputs.Remove(input); - } - for (int o = recipe.Outputs.Count; o-- > 0;) - { - var output = recipe.Outputs[o]; - if (output.ResourceName == "Recyclables") - recipe.Inputs.Remove(output); - } - } - - return recipe; - } } } diff --git a/USITools/USITools/Converters/ModuleEfficiencyPartSwapOption.cs b/USITools/USITools/Converters/ModuleEfficiencyPartSwapOption.cs index 34399b1..99aaf8a 100644 --- a/USITools/USITools/Converters/ModuleEfficiencyPartSwapOption.cs +++ b/USITools/USITools/Converters/ModuleEfficiencyPartSwapOption.cs @@ -10,10 +10,17 @@ public class ModuleEfficiencyPartSwapOption : ModuleConverterSwapOption public override void ApplyConverterChanges(ModuleResourceConverter_USI converter) { + UseBonus = false; // efficiency parts should not use bonuses from other efficiency parts! converter.eMultiplier = eMultiplier; converter.eTag = eTag; base.ApplyConverterChanges(converter); } + + public override void PostProcess(ModuleResourceConverter_USI converter, ConverterResults result, double deltaTime) + { + base.PostProcess(result, deltaTime); + converter.EfficiencyMultiplier = result.TimeFactor / deltaTime; + } } } diff --git a/USITools/USITools/Converters/ModuleResourceConverter_USI.cs b/USITools/USITools/Converters/ModuleResourceConverter_USI.cs index 25ad3e6..e4fcc2a 100644 --- a/USITools/USITools/Converters/ModuleResourceConverter_USI.cs +++ b/USITools/USITools/Converters/ModuleResourceConverter_USI.cs @@ -18,7 +18,25 @@ public class ModuleResourceConverter_USI : public Dictionary BonusList { get; private set; } = new Dictionary(); - public bool useEfficiencyBonus + public float Governor = 1.0f; + private double _efficiencyMultiplier; + public double EfficiencyMultiplier + { + get + { + if (HighLogic.LoadedSceneIsEditor) + return _efficiencyMultiplier * Governor; + if (!IsActivated) + _efficiencyMultiplier = 0d; + return _efficiencyMultiplier * Governor; + } + set + { + _efficiencyMultiplier = value; + } + } + + public bool UseEfficiencyBonus { get { @@ -103,25 +121,27 @@ protected override void PostProcess(ConverterResults result, double deltaTime) { statusPercent = 0d; //Force a reset of the load display. } - } - public override string GetModuleDisplayName() - { - return GetType().Name; + if (_swapOption != null) + { + _swapOption.PostProcess(this, result, deltaTime); + } } - public void EnableConsumer() + public override string GetInfo() { - base.EnableModule(); - isEnabled = true; - MonoUtilities.RefreshContextWindows(part); + if (string.IsNullOrEmpty(eTag)) + return base.GetInfo(); + var resourceConsumption = base.GetInfo(); + int index = resourceConsumption.IndexOf("\n"); // Strip the first line containing the etag + resourceConsumption = resourceConsumption.Substring(index + 1); + return "Boosts efficiency of converters benefiting from a " + eTag + "\n\n" + + "Boost power: " + eMultiplier.ToString() + resourceConsumption; } - public void DisableConsumer() + public override string GetModuleDisplayName() { - DisableModule(); - isEnabled = false; - MonoUtilities.RefreshContextWindows(part); + return GetType().Name; } } } diff --git a/USITools/USITools/Converters/ModuleResourceHarvester_USI.cs b/USITools/USITools/Converters/ModuleResourceHarvester_USI.cs index ec1bb9e..d041ba5 100644 --- a/USITools/USITools/Converters/ModuleResourceHarvester_USI.cs +++ b/USITools/USITools/Converters/ModuleResourceHarvester_USI.cs @@ -11,7 +11,7 @@ public class ModuleResourceHarvester_USI : public Dictionary BonusList { get; private set; } = new Dictionary(); - public bool useEfficiencyBonus + public bool UseEfficiencyBonus { get { @@ -77,19 +77,5 @@ protected override void PostProcess(ConverterResults result, double deltaTime) statusPercent = 0d; //Force a reset of the load display. } } - - public void EnableConsumer() - { - base.EnableModule(); - isEnabled = true; - MonoUtilities.RefreshContextWindows(part); - } - - public void DisableConsumer() - { - DisableModule(); - isEnabled = false; - MonoUtilities.RefreshContextWindows(part); - } } -} \ No newline at end of file +} diff --git a/USITools/USITools/Converters/ModuleSwappableBay.cs b/USITools/USITools/Converters/ModuleSwappableBay.cs index 7615a11..b64ae63 100644 --- a/USITools/USITools/Converters/ModuleSwappableBay.cs +++ b/USITools/USITools/Converters/ModuleSwappableBay.cs @@ -9,9 +9,6 @@ public class ModuleSwappableBay : PartModule [KSPField] public string bayName = ""; - [KSPField] - public bool isConverter = false; - [KSPField] public int moduleIndex = 0; From 25277851dc927d85550615204e3fcd84a028b29e Mon Sep 17 00:00:00 2001 From: jd284 Date: Fri, 5 Oct 2018 11:30:35 +0200 Subject: [PATCH 05/15] Power distributor configuration for crewless operation RequiredSkill field sets type of crew required, empty for crewless operation --- USITools/USITools/Logistics/ModulePowerDistributor.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/USITools/USITools/Logistics/ModulePowerDistributor.cs b/USITools/USITools/Logistics/ModulePowerDistributor.cs index 3e46524..74a13ff 100644 --- a/USITools/USITools/Logistics/ModulePowerDistributor.cs +++ b/USITools/USITools/Logistics/ModulePowerDistributor.cs @@ -15,7 +15,12 @@ public bool isDistributingPower { get { - return this.part != null && LogisticsTools.NearbyCrew(this.vessel, LogisticsSetup.Instance.Config.ScavangeRange, "ConverterSkill"); + if (this.part == null) + return false; + else if (string.IsNullOrEmpty(RequiredSkill)) + return true; + else + return LogisticsTools.NearbyCrew(this.vessel, LogisticsSetup.Instance.Config.ScavangeRange, RequiredSkill); } } @@ -49,4 +54,4 @@ public override string GetInfo() "Range: " + PowerDistributionRange + "m"; } } -} \ No newline at end of file +} From 60861bea51edccc1af466e5eabe242d8e6e2a357 Mon Sep 17 00:00:00 2001 From: jd284 Date: Fri, 5 Oct 2018 11:52:37 +0200 Subject: [PATCH 06/15] Update ModulePowerDistributor.cs --- USITools/USITools/Logistics/ModulePowerDistributor.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/USITools/USITools/Logistics/ModulePowerDistributor.cs b/USITools/USITools/Logistics/ModulePowerDistributor.cs index 74a13ff..4188cf1 100644 --- a/USITools/USITools/Logistics/ModulePowerDistributor.cs +++ b/USITools/USITools/Logistics/ModulePowerDistributor.cs @@ -10,6 +10,9 @@ public class ModulePowerDistributor : PartModule [KSPField(guiActive = true, guiName = "PDU Range")] public string gui_pduRange; + + [KSPField] + public string RequiredSkill = "ConverterSkill"; public bool isDistributingPower { From c4089a5536627cfc8bbe13c13bf1ae5a17e0fc88 Mon Sep 17 00:00:00 2001 From: jd284 Date: Fri, 5 Oct 2018 11:55:50 +0200 Subject: [PATCH 07/15] Update ModulePowerDistributor.cs From 52f81123fb149f4c6e0d15bf794b4d80ff5bd875 Mon Sep 17 00:00:00 2001 From: doktorkrogg Date: Mon, 15 Oct 2018 13:26:52 -0400 Subject: [PATCH 08/15] Editor info Remove KolonyTools reference from ModuleConverterSwapOption; added GetInfo method to ModuleEfficiencyPartSwapOption --- .../USITools/Converters/ModuleConverterSwapOption.cs | 4 +--- .../Converters/ModuleEfficiencyPartSwapOption.cs | 11 +++++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/USITools/USITools/Converters/ModuleConverterSwapOption.cs b/USITools/USITools/Converters/ModuleConverterSwapOption.cs index 47e0255..ec36cff 100644 --- a/USITools/USITools/Converters/ModuleConverterSwapOption.cs +++ b/USITools/USITools/Converters/ModuleConverterSwapOption.cs @@ -1,6 +1,4 @@ -using USITools.KolonyTools; - -namespace USITools +namespace USITools { public class ModuleConverterSwapOption : AbstractSwapOption diff --git a/USITools/USITools/Converters/ModuleEfficiencyPartSwapOption.cs b/USITools/USITools/Converters/ModuleEfficiencyPartSwapOption.cs index 99aaf8a..211fe81 100644 --- a/USITools/USITools/Converters/ModuleEfficiencyPartSwapOption.cs +++ b/USITools/USITools/Converters/ModuleEfficiencyPartSwapOption.cs @@ -17,6 +17,17 @@ public override void ApplyConverterChanges(ModuleResourceConverter_USI converter base.ApplyConverterChanges(converter); } + public override string GetInfo() + { + if (string.IsNullOrEmpty(eTag)) + return base.GetInfo(); + var resourceConsumption = base.GetInfo(); + int index = resourceConsumption.IndexOf("\n"); // Strip the first line containing the etag + resourceConsumption = resourceConsumption.Substring(index + 1); + return "Boosts efficiency of converters benefiting from a " + eTag + "\n\n" + + "Boost power: " + eMultiplier.ToString() + resourceConsumption; + } + public override void PostProcess(ModuleResourceConverter_USI converter, ConverterResults result, double deltaTime) { base.PostProcess(result, deltaTime); From 967548f6e1200b79e48fcc2eb1535c3c59006b78 Mon Sep 17 00:00:00 2001 From: doktorkrogg Date: Tue, 16 Oct 2018 21:53:28 -0400 Subject: [PATCH 09/15] Prevent save game breakage Refactored old classes to be derived from their new counterparts; this should allow us to leave the old module names in the .cfg files and let their settings be loaded in from old saves --- .../USITools/Converters/AbstractSwapOption.cs | 84 +++++ USITools/USITools/Converters/ConverterPart.cs | 2 +- .../Converters/ModuleResourceConverter_USI.cs | 8 +- .../Converters/ModuleResourceHarvester_USI.cs | 6 +- .../Converters/ModuleSwapController.cs | 16 +- .../Converters/ModuleSwapControllerNew.cs | 112 ------ .../USITools/Converters/ModuleSwappableBay.cs | 16 +- .../Converters/ModuleSwappableConverter.cs | 339 ------------------ .../Converters/ModuleSwappableConverterNew.cs | 265 -------------- USITools/USITools/USITools.csproj | 3 - 10 files changed, 112 insertions(+), 739 deletions(-) delete mode 100644 USITools/USITools/Converters/ModuleSwapControllerNew.cs delete mode 100644 USITools/USITools/Converters/ModuleSwappableConverter.cs delete mode 100644 USITools/USITools/Converters/ModuleSwappableConverterNew.cs diff --git a/USITools/USITools/Converters/AbstractSwapOption.cs b/USITools/USITools/Converters/AbstractSwapOption.cs index 3f79f11..eb0b749 100644 --- a/USITools/USITools/Converters/AbstractSwapOption.cs +++ b/USITools/USITools/Converters/AbstractSwapOption.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Text; using UnityEngine; namespace USITools @@ -53,6 +54,89 @@ public abstract class AbstractSwapOption : PartModule public List outputList; public List reqList; + public override string GetInfo() + { + StringBuilder output = new StringBuilder(); + output + .AppendLine(ConverterName) + .AppendLine(); + + if (inputList.Count > 0) + { + output.AppendLine("Inputs:"); + foreach (var resource in inputList) + { + output + .Append(" - ") + .Append(resource.ResourceName) + .Append(": "); + + if (resource.ResourceName == "ElectricCharge") + output + .AppendFormat("{0:F2}/sec", resource.Ratio) + .AppendLine(); + else + output.AppendLine(ParseResourceRatio(resource.Ratio)); + } + } + if (outputList.Count > 0) + { + output.AppendLine("Outputs:"); + foreach (var resource in outputList) + { + output + .Append(" - ") + .Append(resource.ResourceName) + .Append(": "); + + if (resource.ResourceName == "ElectricCharge") + output + .AppendFormat("{0:F2}/sec", resource.Ratio) + .AppendLine(); + else + output.AppendLine(ParseResourceRatio(resource.Ratio)); + } + } + if (reqList.Count > 0) + { + output.AppendLine("Requirements:"); + foreach (var resource in reqList) + { + output + .Append(" - ") + .Append(resource.ResourceName) + .Append(": ") + .AppendFormat("{0:F2}", resource.Ratio) + .AppendLine(); + } + } + + return output.ToString(); + } + + private string ParseResourceRatio(double ratio) + { + //string units = "sec"; + //if (ratio < 0.001) + //{ + // ratio *= 60; + // units = "min"; + //} + //if (ratio < 0.001) + //{ + // ratio *= 60; + // units = "hr"; + //} + //if (ratio < 0.001) + //{ + // ratio *= 6; + // units = "day"; + //} + + // 60 seconds X 60 minutes X 6 hours = 21600 seconds per Kerbin day + return string.Format("{0:F2}/day", ratio * 21600); + } + public virtual void PostProcess(ConverterResults result, double deltaTime) { } diff --git a/USITools/USITools/Converters/ConverterPart.cs b/USITools/USITools/Converters/ConverterPart.cs index 013ed1a..3082da4 100644 --- a/USITools/USITools/Converters/ConverterPart.cs +++ b/USITools/USITools/Converters/ConverterPart.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace USITools +namespace USITools.Converters { [Obsolete] public class ConverterPart diff --git a/USITools/USITools/Converters/ModuleResourceConverter_USI.cs b/USITools/USITools/Converters/ModuleResourceConverter_USI.cs index e4fcc2a..0d4c675 100644 --- a/USITools/USITools/Converters/ModuleResourceConverter_USI.cs +++ b/USITools/USITools/Converters/ModuleResourceConverter_USI.cs @@ -130,13 +130,7 @@ protected override void PostProcess(ConverterResults result, double deltaTime) public override string GetInfo() { - if (string.IsNullOrEmpty(eTag)) - return base.GetInfo(); - var resourceConsumption = base.GetInfo(); - int index = resourceConsumption.IndexOf("\n"); // Strip the first line containing the etag - resourceConsumption = resourceConsumption.Substring(index + 1); - return "Boosts efficiency of converters benefiting from a " + eTag + "\n\n" + - "Boost power: " + eMultiplier.ToString() + resourceConsumption; + return string.Empty; } public override string GetModuleDisplayName() diff --git a/USITools/USITools/Converters/ModuleResourceHarvester_USI.cs b/USITools/USITools/Converters/ModuleResourceHarvester_USI.cs index d041ba5..9fab6e3 100644 --- a/USITools/USITools/Converters/ModuleResourceHarvester_USI.cs +++ b/USITools/USITools/Converters/ModuleResourceHarvester_USI.cs @@ -54,6 +54,11 @@ public void SetEfficiencyBonus(string name, float value) BonusList[name] = value; } + public override string GetInfo() + { + return string.Empty; + } + protected override void PreProcessing() { base.PreProcessing(); @@ -70,7 +75,6 @@ protected override void PostProcess(ConverterResults result, double deltaTime) hasLoad = status.EndsWith("Load"); } - if (result.TimeFactor >= ResourceUtilities.FLOAT_TOLERANCE && !hasLoad) { diff --git a/USITools/USITools/Converters/ModuleSwapController.cs b/USITools/USITools/Converters/ModuleSwapController.cs index 3138f4c..714eb4b 100644 --- a/USITools/USITools/Converters/ModuleSwapController.cs +++ b/USITools/USITools/Converters/ModuleSwapController.cs @@ -1,8 +1,12 @@ +using System; using System.Collections.Generic; using System.Text; namespace USITools { + [Obsolete("Use ModuleSwapController instead.")] + public class ModuleSwapControllerNew : ModuleSwapController { } + public class ModuleSwapController : PartModule { [KSPField] @@ -11,23 +15,21 @@ public class ModuleSwapController : PartModule [KSPField] public string typeName = "Loadout"; - public List ResourceCostRatios = new List(); + public List SwapCosts = new List(); public List Loadouts; private List _converters; - private double lastCheck; - private double checkInterval = 5d; public override void OnStart(StartState state) { Loadouts = part.FindModulesImplementing(); _converters = part.FindModulesImplementing(); - SetupResourceCosts(); + SetupSwapCosts(); } - private void SetupResourceCosts() + private void SetupSwapCosts() { - ResourceCostRatios.Clear(); + SwapCosts.Clear(); if (string.IsNullOrEmpty(ResourceCosts)) return; @@ -35,7 +37,7 @@ private void SetupResourceCosts() var resources = ResourceCosts.Split(','); for (int i = 0; i < resources.Length; i += 2) { - ResourceCostRatios.Add(new ResourceRatio + SwapCosts.Add(new ResourceRatio { ResourceName = resources[i], Ratio = double.Parse(resources[i + 1]) diff --git a/USITools/USITools/Converters/ModuleSwapControllerNew.cs b/USITools/USITools/Converters/ModuleSwapControllerNew.cs deleted file mode 100644 index 24c6919..0000000 --- a/USITools/USITools/Converters/ModuleSwapControllerNew.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace USITools -{ - [Obsolete("Use a class derived from AbstractSwapController instead.")] - public class ModuleSwapControllerNew : PartModule - { - [KSPField] - public string ResourceCosts = ""; - - [KSPField] - public string typeName = "Loadout"; - - public List ResCosts; - public List Loadouts; - private List _harvesters; - private List _converters; - - private double lastCheck; - private double checkInterval = 5d; - - public override void OnStart(StartState state) - { - Loadouts = part.FindModulesImplementing(); - _converters = part.FindModulesImplementing(); - _harvesters = part.FindModulesImplementing(); - SetupResourceCosts(); - } - - private void SetupResourceCosts() - { - ResCosts = new List(); - if (String.IsNullOrEmpty(ResourceCosts)) - return; - - var resources = ResourceCosts.Split(','); - for (int i = 0; i < resources.Length; i += 2) - { - ResCosts.Add(new ResourceRatio - { - ResourceName = resources[i], - Ratio = double.Parse(resources[i + 1]) - }); - } - } - - public override string GetInfo() - { - if (String.IsNullOrEmpty(ResourceCosts)) - return ""; - - var output = new StringBuilder("Resource Cost:\n\n"); - var resources = ResourceCosts.Split(','); - for (int i = 0; i < resources.Length; i += 2) - { - output.Append(string.Format("{0} {1}\n", double.Parse(resources[i + 1]), resources[i])); - } - return output.ToString(); - } - - public void ApplyLoadout(int loadIdx, int moduleIdx, bool isConverter) - { - var loadout = Loadouts[loadIdx]; - if (isConverter) - { - ApplyConverterChanges(_converters[moduleIdx], loadout); - } - else - { - ApplyHarvesterChanges(_harvesters[moduleIdx], loadout); - } - } - - private void ApplyConverterChanges(ModuleResourceConverter_USI converter, ModuleSwapOption loadout) - { - converter.ConverterName = loadout.ConverterName; - converter.StartActionName = loadout.StartActionName; - converter.StopActionName = loadout.StopActionName; - converter.UseSpecialistBonus = loadout.UseSpecialistBonus; - if (converter.UseSpecialistBonus) - converter.ExperienceEffect = loadout.ExperienceEffect; - - converter.inputList.Clear(); - converter.outputList.Clear(); - converter.reqList.Clear(); - - converter.Recipe.Inputs.Clear(); - converter.Recipe.Outputs.Clear(); - converter.Recipe.Requirements.Clear(); - - converter.inputList.AddRange(loadout.inputList); - converter.outputList.AddRange(loadout.outputList); - converter.reqList.AddRange(loadout.reqList); - - converter.Recipe.Inputs.AddRange(loadout.inputList); - converter.Recipe.Outputs.AddRange(loadout.outputList); - converter.Recipe.Requirements.AddRange(loadout.reqList); - } - - private void ApplyHarvesterChanges(ModuleResourceHarvester_USI harvester, ModuleSwapOption loadout) - { - harvester.Efficiency = loadout.Efficiency; - harvester.ResourceName = loadout.ResourceName; - harvester.ConverterName = loadout.ConverterName; - harvester.StartActionName = loadout.StartActionName; - harvester.StopActionName = loadout.StopActionName; - MonoUtilities.RefreshContextWindows(part); - } - } -} \ No newline at end of file diff --git a/USITools/USITools/Converters/ModuleSwappableBay.cs b/USITools/USITools/Converters/ModuleSwappableBay.cs index b64ae63..19bb946 100644 --- a/USITools/USITools/Converters/ModuleSwappableBay.cs +++ b/USITools/USITools/Converters/ModuleSwappableBay.cs @@ -1,5 +1,13 @@ -namespace USITools.Converters +using System; + +namespace USITools.Converters { + [Obsolete("Use ModuleSwappableBay instead.")] + public class ModuleSwappableConverter : ModuleSwappableBay { } + + [Obsolete("Use ModuleSwappableBay instead.")] + public class ModuleSwappableConverterNew : ModuleSwappableBay { } + public class ModuleSwappableBay : PartModule { #region KSP Fields and Events @@ -150,10 +158,10 @@ private bool CheckResources() var allResources = true; var missingResources = ""; //Check that we have everything we need. - var count = _controller.ResourceCostRatios.Count; + var count = _controller.SwapCosts.Count; for (int i = 0; i < count; ++i) { - var r = _controller.ResourceCostRatios[i]; + var r = _controller.SwapCosts[i]; if (!HasResource(r)) { allResources = false; @@ -169,7 +177,7 @@ private bool CheckResources() //Since everything is here... for (int i = 0; i < count; ++i) { - var r = _controller.ResourceCostRatios[i]; + var r = _controller.SwapCosts[i]; TakeResources(r); } return true; diff --git a/USITools/USITools/Converters/ModuleSwappableConverter.cs b/USITools/USITools/Converters/ModuleSwappableConverter.cs deleted file mode 100644 index ed0f48c..0000000 --- a/USITools/USITools/Converters/ModuleSwappableConverter.cs +++ /dev/null @@ -1,339 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace USITools -{ - [Obsolete("Use a class derived from AbstractSwap")] - public class ModuleSwappableConverter : PartModule - { - [KSPField] - public bool autoActivate = true; - - [KSPField(isPersistant = true)] - public int currentLoadout = 0; - - [KSPField] - public string ResourceCosts = ""; - - [KSPField] - public string bayName = ""; - - [KSPField] - public string typeName = "Loadout"; - - [KSPField(guiActive = true, guiActiveEditor = true, guiName = "Active: ")] - public string curTemplate = "???"; - - [KSPEvent(active = true, guiActiveUnfocused = true, externalToEVAOnly = true, guiName = "B1: Install [None]", unfocusedRange = 10f)] - public void LoadSetup() - { - if (!CheckResources()) - return; - var oldTemplate = curTemplate; - currentLoadout = displayLoadout; - SetupMenus(); - ScreenMessages.PostScreenMessage("Reconfiguration from " + oldTemplate + " to " + curTemplate + " completed.", 5f, - ScreenMessageStyle.UPPER_CENTER); - NextSetup(); - } - - [KSPEvent(active = true, guiActiveUnfocused = true, externalToEVAOnly = true, guiName = "B1: Next Loadout", unfocusedRange = 10f)] - public void NextSetup() - { - if (Loadouts.Count < 2) - return; - - displayLoadout++; - if (displayLoadout >= Loadouts.Count) - { - displayLoadout = 0; - } - if (displayLoadout == currentLoadout) - NextSetup(); - ChangeMenu(); - } - - [KSPEvent(active = true, guiActiveUnfocused = true, externalToEVAOnly = true, guiName = "B1: Prev. Loadout", unfocusedRange = 10f)] - public void PrevSetup() - { - if (Loadouts.Count < 2) - return; - - displayLoadout--; - if (displayLoadout < 0) - { - displayLoadout = Loadouts.Count - 1; - } - if (displayLoadout == currentLoadout) - PrevSetup(); - ChangeMenu(); - } - - private bool CheckResources() - { - if (HighLogic.LoadedSceneIsEditor) - return true; - var kerbal = FlightGlobals.ActiveVessel.rootPart.protoModuleCrew[0]; - if (!kerbal.HasEffect("RepairSkill")) - { - ScreenMessages.PostScreenMessage("Only Kerbals with repair skills (engineers, mechanics) can reconfigure modules!", 5f, - ScreenMessageStyle.UPPER_CENTER); - return false; - } - - var allResources = true; - var missingResources = ""; - //Check that we have everything we need. - var count = ResCosts.Count; - for (int i = 0; i < count; ++i) - { - var r = ResCosts[i]; - if (!HasResource(r)) - { - allResources = false; - missingResources += "\n" + r.Ratio + " " + r.ResourceName; - } - } - if (!allResources) - { - ScreenMessages.PostScreenMessage("Missing resources to change module:" + missingResources, 5f, - ScreenMessageStyle.UPPER_CENTER); - return false; - } - //Since everything is here... - for (int i = 0; i < count; ++i) - { - var r = ResCosts[i]; - TakeResources(r); - } - return true; - } - - private bool HasResource(ResourceRatio resInfo) - { - var resourceName = resInfo.ResourceName; - var needed = resInfo.Ratio; - var whpList = LogisticsTools.GetRegionalWarehouses(vessel, "USI_ModuleResourceWarehouse"); - //EC we're a lot less picky... - if (resInfo.ResourceName == "ElectricCharge") - { - whpList.AddRange(part.vessel.parts); - } - var count = whpList.Count; - for (int i = 0; i < count; ++i) - { - var whp = whpList[i]; - if (whp == part) - continue; - - if (resInfo.ResourceName != "ElectricCharge") - { - var wh = whp.FindModuleImplementing(); - if (wh != null) - { - if (!wh.localTransferEnabled) - continue; - } - } - if (whp.Resources.Contains(resourceName)) - { - var res = whp.Resources[resourceName]; - if (res.amount >= needed) - { - needed = 0; - break; - } - else - { - needed -= res.amount; - } - } - } - return (needed < ResourceUtilities.FLOAT_TOLERANCE); - } - - private void TakeResources(ResourceRatio resInfo) - { - var resourceName = resInfo.ResourceName; - var needed = resInfo.Ratio; - //Pull in from warehouses - - var whpList = LogisticsTools.GetRegionalWarehouses(vessel, "USI_ModuleResourceWarehouse"); - var count = whpList.Count; - for (int i = 0; i < count; ++i) - { - var whp = whpList[i]; - if (whp == part) - continue; - var wh = whp.FindModuleImplementing(); - if (wh != null) - { - if (!wh.localTransferEnabled) - continue; - } - if (whp.Resources.Contains(resourceName)) - { - var res = whp.Resources[resourceName]; - if (res.amount >= needed) - { - res.amount -= needed; - needed = 0; - break; - } - else - { - needed -= res.amount; - res.amount = 0; - } - } - } - } - - private void ChangeMenu() - { - Events["NextSetup"].guiName = (bayName + " Next " + typeName).Trim(); - Events["PrevSetup"].guiName = (bayName + " Prev. " + typeName).Trim(); - Fields["curTemplate"].guiName = (bayName + " Active " + typeName).Trim(); - if (Loadouts == null) - SetupLoadouts(); - - curTemplate = Loadouts[currentLoadout].LoadoutName; - Events["LoadSetup"].guiName = - (bayName + " " + curTemplate + "=>" + Loadouts[displayLoadout].LoadoutName).Trim(); - - MonoUtilities.RefreshContextWindows(part); - } - - private int displayLoadout; - public List ResCosts; - public List Loadouts; - //private IResourceBroker _broker; - private List _bays; - - public override void OnStart(StartState state) - { - //_broker = new ResourceBroker(); - _bays = part.FindModulesImplementing(); - if (autoActivate || HighLogic.LoadedSceneIsEditor) - SetModuleState(null, true); - GameEvents.OnAnimationGroupStateChanged.Add(SetModuleState); - MonoUtilities.RefreshContextWindows(part); - } - - public void OnDestroy() - { - GameEvents.OnAnimationGroupStateChanged.Remove(SetModuleState); - } - - private void SetModuleState(ModuleAnimationGroup module, bool enable) - { - if (module != null && module.part != part) - return; - - if (enable) - { - EnableMenus(HighLogic.LoadedSceneIsEditor); - SetupResourceCosts(); - SetupLoadouts(); - displayLoadout = currentLoadout; - SetupMenus(); - NextSetup(); - } - else - { - EnableMenus(false); - } - } - - private void EnableMenus(bool enable) - { - Events["NextSetup"].guiActiveEditor = enable; - Events["PrevSetup"].guiActiveEditor = enable; - Events["LoadSetup"].guiActiveEditor = enable; - Events["NextSetup"].externalToEVAOnly = !enable; - Events["PrevSetup"].externalToEVAOnly = !enable; - Events["LoadSetup"].externalToEVAOnly = !enable; - MonoUtilities.RefreshContextWindows(part); - } - - public void SetupMenus() - { - var modules = part.FindModulesImplementing(); - for (int i = 0; i < modules.Count; ++i) - { - if (EnabledByAnyModule(i)) - modules[i].EnableModule(); - else - modules[i].DisableModule(); - } - ChangeMenu(); - } - - private bool EnabledByAnyModule(int moduleId) - { - var modules = part.FindModulesImplementing(); - for (int i = 0; i < modules.Count; ++i) - { - if (modules[i].currentLoadout == moduleId) - return true; - } - return false; - } - - - private void SetupResourceCosts() - { - ResCosts = new List(); - if (String.IsNullOrEmpty(ResourceCosts)) - return; - - var resources = ResourceCosts.Split(','); - for (int i = 0; i < resources.Length; i += 2) - { - ResCosts.Add(new ResourceRatio - { - ResourceName = resources[i], - Ratio = double.Parse(resources[i + 1]) - }); - } - } - - private void SetupLoadouts() - { - //Get our Module List - Loadouts = new List(); - int id = 0; - var loadoutNames = new List(); - var mods = part.FindModulesImplementing(); - var count = mods.Count; - for (int i = 0; i < count; ++i) - { - var con = mods[i]; - var loadout = new LoadoutInfo(); - loadout.LoadoutName = con.ConverterName; - loadout.ModuleId = id; - loadoutNames.Add(con.ConverterName); - Loadouts.Add(loadout); - if (!con.IsActivated) - con.DisableModule(); - id++; - } - MonoUtilities.RefreshContextWindows(part); - } - - public override string GetInfo() - { - if (String.IsNullOrEmpty(ResourceCosts)) - return ""; - - var output = new StringBuilder("Resource Cost:\n\n"); - var resources = ResourceCosts.Split(','); - for (int i = 0; i < resources.Length; i += 2) - { - output.Append(string.Format("{0} {1}\n", double.Parse(resources[i + 1]), resources[i])); - } - return output.ToString(); - } - } -} \ No newline at end of file diff --git a/USITools/USITools/Converters/ModuleSwappableConverterNew.cs b/USITools/USITools/Converters/ModuleSwappableConverterNew.cs deleted file mode 100644 index f3cce27..0000000 --- a/USITools/USITools/Converters/ModuleSwappableConverterNew.cs +++ /dev/null @@ -1,265 +0,0 @@ -using System; -using System.Collections; -using System.Text; - -namespace USITools -{ - [Obsolete("Use ModuleSwappableBay instead.")] - public class ModuleSwappableConverterNew : PartModule - { - [KSPField] - public bool autoActivate = true; - - [KSPField] - public string bayName = ""; - - [KSPField] - public bool isConverter = false; - - [KSPField] - public int moduleIndex = 0; - - [KSPField(isPersistant = true)] - public int currentLoadout = 0; - - [KSPField(guiActive = true, guiActiveEditor = true, guiName = "Active: ")] - public string curTemplate = "???"; - - [KSPEvent(active = true, guiActiveEditor = true, guiActiveUnfocused = true, externalToEVAOnly = true, guiName = "B1: Install [None]",unfocusedRange = 10f)] - public void LoadSetup() - { - if (!CheckResources()) - return; - var oldTemplate = curTemplate; - currentLoadout = displayLoadout; - NextSetup(); - ScreenMessages.PostScreenMessage("Reconfiguration from " + oldTemplate + " to " + curTemplate + " completed.", 5f, - ScreenMessageStyle.UPPER_CENTER); - ConfigureLoadout(); - } - - [KSPEvent(active = true, guiActiveEditor = true, guiActiveUnfocused = true, externalToEVAOnly = true, guiName = "B1: Next Loadout",unfocusedRange = 10f)] - public void NextSetup() - { - if (_controller.Loadouts.Count < 2) - return; - - displayLoadout++; - if (displayLoadout >= _controller.Loadouts.Count) - { - displayLoadout = 0; - } - if (displayLoadout == currentLoadout) - { - NextSetup(); - } - - ChangeMenu(); - } - - [KSPEvent(active = true, guiActiveEditor = true, guiActiveUnfocused = true, externalToEVAOnly = true, guiName = "B1: Prev. Loadout", unfocusedRange = 10f)] - public void PrevSetup() - { - if (_controller.Loadouts.Count < 2) - return; - - displayLoadout--; - if (displayLoadout < 0) - { - displayLoadout = _controller.Loadouts.Count-1; - } - if (displayLoadout == currentLoadout) - { - PrevSetup(); - } - ChangeMenu(); - } - - private bool CheckResources() - { - if (HighLogic.LoadedSceneIsEditor) - return true; - var kerbal = FlightGlobals.ActiveVessel.rootPart.protoModuleCrew[0]; - if (!kerbal.HasEffect("RepairSkill")) - { - ScreenMessages.PostScreenMessage("Only Kerbals with repair skills (engineers, mechanics) can reconfigure modules!", 5f, - ScreenMessageStyle.UPPER_CENTER); - return false; - } - - var allResources = true; - var missingResources = ""; - //Check that we have everything we need. - var count = _controller.ResCosts.Count; - for(int i = 0; i < count; ++i) - { - var r = _controller.ResCosts[i]; - if (!HasResource(r)) - { - allResources = false; - missingResources += "\n" + r.Ratio + " " + r.ResourceName; - } - } - if (!allResources) - { - ScreenMessages.PostScreenMessage("Missing resources to change module:" + missingResources, 5f, - ScreenMessageStyle.UPPER_CENTER); - return false; - } - //Since everything is here... - for (int i = 0; i < count; ++i) - { - var r = _controller.ResCosts[i]; - TakeResources(r); - } - return true; - } - - private bool HasResource(ResourceRatio resInfo) - { - var resourceName = resInfo.ResourceName; - var needed = resInfo.Ratio; - var whpList = LogisticsTools.GetRegionalWarehouses(vessel, "USI_ModuleResourceWarehouse"); - //EC we're a lot less picky... - if (resInfo.ResourceName == "ElectricCharge") - { - whpList.AddRange(part.vessel.parts); - } - var count = whpList.Count; - for (int i = 0; i < count; ++i) - { - var whp = whpList[i]; - if(whp == part) - continue; - - if (resInfo.ResourceName != "ElectricCharge") - { - var wh = whp.FindModuleImplementing(); - if (wh != null) - { - if (!wh.localTransferEnabled) - continue; - } - } - if (whp.Resources.Contains(resourceName)) - { - var res = whp.Resources[resourceName]; - if (res.amount >= needed) - { - needed = 0; - break; - } - else - { - needed -= res.amount; - } - } - } - return (needed < ResourceUtilities.FLOAT_TOLERANCE); - } - - private void TakeResources(ResourceRatio resInfo) - { - var resourceName = resInfo.ResourceName; - var needed = resInfo.Ratio; - //Pull in from warehouses - - var whpList = LogisticsTools.GetRegionalWarehouses(vessel, "USI_ModuleResourceWarehouse"); - var count = whpList.Count; - for (int i = 0; i < count; ++i) - { - var whp = whpList[i]; - if (whp == part) - continue; - var wh = whp.FindModuleImplementing(); - if (wh != null) - { - if (!wh.localTransferEnabled) - continue; - } - if (whp.Resources.Contains(resourceName)) - { - var res = whp.Resources[resourceName]; - if (res.amount >= needed) - { - res.amount -= needed; - needed = 0; - break; - } - else - { - needed -= res.amount; - res.amount = 0; - } - } - } - } - - private bool _postLoad = false; - - public void FixedUpdate() - { - if (!_postLoad) - { - if (_controller.Loadouts.Count > 2) - { - _postLoad = true; - NextSetup(); - } - } - } - - public void ChangeMenu() - { - Events["NextSetup"].guiName = (bayName + " Next " + _controller.typeName).Trim(); - Events["PrevSetup"].guiName = (bayName + " Prev. " + _controller.typeName).Trim(); - Fields["curTemplate"].guiName = (bayName + " Active " + _controller.typeName).Trim(); - curTemplate = _controller.Loadouts[currentLoadout].ConverterName; - Events["LoadSetup"].guiName = - (bayName + " " + curTemplate + "=>" + _controller.Loadouts[displayLoadout].ConverterName).Trim(); - - MonoUtilities.RefreshContextWindows(part); - } - - private int displayLoadout; - private ModuleSwapControllerNew _controller; - - public override void OnStart(StartState state) - { - _controller = part.FindModuleImplementing(); - GameEvents.OnAnimationGroupStateChanged.Add(SetModuleState); - displayLoadout = currentLoadout; - ConfigureLoadout(); - } - - public void OnDestroy() - { - GameEvents.OnAnimationGroupStateChanged.Remove(SetModuleState); - } - - - private void SetModuleState(ModuleAnimationGroup module, bool enable) - { - if (module != null && module.part != part) - return; - - if (HighLogic.LoadedSceneIsFlight) - { - EnableMenus(enable); - } - } - - private void EnableMenus(bool enable) - { - Events["NextSetup"].active = enable; - Events["PrevSetup"].active = enable; - Events["LoadSetup"].active = enable; - MonoUtilities.RefreshContextWindows(part); - } - - private void ConfigureLoadout() - { - _controller.ApplyLoadout(currentLoadout, moduleIndex,isConverter); - } - } -} \ No newline at end of file diff --git a/USITools/USITools/USITools.csproj b/USITools/USITools/USITools.csproj index a85567f..ea8cbd5 100644 --- a/USITools/USITools/USITools.csproj +++ b/USITools/USITools/USITools.csproj @@ -64,8 +64,6 @@ - - @@ -96,7 +94,6 @@ - From c4379e0a87f406f500ba69931edff1368e8cc6bd Mon Sep 17 00:00:00 2001 From: doktorkrogg Date: Wed, 17 Oct 2018 20:42:55 -0400 Subject: [PATCH 10/15] More broken save game prevention Repurposed the old converter and swapping classes to accept old save game data to determine settings for the new classes; renamed all the new classes to have a USI_ prefix to make them easy to distinguish from the deprecated classes --- USITools/USITools/Converters/ConverterPart.cs | 2 +- .../Converters/ModuleResourceConverter_USI.cs | 182 +++++++----------- .../Converters/ModuleResourceHarvester_USI.cs | 138 +++++++------ .../Converters/ModuleSwappableConverter.cs | 9 + .../Converters/ModuleSwappableConverterNew.cs | 60 ++++++ ...apOption.cs => USI_ConverterSwapOption.cs} | 6 +- ...ion.cs => USI_EfficiencyPartSwapOption.cs} | 6 +- ...apOption.cs => USI_HarvesterSwapOption.cs} | 8 +- .../Converters/USI_ResourceConverter.cs | 141 ++++++++++++++ .../Converters/USI_ResourceHarvester.cs | 85 ++++++++ ...wapController.cs => USI_SwapController.cs} | 4 +- ...uleSwappableBay.cs => USI_SwappableBay.cs} | 20 +- USITools/USITools/USITools.csproj | 18 +- 13 files changed, 479 insertions(+), 200 deletions(-) create mode 100644 USITools/USITools/Converters/ModuleSwappableConverter.cs create mode 100644 USITools/USITools/Converters/ModuleSwappableConverterNew.cs rename USITools/USITools/Converters/{ModuleConverterSwapOption.cs => USI_ConverterSwapOption.cs} (78%) rename USITools/USITools/Converters/{ModuleEfficiencyPartSwapOption.cs => USI_EfficiencyPartSwapOption.cs} (79%) rename USITools/USITools/Converters/{ModuleHarvesterSwapOption.cs => USI_HarvesterSwapOption.cs} (53%) create mode 100644 USITools/USITools/Converters/USI_ResourceConverter.cs create mode 100644 USITools/USITools/Converters/USI_ResourceHarvester.cs rename USITools/USITools/Converters/{ModuleSwapController.cs => USI_SwapController.cs} (94%) rename USITools/USITools/Converters/{ModuleSwappableBay.cs => USI_SwappableBay.cs} (94%) diff --git a/USITools/USITools/Converters/ConverterPart.cs b/USITools/USITools/Converters/ConverterPart.cs index 3082da4..013ed1a 100644 --- a/USITools/USITools/Converters/ConverterPart.cs +++ b/USITools/USITools/Converters/ConverterPart.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace USITools.Converters +namespace USITools { [Obsolete] public class ConverterPart diff --git a/USITools/USITools/Converters/ModuleResourceConverter_USI.cs b/USITools/USITools/Converters/ModuleResourceConverter_USI.cs index 0d4c675..2e913f4 100644 --- a/USITools/USITools/Converters/ModuleResourceConverter_USI.cs +++ b/USITools/USITools/Converters/ModuleResourceConverter_USI.cs @@ -1,141 +1,107 @@ +using System; using System.Collections.Generic; -using USITools.KolonyTools; +using System.Linq; +using UnityEngine; namespace USITools { - public class ModuleResourceConverter_USI : - ModuleResourceConverter, - IEfficiencyBonusConsumer, - ISwappableConverter + [Obsolete("Use USI_ResourceConverter instead.")] + public class ModuleResourceConverter_USI : PartModule { - #region Fields and properties + [KSPField(isPersistant = true)] + public bool IsActivated; + + [KSPField(isPersistant = true)] + public bool hasBeenUpdated; + [KSPField] public double eMultiplier = 1d; [KSPField] public string eTag = ""; - public Dictionary BonusList { get; private set; } = - new Dictionary(); - - public float Governor = 1.0f; - private double _efficiencyMultiplier; - public double EfficiencyMultiplier - { - get - { - if (HighLogic.LoadedSceneIsEditor) - return _efficiencyMultiplier * Governor; - if (!IsActivated) - _efficiencyMultiplier = 0d; - return _efficiencyMultiplier * Governor; - } - set - { - _efficiencyMultiplier = value; - } - } - - public bool UseEfficiencyBonus - { - get - { - if (_swapOption != null) - return _swapOption.UseBonus; - else - return false; - } - } - - private AbstractSwapOption _swapOption; - #endregion - - public void Swap(AbstractSwapOption swapOption) - { - Swap(swapOption as AbstractSwapOption); - } - - public void Swap(AbstractSwapOption swapOption) - { - _swapOption = swapOption; - _swapOption.ApplyConverterChanges(this); - } - - public float GetEfficiencyBonus() - { - var totalBonus = 1f; - foreach (var bonus in BonusList) - { - totalBonus *= bonus.Value; - } - return totalBonus; - } + private ModuleResourceConverter_USI _presidingInstance; - public void SetEfficiencyBonus(string name, float value) - { - if (!BonusList.ContainsKey(name)) - BonusList.Add(name, value); - else - BonusList[name] = value; - } + public List PreviouslyActiveList { get; private set; } = new List(); - protected override void PreProcessing() + public override void OnStart(StartState state) { - base.PreProcessing(); - EfficiencyBonus = GetEfficiencyBonus(); - } + base.OnStart(state); - protected override ConversionRecipe PrepareRecipe(double deltatime) - { - var recipe = base.PrepareRecipe(deltatime); - if (!USI_DifficultyOptions.ConsumeMachineryEnabled && recipe != null) + // Determine which recipe each converter was previously resposible for, + // find which bay(s) are currently responsible for that recipe and + // activate the corresponding converter(s) if they were previously active. + if (_presidingInstance == this && !hasBeenUpdated) { - for (int i = recipe.Inputs.Count; i-- > 0;) - { - var input = recipe.Inputs[i]; - if (input.ResourceName == "Machinery") - recipe.Inputs.Remove(input); - } - for (int output = recipe.Outputs.Count; output-- > 0;) + var swapOptionCount = part.FindModulesImplementing().Count; + var converters = part.FindModulesImplementing(); + var bays = part.FindModulesImplementing(); + var controller = part.FindModuleImplementing(); + + if (controller == null) + Debug.LogError(string.Format("[USI] {0}: Trying to import old loadout(s) into a part with no USI_SwapController. Check the part config file.", GetType().Name)); + else if (swapOptionCount != PreviouslyActiveList.Count) + Debug.LogError(string.Format("[USI] {0}: Trying to import {1} old loadout(s) into a part with {2} swap option(s). Check the part config file.", GetType().Name, PreviouslyActiveList.Count, swapOptionCount)); + else { - var op = recipe.Outputs[output]; - if (op.ResourceName == "Recyclables") - recipe.Inputs.Remove(op); + // i will correspond to the loadout index + for (int i = 0; i < PreviouslyActiveList.Count; i++) + { + bool wasActive = PreviouslyActiveList[i]; + if (wasActive) + { + // Determine which bays (if any) are currently configured for this loadout + var configuredBays = bays.Where(b => b.currentLoadout == i); + if (configuredBays.Any()) + { + var bayIndexes = configuredBays.Select(b => b.moduleIndex); + foreach (var index in bayIndexes) + { + var converter = converters[index]; + converter.isEnabled = true; + converter.IsActivated = true; + } + } + } + } + + hasBeenUpdated = true; } } - return recipe; } - protected override void PostProcess(ConverterResults result, double deltaTime) + public override void OnLoad(ConfigNode node) { - base.PostProcess(result, deltaTime); - var hasLoad = false; - if (status != null) - { - hasLoad = status.EndsWith("Load"); - } + base.OnLoad(node); - - if (result.TimeFactor >= ResourceUtilities.FLOAT_TOLERANCE - && !hasLoad) + // The order these nodes are loaded by the game should match the loadout order + // of the swap options. So we should be able to use this to determine + // which recipes were active previously and thus which converters to re-activate. + if (_presidingInstance == null) { - statusPercent = 0d; //Force a reset of the load display. + // If there are other ModuleResourceConverter_USI instances on this part, + // see if any of them have a PreviouslyActiveList started yet. If so, then + // let that instance handle the remainder of this process. Otherwise, make this instance + // the presiding instance. + var otherInstances = part.FindModulesImplementing(); + if (otherInstances != null) + { + var candidate = otherInstances.Where(i => i.PreviouslyActiveList.Count > 0).FirstOrDefault(); + _presidingInstance = candidate ?? this; + } + else + _presidingInstance = this; } - if (_swapOption != null) - { - _swapOption.PostProcess(this, result, deltaTime); - } + _presidingInstance.PreviouslyActiveList.Add(IsActivated && isEnabled); } - public override string GetInfo() + public override void OnSave(ConfigNode node) { - return string.Empty; - } + if (_presidingInstance != null) + hasBeenUpdated = _presidingInstance.hasBeenUpdated; - public override string GetModuleDisplayName() - { - return GetType().Name; + base.OnSave(node); } } } diff --git a/USITools/USITools/Converters/ModuleResourceHarvester_USI.cs b/USITools/USITools/Converters/ModuleResourceHarvester_USI.cs index 9fab6e3..513d05a 100644 --- a/USITools/USITools/Converters/ModuleResourceHarvester_USI.cs +++ b/USITools/USITools/Converters/ModuleResourceHarvester_USI.cs @@ -1,85 +1,101 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; namespace USITools { - public class ModuleResourceHarvester_USI : - ModuleResourceHarvester, - IEfficiencyBonusConsumer, - ISwappableConverter + [Obsolete("Use USI_ResourceHarvester instead.")] + public class ModuleResourceHarvester_USI : PartModule { - #region Fields and properties - public Dictionary BonusList { get; private set; } = - new Dictionary(); + [KSPField(isPersistant = true)] + public bool IsActivated; - public bool UseEfficiencyBonus - { - get - { - if (_swapOption != null) - return _swapOption.UseBonus; - else - return false; - } - } + [KSPField(isPersistant = true)] + public bool hasBeenUpdated; - private AbstractSwapOption _swapOption; - #endregion + private ModuleResourceHarvester_USI _presidingInstance; - public void Swap(AbstractSwapOption swapOption) - { - Swap(swapOption as AbstractSwapOption); - } + public List PreviouslyActiveList { get; private set; } = new List(); - public void Swap(AbstractSwapOption swapOption) + public override void OnStart(StartState state) { - _swapOption = swapOption; - _swapOption.ApplyConverterChanges(this); - } + base.OnStart(state); - public float GetEfficiencyBonus() - { - var totalBonus = 1f; - foreach (var bonus in BonusList) + // Determine which recipe each harvester was previously resposible for, + // find which bay(s) are currently responsible for that recipe and + // activate the corresponding harvester(s) if they were previously active. + if (_presidingInstance == this && !hasBeenUpdated) { - totalBonus *= bonus.Value; + var swapOptionCount = part.FindModulesImplementing().Count; + var harvesters = part.FindModulesImplementing(); + var bays = part.FindModulesImplementing(); + var controller = part.FindModuleImplementing(); + + if (controller == null) + Debug.LogError(string.Format("[USI] {0}: Trying to import old loadout(s) into a part with no USI_SwapController. Check the part config file.", GetType().Name)); + else if (swapOptionCount != PreviouslyActiveList.Count) + Debug.LogError(string.Format("[USI] {0}: Trying to import {1} old loadout(s) into a part with {2} swap option(s). Check the part config file.", GetType().Name, PreviouslyActiveList.Count, swapOptionCount)); + else + { + // i will correspond to the loadout index + for (int i = 0; i < PreviouslyActiveList.Count; i++) + { + bool wasActive = PreviouslyActiveList[i]; + if (wasActive) + { + // Determine which bays (if any) are currently configured for this loadout + var configuredBays = bays.Where(b => b.currentLoadout == i); + if (configuredBays.Any()) + { + var bayIndexes = configuredBays.Select(b => b.moduleIndex); + foreach (var index in bayIndexes) + { + var harvester = harvesters[index]; + harvester.isEnabled = true; + harvester.IsActivated = true; + } + } + } + } + + hasBeenUpdated = true; + } } - return totalBonus; } - public void SetEfficiencyBonus(string name, float value) + public override void OnLoad(ConfigNode node) { - if (!BonusList.ContainsKey(name)) - BonusList.Add(name, value); - else - BonusList[name] = value; - } + base.OnLoad(node); - public override string GetInfo() - { - return string.Empty; - } + // The order these nodes are loaded by the game should match the loadout order + // of the swap options. So we should be able to use this to determine + // which recipes were active previously and thus which converters to re-activate. + if (_presidingInstance == null) + { + // If there are other ModuleResourceHarvester_USI instances on this part, + // see if any of them have a PreviouslyActiveList started yet. If so, then + // let that instance handle the remainder of this process. Otherwise, make this instance + // the presiding instance. + var otherInstances = part.FindModulesImplementing(); + if (otherInstances != null) + { + var candidate = otherInstances.Where(i => i.PreviouslyActiveList.Count > 0).FirstOrDefault(); + _presidingInstance = candidate ?? this; + } + else + _presidingInstance = this; + } - protected override void PreProcessing() - { - base.PreProcessing(); - EfficiencyBonus = GetEfficiencyBonus(); + _presidingInstance.PreviouslyActiveList.Add(IsActivated && isEnabled); } - protected override void PostProcess(ConverterResults result, double deltaTime) + public override void OnSave(ConfigNode node) { - base.PostProcess(result, deltaTime); - - var hasLoad = false; - if (status != null) - { - hasLoad = status.EndsWith("Load"); - } + if (_presidingInstance != null) + hasBeenUpdated = _presidingInstance.hasBeenUpdated; - if (result.TimeFactor >= ResourceUtilities.FLOAT_TOLERANCE - && !hasLoad) - { - statusPercent = 0d; //Force a reset of the load display. - } + base.OnSave(node); } } } diff --git a/USITools/USITools/Converters/ModuleSwappableConverter.cs b/USITools/USITools/Converters/ModuleSwappableConverter.cs new file mode 100644 index 0000000..211dabc --- /dev/null +++ b/USITools/USITools/Converters/ModuleSwappableConverter.cs @@ -0,0 +1,9 @@ +using System; + +namespace USITools +{ + [Obsolete("Use ModuleSwappableBay instead.")] + public class ModuleSwappableConverter : ModuleSwappableConverterNew + { + } +} diff --git a/USITools/USITools/Converters/ModuleSwappableConverterNew.cs b/USITools/USITools/Converters/ModuleSwappableConverterNew.cs new file mode 100644 index 0000000..733000f --- /dev/null +++ b/USITools/USITools/Converters/ModuleSwappableConverterNew.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace USITools +{ + [Obsolete("Use ModuleSwappableBay instead.")] + public class ModuleSwappableConverterNew : PartModule + { + [KSPField] + public string bayName = ""; + + [KSPField(isPersistant = true)] + public int currentLoadout = 0; + + [KSPField(isPersistant = true)] + public bool hasBeenUpdated; + + private List oldLoadouts = new List(); + + public override void OnStart(StartState state) + { + base.OnStart(state); + + // Import loadouts from the old ModuleSwappableConverterNew class into the new USI_SwappableBay class. + if (!hasBeenUpdated) + { + var bays = part.FindModulesImplementing(); + var controller = part.FindModuleImplementing(); + + if (controller == null) + Debug.LogError("[USI] ModuleSwappableConverter(New): Trying to import old loadout(s) into a part with no USI_SwapController. Check the part config file."); + else if (bays.Count < oldLoadouts.Count) + Debug.LogError(string.Format("[USI] ModuleSwappableConverter(New): Trying to import {0} old loadout(s) into a part with {1} bay(s). Check the part config file.", oldLoadouts.Count, bays.Count)); + else + { + for (int i = 0; i < oldLoadouts.Count; i++) + { + var bay = bays.Where(b => b.moduleIndex == i).FirstOrDefault(); + if (bay != null) + { + bay.currentLoadout = oldLoadouts[i]; + controller.ApplyLoadout(bay.currentLoadout, i); + } + } + + hasBeenUpdated = true; + } + } + } + + public override void OnLoad(ConfigNode node) + { + base.OnLoad(node); + + oldLoadouts.Add(currentLoadout); + } + } +} diff --git a/USITools/USITools/Converters/ModuleConverterSwapOption.cs b/USITools/USITools/Converters/USI_ConverterSwapOption.cs similarity index 78% rename from USITools/USITools/Converters/ModuleConverterSwapOption.cs rename to USITools/USITools/Converters/USI_ConverterSwapOption.cs index ec36cff..4d1983c 100644 --- a/USITools/USITools/Converters/ModuleConverterSwapOption.cs +++ b/USITools/USITools/Converters/USI_ConverterSwapOption.cs @@ -1,9 +1,9 @@ namespace USITools { - public class ModuleConverterSwapOption - : AbstractSwapOption + public class USI_ConverterSwapOption + : AbstractSwapOption { - public override void ApplyConverterChanges(ModuleResourceConverter_USI converter) + public override void ApplyConverterChanges(USI_ResourceConverter converter) { converter.inputList.Clear(); converter.outputList.Clear(); diff --git a/USITools/USITools/Converters/ModuleEfficiencyPartSwapOption.cs b/USITools/USITools/Converters/USI_EfficiencyPartSwapOption.cs similarity index 79% rename from USITools/USITools/Converters/ModuleEfficiencyPartSwapOption.cs rename to USITools/USITools/Converters/USI_EfficiencyPartSwapOption.cs index 211fe81..f06f93f 100644 --- a/USITools/USITools/Converters/ModuleEfficiencyPartSwapOption.cs +++ b/USITools/USITools/Converters/USI_EfficiencyPartSwapOption.cs @@ -1,6 +1,6 @@ namespace USITools { - public class ModuleEfficiencyPartSwapOption : ModuleConverterSwapOption + public class USI_EfficiencyPartSwapOption : USI_ConverterSwapOption { [KSPField] public double eMultiplier = 1d; @@ -8,7 +8,7 @@ public class ModuleEfficiencyPartSwapOption : ModuleConverterSwapOption [KSPField] public string eTag = ""; - public override void ApplyConverterChanges(ModuleResourceConverter_USI converter) + public override void ApplyConverterChanges(USI_ResourceConverter converter) { UseBonus = false; // efficiency parts should not use bonuses from other efficiency parts! converter.eMultiplier = eMultiplier; @@ -28,7 +28,7 @@ public override string GetInfo() "Boost power: " + eMultiplier.ToString() + resourceConsumption; } - public override void PostProcess(ModuleResourceConverter_USI converter, ConverterResults result, double deltaTime) + public override void PostProcess(USI_ResourceConverter converter, ConverterResults result, double deltaTime) { base.PostProcess(result, deltaTime); converter.EfficiencyMultiplier = result.TimeFactor / deltaTime; diff --git a/USITools/USITools/Converters/ModuleHarvesterSwapOption.cs b/USITools/USITools/Converters/USI_HarvesterSwapOption.cs similarity index 53% rename from USITools/USITools/Converters/ModuleHarvesterSwapOption.cs rename to USITools/USITools/Converters/USI_HarvesterSwapOption.cs index 21d8020..ed30bbf 100644 --- a/USITools/USITools/Converters/ModuleHarvesterSwapOption.cs +++ b/USITools/USITools/Converters/USI_HarvesterSwapOption.cs @@ -3,12 +3,12 @@ namespace USITools { [Obsolete("Use a class derived from AbstractSwapOption instead.")] - public class ModuleSwapOption : ModuleHarvesterSwapOption { } + public class ModuleSwapOption : USI_HarvesterSwapOption { } - public class ModuleHarvesterSwapOption - : AbstractSwapOption + public class USI_HarvesterSwapOption + : AbstractSwapOption { - public override void ApplyConverterChanges(ModuleResourceHarvester_USI converter) + public override void ApplyConverterChanges(USI_ResourceHarvester converter) { converter.Efficiency = Efficiency; converter.ResourceName = ResourceName; diff --git a/USITools/USITools/Converters/USI_ResourceConverter.cs b/USITools/USITools/Converters/USI_ResourceConverter.cs new file mode 100644 index 0000000..e46451e --- /dev/null +++ b/USITools/USITools/Converters/USI_ResourceConverter.cs @@ -0,0 +1,141 @@ +using System.Collections.Generic; +using USITools.KolonyTools; + +namespace USITools +{ + public class USI_ResourceConverter : + ModuleResourceConverter, + IEfficiencyBonusConsumer, + ISwappableConverter + { + #region Fields and properties + [KSPField] + public double eMultiplier = 1d; + + [KSPField] + public string eTag = ""; + + public Dictionary BonusList { get; private set; } = + new Dictionary(); + + public float Governor = 1.0f; + private double _efficiencyMultiplier; + public double EfficiencyMultiplier + { + get + { + if (HighLogic.LoadedSceneIsEditor) + return _efficiencyMultiplier * Governor; + if (!IsActivated) + _efficiencyMultiplier = 0d; + return _efficiencyMultiplier * Governor; + } + set + { + _efficiencyMultiplier = value; + } + } + + public bool UseEfficiencyBonus + { + get + { + if (_swapOption != null) + return _swapOption.UseBonus; + else + return false; + } + } + + private AbstractSwapOption _swapOption; + #endregion + + public void Swap(AbstractSwapOption swapOption) + { + Swap(swapOption as AbstractSwapOption); + } + + public void Swap(AbstractSwapOption swapOption) + { + _swapOption = swapOption; + _swapOption.ApplyConverterChanges(this); + } + + public float GetEfficiencyBonus() + { + var totalBonus = 1f; + foreach (var bonus in BonusList) + { + totalBonus *= bonus.Value; + } + return totalBonus; + } + + public void SetEfficiencyBonus(string name, float value) + { + if (!BonusList.ContainsKey(name)) + BonusList.Add(name, value); + else + BonusList[name] = value; + } + + protected override void PreProcessing() + { + base.PreProcessing(); + EfficiencyBonus = GetEfficiencyBonus(); + } + + protected override ConversionRecipe PrepareRecipe(double deltatime) + { + var recipe = base.PrepareRecipe(deltatime); + if (!USI_DifficultyOptions.ConsumeMachineryEnabled && recipe != null) + { + for (int i = recipe.Inputs.Count; i-- > 0;) + { + var input = recipe.Inputs[i]; + if (input.ResourceName == "Machinery") + recipe.Inputs.Remove(input); + } + for (int output = recipe.Outputs.Count; output-- > 0;) + { + var op = recipe.Outputs[output]; + if (op.ResourceName == "Recyclables") + recipe.Inputs.Remove(op); + } + } + return recipe; + } + + protected override void PostProcess(ConverterResults result, double deltaTime) + { + base.PostProcess(result, deltaTime); + var hasLoad = false; + if (status != null) + { + hasLoad = status.EndsWith("Load"); + } + + + if (result.TimeFactor >= ResourceUtilities.FLOAT_TOLERANCE + && !hasLoad) + { + statusPercent = 0d; //Force a reset of the load display. + } + + if (_swapOption != null) + { + _swapOption.PostProcess(this, result, deltaTime); + } + } + + public override string GetInfo() + { + return string.Empty; + } + + public override string GetModuleDisplayName() + { + return GetType().Name; + } + } +} diff --git a/USITools/USITools/Converters/USI_ResourceHarvester.cs b/USITools/USITools/Converters/USI_ResourceHarvester.cs new file mode 100644 index 0000000..28b5a25 --- /dev/null +++ b/USITools/USITools/Converters/USI_ResourceHarvester.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; + +namespace USITools +{ + public class USI_ResourceHarvester : + ModuleResourceHarvester, + IEfficiencyBonusConsumer, + ISwappableConverter + { + #region Fields and properties + public Dictionary BonusList { get; private set; } = + new Dictionary(); + + public bool UseEfficiencyBonus + { + get + { + if (_swapOption != null) + return _swapOption.UseBonus; + else + return false; + } + } + + private AbstractSwapOption _swapOption; + #endregion + + public void Swap(AbstractSwapOption swapOption) + { + Swap(swapOption as AbstractSwapOption); + } + + public void Swap(AbstractSwapOption swapOption) + { + _swapOption = swapOption; + _swapOption.ApplyConverterChanges(this); + } + + public float GetEfficiencyBonus() + { + var totalBonus = 1f; + foreach (var bonus in BonusList) + { + totalBonus *= bonus.Value; + } + return totalBonus; + } + + public void SetEfficiencyBonus(string name, float value) + { + if (!BonusList.ContainsKey(name)) + BonusList.Add(name, value); + else + BonusList[name] = value; + } + + public override string GetInfo() + { + return string.Empty; + } + + protected override void PreProcessing() + { + base.PreProcessing(); + EfficiencyBonus = GetEfficiencyBonus(); + } + + protected override void PostProcess(ConverterResults result, double deltaTime) + { + base.PostProcess(result, deltaTime); + + var hasLoad = false; + if (status != null) + { + hasLoad = status.EndsWith("Load"); + } + + if (result.TimeFactor >= ResourceUtilities.FLOAT_TOLERANCE + && !hasLoad) + { + statusPercent = 0d; //Force a reset of the load display. + } + } + } +} diff --git a/USITools/USITools/Converters/ModuleSwapController.cs b/USITools/USITools/Converters/USI_SwapController.cs similarity index 94% rename from USITools/USITools/Converters/ModuleSwapController.cs rename to USITools/USITools/Converters/USI_SwapController.cs index 714eb4b..26000cf 100644 --- a/USITools/USITools/Converters/ModuleSwapController.cs +++ b/USITools/USITools/Converters/USI_SwapController.cs @@ -5,9 +5,9 @@ namespace USITools { [Obsolete("Use ModuleSwapController instead.")] - public class ModuleSwapControllerNew : ModuleSwapController { } + public class ModuleSwapControllerNew : USI_SwapController { } - public class ModuleSwapController : PartModule + public class USI_SwapController : PartModule { [KSPField] public string ResourceCosts = ""; diff --git a/USITools/USITools/Converters/ModuleSwappableBay.cs b/USITools/USITools/Converters/USI_SwappableBay.cs similarity index 94% rename from USITools/USITools/Converters/ModuleSwappableBay.cs rename to USITools/USITools/Converters/USI_SwappableBay.cs index 19bb946..5226200 100644 --- a/USITools/USITools/Converters/ModuleSwappableBay.cs +++ b/USITools/USITools/Converters/USI_SwappableBay.cs @@ -1,14 +1,8 @@ -using System; +using UnityEngine; -namespace USITools.Converters +namespace USITools { - [Obsolete("Use ModuleSwappableBay instead.")] - public class ModuleSwappableConverter : ModuleSwappableBay { } - - [Obsolete("Use ModuleSwappableBay instead.")] - public class ModuleSwappableConverterNew : ModuleSwappableBay { } - - public class ModuleSwappableBay : PartModule + public class USI_SwappableBay : PartModule { #region KSP Fields and Events [KSPField] @@ -84,12 +78,16 @@ public void PrevSetup() #region Fields and properties private bool _postLoad = false; private int displayLoadout; - private ModuleSwapController _controller; + private USI_SwapController _controller; #endregion public override void OnStart(StartState state) { - _controller = part.FindModuleImplementing(); + _controller = part.FindModuleImplementing(); + if (_controller == null) + { + Debug.LogError(string.Format("[USI] {0}: USI_SwappableBay modules require a USI_SwapController module. Check the part config file.", GetType().Name)); + } GameEvents.OnAnimationGroupStateChanged.Add(SetModuleState); displayLoadout = currentLoadout; ConfigureLoadout(); diff --git a/USITools/USITools/USITools.csproj b/USITools/USITools/USITools.csproj index ea8cbd5..9b6f246 100644 --- a/USITools/USITools/USITools.csproj +++ b/USITools/USITools/USITools.csproj @@ -55,15 +55,19 @@ - + + + + + - - + + - + @@ -90,11 +94,11 @@ - - + + - + From 8e22f0143eb674c54cd502a89a8b0c1561127c87 Mon Sep 17 00:00:00 2001 From: Bob Palmer Date: Sat, 20 Oct 2018 10:46:02 -0400 Subject: [PATCH 11/15] Changes for KSP 1.5.0 --- .../GameData/000_USITools/CHANGELOG.txt | 8 ++++++++ .../GameData/000_USITools/USITools.dll | Bin 111104 -> 111104 bytes .../GameData/000_USITools/USITools.dll.mdb | Bin 61193 -> 61237 bytes .../GameData/000_USITools/USITools.pdb | Bin 316928 -> 316928 bytes .../GameData/000_USITools/USITools.version | 12 ++++++------ .../.vs/USITools/DesignTimeBuild/.dtbcache | Bin 0 -> 178 bytes .../USITools/v15/Server/sqlite3/storage.ide | Bin 864256 -> 868352 bytes USITools/USITools/USITools.csproj | 8 ++++---- 8 files changed, 18 insertions(+), 10 deletions(-) create mode 100644 USITools/.vs/USITools/DesignTimeBuild/.dtbcache diff --git a/FOR_RELEASE/GameData/000_USITools/CHANGELOG.txt b/FOR_RELEASE/GameData/000_USITools/CHANGELOG.txt index 40faf6f..1d37ac2 100644 --- a/FOR_RELEASE/GameData/000_USITools/CHANGELOG.txt +++ b/FOR_RELEASE/GameData/000_USITools/CHANGELOG.txt @@ -1,3 +1,11 @@ +1.0.0 - 2018.10.20 +------------------ +KSP 1.5.0 Compatibility + +0.13.0 - 2018.06.22 +------------------ +KSP 1.4.4 Compatibility + 0.12.0 - 2018.03.13 ------------------ KSP 1.4.1 Compatibility diff --git a/FOR_RELEASE/GameData/000_USITools/USITools.dll b/FOR_RELEASE/GameData/000_USITools/USITools.dll index 12b4439e166e95f20036f3fe8f290537f8b42b6c..e1dcc8fc466c80f3349c4625af86ae509a784c40 100644 GIT binary patch delta 22006 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 delta 22162 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

a_cxdsaxJ%IaMhQZA%#f`Pn(QrUq z?QlXqApa4y6d$D07WyHZh!H>N#~2!T;*Rs~b(}Y;Q(D9e@EH#VgY*;+`~ve|;P!`< zV;7v~RG;T$e#o>@RN;%5O_39)oY&T@(FtdqXdM z3BC+8Tg=B^vqfq=zGJVOp3~9LsGE!veL!49*KZ16lnSX&B+=@SFpo82cYKV8YmF$# zP4uXREjj5P7IwwLPSiql^k`Hn$SwD{46k;04(Sx6p3-rA8?p-WGADWT)1Ql);xR-| z`S6sIto5>|M303%`Sh%XP5853E%-#g?y*Mhg{#Rd(PMG5)F!+)n1z*sQ3n0);rgr? z1L8hc*n>tfJ)H9PRCc`_Djl@$_E?BnJ?POW==a@5@pd+C*0UqJ${A7tzOUCP=I5P| zui%pT(c@QKGJkpe0Wo2L#$S1{T!CLo4tiX{s6vca;7U}8u^!q7MH@%yodHYncnBSi znF;0LyGt~1cZr7S&Kw=)e~R!H9rcAqZ!y$XXzV3Xp_oN~d}-4xQt-g<#hTu{5SH`Q zR*4=yU2zBdN88hY|LIIrbFcG|o_WwCj@xl;;ef~wX^_LY9gf~!LVeOP z3xE8{e;jZ0xJV7z-*~3t%Kr&n>6o$7WERIuHX!X){F7;ju;pGiT^83cE4BE?g;jW} zl*gxfxg{?%<@;84hF2}*;v2oS@Q@F_?kRW0*Zi{Bi7XztP|)ICqVH4~>s`qFLgu@r zYF(CaOEpPVQXxARBWAct(!4k7DZ6X*9rc;mf&85pAWpJq2_M1 zO!=#o;^U8aU)J}*z}&jJ4#B5!nVgHd;EmUA_`rURbTIW#?={lfO4Q9@S7B1UTrY*i z)yr$7iQ%C>8>J(7(r=W$FSSW)q~ob6K4#H}PoBAW=FpjN3d_S0)ZOPXj{7l=yB6#| zulqDgzI+{Z9ZTy;4&$2srJOB3*YuZ)zwtRBe;oaT&lrvWLA1fw0>=Kh_m3B}_m3B} z-!J6T;05Nqz#I$5LyPt&<+Yci&N}&~en?~k(w@P$eTBhV z7U+kkIPNv$bXRil!bg0jv%+PremsTBPRPCVJ8@CpWX?^gqkgF0O=&>LNWUcz5c9O( zWk|v|5;AZXiyQA(3p9m zF+~i)i{~j~IcIvDTwz`BpKplq+wMP39%@_fUuL)`{igpqmUcIM7~^3!%Xu9`k>1Wv zGH>GaZQ@z&XBg%+$K20wuI(G2{)D2V_GZ!@d`-mqLUAYCn8HhaT5$qU;X>f-+}>r$2k;0qT4^-PVF0peLioQLbNsI~E0iPt6 zg;c^Udl|O}hbVB$HUv3O#8zYbs?E*qB}mZ$rcF#mDCrDA>K`z_Sco>3Ct}OOD&c~C z2+}8F-AGX~UWm9jw~g9MnARhGBDRU!BAnVWNKx}cAs$Z%Pil&&gz%*LNa8JyaUk8r zRJ5VCXfql5$IkB{iO6;k_bRv|qLH49C`H;MvObP_H8F+wS7AeBOg#09iRWHZB@t&l zgM3#+Q~cIll8BZ>=lM-K5sY6qb>b!0sKV4B{M#{oHqwyne57+@ijX$=bRvEU(I2?2 z@>M#apeypvX7(h_>^?}_6_MuNJfb4=aikL4jJ1rwb~)Qw5=8B??9oWa6qZxl)f#Zy zuPwQvPa&mGe5qZPMBV=MC;I=|&f;KHr`K>h(?*uNbNgvOR~3SZUQVPJi)er^QYIsP zK7!ic#S<;|rw${^rXg+5?OYGyxbukm`p-nF>ZtVp$&QW;&Hu|FvZH3j(rD9|>dn-S zOr-E1@?L^;*Vf)1Vyi6H&iC>X@gja}hL;!?y>)qtNQ>QiX(qUjFW(#AmwC-17qRp_R>FVyXX~(OpMXtk1U%Hc zbMFJzKNszu;D3+e{`9tMPQyc6b!&xD1l-uqKhjVy2snW4=-DwrLN`DA9nm;{>{?N{ X)pwovmn_BQZ#8egzXO`x&g1_8#|u+R diff --git a/FOR_RELEASE/GameData/000_USITools/USITools.dll.mdb b/FOR_RELEASE/GameData/000_USITools/USITools.dll.mdb index 9aa9e79f398f148110fa2013f6e7752b7856ef32..71cc9761886d5c962c69fd8dd434882bb1f1ea2b 100644 GIT binary patch delta 7247 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 diff --git a/FOR_RELEASE/GameData/000_USITools/USITools.pdb b/FOR_RELEASE/GameData/000_USITools/USITools.pdb index 3bf118b50f42ce291c3a1b6cd15afb7188f16430..14ad5924b52b88f01347301e5f1756da4d68ca27 100644 GIT binary patch delta 17251 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 JPHWZN{~rmx4PyWR delta 17275 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 ZSMT<|Wt6~5zU!1FR`}}=PFZSt{2u`ce>wmF diff --git a/FOR_RELEASE/GameData/000_USITools/USITools.version b/FOR_RELEASE/GameData/000_USITools/USITools.version index 1ec62e5..2fb026c 100644 --- a/FOR_RELEASE/GameData/000_USITools/USITools.version +++ b/FOR_RELEASE/GameData/000_USITools/USITools.version @@ -8,24 +8,24 @@ "ALLOW_PRE_RELEASE":false }, "VERSION":{ - "MAJOR":0, - "MINOR":12, + "MAJOR":1, + "MINOR":0, "PATCH":0, "BUILD":0 }, "KSP_VERSION":{ "MAJOR":1, - "MINOR":4, - "PATCH":1 + "MINOR":5, + "PATCH":0 }, "KSP_VERSION_MIN":{ "MAJOR":1, - "MINOR":4, + "MINOR":5, "PATCH":0 }, "KSP_VERSION_MAX":{ "MAJOR":1, - "MINOR":4, + "MINOR":5, "PATCH":9 } } \ No newline at end of file diff --git a/USITools/.vs/USITools/DesignTimeBuild/.dtbcache b/USITools/.vs/USITools/DesignTimeBuild/.dtbcache new file mode 100644 index 0000000000000000000000000000000000000000..bc0109fc2881a175837bf80c5c4a7595f3402d2f GIT binary patch literal 178 zcmZQ)U|=w0FlEqVFb852hVqwZlf8gqE(}%-F+j42A)lduAqhxU0QsQ|!3>@Zx(x0> zG6c-e14uL}fW06dW*`v3p{ literal 0 HcmV?d00001 diff --git a/USITools/.vs/USITools/v15/Server/sqlite3/storage.ide b/USITools/.vs/USITools/v15/Server/sqlite3/storage.ide index 7443b6bde57d01c29000d37584467060a773b2d8..d92ba3dc5861009de4f1a6dfec0a6715b0290272 100644 GIT binary patch delta 27641 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 diff --git a/USITools/USITools/USITools.csproj b/USITools/USITools/USITools.csproj index 57f67c6..7d4c1b6 100644 --- a/USITools/USITools/USITools.csproj +++ b/USITools/USITools/USITools.csproj @@ -32,11 +32,11 @@ - ..\..\..\..\KSP_DEV\KSP_Data\Managed\Assembly-CSharp.dll + ..\..\..\..\KSP_DEV\KSP_x64_Data\Managed\Assembly-CSharp.dll False - ..\..\..\..\KSP_DEV\KSP_Data\Managed\Assembly-CSharp-firstpass.dll + ..\..\..\..\KSP_DEV\KSP_x64_Data\Managed\Assembly-CSharp-firstpass.dll False @@ -46,11 +46,11 @@ - ..\..\..\..\KSP_DEV\KSP_Data\Managed\UnityEngine.dll + ..\..\..\..\KSP_DEV\KSP_x64_Data\Managed\UnityEngine.dll False - ..\..\..\..\KSP_DEV\KSP_Data\Managed\UnityEngine.UI.dll + ..\..\..\..\KSP_DEV\KSP_x64_Data\Managed\UnityEngine.UI.dll False From 5a764e5e05567edc4a43fe0ca564add27dcee4c7 Mon Sep 17 00:00:00 2001 From: doktorkrogg Date: Mon, 29 Oct 2018 17:20:02 -0400 Subject: [PATCH 12/15] Swappable converters overhaul * Created new USI_SwapController class to keep track of available converters and swap options and to instruct converters to change their loadout. * Created new USI_SwappableBay class to handle the UI interactions related to swapping converter loadouts. * Created new AbstractSwapOption and derived classes to allow different converter recipes to be defined in the part config and applied to the converter at runtime. * Created new AbstractConverterAddon and derived classes to allow AbstractSwapOptions to tack side effects onto a converter recipe. * Created new ISwappableConverter interface that allows converter classes to participate in new swap mechanics. * Created new IConverterWithAddons interface to allow converter classes to produce side effects via addons. * Created new USI_Converter and USI_Harvester classes to replace old converter wrapper classes. * Deprecated old converter and swap-related classes. * Added in-game settings to control converter swap costs, EVA requirements and RepairSkill requirements. * Lots of little refactorings to make code easier to follow. --- .../GameData/000_USITools/AddConsumers.cfg | 11 +- .../GameData/000_USITools/Logistics.cfg | 10 +- .../Converters/AbstractConverterAddon.cs | 25 +++ .../USITools/Converters/AbstractSwapOption.cs | 69 ++++-- USITools/USITools/Converters/ConverterPart.cs | 21 -- .../USITools/Converters/DeprecatedClasses.cs | 28 +++ .../Extensions/ConverterExtensionMethods.cs | 84 +++++++ .../Interfaces/IConverterWithAddons.cs | 15 ++ .../Interfaces/IEfficiencyBonusConsumer.cs | 9 - .../Interfaces/ISwappableConverter.cs | 1 + USITools/USITools/Converters/LoadoutInfo.cs | 13 -- .../Converters/ModuleResourceConverter_USI.cs | 107 --------- .../Converters/ModuleResourceHarvester_USI.cs | 101 --------- .../Converters/ModuleSwappableConverter.cs | 9 - .../Converters/ModuleSwappableConverterNew.cs | 60 ----- USITools/USITools/Converters/USI_Converter.cs | 114 ++++++++++ .../Converters/USI_ConverterOptions.cs | 97 ++++++++ .../Converters/USI_ConverterSwapOption.cs | 51 ++++- .../Converters/USI_EfficiencyBoosterAddon.cs | 35 +++ .../USI_EfficiencyBoosterSwapOption.cs | 36 +++ .../USI_EfficiencyConsumerAddons.cs | 95 ++++++++ .../USI_EfficiencyPartSwapOption.cs | 37 ---- USITools/USITools/Converters/USI_Harvester.cs | 111 ++++++++++ .../Converters/USI_HarvesterSwapOption.cs | 25 ++- .../Converters/USI_ResourceConverter.cs | 141 ------------ .../Converters/USI_ResourceHarvester.cs | 85 ------- .../USITools/Converters/USI_SwapController.cs | 21 +- .../USITools/Converters/USI_SwappableBay.cs | 208 ++++++++++++------ .../USITools/Logistics/LogisticsConfig.cs | 1 - USITools/USITools/USITools.csproj | 20 +- USITools/USITools/USI_DifficultyOptions.cs | 10 +- 31 files changed, 940 insertions(+), 710 deletions(-) create mode 100644 USITools/USITools/Converters/AbstractConverterAddon.cs delete mode 100644 USITools/USITools/Converters/ConverterPart.cs create mode 100644 USITools/USITools/Converters/DeprecatedClasses.cs create mode 100644 USITools/USITools/Converters/Extensions/ConverterExtensionMethods.cs create mode 100644 USITools/USITools/Converters/Interfaces/IConverterWithAddons.cs delete mode 100644 USITools/USITools/Converters/Interfaces/IEfficiencyBonusConsumer.cs delete mode 100644 USITools/USITools/Converters/LoadoutInfo.cs delete mode 100644 USITools/USITools/Converters/ModuleResourceConverter_USI.cs delete mode 100644 USITools/USITools/Converters/ModuleResourceHarvester_USI.cs delete mode 100644 USITools/USITools/Converters/ModuleSwappableConverter.cs delete mode 100644 USITools/USITools/Converters/ModuleSwappableConverterNew.cs create mode 100644 USITools/USITools/Converters/USI_Converter.cs create mode 100644 USITools/USITools/Converters/USI_ConverterOptions.cs create mode 100644 USITools/USITools/Converters/USI_EfficiencyBoosterAddon.cs create mode 100644 USITools/USITools/Converters/USI_EfficiencyBoosterSwapOption.cs create mode 100644 USITools/USITools/Converters/USI_EfficiencyConsumerAddons.cs delete mode 100644 USITools/USITools/Converters/USI_EfficiencyPartSwapOption.cs create mode 100644 USITools/USITools/Converters/USI_Harvester.cs delete mode 100644 USITools/USITools/Converters/USI_ResourceConverter.cs delete mode 100644 USITools/USITools/Converters/USI_ResourceHarvester.cs diff --git a/FOR_RELEASE/GameData/000_USITools/AddConsumers.cfg b/FOR_RELEASE/GameData/000_USITools/AddConsumers.cfg index ae4509c..5b78d83 100644 --- a/FOR_RELEASE/GameData/000_USITools/AddConsumers.cfg +++ b/FOR_RELEASE/GameData/000_USITools/AddConsumers.cfg @@ -1,16 +1,7 @@ -@PART[*]:HAS[!MODULE[ModuleLogisticsConsumer],@MODULE[ModuleCommand]] +@PART[*]:HAS[!MODULE[ModuleLogisticsConsumer],@MODULE[ModuleCommand|KerbalSeat]] { MODULE { name = ModuleLogisticsConsumer } } - -@PART[*]:HAS[!MODULE[ModuleLogisticsConsumer],@MODULE[KerbalSeat]] -{ - MODULE - { - name = ModuleLogisticsConsumer - } -} - diff --git a/FOR_RELEASE/GameData/000_USITools/Logistics.cfg b/FOR_RELEASE/GameData/000_USITools/Logistics.cfg index f50f99e..9dd880b 100644 --- a/FOR_RELEASE/GameData/000_USITools/Logistics.cfg +++ b/FOR_RELEASE/GameData/000_USITools/Logistics.cfg @@ -16,4 +16,12 @@ RESOURCE_DEFINITION //Hidden resource used to manage mass isTweakable = false isVisible = false volume = 1 -} \ No newline at end of file +} + +@PART[*]:HAS[!MODULE[USI_ModuleFieldRepair],RESOURCE[Machinery|EnrichedUranium|DepletedFuel|Recyclables]]:FOR[USITools] +{ + MODULE + { + name = USI_ModuleFieldRepair + } +} diff --git a/USITools/USITools/Converters/AbstractConverterAddon.cs b/USITools/USITools/Converters/AbstractConverterAddon.cs new file mode 100644 index 0000000..a53b7b9 --- /dev/null +++ b/USITools/USITools/Converters/AbstractConverterAddon.cs @@ -0,0 +1,25 @@ +namespace USITools +{ + public abstract class AbstractConverterAddon + where T: BaseConverter + { + protected T Converter { get; private set; } + public bool IsActive + { + get { return Converter.IsActivated; } + } + + public AbstractConverterAddon(T converter) + { + Converter = converter; + } + + public virtual void PreProcessing() + { + } + + public virtual void PostProcess(ConverterResults result, double deltaTime) + { + } + } +} diff --git a/USITools/USITools/Converters/AbstractSwapOption.cs b/USITools/USITools/Converters/AbstractSwapOption.cs index eb0b749..e145685 100644 --- a/USITools/USITools/Converters/AbstractSwapOption.cs +++ b/USITools/USITools/Converters/AbstractSwapOption.cs @@ -4,8 +4,17 @@ namespace USITools { + ///

+ /// Swap options are loadouts that can be applied to a converter to + /// alter its behavior, like changing its recipe or giving it side effects. + /// + /// + /// See and for + /// details on how these loadouts are applied. + /// + /// Any converter type derived from the base game's class. public abstract class AbstractSwapOption : AbstractSwapOption - where T : BaseConverter + where T : BaseConverter, IConverterWithAddons { public virtual void ApplyConverterChanges(T converter) { @@ -18,20 +27,38 @@ public virtual void ApplyConverterChanges(T converter) MonoUtilities.RefreshContextWindows(part); } - public virtual void PostProcess(T Converter, ConverterResults result, double deltaTime) + public virtual ConversionRecipe PrepareRecipe(ConversionRecipe recipe) { - PostProcess(result, deltaTime); + return recipe; + } + + public virtual void PreProcessing(T converter) + { + if (converter.Addons.Count > 0) + { + for (int i = 0; i < converter.Addons.Count; i++) + { + var addon = converter.Addons[i]; + addon.PreProcessing(); + } + } + } + + public virtual void PostProcess(T converter, ConverterResults result, double deltaTime) + { + if (converter.Addons.Count > 0) + { + for (int i = 0; i < converter.Addons.Count; i++) + { + var addon = converter.Addons[i]; + addon.PostProcess(result, deltaTime); + } + } } } public abstract class AbstractSwapOption : PartModule { - [KSPField] - public float Efficiency = 1; - - [KSPField] - public string ResourceName = ""; - [KSPField] public string ConverterName = ""; @@ -47,9 +74,6 @@ public abstract class AbstractSwapOption : PartModule [KSPField] public string ExperienceEffect = ""; - [KSPField] - public bool UseBonus = true; - public List inputList; public List outputList; public List reqList; @@ -114,6 +138,20 @@ public override string GetInfo() return output.ToString(); } + public override string GetModuleDisplayName() + { + string displayName = GetType().Name; + + var idx = displayName.IndexOf('_'); + if (idx >= 0) + { + displayName = displayName.Substring(idx + 1); + } + displayName = displayName.Replace("Swap", " "); + + return displayName; + } + private string ParseResourceRatio(double ratio) { //string units = "sec"; @@ -133,12 +171,7 @@ private string ParseResourceRatio(double ratio) // units = "day"; //} - // 60 seconds X 60 minutes X 6 hours = 21600 seconds per Kerbin day - return string.Format("{0:F2}/day", ratio * 21600); - } - - public virtual void PostProcess(ConverterResults result, double deltaTime) - { + return string.Format("{0:F2}/day", ratio * KSPUtil.dateTimeFormatter.Day); } public override void OnLoad(ConfigNode node) diff --git a/USITools/USITools/Converters/ConverterPart.cs b/USITools/USITools/Converters/ConverterPart.cs deleted file mode 100644 index 013ed1a..0000000 --- a/USITools/USITools/Converters/ConverterPart.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace USITools -{ - [Obsolete] - public class ConverterPart - { - public Part HostPart { get; set; } - public List Converters { get; set; } - public List SwapBays { get; set; } - public ConverterPart(Part p) - { - Converters = new List(); - SwapBays = new List(); - HostPart = p; - Converters.AddRange(p.FindModulesImplementing()); - SwapBays.AddRange(p.FindModulesImplementing()); - } - } -} \ No newline at end of file diff --git a/USITools/USITools/Converters/DeprecatedClasses.cs b/USITools/USITools/Converters/DeprecatedClasses.cs new file mode 100644 index 0000000..4b02df4 --- /dev/null +++ b/USITools/USITools/Converters/DeprecatedClasses.cs @@ -0,0 +1,28 @@ +using System; + +namespace USITools +{ + [Obsolete] + public class ConverterPart { } + + [Obsolete("Use classes derived from AbstractSwapOption instead.")] + public class LoadoutInfo { } + + [Obsolete("Use USI_Converter instead.")] + public class ModuleResourceConverter_USI { } + + [Obsolete("Use USI_Harvester instead.")] + public class ModuleResourceHarvester_USI { } + + [Obsolete("Use USI_SwappableBay instead.")] + public class ModuleSwappableConverter { } + + [Obsolete("Use USI_SwappableBay instead.")] + public class ModuleSwappableConverterNew { } + + [Obsolete("Use a class derived from AbstractSwapOption instead.")] + public class ModuleSwapOption { } + + [Obsolete("Use USI_SwapController instead.")] + public class ModuleSwapControllerNew { } +} diff --git a/USITools/USITools/Converters/Extensions/ConverterExtensionMethods.cs b/USITools/USITools/Converters/Extensions/ConverterExtensionMethods.cs new file mode 100644 index 0000000..bb3d883 --- /dev/null +++ b/USITools/USITools/Converters/Extensions/ConverterExtensionMethods.cs @@ -0,0 +1,84 @@ +using System.Collections.Generic; +using System.Linq; + +namespace USITools +{ + public static class ConverterExtensionMethods + { + /// + /// Find all converter addons of the specified type on the . + /// + /// The converter addon type. + /// The converter type. + /// + /// + public static List FindConverterAddonsImplementing(this Vessel vessel) + where T : AbstractConverterAddon + where T1: BaseConverter + { + var converters = vessel.FindPartModulesImplementing>(); + + return FindConverterAddons(converters); + } + + /// + /// Find all converter addons of the specified type on the . + /// + /// + /// This overload assumes that you're looking for addons for a , + /// since that will be the case 95% of the time. + /// + /// The converter addon type. + /// + /// + public static List FindConverterAddonsImplementing(this Vessel vessel) + where T : AbstractConverterAddon + { + return FindConverterAddonsImplementing(vessel); + } + + /// + /// Find all converter addons of the specified type on the . + /// + /// The converter addon type. + /// The converter type. + /// + /// + public static List FindConverterAddonsImplementing(this Part part) + where T : AbstractConverterAddon + where T1 : BaseConverter + { + var converters = part.FindModulesImplementing>(); + + return FindConverterAddons(converters); + } + + /// + /// Find all converter addons of the specified type on the . + /// + /// + /// This overload assumes that you're looking for addons for a , + /// since that will be the case 95% of the time. + /// + /// The converter addon type. + /// + /// + public static List FindConverterAddonsImplementing(this Part part) + where T : AbstractConverterAddon + { + return FindConverterAddonsImplementing(part); + } + + private static List FindConverterAddons(List> converters) + where T : AbstractConverterAddon + where T1 : BaseConverter + { + var addons = converters + .SelectMany(c => c.Addons + .Where(a => a is T)) + .ToList(); + + return addons.Select(a => (T)a).ToList(); + } + } +} diff --git a/USITools/USITools/Converters/Interfaces/IConverterWithAddons.cs b/USITools/USITools/Converters/Interfaces/IConverterWithAddons.cs new file mode 100644 index 0000000..4fa0610 --- /dev/null +++ b/USITools/USITools/Converters/Interfaces/IConverterWithAddons.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace USITools +{ + /// + /// Any class derived from that can accept + /// an . + /// + /// Any class derived from . + public interface IConverterWithAddons + where T: BaseConverter + { + List> Addons { get; } + } +} diff --git a/USITools/USITools/Converters/Interfaces/IEfficiencyBonusConsumer.cs b/USITools/USITools/Converters/Interfaces/IEfficiencyBonusConsumer.cs deleted file mode 100644 index b9b2383..0000000 --- a/USITools/USITools/Converters/Interfaces/IEfficiencyBonusConsumer.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace USITools -{ - public interface IEfficiencyBonusConsumer - { - float GetEfficiencyBonus(); - void SetEfficiencyBonus(string bonName, float bonValue); - bool UseEfficiencyBonus { get; } - } -} diff --git a/USITools/USITools/Converters/Interfaces/ISwappableConverter.cs b/USITools/USITools/Converters/Interfaces/ISwappableConverter.cs index 80249d4..4405e26 100644 --- a/USITools/USITools/Converters/Interfaces/ISwappableConverter.cs +++ b/USITools/USITools/Converters/Interfaces/ISwappableConverter.cs @@ -2,6 +2,7 @@ { public interface ISwappableConverter { + bool IsStandalone { get; } void Swap(AbstractSwapOption swapOption); } } diff --git a/USITools/USITools/Converters/LoadoutInfo.cs b/USITools/USITools/Converters/LoadoutInfo.cs deleted file mode 100644 index cee99ad..0000000 --- a/USITools/USITools/Converters/LoadoutInfo.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace USITools -{ - [Obsolete("Use classes derived from AbstractSwapOption instead.")] - public class LoadoutInfo - { - public int ModuleId { get; set; } - public string LoadoutName { get; set; } - public float BaseEfficiency { get; set; } - public string DecalTexture { get; set; } - } -} \ No newline at end of file diff --git a/USITools/USITools/Converters/ModuleResourceConverter_USI.cs b/USITools/USITools/Converters/ModuleResourceConverter_USI.cs deleted file mode 100644 index 2e913f4..0000000 --- a/USITools/USITools/Converters/ModuleResourceConverter_USI.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; - -namespace USITools -{ - [Obsolete("Use USI_ResourceConverter instead.")] - public class ModuleResourceConverter_USI : PartModule - { - [KSPField(isPersistant = true)] - public bool IsActivated; - - [KSPField(isPersistant = true)] - public bool hasBeenUpdated; - - [KSPField] - public double eMultiplier = 1d; - - [KSPField] - public string eTag = ""; - - private ModuleResourceConverter_USI _presidingInstance; - - public List PreviouslyActiveList { get; private set; } = new List(); - - public override void OnStart(StartState state) - { - base.OnStart(state); - - // Determine which recipe each converter was previously resposible for, - // find which bay(s) are currently responsible for that recipe and - // activate the corresponding converter(s) if they were previously active. - if (_presidingInstance == this && !hasBeenUpdated) - { - var swapOptionCount = part.FindModulesImplementing().Count; - var converters = part.FindModulesImplementing(); - var bays = part.FindModulesImplementing(); - var controller = part.FindModuleImplementing(); - - if (controller == null) - Debug.LogError(string.Format("[USI] {0}: Trying to import old loadout(s) into a part with no USI_SwapController. Check the part config file.", GetType().Name)); - else if (swapOptionCount != PreviouslyActiveList.Count) - Debug.LogError(string.Format("[USI] {0}: Trying to import {1} old loadout(s) into a part with {2} swap option(s). Check the part config file.", GetType().Name, PreviouslyActiveList.Count, swapOptionCount)); - else - { - // i will correspond to the loadout index - for (int i = 0; i < PreviouslyActiveList.Count; i++) - { - bool wasActive = PreviouslyActiveList[i]; - if (wasActive) - { - // Determine which bays (if any) are currently configured for this loadout - var configuredBays = bays.Where(b => b.currentLoadout == i); - if (configuredBays.Any()) - { - var bayIndexes = configuredBays.Select(b => b.moduleIndex); - foreach (var index in bayIndexes) - { - var converter = converters[index]; - converter.isEnabled = true; - converter.IsActivated = true; - } - } - } - } - - hasBeenUpdated = true; - } - } - } - - public override void OnLoad(ConfigNode node) - { - base.OnLoad(node); - - // The order these nodes are loaded by the game should match the loadout order - // of the swap options. So we should be able to use this to determine - // which recipes were active previously and thus which converters to re-activate. - if (_presidingInstance == null) - { - // If there are other ModuleResourceConverter_USI instances on this part, - // see if any of them have a PreviouslyActiveList started yet. If so, then - // let that instance handle the remainder of this process. Otherwise, make this instance - // the presiding instance. - var otherInstances = part.FindModulesImplementing(); - if (otherInstances != null) - { - var candidate = otherInstances.Where(i => i.PreviouslyActiveList.Count > 0).FirstOrDefault(); - _presidingInstance = candidate ?? this; - } - else - _presidingInstance = this; - } - - _presidingInstance.PreviouslyActiveList.Add(IsActivated && isEnabled); - } - - public override void OnSave(ConfigNode node) - { - if (_presidingInstance != null) - hasBeenUpdated = _presidingInstance.hasBeenUpdated; - - base.OnSave(node); - } - } -} diff --git a/USITools/USITools/Converters/ModuleResourceHarvester_USI.cs b/USITools/USITools/Converters/ModuleResourceHarvester_USI.cs deleted file mode 100644 index 513d05a..0000000 --- a/USITools/USITools/Converters/ModuleResourceHarvester_USI.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; - -namespace USITools -{ - [Obsolete("Use USI_ResourceHarvester instead.")] - public class ModuleResourceHarvester_USI : PartModule - { - [KSPField(isPersistant = true)] - public bool IsActivated; - - [KSPField(isPersistant = true)] - public bool hasBeenUpdated; - - private ModuleResourceHarvester_USI _presidingInstance; - - public List PreviouslyActiveList { get; private set; } = new List(); - - public override void OnStart(StartState state) - { - base.OnStart(state); - - // Determine which recipe each harvester was previously resposible for, - // find which bay(s) are currently responsible for that recipe and - // activate the corresponding harvester(s) if they were previously active. - if (_presidingInstance == this && !hasBeenUpdated) - { - var swapOptionCount = part.FindModulesImplementing().Count; - var harvesters = part.FindModulesImplementing(); - var bays = part.FindModulesImplementing(); - var controller = part.FindModuleImplementing(); - - if (controller == null) - Debug.LogError(string.Format("[USI] {0}: Trying to import old loadout(s) into a part with no USI_SwapController. Check the part config file.", GetType().Name)); - else if (swapOptionCount != PreviouslyActiveList.Count) - Debug.LogError(string.Format("[USI] {0}: Trying to import {1} old loadout(s) into a part with {2} swap option(s). Check the part config file.", GetType().Name, PreviouslyActiveList.Count, swapOptionCount)); - else - { - // i will correspond to the loadout index - for (int i = 0; i < PreviouslyActiveList.Count; i++) - { - bool wasActive = PreviouslyActiveList[i]; - if (wasActive) - { - // Determine which bays (if any) are currently configured for this loadout - var configuredBays = bays.Where(b => b.currentLoadout == i); - if (configuredBays.Any()) - { - var bayIndexes = configuredBays.Select(b => b.moduleIndex); - foreach (var index in bayIndexes) - { - var harvester = harvesters[index]; - harvester.isEnabled = true; - harvester.IsActivated = true; - } - } - } - } - - hasBeenUpdated = true; - } - } - } - - public override void OnLoad(ConfigNode node) - { - base.OnLoad(node); - - // The order these nodes are loaded by the game should match the loadout order - // of the swap options. So we should be able to use this to determine - // which recipes were active previously and thus which converters to re-activate. - if (_presidingInstance == null) - { - // If there are other ModuleResourceHarvester_USI instances on this part, - // see if any of them have a PreviouslyActiveList started yet. If so, then - // let that instance handle the remainder of this process. Otherwise, make this instance - // the presiding instance. - var otherInstances = part.FindModulesImplementing(); - if (otherInstances != null) - { - var candidate = otherInstances.Where(i => i.PreviouslyActiveList.Count > 0).FirstOrDefault(); - _presidingInstance = candidate ?? this; - } - else - _presidingInstance = this; - } - - _presidingInstance.PreviouslyActiveList.Add(IsActivated && isEnabled); - } - - public override void OnSave(ConfigNode node) - { - if (_presidingInstance != null) - hasBeenUpdated = _presidingInstance.hasBeenUpdated; - - base.OnSave(node); - } - } -} diff --git a/USITools/USITools/Converters/ModuleSwappableConverter.cs b/USITools/USITools/Converters/ModuleSwappableConverter.cs deleted file mode 100644 index 211dabc..0000000 --- a/USITools/USITools/Converters/ModuleSwappableConverter.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace USITools -{ - [Obsolete("Use ModuleSwappableBay instead.")] - public class ModuleSwappableConverter : ModuleSwappableConverterNew - { - } -} diff --git a/USITools/USITools/Converters/ModuleSwappableConverterNew.cs b/USITools/USITools/Converters/ModuleSwappableConverterNew.cs deleted file mode 100644 index 733000f..0000000 --- a/USITools/USITools/Converters/ModuleSwappableConverterNew.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; - -namespace USITools -{ - [Obsolete("Use ModuleSwappableBay instead.")] - public class ModuleSwappableConverterNew : PartModule - { - [KSPField] - public string bayName = ""; - - [KSPField(isPersistant = true)] - public int currentLoadout = 0; - - [KSPField(isPersistant = true)] - public bool hasBeenUpdated; - - private List oldLoadouts = new List(); - - public override void OnStart(StartState state) - { - base.OnStart(state); - - // Import loadouts from the old ModuleSwappableConverterNew class into the new USI_SwappableBay class. - if (!hasBeenUpdated) - { - var bays = part.FindModulesImplementing(); - var controller = part.FindModuleImplementing(); - - if (controller == null) - Debug.LogError("[USI] ModuleSwappableConverter(New): Trying to import old loadout(s) into a part with no USI_SwapController. Check the part config file."); - else if (bays.Count < oldLoadouts.Count) - Debug.LogError(string.Format("[USI] ModuleSwappableConverter(New): Trying to import {0} old loadout(s) into a part with {1} bay(s). Check the part config file.", oldLoadouts.Count, bays.Count)); - else - { - for (int i = 0; i < oldLoadouts.Count; i++) - { - var bay = bays.Where(b => b.moduleIndex == i).FirstOrDefault(); - if (bay != null) - { - bay.currentLoadout = oldLoadouts[i]; - controller.ApplyLoadout(bay.currentLoadout, i); - } - } - - hasBeenUpdated = true; - } - } - } - - public override void OnLoad(ConfigNode node) - { - base.OnLoad(node); - - oldLoadouts.Add(currentLoadout); - } - } -} diff --git a/USITools/USITools/Converters/USI_Converter.cs b/USITools/USITools/Converters/USI_Converter.cs new file mode 100644 index 0000000..fc9d709 --- /dev/null +++ b/USITools/USITools/Converters/USI_Converter.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace USITools +{ + /// + /// Wrapper class for the base game's that + /// allows us to do things like swap converter recipes, give converters side effects + /// via , etc. + /// + /// + public class USI_Converter : + ModuleResourceConverter, + IConverterWithAddons, + ISwappableConverter + { + #region Fields and properties + private AbstractSwapOption _swapOption; + + public List> Addons { get; private set; } = + new List>(); + + [KSPField] + protected bool IsStandaloneConverter = false; + + /// + /// This allows standalone converters to co-exist with swappable converters on the same part. + /// + /// + /// Wrapping the KSPField in a property is necessary in order to expose it via + /// and thus allow to ignore standalone converters. + /// + public bool IsStandalone + { + get { return IsStandaloneConverter; } + } + #endregion + + public void Swap(AbstractSwapOption swapOption) + { + Swap(swapOption as AbstractSwapOption); + } + + public void Swap(AbstractSwapOption swapOption) + { + _swapOption = swapOption; + Addons.Clear(); + + try + { + _swapOption.ApplyConverterChanges(this); + } + catch (Exception ex) + { + Debug.LogError("[USI] USI_Converter: Could not apply converter changes. " + ex.Message); + } + } + + protected override ConversionRecipe PrepareRecipe(double deltatime) + { + var recipe = base.PrepareRecipe(deltatime); + if (recipe == null) + { + recipe = Recipe; + } + + if (_swapOption != null) + { + recipe = _swapOption.PrepareRecipe(recipe); + } + + return recipe; + } + + protected override void PreProcessing() + { + base.PreProcessing(); + if (_swapOption != null) + { + _swapOption.PreProcessing(this); + } + } + + protected override void PostProcess(ConverterResults result, double deltaTime) + { + base.PostProcess(result, deltaTime); + + var hasLoad = false; + if (status != null) + { + hasLoad = status.EndsWith("Load"); + } + + if (!hasLoad && result.TimeFactor >= ResourceUtilities.FLOAT_TOLERANCE) + { + statusPercent = 0d; //Force a reset of the load display. + } + + if (_swapOption != null) + { + _swapOption.PostProcess(this, result, deltaTime); + } + } + + public override string GetInfo() + { + if (IsStandaloneConverter) + return base.GetInfo(); + + return string.Empty; + } + } +} diff --git a/USITools/USITools/Converters/USI_ConverterOptions.cs b/USITools/USITools/Converters/USI_ConverterOptions.cs new file mode 100644 index 0000000..cc7fb53 --- /dev/null +++ b/USITools/USITools/Converters/USI_ConverterOptions.cs @@ -0,0 +1,97 @@ +namespace USITools +{ + public class USI_ConverterOptions : GameParameters.CustomParameterNode + { + [GameParameters.CustomParameterUI("EVA Required", toolTip = "If enabled, a Kerbal must be on EVA to swap converter recipes.", autoPersistance = true)] + public bool ConverterSwapRequiresEVA = true; + + [GameParameters.CustomParameterUI("Repair Skill Required", toolTip = "If enabled, a Kerbal with the Repair skill (engineers, mechanics) must be present to swap converter recipes.", autoPersistance = true)] + public bool ConverterSwapRequiresRepairSkill = true; + + [GameParameters.CustomFloatParameterUI("Cost Multiplier", toolTip = "Set to zero to disable converter swap costs.", autoPersistance = true, minValue = 0f, maxValue = 5f, stepCount = 10)] + public float ConverterSwapCostMultiplier = 1f; + + public static bool ConverterSwapRequiresRepairSkillEnabled + { + get + { + var options = HighLogic.CurrentGame.Parameters.CustomParams(); + + return options.ConverterSwapRequiresRepairSkill; + } + } + + public static bool ConverterSwapRequiresEVAEnabled + { + get + { + var options = HighLogic.CurrentGame.Parameters.CustomParams(); + + return options.ConverterSwapRequiresEVA; + } + } + + public static float ConverterSwapCostMultiplierValue + { + get + { + var options = HighLogic.CurrentGame.Parameters.CustomParams(); + + return options.ConverterSwapCostMultiplier; + } + } + + public override string Section + { + get + { + return "Kolonization"; + } + } + + public override string DisplaySection + { + get + { + return "Kolonization"; + } + } + + public override string Title + { + get + { + return "Swappable Converters"; + } + } + + public override int SectionOrder + { + get + { + return 1; + } + } + + public override void SetDifficultyPreset(GameParameters.Preset preset) + { + base.SetDifficultyPreset(preset); + } + + public override GameParameters.GameMode GameMode + { + get + { + return GameParameters.GameMode.ANY; + } + } + + public override bool HasPresets + { + get + { + return false; + } + } + } +} diff --git a/USITools/USITools/Converters/USI_ConverterSwapOption.cs b/USITools/USITools/Converters/USI_ConverterSwapOption.cs index 4d1983c..b2d0f0f 100644 --- a/USITools/USITools/Converters/USI_ConverterSwapOption.cs +++ b/USITools/USITools/Converters/USI_ConverterSwapOption.cs @@ -1,10 +1,25 @@ -namespace USITools +using USITools.KolonyTools; + +namespace USITools { + /// + /// A basic converter loadout, with the option to consume efficiency bonuses. + /// public class USI_ConverterSwapOption - : AbstractSwapOption + : AbstractSwapOption { - public override void ApplyConverterChanges(USI_ResourceConverter converter) + /// + /// Set this to false to ignore efficiency bonuses. + /// + [KSPField] + public bool UseEfficiencyBonus = true; + + [KSPField] + public string EfficiencyTag; + + public override void ApplyConverterChanges(USI_Converter converter) { + // Setup the conversion recipe converter.inputList.Clear(); converter.outputList.Clear(); converter.reqList.Clear(); @@ -21,7 +36,37 @@ public override void ApplyConverterChanges(USI_ResourceConverter converter) converter.Recipe.Outputs.AddRange(outputList); converter.Recipe.Requirements.AddRange(reqList); + // Setup efficiency bonus consumption + if (UseEfficiencyBonus) + { + converter.Addons.Add(new USI_EfficiencyConsumerAddonForConverters(converter) + { + Tag = EfficiencyTag + }); + } + base.ApplyConverterChanges(converter); } + + public override ConversionRecipe PrepareRecipe(ConversionRecipe recipe) + { + if (!USI_DifficultyOptions.ConsumeMachineryEnabled && recipe != null) + { + for (int i = recipe.Inputs.Count; i-- > 0;) + { + var input = recipe.Inputs[i]; + if (input.ResourceName == "Machinery") + recipe.Inputs.Remove(input); + } + for (int output = recipe.Outputs.Count; output-- > 0;) + { + var op = recipe.Outputs[output]; + if (op.ResourceName == "Recyclables") + recipe.Inputs.Remove(op); + } + } + + return recipe; + } } } diff --git a/USITools/USITools/Converters/USI_EfficiencyBoosterAddon.cs b/USITools/USITools/Converters/USI_EfficiencyBoosterAddon.cs new file mode 100644 index 0000000..9f72d90 --- /dev/null +++ b/USITools/USITools/Converters/USI_EfficiencyBoosterAddon.cs @@ -0,0 +1,35 @@ +namespace USITools +{ + public class USI_EfficiencyBoosterAddon : AbstractConverterAddon + { + public double Multiplier = 1d; + public string Tag = ""; + + private double _efficiencyMultiplier; + public double EfficiencyMultiplier + { + get + { + if (HighLogic.LoadedSceneIsEditor) + return _efficiencyMultiplier; + + if (!IsActive) + _efficiencyMultiplier = 0d; + + return _efficiencyMultiplier; + } + set + { + _efficiencyMultiplier = value; + } + } + + public USI_EfficiencyBoosterAddon(USI_Converter converter) : base(converter) { } + + public override void PostProcess(ConverterResults result, double deltaTime) + { + base.PostProcess(result, deltaTime); + EfficiencyMultiplier = result.TimeFactor / deltaTime; + } + } +} diff --git a/USITools/USITools/Converters/USI_EfficiencyBoosterSwapOption.cs b/USITools/USITools/Converters/USI_EfficiencyBoosterSwapOption.cs new file mode 100644 index 0000000..9ca44ac --- /dev/null +++ b/USITools/USITools/Converters/USI_EfficiencyBoosterSwapOption.cs @@ -0,0 +1,36 @@ +namespace USITools +{ + /// + /// A converter loadout that boosts the efficiency of other converters. + /// + public class USI_EfficiencyBoosterSwapOption : USI_ConverterSwapOption + { + [KSPField] + public double EfficiencyMultiplier = 1d; + + public override void ApplyConverterChanges(USI_Converter converter) + { + // Efficiency boosters can't also be efficiency consumers, for now. + UseEfficiencyBonus = false; + + converter.Addons.Add(new USI_EfficiencyBoosterAddon(converter) + { + Multiplier = EfficiencyMultiplier, + Tag = EfficiencyTag + }); + + base.ApplyConverterChanges(converter); + } + + public override string GetInfo() + { + if (string.IsNullOrEmpty(EfficiencyTag)) + return base.GetInfo(); + var resourceConsumption = base.GetInfo(); + int index = resourceConsumption.IndexOf("\n"); // Strip the first line containing the etag + resourceConsumption = resourceConsumption.Substring(index + 1); + return "Boosts efficiency of converters benefiting from a " + EfficiencyTag + "\n\n" + + "Boost power: " + EfficiencyMultiplier.ToString() + resourceConsumption; + } + } +} diff --git a/USITools/USITools/Converters/USI_EfficiencyConsumerAddons.cs b/USITools/USITools/Converters/USI_EfficiencyConsumerAddons.cs new file mode 100644 index 0000000..306a7ce --- /dev/null +++ b/USITools/USITools/Converters/USI_EfficiencyConsumerAddons.cs @@ -0,0 +1,95 @@ +using System.Collections.Generic; + +namespace USITools +{ + /// + /// A converter addon that allows a converter to receive efficiency bonuses. + /// + public class USI_EfficiencyConsumerAddonForConverters : AbstractConverterAddon + { + public string Tag { get; set; } + + protected Dictionary _bonusList { get; private set; } = + new Dictionary(); + + public USI_EfficiencyConsumerAddonForConverters(USI_Converter converter) : base(converter) + { + } + + public override void PreProcessing() + { + base.PreProcessing(); + Converter.EfficiencyBonus = GetEfficiencyBonus(); + } + + public float GetEfficiencyBonus() + { + var totalBonus = 1f; + + foreach (var bonus in _bonusList) + { + totalBonus *= bonus.Value; + } + + return totalBonus; + } + + public bool InCatchupMode() + { + var multiplier = Converter.GetEfficiencyMultiplier(); + if (Converter.lastTimeFactor / 2 > multiplier) + { + return true; + } + + return false; + } + + public void SetEfficiencyBonus(string name, float value) + { + if (!_bonusList.ContainsKey(name)) + _bonusList.Add(name, value); + else + _bonusList[name] = value; + } + } + + /// + /// A converter addon that allows a harvester to receive efficiency bonuses. + /// + public class USI_EfficiencyConsumerAddonForHarvesters : AbstractConverterAddon + { + protected Dictionary _bonusList { get; private set; } = + new Dictionary(); + + public USI_EfficiencyConsumerAddonForHarvesters(USI_Harvester converter) : base(converter) + { + } + + public override void PreProcessing() + { + base.PreProcessing(); + Converter.EfficiencyBonus = GetEfficiencyBonus(); + } + + public float GetEfficiencyBonus() + { + var totalBonus = 1f; + + foreach (var bonus in _bonusList) + { + totalBonus *= bonus.Value; + } + + return totalBonus; + } + + public void SetEfficiencyBonus(string name, float value) + { + if (!_bonusList.ContainsKey(name)) + _bonusList.Add(name, value); + else + _bonusList[name] = value; + } + } +} diff --git a/USITools/USITools/Converters/USI_EfficiencyPartSwapOption.cs b/USITools/USITools/Converters/USI_EfficiencyPartSwapOption.cs deleted file mode 100644 index f06f93f..0000000 --- a/USITools/USITools/Converters/USI_EfficiencyPartSwapOption.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace USITools -{ - public class USI_EfficiencyPartSwapOption : USI_ConverterSwapOption - { - [KSPField] - public double eMultiplier = 1d; - - [KSPField] - public string eTag = ""; - - public override void ApplyConverterChanges(USI_ResourceConverter converter) - { - UseBonus = false; // efficiency parts should not use bonuses from other efficiency parts! - converter.eMultiplier = eMultiplier; - converter.eTag = eTag; - - base.ApplyConverterChanges(converter); - } - - public override string GetInfo() - { - if (string.IsNullOrEmpty(eTag)) - return base.GetInfo(); - var resourceConsumption = base.GetInfo(); - int index = resourceConsumption.IndexOf("\n"); // Strip the first line containing the etag - resourceConsumption = resourceConsumption.Substring(index + 1); - return "Boosts efficiency of converters benefiting from a " + eTag + "\n\n" + - "Boost power: " + eMultiplier.ToString() + resourceConsumption; - } - - public override void PostProcess(USI_ResourceConverter converter, ConverterResults result, double deltaTime) - { - base.PostProcess(result, deltaTime); - converter.EfficiencyMultiplier = result.TimeFactor / deltaTime; - } - } -} diff --git a/USITools/USITools/Converters/USI_Harvester.cs b/USITools/USITools/Converters/USI_Harvester.cs new file mode 100644 index 0000000..daf958b --- /dev/null +++ b/USITools/USITools/Converters/USI_Harvester.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace USITools +{ + /// + /// Wrapper class for the base game's that + /// allows us to do things like swap drill heads, give drills side effects + /// via , etc. + /// + public class USI_Harvester : + ModuleResourceHarvester, + IConverterWithAddons, + ISwappableConverter + { + #region Fields and properties + private AbstractSwapOption _swapOption; + + public List> Addons { get; private set; } = + new List>(); + + [KSPField] + protected bool IsStandaloneHarvester = false; + + /// + /// This allows standalone harvesters to co-exist with swappable harvesters on the same part. + /// + /// + /// Wrapping the KSPField in a property is necessary in order to expose it via + /// and thus allow to ignore standalone harvesters. + /// + public bool IsStandalone + { + get { return IsStandaloneHarvester; } + } + #endregion + + public void Swap(AbstractSwapOption swapOption) + { + Swap(swapOption as AbstractSwapOption); + } + + public void Swap(AbstractSwapOption swapOption) + { + _swapOption = swapOption; + Addons.Clear(); + + try + { + _swapOption.ApplyConverterChanges(this); + } + catch (Exception ex) + { + Debug.LogError("[USI] USI_Harvester: Could not apply harvester changes. " + ex.Message); + } + } + + protected override ConversionRecipe PrepareRecipe(double deltaTime) + { + var recipe = base.PrepareRecipe(deltaTime); + + if (_swapOption != null) + { + recipe = _swapOption.PrepareRecipe(recipe); + } + + return recipe; + } + + protected override void PreProcessing() + { + base.PreProcessing(); + + if (_swapOption != null) + { + _swapOption.PreProcessing(this); + } + } + + protected override void PostProcess(ConverterResults result, double deltaTime) + { + base.PostProcess(result, deltaTime); + + var hasLoad = false; + if (status != null) + { + hasLoad = status.EndsWith("Load"); + } + + if (result.TimeFactor >= ResourceUtilities.FLOAT_TOLERANCE + && !hasLoad) + { + statusPercent = 0d; //Force a reset of the load display. + } + + if (_swapOption != null) + { + _swapOption.PostProcess(this, result, deltaTime); + } + } + + public override string GetInfo() + { + if (_swapOption == null) + return base.GetInfo(); + + return string.Empty; + } + } +} diff --git a/USITools/USITools/Converters/USI_HarvesterSwapOption.cs b/USITools/USITools/Converters/USI_HarvesterSwapOption.cs index ed30bbf..1a7c86f 100644 --- a/USITools/USITools/Converters/USI_HarvesterSwapOption.cs +++ b/USITools/USITools/Converters/USI_HarvesterSwapOption.cs @@ -2,17 +2,32 @@ namespace USITools { - [Obsolete("Use a class derived from AbstractSwapOption instead.")] - public class ModuleSwapOption : USI_HarvesterSwapOption { } - public class USI_HarvesterSwapOption - : AbstractSwapOption + : AbstractSwapOption { - public override void ApplyConverterChanges(USI_ResourceHarvester converter) + [KSPField] + public string ResourceName = ""; + + [KSPField] + public float Efficiency = 1; + + /// + /// Set this to false to ignore efficiency bonuses. + /// + [KSPField] + public bool UseEfficiencyBonus = true; + + public override void ApplyConverterChanges(USI_Harvester converter) { converter.Efficiency = Efficiency; converter.ResourceName = ResourceName; + // Setup efficiency bonus consumption + if (UseEfficiencyBonus) + { + converter.Addons.Add(new USI_EfficiencyConsumerAddonForHarvesters(converter)); + } + base.ApplyConverterChanges(converter); } } diff --git a/USITools/USITools/Converters/USI_ResourceConverter.cs b/USITools/USITools/Converters/USI_ResourceConverter.cs deleted file mode 100644 index e46451e..0000000 --- a/USITools/USITools/Converters/USI_ResourceConverter.cs +++ /dev/null @@ -1,141 +0,0 @@ -using System.Collections.Generic; -using USITools.KolonyTools; - -namespace USITools -{ - public class USI_ResourceConverter : - ModuleResourceConverter, - IEfficiencyBonusConsumer, - ISwappableConverter - { - #region Fields and properties - [KSPField] - public double eMultiplier = 1d; - - [KSPField] - public string eTag = ""; - - public Dictionary BonusList { get; private set; } = - new Dictionary(); - - public float Governor = 1.0f; - private double _efficiencyMultiplier; - public double EfficiencyMultiplier - { - get - { - if (HighLogic.LoadedSceneIsEditor) - return _efficiencyMultiplier * Governor; - if (!IsActivated) - _efficiencyMultiplier = 0d; - return _efficiencyMultiplier * Governor; - } - set - { - _efficiencyMultiplier = value; - } - } - - public bool UseEfficiencyBonus - { - get - { - if (_swapOption != null) - return _swapOption.UseBonus; - else - return false; - } - } - - private AbstractSwapOption _swapOption; - #endregion - - public void Swap(AbstractSwapOption swapOption) - { - Swap(swapOption as AbstractSwapOption); - } - - public void Swap(AbstractSwapOption swapOption) - { - _swapOption = swapOption; - _swapOption.ApplyConverterChanges(this); - } - - public float GetEfficiencyBonus() - { - var totalBonus = 1f; - foreach (var bonus in BonusList) - { - totalBonus *= bonus.Value; - } - return totalBonus; - } - - public void SetEfficiencyBonus(string name, float value) - { - if (!BonusList.ContainsKey(name)) - BonusList.Add(name, value); - else - BonusList[name] = value; - } - - protected override void PreProcessing() - { - base.PreProcessing(); - EfficiencyBonus = GetEfficiencyBonus(); - } - - protected override ConversionRecipe PrepareRecipe(double deltatime) - { - var recipe = base.PrepareRecipe(deltatime); - if (!USI_DifficultyOptions.ConsumeMachineryEnabled && recipe != null) - { - for (int i = recipe.Inputs.Count; i-- > 0;) - { - var input = recipe.Inputs[i]; - if (input.ResourceName == "Machinery") - recipe.Inputs.Remove(input); - } - for (int output = recipe.Outputs.Count; output-- > 0;) - { - var op = recipe.Outputs[output]; - if (op.ResourceName == "Recyclables") - recipe.Inputs.Remove(op); - } - } - return recipe; - } - - protected override void PostProcess(ConverterResults result, double deltaTime) - { - base.PostProcess(result, deltaTime); - var hasLoad = false; - if (status != null) - { - hasLoad = status.EndsWith("Load"); - } - - - if (result.TimeFactor >= ResourceUtilities.FLOAT_TOLERANCE - && !hasLoad) - { - statusPercent = 0d; //Force a reset of the load display. - } - - if (_swapOption != null) - { - _swapOption.PostProcess(this, result, deltaTime); - } - } - - public override string GetInfo() - { - return string.Empty; - } - - public override string GetModuleDisplayName() - { - return GetType().Name; - } - } -} diff --git a/USITools/USITools/Converters/USI_ResourceHarvester.cs b/USITools/USITools/Converters/USI_ResourceHarvester.cs deleted file mode 100644 index 28b5a25..0000000 --- a/USITools/USITools/Converters/USI_ResourceHarvester.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System.Collections.Generic; - -namespace USITools -{ - public class USI_ResourceHarvester : - ModuleResourceHarvester, - IEfficiencyBonusConsumer, - ISwappableConverter - { - #region Fields and properties - public Dictionary BonusList { get; private set; } = - new Dictionary(); - - public bool UseEfficiencyBonus - { - get - { - if (_swapOption != null) - return _swapOption.UseBonus; - else - return false; - } - } - - private AbstractSwapOption _swapOption; - #endregion - - public void Swap(AbstractSwapOption swapOption) - { - Swap(swapOption as AbstractSwapOption); - } - - public void Swap(AbstractSwapOption swapOption) - { - _swapOption = swapOption; - _swapOption.ApplyConverterChanges(this); - } - - public float GetEfficiencyBonus() - { - var totalBonus = 1f; - foreach (var bonus in BonusList) - { - totalBonus *= bonus.Value; - } - return totalBonus; - } - - public void SetEfficiencyBonus(string name, float value) - { - if (!BonusList.ContainsKey(name)) - BonusList.Add(name, value); - else - BonusList[name] = value; - } - - public override string GetInfo() - { - return string.Empty; - } - - protected override void PreProcessing() - { - base.PreProcessing(); - EfficiencyBonus = GetEfficiencyBonus(); - } - - protected override void PostProcess(ConverterResults result, double deltaTime) - { - base.PostProcess(result, deltaTime); - - var hasLoad = false; - if (status != null) - { - hasLoad = status.EndsWith("Load"); - } - - if (result.TimeFactor >= ResourceUtilities.FLOAT_TOLERANCE - && !hasLoad) - { - statusPercent = 0d; //Force a reset of the load display. - } - } - } -} diff --git a/USITools/USITools/Converters/USI_SwapController.cs b/USITools/USITools/Converters/USI_SwapController.cs index 26000cf..02fc4a2 100644 --- a/USITools/USITools/Converters/USI_SwapController.cs +++ b/USITools/USITools/Converters/USI_SwapController.cs @@ -1,19 +1,25 @@ -using System; using System.Collections.Generic; +using System.Linq; using System.Text; namespace USITools { - [Obsolete("Use ModuleSwapController instead.")] - public class ModuleSwapControllerNew : USI_SwapController { } - + /// + /// Responsible for tracking available converter loadouts and the + /// costs associated with swapping them. Also responsible for instructing + /// an to apply a loadout to itself. + /// + /// + /// See and + /// for additional information. + /// public class USI_SwapController : PartModule { [KSPField] public string ResourceCosts = ""; [KSPField] - public string typeName = "Loadout"; + public string typeName = "Bay"; public List SwapCosts = new List(); public List Loadouts; @@ -23,7 +29,10 @@ public class USI_SwapController : PartModule public override void OnStart(StartState state) { Loadouts = part.FindModulesImplementing(); - _converters = part.FindModulesImplementing(); + _converters = part.FindModulesImplementing() + .Where(c => !c.IsStandalone) + .ToList(); + SetupSwapCosts(); } diff --git a/USITools/USITools/Converters/USI_SwappableBay.cs b/USITools/USITools/Converters/USI_SwappableBay.cs index 5226200..f1a3431 100644 --- a/USITools/USITools/Converters/USI_SwappableBay.cs +++ b/USITools/USITools/Converters/USI_SwappableBay.cs @@ -2,25 +2,32 @@ namespace USITools { + /// + /// Responsible for UI interactions related to swappable converters. + /// + /// + /// See and + /// for additional information. + /// public class USI_SwappableBay : PartModule { #region KSP Fields and Events - [KSPField] - public bool autoActivate = true; - [KSPField] public string bayName = ""; [KSPField] public int moduleIndex = 0; + [KSPField] + public bool hasPermanentLoadout = false; + [KSPField(isPersistant = true)] public int currentLoadout = 0; - [KSPField(guiActive = true, guiActiveEditor = true, guiName = "Active: ")] + [KSPField(guiActive = true, guiActiveEditor = true, guiName = "Recipe: ")] public string curTemplate = "???"; - [KSPEvent(active = true, guiActiveEditor = true, guiActiveUnfocused = true, externalToEVAOnly = true, guiName = "B1: Install [None]", unfocusedRange = 10f)] + [KSPEvent(active = true, guiActiveEditor = true, guiActiveUnfocused = true, guiName = "B1: Install [None]", unfocusedRange = 10f)] public void LoadSetup() { if (!CheckResources()) @@ -36,7 +43,7 @@ public void LoadSetup() ConfigureLoadout(); } - [KSPEvent(active = true, guiActiveEditor = true, guiActiveUnfocused = true, externalToEVAOnly = true, guiName = "B1: Next Loadout", unfocusedRange = 10f)] + [KSPEvent(active = true, guiActiveEditor = true, guiActiveUnfocused = true, guiName = "B1: Next Loadout", unfocusedRange = 10f)] public void NextSetup() { if (_controller.Loadouts.Count < 2) @@ -55,7 +62,7 @@ public void NextSetup() ChangeMenu(); } - [KSPEvent(active = true, guiActiveEditor = true, guiActiveUnfocused = true, externalToEVAOnly = true, guiName = "B1: Prev. Loadout", unfocusedRange = 10f)] + [KSPEvent(active = true, guiActiveEditor = true, guiActiveUnfocused = true, guiName = "B1: Prev. Loadout", unfocusedRange = 10f)] public void PrevSetup() { if (_controller.Loadouts.Count < 2) @@ -79,6 +86,7 @@ public void PrevSetup() private bool _postLoad = false; private int displayLoadout; private USI_SwapController _controller; + private bool _repairSkillRequired = true; #endregion public override void OnStart(StartState state) @@ -86,11 +94,22 @@ public override void OnStart(StartState state) _controller = part.FindModuleImplementing(); if (_controller == null) { - Debug.LogError(string.Format("[USI] {0}: USI_SwappableBay modules require a USI_SwapController module. Check the part config file.", GetType().Name)); + Debug.LogError(string.Format("[USI] {0}: Part is misconfigured. USI_SwappableBay modules require a USI_SwapController module.", GetType().Name)); } + GameEvents.OnAnimationGroupStateChanged.Add(SetModuleState); displayLoadout = currentLoadout; + + _repairSkillRequired = USI_ConverterOptions.ConverterSwapRequiresRepairSkillEnabled; + ConfigureLoadout(); + ConfigureMenus(); + + // Disable the menus if there is only one swap option or if this bay has a permanent loadout. + if (hasPermanentLoadout || _controller.Loadouts.Count < 2) + { + EnableMenus(false); + } } private void SetModuleState(ModuleAnimationGroup module, bool enable) @@ -98,30 +117,46 @@ private void SetModuleState(ModuleAnimationGroup module, bool enable) if (module != null && module.part != part) return; - if (HighLogic.LoadedSceneIsFlight) + if (HighLogic.LoadedSceneIsFlight && !hasPermanentLoadout) { EnableMenus(enable); } } + private void ConfigureLoadout() + { + _controller.ApplyLoadout(currentLoadout, moduleIndex); + } + + private void ConfigureMenus() + { + bool evaRequired = USI_ConverterOptions.ConverterSwapRequiresEVAEnabled; + + Events["NextSetup"].externalToEVAOnly = evaRequired; + Events["NextSetup"].guiActive = !evaRequired; + Events["PrevSetup"].externalToEVAOnly = evaRequired; + Events["PrevSetup"].guiActive = !evaRequired; + Events["LoadSetup"].externalToEVAOnly = evaRequired; + Events["LoadSetup"].guiActive = !evaRequired; + + MonoUtilities.RefreshContextWindows(part); + } + private void EnableMenus(bool enable) { Events["NextSetup"].active = enable; Events["PrevSetup"].active = enable; Events["LoadSetup"].active = enable; - MonoUtilities.RefreshContextWindows(part); - } + Fields["curTemplate"].guiActive = enable; - private void ConfigureLoadout() - { - _controller.ApplyLoadout(currentLoadout, moduleIndex); + MonoUtilities.RefreshContextWindows(part); } public void Update() { if (!_postLoad) { - if (_controller.Loadouts.Count > 2) + if (_controller.Loadouts.Count > 1) { _postLoad = true; NextSetup(); @@ -133,7 +168,7 @@ public void ChangeMenu() { Events["NextSetup"].guiName = (bayName + " Next " + _controller.typeName).Trim(); Events["PrevSetup"].guiName = (bayName + " Prev. " + _controller.typeName).Trim(); - Fields["curTemplate"].guiName = (bayName + " Active " + _controller.typeName).Trim(); + Fields["curTemplate"].guiName = (bayName + " Recipe").Trim(); curTemplate = _controller.Loadouts[currentLoadout].ConverterName; Events["LoadSetup"].guiName = (bayName + " " + curTemplate + "=>" + _controller.Loadouts[displayLoadout].ConverterName).Trim(); @@ -145,38 +180,69 @@ private bool CheckResources() { if (HighLogic.LoadedSceneIsEditor) return true; - var kerbal = FlightGlobals.ActiveVessel.rootPart.protoModuleCrew[0]; - if (!kerbal.HasEffect("RepairSkill")) - { - ScreenMessages.PostScreenMessage("Only Kerbals with repair skills (engineers, mechanics) can reconfigure modules!", 5f, - ScreenMessageStyle.UPPER_CENTER); - return false; - } - var allResources = true; - var missingResources = ""; - //Check that we have everything we need. - var count = _controller.SwapCosts.Count; - for (int i = 0; i < count; ++i) + if (USI_ConverterOptions.ConverterSwapRequiresRepairSkillEnabled) { - var r = _controller.SwapCosts[i]; - if (!HasResource(r)) + if (USI_ConverterOptions.ConverterSwapRequiresEVAEnabled) { - allResources = false; - missingResources += "\n" + r.Ratio + " " + r.ResourceName; + var kerbal = FlightGlobals.ActiveVessel.rootPart.protoModuleCrew[0]; + if (!kerbal.HasEffect("RepairSkill")) + { + ScreenMessages.PostScreenMessage("Only Kerbals with repair skills (e.g. engineers, mechanics) can reconfigure modules!", 5f, + ScreenMessageStyle.UPPER_CENTER); + return false; + } + } + else + { + bool foundRepairSkill = false; + var crew = FlightGlobals.ActiveVessel.GetVesselCrew(); + for (int i = 0; i < crew.Count; i++) + { + var kerbal = crew[i]; + if (kerbal.HasEffect("RepairSkill")) + { + foundRepairSkill = true; + break; + } + } + if (!foundRepairSkill) + { + ScreenMessages.PostScreenMessage("A Kerbal with repair skills (e.g. engineer, mechanic) must be on board to reconfigure modules!", 5f, + ScreenMessageStyle.UPPER_CENTER); + return false; + } } } - if (!allResources) - { - ScreenMessages.PostScreenMessage("Missing resources to change module:" + missingResources, 5f, - ScreenMessageStyle.UPPER_CENTER); - return false; - } - //Since everything is here... - for (int i = 0; i < count; ++i) + + float costMultiplier = USI_ConverterOptions.ConverterSwapCostMultiplierValue; + if (costMultiplier > ResourceUtilities.FLOAT_TOLERANCE) { - var r = _controller.SwapCosts[i]; - TakeResources(r); + var allResources = true; + var missingResources = ""; + //Check that we have everything we need. + var count = _controller.SwapCosts.Count; + for (int i = 0; i < count; ++i) + { + var resource = _controller.SwapCosts[i]; + if (!HasResource(resource)) + { + allResources = false; + missingResources += "\n" + (resource.Ratio * costMultiplier) + " " + resource.ResourceName; + } + } + if (!allResources) + { + ScreenMessages.PostScreenMessage("Missing resources to change module:" + missingResources, 5f, + ScreenMessageStyle.UPPER_CENTER); + return false; + } + //Since everything is here... + for (int i = 0; i < count; ++i) + { + var resource = _controller.SwapCosts[i]; + TakeResources(resource); + } } return true; } @@ -184,7 +250,14 @@ private bool CheckResources() private bool HasResource(ResourceRatio resInfo) { var resourceName = resInfo.ResourceName; - var needed = resInfo.Ratio; + var costMultiplier = USI_ConverterOptions.ConverterSwapCostMultiplierValue; + + if (costMultiplier <= ResourceUtilities.FLOAT_TOLERANCE) + { + return true; + } + + var needed = resInfo.Ratio * costMultiplier; var whpList = LogisticsTools.GetRegionalWarehouses(vessel, "USI_ModuleResourceWarehouse"); //EC we're a lot less picky... if (resInfo.ResourceName == "ElectricCharge") @@ -227,35 +300,40 @@ private bool HasResource(ResourceRatio resInfo) private void TakeResources(ResourceRatio resInfo) { var resourceName = resInfo.ResourceName; - var needed = resInfo.Ratio; - //Pull in from warehouses + var costMultiplier = USI_ConverterOptions.ConverterSwapCostMultiplierValue; - var whpList = LogisticsTools.GetRegionalWarehouses(vessel, "USI_ModuleResourceWarehouse"); - var count = whpList.Count; - for (int i = 0; i < count; ++i) + if (costMultiplier > 0) { - var whp = whpList[i]; - if (whp == part) - continue; - var wh = whp.FindModuleImplementing(); - if (wh != null) + var needed = resInfo.Ratio * costMultiplier; + //Pull in from warehouses + + var whpList = LogisticsTools.GetRegionalWarehouses(vessel, "USI_ModuleResourceWarehouse"); + var count = whpList.Count; + for (int i = 0; i < count; ++i) { - if (!wh.localTransferEnabled) + var whp = whpList[i]; + if (whp == part) continue; - } - if (whp.Resources.Contains(resourceName)) - { - var res = whp.Resources[resourceName]; - if (res.amount >= needed) + var wh = whp.FindModuleImplementing(); + if (wh != null) { - res.amount -= needed; - needed = 0; - break; + if (!wh.localTransferEnabled) + continue; } - else + if (whp.Resources.Contains(resourceName)) { - needed -= res.amount; - res.amount = 0; + var res = whp.Resources[resourceName]; + if (res.amount >= needed) + { + res.amount -= needed; + needed = 0; + break; + } + else + { + needed -= res.amount; + res.amount = 0; + } } } } diff --git a/USITools/USITools/Logistics/LogisticsConfig.cs b/USITools/USITools/Logistics/LogisticsConfig.cs index c63e8f5..8419821 100644 --- a/USITools/USITools/Logistics/LogisticsConfig.cs +++ b/USITools/USITools/Logistics/LogisticsConfig.cs @@ -6,6 +6,5 @@ public class LogisticsConfig public double LogisticsTime { get; set; } public double WarehouseTime { get; set; } public double MaintenanceRange { get; set; } - } } diff --git a/USITools/USITools/USITools.csproj b/USITools/USITools/USITools.csproj index 9b6f246..5be60b1 100644 --- a/USITools/USITools/USITools.csproj +++ b/USITools/USITools/USITools.csproj @@ -55,18 +55,19 @@
- - - - + + + + + + + - - + - @@ -85,7 +86,6 @@ - @@ -94,8 +94,8 @@ - - + + diff --git a/USITools/USITools/USI_DifficultyOptions.cs b/USITools/USITools/USI_DifficultyOptions.cs index 633b12f..e14b5b8 100644 --- a/USITools/USITools/USI_DifficultyOptions.cs +++ b/USITools/USITools/USI_DifficultyOptions.cs @@ -1,15 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace USITools +namespace USITools { namespace KolonyTools { public class USI_DifficultyOptions : GameParameters.CustomParameterNode { - [GameParameters.CustomParameterUI("Consume Machinery (MKS)", toolTip = "If enabled, machienry will be consumed as part of a converter's processing.", autoPersistance = true)] + [GameParameters.CustomParameterUI("Consume Machinery (MKS)", toolTip = "If enabled, machinery will be consumed as part of a converter's processing.", autoPersistance = true)] public bool ConsumeMachinery = true; public static bool ConsumeMachineryEnabled @@ -21,7 +16,6 @@ public static bool ConsumeMachineryEnabled } } - public override string Section { get From f54e076f29e0d279d4477c997185f3ca56bbbb5b Mon Sep 17 00:00:00 2001 From: doktorkrogg Date: Tue, 6 Nov 2018 19:56:54 -0500 Subject: [PATCH 13/15] Dependency injection Created a dependency injection service, similar to di in ASP.NET Core, to facilitate unit testing in WOLF and other mods down the road; unit tests written and passing; updated .gitignore to a more recent version of the standard template for Visual Studio --- .gitignore | 371 +++++++++++++++--- .../USITools.Tests.Unit/Mocks/MockServices.cs | 71 ++++ .../Mocks/MockSingletonServiceCollections.cs | 72 ++++ .../MockTransientMonoBehaviourCollection.cs | 55 +++ .../Mocks/MockTransientServiceCollections.cs | 72 ++++ .../Properties/AssemblyInfo.cs | 22 +- .../ServiceCollection_Tests.cs | 266 +++++++++++++ .../ServiceManager_Tests.cs | 199 ++++++++++ .../USITools.Tests.Unit.csproj | 118 ++++-- USITools/USITools.Tests.Unit/UnitTest1.cs | 30 -- USITools/USITools.Tests.Unit/packages.config | 17 + USITools/USITools.sln | 13 +- .../USITools/DependencyInjection/DI_README.md | 45 +++ .../DependencyInjectionExceptions.cs | 75 ++++ .../Interfaces/IDependencyService.cs | 17 + .../Interfaces/IServiceCollection.cs | 19 + .../Interfaces/IServiceManager.cs | 10 + .../DependencyInjection/ServiceCollection.cs | 77 ++++ .../DependencyInjection/ServiceDefinition.cs | 54 +++ .../DependencyInjection/ServiceManager.cs | 112 ++++++ .../USI_AddonServiceManager.cs | 29 ++ USITools/USITools/USITools.csproj | 20 +- 22 files changed, 1603 insertions(+), 161 deletions(-) create mode 100644 USITools/USITools.Tests.Unit/Mocks/MockServices.cs create mode 100644 USITools/USITools.Tests.Unit/Mocks/MockSingletonServiceCollections.cs create mode 100644 USITools/USITools.Tests.Unit/Mocks/MockTransientMonoBehaviourCollection.cs create mode 100644 USITools/USITools.Tests.Unit/Mocks/MockTransientServiceCollections.cs create mode 100644 USITools/USITools.Tests.Unit/ServiceCollection_Tests.cs create mode 100644 USITools/USITools.Tests.Unit/ServiceManager_Tests.cs delete mode 100644 USITools/USITools.Tests.Unit/UnitTest1.cs create mode 100644 USITools/USITools.Tests.Unit/packages.config create mode 100644 USITools/USITools/DependencyInjection/DI_README.md create mode 100644 USITools/USITools/DependencyInjection/DependencyInjectionExceptions.cs create mode 100644 USITools/USITools/DependencyInjection/Interfaces/IDependencyService.cs create mode 100644 USITools/USITools/DependencyInjection/Interfaces/IServiceCollection.cs create mode 100644 USITools/USITools/DependencyInjection/Interfaces/IServiceManager.cs create mode 100644 USITools/USITools/DependencyInjection/ServiceCollection.cs create mode 100644 USITools/USITools/DependencyInjection/ServiceDefinition.cs create mode 100644 USITools/USITools/DependencyInjection/ServiceManager.cs create mode 100644 USITools/USITools/DependencyInjection/USI_AddonServiceManager.cs diff --git a/.gitignore b/.gitignore index c643866..92bf3cc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,32 +1,33 @@ -# Windows image file caches -Thumbs.db -ehthumbs.db - -# Folder config file -Desktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msm -*.msp - -# ========================= -# Operating System Files -# ========================= - -# OSX -# ========================= - +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# ========================= +# Operating System Files +# ========================= + +# OSX +# ========================= + .DS_Store .AppleDouble .LSOverride # Icon must ends with two \r. -Icon +Icon + # Thumbnails ._* @@ -34,41 +35,287 @@ Icon # Files that might appear on external disk .Spotlight-V100 .Trashes - - -#OS junk files -[Tt]humbs.db -*.DS_Store - -#Visual Studio files -*.[Oo]bj -*.user -*.aps -*.pch -*.vspscc -*.vssscc -*_i.c -*_p.c -*.ncb -*.suo -*.tlb -*.tlh -*.bak -*.[Cc]ache -*.ilk -*.log -*.lib -*.sbr -*.sdf -*.opensdf -*.unsuccessfulbuild -ipch/ -[Oo]bj/ -[Bb]in -[Dd]ebug*/ -[Rr]elease*/ -Ankh.NoLoad - -#MonoDevelop -*.pidb -*.zip + + +#OS junk files +[Tt]humbs.db +*.DS_Store + +#Visual Studio files +*.[Oo]bj +*.user +*.aps +*.pch +*.vspscc +*.vssscc +*_i.c +*_p.c +*.ncb +*.suo +*.tlb +*.tlh +*.bak +*.[Cc]ache +*.ilk +*.log +*.lib +*.sbr +*.sdf +*.opensdf +*.unsuccessfulbuild +ipch/ +[Oo]bj/ +[Bb]in +[Dd]ebug*/ +[Rr]elease*/ +Ankh.NoLoad + +#MonoDevelop +*.pidb +*.zip + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +[Xx]64/ +[Xx]86/ +[Bb]uild/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml + +# TODO: Un-comment the next line if you do not want to checkin +# your web deploy settings because they may include unencrypted +# passwords +#*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Microsoft Azure ApplicationInsights config file +ApplicationInsights.config + +# Windows Store app package directory +AppPackages/ +BundleArtifacts/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# LightSwitch generated files +GeneratedArtifacts/ +ModelManifest.xml + +# Paket dependency manager +.paket/paket.exe + +# FAKE - F# Make +.fake/ diff --git a/USITools/USITools.Tests.Unit/Mocks/MockServices.cs b/USITools/USITools.Tests.Unit/Mocks/MockServices.cs new file mode 100644 index 0000000..a19b900 --- /dev/null +++ b/USITools/USITools.Tests.Unit/Mocks/MockServices.cs @@ -0,0 +1,71 @@ +using UnityEngine; + +namespace USITools.Tests.Unit +{ + interface ITestInterface { } + + class TestMonoBehaviourImplementingInterface : MonoBehaviour, ITestInterface + { + public TestMonoBehaviourImplementingInterface() { } + } + + class TestServiceImplmentingInterface : ITestInterface + { + public TestServiceImplmentingInterface() { } + } + + class AnotherTestMonoBehaviourImplementingInterface : MonoBehaviour, ITestInterface + { + public AnotherTestMonoBehaviourImplementingInterface() { } + } + + class AnotherTestServiceImplementingInterface : ITestInterface + { + public AnotherTestServiceImplementingInterface() { } + } + + class TestMonoBehaviourWithoutInterface : MonoBehaviour + { + public TestMonoBehaviourWithoutInterface() { } + } + + class TestServiceWithoutInterface + { + public TestServiceWithoutInterface() { } + } + + class TestServiceWithInterfaceDependency + { + public ITestInterface Dependency { get; private set; } + public TestServiceWithInterfaceDependency(ITestInterface dependency) + { + Dependency = dependency; + } + } + + class TestServiceWithClassDependency + { + public TestServiceWithoutInterface Dependency { get; private set; } + public TestServiceWithClassDependency(TestServiceWithoutInterface dependency) + { + Dependency = dependency; + } + } + + class TestServiceWithInterfaceAndClassDependencies + { + public ITestInterface InterfaceDependency { get; private set; } + public TestServiceWithoutInterface ClassDependency { get; private set; } + public TestServiceWithInterfaceAndClassDependencies(ITestInterface interfaceDependency, TestServiceWithoutInterface classDependency) + { + InterfaceDependency = interfaceDependency; + ClassDependency = classDependency; + } + } + + class TestServiceWithMultipleConstructors + { + public TestServiceWithMultipleConstructors(ITestInterface dependency) { } + public TestServiceWithMultipleConstructors(TestServiceWithoutInterface dependency) { } + } +} diff --git a/USITools/USITools.Tests.Unit/Mocks/MockSingletonServiceCollections.cs b/USITools/USITools.Tests.Unit/Mocks/MockSingletonServiceCollections.cs new file mode 100644 index 0000000..d4193b8 --- /dev/null +++ b/USITools/USITools.Tests.Unit/Mocks/MockSingletonServiceCollections.cs @@ -0,0 +1,72 @@ +using Moq; +using System; +using System.Collections.Generic; + +namespace USITools.Tests.Unit +{ + class MockSingletonServiceCollection + { + public IServiceCollection ServiceCollection { get; private set; } + + public MockSingletonServiceCollection() + { + var services = new Dictionary + { + { + typeof(ITestInterface), + new ServiceDefinition(typeof(TestServiceImplmentingInterface), ServiceDefinitionLifetime.Singleton) + }, + { + typeof(TestServiceWithoutInterface), + new ServiceDefinition(typeof(TestServiceWithoutInterface), ServiceDefinitionLifetime.Singleton) + }, + { + typeof(TestServiceWithInterfaceDependency), + new ServiceDefinition(typeof(TestServiceWithInterfaceDependency), ServiceDefinitionLifetime.Singleton) + }, + { + typeof(TestServiceWithClassDependency), + new ServiceDefinition(typeof(TestServiceWithClassDependency), ServiceDefinitionLifetime.Singleton) + }, + { + typeof(TestServiceWithInterfaceAndClassDependencies), + new ServiceDefinition(typeof(TestServiceWithInterfaceAndClassDependencies), ServiceDefinitionLifetime.Singleton) + } + }; + + var mock = new Mock(); + mock.Setup(x => x.Services).Returns(services); + + ServiceCollection = mock.Object; + } + } + + /// + /// Intentionally setup incorrectly to test misconfiguration error handling. + /// + class BrokenMockSingletonServiceCollection + { + public IServiceCollection ServiceCollection { get; private set; } + + public BrokenMockSingletonServiceCollection() + { + var services = new Dictionary + { + { + typeof(TestServiceImplmentingInterface), // Should be registered by the interface, not the implementing class + new ServiceDefinition(typeof(TestServiceImplmentingInterface), ServiceDefinitionLifetime.Singleton) + }, + // These services are missing their dependencies + { + typeof(TestServiceWithInterfaceDependency), + new ServiceDefinition(typeof(TestServiceWithInterfaceDependency), ServiceDefinitionLifetime.Singleton) + } + }; + + var mock = new Mock(); + mock.Setup(x => x.Services).Returns(services); + + ServiceCollection = mock.Object; + } + } +} diff --git a/USITools/USITools.Tests.Unit/Mocks/MockTransientMonoBehaviourCollection.cs b/USITools/USITools.Tests.Unit/Mocks/MockTransientMonoBehaviourCollection.cs new file mode 100644 index 0000000..79e28b6 --- /dev/null +++ b/USITools/USITools.Tests.Unit/Mocks/MockTransientMonoBehaviourCollection.cs @@ -0,0 +1,55 @@ +using Moq; +using System; +using System.Collections.Generic; + +namespace USITools.Tests.Unit +{ + class MockTransientMonoBehaviourCollection + { + public IServiceCollection ServiceCollection { get; private set; } + + public MockTransientMonoBehaviourCollection() + { + var services = new Dictionary + { + { + typeof(ITestInterface), + new ServiceDefinition(typeof(TestMonoBehaviourImplementingInterface), ServiceDefinitionLifetime.Transient, true) + }, + { + typeof(TestMonoBehaviourWithoutInterface), + new ServiceDefinition(typeof(TestMonoBehaviourWithoutInterface), ServiceDefinitionLifetime.Transient, true) + } + }; + + var mock = new Mock(); + mock.Setup(x => x.Services).Returns(services); + + ServiceCollection = mock.Object; + } + } + + /// + /// Intentionally setup incorrectly to test misconfiguration error handling. + /// + class BrokenMockTransientMonoBehaviourCollection + { + public IServiceCollection ServiceCollection { get; private set; } + + public BrokenMockTransientMonoBehaviourCollection() + { + var services = new Dictionary + { + { + typeof(TestMonoBehaviourImplementingInterface), // Should be registered by the interface, not the implementing class + new ServiceDefinition(typeof(TestMonoBehaviourImplementingInterface), ServiceDefinitionLifetime.Transient, true) + } + }; + + var mock = new Mock(); + mock.Setup(x => x.Services).Returns(services); + + ServiceCollection = mock.Object; + } + } +} diff --git a/USITools/USITools.Tests.Unit/Mocks/MockTransientServiceCollections.cs b/USITools/USITools.Tests.Unit/Mocks/MockTransientServiceCollections.cs new file mode 100644 index 0000000..1b86748 --- /dev/null +++ b/USITools/USITools.Tests.Unit/Mocks/MockTransientServiceCollections.cs @@ -0,0 +1,72 @@ +using Moq; +using System; +using System.Collections.Generic; + +namespace USITools.Tests.Unit +{ + class MockTransientServiceCollection + { + public IServiceCollection ServiceCollection { get; private set; } + + public MockTransientServiceCollection() + { + var services = new Dictionary + { + { + typeof(ITestInterface), + new ServiceDefinition(typeof(TestServiceImplmentingInterface), ServiceDefinitionLifetime.Transient) + }, + { + typeof(TestServiceWithoutInterface), + new ServiceDefinition(typeof(TestServiceWithoutInterface), ServiceDefinitionLifetime.Transient) + }, + { + typeof(TestServiceWithInterfaceDependency), + new ServiceDefinition(typeof(TestServiceWithInterfaceDependency), ServiceDefinitionLifetime.Transient) + }, + { + typeof(TestServiceWithClassDependency), + new ServiceDefinition(typeof(TestServiceWithClassDependency), ServiceDefinitionLifetime.Transient) + }, + { + typeof(TestServiceWithInterfaceAndClassDependencies), + new ServiceDefinition(typeof(TestServiceWithInterfaceAndClassDependencies), ServiceDefinitionLifetime.Transient) + } + }; + + var mock = new Mock(); + mock.Setup(x => x.Services).Returns(services); + + ServiceCollection = mock.Object; + } + } + + /// + /// Intentionally setup incorrectly to test misconfiguration error handling. + /// + class BrokenMockTransientServiceCollection + { + public IServiceCollection ServiceCollection { get; private set; } + + public BrokenMockTransientServiceCollection() + { + var services = new Dictionary + { + { + typeof(TestServiceImplmentingInterface), // Should be registered by the interface, not the implementing class + new ServiceDefinition(typeof(TestServiceImplmentingInterface), ServiceDefinitionLifetime.Transient) + }, + // These services are missing their dependencies + { + typeof(TestServiceWithInterfaceDependency), + new ServiceDefinition(typeof(TestServiceWithInterfaceDependency), ServiceDefinitionLifetime.Transient) + } + }; + + var mock = new Mock(); + mock.Setup(x => x.Services).Returns(services); + + ServiceCollection = mock.Object; + } + } +} diff --git a/USITools/USITools.Tests.Unit/Properties/AssemblyInfo.cs b/USITools/USITools.Tests.Unit/Properties/AssemblyInfo.cs index 957c16c..43bd44a 100644 --- a/USITools/USITools.Tests.Unit/Properties/AssemblyInfo.cs +++ b/USITools/USITools.Tests.Unit/Properties/AssemblyInfo.cs @@ -1,36 +1,20 @@ -using System.Reflection; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. [assembly: AssemblyTitle("USITools.Tests.Unit")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("USITools.Tests.Unit")] -[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyCopyright("Copyright © 2018")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("e79f4e83-06ed-4314-a485-3ab928670e26")] +[assembly: Guid("a7287442-01ba-490f-9e37-20ad3f1f4e39")] -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/USITools/USITools.Tests.Unit/ServiceCollection_Tests.cs b/USITools/USITools.Tests.Unit/ServiceCollection_Tests.cs new file mode 100644 index 0000000..53b8659 --- /dev/null +++ b/USITools/USITools.Tests.Unit/ServiceCollection_Tests.cs @@ -0,0 +1,266 @@ +using Xunit; + +namespace USITools.Tests.Unit +{ + public class ServiceCollection_Tests + { + [Fact] + public void Can_register_transient_service_by_class_type() + { + // Assign + var collection = new ServiceCollection(); + var expectedLifetime = ServiceDefinitionLifetime.Transient; + + // Act + collection.AddService(); + + // Assert + Assert.Contains(typeof(TestServiceWithoutInterface), collection.Services.Keys); + var service = collection.Services[typeof(TestServiceWithoutInterface)]; + Assert.Equal(expectedLifetime, service.Lifetime); + Assert.Equal(typeof(TestServiceWithoutInterface), service.Type); + Assert.Empty(service.ConstructorParams); + } + + [Fact] + public void Can_register_transient_MonoBehaviour_by_class_type() + { + // Assign + var collection = new ServiceCollection(); + var expectedLifetime = ServiceDefinitionLifetime.Transient; + + // Act + collection.AddMonoBehaviour(); + + // Assert + Assert.Contains(typeof(TestMonoBehaviourWithoutInterface), collection.Services.Keys); + var service = collection.Services[typeof(TestMonoBehaviourWithoutInterface)]; + Assert.Equal(typeof(TestMonoBehaviourWithoutInterface), service.Type); + Assert.Equal(expectedLifetime, service.Lifetime); + Assert.Empty(service.ConstructorParams); + } + + [Fact] + public void Can_register_transient_service_by_interface_type() + { + // Assign + var collection = new ServiceCollection(); + var expectedLifetime = ServiceDefinitionLifetime.Transient; + + // Act + collection.AddService(); + + // Assert + Assert.Contains(typeof(ITestInterface), collection.Services.Keys); + var service = collection.Services[typeof(ITestInterface)]; + Assert.Equal(expectedLifetime, service.Lifetime); + Assert.Equal(typeof(TestServiceImplmentingInterface), service.Type); + Assert.Empty(service.ConstructorParams); + } + + [Fact] + public void Can_register_transient_MonoBehaviour_by_interface_type() + { + // Assign + var collection = new ServiceCollection(); + var expectedLifetime = ServiceDefinitionLifetime.Transient; + + // Act + collection.AddMonoBehaviour(); + + // Assert + Assert.Contains(typeof(ITestInterface), collection.Services.Keys); + var service = collection.Services[typeof(ITestInterface)]; + Assert.Equal(expectedLifetime, service.Lifetime); + Assert.Equal(typeof(TestMonoBehaviourImplementingInterface), service.Type); + Assert.Empty(service.ConstructorParams); + } + + [Fact] + public void Can_register_singleton_service_by_class_type() + { + // Assign + var collection = new ServiceCollection(); + var expectedLifetime = ServiceDefinitionLifetime.Singleton; + + // Act + collection.AddSingletonService(); + + // Assert + Assert.Contains(typeof(TestServiceWithoutInterface), collection.Services.Keys); + var service = collection.Services[typeof(TestServiceWithoutInterface)]; + Assert.Equal(expectedLifetime, service.Lifetime); + Assert.Equal(typeof(TestServiceWithoutInterface), service.Type); + Assert.Empty(service.ConstructorParams); + } + + [Fact] + public void Can_register_singleton_MonoBehaviour_by_class_type() + { + // Assign + var collection = new ServiceCollection(); + var expectedLifetime = ServiceDefinitionLifetime.Singleton; + + // Act + collection.AddSingletonMonoBehaviour(); + + // Assert + Assert.Contains(typeof(TestMonoBehaviourWithoutInterface), collection.Services.Keys); + var service = collection.Services[typeof(TestMonoBehaviourWithoutInterface)]; + Assert.Equal(expectedLifetime, service.Lifetime); + Assert.Equal(typeof(TestMonoBehaviourWithoutInterface), service.Type); + Assert.Empty(service.ConstructorParams); + } + + [Fact] + public void Can_register_singleton_service_by_interface_type() + { + // Assign + var collection = new ServiceCollection(); + var expectedLifetime = ServiceDefinitionLifetime.Singleton; + + // Act + collection.AddSingletonService(); + + // Assert + Assert.Contains(typeof(ITestInterface), collection.Services.Keys); + var service = collection.Services[typeof(ITestInterface)]; + Assert.Equal(expectedLifetime, service.Lifetime); + Assert.Equal(typeof(TestServiceImplmentingInterface), service.Type); + Assert.Empty(service.ConstructorParams); + } + + [Fact] + public void Can_register_singleton_MonoBehaviour_by_interface_type() + { + // Assign + var collection = new ServiceCollection(); + var expectedLifetime = ServiceDefinitionLifetime.Singleton; + + // Act + collection.AddSingletonMonoBehaviour(); + + // Assert + Assert.Contains(typeof(ITestInterface), collection.Services.Keys); + var service = collection.Services[typeof(ITestInterface)]; + Assert.Equal(expectedLifetime, service.Lifetime); + Assert.Equal(typeof(TestMonoBehaviourImplementingInterface), service.Type); + Assert.Empty(service.ConstructorParams); + } + + [Fact] + public void Can_register_service_with_a_class_dependency() + { + // Assign + var collection = new ServiceCollection(); + var expectedDependencyType = typeof(TestServiceWithoutInterface); + + // Act + collection.AddService(); + + // Assert + var service = collection.Services[typeof(TestServiceWithClassDependency)]; + Assert.NotNull(service); + Assert.NotEmpty(service.ConstructorParams); + Assert.Contains(expectedDependencyType, service.ConstructorParams); + } + + [Fact] + public void Can_register_service_with_an_interface_dependency() + { + // Assign + var collection = new ServiceCollection(); + var expectedDependencyType = typeof(ITestInterface); + + // Act + collection.AddService(); + + // Assert + var service = collection.Services[typeof(TestServiceWithInterfaceDependency)]; + Assert.NotNull(service); + Assert.NotEmpty(service.ConstructorParams); + Assert.Contains(expectedDependencyType, service.ConstructorParams); + } + + [Fact] + public void Can_register_service_with_multiple_dependencies() + { + // Assign + var collection = new ServiceCollection(); + var expectedDependencyType1 = typeof(ITestInterface); + var expectedDependencyType2 = typeof(TestServiceWithoutInterface); + + // Act + collection.AddService(); + + // Assert + var service = collection.Services[typeof(TestServiceWithInterfaceAndClassDependencies)]; + Assert.NotNull(service); + Assert.NotEmpty(service.ConstructorParams); + Assert.Contains(expectedDependencyType1, service.ConstructorParams); + Assert.Contains(expectedDependencyType2, service.ConstructorParams); + } + + [Fact] + public void Should_not_allow_registering_multiple_MonoBehaviours_by_the_same_class_type() + { + // Assign + var collection = new ServiceCollection(); + + // Act + collection.AddMonoBehaviour(); + + // Assert + Assert.Throws(() => collection.AddMonoBehaviour()); + } + + [Fact] + public void Should_not_allow_registering_multiple_services_by_the_same_class_type() + { + // Assign + var collection = new ServiceCollection(); + + // Act + collection.AddService(); + + // Assert + Assert.Throws(() => collection.AddService()); + } + + [Fact] + public void Should_not_allow_registering_multiple_MonoBehaviours_by_the_same_interface_type() + { + // Assign + var collection = new ServiceCollection(); + + // Act + collection.AddMonoBehaviour(); + + // Assert + Assert.Throws(() => collection.AddMonoBehaviour()); + } + + [Fact] + public void Should_not_allow_registering_multiple_services_by_the_same_interface_type() + { + // Assign + var collection = new ServiceCollection(); + + // Act + collection.AddService(); + + // Assert + Assert.Throws(() => collection.AddService()); + } + + [Fact] + public void Should_not_allow_registering_a_service_with_multiple_constructors() + { + // Assign + var collection = new ServiceCollection(); + + // Assert + Assert.Throws(() => collection.AddService()); + } + } +} diff --git a/USITools/USITools.Tests.Unit/ServiceManager_Tests.cs b/USITools/USITools.Tests.Unit/ServiceManager_Tests.cs new file mode 100644 index 0000000..98c7f33 --- /dev/null +++ b/USITools/USITools.Tests.Unit/ServiceManager_Tests.cs @@ -0,0 +1,199 @@ +using System; +using UnityEngine; +using Xunit; + +namespace USITools.Tests.Unit +{ + public class ServiceManager_Tests + { + private IServiceCollection _transientServiceCollection; + private IServiceCollection _singletonServiceCollection; + private IServiceCollection _monobehaviourCollection; + private IServiceCollection _brokenServiceCollection; + + public ServiceManager_Tests() + { + var mockTransientServiceCollection = new MockTransientServiceCollection(); + _transientServiceCollection = mockTransientServiceCollection.ServiceCollection; + + var mockSingletonServiceCollection = new MockSingletonServiceCollection(); + _singletonServiceCollection = mockSingletonServiceCollection.ServiceCollection; + + var mockMonoBehaviourServiceCollection = new BrokenMockTransientMonoBehaviourCollection(); + _monobehaviourCollection = mockMonoBehaviourServiceCollection.ServiceCollection; + + var mockBrokenServiceCollection = new BrokenMockTransientServiceCollection(); + _brokenServiceCollection = mockBrokenServiceCollection.ServiceCollection; + } + + [Fact] + public void Can_get_a_singleton_instance_of_a_service() + { + // Assign + IServiceManager serviceManager = new ServiceManager(_singletonServiceCollection); + + // Act + var firstService = serviceManager.GetService(); + var secondService = serviceManager.GetService(); + + // Assert + Assert.NotNull(firstService); + Assert.NotNull(secondService); + Assert.Equal(firstService, secondService); + } + + [Fact] + public void Can_get_a_transient_instance_of_a_service() + { + // Assign + IServiceManager serviceManager = new ServiceManager(_transientServiceCollection); + + // Act + var firstService = serviceManager.GetService(); + var secondService = serviceManager.GetService(); + + // Assert + Assert.NotNull(firstService); + Assert.NotNull(secondService); + Assert.NotEqual(firstService, secondService); + Assert.IsType(firstService); + } + + [Fact] + public void Can_get_a_service_instance_by_interface_type() + { + // Assign + IServiceManager serviceManager = new ServiceManager(_transientServiceCollection); + + // Act + var service = serviceManager.GetService(); + + // Assert + Assert.NotNull(service); + Assert.IsType(service); + } + + [Fact] + public void Can_get_a_service_instance_with_interface_dependencies() + { + // Assign + IServiceManager serviceManager = new ServiceManager(_transientServiceCollection); + + // Act + var service = serviceManager.GetService(); + + // Assert + Assert.NotNull(service); + var serviceWithDependencies = Assert.IsType(service); + var dependency = serviceWithDependencies.Dependency; + Assert.NotNull(dependency); + Assert.IsType(dependency); + } + + [Fact] + public void Can_get_a_service_instance_with_class_dependencies() + { + // Assign + IServiceManager serviceManager = new ServiceManager(_transientServiceCollection); + + // Act + var service = serviceManager.GetService(); + + // Assert + Assert.NotNull(service); + var serviceWithDependencies = Assert.IsType(service); + var dependency = serviceWithDependencies.Dependency; + Assert.NotNull(dependency); + } + + [Fact] + public void Can_get_a_service_instance_with_multiple_dependencies() + { + // Assign + IServiceManager serviceManager = new ServiceManager(_transientServiceCollection); + + // Act + var service = serviceManager.GetService(); + + // Assert + Assert.NotNull(service); + var serviceWithDependencies = Assert.IsType(service); + var classDependency = serviceWithDependencies.ClassDependency; + var interfaceDependency = serviceWithDependencies.InterfaceDependency; + Assert.NotNull(classDependency); + Assert.NotNull(interfaceDependency); + Assert.IsType(classDependency); + Assert.IsAssignableFrom(interfaceDependency); + } + + /* + * We can't test any of the MonoBehaviour-related methods outside of Unity, unfortunately + */ + //[Fact] + //public void Can_get_a_MonoBehaviour_instance() + //{ + // // Assign + // ServiceManager serviceManager = new ServiceManager(_monobehaviourCollection); + + // // Act + // var service = serviceManager.GetMonoBehaviour(); + + // // Assert + // Assert.NotNull(service); + // Assert.IsType(service); + // Assert.IsAssignableFrom(service); + //} + + [Fact] + public void Services_with_transient_dependencies_should_get_a_transient_instance() + { + // Assign + IServiceManager serviceManager = new ServiceManager(_transientServiceCollection); + + // Act + var dependency = serviceManager.GetService(); + var service = serviceManager.GetService(); + + // Assert + Assert.NotNull(service); + Assert.NotNull(dependency); + Assert.NotEqual(dependency, service.Dependency); + } + + [Fact] + public void Services_with_singleton_dependencies_should_get_the_singleton_instance() + { + // Assign + IServiceManager serviceManager = new ServiceManager(_singletonServiceCollection); + + // Act + var dependency = serviceManager.GetService(); + var service = serviceManager.GetService(); + + // Assert + Assert.NotNull(service); + Assert.NotNull(dependency); + Assert.Equal(dependency, service.Dependency); + } + + [Fact] + public void Should_throw_exception_when_unregistered_service_is_requested() + { + // Assign + IServiceManager serviceManager = new ServiceManager(_transientServiceCollection); + + // Assert + Assert.Throws(() => serviceManager.GetService()); + } + + [Fact] + public void Should_throw_exception_when_dependencies_arent_registered_for_a_service() + { + // Assign + IServiceManager serviceManager = new ServiceManager(_brokenServiceCollection); + + // Assert + Assert.Throws(() => serviceManager.GetService()); + } + } +} diff --git a/USITools/USITools.Tests.Unit/USITools.Tests.Unit.csproj b/USITools/USITools.Tests.Unit/USITools.Tests.Unit.csproj index 846bb10..53839c6 100644 --- a/USITools/USITools.Tests.Unit/USITools.Tests.Unit.csproj +++ b/USITools/USITools.Tests.Unit/USITools.Tests.Unit.csproj @@ -1,21 +1,27 @@  - + + + + + Debug AnyCPU - {1862B298-A4FA-401E-AD46-9091C29B1BA2} + {A7287442-01BA-490F-9E37-20AD3F1F4E39} Library Properties USITools.Tests.Unit USITools.Tests.Unit - v3.5 + v4.6.1 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 10.0 + 15.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages False UnitTest + + true @@ -35,52 +41,78 @@ 4 + + ..\packages\Castle.Core.4.3.1\lib\net45\Castle.Core.dll + + + ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + + ..\packages\Moq.4.10.0\lib\net45\Moq.dll + - - 3.5 + + + + ..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll + + + ..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll + + + ..\..\..\..\KSP_DEV\KSP_x64_Data\Managed\UnityEngine.dll + True + + + ..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll + + + ..\packages\xunit.assert.2.4.1\lib\netstandard1.1\xunit.assert.dll + + + ..\packages\xunit.extensibility.core.2.4.1\lib\net452\xunit.core.dll + + + ..\packages\xunit.extensibility.execution.2.4.1\lib\net452\xunit.execution.desktop.dll - - - - - - - - - - - - - + + + + + + + + + + + + + {8699bbcb-15f3-40c6-a001-46e08886360c} + USITools + True + + + + - - - - - False - - - False - - - False - - - False - - - - - + + \ No newline at end of file diff --git a/USITools/USITools.Tests.Unit/UnitTest1.cs b/USITools/USITools.Tests.Unit/UnitTest1.cs deleted file mode 100644 index 4d6a73a..0000000 --- a/USITools/USITools.Tests.Unit/UnitTest1.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Linq.Expressions; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace USITools.Tests.Unit -{ - [TestClass] - public class when_reading_a_byte_stream - { - [TestMethod] - public void Should_be_able_to_compress_a_byte_stream() - { - var stream = GetSampleByteArray(); - - } - - private byte[] GetSampleByteArray() - { - var b = new byte[4096]; - var r = new Random(); - for (int i = 0; i < 4096; i++) - { - var element = (byte)r.Next(0, 255); - b[i] = element; - } - return b; - } - } -} - \ No newline at end of file diff --git a/USITools/USITools.Tests.Unit/packages.config b/USITools/USITools.Tests.Unit/packages.config new file mode 100644 index 0000000..b10dde6 --- /dev/null +++ b/USITools/USITools.Tests.Unit/packages.config @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/USITools/USITools.sln b/USITools/USITools.sln index c317c1e..590059d 100644 --- a/USITools/USITools.sln +++ b/USITools/USITools.sln @@ -1,10 +1,12 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.21005.1 +# Visual Studio 15 +VisualStudioVersion = 15.0.28010.2048 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "USITools", "USITools\USITools.csproj", "{8699BBCB-15F3-40C6-A001-46E08886360C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "USITools.Tests.Unit", "USITools.Tests.Unit\USITools.Tests.Unit.csproj", "{A7287442-01BA-490F-9E37-20AD3F1F4E39}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,8 +17,15 @@ Global {8699BBCB-15F3-40C6-A001-46E08886360C}.Debug|Any CPU.Build.0 = Debug|Any CPU {8699BBCB-15F3-40C6-A001-46E08886360C}.Release|Any CPU.ActiveCfg = Release|Any CPU {8699BBCB-15F3-40C6-A001-46E08886360C}.Release|Any CPU.Build.0 = Release|Any CPU + {A7287442-01BA-490F-9E37-20AD3F1F4E39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A7287442-01BA-490F-9E37-20AD3F1F4E39}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A7287442-01BA-490F-9E37-20AD3F1F4E39}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A7287442-01BA-490F-9E37-20AD3F1F4E39}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {21E39270-C22B-4444-86C5-967F5691F5C6} + EndGlobalSection EndGlobal diff --git a/USITools/USITools/DependencyInjection/DI_README.md b/USITools/USITools/DependencyInjection/DI_README.md new file mode 100644 index 0000000..056de18 --- /dev/null +++ b/USITools/USITools/DependencyInjection/DI_README.md @@ -0,0 +1,45 @@ +# How to Dependency Inject + +The concept for this implementation of DI was inspired by ASP.NET Core-style DI (https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1). So if you're already familiar with that, then this should feel similar. + +## Summary + +You use an `IServiceCollection` to setup your dependencies and an `IServiceManager` to request instances of services at runtime. USITools comes with concrete implementations of these interfaces called simply `ServiceCollection` and `ServiceManager` that should suffice for most use cases. The `ServiceCollection` will create a `ServiceDefinition` for each service you register that `ServiceManager` then uses to determine how to instantiate your services. + +### The really cool thing about this system... + +...is that `ServiceManager` will automagically instantiate all the dependencies required by the services you request. So if you have a class `Widget` that requires an instance of `Gizmo`, the `ServiceManager` will automatically create a `Gizmo` and pass it to the `Widget` via it's constructor before passing the `Widget` back to you. Neat! It's even smart enough to know that if `Gizmo` is a singleton, to pass the singleton instance of it to `Widget` instead of creating a new instance. This eliminates the need to do things like setup static instance properties on a class and use getters that instantiate singletons as side effects. Bleck. + +## Registering Services + +The initial setup is fairly simple: + +* Create a `ServiceCollection` to register all the service classes that will participate in DI. Service classes can be registered several different ways: + * Typically, you should be using interfaces in order to facilitate unit testing. That's kinda the whole reason we use DI in the first place. So constructors should be written to accept interfaces as parameters and service classes should be registered in DI using the `Add...` methods that accept **two** type parameters. The first parameter is the interface type, the second is the concrete type that implements the interface. + * If you want to inject something like a Unity or KSP class as a dependency, the `Add...` methods that accept a single type parameter will happily allow that. + * For services that should only ever be instantiated once and reused, use one of the `AddSingleton...` methods. + * If you want a new instance of a service every time you request the service from `ServiceManager`, use the `AddService...` methods. + +## Consuming Services + +* Create a `ServiceManager`, passing in your `ServiceCollection` via its constructor, and then call one of the `Get...` methods. That's all there is to it! +* For services that are registered via interface, you will pass the **interface** type to the `Get...` method, not the concrete type. The `ServiceManager` will know which concrete implementation to return based on what you configured in the `ServiceCollection`. +* The `ServiceCollection` and `ServiceManager` have to be bootstrapped somehow. We currently do it in a `KSPAddon`. +* There can be multiple `ServiceManager` instances running at once. They don't interoperate though. So if you have a class with dependencies handled by different `ServiceManager` instances, you will only be able to get automatic dependency injection from one of them. That said, you could probaby accomplish some trickery using the `IDependencyService` interface explained in the MonoBehaviours section of this document. + +## MonoBehaviours + +`MonoBehaviour` classes require a different approach in order to be used with DI because they can't accept parameters via constructor. `IDependencyService` to the rescue! The `ServiceManager` will check each registered service type to see if it's assignable from `IDependencyService` and if so, it will bypass automatic dependency injection and call the service's `SetServiceManager` method instead, passing itself in as the paramater. + +**Note: Classes that implement `IDependencyService` must have a parameterless constructor.** + +The `MonoBehaviour` then will have to be responsible for requesting its dependencies from the `ServiceManager` manually. The upside is that `ServiceManager` will automatically create game objects for you, prevent them from being destroyed if they are registered as singletons and will keep track of singleton `MonoBehaviour` instances for you just like it would with a normal service. + +`MonoBehaviour`s are registered in the `ServiceCollection` via the `AddMonoBehaviour...` methods. + +## Dev Notes + +* `IServiceCollection` is setup for fluent method chaining to make setting up multiple services a little easier. +* The order services are registered doesn't matter. +* Services that implement the same interface can't be registered in the `ServiceCollection` at the same time. That is to say, they can't both be registered implementations of the interface. One could be registered as a standalone service but then why have it implement the same interface as the other? If you find yourself wanting to register multiple classes in DI under the same interface, you probably need to reconsider your design. +* `MonoBehaviour`s should be used sparingly. They're best used as wrappers around a POCO that can participate fully in DI. \ No newline at end of file diff --git a/USITools/USITools/DependencyInjection/DependencyInjectionExceptions.cs b/USITools/USITools/DependencyInjection/DependencyInjectionExceptions.cs new file mode 100644 index 0000000..7b40956 --- /dev/null +++ b/USITools/USITools/DependencyInjection/DependencyInjectionExceptions.cs @@ -0,0 +1,75 @@ +using System; + +namespace USITools +{ + /// + /// Thrown when a service class has already been registered in an . + /// + public class ServiceAlreadyRegisteredException : Exception + { + private static string messageTemplate = "{0} is already registered."; + + public ServiceAlreadyRegisteredException(Type type) + : base(string.Format(messageTemplate, type.Name)) { } + } + + /// + /// Thrown when an attempt is made to register a service class with multiple constructors in an . + /// + public class ServiceHasTooManyConstructorsException : Exception + { + private static string messageTemplate + = "Cannot register {0}. Only classes with a single constructor can be used as a service."; + + public ServiceHasTooManyConstructorsException(Type type) + : base(string.Format(messageTemplate, type.Name)) { } + } + + /// + /// Thrown when an attempt is made to register a MonoBehaviour service class with a parameterized constructor in an . + /// + public class MonoBehaviourServiceCannotHaveParameterizedConstructorException : Exception + { + private static string messageTemplate + = "Cannot register {0}. MonoBehaviours cannot accept dependencies via constructor. Setup dependencies in Awake or Start methods."; + + public MonoBehaviourServiceCannotHaveParameterizedConstructorException(Type type) + : base(string.Format(messageTemplate, type.Name)) { } + } + + /// + /// Thrown by when a request is made for a service + /// that was not registered in its . + /// + public class ServiceNotRegisteredException : Exception + { + private static string messageTemplate = "{0} is not registered as a service."; + + public ServiceNotRegisteredException(Type type) + : base(string.Format(messageTemplate, type.Name)) { } + } + + /// + /// Thrown by when a request is made for a service + /// that has a dependency which is not registered in its . + /// + public class ServiceDependencyNotRegisteredException : Exception + { + private static string messageTemplate = "{0} is dependent on {1}, which is not registered as a service."; + + public ServiceDependencyNotRegisteredException(Type type, Type dependency) + : base(string.Format(messageTemplate, type.Name, dependency.Name)) { } + } + + /// + /// Thrown by when a request is made for a + /// service that isn't a . + /// + public class ServiceNotAssignableFromMonoBehaviourException : Exception + { + private static string messageTemplate = "{0} is not a MonoBehaviour."; + + public ServiceNotAssignableFromMonoBehaviourException(Type type) + : base(string.Format(messageTemplate, type.Name)) { } + } +} diff --git a/USITools/USITools/DependencyInjection/Interfaces/IDependencyService.cs b/USITools/USITools/DependencyInjection/Interfaces/IDependencyService.cs new file mode 100644 index 0000000..d0c236c --- /dev/null +++ b/USITools/USITools/DependencyInjection/Interfaces/IDependencyService.cs @@ -0,0 +1,17 @@ +using UnityEngine; + +namespace USITools +{ + /// + /// Classes that want to participate in automatic dependency injection, but cannot + /// accept constructor parameters, should implement this interface instead. + /// + /// + /// The primary use case for this interface is for s, + /// since they can't accept dependencies via constructor. + /// + public interface IDependencyService + { + void SetServiceManager(IServiceManager serviceManager); + } +} diff --git a/USITools/USITools/DependencyInjection/Interfaces/IServiceCollection.cs b/USITools/USITools/DependencyInjection/Interfaces/IServiceCollection.cs new file mode 100644 index 0000000..6bdb9bd --- /dev/null +++ b/USITools/USITools/DependencyInjection/Interfaces/IServiceCollection.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace USITools +{ + public interface IServiceCollection + { + Dictionary Services { get; } + IServiceCollection AddService() where T : class, U; + IServiceCollection AddService() where T : class; + IServiceCollection AddMonoBehaviour() where T : MonoBehaviour, U; + IServiceCollection AddMonoBehaviour() where T : MonoBehaviour; + IServiceCollection AddSingletonService() where T : class, U; + IServiceCollection AddSingletonService() where T : class; + IServiceCollection AddSingletonMonoBehaviour() where T : MonoBehaviour; + IServiceCollection AddSingletonMonoBehaviour() where T : MonoBehaviour, U; + } +} diff --git a/USITools/USITools/DependencyInjection/Interfaces/IServiceManager.cs b/USITools/USITools/DependencyInjection/Interfaces/IServiceManager.cs new file mode 100644 index 0000000..b3e596f --- /dev/null +++ b/USITools/USITools/DependencyInjection/Interfaces/IServiceManager.cs @@ -0,0 +1,10 @@ +using System; + +namespace USITools +{ + public interface IServiceManager + { + object GetService(Type type); + T GetService() where T : class; + } +} diff --git a/USITools/USITools/DependencyInjection/ServiceCollection.cs b/USITools/USITools/DependencyInjection/ServiceCollection.cs new file mode 100644 index 0000000..2872332 --- /dev/null +++ b/USITools/USITools/DependencyInjection/ServiceCollection.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace USITools +{ + /// + /// Use this to register services that can provide. + /// + public class ServiceCollection : IServiceCollection + { + public Dictionary Services { get; private set; } + = new Dictionary(); + + private void DoAddService(ServiceDefinitionLifetime lifetime, bool isMonoBehaviour = false) + { + if (Services.ContainsKey(typeof(U))) + throw new ServiceAlreadyRegisteredException(typeof(U)); + + var definition = new ServiceDefinition(typeof(T), lifetime, isMonoBehaviour); + + Services.Add(typeof(U), definition); + } + + public IServiceCollection AddService() + where T : class, U + { + DoAddService(ServiceDefinitionLifetime.Transient); + return this; + } + + public IServiceCollection AddService() + where T : class + { + return AddService(); + } + + public IServiceCollection AddMonoBehaviour() + where T : MonoBehaviour, U + { + DoAddService(ServiceDefinitionLifetime.Transient, true); + return this; + } + + public IServiceCollection AddMonoBehaviour() + where T : MonoBehaviour + { + return AddMonoBehaviour(); + } + + public IServiceCollection AddSingletonService() + where T : class, U + { + DoAddService(ServiceDefinitionLifetime.Singleton); + return this; + } + + public IServiceCollection AddSingletonService() + where T : class + { + return AddSingletonService(); + } + + public IServiceCollection AddSingletonMonoBehaviour() + where T : MonoBehaviour, U + { + DoAddService(ServiceDefinitionLifetime.Singleton, true); + return this; + } + + public IServiceCollection AddSingletonMonoBehaviour() + where T : MonoBehaviour + { + return AddSingletonMonoBehaviour(); + } + } +} diff --git a/USITools/USITools/DependencyInjection/ServiceDefinition.cs b/USITools/USITools/DependencyInjection/ServiceDefinition.cs new file mode 100644 index 0000000..33e822c --- /dev/null +++ b/USITools/USITools/DependencyInjection/ServiceDefinition.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace USITools +{ + /// + /// Used by to determine the intended lifetime of a requested service. + /// + /// + /// Scoped services aren't a thing yet. We'll make them a thing if the need ever arises. + /// + public enum ServiceDefinitionLifetime { Scoped, Singleton, Transient } + + /// + /// Used by to determine how to instantiate a requested service. + /// + public class ServiceDefinition + { + public Type Type { get; private set; } + public ServiceDefinitionLifetime Lifetime { get; private set; } + public ConstructorInfo ConstructorInfo { get; private set; } + public List ConstructorParams { get; private set; } + + public ServiceDefinition(Type type, ServiceDefinitionLifetime lifetime, bool isMonoBehaviour = false) + { + Type = type; + Lifetime = lifetime; + + GetConstructorInfo(isMonoBehaviour); + } + + private void GetConstructorInfo(bool isMonoBehaviour) + { + var constructors = Type.GetConstructors(); + if (constructors.Length > 1) + throw new ServiceHasTooManyConstructorsException(Type); + + ConstructorInfo = constructors[0]; + var constructorParams = ConstructorInfo.GetParameters(); + + // MonoBehaviours cannot have parameterized constructors + if (isMonoBehaviour && constructorParams.Count() > 0) + throw new MonoBehaviourServiceCannotHaveParameterizedConstructorException(Type); + + // We cache the constructor params to avoid unnecessary Linq and foreach queries + // every time a service instance is requested. + ConstructorParams = constructorParams + .Select(p => p.ParameterType) + .ToList(); + } + } +} diff --git a/USITools/USITools/DependencyInjection/ServiceManager.cs b/USITools/USITools/DependencyInjection/ServiceManager.cs new file mode 100644 index 0000000..f050999 --- /dev/null +++ b/USITools/USITools/DependencyInjection/ServiceManager.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace USITools +{ + /// + /// Provides instances of service classes, dependency injection-style. + /// + /// + /// This is most useful for managing singletons and making classes + /// easier to unit test. This eliminates the need to setup static + /// properties on every class that is intended to be used as a singleton. + /// It also makes it easier for transient classes to consume singletons. + /// + public class ServiceManager : IServiceManager + { + private readonly IServiceCollection _collection; + private readonly Dictionary _singletons = new Dictionary(); + + public ServiceManager(IServiceCollection collection) + { + _collection = collection; + } + + public T GetService() + where T : class + { + return GetService(typeof(T)) as T; + } + + public object GetService(Type type) + { + if (!HasRegisteredService(type)) + throw new ServiceNotRegisteredException(type); + + // Short circuit if there is already a singleton instance of this type + if (_singletons.ContainsKey(type)) + return _singletons[type]; + + var serviceDefinition = _collection.Services[type]; + + object service; + if (type.IsAssignableFrom(typeof(IDependencyService))) + { + service = serviceDefinition.ConstructorInfo.Invoke(new object[0]); + (service as IDependencyService).SetServiceManager(this); + } + else + { + var serviceParams = new List(); + var serviceParamTypes = serviceDefinition.ConstructorParams; + for (int i = 0; i < serviceParamTypes.Count; i++) + { + var dependencyType = serviceParamTypes[i]; + try + { + serviceParams.Add(GetService(dependencyType)); + } + catch (ServiceNotRegisteredException) + { + throw new ServiceDependencyNotRegisteredException(type, dependencyType); + } + } + + service = serviceDefinition.ConstructorInfo.Invoke(serviceParams.ToArray()); + } + + if (serviceDefinition.Lifetime == ServiceDefinitionLifetime.Singleton) + { + _singletons.Add(type, service); + } + + return service; + } + + public T GetMonoBehaviour() + where T : class + { + return GetMonoBehaviour(typeof(T)) as T; + } + + public MonoBehaviour GetMonoBehaviour(Type type) + { + if (!HasRegisteredService(type)) + throw new ServiceNotRegisteredException(type); + + // Short circuit if there is already a singleton instance of this type + if (_singletons.ContainsKey(type)) + return _singletons[type] as MonoBehaviour; + + var service = new GameObject(type.Name + "_" + Guid.NewGuid().ToString()).AddComponent(type); + + if (type.IsAssignableFrom(typeof(IDependencyService))) + (service as IDependencyService).SetServiceManager(this); + + var serviceDefinition = _collection.Services[type]; + if (serviceDefinition.Lifetime == ServiceDefinitionLifetime.Singleton) + { + _singletons.Add(type, service); + UnityEngine.Object.DontDestroyOnLoad(service.gameObject); + } + + return service as MonoBehaviour; + } + + private bool HasRegisteredService(Type type) + { + return _collection.Services.ContainsKey(type); + } + } +} diff --git a/USITools/USITools/DependencyInjection/USI_AddonServiceManager.cs b/USITools/USITools/DependencyInjection/USI_AddonServiceManager.cs new file mode 100644 index 0000000..5880e15 --- /dev/null +++ b/USITools/USITools/DependencyInjection/USI_AddonServiceManager.cs @@ -0,0 +1,29 @@ +using UnityEngine; + +namespace USITools +{ + [KSPAddon(KSPAddon.Startup.AllGameScenes, true)] + public class USI_AddonServiceManager : MonoBehaviour + { + public static USI_AddonServiceManager Instance { get; private set; } + public ServiceManager ServiceManager { get; private set; } + + void Awake() + { + if (Instance != null) + { + Destroy(gameObject); + return; + } + + Instance = this; + DontDestroyOnLoad(gameObject); + + // Setup dependency injection for USI classes + // ...when we have some to setup ^_^ + var collection = new ServiceCollection(); + + ServiceManager = new ServiceManager(collection); + } + } +} diff --git a/USITools/USITools/USITools.csproj b/USITools/USITools/USITools.csproj index 5be60b1..04b5866 100644 --- a/USITools/USITools/USITools.csproj +++ b/USITools/USITools/USITools.csproj @@ -32,11 +32,11 @@ - ..\..\..\..\KSP_DEV\KSP_Data\Managed\Assembly-CSharp.dll + ..\..\..\..\KSP_DEV\KSP_x64_Data\Managed\Assembly-CSharp.dll False - ..\..\..\..\KSP_DEV\KSP_Data\Managed\Assembly-CSharp-firstpass.dll + ..\..\..\..\KSP_DEV\KSP_x64_Data\Managed\Assembly-CSharp-firstpass.dll False @@ -46,11 +46,11 @@ - ..\..\..\..\KSP_DEV\KSP_Data\Managed\UnityEngine.dll + ..\..\..\..\KSP_DEV\KSP_x64_Data\Managed\UnityEngine.dll False - ..\..\..\..\KSP_DEV\KSP_Data\Managed\UnityEngine.UI.dll + ..\..\..\..\KSP_DEV\KSP_x64_Data\Managed\UnityEngine.UI.dll False @@ -67,8 +67,14 @@ + + + + + + @@ -130,12 +136,16 @@ + + - + + + "D:\Games\pdb2mdb\pdb2mdb.exe" "$(TargetFileName)" From d663a6d2abe9d6418f977bf9ce082a3be2b6861e Mon Sep 17 00:00:00 2001 From: Bob Palmer Date: Tue, 20 Nov 2018 08:39:36 -0500 Subject: [PATCH 14/15] 2018.11.20 Release --- FOR_RELEASE/GameData/000_USITools/CHANGELOG.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FOR_RELEASE/GameData/000_USITools/CHANGELOG.txt b/FOR_RELEASE/GameData/000_USITools/CHANGELOG.txt index 1d37ac2..a900394 100644 --- a/FOR_RELEASE/GameData/000_USITools/CHANGELOG.txt +++ b/FOR_RELEASE/GameData/000_USITools/CHANGELOG.txt @@ -1,4 +1,4 @@ -1.0.0 - 2018.10.20 +1.0.0 - 2018.11.20 ------------------ KSP 1.5.0 Compatibility From e7b70bce575762c231c8a1e7c9e018513b6172d3 Mon Sep 17 00:00:00 2001 From: Bob Palmer Date: Mon, 4 Feb 2019 21:27:45 -0500 Subject: [PATCH 15/15] KSP 1.6.x --- .../GameData/000_USITools/CHANGELOG.txt | 4 ++++ .../GameData/000_USITools/USITools.dll | Bin 111104 -> 112640 bytes .../GameData/000_USITools/USITools.version | 8 ++++---- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/FOR_RELEASE/GameData/000_USITools/CHANGELOG.txt b/FOR_RELEASE/GameData/000_USITools/CHANGELOG.txt index a900394..4eb4163 100644 --- a/FOR_RELEASE/GameData/000_USITools/CHANGELOG.txt +++ b/FOR_RELEASE/GameData/000_USITools/CHANGELOG.txt @@ -1,3 +1,7 @@ +1.1.0 - 2019.02.05 +------------------ +KSP 1.6.x Compatibility + 1.0.0 - 2018.11.20 ------------------ KSP 1.5.0 Compatibility diff --git a/FOR_RELEASE/GameData/000_USITools/USITools.dll b/FOR_RELEASE/GameData/000_USITools/USITools.dll index e1dcc8fc466c80f3349c4625af86ae509a784c40..a3a4d6d6fb6d40ec6cd433da065714b7ace21bbc 100644 GIT binary patch literal 112640 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%0DWnR#TcCxKnW16}Cva)=P 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 z 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 K{~39N68H};`6T=R diff --git a/FOR_RELEASE/GameData/000_USITools/USITools.version b/FOR_RELEASE/GameData/000_USITools/USITools.version index 2fb026c..6e22adb 100644 --- a/FOR_RELEASE/GameData/000_USITools/USITools.version +++ b/FOR_RELEASE/GameData/000_USITools/USITools.version @@ -9,23 +9,23 @@ }, "VERSION":{ "MAJOR":1, - "MINOR":0, + "MINOR":1, "PATCH":0, "BUILD":0 }, "KSP_VERSION":{ "MAJOR":1, - "MINOR":5, + "MINOR":6, "PATCH":0 }, "KSP_VERSION_MIN":{ "MAJOR":1, - "MINOR":5, + "MINOR":6, "PATCH":0 }, "KSP_VERSION_MAX":{ "MAJOR":1, - "MINOR":5, + "MINOR":6, "PATCH":9 } } \ No newline at end of file