Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for #236 : Improved ModuleIndexingMismatch patch #264

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
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
152 changes: 100 additions & 52 deletions KSPCommunityFixes/BugFixes/ModuleIndexingMismatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class ModuleIndexingMismatch : BasePatch
{
private const string VALUENAME_MODULEPARTCONFIGID = "modulePartConfigId";
private static readonly HashSet<string> multiModules = new HashSet<string>();
private static readonly Dictionary<string, Type> allModuleTypes = new Dictionary<string, Type>();

protected override Version VersionMin => new Version(1, 8, 0);

Expand Down Expand Up @@ -56,9 +57,16 @@ protected override void ApplyPatches()
{
foreach (Type type in loadedAssembly.assembly.GetTypes())
{
if (multiModuleType.IsAssignableFrom(type) && partModuleType.IsAssignableFrom(type))
if (partModuleType.IsAssignableFrom(type))
{
multiModules.Add(type.Name);
if (type == partModuleType)
continue;

if (!type.IsAbstract && !type.IsInterface)
allModuleTypes.Add(type.Name, type);

if (multiModuleType.IsAssignableFrom(type))
multiModules.Add(type.Name);
}
}
}
Expand Down Expand Up @@ -197,6 +205,19 @@ static IEnumerable<CodeInstruction> ProtoPartSnapshot_ConfigurePart_Transpiler(I
return code;
}

/// <summary>
/// Our own version of ProtoPartModuleSnapshot.Load(). We reimplement it because :
/// - Stock would do again all the indice / module matching that we just did
/// - That stock logic would prevent our handling of loading derived into base / base into derived modules.
/// </summary>
private static void LoadProtoPartSnapshotModule(ProtoPartModuleSnapshot protoModule, PartModule module)
{
GameEvents.onProtoPartModuleSnapshotLoad.Fire(new GameEvents.FromToAction<ProtoPartModuleSnapshot, ConfigNode>(protoModule, null));
module.Load(protoModule.moduleValues);
module.snapshot = protoModule;
protoModule.moduleRef = module;
}

static void LoadModules(ProtoPartSnapshot protoPart, Part part)
{
int protoModuleCount = protoPart.modules.Count;
Expand Down Expand Up @@ -246,17 +267,17 @@ static void LoadModules(ProtoPartSnapshot protoPart, Part part)

}

protoPart.modules[i].Load(part, ref moduleIndex);
LoadProtoPartSnapshotModule(protoPart.modules[i], part.modules[moduleIndex]);
}
return;
}

Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Part \"{protoPart.partName}\" configuration has changed. Synchronizing persisted modules...");
ProtoPartModuleSnapshot[] foundModules = new ProtoPartModuleSnapshot[partModuleCount];

for (int i = 0; i < protoModuleCount; i++)
for (int protoModuleIdx = 0; protoModuleIdx < protoModuleCount; protoModuleIdx++)
{
ProtoPartModuleSnapshot protoModule = protoPart.modules[i];
ProtoPartModuleSnapshot protoModule = protoPart.modules[protoModuleIdx];

if (multiModules.Contains(protoModule.moduleName))
{
Expand All @@ -265,28 +286,26 @@ static void LoadModules(ProtoPartSnapshot protoPart, Part part)
if (!string.IsNullOrEmpty(protoModuleId))
{
bool multiFound = false;
for (int j = 0; j < partModuleCount; j++)
for (int moduleIdx = 0; moduleIdx < partModuleCount; moduleIdx++)
{
if (part.Modules[j] is IMultipleModuleInPart multiModule && multiModule.ModulePartConfigId == protoModuleId)
if (part.Modules[moduleIdx] is IMultipleModuleInPart multiModule && multiModule.ModulePartConfigId == protoModuleId)
{
int moduleIndex = j;
protoModule.Load(part, ref moduleIndex);
foundModules[j] = protoModule;
LoadProtoPartSnapshotModule(protoPart.modules[protoModuleIdx], part.modules[moduleIdx]);
foundModules[moduleIdx] = protoModule;

if (i != j)
Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Persisted module \"{protoModule.moduleName}\" with {VALUENAME_MODULEPARTCONFIGID}={protoModuleId} at index [{i}] moved to index [{j}]");
if (protoModuleIdx != moduleIdx)
Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Persisted module \"{protoModule.moduleName}\" with {VALUENAME_MODULEPARTCONFIGID}={protoModuleId} at index [{protoModuleIdx}] moved to index [{moduleIdx}]");

multiFound = true;
break;
}
}

if (!multiFound)
Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Persisted module \"{protoModule.moduleName}\" with {VALUENAME_MODULEPARTCONFIGID}={protoModuleId} at index [{i}] has been removed, no matching module in the part config");
Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Persisted module \"{protoModule.moduleName}\" with {VALUENAME_MODULEPARTCONFIGID}={protoModuleId} at index [{protoModuleIdx}] has been removed, no matching module in the part config");
}
}


int protoIndexInType = 0;
foreach (ProtoPartModuleSnapshot otherppms in protoPart.modules)
{
Expand All @@ -301,18 +320,17 @@ static void LoadModules(ProtoPartSnapshot protoPart, Part part)

int prefabIndexInType = 0;
bool found = false;
for (int j = 0; j < partModuleCount; j++)
for (int moduleIdx = 0; moduleIdx < partModuleCount; moduleIdx++)
{
if (part.Modules[j].moduleName == protoModule.moduleName)
if (part.Modules[moduleIdx].moduleName == protoModule.moduleName)
{
if (prefabIndexInType == protoIndexInType)
{
int moduleIndex = j;
protoModule.Load(part, ref moduleIndex);
foundModules[j] = protoModule;
LoadProtoPartSnapshotModule(protoPart.modules[protoModuleIdx], part.modules[moduleIdx]);
foundModules[moduleIdx] = protoModule;

if (i != j)
Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Persisted module \"{protoModule.moduleName}\" at index [{i}] moved to index [{j}]");
if (protoModuleIdx != moduleIdx)
Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Persisted module \"{protoModule.moduleName}\" at index [{protoModuleIdx}] moved to index [{moduleIdx}]");

found = true;
break;
Expand All @@ -323,7 +341,21 @@ static void LoadModules(ProtoPartSnapshot protoPart, Part part)
}

if (!found)
Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Persisted module \"{protoModule.moduleName}\" at index [{i}] has been removed, no matching module in the part config");
{
// see https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/236
// when persisted type is a derived type of the module type, or when the module type is a derived type of the persisted type, still attempt to load it.
if (protoModuleIdx < partModuleCount && allModuleTypes.TryGetValue(protoModule.moduleName, out Type moduleType)
&& (part.Modules[protoModuleIdx].GetType().IsAssignableFrom(moduleType) || moduleType.IsInstanceOfType(part.Modules[protoModuleIdx])))
{
LoadProtoPartSnapshotModule(protoPart.modules[protoModuleIdx], part.modules[protoModuleIdx]);
foundModules[protoModuleIdx] = protoModule;
Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Persisted module \"{protoModule.moduleName}\" at index [{protoModuleIdx}] has been loaded as a \"{part.Modules[protoModuleIdx].moduleName}\"");
}
else
{
Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Persisted module \"{protoModule.moduleName}\" at index [{protoModuleIdx}] has been removed, no matching module in the part config");
}
}
}

for (int i = 0; i < partModuleCount; i++)
Expand Down Expand Up @@ -419,18 +451,21 @@ static void LoadShipModuleNodes(Part part, ConfigNode partNode)
if (compNode.name == "MODULE")
currentModuleNodes.Add(compNode);

int nodeModuleCount = currentModuleNodes.Count;
int moduleNodeCount = currentModuleNodes.Count;

string[] nodeModuleNames = new string[nodeModuleCount];
for (int i = 0; i < nodeModuleCount; i++)
nodeModuleNames[i] = currentModuleNodes[i].GetValue("name");
string[] nodeModuleNames = new string[moduleNodeCount];
for (int i = 0; i < moduleNodeCount; i++)
{
string moduleName = currentModuleNodes[i].GetValue("name") ?? string.Empty;
nodeModuleNames[i] = moduleName;
}

int partModuleCount = part.Modules.Count;
bool inSync = nodeModuleCount == partModuleCount;
bool inSync = moduleNodeCount == partModuleCount;

if (inSync)
{
for (int i = 0; i < nodeModuleCount; i++)
for (int i = 0; i < moduleNodeCount; i++)
{
if (part.Modules[i].moduleName != nodeModuleNames[i])
{
Expand All @@ -442,7 +477,7 @@ static void LoadShipModuleNodes(Part part, ConfigNode partNode)

if (inSync)
{
for (int i = 0; i < nodeModuleCount; i++)
for (int i = 0; i < moduleNodeCount; i++)
{
int moduleIndex = i;

Expand Down Expand Up @@ -471,54 +506,53 @@ static void LoadShipModuleNodes(Part part, ConfigNode partNode)

}

part.LoadModule(currentModuleNodes[i], ref moduleIndex);
part.Modules[moduleIndex].Load(currentModuleNodes[i]);
}
return;
}

Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Part \"{part.partInfo.name}\" configuration has changed. Synchronizing persisted modules...");
ConfigNode[] foundModules = new ConfigNode[partModuleCount];

for (int i = 0; i < nodeModuleCount; i++)
for (int moduleNodeIdx = 0; moduleNodeIdx < moduleNodeCount; moduleNodeIdx++)
{
ConfigNode nodeModule = currentModuleNodes[i];
string nodeModuleName = nodeModuleNames[i];
ConfigNode moduleNode = currentModuleNodes[moduleNodeIdx];
string nodeModuleName = nodeModuleNames[moduleNodeIdx];

if (multiModules.Contains(nodeModuleName))
{
string nodeModuleId = nodeModule.GetValue(VALUENAME_MODULEPARTCONFIGID);
string nodeModuleId = moduleNode.GetValue(VALUENAME_MODULEPARTCONFIGID);

if (!string.IsNullOrEmpty(nodeModuleId))
{
bool multiFound = false;
for (int j = 0; j < partModuleCount; j++)
for (int moduleIdx = 0; moduleIdx < partModuleCount; moduleIdx++)
{
if (part.Modules[j] is IMultipleModuleInPart multiModule && multiModule.ModulePartConfigId == nodeModuleId)
if (part.Modules[moduleIdx] is IMultipleModuleInPart multiModule && multiModule.ModulePartConfigId == nodeModuleId)
{
int moduleIndex = j;
part.LoadModule(nodeModule, ref moduleIndex);
foundModules[j] = nodeModule;
part.Modules[moduleIdx].Load(moduleNode);
foundModules[moduleIdx] = moduleNode;

if (i != j)
Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Persisted module \"{nodeModuleName}\" with {VALUENAME_MODULEPARTCONFIGID}={nodeModuleId} at index [{i}] moved to index [{j}]");
if (moduleNodeIdx != moduleIdx)
Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Persisted module \"{nodeModuleName}\" with {VALUENAME_MODULEPARTCONFIGID}={nodeModuleId} at index [{moduleNodeIdx}] moved to index [{moduleIdx}]");

multiFound = true;
break;
}
}

if (!multiFound)
Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Persisted module \"{nodeModuleName}\" with {VALUENAME_MODULEPARTCONFIGID}={nodeModuleId} at index [{i}] has been removed, no matching module in the part config");
Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Persisted module \"{nodeModuleName}\" with {VALUENAME_MODULEPARTCONFIGID}={nodeModuleId} at index [{moduleNodeIdx}] has been removed, no matching module in the part config");
}
}


int nodeIndexInType = 0;
for (int j = 0; j < nodeModuleCount; j++)
for (int j = 0; j < moduleNodeCount; j++)
{
if (nodeModuleNames[j] == nodeModuleName)
{
if (currentModuleNodes[j] == nodeModule)
if (currentModuleNodes[j] == moduleNode)
break;

nodeIndexInType++;
Expand All @@ -527,18 +561,17 @@ static void LoadShipModuleNodes(Part part, ConfigNode partNode)

int prefabIndexInType = 0;
bool found = false;
for (int j = 0; j < partModuleCount; j++)
for (int moduleIdx = 0; moduleIdx < partModuleCount; moduleIdx++)
{
if (part.Modules[j].moduleName == nodeModuleName)
if (part.Modules[moduleIdx].moduleName == nodeModuleName)
{
if (prefabIndexInType == nodeIndexInType)
{
int moduleIndex = j;
part.LoadModule(nodeModule, ref moduleIndex);
foundModules[j] = nodeModule;
part.Modules[moduleIdx].Load(moduleNode);
foundModules[moduleIdx] = moduleNode;

if (i != j)
Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Persisted module \"{nodeModuleName}\" at index [{i}] moved to index [{j}]");
if (moduleNodeIdx != moduleIdx)
Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Persisted module \"{nodeModuleName}\" at index [{moduleNodeIdx}] moved to index [{moduleIdx}]");

found = true;
break;
Expand All @@ -549,7 +582,22 @@ static void LoadShipModuleNodes(Part part, ConfigNode partNode)
}

if (!found)
Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Persisted module \"{nodeModuleName}\" at index [{i}] has been removed, no matching module in the part config");
{
// see https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/236
// when persisted type is a derived type of the module type, or when the module type is a derived type of the persisted type, still attempt to load it.
if (moduleNodeIdx < partModuleCount
&& allModuleTypes.TryGetValue(nodeModuleName, out Type moduleType)
&& (part.Modules[moduleNodeIdx].GetType().IsAssignableFrom(moduleType) || moduleType.IsInstanceOfType(part.Modules[moduleNodeIdx])))
{
part.Modules[moduleNodeIdx].Load(moduleNode);
foundModules[moduleNodeIdx] = moduleNode;
Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Persisted module \"{nodeModuleName}\" at index [{moduleNodeIdx}] has been loaded as a \"{part.Modules[moduleNodeIdx].moduleName}\"");
}
else
{
Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Persisted module \"{nodeModuleName}\" at index [{moduleNodeIdx}] has been removed, no matching module in the part config");
}
}
}

for (int i = 0; i < partModuleCount; i++)
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,9 @@ If doing so in the `Debug` configuration and if your KSP install is modified to
- New KSP bufix : [**DragCubeLoadException**](https://github.com/KSPModdingLibs/KSPCommunityFixes/pull/232) [KSP 1.8.0 - 1.12.5] : Fix loading of drag cubes without a name failing with an IndexOutOfRangeException (contributed by @Nazfib).
- New KSP bufix : [**TimeWarpBodyCollision**](https://github.com/KSPModdingLibs/KSPCommunityFixes/pull/259) [KSP 1.12.0 - 1.12.5] : Fix timewarp rate not always being limited on SOI transistions, sometimes resulting in failure to detect an encounter/collision with the body in the next SOI (contributed by @JonnyOThan).
- New modding API improvement : [**KSPFieldEnumDesc**](https://github.com/KSPModdingLibs/KSPCommunityFixes/pull/243) [KSP 1.12.2 - 1.12.5] : Disabled by default, you can enable it with a MM patch. Adds display name and localization support for enum KSPFields. To use add `Description` attribute to the field (contributed by @siimav).
- **PAWStockGroups** : [Added PAW groups for generators](https://github.com/KSPModdingLibs/KSPCommunityFixes/pull/235), making the UI less confusing when multiple generators are present (contributed by @yalov).
- New KSP bugfix : [**ModuleActiveRadiatorNoParentException**](https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/249) [KSP 1.12.3 - 1.12.5] : Fix exception spam when a radiator set to `parentCoolingOnly` is detached from the vessel (reported by @BrettRyland).
- **PAWStockGroups** : [Added PAW groups for generators](https://github.com/KSPModdingLibs/KSPCommunityFixes/pull/235), making the UI less confusing when multiple generators are present (contributed by @yalov).
- **ModuleIndexingMismatch** : [Improved patch](https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/236), now will still load a module data when the mismatched module is of a known base or derived type. Notably prevent engine state such as action groups configuration from being lost when installing/uninstalling Waterfall, or when exchanging craft files between stock and Waterfall installs.

**Internal changes**
- Patching now always run as the first ModuleManagerPostLoad callback, ensuring other callbacks can benefit from the patches (contributed by @al2me6).
Expand Down
Loading