using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using MMTools; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Undo Build Action")] [assembly: AssemblyProduct("Undo Build Action")] [assembly: ComVisible(false)] [assembly: Guid("eac82e42-17ee-466e-a457-6b853a2699ca")] [assembly: AssemblyFileVersion("0.2.0.0")] [assembly: AssemblyVersion("0.2.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace UndoBuildAction { [BepInPlugin("com.cotl.betterbuildertools.undobuildaction", "Undo Build Action", "0.2.1")] public sealed class UndoBuildActionPlugin : BaseUnityPlugin { public const string PluginGuid = "com.cotl.betterbuildertools.undobuildaction"; public const string PluginName = "Undo Build Action"; public const string PluginVersion = "0.2.1"; private readonly List _undoStack = new List(); private Harmony _harmony; private KeyCode _undoKey; private Indicator _promptOwner; private GameObject _undoPromptObject; private GameObject _undoPromptTextObject; private MMControlPrompt _undoControlPrompt; private TMP_Text _undoPromptText; private GameObject _promptRowObject; private GameObject _undoPromptCellObject; private bool _promptCreationWarningLogged; private bool? _promptWasController; internal static ManualLogSource Log { get; private set; } internal static UndoBuildActionPlugin Instance { get; private set; } private void Awake() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; UndoBuildActionConfig.Bind(((BaseUnityPlugin)this).Config); _undoKey = ParseKey(UndoBuildActionConfig.UndoKey.Value, (KeyCode)122); _harmony = new Harmony("com.cotl.betterbuildertools.undobuildaction"); _harmony.PatchAll(typeof(UndoBuildActionPlugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Undo Build Action 0.2.1 loaded."); } private void OnDestroy() { DestroyUndoPrompt(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; Instance = null; } private void Update() { if (UndoBuildActionConfig.Enabled.Value && UndoPressed()) { UndoLatest(); } } private void LateUpdate() { UpdateUndoPrompt(); } private bool UndoPressed() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) if (Input.GetKeyDown(_undoKey)) { if (UndoBuildActionConfig.RequireControl.Value && !Input.GetKey((KeyCode)306)) { return Input.GetKey((KeyCode)305); } return true; } if (!UndoBuildActionConfig.EnableControllerUndo.Value) { return false; } try { PlayerFarming instance = PlayerFarming.Instance; return (Object)(object)instance != (Object)null && InputManager.General.InputIsController(instance) && InputManager.UI.GetPageNavigateLeftDown(instance); } catch { return false; } } internal void RecordLatest(StructureBrain brain) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) if (brain != null && brain.Data != null && IsInPlacementFlow() && (UndoBuildActionConfig.AllowCriticalStructures.Value || !IsCritical(brain.Data.Type))) { _undoStack.Add(brain.Data.ID); while (_undoStack.Count > UndoBuildActionConfig.UndoDepth.Value) { _undoStack.RemoveAt(0); } } } internal void ClearSession() { _undoStack.Clear(); HideUndoPrompt(); } private unsafe void UndoLatest() { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) while (_undoStack.Count > 0) { int index = _undoStack.Count - 1; int id = _undoStack[index]; _undoStack.RemoveAt(index); Structure val = FindStructureById(id); if ((Object)(object)val != (Object)null && val.Brain != null && val.Brain.Data != null) { TYPES type = val.Brain.Data.Type; if (RemoveStructureCleanly(val)) { Log.LogInfo((object)("Undid placement of " + ((object)(*(TYPES*)(&type))/*cast due to .constrained prefix*/).ToString() + ".")); } break; } } } private static Structure FindStructureById(int id) { Structure[] array = Structure.Structures.ToArray(); foreach (Structure val in array) { if ((Object)(object)val != (Object)null && val.Brain != null && val.Brain.Data != null && val.Brain.Data.ID == id) { return val; } } return null; } private static bool RemoveStructureCleanly(Structure structure) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)structure == (Object)null || structure.Brain == null || structure.Brain.Data == null) { return false; } TYPES type = structure.Brain.Data.Type; TYPES toBuildType = structure.Brain.Data.ToBuildType; Vector3 position = ((Component)structure).transform.position; try { if ((Object)(object)PlacementRegion.Instance != (Object)null) { PlacementRegion.Instance.DestroyBuilding(structure); } else { structure.RemoveStructure(); Object.Destroy((Object)(object)((Component)structure).gameObject); } RefundBuildSiteMaterials(type, toBuildType, position); return true; } catch (Exception ex) { Log.LogWarning((object)("Undo clean removal failed: " + ex.Message)); return false; } } private static void RefundBuildSiteMaterials(TYPES type, TYPES toBuildType, Vector3 position) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) if ((int)type != 31 && (int)type != 118) { return; } List cost = StructuresData.GetCost(((int)toBuildType != 0) ? toBuildType : type); if (cost == null) { return; } for (int i = 0; i < cost.Count; i++) { ItemCost val = cost[i]; if (val.CostValue > 0) { InventoryItem.Spawn(val.CostItem, val.CostValue, position, 4f, (Action)null); } } } private static bool IsInPlacementFlow() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Invalid comparison between Unknown and I4 if (!((Object)(object)PlacementObject.Instance != (Object)null)) { if ((Object)(object)PlacementRegion.Instance != (Object)null) { return (int)PlacementRegion.Instance.CurrentMode > 0; } return false; } return true; } private unsafe static bool IsCritical(TYPES type) { string text = ((object)(*(TYPES*)(&type))/*cast due to .constrained prefix*/).ToString(); if (!text.Contains("TEMPLE") && !text.Contains("SHRINE") && !text.Contains("ALTAR")) { return text.Contains("BUILDER"); } return true; } private void UpdateUndoPrompt() { if (!UndoBuildActionConfig.ShowUndoPrompt.Value || !UndoBuildActionConfig.Enabled.Value || _undoStack.Count == 0 || !IsInPlacementFlow() || (Object)(object)PlayerFarming.Instance == (Object)null || (Object)(object)PlayerFarming.Instance.indicator == (Object)null) { HideUndoPrompt(); return; } Indicator indicator = PlayerFarming.Instance.indicator; if (EnsureUndoPrompt(indicator)) { bool flag = false; try { flag = InputManager.General.InputIsController(PlayerFarming.Instance); } catch { } _undoControlPrompt.playerFarming = PlayerFarming.Instance; _undoControlPrompt.PrioritizeMouse = false; _undoPromptCellObject.SetActive(true); SizePromptRow(_promptRowObject); _promptRowObject.SetActive(true); _undoPromptObject.SetActive(true); _undoPromptTextObject.SetActive(true); if (_promptWasController != flag) { _promptWasController = flag; RefreshPromptGlyph(flag); } _undoPromptText.text = "Undo"; } } private bool EnsureUndoPrompt(Indicator indicator) { //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Expected O, but got Unknown //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_promptOwner == (Object)(object)indicator && (Object)(object)_undoPromptCellObject != (Object)null && (Object)(object)_undoControlPrompt != (Object)null && (Object)(object)_undoPromptText != (Object)null) { return true; } DestroyUndoPrompt(); if ((Object)(object)indicator.FourthControlPrompt == (Object)null || (Object)(object)((Transform)indicator.FourthControlPrompt).parent == (Object)null || (Object)(object)indicator.Fourthtext == (Object)null || (Object)(object)((TMP_Text)indicator.Fourthtext).transform.parent == (Object)null) { return false; } EnsurePromptRow(indicator); if ((Object)(object)_promptRowObject == (Object)null) { return false; } GameObject val = new GameObject("UndoBuildAction_UndoCell"); ((Transform)val.AddComponent()).SetParent(_promptRowObject.transform, false); LayoutElement obj = val.AddComponent(); obj.preferredWidth = 110f; obj.minWidth = 110f; HorizontalLayoutGroup obj2 = val.AddComponent(); ((HorizontalOrVerticalLayoutGroup)obj2).spacing = 6f; ((LayoutGroup)obj2).childAlignment = (TextAnchor)4; ((HorizontalOrVerticalLayoutGroup)obj2).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)obj2).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)obj2).childForceExpandWidth = false; ((HorizontalOrVerticalLayoutGroup)obj2).childForceExpandHeight = true; GameObject val2 = Object.Instantiate(((Component)indicator.FourthControlPrompt).gameObject, val.transform, false); ((Object)val2).name = "UndoBuildAction_UndoPrompt"; val2.SetActive(true); val2.transform.localScale = Vector3.one; MMControlPrompt val3 = val2.GetComponent() ?? val2.GetComponentInChildren(true); LayoutElement obj3 = val2.GetComponent() ?? val2.AddComponent(); obj3.preferredWidth = 50f; obj3.minWidth = 42f; obj3.preferredHeight = 40f; TMP_Text val4 = (TMP_Text)(object)Object.Instantiate(indicator.Fourthtext, val.transform, false); ((Object)((Component)val4).gameObject).name = "UndoBuildAction_UndoText"; ((Component)val4).gameObject.SetActive(true); val4.transform.localScale = Vector3.one; val4.alignment = (TextAlignmentOptions)4097; val4.enableAutoSizing = true; val4.fontSizeMin = 12f; val4.fontSizeMax = 22f; LayoutElement obj4 = ((Component)val4).GetComponent() ?? ((Component)val4).gameObject.AddComponent(); obj4.preferredWidth = 52f; obj4.minWidth = 52f; Behaviour[] components = ((Component)val4).GetComponents(); foreach (Behaviour val5 in components) { if ((Object)(object)val5 != (Object)null && ((object)val5).GetType().FullName == "I2.Loc.Localize") { val5.enabled = false; } } if ((Object)(object)val3 == (Object)null) { Object.Destroy((Object)(object)val); if (!_promptCreationWarningLogged) { _promptCreationWarningLogged = true; Log.LogWarning((object)"Could not create the standalone Undo control prompt."); } return false; } _promptOwner = indicator; _undoPromptCellObject = val; _undoPromptObject = val2; _undoPromptTextObject = ((Component)val4).gameObject; _undoControlPrompt = val3; _undoPromptText = val4; _promptCreationWarningLogged = false; _promptWasController = null; return true; } private void EnsurePromptRow(Indicator indicator) { //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown if ((Object)(object)_promptRowObject != (Object)null && (Object)(object)_promptOwner == (Object)(object)indicator) { return; } Transform promptParent = GetPromptParent(indicator); if (!((Object)(object)promptParent == (Object)null)) { Transform val = promptParent.Find("RemoteBuildCancel_BuilderToolsPromptRow"); if ((Object)(object)val != (Object)null) { ConfigurePromptRow(((Component)val).gameObject); AddVanillaPromptBackground(((Component)val).gameObject, indicator); _promptOwner = indicator; _promptRowObject = ((Component)val).gameObject; } else { GameObject val2 = new GameObject("RemoteBuildCancel_BuilderToolsPromptRow"); ((Transform)val2.AddComponent()).SetParent(promptParent, false); ConfigurePromptRow(val2); AddVanillaPromptBackground(val2, indicator); val2.transform.SetAsLastSibling(); _promptOwner = indicator; _promptRowObject = val2; } } } private static void ConfigurePromptRow(GameObject row) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Expected O, but got Unknown RectTransform obj = row.GetComponent() ?? row.AddComponent(); obj.anchorMin = new Vector2(0.5f, 0f); obj.anchorMax = new Vector2(0.5f, 0f); obj.pivot = new Vector2(0f, 0f); obj.anchoredPosition = new Vector2(240f, 112f); obj.sizeDelta = new Vector2(0f, 52f); LayoutElement obj2 = row.GetComponent() ?? row.AddComponent(); obj2.minWidth = 0f; obj2.preferredWidth = 0f; obj2.flexibleWidth = 0f; obj2.minHeight = 52f; obj2.preferredHeight = 52f; obj2.flexibleHeight = 0f; ContentSizeFitter obj3 = row.GetComponent() ?? row.AddComponent(); obj3.horizontalFit = (FitMode)2; obj3.verticalFit = (FitMode)0; ((Behaviour)obj3).enabled = false; HorizontalLayoutGroup obj4 = row.GetComponent() ?? row.AddComponent(); ((LayoutGroup)obj4).padding = new RectOffset(18, 18, 5, 5); ((HorizontalOrVerticalLayoutGroup)obj4).spacing = 18f; ((LayoutGroup)obj4).childAlignment = (TextAnchor)4; ((HorizontalOrVerticalLayoutGroup)obj4).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)obj4).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)obj4).childForceExpandWidth = false; ((HorizontalOrVerticalLayoutGroup)obj4).childForceExpandHeight = true; } private static void SizePromptRow(GameObject row) { //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)row == (Object)null) { return; } RectTransform component = row.GetComponent(); HorizontalLayoutGroup component2 = row.GetComponent(); if ((Object)(object)component == (Object)null || (Object)(object)component2 == (Object)null) { return; } float num = ((LayoutGroup)component2).padding.left + ((LayoutGroup)component2).padding.right; int num2 = 0; for (int i = 0; i < row.transform.childCount; i++) { GameObject gameObject = ((Component)row.transform.GetChild(i)).gameObject; if (!gameObject.activeSelf || ((Object)gameObject).name.IndexOf("VanillaPromptBackground", StringComparison.Ordinal) >= 0) { continue; } LayoutElement component3 = gameObject.GetComponent(); float num3 = (((Object)(object)component3 != (Object)null) ? Mathf.Max(component3.minWidth, component3.preferredWidth) : 0f); if (num3 <= 0f) { RectTransform component4 = gameObject.GetComponent(); float num4; if (!((Object)(object)component4 != (Object)null)) { num4 = 0f; } else { Rect rect = component4.rect; num4 = ((Rect)(ref rect)).width; } num3 = num4; } if (num3 <= 0f) { num3 = 40f; } if (num2 > 0) { num += ((HorizontalOrVerticalLayoutGroup)component2).spacing; } num += num3; num2++; } num = Mathf.Max(num, 1f); component.SetSizeWithCurrentAnchors((Axis)0, num); component.SetSizeWithCurrentAnchors((Axis)1, 52f); LayoutElement component5 = row.GetComponent(); if ((Object)(object)component5 != (Object)null) { component5.minWidth = num; component5.preferredWidth = num; } } private static Transform GetPromptParent(Indicator indicator) { Transform val = (((Object)(object)indicator != (Object)null && (Object)(object)indicator.ControlPromptUI != (Object)null) ? indicator.ControlPromptUI.transform : (((Object)(object)indicator != (Object)null) ? ((Component)indicator).transform : null)); if ((Object)(object)val == (Object)null) { return null; } Canvas val2 = ((Component)val).GetComponent() ?? ((Component)val).GetComponentInParent(); if (!((Object)(object)val2 != (Object)null)) { if (!((Object)(object)val.parent != (Object)null)) { return val; } return val.parent; } return ((Component)val2).transform; } private static void AddVanillaPromptBackground(GameObject row, Indicator indicator) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) Image val = (((Object)(object)indicator != (Object)null && (Object)(object)indicator.BG != (Object)null) ? (((Component)indicator.BG).GetComponent() ?? ((Component)indicator.BG).GetComponentInChildren(true)) : null); Image val2 = row.GetComponent() ?? row.AddComponent(); ((Behaviour)val2).enabled = true; ((Graphic)val2).raycastTarget = false; val2.sprite = (((Object)(object)val != (Object)null) ? val.sprite : null); val2.type = (Type)(((Object)(object)val != (Object)null) ? ((int)val.type) : 0); if ((Object)(object)val2.sprite == (Object)null) { val2.sprite = Sprite.Create(Texture2D.whiteTexture, new Rect(0f, 0f, 1f, 1f), new Vector2(0.5f, 0.5f)); val2.type = (Type)0; } ((Graphic)val2).color = new Color(0f, 0f, 0f, 0.88f); ((Graphic)val2).canvasRenderer.SetColor(((Graphic)val2).color); for (int i = 0; i < row.transform.childCount; i++) { Transform child = row.transform.GetChild(i); if (((Object)child).name.IndexOf("VanillaPromptBackground", StringComparison.Ordinal) >= 0) { ((Component)child).gameObject.SetActive(false); } } } private void RefreshPromptGlyph(bool controller) { if ((Object)(object)_undoControlPrompt == (Object)null) { return; } if (controller) { _undoControlPrompt.IgnoreControllerChange = false; _undoControlPrompt.Category = 1; _undoControlPrompt.Action = 43; _undoControlPrompt.ForceUpdate(); return; } _undoControlPrompt.IgnoreControllerChange = true; object? obj = AccessTools.Field(typeof(MMControlPrompt), "_text")?.GetValue(_undoControlPrompt); TMP_Text val = (TMP_Text)((obj is TMP_Text) ? obj : null); object? obj2 = AccessTools.Field(typeof(MMControlPrompt), "_iconText")?.GetValue(_undoControlPrompt); TMP_Text val2 = (TMP_Text)((obj2 is TMP_Text) ? obj2 : null); object? obj3 = AccessTools.Field(typeof(MMControlPrompt), "_icon")?.GetValue(_undoControlPrompt); Image val3 = (Image)((obj3 is Image) ? obj3 : null); if ((Object)(object)val != (Object)null) { val.text = (UndoBuildActionConfig.RequireControl.Value ? ("Ctrl+" + ((object)Unsafe.As(ref _undoKey)/*cast due to .constrained prefix*/).ToString()) : ((object)Unsafe.As(ref _undoKey)/*cast due to .constrained prefix*/).ToString()); val.fontSize = 20f; val.enableAutoSizing = true; val.fontSizeMin = 8f; val.fontSizeMax = 20f; ((Component)val).gameObject.SetActive(true); } if ((Object)(object)val2 != (Object)null) { ((Component)val2).gameObject.SetActive(false); } if ((Object)(object)val3 != (Object)null) { ((Component)val3).gameObject.SetActive(true); } } private void HideUndoPrompt() { if ((Object)(object)_undoPromptCellObject != (Object)null) { _undoPromptCellObject.SetActive(false); } RefreshPromptRowVisibility(_promptRowObject); } private static void RefreshPromptRowVisibility(GameObject row) { if ((Object)(object)row == (Object)null) { return; } bool active = false; for (int i = 0; i < row.transform.childCount; i++) { GameObject gameObject = ((Component)row.transform.GetChild(i)).gameObject; if (gameObject.activeSelf && ((Object)gameObject).name.IndexOf("VanillaPromptBackground", StringComparison.Ordinal) < 0) { active = true; break; } } SizePromptRow(row); row.SetActive(active); } private void DestroyUndoPrompt() { if ((Object)(object)_undoPromptCellObject != (Object)null) { Object.Destroy((Object)(object)_undoPromptCellObject); } else { if ((Object)(object)_undoPromptObject != (Object)null) { Object.Destroy((Object)(object)_undoPromptObject); } if ((Object)(object)_undoPromptTextObject != (Object)null) { Object.Destroy((Object)(object)_undoPromptTextObject); } } _undoPromptCellObject = null; _undoPromptObject = null; _undoPromptTextObject = null; _undoControlPrompt = null; _undoPromptText = null; _promptWasController = null; } private static KeyCode ParseKey(string raw, KeyCode fallback) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) try { return (KeyCode)Enum.Parse(typeof(KeyCode), raw, ignoreCase: true); } catch { return fallback; } } } internal static class UndoBuildActionConfig { internal static ConfigEntry Enabled; internal static ConfigEntry UndoKey; internal static ConfigEntry RequireControl; internal static ConfigEntry EnableControllerUndo; internal static ConfigEntry ShowUndoPrompt; internal static ConfigEntry UndoDepth; internal static ConfigEntry AllowCriticalStructures; internal static void Bind(ConfigFile config) { //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Expected O, but got Unknown Enabled = config.Bind("General", "Enabled", true, "Enable Undo Build Action."); UndoKey = config.Bind("Controls", "UndoKey", "Z", "KeyCode used for undo."); RequireControl = config.Bind("Controls", "RequireControl", true, "Require Ctrl plus UndoKey."); EnableControllerUndo = config.Bind("Controls", "EnableControllerUndo", true, "Allow the UI Page Left controller action (normally LB/L1) to undo during build placement."); ShowUndoPrompt = config.Bind("UI", "ShowUndoPrompt", true, "Show a vanilla bottom-bar Undo prompt while build placement has an action available to undo."); UndoDepth = config.Bind("Undo", "UndoDepth", 10, new ConfigDescription("How many recent placements are kept.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 50), Array.Empty())); if (UndoDepth.Value < 10) { UndoDepth.Value = 10; } AllowCriticalStructures = config.Bind("Safety", "AllowCriticalStructures", false, "Allow undo removal of temple/shrine/altar/builder style critical structures."); } } [HarmonyPatch(typeof(Interaction_PlacementRegion), "OnInteract")] internal static class InteractionPlacementRegionOnInteractPatch { private static void Prefix() { UndoBuildActionPlugin.Instance?.ClearSession(); } } [HarmonyPatch(typeof(StructureManager), "AddStructure")] internal static class StructureManagerAddStructurePatch { private static void Postfix(StructureBrain __result) { UndoBuildActionPlugin.Instance?.RecordLatest(__result); } } }