Skip to content

Commit

Permalink
Merge pull request #150 from blowfishpro/TechRestrictions
Browse files Browse the repository at this point in the history
Tech restrictions
  • Loading branch information
blowfishpro authored Aug 4, 2019
2 parents 2386e44 + 33c978b commit 4945d69
Show file tree
Hide file tree
Showing 13 changed files with 372 additions and 225 deletions.
4 changes: 2 additions & 2 deletions B9PartSwitch/B9PartSwitch.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@
<Compile Include="ModuleB9DisableTransform.cs" />
<Compile Include="ModuleB9PropagateCopyEvents.cs" />
<Compile Include="PartSwitch\AttachNodeModifierInfo.cs" />
<Compile Include="PartSwitch\BestSubtypeDeterminator.cs" />
<Compile Include="PartSwitch\LockedSubtypeWarningHandler.cs" />
<Compile Include="PartSwitch\ModuleB9PartInfo.cs" />
<Compile Include="PartSwitch\ModuleB9PartSwitch.cs" />
<Compile Include="PartSwitch\PartModifiers\AttachNodeMover.cs" />
Expand Down Expand Up @@ -138,8 +140,6 @@
<Compile Include="TankSettings\B9TankSettings.cs" />
<Compile Include="TankSettings\Tanks.cs" />
<Compile Include="TankSettings\TankTypeValueParser.cs" />
<Compile Include="Test\ModuleB9PartSwitchTest.cs" />
<Compile Include="Test\PartModuleTest.cs" />
<Compile Include="UI\PrefabManager.cs" />
<Compile Include="UI\SwitcherSubtypeDescriptionGenerator.cs" />
<Compile Include="UI\TooltipHelper.cs" />
Expand Down
26 changes: 25 additions & 1 deletion B9PartSwitch/Extensions/IEnumerableExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

using System;
using System.Collections.Generic;

namespace B9PartSwitch
Expand All @@ -12,5 +12,29 @@ public static IEnumerable<T> All<T>(this IEnumerable<T> enumerable)
yield return item;
}
}

public static Tcollection MaxBy<Tcollection, Tcompare>(this IEnumerable<Tcollection> enumerable, Func<Tcollection, Tcompare> mapper) where Tcompare : IComparable<Tcompare>
{
enumerable.ThrowIfNullArgument(nameof(enumerable));
mapper.ThrowIfNullArgument(nameof(mapper));

IEnumerator<Tcollection> enumerator = enumerable.GetEnumerator();

if (!enumerator.MoveNext()) throw new InvalidOperationException("Enumerable is empty!");

Tcollection result = enumerator.Current;
Tcompare resultValue = mapper(result);

while (enumerator.MoveNext())
{
Tcollection testResult = enumerator.Current;
Tcompare testValue = mapper(testResult);
if (testValue.CompareTo(resultValue) <= 0) continue;
result = testResult;
resultValue = testValue;
}

return result;
}
}
}
53 changes: 53 additions & 0 deletions B9PartSwitch/PartSwitch/BestSubtypeDeterminator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace B9PartSwitch
{
public class BestSubtypeDeterminator
{
public PartSubtype FindBestSubtype(IEnumerable<PartSubtype> subtypes, IEnumerable<string> resourceNamesOnPart)
{
// No managed resources, so no way to guess subtype
if (!subtypes.Any(s => s.HasTank)) return subtypes.MaxBy(subtype => subtype.defaultSubtypePriority);

// Now use resources
// This finds all the managed resources that currently exist on teh part
IEnumerable<string> managedResourceNames = subtypes.SelectMany(s => s.ResourceNames).Distinct();
string[] managedResourcesOnPart = managedResourceNames.Intersect(resourceNamesOnPart).ToArray();

// If any of the part's current resources are managed, look for a subtype which has all of the managed resources (and all of its resources exist)
// Otherwise, look for a structural subtype (no resources)
if (managedResourcesOnPart.Any())
{
PartSubtype bestSubtype = null;

foreach (PartSubtype subtype in subtypes)
{
if (!subtype.HasTank) continue;
if (!subtype.ResourceNames.SameElementsAs(managedResourcesOnPart)) continue;
if (bestSubtype.IsNotNull() && subtype.defaultSubtypePriority <= bestSubtype.defaultSubtypePriority) continue;
bestSubtype = subtype;
}

if (bestSubtype.IsNotNull()) return bestSubtype;
}
else
{
PartSubtype bestSubtype = null;

foreach (PartSubtype subtype in subtypes)
{
if (subtype.HasTank) continue;
if (bestSubtype.IsNotNull() && subtype.defaultSubtypePriority <= bestSubtype.defaultSubtypePriority) continue;
bestSubtype = subtype;
}

if (bestSubtype.IsNotNull()) return bestSubtype;
}

// No useful way to determine correct subtype, just pick first
return subtypes.MaxBy(subtype => subtype.defaultSubtypePriority);
}
}
}
69 changes: 69 additions & 0 deletions B9PartSwitch/PartSwitch/LockedSubtypeWarningHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using UnityEngine;

namespace B9PartSwitch
{
public class LockedSubtypeWarningHandler
{
private const int MAX_MESSAGE_COUNT = 10;
private static PopupDialog dialog;
private static List<string> allMessages = new List<string>();

public static void WarnSubtypeLocked(string message)
{
try
{
UpsertDialog(message);
}
catch (Exception ex)
{
Debug.LogError("Exception while trying to create locked subtype warning dialog");
Debug.LogException(ex);
Application.Quit();
}
}

private static void UpsertDialog(string message)
{
if (string.IsNullOrEmpty(message) || allMessages.Contains(message)) return;

if (allMessages.Count < MAX_MESSAGE_COUNT)
{
allMessages.Add(message);
}
else if (allMessages.Count == MAX_MESSAGE_COUNT)
{
Debug.LogError("[LockedSubtypeWarningHandler] Not displaying locked subtype warning because too many warnings have already been added:");
Debug.LogError(message);
allMessages.Add("(too many locked subtype messages to display)");
}
else
{
Debug.LogError("[LockedSubtypeWarningHandler] Not displaying locked subtype warning because too many warnings have already been added:");
Debug.LogError(message);
return;
}

if (dialog != null) dialog.Dismiss();

dialog = PopupDialog.SpawnPopupDialog(new Vector2(0.5f, 0.5f),
new Vector2(0.5f, 0.5f),
new MultiOptionDialog(
"B9PartSwitchLockedSubtypeWarning",
$"Some parts have locked subtypes selected. They have been replaced with the highest priority unlocked subtype\n\n{string.Join("\n\n", allMessages.ToArray())}",
"B9PartSwitch - Locked Subtypes",
HighLogic.UISkin,
new Rect(0.5f, 0.5f, 500f, 60f),
new DialogGUIFlexibleSpace(),
new DialogGUIHorizontalLayout(
new DialogGUIFlexibleSpace(),
new DialogGUIButton("OK", delegate () { }, 140.0f, 30.0f, true),
new DialogGUIFlexibleSpace()
)
),
true,
HighLogic.UISkin);
}
}
}
60 changes: 33 additions & 27 deletions B9PartSwitch/PartSwitch/ModuleB9PartSwitch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ public override void OnStart(PartModule.StartState state)

InitializeSubtypes();

EnsureAtLeastOneUnrestrictedSubtype();

FindBestSubtype();

SetupGUI();
Expand Down Expand Up @@ -388,72 +390,76 @@ private void InitializeSubtypes(bool displayWarnings = true)
}
}

private void EnsureAtLeastOneUnrestrictedSubtype()
{
if (subtypes.Any(subtype => !subtype.HasUpgradeRequired)) return;
SeriousWarningHandler.DisplaySeriousWarning($"{this}: must have at least one subtype without tech restrictions, removing tech restriction on first subtype");
LogError("must have at least one subtype without tech restrictions, removing tech restriction on first subtype");
subtypes[0].upgradeRequired = null;
}

private void SetupForIcon()
{
// This will deactivate objects on non-active subtypes before the part icon is created, avoiding a visual mess

PartSubtype defaultSubtype = subtypes.Where(s => !s.HasUpgradeRequired).MaxBy(s => s.defaultSubtypePriority);

foreach (PartSubtype subtype in subtypes)
{
if (subtype == CurrentSubtype) continue;
if (subtype == defaultSubtype) continue;
subtype.DeactivateForIcon();
}

CurrentSubtype.ActivateForIcon();
defaultSubtype.ActivateForIcon();
}

private void FindBestSubtype()
{
if (subtypes.ValidIndex(currentSubtypeIndex)) return;

if (ManagesResources)
PartSubtype lockedSubtype = null;
if (subtypes.ValidIndex(currentSubtypeIndex))
{
// Now use resources
// This finds all the managed resources that currently exist on teh part
string[] resourcesOnPart = ManagedResourceNames.Intersect(part.Resources.Select(resource => resource.resourceName)).ToArray();

// If any of the part's current resources are managed, look for a subtype which has all of the managed resources (and all of its resources exist)
// Otherwise, look for a structural subtype (no resources)
if (resourcesOnPart.Any())
{
currentSubtypeIndex = subtypes.FindIndex(subtype => subtype.HasTank && subtype.ResourceNames.SameElementsAs(resourcesOnPart));
LogInfo($"Inferred subtype based on part's resources: '{CurrentSubtype.Name}'");
}
else
{
currentSubtypeIndex = subtypes.FindIndex(subtype => !subtype.HasTank);
}
if (CurrentSubtype.IsUnlocked()) return;
else lockedSubtype = CurrentSubtype;
}

// No useful way to determine correct subtype, just pick first
if (!subtypes.ValidIndex(currentSubtypeIndex))
currentSubtypeIndex = 0;
BestSubtypeDeterminator determinator = new BestSubtypeDeterminator();
IEnumerable<string> resourceNamesOnPart = part.Resources.Select(resource => resource.resourceName);
PartSubtype bestSubtype = determinator.FindBestSubtype(subtypes.Where(s => s.IsUnlocked()), resourceNamesOnPart);

currentSubtypeIndex = subtypes.IndexOf(bestSubtype);

if (lockedSubtype.IsNotNull())
LockedSubtypeWarningHandler.WarnSubtypeLocked($"{this}: locked subtype '{lockedSubtype.title}' replaced with '{CurrentSubtype.title}'");
}

private void SetupGUI()
{
int unlockedSubtypesCount = subtypes.Count(subtype => subtype.IsUnlocked());

BaseField chooseField = Fields[nameof(currentSubtypeIndex)];
chooseField.guiName = switcherDescription;
chooseField.advancedTweakable = advancedTweakablesOnly;
chooseField.guiActiveEditor = subtypes.Count > 1;
chooseField.guiActiveEditor = unlockedSubtypesCount > 1;

chooseField.uiControlEditor.onFieldChanged = OnSliderUpdate;

BaseEvent switchSubtypeEvent = Events[nameof(ShowSubtypesWindow)];
switchSubtypeEvent.guiName = Localization.ModuleB9PartSwitch_SelectSubtype(switcherDescription); // Select <<1>>
switchSubtypeEvent.advancedTweakable = advancedTweakablesOnly;
switchSubtypeEvent.guiActiveEditor = subtypes.Count > 1;
switchSubtypeEvent.guiActiveEditor = unlockedSubtypesCount > 1;

BaseField subtypeTitleField = Fields[nameof(currentSubtypeTitle)];
subtypeTitleField.guiName = switcherDescription;
subtypeTitleField.advancedTweakable = advancedTweakablesOnly;
subtypeTitleField.guiActiveEditor = subtypes.Count == 1;
subtypeTitleField.guiActiveEditor = unlockedSubtypesCount == 1;

if (HighLogic.LoadedSceneIsFlight)
UpdateSwitchEventFlightVisibility();
}

private void UpdateSwitchEventFlightVisibility()
{
bool switchInFlightEnabled = subtypes.Any(s => s != CurrentSubtype && s.allowSwitchInFlight);
bool switchInFlightEnabled = subtypes.Any(s => s != CurrentSubtype && s.allowSwitchInFlight && s.IsUnlocked());
BaseEvent switchSubtypeEvent = Events[nameof(ShowSubtypesWindow)];
switchSubtypeEvent.guiActive = switchInFlight && switchInFlightEnabled;

Expand Down
23 changes: 23 additions & 0 deletions B9PartSwitch/PartSwitch/PartSubtype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ public class PartSubtype : IContextualNode
[NodeData]
public Color? secondaryColor;

[NodeData]
public string upgradeRequired;

[NodeData]
public float defaultSubtypePriority = 0;

[NodeData(name = "transform")]
public List<string> transformNames = new List<string>();

Expand Down Expand Up @@ -125,6 +131,8 @@ public class PartSubtype : IContextualNode

public bool HasTank => tankType != null && tankType.ResourcesCount > 0;

public bool HasUpgradeRequired => !upgradeRequired.IsNullOrEmpty();

public IEnumerable<Transform> Transforms => transforms.Select(transform => transform.transform);
public IEnumerable<AttachNode> Nodes => nodes.All();
public IEnumerable<string> ResourceNames => tankType.ResourceNames;
Expand Down Expand Up @@ -184,6 +192,13 @@ private void OnLoad(ConfigNode node, OperationContext context)
LogError("Subtype has no name");
}

if (HasUpgradeRequired && PartUpgradeManager.Handler.GetUpgrade(upgradeRequired).IsNull())
{
SeriousWarningHandler.DisplaySeriousWarning($"Upgrade does not exist: {upgradeRequired} on: {this}");
LogError("Upgrade does not exist: " + upgradeRequired);
upgradeRequired = null;
}

if (tankType == null)
tankType = B9TankSettings.StructuralTankType;

Expand Down Expand Up @@ -480,6 +495,14 @@ public void AssignStructuralTankType()
tankType = B9TankSettings.StructuralTankType;
}

public bool IsUnlocked()
{
if (!HasUpgradeRequired) return true;
if (HighLogic.CurrentGame.IsNull()) return true;
if (HighLogic.CurrentGame.Mode == Game.Modes.SANDBOX) return true;
return PartUpgradeManager.Handler.IsUnlocked(upgradeRequired);
}

public override string ToString()
{
string log = "PartSubtype";
Expand Down
2 changes: 2 additions & 0 deletions B9PartSwitch/PartSwitch/PartSwitchFlightDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ private static DialogGUIBase[] CreateOptions(ModuleB9PartSwitch module, IList<Ca

foreach (PartSubtype subtype in module.subtypes)
{
if (!subtype.IsUnlocked()) continue;

if (subtype == module.CurrentSubtype)
{
string currentSubtypeText = Localization.PartSwitchFlightDialog_CurrentSubtypeLabel(subtype.title); // <<1>> (Current)
Expand Down
Loading

0 comments on commit 4945d69

Please sign in to comment.