Decompiled source of DelicateWatchSaver v1.4.3

DelicateWatchSaver.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using Microsoft.CodeAnalysis;
using On.RoR2;
using RiskOfOptions;
using RiskOfOptions.OptionConfigs;
using RiskOfOptions.Options;
using RoR2;
using UnityEngine;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("DelicateWatchSaver")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("DelicateWatchSaver")]
[assembly: AssemblyTitle("DelicateWatchSaver")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[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 WatchSaverMod
{
	[BepInPlugin("com.CATAHA_CCCP.DelicateWatchSaver", "DelicateWatchSaver", "1.4.3")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class WatchSaverPlugin : BaseUnityPlugin
	{
		private enum InteractionBlockResult
		{
			Allow,
			FirstClickBlock,
			DebounceBlock
		}

		private struct ShrineUnlockKey : IEquatable<ShrineUnlockKey>
		{
			public int ShrineId;

			public int PlayerBodyId;

			public ShrineUnlockKey(int shrineId, int playerBodyId)
			{
				ShrineId = shrineId;
				PlayerBodyId = playerBodyId;
			}

			public bool Equals(ShrineUnlockKey other)
			{
				if (ShrineId == other.ShrineId)
				{
					return PlayerBodyId == other.PlayerBodyId;
				}
				return false;
			}

			public override bool Equals(object obj)
			{
				if (obj is ShrineUnlockKey other)
				{
					return Equals(other);
				}
				return false;
			}

			public override int GetHashCode()
			{
				return (ShrineId * 397) ^ PlayerBodyId;
			}
		}

		public const string PluginGUID = "com.CATAHA_CCCP.DelicateWatchSaver";

		public const string PluginName = "DelicateWatchSaver";

		public const string PluginVersion = "1.4.3";

		private readonly Dictionary<ShrineUnlockKey, float> unlockedShrines = new Dictionary<ShrineUnlockKey, float>();

		private readonly HashSet<NetworkInstanceId> handshakedPlayers = new HashSet<NetworkInstanceId>();

		private bool serverHasMod;

		private BuffIndex parryingBuffIndex = (BuffIndex)(-1);

		private bool parryingBuffCached;

		private ConfigEntry<bool> configGlobalMode;

		private ConfigEntry<float> configConfirmWindow;

		private ConfigEntry<float> configDebounceTime;

		private ConfigEntry<string> configCustomWarningText;

		private ConfigEntry<string> configCustomDebounceWarningText;

		private ConfigEntry<bool> configEnableDebugLogs;

		private ConfigEntry<bool> configTrustSaferSpaces;

		private ConfigEntry<bool> configTrustParry;

		public void Awake()
		{
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Expected O, but got Unknown
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Expected O, but got Unknown
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: Expected O, but got Unknown
			configGlobalMode = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Global Mode", false, "If true, operates in Global mode (protects everyone, requires you to be Host/Server, broadcasts warnings to everyone in chat with player names).\nIf false (default), operates in Local mode (only protects you, messages are private, works on any server).\nNOTE: This setting automatically falls back to Local mode if you join someone else's server as a client to ensure you stay protected. Use this mode ONLY if you (the host) have the mod downloaded, but your friends do not. If everyone in the lobby has the mod, it’s best to leave this turned off.");
			configConfirmWindow = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Confirm Window", 3f, "Time in seconds you have to click again to confirm.");
			configDebounceTime = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Debounce Time", 0.3f, "Minimum delay in seconds required before the second click is accepted. Prevents accidental instant double-clicks. Set to 0 to disable.");
			configCustomWarningText = ((BaseUnityPlugin)this).Config.Bind<string>("General", "Custom Warning Text", "", "Leave empty to use automatic game language. Use {0} for the timer. In Global mode, {1} will be replaced with the player's name.");
			configCustomDebounceWarningText = ((BaseUnityPlugin)this).Config.Bind<string>("General", "Custom Debounce Warning Text", "", "Leave empty to use automatic game language. Short warning shown when clicking too fast.");
			configEnableDebugLogs = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "Enable Debug Logs", false, "Prints math calculations to console.");
			configTrustSaferSpaces = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Trust Safer Spaces", true, "If true, no warning will be shown if Safer Spaces is ready to block the damage.");
			configTrustParry = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Trust Parry", true, "If true, no warning will be shown if you are actively parrying (e.g. using Deus Ex Machina equipment from Alloyed Collective DLC).");
			Interactor.AttemptInteraction += new hook_AttemptInteraction(Interactor_AttemptInteraction);
			Interactor.PerformInteraction += new hook_PerformInteraction(Interactor_PerformInteraction);
			Chat.AddMessage_ChatMessageBase += new hook_AddMessage_ChatMessageBase(Chat_AddMessage_ChatMessageBase);
			Stage.onStageStartGlobal += OnStageStart;
			Run.onRunStartGlobal += OnRunStart;
			Run.onRunDestroyGlobal += OnRunDestroy;
			CharacterBody.onBodyStartGlobal += OnBodyStart;
			configGlobalMode.SettingChanged += OnGlobalModeChanged;
			if (Chainloader.PluginInfos.ContainsKey("com.rune580.riskofoptions"))
			{
				InitRiskOfOptions();
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"DelicateWatchSaver 1.4.3 loaded successfully!");
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private void InitRiskOfOptions()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: 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_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Expected O, but got Unknown
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Expected O, but got Unknown
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Expected O, but got Unknown
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Expected O, but got Unknown
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Expected O, but got Unknown
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Expected O, but got Unknown
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Expected O, but got Unknown
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Expected O, but got Unknown
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Expected O, but got Unknown
			ModSettingsManager.SetModDescription("Safety confirmation for health-costing interactions to prevent breaking Delicate Watches.");
			ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(configGlobalMode));
			ModSettingsManager.AddOption((BaseOption)new StepSliderOption(configConfirmWindow, new StepSliderConfig
			{
				min = 1f,
				max = 10f,
				increment = 0.5f
			}));
			ModSettingsManager.AddOption((BaseOption)new StepSliderOption(configDebounceTime, new StepSliderConfig
			{
				min = 0f,
				max = 2f,
				increment = 0.05f
			}));
			ModSettingsManager.AddOption((BaseOption)new StringInputFieldOption(configCustomWarningText));
			ModSettingsManager.AddOption((BaseOption)new StringInputFieldOption(configCustomDebounceWarningText));
			ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(configTrustSaferSpaces));
			ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(configTrustParry));
			ModSettingsManager.AddOption((BaseOption)new CheckBoxOption(configEnableDebugLogs));
		}

		private void OnDestroy()
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Expected O, but got Unknown
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Expected O, but got Unknown
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected O, but got Unknown
			Interactor.AttemptInteraction -= new hook_AttemptInteraction(Interactor_AttemptInteraction);
			Interactor.PerformInteraction -= new hook_PerformInteraction(Interactor_PerformInteraction);
			Chat.AddMessage_ChatMessageBase -= new hook_AddMessage_ChatMessageBase(Chat_AddMessage_ChatMessageBase);
			Stage.onStageStartGlobal -= OnStageStart;
			Run.onRunStartGlobal -= OnRunStart;
			Run.onRunDestroyGlobal -= OnRunDestroy;
			CharacterBody.onBodyStartGlobal -= OnBodyStart;
			configGlobalMode.SettingChanged -= OnGlobalModeChanged;
		}

		private void OnRunStart(Run run)
		{
			unlockedShrines.Clear();
			handshakedPlayers.Clear();
		}

		private void OnRunDestroy(Run run)
		{
			unlockedShrines.Clear();
			handshakedPlayers.Clear();
			serverHasMod = false;
		}

		private void OnStageStart(Stage stage)
		{
			unlockedShrines.Clear();
		}

		private void OnGlobalModeChanged(object sender, EventArgs e)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Expected O, but got Unknown
			if (NetworkServer.active)
			{
				handshakedPlayers.Clear();
				string text = (configGlobalMode.Value ? "[WatchSaverHandshake]" : "[WatchSaverHandshakeDisable]");
				LogDebug($"Global Mode changed to {configGlobalMode.Value}. Broadcasting signal.");
				Chat.SendBroadcastChat((ChatMessageBase)new SimpleChatMessage
				{
					baseToken = "<size=0>" + text + "</size>"
				});
			}
		}

		private void OnBodyStart(CharacterBody body)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)body != (Object)null && NetworkServer.active && configGlobalMode.Value && body.isPlayerControlled && !body.hasEffectiveAuthority && handshakedPlayers.Add(body.masterObjectId))
			{
				((MonoBehaviour)this).StartCoroutine(SendDelayedHandshake(body.GetUserName()));
			}
		}

		private IEnumerator SendDelayedHandshake(string userName)
		{
			yield return (object)new WaitForSeconds(1f);
			if (NetworkServer.active && configGlobalMode.Value)
			{
				LogDebug("New player body spawned (" + userName + "). Sending handshake signal.");
				Chat.SendBroadcastChat((ChatMessageBase)new SimpleChatMessage
				{
					baseToken = "<size=0>[WatchSaverHandshake]</size>"
				});
			}
		}

		private void LogDebug(string message)
		{
			if (configEnableDebugLogs.Value)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("[WatchSaver] " + message));
			}
		}

		private void Chat_AddMessage_ChatMessageBase(orig_AddMessage_ChatMessageBase orig, ChatMessageBase message)
		{
			SimpleChatMessage val = (SimpleChatMessage)(object)((message is SimpleChatMessage) ? message : null);
			if (val != null && !string.IsNullOrEmpty(val.baseToken))
			{
				bool flag = val.baseToken.Contains("[WatchSaverHandshake]");
				bool flag2 = val.baseToken.Contains("[WatchSaverHandshakeDisable]");
				if (flag || flag2)
				{
					if (!NetworkServer.active)
					{
						serverHasMod = flag;
						LogDebug($"Handshake signal received. serverHasMod set to: {serverHasMod}");
					}
					return;
				}
			}
			orig.Invoke(message);
		}

		private BuffIndex GetParryingBuffIndex()
		{
			//IL_002c: 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_001f: Unknown result type (might be due to invalid IL or missing references)
			if (!parryingBuffCached && ((ResourceAvailability)(ref BuffCatalog.availability)).available)
			{
				parryingBuffIndex = BuffCatalog.FindBuffIndex("bdParrying");
				parryingBuffCached = true;
			}
			return parryingBuffIndex;
		}

		private string GetLocalizedWarning(float timer, string playerName = "", InteractionBlockResult result = InteractionBlockResult.FirstClickBlock)
		{
			try
			{
				string text = (string.IsNullOrEmpty(playerName) ? "" : Util.EscapeRichTextForTextMeshPro(playerName));
				if (result == InteractionBlockResult.DebounceBlock)
				{
					if (!string.IsNullOrWhiteSpace(configCustomDebounceWarningText.Value))
					{
						return configCustomDebounceWarningText.Value;
					}
					if (!string.IsNullOrEmpty(text))
					{
						return "<color=#ffcc00>Slow down (" + text + "):</color> Clicking too fast!";
					}
					return "<color=#ffcc00>Slow down:</color> Clicking too fast!";
				}
				if (!string.IsNullOrWhiteSpace(configCustomWarningText.Value))
				{
					string text2 = configCustomWarningText.Value.Replace("{0}", timer.ToString());
					string newValue = (string.IsNullOrEmpty(text) ? "You" : text);
					return text2.Replace("{1}", newValue);
				}
				if (!string.IsNullOrEmpty(text))
				{
					return $"<color=#ff0000>Warning ({text}):</color> This action will break your Delicate Watch! Click again within {timer} sec to confirm.";
				}
				return $"<color=#ff0000>Warning:</color> This interaction will break your Delicate Watch! Click again within {timer} sec to confirm.";
			}
			catch (Exception arg)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)$"[WatchSaver] Warning text formatting error: {arg}");
			}
			if (result != InteractionBlockResult.DebounceBlock)
			{
				return $"<color=#ff0000>Warning:</color> Interaction will break Delicate Watch! Click again within {timer} sec.";
			}
			return "<color=#ffcc00>Slow down:</color> Clicking too fast!";
		}

		private void Interactor_AttemptInteraction(orig_AttemptInteraction orig, Interactor self, GameObject interactableObject)
		{
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Invalid comparison between Unknown and I4
			if ((Object)(object)self == (Object)null || (Object)(object)interactableObject == (Object)null)
			{
				orig.Invoke(self, interactableObject);
				return;
			}
			CharacterBody component = ((Component)self).GetComponent<CharacterBody>();
			if ((Object)(object)component != (Object)null && component.hasEffectiveAuthority)
			{
				if (serverHasMod || (NetworkServer.active && configGlobalMode.Value))
				{
					LogDebug("Bypassing local check because Global Mode is active.");
					orig.Invoke(self, interactableObject);
					return;
				}
				PurchaseInteraction component2 = interactableObject.GetComponent<PurchaseInteraction>();
				if ((Object)(object)component2 != (Object)null && (int)component2.costType == 2 && component2.available && component2.CanBeAffordedByInteractor(self))
				{
					InteractionBlockResult interactionBlockResult = ShouldBlockInteraction(component, component2.cost, ((Object)((Component)component2).gameObject).GetInstanceID());
					if (interactionBlockResult != InteractionBlockResult.Allow)
					{
						Chat.AddMessage(GetLocalizedWarning(configConfirmWindow.Value, "", interactionBlockResult));
						return;
					}
				}
			}
			orig.Invoke(self, interactableObject);
		}

		private void Interactor_PerformInteraction(orig_PerformInteraction orig, Interactor self, GameObject interactableObject)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Invalid comparison between Unknown and I4
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Expected O, but got Unknown
			if (!NetworkServer.active || !configGlobalMode.Value || (Object)(object)self == (Object)null || (Object)(object)interactableObject == (Object)null)
			{
				orig.Invoke(self, interactableObject);
				return;
			}
			PurchaseInteraction component = interactableObject.GetComponent<PurchaseInteraction>();
			if ((Object)(object)component != (Object)null && (int)component.costType == 2 && component.available && component.CanBeAffordedByInteractor(self))
			{
				CharacterBody component2 = ((Component)self).GetComponent<CharacterBody>();
				if ((Object)(object)component2 != (Object)null)
				{
					InteractionBlockResult interactionBlockResult = ShouldBlockInteraction(component2, component.cost, ((Object)((Component)component).gameObject).GetInstanceID());
					if (interactionBlockResult != InteractionBlockResult.Allow)
					{
						string text = component2.GetUserName();
						if (string.IsNullOrEmpty(text))
						{
							text = "Someone";
						}
						Chat.SendBroadcastChat((ChatMessageBase)new SimpleChatMessage
						{
							baseToken = GetLocalizedWarning(configConfirmWindow.Value, text, interactionBlockResult)
						});
						return;
					}
				}
			}
			orig.Invoke(self, interactableObject);
		}

		private InteractionBlockResult ShouldBlockInteraction(CharacterBody body, float cost, int objectId)
		{
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Invalid comparison between Unknown and I4
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)body == (Object)null || (Object)(object)body.inventory == (Object)null || (Object)(object)body.healthComponent == (Object)null)
			{
				return InteractionBlockResult.Allow;
			}
			if (body.inventory.GetItemCountEffective(Items.FragileDamageBonus) <= 0)
			{
				return InteractionBlockResult.Allow;
			}
			if (configTrustSaferSpaces.Value && body.HasBuff(Buffs.BearVoidReady))
			{
				LogDebug("Safer Spaces is ready! Bypassing block.");
				return InteractionBlockResult.Allow;
			}
			if (configTrustParry.Value)
			{
				BuffIndex val = GetParryingBuffIndex();
				if ((int)val != -1 && body.HasBuff(val))
				{
					LogDebug("Player is actively parrying (Deus Ex Machina)! Bypassing block.");
					return InteractionBlockResult.Allow;
				}
			}
			HealthComponent healthComponent = body.healthComponent;
			float num = cost / 100f;
			float num2 = healthComponent.fullCombinedHealth * num;
			if (healthComponent.combinedHealth <= num2)
			{
				LogDebug($"Bypassing block: combinedHealth ({healthComponent.combinedHealth:0.##}) <= incomingDamage ({num2:0.##}). Game will deal 0 damage.");
				return InteractionBlockResult.Allow;
			}
			float num3 = Mathf.Max(0f, num2 - healthComponent.barrier);
			float num4 = healthComponent.health + healthComponent.shield - num3;
			float num5 = healthComponent.fullCombinedHealth * 0.25f;
			LogDebug($"Cost: {cost}%. Incoming Damage: {num2:0.##}. Health+Shield After: {num4:0.##}. Threshold: {num5:0.##}");
			if (num4 <= num5 + 1f)
			{
				int instanceID = ((Object)((Component)body).gameObject).GetInstanceID();
				ShrineUnlockKey key = new ShrineUnlockKey(objectId, instanceID);
				if (unlockedShrines.TryGetValue(key, out var value))
				{
					float num6 = Time.time - value;
					if (num6 < configDebounceTime.Value)
					{
						LogDebug($"Confirmation click ignored: too fast ({num6:0.00}s elapsed, debounce is {configDebounceTime.Value}s).");
						return InteractionBlockResult.DebounceBlock;
					}
					if (num6 <= configConfirmWindow.Value)
					{
						LogDebug($"Confirmation active for Player {instanceID}. Allowing (elapsed: {num6:0.00}s).");
						unlockedShrines.Remove(key);
						return InteractionBlockResult.Allow;
					}
				}
				unlockedShrines[key] = Time.time;
				return InteractionBlockResult.FirstClickBlock;
			}
			return InteractionBlockResult.Allow;
		}
	}
}