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
9 changes: 9 additions & 0 deletions GameData/KSPCommunityPartModules/Localization/en-us.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,14 @@ 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

// ModuleAttachmentVisuals
#KSPCPM_AttachmentVisual = Capping
}
}
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ Compatible with **KSP 1.12.3** and up - Available on [CKAN]
- **ModuleDepthMask**<br/>This module allows for parts to have hollow insets that dont clip into other parts, ideal for engine nozzles, landing gear, air intakes, solar panel bays, and more.

- **ModuleNameTag**<br/>This module adds a user-editable name tag to a part, set through an in-game window. Shared by kOS (part:TAG) and kRPC (Part.Tag) so a tag assigned by one is visible to the other. Consuming mods add the module to parts with their own ModuleManager patch; legacy KOSNameTag tags from older kOS/kRPC saves are migrated automatically.

- **ModuleAttachmentVisuals**<br/>This module adds configurable visibility for part objects similar to `ModuleJettison` but with improved configurability and the ability to support multiple instances on the same part.

- **ModuleExclusiveResourceConverter**<br/>This module is a variant of the stock resource converter which will only allow one process to run at a time (for example cannot convert LFO and Monoprop at the same time).

- **ModuleToggleTracking**<br/>This module allows the sun-tracking behaviour of solar/radiator panels or any `ModuelDeployablePart` instance to be enabled or disabled on the fly.


[CKAN]: https://forum.kerbalspaceprogram.com/topic/197082-ckan-the-comprehensive-kerbal-archive-network-v1332-laplace-ksp-2-support/
284 changes: 284 additions & 0 deletions Source/Modules/ModuleAttachmentVisuals.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
/*
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;
using KSP.Localization;

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
enabled = false;
}
Comment thread
Spartwo marked this conversation as resolved.

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 adjective = transformEnabled
? Localizer.Format("#autoLOC_900889")
: Localizer.Format("#autoLOC_247995");

string displayName = string.IsNullOrWhiteSpace(objectDisplayName)
? Localizer.Format("#KSPCPM_AttachmentVisual")
: Localizer.Format(objectDisplayName);

toggleEvent.guiName = $"{displayName} {adjective}";
}

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