Skip to content

Commit

Permalink
Merge pull request #190 from blowfishpro/UIGroups
Browse files Browse the repository at this point in the history
Part action window groups
  • Loading branch information
blowfishpro authored Mar 31, 2020
2 parents ba3eab5 + fb694a9 commit 8a13303
Show file tree
Hide file tree
Showing 6 changed files with 284 additions and 146 deletions.
2 changes: 2 additions & 0 deletions B9PartSwitch/B9PartSwitch.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@
<Compile Include="Fishbones\OperationManager.cs" />
<Compile Include="Fishbones\UseParser.cs" />
<Compile Include="Localization.cs" />
<Compile Include="ModuleB9AssignUiGroups.cs" />
<Compile Include="ModuleMatcher.cs" />
<Compile Include="PartSwitch\PartModifiers\AttachNodeSizeModifier.cs" />
<Compile Include="Utils\ChangeTransactionManager.cs" />
<Compile Include="ModuleB9DisableTransform.cs" />
Expand Down
21 changes: 21 additions & 0 deletions B9PartSwitch/Extensions/PartModuleExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,27 @@ public static class PartModuleExtensions
// partInfo is assigned after modules are created and loaded
public static bool ParsedPrefab(this PartModule module) => module.part.partInfo != null;

public static void SetUiGroups(this PartModule module, string uiGroupName, string uiGroupDisplayName)
{
module.ThrowIfNullArgument(nameof(module));

foreach (BaseField field in module.Fields)
{
if (!field.group.name.IsNullOrEmpty()) continue;

field.group.name = uiGroupName;
field.group.displayName = uiGroupDisplayName;
}

foreach (BaseEvent baseEvent in module.Events)
{
if (!baseEvent.group.name.IsNullOrEmpty()) continue;

baseEvent.group.name = uiGroupName;
baseEvent.group.displayName = uiGroupDisplayName;
}
}

#region Logging

public static void LogInfo(this PartModule module, object message) => module.part.LogInfo($"{module.LogTagString()} {message}");
Expand Down
68 changes: 68 additions & 0 deletions B9PartSwitch/ModuleB9AssignUiGroups.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using B9PartSwitch.Fishbones;
using B9PartSwitch.Fishbones.Context;

namespace B9PartSwitch
{
public class ModuleB9AssignUiGroups : CustomPartModule
{
private static readonly string[] MODULE_NAME_BLACKLIST = { "ModuleSimpleAdjustableFairing" };
private static readonly Type[] MODULE_TYPE_BLACKLIST = { typeof(ModuleB9PartSwitch), typeof(ModuleB9PartInfo), typeof(ModuleB9AssignUiGroups) };

public class ModuleInfo : IContextualNode
{
[NodeData(name = "IDENTIFIER")]
public ConfigNode identifierNode;

[NodeData]
public string uiGroupName;

[NodeData]
public string uiGroupDisplayName;

public void Load(ConfigNode node, OperationContext context) => this.LoadFields(node, context);

public void Save(ConfigNode node, OperationContext context) => this.SaveFields(node, context);

public void Apply(Part part)
{
part.ThrowIfNullArgument(nameof(part));
if (identifierNode == null) throw new InvalidOperationException("Identifier node is null!");

ModuleMatcher moduleMatcher = new ModuleMatcher(identifierNode);
PartModule module = moduleMatcher.FindModule(part);

if (MODULE_TYPE_BLACKLIST.Any(type => module.GetType().Implements(type))) throw new Exception($"Cannot assign UI groups on {module}");
if (MODULE_NAME_BLACKLIST.Any(typeName => module.GetType().Name == typeName)) throw new Exception($"Cannot assign UI groups on {module}");

module.SetUiGroups(uiGroupName, uiGroupDisplayName);
}
}

[NodeData(name = "MODULE", alwaysSerialize = true)]
public List<ModuleInfo> moduleInfos = new List<ModuleInfo>();

public override void OnStart(StartState state)
{
base.OnStart(state);

if (moduleInfos.Count == 0) LogError("No MODULEs were specified");

foreach (ModuleInfo moduleInfo in moduleInfos)
{
try
{
moduleInfo.Apply(part);
}
catch (Exception ex)
{
LogError("Exception when setting up UI group for MODULE");
Debug.LogException(ex);
}
}
}
}
}
181 changes: 181 additions & 0 deletions B9PartSwitch/ModuleMatcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
using System;
using B9PartSwitch.Fishbones.Parsers;
using B9PartSwitch.Utils;

namespace B9PartSwitch
{
public class ModuleMatcher
{
public sealed class CannotParseFieldException : Exception
{
private CannotParseFieldException(string message) : base(message) { }
private CannotParseFieldException(string message, Exception innerException) : base(message, innerException) { }

public static CannotParseFieldException CannotFindParser(string fieldName, Type fieldType)
{
fieldName.ThrowIfNullArgument(nameof(fieldName));
fieldType.ThrowIfNullArgument(nameof(fieldType));

string message = $"Could not find a suitable way to parse type {fieldType.Name} for field {fieldName}";
return new CannotParseFieldException(message);
}

public static CannotParseFieldException ExceptionWhileParsing(string fieldName, Type fieldType, Exception innerException)
{
fieldName.ThrowIfNullArgument(nameof(fieldName));
fieldType.ThrowIfNullArgument(nameof(fieldType));
innerException.ThrowIfNullArgument(nameof(innerException));

string message = $"Exception while parsing type {fieldType.Name} for field {fieldName}";
return new CannotParseFieldException(message, innerException);
}
}

private readonly ConfigNode identifierNode;
private readonly IStringMatcher moduleName;

public ModuleMatcher(ConfigNode identifierNode)
{
this.identifierNode = identifierNode ?? throw new ArgumentNullException(nameof(identifierNode));

string moduleNameStr = identifierNode.GetValue("name");
if (moduleNameStr == null) throw new ArgumentException("node has no name", nameof(identifierNode));
if (moduleNameStr == "") throw new ArgumentException("node has empty name", nameof(identifierNode));
moduleName = StringMatcher.Parse(moduleNameStr);
}

public PartModule FindModule(Part part)
{
PartModule matchedModule = null;

foreach (PartModule module in part.Modules)
{
if (!IsMatch(module)) continue;

if (matchedModule.IsNotNull()) throw new Exception("Found more than one matching module");

matchedModule = module;
}

if (matchedModule.IsNull()) throw new Exception("Could not find matching module");

return matchedModule;
}

public ConfigNode FindPrefabNode(PartModule module)
{
if (!(module.part.partInfo is AvailablePart partInfo)) throw new InvalidOperationException($"partInfo is null on part {module.part.name}");
if (!(partInfo.partConfig is ConfigNode partConfig)) throw new InvalidOperationException($"partInfo.partConfig is null on part {partInfo.name}");

ConfigNode matchedNode = null;

foreach (ConfigNode subNode in partConfig.nodes)
{
if (subNode.name != "MODULE") continue;

if (!NodeMatchesModule(module, subNode)) continue;

if (matchedNode.IsNotNull()) throw new Exception("Found more than one matching module node");

matchedNode = subNode;
}

if (matchedNode.IsNull()) throw new Exception("Could not find matching module node");

return matchedNode;
}

private bool IsMatch(PartModule module)
{
if (!moduleName.Match(module.GetType().Name)) return false;

foreach (ConfigNode.Value value in identifierNode.values)
{
if (value.name == "name") continue;

if (module.Fields[value.name] is BaseField baseField)
{
IValueParser parser;
object parsedValue;

try
{
parser = DefaultValueParseMap.Instance.GetParser(baseField.FieldInfo.FieldType);
}
catch (ParseTypeNotRegisteredException)
{
throw CannotParseFieldException.CannotFindParser(baseField.name, baseField.FieldInfo.FieldType);
}

try
{
parsedValue = parser.Parse(value.value);
}
catch (Exception ex)
{
throw CannotParseFieldException.ExceptionWhileParsing(baseField.name, baseField.FieldInfo.FieldType, ex);
}

if (!Equals(parsedValue, baseField.GetValue(module))) return false;
}
else if (module is CustomPartModule cpm && value.name == "moduleID")
{
if (cpm.moduleID != value.value) return false;
}
else
{
return false;
}
}

return true;
}

private bool NodeMatchesModule(PartModule module, ConfigNode node)
{
string nameValue = node.GetValue("name");
if (nameValue.IsNullOrEmpty()) throw new ArgumentException("Cannot match a module node without a name!");

if (!moduleName.Match(nameValue)) return false;

foreach (ConfigNode.Value value in identifierNode.values)
{
if (value.name == "name") continue;

if (!(node.GetValue(value.name) is string testValue)) return false;

if (module.Fields[value.name] is BaseField baseField)
{
object parsedValue, nodeValue;

try
{
IValueParser parser = DefaultValueParseMap.Instance.GetParser(baseField.FieldInfo.FieldType);
parsedValue = parser.Parse(value.value);
nodeValue = parser.Parse(testValue);
}
catch (ParseTypeNotRegisteredException)
{
throw CannotParseFieldException.CannotFindParser(baseField.name, baseField.FieldInfo.FieldType);
}
catch (Exception ex)
{
throw CannotParseFieldException.ExceptionWhileParsing(baseField.name, baseField.FieldInfo.FieldType, ex);
}

if (!Equals(parsedValue, nodeValue)) return false;
}
else if (module is CustomPartModule && value.name == nameof(CustomPartModule.moduleID))
{
if (node.GetValue(nameof(CustomPartModule.moduleID)) != value.value) return false;
}
else
{
return false;
}
}

return true;
}
}
}
8 changes: 8 additions & 0 deletions B9PartSwitch/PartSwitch/ModuleB9PartSwitch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ public class ModuleB9PartSwitch : CustomPartModule, IPartMassModifier, IPartCost
[NodeData]
public bool advancedTweakablesOnly = false;

[NodeData]
public string uiGroupName;

[NodeData]
public string uiGroupDisplayName;

[NodeData(name = "currentSubtype", persistent = true)]
public string CurrentSubtypeName
{
Expand Down Expand Up @@ -492,6 +498,8 @@ private void SetupGUI()

if (HighLogic.LoadedSceneIsFlight)
UpdateSwitchEventFlightVisibility();

this.SetUiGroups(uiGroupName, uiGroupDisplayName);
}

private void UpdateSwitchEventFlightVisibility()
Expand Down
Loading

0 comments on commit 8a13303

Please sign in to comment.