diff --git a/GameData/KSPCommunityPartModules/Localization/en-us.cfg b/GameData/KSPCommunityPartModules/Localization/en-us.cfg index d0f4d05..42c0ac6 100644 --- a/GameData/KSPCommunityPartModules/Localization/en-us.cfg +++ b/GameData/KSPCommunityPartModules/Localization/en-us.cfg @@ -4,5 +4,11 @@ Localization { // ModuleAutoCutDrogue #KSPCPM_CutDrogues = Auto-Cut Drogue Chute(s) + + // ModuleToggleTracking + #KSPCPM_Tracking = Tracking + #KSPCPM_ToggleTracking = Toggle Tracking + #KSPCPM_DisableTracking = Disable Tracking + #KSPCPM_EnableTracking = Enable Tracking } } diff --git a/Source/Modules/ModuleAttachmentVisuals.cs b/Source/Modules/ModuleAttachmentVisuals.cs new file mode 100644 index 0000000..35d78c5 --- /dev/null +++ b/Source/Modules/ModuleAttachmentVisuals.cs @@ -0,0 +1,282 @@ +/* + Usecase: Part Gameobject Visibility based on Stack Attachment Node Occupancy with support for multiple nodes per part. + Originally By: Spartwo + Originally For: Kerbal Powers + License: GNU General Public License v3.0, see https://www.gnu.org/licenses/gpl-3.0.html +*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace KSPCommunityPartModules.Modules +{ + public class ModuleAttachmentVisuals : PartModule + { + [KSPField] + public string requiredNodes; + + //visible transforms when node occupied + [KSPField] + public string showAttached; + + //visible transforms when node unoccupied + [KSPField] + public string showFree; + + //"Enable/Disable " in the editor + [KSPField] + public string objectDisplayName; + + [KSPEvent( + guiActive = false, + guiActiveEditor = true, + guiName = "#KSPCPM_Capping" + )] + public void EventToggleVisual() => ToggleVisual(); + + [KSPField(isPersistant = true)] + public bool transformEnabled = true; + + // Nodes used as conditions + private List nodes = new List(); + + // Transforms shown when nodes are occupied + private List attachedTransforms = new List(); + + // Transforms shown when nodes are free + private List freeTransforms = new List(); + + private HashSet directChildren = new HashSet(); + + public override void OnStart(StartState state) + { + base.OnStart(state); + + if (HighLogic.LoadedSceneIsEditor) + { + GameEvents.onEditorPartEvent.Add(OnEditorEvent); + } + + CacheInitialChildren(); + ParseConfig(); + UpdateVisuals(); + + // make this module cheaper in update loops + isEnabled = false; + enabled = false; + } + + public void OnDestroy() + { + if (HighLogic.LoadedSceneIsEditor) + { + GameEvents.onEditorPartEvent.Remove(OnEditorEvent); + } + } + + private void ParseConfig() + { + nodes.Clear(); + attachedTransforms.Clear(); + freeTransforms.Clear(); + + // Parse attachment nodes + if (!string.IsNullOrWhiteSpace(requiredNodes)) + { + foreach (string nodeName in requiredNodes.Split(';')) + { + string nodeId = nodeName.Trim(); + + if (string.IsNullOrEmpty(nodeId)) + continue; + + AttachNode node = part.FindAttachNode(nodeId); + + if (node != null) + { + nodes.Add(node); + + Debug.Log( + $"[ModuleAttachmentVisuals] Found node '{nodeId}' " + + $"for part '{part.name}'" + ); + } + else + { + Debug.LogWarning( + $"[ModuleAttachmentVisuals] Node '{nodeId}' " + + $"not found on part '{part.name}'" + ); + } + } + } + + // Parse transforms shown when condition is true + if (!string.IsNullOrWhiteSpace(showAttached)) + { + foreach (string transformName in showAttached.Split(',')) + { + string name = transformName.Trim(); + + if (string.IsNullOrEmpty(name)) + continue; + + Transform transform = part.FindModelTransform(name); + + if (transform != null) + { + attachedTransforms.Add(transform); + } + else + { + Debug.LogWarning( + $"[ModuleAttachmentVisuals] Could not find attached transform " + + $"'{name}' on '{part.name}'" + ); + } + } + } + + // Parse transforms shown when condition is false + if (!string.IsNullOrWhiteSpace(showFree)) + { + foreach (string transformName in showFree.Split(',')) + { + string name = transformName.Trim(); + + if (string.IsNullOrEmpty(name)) + continue; + + Transform transform = part.FindModelTransform(name); + + if (transform != null) + { + freeTransforms.Add(transform); + } + else + { + Debug.LogWarning( + $"[ModuleAttachmentVisuals] Could not find free transform " + + $"'{name}' on '{part.name}'" + ); + } + } + } + } + + private void UpdateVisuals() + { + // The objects only show when: + // - transform is enabled + // - all config nodes are occupied + + bool allNodesAttached = + nodes.Count > 0 && + nodes.All(node => node != null && node.attachedPart != null); + + bool visualActive = transformEnabled && allNodesAttached; + + SetTransforms(attachedTransforms, visualActive); + SetTransforms(freeTransforms, !visualActive); + + UpdateToggleEventUI(allNodesAttached); + } + + private void UpdateToggleEventUI(bool allNodesAttached) + { + BaseEvent toggleEvent = Events["EventToggleVisual"]; + + // Only offer the toggle when there's actually something capped to toggle + toggleEvent.active = allNodesAttached; + + string verb = transformEnabled ? "Disable" : "Enable"; + + string displayName = string.IsNullOrWhiteSpace(objectDisplayName) + ? $"{verb} Capping" + : $"{verb} {objectDisplayName}"; + + toggleEvent.guiName = displayName; + } + + private void SetTransforms(List transforms, bool active) + { + foreach (Transform transform in transforms) + { + if (transform == null) + { + Debug.LogWarning( + $"[ModuleAttachmentVisuals] Transform is null on part '{part.name}'" + ); + + continue; + } + + transform.gameObject.SetActive(active); + } + } + + private void ToggleVisual() + { + transformEnabled = !transformEnabled; + + UpdateVisuals(); + } + + private void OnEditorEvent(ConstructionEventType evt, Part p) + { + if ( + evt != ConstructionEventType.PartAttached && + evt != ConstructionEventType.PartDetached + ) + { + return; + } + + // Event directly involving this part + if (part == p) + { + CacheInitialChildren(); + UpdateVisuals(); + return; + } + + bool wasDirectChild = directChildren.Contains(p); + bool isDirectChildNow = p.parent == part; + + switch (evt) + { + case ConstructionEventType.PartAttached: + + if (isDirectChildNow) + { + directChildren.Add(p); + UpdateVisuals(); + } + + break; + + case ConstructionEventType.PartDetached: + + if (wasDirectChild) + { + directChildren.Remove(p); + UpdateVisuals(); + } + + break; + } + } + + private void CacheInitialChildren() + { + directChildren.Clear(); + + foreach (Part child in part.children) + { + directChildren.Add(child); + } + } + } +} \ No newline at end of file diff --git a/Source/Modules/ModuleExclusiveResourceConverter.cs b/Source/Modules/ModuleExclusiveResourceConverter.cs new file mode 100644 index 0000000..ab9ac45 --- /dev/null +++ b/Source/Modules/ModuleExclusiveResourceConverter.cs @@ -0,0 +1,38 @@ +/* + Usecase: Extends the stock resource converter so that only one type can run at a time. + Originally By: Spartwo + Originally For: Kerbal Powers + License: GNU General Public License v3.0, see https://www.gnu.org/licenses/gpl-3.0.html +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using KSP.IO; +using KSP.UI.Screens; + +namespace KSPCommunityPartModules.Modules +{ + public class ModuleExclusiveResourceConverter : ModuleResourceConverter + { + public override void StartResourceConverter() + { + StopOtherConverters(); + base.StartResourceConverter(); + } + + private void StopOtherConverters () + { + ModuleExclusiveResourceConverter[] otherConverters = part.GetComponents(); + foreach (ModuleExclusiveResourceConverter e in otherConverters) + { + e.StopResourceConverter(); + } + } + + } +} diff --git a/Source/Modules/ModuleToggleTracking.cs b/Source/Modules/ModuleToggleTracking.cs new file mode 100644 index 0000000..b3925e2 --- /dev/null +++ b/Source/Modules/ModuleToggleTracking.cs @@ -0,0 +1,98 @@ +/* + Usecase: This module is applied to parts such as extendable solar panels and radiators to toggle their ability to track the sun. + Originally By: Spartwo + Originally For: Kerbal Powers + License: GNU General Public License v3.0, see https://www.gnu.org/licenses/gpl-3.0.html +*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using KSP.IO; +using KSP.UI.Screens; +using UnityEngine.SceneManagement; + +namespace KSPCommunityPartModules.Modules +{ + public class ModuleToggleTracking : PartModule + { + + [KSPEvent(guiActive = true, + guiActiveEditor = true, + guiName = "#KSPCPM_Tracking")] + public void EventToggleTracking() => ToggleTracking(); + + + [KSPAction("#KSPCPM_ToggleTracking")] + public void AGToggleTracking(KSPActionParam param) => ToggleTracking(); + + [KSPAction("#KSPCPM_DisableTracking")] + public void AGDisableTracking(KSPActionParam param) => SetTracking(false); + + [KSPAction("#KSPCPM_EnableTracking")] + public void AGEnableTracking(KSPActionParam param) => SetTracking(true); + + [KSPField(isPersistant = true)] + public bool trackingEnabled; + + ModuleDeployablePart tracker; + + public override void OnStart(StartState state) + { + base.OnStart(state); + + try + { + tracker = part.GetComponent(); + SetToggleName(); + if (state != StartState.Editor) + { + SetTracking(trackingEnabled); + } + } + catch(Exception e) + { + Debug.Log($"Setup Error: {e}"); + } + + } + + public override void OnLoad(ConfigNode node) + { + try + { + tracker = part.GetComponent(); + SetToggleName(); + SetTracking(trackingEnabled); + } + catch (Exception e) + { + Debug.Log($"Load Error: {e}"); + } + } + + private void SetTracking(bool newState) + { + trackingEnabled = newState; + tracker.isTracking = newState; + SetToggleName(); + } + + private void ToggleTracking() + { + bool newState = !trackingEnabled; + trackingEnabled = newState; + tracker.isTracking = newState; + SetToggleName(); + } + + private void SetToggleName() + { + Events["EventToggleTracking"].guiName = trackingEnabled ? "#KSPCPM_DisableTracking" : "#KSPCPM_EnableTracking"; + } + } +}