Decompiled source of AutoCloseDoors v1.0.3

Landoria.AutoCloseDoors.dll

Decompiled 8 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Landoria.AutoCloseDoors")]
[assembly: AssemblyDescription("Automatically closes player-built doors when no player is nearby.")]
[assembly: AssemblyCompany("Landoria")]
[assembly: AssemblyProduct("Landoria.AutoCloseDoors")]
[assembly: AssemblyCopyright("Copyright © 2026 Landoria")]
[assembly: ComVisible(false)]
[assembly: Guid("92DF0504-5C40-43E7-AC8C-9F36792D6FED")]
[assembly: AssemblyFileVersion("1.0.3")]
[assembly: AssemblyInformationalVersion("1.0.3")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.0.3.39085")]
[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 Landoria.AutoCloseDoors
{
	internal static class ConfigWatcher
	{
		private const int ReloadDelayMilliseconds = 250;

		private const int MaximumReloadAttempts = 5;

		private static ConfigFile config;

		private static string configPath;

		private static ManualLogSource logger;

		private static FileSystemWatcher watcher;

		private static long lastChangeTicks;

		private static int reloadPending;

		private static int reloadAttempts;

		private static long ignoredWriteTicks;

		private static string displayName;

		private static Action restoreDefaults;

		private static Action applyConfiguration;

		internal static void Initialize(ConfigFile configFile, ManualLogSource logSource, string configurationDisplayName, Action restoreDefaultsAction, Action applyConfigurationAction = null)
		{
			Dispose();
			config = configFile;
			logger = logSource;
			displayName = configurationDisplayName;
			restoreDefaults = restoreDefaultsAction;
			applyConfiguration = applyConfigurationAction;
			configPath = Path.GetFullPath(config.ConfigFilePath);
			watcher = new FileSystemWatcher(Path.GetDirectoryName(configPath), Path.GetFileName(configPath));
			watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite | NotifyFilters.CreationTime;
			watcher.Changed += OnChanged;
			watcher.Created += OnChanged;
			watcher.Deleted += OnChanged;
			watcher.Renamed += OnRenamed;
			watcher.EnableRaisingEvents = true;
		}

		internal static void IgnoreCurrentFileVersion()
		{
			if (string.IsNullOrEmpty(configPath) || !File.Exists(configPath))
			{
				return;
			}
			try
			{
				Interlocked.Exchange(ref ignoredWriteTicks, File.GetLastWriteTimeUtc(configPath).Ticks);
				Interlocked.Exchange(ref reloadPending, 0);
			}
			catch (IOException)
			{
				ManualLogSource obj = logger;
				if (obj != null)
				{
					obj.LogDebug((object)"Could not mark the internal configuration write.");
				}
			}
		}

		internal static void Update()
		{
			if (Volatile.Read(in reloadPending) == 0 || !ReloadDelayElapsed())
			{
				return;
			}
			Interlocked.Exchange(ref reloadPending, 0);
			try
			{
				bool flag = !File.Exists(configPath);
				if (flag)
				{
					restoreDefaults?.Invoke();
				}
				else
				{
					config.Reload();
				}
				applyConfiguration?.Invoke();
				reloadAttempts = 0;
				logger.LogInfo((object)(flag ? "Recreated the deleted configuration with default values." : "Reloaded configuration after a file change."));
				ShowReloadMessage(flag);
			}
			catch (Exception exception)
			{
				RetryReload(exception);
			}
		}

		internal static void Dispose()
		{
			if (watcher != null)
			{
				watcher.EnableRaisingEvents = false;
				watcher.Dispose();
				watcher = null;
			}
			Interlocked.Exchange(ref reloadPending, 0);
			Interlocked.Exchange(ref ignoredWriteTicks, 0L);
			reloadAttempts = 0;
			displayName = null;
			restoreDefaults = null;
			applyConfiguration = null;
		}

		private static void OnChanged(object sender, FileSystemEventArgs eventArgs)
		{
			QueueReload();
		}

		private static void OnRenamed(object sender, RenamedEventArgs eventArgs)
		{
			QueueReload();
		}

		private static void QueueReload()
		{
			if (!IsIgnoredWrite())
			{
				Interlocked.Exchange(ref lastChangeTicks, DateTime.UtcNow.Ticks);
				Interlocked.Exchange(ref reloadPending, 1);
				reloadAttempts = 0;
			}
		}

		private static bool IsIgnoredWrite()
		{
			try
			{
				long num = (File.Exists(configPath) ? File.GetLastWriteTimeUtc(configPath).Ticks : 0);
				return num != 0L && num == Interlocked.Read(in ignoredWriteTicks);
			}
			catch (IOException)
			{
				return false;
			}
		}

		private static bool ReloadDelayElapsed()
		{
			return DateTime.UtcNow.Ticks - Interlocked.Read(in lastChangeTicks) >= 2500000;
		}

		private static void RetryReload(Exception exception)
		{
			reloadAttempts++;
			logger.LogWarning((object)("Configuration reload failed: " + exception.Message));
			if (reloadAttempts < 5)
			{
				Interlocked.Exchange(ref lastChangeTicks, DateTime.UtcNow.Ticks);
				Interlocked.Exchange(ref reloadPending, 1);
			}
		}

		private static void ShowReloadMessage(bool recreated)
		{
			Player localPlayer = Player.m_localPlayer;
			if (localPlayer != null)
			{
				((Character)localPlayer).Message((MessageType)1, recreated ? (displayName + " configuration restored to defaults.") : (displayName + " configuration reloaded."), 0, (Sprite)null, false);
			}
		}
	}
	internal static class DoorChoice
	{
		internal static bool IsTarget(Door door)
		{
			if ((Object)(object)door == (Object)null || (Object)(object)door.m_keyItem != (Object)null || door.m_canNotBeClosed)
			{
				return false;
			}
			Piece component = ((Component)door).GetComponent<Piece>();
			if ((Object)(object)component != (Object)null)
			{
				return component.IsPlacedByPlayer();
			}
			return false;
		}

		internal static bool CanClose(Door door)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			if (IsTarget(door) && (Object)(object)Player.m_localPlayer != (Object)null)
			{
				if (door.m_checkGuardStone)
				{
					return PrivateArea.CheckAccess(((Component)door).transform.position, 0f, false, false);
				}
				return true;
			}
			return false;
		}

		internal static bool IsEnabled(Door door)
		{
			Player localPlayer = Player.m_localPlayer;
			string text = Key(door, "Enabled");
			string key = Key(door, "Disabled");
			if ((Object)(object)localPlayer != (Object)null && text != null)
			{
				if (localPlayer.m_customData.ContainsKey(text))
				{
					return true;
				}
				if (localPlayer.m_customData.ContainsKey(key))
				{
					return false;
				}
			}
			return Plugin.Settings?.EnabledByDefault ?? true;
		}

		internal static void Toggle(Door door)
		{
			Player localPlayer = Player.m_localPlayer;
			string text = Key(door, "Enabled");
			string text2 = Key(door, "Disabled");
			if ((Object)(object)localPlayer == (Object)null || text == null)
			{
				return;
			}
			bool flag = !IsEnabled(door);
			localPlayer.m_customData.Remove(text);
			localPlayer.m_customData.Remove(text2);
			localPlayer.m_customData[flag ? text : text2] = "1";
			if (flag)
			{
				ZNetView component = ((Component)door).GetComponent<ZNetView>();
				if ((Object)(object)component != (Object)null && component.IsValid() && component.GetZDO().GetInt(ZDOVars.s_state, 0) != 0)
				{
					DoorProximity.Track(door);
				}
			}
			else
			{
				DoorProximity.Cancel(door);
			}
			string text3 = (flag ? "enabled" : "disabled");
			((Character)localPlayer).Message((MessageType)2, "Auto-close " + text3, 0, (Sprite)null, false);
		}

		private static string Key(Door door, string choice)
		{
			//IL_002d: 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)
			ZNetView component = ((Component)door).GetComponent<ZNetView>();
			if ((Object)(object)component == (Object)null || !component.IsValid() || (Object)(object)ZNet.instance == (Object)null)
			{
				return null;
			}
			ZDOID uid = component.GetZDO().m_uid;
			return "Landoria.AutoCloseDoors." + choice + ":" + ZNet.instance.GetWorldUID() + ":" + ((ZDOID)(ref uid)).UserID + ":" + ((ZDOID)(ref uid)).ID;
		}
	}
	[HarmonyPatch]
	internal static class DoorPatches
	{
		[HarmonyPatch(typeof(Door), "GetHoverText")]
		[HarmonyPostfix]
		private static void Hover(Door __instance, ref string __result)
		{
			if (DoorChoice.CanClose(__instance) && !string.IsNullOrEmpty(__result))
			{
				string text = (DoorChoice.IsEnabled(__instance) ? "on" : "off");
				string text2 = Localization.instance.Localize("$KEY_Use");
				__result = __result + "\n[<color=yellow><b>" + Plugin.Settings.ToggleModifierName + " + " + text2 + "</b></color>] Auto-close: " + text;
			}
		}

		[HarmonyPatch(typeof(Door), "Interact")]
		[HarmonyPrefix]
		private static bool Interact(Door __instance, Humanoid character, bool hold, bool alt, ref bool __result, out bool? __state)
		{
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			__state = false;
			if (DoorChoice.IsTarget(__instance) && !hold && (Object)(object)character == (Object)(object)Player.m_localPlayer)
			{
				ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
				__state = (Object)(object)component != (Object)null && component.IsValid() && component.GetZDO().GetInt(ZDOVars.s_state, 0) == 0;
			}
			if (!DoorChoice.IsTarget(__instance) || hold || Plugin.Settings == null || !Input.GetKey(Plugin.Settings.ToggleModifier) || (Object)(object)character != (Object)(object)Player.m_localPlayer)
			{
				return true;
			}
			if (!DoorChoice.CanClose(__instance))
			{
				return true;
			}
			DoorChoice.Toggle(__instance);
			__state = null;
			__result = true;
			return false;
		}

		[HarmonyPatch(typeof(Door), "Interact")]
		[HarmonyPostfix]
		private static void AfterInteract(Door __instance, Humanoid character, bool hold, bool alt, bool __result, bool? __state)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			if (!(!__state.HasValue || !DoorChoice.IsTarget(__instance) || hold) && __result && !((Object)(object)character != (Object)(object)Player.m_localPlayer) && (!__instance.m_checkGuardStone || PrivateArea.CheckAccess(((Component)__instance).transform.position, 0f, false, false)))
			{
				if (__state.Value && DoorChoice.IsEnabled(__instance))
				{
					DoorProximity.Track(__instance);
				}
				else
				{
					DoorProximity.Cancel(__instance);
				}
			}
		}
	}
	internal static class DoorProximity
	{
		private const float OpenRequestTimeout = 2f;

		private static readonly Dictionary<Door, float> Tracked = new Dictionary<Door, float>();

		private static readonly List<Door> Finished = new List<Door>();

		internal static void Track(Door door)
		{
			Tracked[door] = Time.time;
		}

		internal static void Cancel(Door door)
		{
			Tracked.Remove(door);
		}

		internal static void Update()
		{
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			Finished.Clear();
			float num = Mathf.Max(0f, Plugin.Settings.CloseDistanceMeters);
			foreach (KeyValuePair<Door, float> item in Tracked)
			{
				Door key = item.Key;
				if ((Object)(object)key == (Object)null || !DoorChoice.IsEnabled(key))
				{
					Finished.Add(key);
					continue;
				}
				ZNetView component = ((Component)key).GetComponent<ZNetView>();
				if ((Object)(object)component == (Object)null || !component.IsValid())
				{
					Finished.Add(key);
				}
				else if (component.GetZDO().GetInt(ZDOVars.s_state, 0) == 0)
				{
					if (Time.time - item.Value >= 2f)
					{
						Finished.Add(key);
					}
				}
				else if (!Player.IsPlayerInRange(((Component)key).transform.position, num) && DoorChoice.CanClose(key))
				{
					component.InvokeRPC("UseDoor", new object[1] { false });
					Finished.Add(key);
				}
			}
			foreach (Door item2 in Finished)
			{
				Tracked.Remove(item2);
			}
		}

		internal static void Clear()
		{
			Tracked.Clear();
			Finished.Clear();
		}
	}
	internal enum ToggleModifierOption
	{
		LeftAlt,
		LeftShift,
		LeftControl
	}
	internal sealed class ModConfig
	{
		private readonly ConfigFile config;

		private readonly ConfigEntry<bool> enabledByDefault;

		private readonly ConfigEntry<float> closeDistance;

		private readonly ConfigEntry<ToggleModifierOption> toggleModifier;

		internal bool EnabledByDefault => enabledByDefault.Value;

		internal float CloseDistanceMeters => closeDistance.Value;

		internal KeyCode ToggleModifier
		{
			get
			{
				if (toggleModifier.Value != ToggleModifierOption.LeftShift)
				{
					if (toggleModifier.Value == ToggleModifierOption.LeftControl)
					{
						return (KeyCode)306;
					}
					return (KeyCode)308;
				}
				return (KeyCode)304;
			}
		}

		internal string ToggleModifierName
		{
			get
			{
				if (toggleModifier.Value != ToggleModifierOption.LeftShift)
				{
					if (toggleModifier.Value != ToggleModifierOption.LeftControl)
					{
						return "Alt";
					}
					return "Ctrl";
				}
				return "Shift";
			}
		}

		internal ModConfig(ConfigFile configFile)
		{
			config = configFile;
			enabledByDefault = config.Bind<bool>("General", "EnabledByDefault", true, "Whether supported doors close automatically unless changed with the modifier and use key.");
			closeDistance = config.Bind<float>("General", "CloseDistanceMeters", 5f, "Distance players must leave before an enabled door closes automatically.");
			toggleModifier = config.Bind<ToggleModifierOption>("General", "ToggleModifier", ToggleModifierOption.LeftAlt, "Modifier used with the door interaction key to toggle auto-close.");
		}

		internal void RestoreDefaults()
		{
			enabledByDefault.Value = true;
			closeDistance.Value = 5f;
			toggleModifier.Value = ToggleModifierOption.LeftAlt;
			config.Save();
			ConfigWatcher.IgnoreCurrentFileVersion();
		}
	}
	[BepInPlugin("Landoria.AutoCloseDoors", "AutoClose Doors", "1.0.3")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "Landoria.AutoCloseDoors";

		public const string PluginName = "AutoClose Doors";

		public const string PluginVersion = "1.0.3";

		internal static ModConfig Settings;

		private Harmony harmony;

		private void Awake()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			Settings = new ModConfig(((BaseUnityPlugin)this).Config);
			harmony = new Harmony("Landoria.AutoCloseDoors");
			harmony.PatchAll();
			ConfigWatcher.Initialize(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger, "AutoClose Doors", Settings.RestoreDefaults);
		}

		private void Update()
		{
			ConfigWatcher.Update();
			DoorProximity.Update();
		}

		private void OnDestroy()
		{
			ConfigWatcher.Dispose();
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			DoorProximity.Clear();
			Settings = null;
		}
	}
}