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