From 1770a4716d90e90df76cd8aaa976f91a8b1075a2 Mon Sep 17 00:00:00 2001 From: gotmachine <24925209+gotmachine@users.noreply.github.com> Date: Sun, 13 Oct 2024 20:47:25 +0200 Subject: [PATCH] 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. --- .../BugFixes/ModuleIndexingMismatch.cs | 152 ++++++++++++------ README.md | 3 +- 2 files changed, 102 insertions(+), 53 deletions(-) diff --git a/KSPCommunityFixes/BugFixes/ModuleIndexingMismatch.cs b/KSPCommunityFixes/BugFixes/ModuleIndexingMismatch.cs index f876b85..2014491 100644 --- a/KSPCommunityFixes/BugFixes/ModuleIndexingMismatch.cs +++ b/KSPCommunityFixes/BugFixes/ModuleIndexingMismatch.cs @@ -28,6 +28,7 @@ public class ModuleIndexingMismatch : BasePatch { private const string VALUENAME_MODULEPARTCONFIGID = "modulePartConfigId"; private static readonly HashSet multiModules = new HashSet(); + private static readonly Dictionary allModuleTypes = new Dictionary(); protected override Version VersionMin => new Version(1, 8, 0); @@ -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); } } } @@ -197,6 +205,19 @@ static IEnumerable ProtoPartSnapshot_ConfigurePart_Transpiler(I return code; } + /// + /// 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. + /// + private static void LoadProtoPartSnapshotModule(ProtoPartModuleSnapshot protoModule, PartModule module) + { + GameEvents.onProtoPartModuleSnapshotLoad.Fire(new GameEvents.FromToAction(protoModule, null)); + module.Load(protoModule.moduleValues); + module.snapshot = protoModule; + protoModule.moduleRef = module; + } + static void LoadModules(ProtoPartSnapshot protoPart, Part part) { int protoModuleCount = protoPart.modules.Count; @@ -246,7 +267,7 @@ static void LoadModules(ProtoPartSnapshot protoPart, Part part) } - protoPart.modules[i].Load(part, ref moduleIndex); + LoadProtoPartSnapshotModule(protoPart.modules[i], part.modules[moduleIndex]); } return; } @@ -254,9 +275,9 @@ static void LoadModules(ProtoPartSnapshot protoPart, Part part) 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)) { @@ -265,16 +286,15 @@ 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; @@ -282,11 +302,10 @@ static void LoadModules(ProtoPartSnapshot protoPart, Part part) } 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) { @@ -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; @@ -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++) @@ -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]) { @@ -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; @@ -471,7 +506,7 @@ static void LoadShipModuleNodes(Part part, ConfigNode partNode) } - part.LoadModule(currentModuleNodes[i], ref moduleIndex); + part.Modules[moduleIndex].Load(currentModuleNodes[i]); } return; } @@ -479,28 +514,27 @@ static void LoadShipModuleNodes(Part part, ConfigNode partNode) 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; @@ -508,17 +542,17 @@ static void LoadShipModuleNodes(Part part, ConfigNode partNode) } 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++; @@ -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; @@ -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++) diff --git a/README.md b/README.md index 7a534bb..a5184c4 100644 --- a/README.md +++ b/README.md @@ -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).