Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions GameData/KSPCommunityPartModules/Localization/en-us.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
282 changes: 282 additions & 0 deletions Source/Modules/ModuleAttachmentVisuals.cs
Original file line number Diff line number Diff line change
@@ -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 <objectDisplayName>" 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<AttachNode> nodes = new List<AttachNode>();

// Transforms shown when nodes are occupied
private List<Transform> attachedTransforms = new List<Transform>();

// Transforms shown when nodes are free
private List<Transform> freeTransforms = new List<Transform>();

private HashSet<Part> directChildren = new HashSet<Part>();

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;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this module doesn't need to do anything in Update, it might be wise to disable it: https://github.com/KSP-KOS/KOS/blob/8f281a459b6ea0ba917c91bbbc117e68bb948199/src/kOS/Module/KOSNameTag.cs#L66

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. It's only driven by editor events


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<Transform> 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);
}
}
}
}
38 changes: 38 additions & 0 deletions Source/Modules/ModuleExclusiveResourceConverter.cs
Original file line number Diff line number Diff line change
@@ -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<ModuleExclusiveResourceConverter>();
foreach (ModuleExclusiveResourceConverter e in otherConverters)
{
e.StopResourceConverter();
}
}

}
}
Loading