From ec7b540d6e08d2bbf7e91d3eb3252839d56c5305 Mon Sep 17 00:00:00 2001 From: zhangyuesai Date: Fri, 26 Jan 2024 09:44:36 +0800 Subject: [PATCH 1/4] zh-cn localization for ManufacturerFixes.cfg (#178) --- .../MMPatches/StockTweaks/ManufacturerFixes.cfg | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/GameData/KSPCommunityFixes/MMPatches/StockTweaks/ManufacturerFixes.cfg b/GameData/KSPCommunityFixes/MMPatches/StockTweaks/ManufacturerFixes.cfg index b959383..566b665 100644 --- a/GameData/KSPCommunityFixes/MMPatches/StockTweaks/ManufacturerFixes.cfg +++ b/GameData/KSPCommunityFixes/MMPatches/StockTweaks/ManufacturerFixes.cfg @@ -56,4 +56,9 @@ Localization #KSPCF_Agents_clampOTronTitle = Хват-О-Трон #KSPCF_Agents_freeFallTitle = Парашюты Свободного Падения } + + zh-cn { + #KSPCF_Agents_clampOTronTitle = 夹具奥创 + #KSPCF_Agents_freeFallTitle = 自由落体降落伞 + } } \ No newline at end of file From a01a7027a697a3e877008523260990256449a381 Mon Sep 17 00:00:00 2001 From: JonnyOThan Date: Thu, 25 Jan 2024 20:49:36 -0500 Subject: [PATCH 2/4] Remove ENABLE_PROFILER define that got added recently --- KSPCommunityFixes/KSPCommunityFixes.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KSPCommunityFixes/KSPCommunityFixes.csproj b/KSPCommunityFixes/KSPCommunityFixes.csproj index fd15411..d54a407 100644 --- a/KSPCommunityFixes/KSPCommunityFixes.csproj +++ b/KSPCommunityFixes/KSPCommunityFixes.csproj @@ -33,7 +33,7 @@ portable true bin\Release\ - TRACE;ENABLE_PROFILER + TRACE prompt 4 true From baf96a7fa75abb7701d4ddab31a3da274b65f8cb Mon Sep 17 00:00:00 2001 From: JonnyOThan Date: Tue, 30 Jan 2024 13:34:32 -0500 Subject: [PATCH 3/4] Merge 1.34 from dev to master (#188) * New KSP QoL/performance patch : [**LowerMinPhysicsDTPerFrame**](https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/175) : Allow a min value of 0.02 instead of 0.03 for the "Max Physics Delta-Time Per Frame" main menu setting. This was already possible by manually editing the `settings.cfg` file, but changes would revert when going into the settings screen. * Add tooltip describing the "Max Physics Delta-Time Per Frame" main menu setting * merging master to dev (#181) * zh-cn localization for ManufacturerFixes.cfg (#178) * Remove ENABLE_PROFILER define that got added recently --------- Co-authored-by: zhangyuesai * Fix #182: ModulePartInventory now accounts for changes to a part's mass or volume -for example, changing a part's variant (rcs thruster blocks, structural tube) or its resource level could change its mass, but when you store it in inventory the mass would always use the value from the prefab * oops, revert a couple changes to the csproj that made it into the last commit by accident * Fix #185: calculate the correct mass for parts when picking them up off the ground in EVA construction * Fix #104 : set dead kerbals to missing when loading a game and respawning is enabled * Standardize spaces for indent in .editorconfig and add it to the SLN (note personally I'd prefer tabs for indents, but all the existing code in here that didn't come from me uses spaces and consistency is more important) * Change tabs -> spaces in the few files that used it Whitespace-only change. (note most of these were originally authored by me, but the project as a whole uses spaces pretty consistently) * Fix #180: add ZeroCostTechNode patch * Better editor undo redo (#176) * BetterEditorUndoRedo : Exploratory fix for undo/redo state, doesn't work because of VesselCrewManifest side effects See https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/172 * BetterEditorUndoRedo : functional, obvious issues fixed, still likely to cause side effects. * disable BetterEditorUndoRedo by default --------- Co-authored-by: JonnyOThan * PAWGroupMemory is no longer per-window, but rather per-group (#186) * Fix #50: PAWGroupMemory is no longer per-PAW, but stores the expansion state for all windows and is not cleared on scene changes -possible future work: persist the collapse state to disk * collapse or expand PAW groups when the PAW is shown * Fix #179: ModulePartVariants now applies node position changes in all scenes * enable BetterEditorUndoRedo by default * Merge master -> dev (#187) * zh-cn localization for ManufacturerFixes.cfg (#178) * Remove ENABLE_PROFILER define that got added recently --------- Co-authored-by: zhangyuesai * update readme with the new fixes --------- Co-authored-by: gotmachine <24925209+gotmachine@users.noreply.github.com> Co-authored-by: zhangyuesai --- .editorconfig | 7 +- .../KSPCommunityFixes.version | 2 +- .../KSPCommunityFixes/Localization/en-us.cfg | 26 +- GameData/KSPCommunityFixes/Settings.cfg | 28 ++ KSPCommunityFixes.sln | 1 + .../BugFixes/EVAConstructionMass.cs | 55 +++ .../BugFixes/InventoryPartMass.cs | 167 +++++++ .../ModulePartVariantsNodePersistence.cs | 76 +++ KSPCommunityFixes/BugFixes/PAWGroupMemory.cs | 41 +- .../BugFixes/RespawnDeadKerbals.cs | 38 ++ .../BugFixes/StickySplashedFixer.cs | 4 +- .../BugFixes/ThumbnailSpotlight.cs | 30 +- .../BugFixes/ZeroCostTechNodes.cs | 55 +++ KSPCommunityFixes/KSPCommunityFixes.csproj | 7 + KSPCommunityFixes/Library/StaticHelpers.cs | 42 +- .../Performance/CommNetThrottling.cs | 68 +-- .../Performance/LowerMinPhysicsDTPerFrame.cs | 110 +++++ .../Performance/ProgressTrackingSpeedBoost.cs | 54 +-- KSPCommunityFixes/Properties/AssemblyInfo.cs | 4 +- KSPCommunityFixes/QoL/BetterEditorUndoRedo.cs | 437 ++++++++++++++++++ README.md | 19 + 21 files changed, 1150 insertions(+), 121 deletions(-) create mode 100644 KSPCommunityFixes/BugFixes/EVAConstructionMass.cs create mode 100644 KSPCommunityFixes/BugFixes/InventoryPartMass.cs create mode 100644 KSPCommunityFixes/BugFixes/ModulePartVariantsNodePersistence.cs create mode 100644 KSPCommunityFixes/BugFixes/RespawnDeadKerbals.cs create mode 100644 KSPCommunityFixes/BugFixes/ZeroCostTechNodes.cs create mode 100644 KSPCommunityFixes/Performance/LowerMinPhysicsDTPerFrame.cs create mode 100644 KSPCommunityFixes/QoL/BetterEditorUndoRedo.cs diff --git a/.editorconfig b/.editorconfig index fbb29e8..95cecef 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,9 @@ -[*.cs] +root = true + +[*.cs] + +indent_style = space +indent_size = 4 # IDE0051: Remove unused private members dotnet_diagnostic.IDE0051.severity = none diff --git a/GameData/KSPCommunityFixes/KSPCommunityFixes.version b/GameData/KSPCommunityFixes/KSPCommunityFixes.version index d9c4cd4..84587b4 100644 --- a/GameData/KSPCommunityFixes/KSPCommunityFixes.version +++ b/GameData/KSPCommunityFixes/KSPCommunityFixes.version @@ -2,7 +2,7 @@ "NAME": "KSPCommunityFixes", "URL": "https://raw.githubusercontent.com/KSPModdingLibs/KSPCommunityFixes/master/GameData/KSPCommunityFixes/KSPCommunityFixes.version", "DOWNLOAD": "https://github.com/KSPModdingLibs/KSPCommunityFixes/releases", - "VERSION": {"MAJOR": 1, "MINOR": 33, "PATCH": 0, "BUILD": 0}, + "VERSION": {"MAJOR": 1, "MINOR": 34, "PATCH": 0, "BUILD": 0}, "KSP_VERSION": {"MAJOR": 1, "MINOR": 12, "PATCH": 5}, "KSP_VERSION_MIN": {"MAJOR": 1, "MINOR": 8, "PATCH": 0}, "KSP_VERSION_MAX": {"MAJOR": 1, "MINOR": 12, "PATCH": 5} diff --git a/GameData/KSPCommunityFixes/Localization/en-us.cfg b/GameData/KSPCommunityFixes/Localization/en-us.cfg index 9f9a434..f292023 100644 --- a/GameData/KSPCommunityFixes/Localization/en-us.cfg +++ b/GameData/KSPCommunityFixes/Localization/en-us.cfg @@ -3,16 +3,6 @@ Localization en-us { - // KSPCFFastLoader - - #KSPCF__SettingsTitle = Texture caching optimization - #KSPCF_KSPCFFastLoader_SettingsTooltip = Cache PNG textures on disk instead of converting them on every KSP launch.\nSpeedup loading time but increase disk space usage.\nChanges will take effect after relaunching KSP - #KSPCF_KSPCFFastLoader_PopupL1 = KSPCommunityFixes can cache converted PNG textures on disk to speed up loading time. - #KSPCF_KSPCFFastLoader_F_PopupL2 = In your current install, this should reduce future loading time by about <<1>> seconds. - #KSPCF_KSPCFFastLoader_F_PopupL3 = However, this will use about <<1>> MB of additional disk space, and potentially much more if you install additional mods. - #KSPCF_KSPCFFastLoader_PopupL4 = You can change this setting later in the in-game settings menu - #KSPCF_KSPCFFastLoader_PopupL5 = Do you want to enable this optimization ? - // KSPCommunityFixes #KSPCF_KSPCommunityFixes_KSPCF_Title = KSP Community Fixes @@ -34,6 +24,20 @@ Localization #KSPCF_DisableManeuverTool_SettingsTooltip = The stock maneuver tool can cause severe lag and stutter issues,\nespecially with Kopernicus modified systems.\nThis option allow to disable it entirely + // KSPCFFastLoader + + #KSPCF_KSPCFFastLoader_SettingsTitle = Texture caching optimization + #KSPCF_KSPCFFastLoader_SettingsTooltip = Cache PNG textures on disk instead of converting them on every KSP launch.\nSpeedup loading time but increase disk space usage.\nChanges will take effect after relaunching KSP + #KSPCF_KSPCFFastLoader_PopupL1 = KSPCommunityFixes can cache converted PNG textures on disk to speed up loading time. + #KSPCF_KSPCFFastLoader_F_PopupL2 = In your current install, this should reduce future loading time by about <<1>> seconds. + #KSPCF_KSPCFFastLoader_F_PopupL3 = However, this will use about <<1>> MB of additional disk space, and potentially much more if you install additional mods. + #KSPCF_KSPCFFastLoader_PopupL4 = You can change this setting later in the in-game settings menu + #KSPCF_KSPCFFastLoader_PopupL5 = Do you want to enable this optimization ? + + // LowerMinPhysicsDTPerFrame + + #KSPCF_LowerMinPhysicsDTPerFrame_SettingsTooltip = How the game handle lag in CPU bound situations.\nMostly relevant with large part count vessels.\n\nLower value :\nHigher and smoother FPS, but game time might advance slower than real time.\n\nHigher value :\nLower and choppier FPS, but game time will advance closer to real time. + // ReflectionTypeLoadExceptionHandler #KSPCF_ReflectionTypeLoadExceptionHandler_KSPCFWarning = KSPCommunityFixes warning @@ -41,4 +45,4 @@ Localization #KSPCF_ReflectionTypeLoadExceptionHandler_F_PluginLoadFailed_name_in_location = <<1>> in <<2>> #KSPCF_ReflectionTypeLoadExceptionHandler_PluginLoadFailed_missingDep = Load failed due to missing dependencies } -} \ No newline at end of file +} diff --git a/GameData/KSPCommunityFixes/Settings.cfg b/GameData/KSPCommunityFixes/Settings.cfg index 75eddd6..e6d4d44 100644 --- a/GameData/KSPCommunityFixes/Settings.cfg +++ b/GameData/KSPCommunityFixes/Settings.cfg @@ -195,6 +195,28 @@ KSP_COMMUNITY_FIXES // Fix active vessel orbit moving randomly when engaging timewarp while under heavy CPU load. TimeWarpOrbitShift = true + // Fixes mass and volume of parts stored in inventories, especially when part variants or + // non-default resource amounts are used. + // Prevents changing the variant of parts in inventory if it can modify the cost or mass. + // Fixes the part info tooltip so that it shows the stored part's data instead of the prefab. + InventoryPartMass = true + + // Fixes bugs related to part mass changing during EVA construction + EVAConstructionMass = true + + // If respawning is enabled, starts the respawn timer for dead kerbals on game load, changing + // them to "missing." There appears to be a bug in the stock game where sometimes Kerbals are + // set to "dead" instead of "missing" when they die, even when the respawn option is enabled. + RespawnDeadKerbals = true + + // Fixes an issue where parts in tech nodes that have zero science cost become unusable + ZeroCostTechNodes = true + + // Fixes a bug where ModulePartVariants does not alter attachnode positions when resuming a + // vessel in flight. This can lead to different part joint behaviors depending on whether the + // vessel was spawned directly from the editor or loaded from the saved game. + ModulePartVariantsNodePersistence = true + // ########################## // Obsolete bugfixes // ########################## @@ -259,6 +281,9 @@ KSP_COMMUNITY_FIXES // Add part actions for locking/unlocking part resources flow state. ResourceLockActions = true + // Invert the editor undo state capturing logic so part tweaks aren't lost when undoing. + BetterEditorUndoRedo = true + // ########################## // Performance tweaks // ########################## @@ -375,6 +400,9 @@ KSP_COMMUNITY_FIXES // docking, undocking, decoupling and joint failure events. CollisionManagerFastUpdate = true + // Allow a min value of 0.02 instead of 0.03 for the "Max Physics Delta-Time Per Frame" main menu setting. + LowerMinPhysicsDTPerFrame = true + // ########################## // Modding // ########################## diff --git a/KSPCommunityFixes.sln b/KSPCommunityFixes.sln index ada51a7..86ca9e6 100644 --- a/KSPCommunityFixes.sln +++ b/KSPCommunityFixes.sln @@ -7,6 +7,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KSPCommunityFixes", "KSPCom EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3795CD6A-BBEE-4827-B716-DD274135EEF3}" ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig KSPCommunityFixes\KSPCommunityFixes.csproj.user = KSPCommunityFixes\KSPCommunityFixes.csproj.user GameData\KSPCommunityFixes\KSPCommunityFixes.version = GameData\KSPCommunityFixes\KSPCommunityFixes.version README.md = README.md diff --git a/KSPCommunityFixes/BugFixes/EVAConstructionMass.cs b/KSPCommunityFixes/BugFixes/EVAConstructionMass.cs new file mode 100644 index 0000000..d8d9b6c --- /dev/null +++ b/KSPCommunityFixes/BugFixes/EVAConstructionMass.cs @@ -0,0 +1,55 @@ +using HarmonyLib; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Emit; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace KSPCommunityFixes.BugFixes +{ + // https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/185 + + class EVAConstructionMass : BasePatch + { + protected override Version VersionMin => new Version(1, 12, 3); + + protected override void ApplyPatches(List patches) + { + patches.Add(new PatchInfo( + PatchMethodType.Transpiler, + AccessTools.Method(typeof(EVAConstructionModeEditor), nameof(EVAConstructionModeEditor.PickupPart)), + this)); + } + + // the stock code sets selectedpart.mass to the prefab mass, which breaks terribly in cases where there are mass modifiers involved + // I suspect this code exists at all because ModuleCargoPart.MakePartSettle alters the part's prefabMass to make it harder to move the part around when it's been dropped on the ground + // To fix this, we restore the *prefabMass* field from the prefab, and then call UpdateMass so that IPartMassModifiers can do their thing. + static IEnumerable EVAConstructionModeEditor_PickupPart_Transpiler(IEnumerable instructions) + { + FieldInfo partMassField = AccessTools.Field(typeof(Part), nameof(Part.mass)); + FieldInfo partPrefabMassField = AccessTools.Field(typeof(Part), nameof(Part.prefabMass)); + FieldInfo EditorLogicBase_selectedPart = AccessTools.Field(typeof(EditorLogicBase), nameof(EditorLogicBase.selectedPart)); + MethodInfo Part_UpdateMass = AccessTools.Method(typeof(Part), nameof(Part.UpdateMass)); + foreach (var instruction in instructions) + { + if (instruction.StoresField(partMassField)) + { + instruction.operand = partPrefabMassField; + yield return instruction; + + // call Part.UpdateMass + yield return new CodeInstruction(OpCodes.Ldarg_0); + yield return new CodeInstruction(OpCodes.Ldfld, EditorLogicBase_selectedPart); + yield return new CodeInstruction(OpCodes.Call, Part_UpdateMass); + } + else + { + yield return instruction; + } + } + } + } +} diff --git a/KSPCommunityFixes/BugFixes/InventoryPartMass.cs b/KSPCommunityFixes/BugFixes/InventoryPartMass.cs new file mode 100644 index 0000000..f2ae18f --- /dev/null +++ b/KSPCommunityFixes/BugFixes/InventoryPartMass.cs @@ -0,0 +1,167 @@ +using HarmonyLib; +using KSP.Localization; +using KSP.UI.Screens; +using KSP.UI.Screens.Editor; +using System; +using System.Collections.Generic; + +namespace KSPCommunityFixes.BugFixes +{ + // https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/182 + + class InventoryPartMass : BasePatch + { + protected override Version VersionMin => new Version(1, 12, 0); + + protected override void ApplyPatches(List patches) + { + patches.Add(new PatchInfo( + PatchMethodType.Prefix, + AccessTools.Method(typeof(ModuleInventoryPart), nameof(ModuleInventoryPart.UpdateCapacityValues)), + this)); + + var EditorPartIcon_Create_ArgTypes = new Type[] + { + typeof(EditorPartList), + typeof(AvailablePart), + typeof(StoredPart), + typeof(float), + typeof(float), + typeof(float), + typeof(Callback), + typeof(bool), + typeof(bool), + typeof(PartVariant), + typeof(bool), + typeof(bool) + }; + + patches.Add(new PatchInfo( + PatchMethodType.Postfix, + AccessTools.Method(typeof(KSP.UI.Screens.EditorPartIcon), nameof(KSP.UI.Screens.EditorPartIcon.Create), EditorPartIcon_Create_ArgTypes), + this)); + + patches.Add(new PatchInfo( + PatchMethodType.Postfix, + AccessTools.Method(typeof(InventoryPartListTooltip), nameof(InventoryPartListTooltip.CreateInfoWidgets)), + this)); + + // Making packedVolume persistent helps track what cargo modules *should* be if they were changed from the prefab before being added to the inventory + StaticHelpers.EditPartModuleKSPFieldAttributes(typeof(ModuleCargoPart), nameof(ModuleCargoPart.packedVolume), field => field.isPersistant = true); + } + + // the stock version of this function uses values from the prefab only, which is incorrect when mass modifiers are used (e.g. ModulePartVariants) or the packed volume is changed (e.g. TweakScale) or the resource levels are changed + static bool ModuleInventoryPart_UpdateCapacityValues_Prefix(ModuleInventoryPart __instance) + { + __instance.volumeOccupied = 0.0f; + __instance.massOccupied = 0.0f; + foreach (StoredPart storedPart in __instance.storedParts.ValuesList) + { + if (storedPart != null && storedPart.snapshot != null) + { + __instance.massOccupied += GetPartSnapshotMass(storedPart.snapshot) * storedPart.quantity; // This won't be correct if different parts in the stack have different mass modifiers, but really they shouldn't have been stacked in the first place + __instance.volumeOccupied += GetPartSnapshotVolume(storedPart.snapshot) * storedPart.quantity; // see above. + } + } + __instance.UpdateMassVolumeDisplay(true, false); + return false; + } + + static float GetPartSnapshotMass(ProtoPartSnapshot partSnapshot) + { + double mass = partSnapshot.mass; + + foreach (var resource in partSnapshot.resources) + { + mass += resource.amount * resource.definition.density; + } + + return (float)mass; + } + + static float GetPartSnapshotVolume(ProtoPartSnapshot partSnapshot) + { + // fetch the volume from the cargo module snapshot + foreach (var moduleSnapshot in partSnapshot.modules) + { + if (moduleSnapshot.moduleName != nameof(ModuleCargoPart)) continue; + + float packedVolume = 0; + if (moduleSnapshot.moduleValues.TryGetValue(nameof(ModuleCargoPart.packedVolume), ref packedVolume)) + { + return packedVolume; + } + } + + // otherwise we have to fall back to the prefab volume (this is stock behavior) + ModuleCargoPart moduleCargoPart = partSnapshot.partPrefab.FindModuleImplementing(); + if (moduleCargoPart != null) + { + return moduleCargoPart.packedVolume; + } + return 0f; + } + + // the game doesn't handle swapping variants very well for parts in inventories - mass and cost modifiers are not applied, etc. + // It would be possible but messy and bug-prone to go modify the partsnapshot in the inventory when you swap variants + // To sidestep the whole thing, just disallow changing variants for parts that have cost or mass modifiers while they're in inventory. + static void EditorPartIcon_Create_Postfix(EditorPartIcon __instance, AvailablePart part, bool inInventory) + { + if (!inInventory || part.Variants == null || __instance.btnSwapTexture == null) return; + + foreach (var variant in part.Variants) + { + if (variant.cost != 0 || variant.mass != 0) + { + __instance.btnSwapTexture.gameObject.SetActive(false); + return; + } + } + } + + // The stock method gets the ModuleInfo strings from the prefab. ModuleCargoPart reports the dry mass and packed volume of the part, and + // swapping variants in the editor parts list will change this so that it doesn't reflect the state of the part that's actually in inventory. + // We don't have a good way to get an updated moduleinfo from the part in inventory (it requires a live part, and it's not stored in the part snapshot) + static void InventoryPartListTooltip_CreateInfoWidgets_Postfix(InventoryPartListTooltip __instance) + { + string moduleTitle = KSPUtil.PrintModuleName(nameof(ModuleCargoPart)); + + // find the widget corresponding to ModuleCargoPart + foreach (var moduleInfo in __instance.partInfo.moduleInfos) + { + if (moduleInfo.moduleName == moduleTitle) + { + foreach (var widget in __instance.extInfoModules) + { + if (widget.gameObject.activeSelf && widget.textName.text == moduleInfo.moduleDisplayName) + { + widget.textInfo.text = GetModuleCargoPartInfo(__instance.inventoryStoredPart); + return; + } + } + } + } + } + + // this is effectively ModuleCargoPart.GetInfo but can operate on a storedPart instead + static string GetModuleCargoPartInfo(StoredPart storedPart) + { + float packedVolume = GetPartSnapshotVolume(storedPart.snapshot); + int stackableQuantity = storedPart.snapshot.moduleCargoStackableQuantity; + + string text = ""; + text = ((!(packedVolume < 0f)) ? Localizer.Format("#autoLOC_8002220") : Localizer.Format("#autoLOC_6002641")); + text += "\n\n"; + text = text + Localizer.Format("#autoLOC_8002186") + " " + storedPart.snapshot.mass.ToString("F3") + " t\\n"; + if (packedVolume > 0f) + { + text = text + Localizer.Format("#autoLOC_8004190", Localizer.Format("#autoLOC_8003414"), Localizer.Format("<<1>><<2>>", packedVolume.ToString("0.0"), "L")) + "\n"; + } + if (stackableQuantity > 1) + { + text = text + Localizer.Format("#autoLOC_8004190", Localizer.Format("#autoLOC_8003418"), stackableQuantity.ToString("0")) + "\n"; + } + return text; + } + } +} diff --git a/KSPCommunityFixes/BugFixes/ModulePartVariantsNodePersistence.cs b/KSPCommunityFixes/BugFixes/ModulePartVariantsNodePersistence.cs new file mode 100644 index 0000000..69a6d89 --- /dev/null +++ b/KSPCommunityFixes/BugFixes/ModulePartVariantsNodePersistence.cs @@ -0,0 +1,76 @@ +using HarmonyLib; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace KSPCommunityFixes.BugFixes +{ + // https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/179 + // ModulePartVariants can move attachnodes - most notably for the structural tube parts in MakingHistory. + // When a vessel is started from a craft file, the actual positions of the attachnodes are stored in the + // craft file and get created correctly. However when resuming a flight in progress, the attachnode + // positions are NOT stored in the persistence file and will use whatever location was set in the prefab. + // This affects where the part joints are created, and can have a significant impact on part flexibility. + + // This patch alters ModulePartVariants.OnStart so that it processes attachnode positions in all scenes + // (the stock code only does this in EDITOR and LOADING scenes). However UpdateNode will then call + // UpdatePartPosition which is something that should only be done in the editor. + + internal class ModulePartVariantsNodePersistence : BasePatch + { + protected override void ApplyPatches(List patches) + { + Type[] ApplyVariant_parameterTypes = new Type[] + { + typeof(Part), + typeof(Transform), + typeof(PartVariant), + typeof(Material[]), + typeof(bool), + typeof(int) + }; + + patches.Add(new PatchInfo( + PatchMethodType.Transpiler, + AccessTools.Method(typeof(ModulePartVariants), nameof(ModulePartVariants.ApplyVariant), ApplyVariant_parameterTypes), + this)); + + patches.Add(new PatchInfo( + PatchMethodType.Prefix, + AccessTools.Method(typeof(ModulePartVariants), nameof(ModulePartVariants.UpdatePartPosition)), + this)); + } + + static IEnumerable ModulePartVariants_ApplyVariant_Transpiler(IEnumerable instructions) + { + CodeInstruction[] instructionsArr = instructions.ToArray(); + + FieldInfo HighLogic_LoadedScene_FieldInfo = AccessTools.Field(typeof(HighLogic), nameof(HighLogic.LoadedScene)); + + for (int i = 0; i < instructionsArr.Length; i++) + { + // find the if statement that checks for EDITOR scene, and make it always compare equal + if (instructionsArr[i+0].LoadsField(HighLogic_LoadedScene_FieldInfo) && + instructionsArr[i+1].LoadsConstant(GameScenes.EDITOR) && + instructionsArr[i+2].opcode == OpCodes.Beq_S) + { + instructionsArr[i+1] = new CodeInstruction(OpCodes.Ldsfld, HighLogic_LoadedScene_FieldInfo); + return instructionsArr; + } + } + + throw new Exception("Failed to find code patch location"); + } + + // Prevent UpdatePartPosition from running outside of the editor scene + static bool ModulePartVariants_UpdatePartPosition_Prefix() + { + return HighLogic.LoadedSceneIsEditor; + } + } +} diff --git a/KSPCommunityFixes/BugFixes/PAWGroupMemory.cs b/KSPCommunityFixes/BugFixes/PAWGroupMemory.cs index ac01d19..4ece5f6 100644 --- a/KSPCommunityFixes/BugFixes/PAWGroupMemory.cs +++ b/KSPCommunityFixes/BugFixes/PAWGroupMemory.cs @@ -10,7 +10,7 @@ public class PAWGroupMemory : BasePatch { protected override Version VersionMin => new Version(1, 8, 0); - private static Dictionary> collapseState; + private static Dictionary collapseState; protected override void ApplyPatches(List patches) { @@ -29,14 +29,20 @@ protected override void ApplyPatches(List patches) AccessTools.Method(typeof(UIPartActionGroup), nameof(UIPartActionGroup.Expand)), this)); - collapseState = new Dictionary>(); + collapseState = new Dictionary(); - GameEvents.onGameSceneSwitchRequested.Add(OnSceneSwitch); + GameEvents.onPartActionUIShown.Add(OnPartActionUIShown); } - private void OnSceneSwitch(GameEvents.FromToAction data) + private void OnPartActionUIShown(UIPartActionWindow paw, Part part) { - collapseState.Clear(); + foreach (var groupPair in paw.parameterGroups.dict) + { + if (collapseState.TryGetValue(groupPair.Key, out bool state) && state != groupPair.Value.isContentCollapsed) + { + groupPair.Value.CollapseGroupToggle(); + } + } } static IEnumerable UIPartActionGroup_Initialize_Transpiler(IEnumerable instructions, ILGenerator il) @@ -168,8 +174,7 @@ IL_002b ldarg.0 static bool IsGroupCollapsed(UIPartActionWindow pawWindow, string groupName, out bool collapsed) { - int id = pawWindow.part?.GetInstanceID() ?? 0; - if (id != 0 && collapseState.TryGetValue(id, out Dictionary groups) && groups.TryGetValue(groupName, out collapsed)) + if (collapseState.TryGetValue(groupName, out collapsed)) return true; collapsed = false; @@ -181,16 +186,7 @@ static void UIPartActionGroup_Collapse_Postfix(UIPartActionGroup __instance, str if (string.IsNullOrEmpty(___groupName)) return; - int id = __instance.Window?.part?.GetInstanceID() ?? 0; - if (id == 0) - return; - - if (!collapseState.TryGetValue(id, out Dictionary partGroups)) - { - partGroups = new Dictionary(); - collapseState[id] = partGroups; - } - partGroups[___groupName] = true; + collapseState[___groupName] = true; } static void UIPartActionGroup_Expand_Postfix(UIPartActionGroup __instance, string ___groupName) @@ -198,16 +194,7 @@ static void UIPartActionGroup_Expand_Postfix(UIPartActionGroup __instance, strin if (string.IsNullOrEmpty(___groupName)) return; - int id = __instance.Window?.part?.GetInstanceID() ?? 0; - if (id == 0) - return; - - if (!collapseState.TryGetValue(id, out Dictionary partGroups)) - { - partGroups = new Dictionary(); - collapseState[id] = partGroups; - } - partGroups[___groupName] = false; + collapseState[___groupName] = false; } } } diff --git a/KSPCommunityFixes/BugFixes/RespawnDeadKerbals.cs b/KSPCommunityFixes/BugFixes/RespawnDeadKerbals.cs new file mode 100644 index 0000000..de47b03 --- /dev/null +++ b/KSPCommunityFixes/BugFixes/RespawnDeadKerbals.cs @@ -0,0 +1,38 @@ +using HarmonyLib; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KSPCommunityFixes.BugFixes +{ + class RespawnDeadKerbals : BasePatch + { + + protected override void ApplyPatches(List patches) + { + patches.Add(new PatchInfo( + PatchMethodType.Postfix, + AccessTools.Constructor(typeof(Game), new Type[] { typeof(ConfigNode) }), + this, + nameof(Game_Constructor_Postfix))); + } + + static void Game_Constructor_Postfix(Game __instance) + { + if (__instance.Parameters.Difficulty.MissingCrewsRespawn) + { + double respawnUTC = __instance.UniversalTime + __instance.Parameters.Difficulty.RespawnTimer; + + foreach (var protoCrewMember in __instance.CrewRoster.kerbals.ValuesList) + { + if (protoCrewMember.type == ProtoCrewMember.KerbalType.Crew && protoCrewMember.rosterStatus == ProtoCrewMember.RosterStatus.Dead) + { + protoCrewMember.SetTimeForRespawn(respawnUTC); + } + } + } + } + } +} diff --git a/KSPCommunityFixes/BugFixes/StickySplashedFixer.cs b/KSPCommunityFixes/BugFixes/StickySplashedFixer.cs index 879789f..404faf6 100644 --- a/KSPCommunityFixes/BugFixes/StickySplashedFixer.cs +++ b/KSPCommunityFixes/BugFixes/StickySplashedFixer.cs @@ -38,7 +38,7 @@ protected override void ApplyPatches(List patches) static bool Vessel_updateSituation_Prefix(Vessel __instance) { - bool evaOnLadderOnOtherVessel; + bool evaOnLadderOnOtherVessel; if (__instance.EVALadderVessel != __instance) { __instance.situation = __instance.evaController.LadderPart.vessel.situation; @@ -95,7 +95,7 @@ static bool Vessel_updateSituation_Prefix(Vessel __instance) { __instance.wasLadder = evaOnLadderOnOtherVessel; } - + return false; } diff --git a/KSPCommunityFixes/BugFixes/ThumbnailSpotlight.cs b/KSPCommunityFixes/BugFixes/ThumbnailSpotlight.cs index 85996e7..460cc34 100644 --- a/KSPCommunityFixes/BugFixes/ThumbnailSpotlight.cs +++ b/KSPCommunityFixes/BugFixes/ThumbnailSpotlight.cs @@ -8,20 +8,20 @@ class ThumbnailSpotlight : BasePatch { protected override Version VersionMin => new Version(1, 12, 0); - protected override void ApplyPatches(List patches) - { - patches.Add(new PatchInfo( - PatchMethodType.Postfix, - AccessTools.Method(typeof(CraftThumbnail), nameof(CraftThumbnail.TakePartSnapshot)), - this)); - } + protected override void ApplyPatches(List patches) + { + patches.Add(new PatchInfo( + PatchMethodType.Postfix, + AccessTools.Method(typeof(CraftThumbnail), nameof(CraftThumbnail.TakePartSnapshot)), + this)); + } - private static void CraftThumbnail_TakePartSnapshot_Postfix() - { - if (CraftThumbnail.snapshotCamera.IsNotNullOrDestroyed()) - { - UnityEngine.Object.Destroy(CraftThumbnail.snapshotCamera.gameObject); - } - } - } + private static void CraftThumbnail_TakePartSnapshot_Postfix() + { + if (CraftThumbnail.snapshotCamera.IsNotNullOrDestroyed()) + { + UnityEngine.Object.Destroy(CraftThumbnail.snapshotCamera.gameObject); + } + } + } } diff --git a/KSPCommunityFixes/BugFixes/ZeroCostTechNodes.cs b/KSPCommunityFixes/BugFixes/ZeroCostTechNodes.cs new file mode 100644 index 0000000..1edc9c5 --- /dev/null +++ b/KSPCommunityFixes/BugFixes/ZeroCostTechNodes.cs @@ -0,0 +1,55 @@ +using HarmonyLib; +using System; +using System.Collections.Generic; +using System.Reflection.Emit; + +namespace KSPCommunityFixes.BugFixes +{ + // RDTech.Load will force the tech's state to State.Available (i.e. researched) if the science cost is 0 + // this doesn't work properly in game modes where you need to purchase parts after researching the tech + // because it doesn't set up the correct data structures. + // To fix this, we remove the modification to the state field in the Load function and then research the + // tech after Start has run to set up all the data properly. + + internal class ZeroCostTechNodes : BasePatch + { + protected override void ApplyPatches(List patches) + { + patches.Add(new PatchInfo( + PatchMethodType.Postfix, + AccessTools.Method(typeof(RDTech), nameof(RDTech.Start)), + this)); + + patches.Add(new PatchInfo( + PatchMethodType.Transpiler, + AccessTools.Method(typeof(RDTech), nameof(RDTech.Load)), + this)); + } + + static void RDTech_Start_Postfix(RDTech __instance) + { + if (__instance.scienceCost == 0 && __instance.state != RDTech.State.Available) + { + __instance.UnlockTech(true); + } + } + + static IEnumerable RDTech_Load_Transpiler(IEnumerable instructions) + { + var stateField = AccessTools.Field(typeof(RDTech), nameof(RDTech.state)); + foreach (CodeInstruction instruction in instructions) + { + if (instruction.StoresField(stateField)) + { + // need to pop 2 values off the stack - the value to store and the field where to store it + yield return new CodeInstruction(OpCodes.Pop); + yield return new CodeInstruction(OpCodes.Pop); + } + else + { + yield return instruction; + } + } + } + } +} diff --git a/KSPCommunityFixes/KSPCommunityFixes.csproj b/KSPCommunityFixes/KSPCommunityFixes.csproj index d54a407..0b27596 100644 --- a/KSPCommunityFixes/KSPCommunityFixes.csproj +++ b/KSPCommunityFixes/KSPCommunityFixes.csproj @@ -104,12 +104,16 @@ + + + + @@ -117,6 +121,7 @@ + @@ -134,6 +139,7 @@ + @@ -167,6 +173,7 @@ + diff --git a/KSPCommunityFixes/Library/StaticHelpers.cs b/KSPCommunityFixes/Library/StaticHelpers.cs index d968980..20441d9 100644 --- a/KSPCommunityFixes/Library/StaticHelpers.cs +++ b/KSPCommunityFixes/Library/StaticHelpers.cs @@ -1,4 +1,10 @@ -namespace KSPCommunityFixes +using System; +using System.Reflection; +using HarmonyLib; +using KSP.UI.TooltipTypes; +using UnityEngine; + +namespace KSPCommunityFixes { static class StaticHelpers { @@ -38,5 +44,39 @@ public static string HumanReadableBytes(long bytes) // Return formatted number with suffix return readable.ToString("0.### ") + suffix; } + + private static Tooltip_Text _tooltipTextPrefab; + + public static TooltipController_Text AddUITooltip(GameObject go) + { + TooltipController_Text tooltip = go.AddComponent(); + tooltip.prefab = _tooltipTextPrefab ??= AssetBase.GetPrefab("Tooltip_Text"); + return tooltip; + } + + public static bool EditPartModuleKSPFieldAttributes(Type partModuleType, string fieldName, Action editAction) + { + BaseFieldList.ReflectedData reflectedData; + try + { + MethodInfo BaseFieldList_GetReflectedAttributes = AccessTools.Method(typeof(BaseFieldList), "GetReflectedAttributes"); + reflectedData = (BaseFieldList.ReflectedData)BaseFieldList_GetReflectedAttributes.Invoke(null, new object[] { partModuleType, false }); + } + catch + { + return false; + } + + for (int i = 0; i < reflectedData.fields.Count; i++) + { + if (reflectedData.fields[i].Name == fieldName) + { + editAction.Invoke(reflectedData.fieldAttributes[i]); + return true; + } + } + + return false; + } } } diff --git a/KSPCommunityFixes/Performance/CommNetThrottling.cs b/KSPCommunityFixes/Performance/CommNetThrottling.cs index 8733657..fdf727c 100644 --- a/KSPCommunityFixes/Performance/CommNetThrottling.cs +++ b/KSPCommunityFixes/Performance/CommNetThrottling.cs @@ -5,44 +5,44 @@ namespace KSPCommunityFixes.Performance { class CommNetThrottling : BasePatch - { - private static double packedInterval = 0.5; - private static double unpackedInterval = 5.0; + { + private static double packedInterval = 0.5; + private static double unpackedInterval = 5.0; - protected override void ApplyPatches(List patches) - { - ConfigNode settingsNode = KSPCommunityFixes.SettingsNode.GetNode("COMMNET_THROTTLING_SETTINGS"); + protected override void ApplyPatches(List patches) + { + ConfigNode settingsNode = KSPCommunityFixes.SettingsNode.GetNode("COMMNET_THROTTLING_SETTINGS"); - if (settingsNode != null) - { - settingsNode.TryGetValue("packedInterval", ref packedInterval); - settingsNode.TryGetValue("unpackedInterval", ref unpackedInterval); - } + if (settingsNode != null) + { + settingsNode.TryGetValue("packedInterval", ref packedInterval); + settingsNode.TryGetValue("unpackedInterval", ref unpackedInterval); + } - patches.Add(new PatchInfo( - PatchMethodType.Prefix, - AccessTools.Method(typeof(CommNet.CommNetNetwork), nameof(CommNet.CommNetNetwork.Update)), - this)); - } + patches.Add(new PatchInfo( + PatchMethodType.Prefix, + AccessTools.Method(typeof(CommNet.CommNetNetwork), nameof(CommNet.CommNetNetwork.Update)), + this)); + } - static bool CommNetNetwork_Update_Prefix(CommNet.CommNetNetwork __instance) - { - if (!__instance.queueRebuild && !__instance.commNet.IsDirty) - { - double timeSinceLastUpdate = Time.timeSinceLevelLoad - __instance.prevUpdate; + static bool CommNetNetwork_Update_Prefix(CommNet.CommNetNetwork __instance) + { + if (!__instance.queueRebuild && !__instance.commNet.IsDirty) + { + double timeSinceLastUpdate = Time.timeSinceLevelLoad - __instance.prevUpdate; - if (FlightGlobals.ActiveVessel != null) - { - double interval = FlightGlobals.ActiveVessel.packed ? packedInterval : unpackedInterval; - if (timeSinceLastUpdate < interval) - { - __instance.graphDirty = true; - return false; - } - } - } + if (FlightGlobals.ActiveVessel != null) + { + double interval = FlightGlobals.ActiveVessel.packed ? packedInterval : unpackedInterval; + if (timeSinceLastUpdate < interval) + { + __instance.graphDirty = true; + return false; + } + } + } - return true; - } - } + return true; + } + } } diff --git a/KSPCommunityFixes/Performance/LowerMinPhysicsDTPerFrame.cs b/KSPCommunityFixes/Performance/LowerMinPhysicsDTPerFrame.cs new file mode 100644 index 0000000..01ae6fc --- /dev/null +++ b/KSPCommunityFixes/Performance/LowerMinPhysicsDTPerFrame.cs @@ -0,0 +1,110 @@ +// Implement min value of 0.02 instead of 0.03 for the "Max Physics Delta-Time Per Frame" main menu setting. +// See https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/175 + +using HarmonyLib; +using KSP.UI.Screens.Settings; +using KSP.UI.TooltipTypes; +using System; +using System.Collections.Generic; +using UnityEngine; +using static KSP.UI.Screens.Settings.ReflectedSettingsWindow; + +namespace KSPCommunityFixes.Performance +{ + internal class LowerMinPhysicsDTPerFrame : BasePatch + { + public static string LOC_SettingsTooltip = + "How the game handle lag in CPU bound situations.\n" + + "Mostly relevant with large part count vessels.\n" + + "\n" + + "Lower value :\n" + + "Higher and smoother FPS, but game time might advance slower than real time.\n" + + "\n" + + "Higher value :\n" + + "Lower and choppier FPS, but game time will advance closer to real time."; + + protected override Version VersionMin => new Version(1, 12, 3); + + protected override void ApplyPatches(List patches) + { + patches.Add( + new PatchInfo(PatchMethodType.Prefix, + AccessTools.Method(typeof(SettingsScreen), nameof(SettingsScreen.Awake)), + this)); + + patches.Add( + new PatchInfo(PatchMethodType.Prefix, + AccessTools.Method(typeof(ReflectedSettingsWindow), nameof(SetupReflectionValues)), + this)); + } + + static void SettingsScreen_Awake_Prefix(SettingsScreen __instance) + { + try + { + ReflectedSettingsWindow settingsWindow = (ReflectedSettingsWindow)__instance.setupPrefab.setup.tabs[0].window.prefab; + + ControlWrapper controlWrapper = settingsWindow.tabs[1].tabs[1].controls[9]; + + if (controlWrapper.settingName != "PHYSICS_FRAME_DT_LIMIT") + throw new Exception("PHYSICS_FRAME_DT_LIMIT control not found"); + + ValueWrapper controlValue = controlWrapper.values[0]; + if (controlValue.name != "minValue") + throw new Exception("minValue control value not found"); + + controlValue.value = "0.02"; + + controlWrapper.values.Add(new ValueWrapper {name = "tooltipText", value = LOC_SettingsTooltip }); + } + catch (Exception e) + { + Debug.LogWarning($"[KSPCF:LowerMinPhysicsDTPerFrame] Control not found at predetermined index, falling back to manual search ({e.Message})..."); + foreach (SettingsSetup.MainTab tab in __instance.setupPrefab.setup.tabs) + { + if (tab.window.prefab is ReflectedSettingsWindow settingsWindow) + { + foreach (TabWrapper tabWrapper in settingsWindow.tabs) + { + foreach (SubTabWrapper subTabWrapper in tabWrapper.tabs) + { + foreach (ControlWrapper controlWrapper in subTabWrapper.controls) + { + if (controlWrapper.settingName == "PHYSICS_FRAME_DT_LIMIT") + { + foreach (ValueWrapper controlWrapperValue in controlWrapper.values) + { + if (controlWrapperValue.name == "minValue") + { + controlWrapperValue.value = "0.02"; + return; + } + } + + controlWrapper.values.Add(new ValueWrapper { name = "tooltipText", value = LOC_SettingsTooltip }); + } + } + } + } + } + } + } + } + + static void ReflectedSettingsWindow_SetupReflectionValues_Prefix(object instance, List values) + { + for (int i = values.Count; i-- > 0;) + { + ValueWrapper valueWrapper = values[i]; + if (valueWrapper.name == "tooltipText") + { + TooltipController_Text tooltip = StaticHelpers.AddUITooltip(((Component)instance).gameObject); + tooltip.continuousUpdate = false; + tooltip.textString = valueWrapper.value; + values.RemoveAt(i); + break; + } + } + } + } +} \ No newline at end of file diff --git a/KSPCommunityFixes/Performance/ProgressTrackingSpeedBoost.cs b/KSPCommunityFixes/Performance/ProgressTrackingSpeedBoost.cs index f77c5fa..00b1ef7 100644 --- a/KSPCommunityFixes/Performance/ProgressTrackingSpeedBoost.cs +++ b/KSPCommunityFixes/Performance/ProgressTrackingSpeedBoost.cs @@ -4,35 +4,35 @@ namespace KSPCommunityFixes.Performance { - public class ProgressTrackingSpeedBoost : BasePatch - { - protected override void ApplyPatches(List patches) - { - patches.Add(new PatchInfo( - PatchMethodType.Prefix, - AccessTools.Method(typeof(ProgressTracking), nameof(ProgressTracking.Update)), - this)); + public class ProgressTrackingSpeedBoost : BasePatch + { + protected override void ApplyPatches(List patches) + { + patches.Add(new PatchInfo( + PatchMethodType.Prefix, + AccessTools.Method(typeof(ProgressTracking), nameof(ProgressTracking.Update)), + this)); - patches.Add(new PatchInfo( - PatchMethodType.Postfix, - AccessTools.DeclaredConstructor(typeof(KSPAchievements.CelestialBodySubtree), new Type[] { typeof(CelestialBody) }), - this, - nameof(CelestialBodySubtree_Constructor_Postfix))); - } + patches.Add(new PatchInfo( + PatchMethodType.Postfix, + AccessTools.DeclaredConstructor(typeof(KSPAchievements.CelestialBodySubtree), new Type[] { typeof(CelestialBody) }), + this, + nameof(CelestialBodySubtree_Constructor_Postfix))); + } - static bool ProgressTracking_Update_Prefix(ProgressTracking __instance) - { - if (!HighLogic.LoadedSceneIsEditor && FlightGlobals.ActiveVessel != null) - { - __instance.achievementTree.IterateVessels(FlightGlobals.ActiveVessel); - } + static bool ProgressTracking_Update_Prefix(ProgressTracking __instance) + { + if (!HighLogic.LoadedSceneIsEditor && FlightGlobals.ActiveVessel != null) + { + __instance.achievementTree.IterateVessels(FlightGlobals.ActiveVessel); + } - return false; - } + return false; + } - static void CelestialBodySubtree_Constructor_Postfix(KSPAchievements.CelestialBodySubtree __instance) - { - __instance.OnIterateVessels = null; - } - } + static void CelestialBodySubtree_Constructor_Postfix(KSPAchievements.CelestialBodySubtree __instance) + { + __instance.OnIterateVessels = null; + } + } } diff --git a/KSPCommunityFixes/Properties/AssemblyInfo.cs b/KSPCommunityFixes/Properties/AssemblyInfo.cs index 84f99a7..99fe3fb 100644 --- a/KSPCommunityFixes/Properties/AssemblyInfo.cs +++ b/KSPCommunityFixes/Properties/AssemblyInfo.cs @@ -30,7 +30,7 @@ // Revision // [assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.33.0.0")] +[assembly: AssemblyFileVersion("1.34.0.0")] -[assembly: KSPAssembly("KSPCommunityFixes", 1, 33, 0)] +[assembly: KSPAssembly("KSPCommunityFixes", 1, 34, 0)] [assembly: KSPAssemblyDependency("MultipleModulePartAPI", 1, 0, 0)] diff --git a/KSPCommunityFixes/QoL/BetterEditorUndoRedo.cs b/KSPCommunityFixes/QoL/BetterEditorUndoRedo.cs new file mode 100644 index 0000000..36cf9ca --- /dev/null +++ b/KSPCommunityFixes/QoL/BetterEditorUndoRedo.cs @@ -0,0 +1,437 @@ +// see https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/172 + +// In stock, undo state is captured after part events are complete, and undoing will restore that state captured before that. +// This make the user experience quite poor as undoing will loose all PAW tweaks made in between attach/detach actions. +// This patch invert the undo/redo state capture logic, by moving state capture before attaching / detaching instead of after, +// and by capturing the current state when undoing is requested, in case a redo is requested next (see the RestoreState() patch) +// Unfortunately, the crew assignement (VesselCrewManifest) is updated based on the last serialized undo state, so doing this notably +// result in the crew assignement window being out of sync with the ship current state. To fix this, after attaching or detaching, +// we call a reimplementation of the VesselCrewManifest update using the live ship state instead of the serialized state. + +// Still, due to many mostly unrelated code paths being triggered from the undo/redo code, this patch introduce a bunch of unavoidable +// behavior changes. We fix the most obvious, ie GameEvents.onEditorShipModified.Fire() being called before attach/detach, but +// this still introduce other more subtle changes, which might cause weird side effects in plugins overly relying on the notably +// messy editor code paths. + +// enable additional debug logging +// #define BEUR_DEBUG + +// use replacement callbacks with modified stock code instead of stock code transpilers +// #define BEUR_REPLACE_CALLBACKS + +using System; +using HarmonyLib; +using KSP.UI; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; +using UnityEngine; + +namespace KSPCommunityFixes.QoL +{ + internal class BetterEditorUndoRedo : BasePatch + { + private static bool editorPatched = false; + + private static MethodInfo m_EditorLogic_SetBackup; + private static MethodInfo m_EditorLogicSetBackupNoShipModifiedEvent; + private static MethodInfo m_EditorShipModifiedGameEvent; + private static MethodInfo m_EditorLogic_attachPart; + private static MethodInfo m_EditorLogic_RefreshCrewAssignment; + private static MethodInfo m_RefreshCrewAssignmentFromLiveState; + private static MethodInfo m_ShipConstruct_Contains; + + protected override Version VersionMin => new Version(1, 12, 3); // too many changes in previous versions, too lazy to check + + protected override void ApplyPatches(List patches) + { + m_EditorLogic_SetBackup = AccessTools.Method(typeof(EditorLogic), nameof(EditorLogic.SetBackup)); + m_EditorLogicSetBackupNoShipModifiedEvent = AccessTools.Method(typeof(BetterEditorUndoRedo), nameof(EditorLogicSetBackupNoShipModifiedEvent)); + m_EditorShipModifiedGameEvent = AccessTools.Method(typeof(BetterEditorUndoRedo), nameof(EditorShipModifiedGameEvent)); + m_EditorLogic_attachPart = AccessTools.Method(typeof(EditorLogic), nameof(EditorLogic.attachPart)); + m_EditorLogic_RefreshCrewAssignment = AccessTools.Method(typeof(EditorLogic), nameof(EditorLogic.RefreshCrewAssignment)); + m_RefreshCrewAssignmentFromLiveState = AccessTools.Method(typeof(BetterEditorUndoRedo), nameof(RefreshCrewAssignmentFromLiveState)); + m_ShipConstruct_Contains = AccessTools.Method(typeof(ShipConstruct), nameof(ShipConstruct.Contains), new[] { typeof(Part) }); + + patches.Add(new PatchInfo( + PatchMethodType.Prefix, + AccessTools.Method(typeof(EditorLogic), nameof(EditorLogic.RestoreState)), + this)); + + patches.Add(new PatchInfo( + PatchMethodType.Postfix, + AccessTools.Method(typeof(EditorLogic), nameof(EditorLogic.SetupFSM)), + this)); + +#if BEUR_DEBUG + patches.Add(new PatchInfo( + PatchMethodType.Postfix, + AccessTools.Method(typeof(EditorLogic), nameof(EditorLogic.SetBackup)), + this)); + + patches.Add(new PatchInfo( + PatchMethodType.Postfix, + AccessTools.Method(typeof(EditorLogic), nameof(EditorLogic.RestoreState)), + this)); +#endif + } + +#if BEUR_DEBUG + static void EditorLogic_SetBackup_Postfix(EditorLogic __instance) + { + if (__instance.ship.parts.Count == 0) + return; + + Debug.Log($"[UNDO/REDO] backup created, undoLevel={__instance.undoLevel}, states={ShipConstruction.backups.Count}"); + } +#endif + +#if BEUR_DEBUG + static void EditorLogic_RestoreState_Postfix(EditorLogic __instance, int offset) + { + Debug.Log($"[UNDO/REDO] state {offset} restored, undoLevel={__instance.undoLevel}, states={ShipConstruction.backups.Count}"); + } +#endif + + static void EditorLogic_RestoreState_Prefix(EditorLogic __instance, int offset) + { + if (__instance.ship.parts.Count == 0 || offset >= 0 || __instance.undoLevel < ShipConstruction.backups.Count) + return; + + __instance.SetBackup(); + +#if BEUR_DEBUG + Debug.Log($"[UNDO/REDO] created backup for redo"); +#endif + } + + static void EditorLogic_SetupFSM_Postfix(EditorLogic __instance) + { + +#if BEUR_REPLACE_CALLBACKS + __instance.on_partPicked.OnEvent = OnPartPickedReplacement; + __instance.on_partAttached.OnEvent = OnPartAttachedReplacement; +#else + if (editorPatched) + return; + + editorPatched = true; + + MethodInfo m_onPartPicked = __instance.on_partPicked.OnEvent.Method; // b__189_21() + MethodInfo m_onPartAttached = __instance.on_partAttached.OnEvent.Method; // b__189_29() + + KSPCommunityFixes.Harmony.Patch(m_onPartPicked, null, null, new HarmonyMethod(AccessTools.Method(typeof(BetterEditorUndoRedo), nameof(OnPartPickedTranspiler)))); + KSPCommunityFixes.Harmony.Patch(m_onPartAttached, null, null, new HarmonyMethod(AccessTools.Method(typeof(BetterEditorUndoRedo), nameof(OnPartAttachedTranspiler)))); +#endif + } + + /// + /// Reimplementation of the EditorLogic.RefreshCrewAssignment() method using the live ship state + /// instead of the last serialized ship state found at ShipConstruction.ShipManifest + /// As a bonus, this is significantly faster... + /// + static void RefreshCrewAssignmentFromLiveState() + { + if (CrewAssignmentDialog.Instance == null) + return; + + VesselCrewManifest oldVesselCrewManifest = ShipConstruction.ShipManifest; + VesselCrewManifest newVesselCrewManifest = new VesselCrewManifest(); + + List shipParts = EditorLogic.fetch.ship.parts; + int shipPartsCount = shipParts.Count; + for (int i = 0; i < shipPartsCount; i++) + { + Part part = shipParts[i]; + + if (part.partInfo == null) + continue; + + PartCrewManifest partCrewManifest = new PartCrewManifest(newVesselCrewManifest); + partCrewManifest.partInfo = part.partInfo; + partCrewManifest.partID = part.craftID; + + int crewCapacity = partCrewManifest.partInfo.partPrefab.CrewCapacity; + partCrewManifest.partCrew = new string[crewCapacity]; + for (int j = 0; j < crewCapacity; j++) + partCrewManifest.partCrew[j] = string.Empty; + + newVesselCrewManifest.SetPartManifest(partCrewManifest.PartID, partCrewManifest); + } + + List allParts = Part.allParts; + HashSet allPartIdsHashSet = null; + int count = oldVesselCrewManifest.partManifests.Count; + for (int i = 0; i < count; i++) + { + PartCrewManifest oldPartCrewManifest = oldVesselCrewManifest.partManifests[i]; + + if (oldPartCrewManifest.partCrew.Length == 0) + continue; + + if (allPartIdsHashSet == null) + { + allPartIdsHashSet = new HashSet(allParts.Count); + for (int j = allParts.Count; j-- > 0;) + allPartIdsHashSet.Add(allParts[j].craftID); + } + + if (allPartIdsHashSet.Contains(oldPartCrewManifest.partID)) + newVesselCrewManifest.UpdatePartManifest(oldPartCrewManifest.partID, oldPartCrewManifest); + } + + ShipConstruction.ShipManifest = newVesselCrewManifest; + CrewAssignmentDialog.Instance.RefreshCrewLists(newVesselCrewManifest, setAsDefault: false, updateUI: false); + GameEvents.onEditorShipCrewModified.Fire(newVesselCrewManifest); + } + + static void EditorLogicSetBackupNoShipModifiedEvent() + { + EditorLogic el = EditorLogic.fetch; + + if (el.ship.parts.Count == 0) + return; + + if (el.undoLevel < ShipConstruction.backups.Count) + { + Debug.Log($"Clearing undo states from #{el.undoLevel} forward ({ShipConstruction.backups.Count - el.undoLevel} entries)"); + ShipConstruction.backups.RemoveRange(el.undoLevel, ShipConstruction.backups.Count - el.undoLevel); + } + + el.ship.shipName = el.shipNameField.text; + el.ship.shipDescription = el.shipDescriptionField.text; + el.ship.missionFlag = EditorLogic.FlagURL; + + if (ShipConstruction.backups.Count >= el.undoLimit) + ShipConstruction.ShiftAndCreateBackup(el.ship); + else + ShipConstruction.CreateBackup(el.ship); + + el.undoLevel = ShipConstruction.backups.Count; + GameEvents.onEditorSetBackup.Fire(el.ship); + } + + static void EditorShipModifiedGameEvent(EditorLogic editorLogic) + { + GameEvents.onEditorShipModified.Fire(editorLogic.ship); + } + +#if BEUR_DEBUG + static void OnAttachedMessage() => Debug.Log("[UNDO/REDO] State captured before attaching"); + static void OnPickedMessage() => Debug.Log("[UNDO/REDO] State captured before detaching"); +#endif + + static IEnumerable OnPartPickedTranspiler(IEnumerable instructions, ILGenerator ilGenerator) + { + foreach (CodeInstruction il in instructions) + { + if (il.opcode == OpCodes.Callvirt && ReferenceEquals(il.operand, m_ShipConstruct_Contains)) + { + yield return il; + Label label = ilGenerator.DefineLabel(); + yield return new CodeInstruction(OpCodes.Dup); + yield return new CodeInstruction(OpCodes.Brfalse_S, label); + yield return new CodeInstruction(OpCodes.Call, m_EditorLogicSetBackupNoShipModifiedEvent); +#if BEUR_DEBUG + yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(BetterEditorUndoRedo), nameof(OnPickedMessage))); +#endif + CodeInstruction next = new CodeInstruction(OpCodes.Nop); + next.labels.Add(label); + yield return next; + continue; + } + + if (il.opcode == OpCodes.Call && ReferenceEquals(il.operand, m_EditorLogic_RefreshCrewAssignment)) + { + yield return new CodeInstruction(OpCodes.Pop); + yield return new CodeInstruction(OpCodes.Pop); + yield return new CodeInstruction(OpCodes.Pop); + yield return new CodeInstruction(OpCodes.Call, m_RefreshCrewAssignmentFromLiveState); + continue; + } + + if (il.opcode == OpCodes.Call && ReferenceEquals(il.operand, m_EditorLogic_SetBackup)) + { + il.operand = m_EditorShipModifiedGameEvent; + } + + yield return il; + } + } + + static IEnumerable OnPartAttachedTranspiler(IEnumerable instructions) + { + List code = new List(instructions); + + for (int i = 0; i < code.Count; i++) + { + CodeInstruction il = code[i]; + if (il.opcode == OpCodes.Call && ReferenceEquals(il.operand, m_EditorLogic_attachPart)) + { + for (int j = i - 1; j-- > 0;) + { + if (code[j].opcode == OpCodes.Ldarg_0 && code[j + 1].opcode == OpCodes.Ldarg_0) + { + CodeInstruction callStart = code[j]; + CodeInstruction newCallStart = new CodeInstruction(OpCodes.Call, m_EditorLogicSetBackupNoShipModifiedEvent); + int adds = 0; + code.Insert(j + adds++, newCallStart); +#if BEUR_DEBUG + code.Insert(j + adds++, new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(BetterEditorUndoRedo), nameof(OnAttachedMessage)))); +#endif + i += adds; + + if (callStart.labels.Count > 0) + { + newCallStart.labels.AddRange(callStart.labels); + callStart.labels.Clear(); + } + break; + } + } + } + + if (il.opcode == OpCodes.Call && ReferenceEquals(il.operand, m_EditorLogic_SetBackup)) + { + il.operand = m_EditorShipModifiedGameEvent; + } + + if (il.opcode == OpCodes.Call && ReferenceEquals(il.operand, m_EditorLogic_RefreshCrewAssignment)) + { + code.Insert(i, new CodeInstruction(OpCodes.Pop)); + code.Insert(i, new CodeInstruction(OpCodes.Pop)); + code.Insert(i, new CodeInstruction(OpCodes.Pop)); + il.operand = m_RefreshCrewAssignmentFromLiveState; + i += 3; + continue; + } + } + + return code; + } + +#if BEUR_REPLACE_CALLBACKS + static void OnPartPickedReplacement() + { + EditorLogic el = EditorLogic.fetch; + + if (el.selectedPart != el.selectedPart.localRoot) + { + bool pickedPartIsOnShip = el.ship.Contains(el.selectedPart); + + if (pickedPartIsOnShip) // added + { + EditorLogicSetBackupNoShipModifiedEvent(); // added +#if BEUR_DEBUG + OnPickedMessage(); // added +#endif + } + + el.detachPart(el.selectedPart); + el.deleteSymmetryParts(); + + if (pickedPartIsOnShip) + { + GameEvents.onEditorPartPicked.Fire(el.selectedPart); + //el.SetBackup(); // removed + EditorShipModifiedGameEvent(el); // added + if (el.selectedPart.CrewCapacity > 0) + { + //el.RefreshCrewAssignment(ShipConstruction.ShipConfig, el.GetPartExistsFilter()); // removed + RefreshCrewAssignmentFromLiveState(); // added + } + GameEvents.onEditorPartEvent.Fire(ConstructionEventType.PartDetached, el.selectedPart); + return; + } + } + else + { + el.SetBackup(); + } + if (el.selectedPart.frozen) + { + el.selectedPart.unfreeze(); + } + el.isCurrentPartFlag = el.selectedPart != null && el.selectedPart.GetComponent() != null; + if (el.selectedPart != null && el.selectedPart.FindModuleImplementing() != null && UIPartActionControllerInventory.Instance != null) + { + UIPartActionControllerInventory.Instance.CurrentCargoPart = el.selectedPart; + } + GameEvents.onEditorPartEvent.Fire(ConstructionEventType.PartPicked, el.selectedPart); + } + + static void OnPartAttachedReplacement() + { + EditorLogic el = EditorLogic.fetch; + + el.isCurrentPartFlag = false; + if (el.selectedPart.symmetryCounterparts.Count > 0) + { + el.RestoreSymmetryState(); + bool flag = true; + int num3 = el.cPartAttachments.Length; + while (num3-- > 0) + { + if (!el.cPartAttachments[num3].possible) + { + flag = false; + break; + } + } + if (!flag) + { + el.audioSource.PlayOneShot(el.cannotPlaceClip); + el.on_partAttached.GoToStateOnEvent = el.st_place; + if (UIPartActionControllerInventory.Instance != null) + { + UIPartActionControllerInventory.Instance.DestroyHeldPartAsIcon(); + } + return; + } + EditorLogicSetBackupNoShipModifiedEvent(); // added +#if BEUR_DEBUG + OnAttachedMessage(); // added +#endif + el.attachPart(el.selectedPart, el.attachment); + el.attachSymParts(el.cPartAttachments); + } + else + { + EditorLogicSetBackupNoShipModifiedEvent(); // added +#if BEUR_DEBUG + OnAttachedMessage(); // added +#endif + el.attachPart(el.selectedPart, el.attachment); + if (el.symmetryModeBeforeNodeAttachment >= 0) + { + el.RestoreSymmetryModeBeforeNodeAttachment(); + } + } + + //el.SetBackup(); // removed + EditorShipModifiedGameEvent(el); // added + + if (el.selectedPart.CrewCapacity > 0) + { + //el.RefreshCrewAssignment(ShipConstruction.ShipConfig, el.GetPartExistsFilter()); // removed + RefreshCrewAssignmentFromLiveState(); // added + } + + ModuleCargoPart moduleCargoPart = el.selectedPart.FindModuleImplementing(); + if (UIPartActionControllerInventory.Instance != null) + { + if (moduleCargoPart != null && !moduleCargoPart.IsDeployedSciencePart()) + { + UIPartActionControllerInventory.Instance.CurrentCargoPart = null; + UIPartActionControllerInventory.Instance.CurrentInventory = null; + } + UIPartActionControllerInventory.Instance.DestroyHeldPartAsIcon(); + } + el.audioSource.PlayOneShot(el.attachClip); + el.on_partAttached.GoToStateOnEvent = el.st_idle; + el.CenterDragPlane(el.selectedPart.transform.position + el.selPartGrabOffset); + GameEvents.onEditorPartEvent.Fire(ConstructionEventType.PartAttached, el.selectedPart); + } +#endif + } +} diff --git a/README.md b/README.md index 1d54f29..0979ffc 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,11 @@ User options are available from the "ESC" in-game settings menu :
Fixes a bug where parts in tech nodes that have 0 science cost would become unusable. +- [**ModulePartVariantsNodePersistence**](https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/179) [KSP 1.12.3 - 1.12.5]
Fixes an issue with ModulePartVariants where attachnodes would use their default state when resuming flight on a vessel from a saved game. This would lead to different behavior in part joints and flexibility between initial launch and loading a save. #### Quality of Life tweaks @@ -99,6 +104,7 @@ User options are available from the "ESC" in-game settings menu :
Add a button for hiding/showing the stock toolbar. Also allow accessing the toolbar while in the space center facilities windows (mission control, admin building, R&D...). - **ResourceLockActions** [KSP 1.8.0 - 1.12.5]
Add part actions for locking/unlocking resources flow state. +- [**BetterEditorUndoRedo**](https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/172) [KSP 1.12.3 - 1.12.5]
Invert the editor undo state capturing logic so part tweaks aren't lost when undoing. #### Performance tweaks @@ -121,6 +127,8 @@ User options are available from the "ESC" in-game settings menu :
This API allow other plugins to implement PartModules that can exist in multiple occurrence in a single part and won't suffer "module indexing mismatch" persistent data losses following part configuration changes. [See documentation on the wiki](https://github.com/KSPModdingLibs/KSPCommunityFixes/wiki/MultipleModuleInPartAPI). @@ -180,6 +188,17 @@ If doing so in the `Debug` configuration and if your KSP install is modified to ### Changelog +##### 1.34.0 +- New KSP QoL/performance patch : [**LowerMinPhysicsDTPerFrame**](https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/175) : Allow a min value of 0.02 instead of 0.03 for the "Max Physics Delta-Time Per Frame" main menu setting. This allows for higher and smoother framerate at the expense of the game lagging behind real time. This was already possible by manually editing the `settings.cfg` file, but changes would revert when going into the settings screen. +- New KSP QoL patch : [**BetterEditorUndoRedo**](https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/172) : Invert the editor undo state capturing logic so part tweaks aren't lost when undoing. +- New KSP bugfix: [**InventoryPartMass**](https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/182) : Fixes bugs where parts stored in inventories would not have the correct mass or volume when their resource levels were modified or variants changed. +- New KSP bugfix: [**EVAConstructionMass**](https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/185) : Fixes a bug where picking up a part in EVA construction would set its mass to the wrong value when mass modifiers are involved (e.g. part variants). +- New KSP bugfix: [**RespawnDeadKerbals**](https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/104) : When respawning is enabled, starts the respawn timer for any dead kerbals (changing their state to "missing") when loading a save. This addresses stock bugs where kerbals could be set to dead even when respawning is enabled. +- New KSP bugfix: [**ZeroCostTechNode**](https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/180) : Fixes a bug where parts in tech nodes that have 0 science cost would become unusable. +- New KSP bugfix: [**ModulePartVariantsNodePersistence**](https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/179) : Fixes an issue with ModulePartVariants where attachnodes would use their default state when resuming flight on a vessel from a saved game. This would lead to different behavior in part joints and flexibility between initial launch and loading a save. +- Changed patch behavior: PAWGroupMemory now tracks group state globally instead of per-window. +- Added zh-cn localization for ManufacturerFixes.cfg (thanks @zhangyuesai) + ##### 1.33.0 - New KSP performance patch : [**CollisionManagerFastUpdate**](https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/174) : 3-4 times faster update of parts inter-collision state, significantly reduce stutter on docking, undocking, decoupling and joint failure events. From 7fc5cdf1180cbb7a6b61b9837fbb3f12bdb8ac9f Mon Sep 17 00:00:00 2001 From: JonnyOThan Date: Thu, 1 Feb 2024 14:26:56 -0500 Subject: [PATCH 4/4] Workaround for #191: disable BetterEditorUndoRedo when TweakScale/L is installed --- GameData/KSPCommunityFixes/KSPCommunityFixes.version | 2 +- .../KSPCommunityFixes/MMPatches/ModSupport/TweakScale.cfg | 6 ++++++ KSPCommunityFixes/Properties/AssemblyInfo.cs | 4 ++-- README.md | 5 ++++- 4 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 GameData/KSPCommunityFixes/MMPatches/ModSupport/TweakScale.cfg diff --git a/GameData/KSPCommunityFixes/KSPCommunityFixes.version b/GameData/KSPCommunityFixes/KSPCommunityFixes.version index 84587b4..f956461 100644 --- a/GameData/KSPCommunityFixes/KSPCommunityFixes.version +++ b/GameData/KSPCommunityFixes/KSPCommunityFixes.version @@ -2,7 +2,7 @@ "NAME": "KSPCommunityFixes", "URL": "https://raw.githubusercontent.com/KSPModdingLibs/KSPCommunityFixes/master/GameData/KSPCommunityFixes/KSPCommunityFixes.version", "DOWNLOAD": "https://github.com/KSPModdingLibs/KSPCommunityFixes/releases", - "VERSION": {"MAJOR": 1, "MINOR": 34, "PATCH": 0, "BUILD": 0}, + "VERSION": {"MAJOR": 1, "MINOR": 34, "PATCH": 1, "BUILD": 0}, "KSP_VERSION": {"MAJOR": 1, "MINOR": 12, "PATCH": 5}, "KSP_VERSION_MIN": {"MAJOR": 1, "MINOR": 8, "PATCH": 0}, "KSP_VERSION_MAX": {"MAJOR": 1, "MINOR": 12, "PATCH": 5} diff --git a/GameData/KSPCommunityFixes/MMPatches/ModSupport/TweakScale.cfg b/GameData/KSPCommunityFixes/MMPatches/ModSupport/TweakScale.cfg new file mode 100644 index 0000000..6e3bce6 --- /dev/null +++ b/GameData/KSPCommunityFixes/MMPatches/ModSupport/TweakScale.cfg @@ -0,0 +1,6 @@ +// https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/191 + +@KSP_COMMUNITY_FIXES:NEEDS[KSPE.LIGHT.TWEAKSCALE] +{ + @BetterEditorUndoRedo = false +} \ No newline at end of file diff --git a/KSPCommunityFixes/Properties/AssemblyInfo.cs b/KSPCommunityFixes/Properties/AssemblyInfo.cs index 99fe3fb..fb18da3 100644 --- a/KSPCommunityFixes/Properties/AssemblyInfo.cs +++ b/KSPCommunityFixes/Properties/AssemblyInfo.cs @@ -30,7 +30,7 @@ // Revision // [assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.34.0.0")] +[assembly: AssemblyFileVersion("1.34.1.0")] -[assembly: KSPAssembly("KSPCommunityFixes", 1, 34, 0)] +[assembly: KSPAssembly("KSPCommunityFixes", 1, 34, 1)] [assembly: KSPAssemblyDependency("MultipleModulePartAPI", 1, 0, 0)] diff --git a/README.md b/README.md index 0979ffc..595aa4a 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ User options are available from the "ESC" in-game settings menu :
Add a button for hiding/showing the stock toolbar. Also allow accessing the toolbar while in the space center facilities windows (mission control, admin building, R&D...). - **ResourceLockActions** [KSP 1.8.0 - 1.12.5]
Add part actions for locking/unlocking resources flow state. -- [**BetterEditorUndoRedo**](https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/172) [KSP 1.12.3 - 1.12.5]
Invert the editor undo state capturing logic so part tweaks aren't lost when undoing. +- [**BetterEditorUndoRedo**](https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/172) [KSP 1.12.3 - 1.12.5]
Invert the editor undo state capturing logic so part tweaks aren't lost when undoing. NOTE: this patch is disabled when TweakScale/L is installed. #### Performance tweaks @@ -188,6 +188,9 @@ If doing so in the `Debug` configuration and if your KSP install is modified to ### Changelog +##### 1.34.1 +- Disable BetterEditorUndoRedo when TweakScale/L is installed due to introducing a bug with part attachments in the editor. + ##### 1.34.0 - New KSP QoL/performance patch : [**LowerMinPhysicsDTPerFrame**](https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/175) : Allow a min value of 0.02 instead of 0.03 for the "Max Physics Delta-Time Per Frame" main menu setting. This allows for higher and smoother framerate at the expense of the game lagging behind real time. This was already possible by manually editing the `settings.cfg` file, but changes would revert when going into the settings screen. - New KSP QoL patch : [**BetterEditorUndoRedo**](https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/172) : Invert the editor undo state capturing logic so part tweaks aren't lost when undoing.