Skip to content

Commit 6d58c6c

Browse files
authored
Fix for #236 : Improved ModuleIndexingMismatch patch (#264)
Fix for #236 : Improved ModuleIndexingMismatch patch, now will still load a module data when the mismatched module is of a 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.
1 parent ed62615 commit 6d58c6c

File tree

2 files changed

+102
-53
lines changed

2 files changed

+102
-53
lines changed

KSPCommunityFixes/BugFixes/ModuleIndexingMismatch.cs

Lines changed: 100 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public class ModuleIndexingMismatch : BasePatch
2828
{
2929
private const string VALUENAME_MODULEPARTCONFIGID = "modulePartConfigId";
3030
private static readonly HashSet<string> multiModules = new HashSet<string>();
31+
private static readonly Dictionary<string, Type> allModuleTypes = new Dictionary<string, Type>();
3132

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

@@ -56,9 +57,16 @@ protected override void ApplyPatches()
5657
{
5758
foreach (Type type in loadedAssembly.assembly.GetTypes())
5859
{
59-
if (multiModuleType.IsAssignableFrom(type) && partModuleType.IsAssignableFrom(type))
60+
if (partModuleType.IsAssignableFrom(type))
6061
{
61-
multiModules.Add(type.Name);
62+
if (type == partModuleType)
63+
continue;
64+
65+
if (!type.IsAbstract && !type.IsInterface)
66+
allModuleTypes.Add(type.Name, type);
67+
68+
if (multiModuleType.IsAssignableFrom(type))
69+
multiModules.Add(type.Name);
6270
}
6371
}
6472
}
@@ -197,6 +205,19 @@ static IEnumerable<CodeInstruction> ProtoPartSnapshot_ConfigurePart_Transpiler(I
197205
return code;
198206
}
199207

208+
/// <summary>
209+
/// Our own version of ProtoPartModuleSnapshot.Load(). We reimplement it because :
210+
/// - Stock would do again all the indice / module matching that we just did
211+
/// - That stock logic would prevent our handling of loading derived into base / base into derived modules.
212+
/// </summary>
213+
private static void LoadProtoPartSnapshotModule(ProtoPartModuleSnapshot protoModule, PartModule module)
214+
{
215+
GameEvents.onProtoPartModuleSnapshotLoad.Fire(new GameEvents.FromToAction<ProtoPartModuleSnapshot, ConfigNode>(protoModule, null));
216+
module.Load(protoModule.moduleValues);
217+
module.snapshot = protoModule;
218+
protoModule.moduleRef = module;
219+
}
220+
200221
static void LoadModules(ProtoPartSnapshot protoPart, Part part)
201222
{
202223
int protoModuleCount = protoPart.modules.Count;
@@ -246,17 +267,17 @@ static void LoadModules(ProtoPartSnapshot protoPart, Part part)
246267

247268
}
248269

249-
protoPart.modules[i].Load(part, ref moduleIndex);
270+
LoadProtoPartSnapshotModule(protoPart.modules[i], part.modules[moduleIndex]);
250271
}
251272
return;
252273
}
253274

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

257-
for (int i = 0; i < protoModuleCount; i++)
278+
for (int protoModuleIdx = 0; protoModuleIdx < protoModuleCount; protoModuleIdx++)
258279
{
259-
ProtoPartModuleSnapshot protoModule = protoPart.modules[i];
280+
ProtoPartModuleSnapshot protoModule = protoPart.modules[protoModuleIdx];
260281

261282
if (multiModules.Contains(protoModule.moduleName))
262283
{
@@ -265,28 +286,26 @@ static void LoadModules(ProtoPartSnapshot protoPart, Part part)
265286
if (!string.IsNullOrEmpty(protoModuleId))
266287
{
267288
bool multiFound = false;
268-
for (int j = 0; j < partModuleCount; j++)
289+
for (int moduleIdx = 0; moduleIdx < partModuleCount; moduleIdx++)
269290
{
270-
if (part.Modules[j] is IMultipleModuleInPart multiModule && multiModule.ModulePartConfigId == protoModuleId)
291+
if (part.Modules[moduleIdx] is IMultipleModuleInPart multiModule && multiModule.ModulePartConfigId == protoModuleId)
271292
{
272-
int moduleIndex = j;
273-
protoModule.Load(part, ref moduleIndex);
274-
foundModules[j] = protoModule;
293+
LoadProtoPartSnapshotModule(protoPart.modules[protoModuleIdx], part.modules[moduleIdx]);
294+
foundModules[moduleIdx] = protoModule;
275295

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

279299
multiFound = true;
280300
break;
281301
}
282302
}
283303

284304
if (!multiFound)
285-
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");
305+
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");
286306
}
287307
}
288308

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

302321
int prefabIndexInType = 0;
303322
bool found = false;
304-
for (int j = 0; j < partModuleCount; j++)
323+
for (int moduleIdx = 0; moduleIdx < partModuleCount; moduleIdx++)
305324
{
306-
if (part.Modules[j].moduleName == protoModule.moduleName)
325+
if (part.Modules[moduleIdx].moduleName == protoModule.moduleName)
307326
{
308327
if (prefabIndexInType == protoIndexInType)
309328
{
310-
int moduleIndex = j;
311-
protoModule.Load(part, ref moduleIndex);
312-
foundModules[j] = protoModule;
329+
LoadProtoPartSnapshotModule(protoPart.modules[protoModuleIdx], part.modules[moduleIdx]);
330+
foundModules[moduleIdx] = protoModule;
313331

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

317335
found = true;
318336
break;
@@ -323,7 +341,21 @@ static void LoadModules(ProtoPartSnapshot protoPart, Part part)
323341
}
324342

325343
if (!found)
326-
Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Persisted module \"{protoModule.moduleName}\" at index [{i}] has been removed, no matching module in the part config");
344+
{
345+
// see https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/236
346+
// 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.
347+
if (protoModuleIdx < partModuleCount && allModuleTypes.TryGetValue(protoModule.moduleName, out Type moduleType)
348+
&& (part.Modules[protoModuleIdx].GetType().IsAssignableFrom(moduleType) || moduleType.IsInstanceOfType(part.Modules[protoModuleIdx])))
349+
{
350+
LoadProtoPartSnapshotModule(protoPart.modules[protoModuleIdx], part.modules[protoModuleIdx]);
351+
foundModules[protoModuleIdx] = protoModule;
352+
Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Persisted module \"{protoModule.moduleName}\" at index [{protoModuleIdx}] has been loaded as a \"{part.Modules[protoModuleIdx].moduleName}\"");
353+
}
354+
else
355+
{
356+
Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Persisted module \"{protoModule.moduleName}\" at index [{protoModuleIdx}] has been removed, no matching module in the part config");
357+
}
358+
}
327359
}
328360

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

422-
int nodeModuleCount = currentModuleNodes.Count;
454+
int moduleNodeCount = currentModuleNodes.Count;
423455

424-
string[] nodeModuleNames = new string[nodeModuleCount];
425-
for (int i = 0; i < nodeModuleCount; i++)
426-
nodeModuleNames[i] = currentModuleNodes[i].GetValue("name");
456+
string[] nodeModuleNames = new string[moduleNodeCount];
457+
for (int i = 0; i < moduleNodeCount; i++)
458+
{
459+
string moduleName = currentModuleNodes[i].GetValue("name") ?? string.Empty;
460+
nodeModuleNames[i] = moduleName;
461+
}
427462

428463
int partModuleCount = part.Modules.Count;
429-
bool inSync = nodeModuleCount == partModuleCount;
464+
bool inSync = moduleNodeCount == partModuleCount;
430465

431466
if (inSync)
432467
{
433-
for (int i = 0; i < nodeModuleCount; i++)
468+
for (int i = 0; i < moduleNodeCount; i++)
434469
{
435470
if (part.Modules[i].moduleName != nodeModuleNames[i])
436471
{
@@ -442,7 +477,7 @@ static void LoadShipModuleNodes(Part part, ConfigNode partNode)
442477

443478
if (inSync)
444479
{
445-
for (int i = 0; i < nodeModuleCount; i++)
480+
for (int i = 0; i < moduleNodeCount; i++)
446481
{
447482
int moduleIndex = i;
448483

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

472507
}
473508

474-
part.LoadModule(currentModuleNodes[i], ref moduleIndex);
509+
part.Modules[moduleIndex].Load(currentModuleNodes[i]);
475510
}
476511
return;
477512
}
478513

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

482-
for (int i = 0; i < nodeModuleCount; i++)
517+
for (int moduleNodeIdx = 0; moduleNodeIdx < moduleNodeCount; moduleNodeIdx++)
483518
{
484-
ConfigNode nodeModule = currentModuleNodes[i];
485-
string nodeModuleName = nodeModuleNames[i];
519+
ConfigNode moduleNode = currentModuleNodes[moduleNodeIdx];
520+
string nodeModuleName = nodeModuleNames[moduleNodeIdx];
486521

487522
if (multiModules.Contains(nodeModuleName))
488523
{
489-
string nodeModuleId = nodeModule.GetValue(VALUENAME_MODULEPARTCONFIGID);
524+
string nodeModuleId = moduleNode.GetValue(VALUENAME_MODULEPARTCONFIGID);
490525

491526
if (!string.IsNullOrEmpty(nodeModuleId))
492527
{
493528
bool multiFound = false;
494-
for (int j = 0; j < partModuleCount; j++)
529+
for (int moduleIdx = 0; moduleIdx < partModuleCount; moduleIdx++)
495530
{
496-
if (part.Modules[j] is IMultipleModuleInPart multiModule && multiModule.ModulePartConfigId == nodeModuleId)
531+
if (part.Modules[moduleIdx] is IMultipleModuleInPart multiModule && multiModule.ModulePartConfigId == nodeModuleId)
497532
{
498-
int moduleIndex = j;
499-
part.LoadModule(nodeModule, ref moduleIndex);
500-
foundModules[j] = nodeModule;
533+
part.Modules[moduleIdx].Load(moduleNode);
534+
foundModules[moduleIdx] = moduleNode;
501535

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

505539
multiFound = true;
506540
break;
507541
}
508542
}
509543

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

515549

516550
int nodeIndexInType = 0;
517-
for (int j = 0; j < nodeModuleCount; j++)
551+
for (int j = 0; j < moduleNodeCount; j++)
518552
{
519553
if (nodeModuleNames[j] == nodeModuleName)
520554
{
521-
if (currentModuleNodes[j] == nodeModule)
555+
if (currentModuleNodes[j] == moduleNode)
522556
break;
523557

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

528562
int prefabIndexInType = 0;
529563
bool found = false;
530-
for (int j = 0; j < partModuleCount; j++)
564+
for (int moduleIdx = 0; moduleIdx < partModuleCount; moduleIdx++)
531565
{
532-
if (part.Modules[j].moduleName == nodeModuleName)
566+
if (part.Modules[moduleIdx].moduleName == nodeModuleName)
533567
{
534568
if (prefabIndexInType == nodeIndexInType)
535569
{
536-
int moduleIndex = j;
537-
part.LoadModule(nodeModule, ref moduleIndex);
538-
foundModules[j] = nodeModule;
570+
part.Modules[moduleIdx].Load(moduleNode);
571+
foundModules[moduleIdx] = moduleNode;
539572

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

543576
found = true;
544577
break;
@@ -549,7 +582,22 @@ static void LoadShipModuleNodes(Part part, ConfigNode partNode)
549582
}
550583

551584
if (!found)
552-
Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Persisted module \"{nodeModuleName}\" at index [{i}] has been removed, no matching module in the part config");
585+
{
586+
// see https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/236
587+
// 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.
588+
if (moduleNodeIdx < partModuleCount
589+
&& allModuleTypes.TryGetValue(nodeModuleName, out Type moduleType)
590+
&& (part.Modules[moduleNodeIdx].GetType().IsAssignableFrom(moduleType) || moduleType.IsInstanceOfType(part.Modules[moduleNodeIdx])))
591+
{
592+
part.Modules[moduleNodeIdx].Load(moduleNode);
593+
foundModules[moduleNodeIdx] = moduleNode;
594+
Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Persisted module \"{nodeModuleName}\" at index [{moduleNodeIdx}] has been loaded as a \"{part.Modules[moduleNodeIdx].moduleName}\"");
595+
}
596+
else
597+
{
598+
Debug.LogWarning($"[KSPCF:ModuleIndexingMismatch] Persisted module \"{nodeModuleName}\" at index [{moduleNodeIdx}] has been removed, no matching module in the part config");
599+
}
600+
}
553601
}
554602

555603
for (int i = 0; i < partModuleCount; i++)

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,9 @@ Note that this patch [might be causing other issues](https://github.com/KSPModdi
219219
- 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).
220220
- 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).
221221
- 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).
222-
- **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).
223222
- 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).
223+
- **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).
224+
- **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.
224225

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

0 commit comments

Comments
 (0)