-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #190 from blowfishpro/UIGroups
Part action window groups
- Loading branch information
Showing
6 changed files
with
284 additions
and
146 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.