From e1ba0f4f249767d2a96ca6343d09f986c181e424 Mon Sep 17 00:00:00 2001 From: gotmachine <24925209+gotmachine@users.noreply.github.com> Date: Sat, 6 Jan 2024 23:51:21 +0100 Subject: [PATCH 1/3] BetterEditorUndoRedo : Exploratory fix for undo/redo state, doesn't work because of VesselCrewManifest side effects See https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/172 --- GameData/KSPCommunityFixes/Settings.cfg | 2 + KSPCommunityFixes/KSPCommunityFixes.csproj | 1 + KSPCommunityFixes/QoL/BetterEditorUndoRedo.cs | 178 ++++++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 KSPCommunityFixes/QoL/BetterEditorUndoRedo.cs diff --git a/GameData/KSPCommunityFixes/Settings.cfg b/GameData/KSPCommunityFixes/Settings.cfg index a76f445..fe4f590 100644 --- a/GameData/KSPCommunityFixes/Settings.cfg +++ b/GameData/KSPCommunityFixes/Settings.cfg @@ -259,6 +259,8 @@ KSP_COMMUNITY_FIXES // Add part actions for locking/unlocking part resources flow state. ResourceLockActions = true + BetterEditorUndoRedo = true + // ########################## // Performance tweaks // ########################## diff --git a/KSPCommunityFixes/KSPCommunityFixes.csproj b/KSPCommunityFixes/KSPCommunityFixes.csproj index 2fd321c..2ec98e9 100644 --- a/KSPCommunityFixes/KSPCommunityFixes.csproj +++ b/KSPCommunityFixes/KSPCommunityFixes.csproj @@ -168,6 +168,7 @@ + diff --git a/KSPCommunityFixes/QoL/BetterEditorUndoRedo.cs b/KSPCommunityFixes/QoL/BetterEditorUndoRedo.cs new file mode 100644 index 0000000..d1abe77 --- /dev/null +++ b/KSPCommunityFixes/QoL/BetterEditorUndoRedo.cs @@ -0,0 +1,178 @@ +// backups are created from : +// - KerbalFSM +// - pod_select : root part dropped ? +// - podDeleted : root part deleted ? +// - partPicked : any attached part picked +// - partDropped : any part dropped without attaching ? +// - partAttached : any part attached +// - variant changed +// - action group edited +// - offset/rotate gizmo updated + +// see https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/172 +// In stock, undo state is captured after part events are complete, which is annoying as undoing will loose any tweaks made in between +// This patch invert the undo/redo state capture logic, by moving state capture before attaching / detaching instead of after +// 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, but this will likely have other weird side effects. +// My thanks to the spaghetti mess of the editor code... +// Not sure this is really fixable, this would likely require a complete rewrite of the VesselCrewManifest creation/update as well. +// Of course I could just double-save the ship and update the crew manifest separatly, but the whole point was trying to avoid the stutter +// induced by excessive ship state saving... + +using HarmonyLib; +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; + + protected override void ApplyPatches(List patches) + { + 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)); + + 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)); + } + + 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}"); + } + + static void EditorLogic_RestoreState_Postfix(EditorLogic __instance, int offset) + { + Debug.Log($"[UNDO/REDO] state {offset} restored, undoLevel={__instance.undoLevel}, states={ShipConstruction.backups.Count}"); + } + + 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(); + + Debug.Log($"[UNDO/REDO] created backup for redo"); + } + + static void EditorLogic_SetupFSM_Postfix(EditorLogic __instance) + { + 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)))); + } + + static IEnumerable OnPartPickedTranspiler(IEnumerable instructions, ILGenerator ilGenerator) + { + MethodInfo m_EditorLogic_SetBackup = AccessTools.Method(typeof(EditorLogic), nameof(EditorLogic.SetBackup)); + MethodInfo m_ShipConstruct_Contains = AccessTools.Method(typeof(ShipConstruct), nameof(ShipConstruct.Contains), new[] { typeof(Part) }); + + List code = new List(instructions); + + for (int i = 0; i < code.Count; i++) + { + CodeInstruction il = code[i]; + 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.Ldarg_0); + yield return new CodeInstruction(OpCodes.Call, m_EditorLogic_SetBackup); + yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(BetterEditorUndoRedo), nameof(OnPickedMessage))); + CodeInstruction next = new CodeInstruction(OpCodes.Nop); + next.labels.Add(label); + yield return next; + continue; + } + + if (il.opcode == OpCodes.Call && ReferenceEquals(il.operand, m_EditorLogic_SetBackup)) + { + il.opcode = OpCodes.Pop; + il.operand = null; + } + + yield return il; + } + } + + static IEnumerable OnPartAttachedTranspiler(IEnumerable instructions) + { + MethodInfo m_EditorLogic_SetBackup = AccessTools.Method(typeof(EditorLogic), nameof(EditorLogic.SetBackup)); + MethodInfo m_EditorLogic_attachPart = AccessTools.Method(typeof(EditorLogic), nameof(EditorLogic.attachPart)); + + 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.Ldarg_0); + int adds = 0; + code.Insert(j + adds++, newCallStart); + code.Insert(j + adds++, new CodeInstruction(OpCodes.Call, m_EditorLogic_SetBackup)); + code.Insert(j + adds++, new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(BetterEditorUndoRedo), nameof(OnAttachedMessage)))); + 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.opcode = OpCodes.Pop; + il.operand = null; + } + } + + return code; + } + + static void OnAttachedMessage() => Debug.Log("[UNDO/REDO] State captured before attaching"); + static void OnPickedMessage() => Debug.Log("[UNDO/REDO] State captured before detaching"); + } +} From a0261b1835f77cd7c0f5e3e27ba0c86b269d57e4 Mon Sep 17 00:00:00 2001 From: gotmachine <24925209+gotmachine@users.noreply.github.com> Date: Sun, 7 Jan 2024 18:38:23 +0100 Subject: [PATCH 2/3] BetterEditorUndoRedo : functional, obvious issues fixed, still likely to cause side effects. --- GameData/KSPCommunityFixes/Settings.cfg | 1 + KSPCommunityFixes/QoL/BetterEditorUndoRedo.cs | 351 +++++++++++++++--- README.md | 2 + 3 files changed, 308 insertions(+), 46 deletions(-) diff --git a/GameData/KSPCommunityFixes/Settings.cfg b/GameData/KSPCommunityFixes/Settings.cfg index fe4f590..f5c3c15 100644 --- a/GameData/KSPCommunityFixes/Settings.cfg +++ b/GameData/KSPCommunityFixes/Settings.cfg @@ -259,6 +259,7 @@ 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 // ########################## diff --git a/KSPCommunityFixes/QoL/BetterEditorUndoRedo.cs b/KSPCommunityFixes/QoL/BetterEditorUndoRedo.cs index d1abe77..36cf9ca 100644 --- a/KSPCommunityFixes/QoL/BetterEditorUndoRedo.cs +++ b/KSPCommunityFixes/QoL/BetterEditorUndoRedo.cs @@ -1,25 +1,27 @@ -// backups are created from : -// - KerbalFSM -// - pod_select : root part dropped ? -// - podDeleted : root part deleted ? -// - partPicked : any attached part picked -// - partDropped : any part dropped without attaching ? -// - partAttached : any part attached -// - variant changed -// - action group edited -// - offset/rotate gizmo updated - -// see https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/172 -// In stock, undo state is captured after part events are complete, which is annoying as undoing will loose any tweaks made in between -// This patch invert the undo/redo state capture logic, by moving state capture before attaching / detaching instead of after +// 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, but this will likely have other weird side effects. -// My thanks to the spaghetti mess of the editor code... -// Not sure this is really fixable, this would likely require a complete rewrite of the VesselCrewManifest creation/update as well. -// Of course I could just double-save the ship and update the crew manifest separatly, but the whole point was trying to avoid the stutter -// induced by excessive ship state saving... +// 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; @@ -31,29 +33,50 @@ 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.Postfix, - AccessTools.Method(typeof(EditorLogic), nameof(EditorLogic.SetBackup)), + PatchMethodType.Prefix, + AccessTools.Method(typeof(EditorLogic), nameof(EditorLogic.RestoreState)), this)); patches.Add(new PatchInfo( PatchMethodType.Postfix, - AccessTools.Method(typeof(EditorLogic), nameof(EditorLogic.RestoreState)), + AccessTools.Method(typeof(EditorLogic), nameof(EditorLogic.SetupFSM)), this)); +#if BEUR_DEBUG patches.Add(new PatchInfo( - PatchMethodType.Prefix, - AccessTools.Method(typeof(EditorLogic), nameof(EditorLogic.RestoreState)), + PatchMethodType.Postfix, + AccessTools.Method(typeof(EditorLogic), nameof(EditorLogic.SetBackup)), this)); patches.Add(new PatchInfo( PatchMethodType.Postfix, - AccessTools.Method(typeof(EditorLogic), nameof(EditorLogic.SetupFSM)), + 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) @@ -61,11 +84,14 @@ static void EditorLogic_SetBackup_Postfix(EditorLogic __instance) 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) { @@ -74,11 +100,18 @@ static void EditorLogic_RestoreState_Prefix(EditorLogic __instance, int offset) __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; @@ -89,37 +122,137 @@ static void EditorLogic_SetupFSM_Postfix(EditorLogic __instance) 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 } - static IEnumerable OnPartPickedTranspiler(IEnumerable instructions, ILGenerator ilGenerator) + /// + /// 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() { - MethodInfo m_EditorLogic_SetBackup = AccessTools.Method(typeof(EditorLogic), nameof(EditorLogic.SetBackup)); - MethodInfo m_ShipConstruct_Contains = AccessTools.Method(typeof(ShipConstruct), nameof(ShipConstruct.Contains), new[] { typeof(Part) }); + if (CrewAssignmentDialog.Instance == null) + return; - List code = new List(instructions); + VesselCrewManifest oldVesselCrewManifest = ShipConstruction.ShipManifest; + VesselCrewManifest newVesselCrewManifest = new VesselCrewManifest(); - for (int i = 0; i < code.Count; i++) + 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) { - CodeInstruction il = code[i]; 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.Ldarg_0); - yield return new CodeInstruction(OpCodes.Call, m_EditorLogic_SetBackup); + 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.opcode = OpCodes.Pop; - il.operand = null; + il.operand = m_EditorShipModifiedGameEvent; } yield return il; @@ -128,9 +261,6 @@ static IEnumerable OnPartPickedTranspiler(IEnumerable OnPartAttachedTranspiler(IEnumerable instructions) { - MethodInfo m_EditorLogic_SetBackup = AccessTools.Method(typeof(EditorLogic), nameof(EditorLogic.SetBackup)); - MethodInfo m_EditorLogic_attachPart = AccessTools.Method(typeof(EditorLogic), nameof(EditorLogic.attachPart)); - List code = new List(instructions); for (int i = 0; i < code.Count; i++) @@ -143,11 +273,12 @@ static IEnumerable OnPartAttachedTranspiler(IEnumerable 0) @@ -155,8 +286,6 @@ static IEnumerable OnPartAttachedTranspiler(IEnumerable OnPartAttachedTranspiler(IEnumerable Debug.Log("[UNDO/REDO] State captured before attaching"); - static void OnPickedMessage() => Debug.Log("[UNDO/REDO] State captured before detaching"); +#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 3751514..80270a2 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,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 @@ -184,6 +185,7 @@ If doing so in the `Debug` configuration and if your KSP install is modified to ##### 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 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. ##### 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 4a9a69928c8da2075c24af60d91e1e6fb3bf06f1 Mon Sep 17 00:00:00 2001 From: JonnyOThan Date: Mon, 29 Jan 2024 14:32:23 -0500 Subject: [PATCH 3/3] disable BetterEditorUndoRedo by default --- GameData/KSPCommunityFixes/Settings.cfg | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/GameData/KSPCommunityFixes/Settings.cfg b/GameData/KSPCommunityFixes/Settings.cfg index f5c3c15..8a4f07e 100644 --- a/GameData/KSPCommunityFixes/Settings.cfg +++ b/GameData/KSPCommunityFixes/Settings.cfg @@ -260,7 +260,9 @@ KSP_COMMUNITY_FIXES ResourceLockActions = true // Invert the editor undo state capturing logic so part tweaks aren't lost when undoing. - BetterEditorUndoRedo = true + // This is disabled by default due to the high likelihood of adverse interactions with mods. + // It may be enabled in the future after wider testing. + BetterEditorUndoRedo = false // ########################## // Performance tweaks