Decompiled source of DungeonEnvironmentGuard v0.1.0

plugins\DungeonEnvironmentGuard\DungeonEnvironmentGuard.dll

Decompiled 2 days ago
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: AssemblyCompany("Daishi11")]
[assembly: AssemblyTitle("DungeonEnvironmentGuard")]
[assembly: AssemblyDescription("Guards against rapid Crypt/SunkenCrypt forced-environment ping-pong in Valheim")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyProduct("DungeonEnvironmentGuard")]
[assembly: CompilationRelaxations(8)]
[assembly: AssemblyVersion("0.1.0.0")]
namespace Daishi11.DungeonEnvironmentGuard;

[BepInPlugin("daishi11.valheim.dungeonenvironmentguard", "Dungeon Environment Guard", "0.1.0")]
public sealed class Plugin : BaseUnityPlugin
{
	public const string PluginGuid = "daishi11.valheim.dungeonenvironmentguard";

	public const string PluginName = "Dungeon Environment Guard";

	public const string PluginVersion = "0.1.0";

	private Harmony harmony;

	internal static ManualLogSource Log;

	internal static ConfigEntry<bool> Enabled;

	internal static ConfigEntry<int> ConflictWindowMs;

	internal static ConfigEntry<int> ReleaseDelayMs;

	internal static ConfigEntry<bool> DiagnosticLogging;

	private void Awake()
	{
		//IL_0057: Unknown result type (might be due to invalid IL or missing references)
		//IL_0061: Expected O, but got Unknown
		//IL_0092: Unknown result type (might be due to invalid IL or missing references)
		//IL_009c: Expected O, but got Unknown
		//IL_0118: Unknown result type (might be due to invalid IL or missing references)
		//IL_0122: Expected O, but got Unknown
		//IL_012a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0138: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable the Crypt/SunkenCrypt environment conflict guard.");
		ConflictWindowMs = ((BaseUnityPlugin)this).Config.Bind<int>("General", "ConflictWindowMs", 250, new ConfigDescription("Maximum time between alternating Crypt/SunkenCrypt requests for them to count as the broken ping-pong pattern.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(50, 2000), new object[0]));
		ReleaseDelayMs = ((BaseUnityPlugin)this).Config.Bind<int>("General", "ReleaseDelayMs", 750, new ConfigDescription("While guarded, allow a real Crypt transition once no SunkenCrypt request has been seen for this long.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(100, 5000), new object[0]));
		DiagnosticLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DiagnosticLogging", true, "Log one message when a conflict is detected and one summary when the guard releases.");
		MethodInfo methodInfo = FindSetForceEnvironment();
		if (methodInfo == null)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)"Could not find EnvMan.SetForceEnvironment(string). No patches were applied.");
			return;
		}
		MethodInfo methodInfo2 = AccessTools.Method(typeof(EnvironmentGuardPatch), "Prefix", (Type[])null, (Type[])null);
		if (methodInfo2 == null)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)"Internal prefix method was not found. No patches were applied.");
			return;
		}
		harmony = new Harmony("daishi11.valheim.dungeonenvironmentguard");
		harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		((BaseUnityPlugin)this).Logger.LogInfo((object)("Dungeon Environment Guard active. Target: " + methodInfo.DeclaringType.FullName + "." + methodInfo.Name + "; conflict window=" + ConflictWindowMs.Value + " ms; release delay=" + ReleaseDelayMs.Value + " ms."));
	}

	private void OnDestroy()
	{
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
	}

	private static MethodInfo FindSetForceEnvironment()
	{
		MethodInfo[] methods = typeof(EnvMan).GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		foreach (MethodInfo methodInfo in methods)
		{
			if (!(methodInfo.Name != "SetForceEnvironment"))
			{
				ParameterInfo[] parameters = methodInfo.GetParameters();
				if (parameters.Length == 1 && parameters[0].ParameterType == typeof(string))
				{
					return methodInfo;
				}
			}
		}
		return null;
	}
}
internal static class EnvironmentGuardPatch
{
	private const string Crypt = "Crypt";

	private const string SunkenCrypt = "SunkenCrypt";

	private static string lastDungeonRequest;

	private static float lastDungeonRequestAt = -1000f;

	private static float lastSunkenCryptAt = -1000f;

	private static int rapidAlternations;

	private static bool guardActive;

	private static int suppressedRequests;

	private static float guardStartedAt;

	public static bool Prefix(object[] __args)
	{
		try
		{
			if (Plugin.Enabled == null || !Plugin.Enabled.Value)
			{
				ResetState(logRelease: false);
				return true;
			}
			if (__args == null || __args.Length != 1)
			{
				return true;
			}
			string text = __args[0] as string;
			if (text == null)
			{
				text = string.Empty;
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			bool flag = text == "Crypt";
			bool flag2 = text == "SunkenCrypt";
			if (guardActive)
			{
				if (string.IsNullOrEmpty(text) || (!flag && !flag2))
				{
					ReleaseGuard("forced environment changed to '" + text + "'", realtimeSinceStartup);
					TrackNormalRequest(text, realtimeSinceStartup);
					return true;
				}
				if (flag2)
				{
					lastSunkenCryptAt = realtimeSinceStartup;
					suppressedRequests++;
					return false;
				}
				float num = (float)Plugin.ReleaseDelayMs.Value / 1000f;
				if (realtimeSinceStartup - lastSunkenCryptAt >= num)
				{
					ReleaseGuard("SunkenCrypt requests stopped for " + Plugin.ReleaseDelayMs.Value + " ms", realtimeSinceStartup);
					TrackNormalRequest(text, realtimeSinceStartup);
					return true;
				}
				suppressedRequests++;
				return false;
			}
			if (!flag && !flag2)
			{
				TrackNormalRequest(text, realtimeSinceStartup);
				return true;
			}
			if (flag2)
			{
				lastSunkenCryptAt = realtimeSinceStartup;
			}
			float num2 = (float)Plugin.ConflictWindowMs.Value / 1000f;
			bool flag3 = lastDungeonRequest != null && lastDungeonRequest != text;
			if (flag3 && realtimeSinceStartup - lastDungeonRequestAt <= num2)
			{
				rapidAlternations++;
			}
			else if (flag3)
			{
				rapidAlternations = 1;
			}
			else if (lastDungeonRequest == null)
			{
				rapidAlternations = 0;
			}
			lastDungeonRequest = text;
			lastDungeonRequestAt = realtimeSinceStartup;
			if (rapidAlternations >= 2)
			{
				guardActive = true;
				guardStartedAt = realtimeSinceStartup;
				suppressedRequests = 0;
				if (Plugin.DiagnosticLogging.Value)
				{
					Plugin.Log.LogWarning((object)"Detected rapid Crypt <-> SunkenCrypt forced-environment conflict. Locking the effective environment to SunkenCrypt until the conflict clears.");
				}
				if (flag2)
				{
					return true;
				}
				suppressedRequests++;
				return false;
			}
			return true;
		}
		catch (Exception ex)
		{
			if (Plugin.Log != null)
			{
				Plugin.Log.LogError((object)("Dungeon Environment Guard failed open: " + ex));
			}
			return true;
		}
	}

	private static void TrackNormalRequest(string requested, float now)
	{
		if (requested == "Crypt" || requested == "SunkenCrypt")
		{
			lastDungeonRequest = requested;
			lastDungeonRequestAt = now;
			if (requested == "SunkenCrypt")
			{
				lastSunkenCryptAt = now;
			}
		}
		else
		{
			lastDungeonRequest = null;
			lastDungeonRequestAt = -1000f;
			lastSunkenCryptAt = -1000f;
			rapidAlternations = 0;
		}
	}

	private static void ReleaseGuard(string reason, float now)
	{
		if (guardActive && Plugin.DiagnosticLogging.Value)
		{
			Plugin.Log.LogInfo((object)("Dungeon environment guard released after " + (now - guardStartedAt).ToString("0.00") + " s; suppressed " + suppressedRequests + " forced-environment requests; reason: " + reason + "."));
		}
		guardActive = false;
		suppressedRequests = 0;
		guardStartedAt = 0f;
		rapidAlternations = 0;
	}

	private static void ResetState(bool logRelease)
	{
		if (guardActive && logRelease && Plugin.DiagnosticLogging != null && Plugin.DiagnosticLogging.Value)
		{
			Plugin.Log.LogInfo((object)"Dungeon environment guard reset.");
		}
		lastDungeonRequest = null;
		lastDungeonRequestAt = -1000f;
		lastSunkenCryptAt = -1000f;
		rapidAlternations = 0;
		guardActive = false;
		suppressedRequests = 0;
		guardStartedAt = 0f;
	}
}