Decompiled source of MobChaseControl v1.2.0

plugins/MobChaseControl.dll

Decompiled 15 hours ago
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace MobChaseControl;

[BepInPlugin("flo.mobchasecontrol", "MobChaseControl", "1.2.0")]
public class MobChasePlugin : BaseUnityPlugin
{
	internal class ConfigurationManagerAttributes
	{
		public bool? ReadOnly;
	}

	private struct Orig
	{
		public float view;

		public float hear;

		public float chase;
	}

	public const string GUID = "flo.mobchasecontrol";

	public const string NAME = "MobChaseControl";

	public const string VERSION = "1.2.0";

	internal const float VanillaGiveUpTime = 30f;

	private const string RpcName = "MobChaseControl_Cfg";

	internal const string RpcSet = "MobChaseControl_Set";

	private const float PushInterval = 2f;

	internal static ManualLogSource Log;

	internal static MobChasePlugin Instance;

	internal static ConfigEntry<bool> cfgEnabled;

	internal static ConfigEntry<bool> cfgServerSync;

	internal static ConfigEntry<float> cfgMaxChaseDistance;

	internal static ConfigEntry<float> cfgGiveUpTime;

	internal static ConfigEntry<float> cfgViewRangeMult;

	internal static ConfigEntry<float> cfgHearRangeMult;

	internal static ConfigEntry<bool> cfgExcludeBosses;

	internal static ConfigEntry<bool> cfgExcludeTamed;

	internal static ConfigEntry<string> cfgExcludedPrefabs;

	internal static bool ServerOverride;

	internal static bool IsAdminHere;

	private static bool applyingFromServer;

	private static bool sEnabled = true;

	private static float sMaxChase = -1f;

	private static float sGiveUp = -1f;

	private static float sView = 1f;

	private static float sHear = 1f;

	private static bool sExclBoss = true;

	private static bool sExclTamed = true;

	private static HashSet<string> sExcluded = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

	private static readonly List<ConfigurationManagerAttributes> LockAttributes = new List<ConfigurationManagerAttributes>();

	private static HashSet<string> sLocalExcludedCache;

	private static string sLocalExcludedRaw;

	private static readonly Dictionary<string, Orig> Originals = new Dictionary<string, Orig>();

	private static readonly ConditionalWeakTable<MonsterAI, float[]> LastApplied = new ConditionalWeakTable<MonsterAI, float[]>();

	private Harmony harmony;

	internal float pushTimer;

	private float checkTimer;

	private string lastSignature = "";

	private int lastPeerCount = -1;

	internal static bool EffEnabled
	{
		get
		{
			if (ServerOverride)
			{
				return sEnabled;
			}
			if (IsRemoteClient)
			{
				return false;
			}
			return cfgEnabled.Value;
		}
	}

	internal static float EffMaxChase => (!ServerOverride) ? cfgMaxChaseDistance.Value : sMaxChase;

	internal static float EffGiveUp => (!ServerOverride) ? cfgGiveUpTime.Value : sGiveUp;

	internal static float EffView => (!ServerOverride) ? cfgViewRangeMult.Value : sView;

	internal static float EffHear => (!ServerOverride) ? cfgHearRangeMult.Value : sHear;

	internal static bool EffExclBoss => (!ServerOverride) ? cfgExcludeBosses.Value : sExclBoss;

	internal static bool EffExclTamed => (!ServerOverride) ? cfgExcludeTamed.Value : sExclTamed;

	internal static HashSet<string> EffExcluded => (!ServerOverride) ? ParseList(cfgExcludedPrefabs.Value) : sExcluded;

	internal static bool IsRemoteClient => (Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer();

	private static ConfigDescription Desc(string text, AcceptableValueBase range)
	{
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_004d: Expected O, but got Unknown
		ConfigurationManagerAttributes configurationManagerAttributes = new ConfigurationManagerAttributes();
		configurationManagerAttributes.ReadOnly = false;
		ConfigurationManagerAttributes configurationManagerAttributes2 = configurationManagerAttributes;
		LockAttributes.Add(configurationManagerAttributes2);
		return (range != null) ? new ConfigDescription(text, range, new object[1] { configurationManagerAttributes2 }) : new ConfigDescription(text, (AcceptableValueBase)null, new object[1] { configurationManagerAttributes2 });
	}

	private static void SetLocked(bool locked)
	{
		for (int i = 0; i < LockAttributes.Count; i++)
		{
			LockAttributes[i].ReadOnly = locked;
		}
	}

	private static HashSet<string> ParseList(string raw)
	{
		if (sLocalExcludedCache != null && sLocalExcludedRaw == raw)
		{
			return sLocalExcludedCache;
		}
		HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
		if (!string.IsNullOrEmpty(raw))
		{
			string[] array = raw.Split(',');
			foreach (string text in array)
			{
				string text2 = text.Trim();
				if (text2.Length > 0)
				{
					hashSet.Add(text2);
				}
			}
		}
		sLocalExcludedRaw = raw;
		sLocalExcludedCache = hashSet;
		return hashSet;
	}

	private static void Remember(MonsterAI ai)
	{
		LastApplied.Remove(ai);
		LastApplied.Add(ai, new float[3]
		{
			((BaseAI)ai).m_viewRange,
			((BaseAI)ai).m_hearRange,
			ai.m_maxChaseDistance
		});
	}

	private static bool ForeignChange(MonsterAI ai)
	{
		if (!LastApplied.TryGetValue(ai, out var value))
		{
			return false;
		}
		return Math.Abs(value[0] - ((BaseAI)ai).m_viewRange) > 0.001f || Math.Abs(value[1] - ((BaseAI)ai).m_hearRange) > 0.001f || Math.Abs(value[2] - ai.m_maxChaseDistance) > 0.001f;
	}

	private void Awake()
	{
		//IL_01ca: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d4: Expected O, but got Unknown
		Instance = this;
		Log = ((BaseUnityPlugin)this).Logger;
		cfgEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - Allgemein", "Enabled", true, Desc("Mod aktiv. false = alles bleibt Vanilla.\nAuf Clients ohne Wirkung - dort gilt immer die Server-Einstellung.", null));
		cfgMaxChaseDistance = ((BaseUnityPlugin)this).Config.Bind<float>("2 - Verfolgung", "MaxChaseDistance", 60f, Desc("Maximale Verfolgungsdistanz in Metern. Ab dieser Entfernung verliert ein Mob sein Ziel.\n-1 = Vanilla-Wert des jeweiligen Mobs unverändert lassen\n 0 = unbegrenzt (Mob gibt nie wegen Entfernung auf)\n>0 = Meter (greift erst, wenn der Mob das Ziel >1s nicht mehr wahrgenommen hat)", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-1f, 2000f)));
		cfgGiveUpTime = ((BaseUnityPlugin)this).Config.Bind<float>("2 - Verfolgung", "GiveUpTime", 30f, Desc("Sekunden ohne Wahrnehmung des Ziels, bis der Mob aufgibt (Vanilla: 30).\n-1 = unverändert lassen.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-1f, 3600f)));
		cfgViewRangeMult = ((BaseUnityPlugin)this).Config.Bind<float>("3 - Wahrnehmung", "ViewRangeMultiplier", 1f, Desc("Multiplikator für die Sichtweite aller Mobs (1 = Vanilla).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 20f)));
		cfgHearRangeMult = ((BaseUnityPlugin)this).Config.Bind<float>("3 - Wahrnehmung", "HearRangeMultiplier", 1f, Desc("Multiplikator für die Hörweite aller Mobs (1 = Vanilla).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 20f)));
		cfgExcludeBosses = ((BaseUnityPlugin)this).Config.Bind<bool>("4 - Ausnahmen", "ExcludeBosses", true, Desc("Bosse (Eikthyr, Elder, Bonemass, Moder, Yagluth, Queen, Fader) nicht verändern.", null));
		cfgExcludeTamed = ((BaseUnityPlugin)this).Config.Bind<bool>("4 - Ausnahmen", "ExcludeTamed", true, Desc("Gezähmte Tiere nicht verändern.", null));
		cfgExcludedPrefabs = ((BaseUnityPlugin)this).Config.Bind<string>("4 - Ausnahmen", "ExcludedPrefabs", "", Desc("Zusätzliche Prefab-Namen, kommagetrennt, die unverändert bleiben. Beispiel: Greydwarf,Neck,Boar", null));
		cfgServerSync = ((BaseUnityPlugin)this).Config.Bind<bool>("5 - Server", "ServerSync", true, "Nur auf dem Server relevant: true = der Server erzwingt seine Werte bei allen Clients.\nAuf Clients hat diese Option keine Wirkung.");
		((BaseUnityPlugin)this).Config.SettingChanged += OnLocalSettingChanged;
		harmony = new Harmony("flo.mobchasecontrol");
		harmony.PatchAll(Assembly.GetExecutingAssembly());
		Log.LogInfo((object)"MobChaseControl 1.2.0 geladen.");
	}

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

	private void Update()
	{
		//IL_015f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0166: Expected O, but got Unknown
		if ((Object)(object)ZNet.instance == (Object)null)
		{
			if (ServerOverride)
			{
				ServerOverride = false;
				IsAdminHere = false;
				SetLocked(locked: false);
			}
			return;
		}
		checkTimer -= Time.deltaTime;
		if (checkTimer <= 0f)
		{
			checkTimer = 1f;
			string text = BuildSignature();
			if (text != lastSignature)
			{
				lastSignature = text;
				ApplyToAll();
			}
		}
		if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || !cfgServerSync.Value || ZRoutedRpc.instance == null)
		{
			return;
		}
		int count = ZNet.instance.GetPeers().Count;
		pushTimer -= Time.deltaTime;
		if (pushTimer > 0f && count == lastPeerCount)
		{
			return;
		}
		lastPeerCount = count;
		pushTimer = 2f;
		try
		{
			string text2 = Serialize();
			List<ZNetPeer> peers = ZNet.instance.GetPeers();
			for (int i = 0; i < peers.Count; i++)
			{
				ZNetPeer val = peers[i];
				if (val != null)
				{
					bool flag = val.m_socket != null && ZNet.instance.IsAdmin(val.m_socket.GetHostName());
					ZPackage val2 = new ZPackage();
					val2.Write(text2 + "|" + ((!flag) ? "0" : "1"));
					ZRoutedRpc.instance.InvokeRoutedRPC(val.m_uid, "MobChaseControl_Cfg", new object[1] { val2 });
				}
			}
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("Config-Push fehlgeschlagen: " + ex.Message));
		}
	}

	private static string BuildSignature()
	{
		return string.Join("|", (!EffEnabled) ? "0" : "1", EffMaxChase.ToString(CultureInfo.InvariantCulture), EffGiveUp.ToString(CultureInfo.InvariantCulture), EffView.ToString(CultureInfo.InvariantCulture), EffHear.ToString(CultureInfo.InvariantCulture), (!EffExclBoss) ? "0" : "1", (!EffExclTamed) ? "0" : "1", string.Join(",", new List<string>(EffExcluded).ToArray()));
	}

	private static string Serialize()
	{
		return "1;" + BuildSignature();
	}

	internal static void RPC_Config(long sender, ZPackage pkg)
	{
		try
		{
			if (((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) || pkg == null)
			{
				return;
			}
			string text = pkg.ReadString();
			if (string.IsNullOrEmpty(text))
			{
				return;
			}
			int num = text.IndexOf(';');
			if (num < 0)
			{
				return;
			}
			string[] array = text.Substring(num + 1).Split('|');
			if (array.Length < 8)
			{
				return;
			}
			bool flag = array[0] == "1";
			float num2 = float.Parse(array[1], CultureInfo.InvariantCulture);
			float num3 = float.Parse(array[2], CultureInfo.InvariantCulture);
			float num4 = float.Parse(array[3], CultureInfo.InvariantCulture);
			float num5 = float.Parse(array[4], CultureInfo.InvariantCulture);
			bool flag2 = array[5] == "1";
			bool flag3 = array[6] == "1";
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			string[] array2 = array[7].Split(',');
			foreach (string text2 in array2)
			{
				string text3 = text2.Trim();
				if (text3.Length > 0)
				{
					hashSet.Add(text3);
				}
			}
			bool flag4 = !ServerOverride || flag != sEnabled || num2 != sMaxChase || num3 != sGiveUp || num4 != sView || num5 != sHear || flag2 != sExclBoss || flag3 != sExclTamed || !hashSet.SetEquals(sExcluded);
			sEnabled = flag;
			sMaxChase = num2;
			sGiveUp = num3;
			sView = num4;
			sHear = num5;
			sExclBoss = flag2;
			sExclTamed = flag3;
			sExcluded = hashSet;
			ServerOverride = true;
			IsAdminHere = array.Length > 8 && array[8] == "1";
			SetLocked(!IsAdminHere);
			if (IsAdminHere)
			{
				MirrorToConfig();
			}
			if (flag4)
			{
				Log.LogInfo((object)("Server-Konfiguration übernommen: Distanz=" + num2 + "m, GiveUp=" + num3 + "s, View x" + num4 + ", Hear x" + num5));
				ApplyToAll();
			}
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("Server-Config konnte nicht gelesen werden: " + ex.Message));
		}
	}

	private static string ConfigString()
	{
		return string.Join("|", (!cfgEnabled.Value) ? "0" : "1", cfgMaxChaseDistance.Value.ToString(CultureInfo.InvariantCulture), cfgGiveUpTime.Value.ToString(CultureInfo.InvariantCulture), cfgViewRangeMult.Value.ToString(CultureInfo.InvariantCulture), cfgHearRangeMult.Value.ToString(CultureInfo.InvariantCulture), (!cfgExcludeBosses.Value) ? "0" : "1", (!cfgExcludeTamed.Value) ? "0" : "1", cfgExcludedPrefabs.Value.Replace('|', ','));
	}

	private static void MirrorToConfig()
	{
		applyingFromServer = true;
		try
		{
			cfgEnabled.Value = sEnabled;
			cfgMaxChaseDistance.Value = sMaxChase;
			cfgGiveUpTime.Value = sGiveUp;
			cfgViewRangeMult.Value = sView;
			cfgHearRangeMult.Value = sHear;
			cfgExcludeBosses.Value = sExclBoss;
			cfgExcludeTamed.Value = sExclTamed;
			cfgExcludedPrefabs.Value = string.Join(",", new List<string>(sExcluded).ToArray());
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("Spiegeln: " + ex.Message));
		}
		finally
		{
			applyingFromServer = false;
		}
	}

	private void OnLocalSettingChanged(object sender, SettingChangedEventArgs e)
	{
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Expected O, but got Unknown
		if (applyingFromServer || !IsRemoteClient || !IsAdminHere)
		{
			return;
		}
		try
		{
			if (ZRoutedRpc.instance != null)
			{
				ZPackage val = new ZPackage();
				val.Write(ConfigString());
				ZRoutedRpc.instance.InvokeRoutedRPC("MobChaseControl_Set", new object[1] { val });
			}
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("Senden an Server fehlgeschlagen: " + ex.Message));
		}
	}

	private static bool SenderIsAdmin(long sender)
	{
		ZNet instance = ZNet.instance;
		if ((Object)(object)instance == (Object)null)
		{
			return false;
		}
		ZNetPeer peer = instance.GetPeer(sender);
		if (peer == null || peer.m_socket == null)
		{
			return false;
		}
		return instance.IsAdmin(peer.m_socket.GetHostName());
	}

	internal static void RPC_SetConfig(long sender, ZPackage pkg)
	{
		try
		{
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || pkg == null)
			{
				return;
			}
			if (!SenderIsAdmin(sender))
			{
				Log.LogWarning((object)("Konfigurationsänderung von Nicht-Admin abgelehnt (Peer " + sender + ")."));
				return;
			}
			string[] array = pkg.ReadString().Split('|');
			if (array.Length >= 8)
			{
				applyingFromServer = true;
				try
				{
					cfgEnabled.Value = array[0] == "1";
					cfgMaxChaseDistance.Value = float.Parse(array[1], CultureInfo.InvariantCulture);
					cfgGiveUpTime.Value = float.Parse(array[2], CultureInfo.InvariantCulture);
					cfgViewRangeMult.Value = float.Parse(array[3], CultureInfo.InvariantCulture);
					cfgHearRangeMult.Value = float.Parse(array[4], CultureInfo.InvariantCulture);
					cfgExcludeBosses.Value = array[5] == "1";
					cfgExcludeTamed.Value = array[6] == "1";
					cfgExcludedPrefabs.Value = array[7];
				}
				finally
				{
					applyingFromServer = false;
				}
				Log.LogInfo((object)("Konfiguration durch Admin geändert (Peer " + sender + ")."));
				ApplyToAll();
				if ((Object)(object)Instance != (Object)null)
				{
					Instance.pushTimer = 0f;
				}
			}
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("Adminänderung konnte nicht übernommen werden: " + ex.Message));
		}
	}

	internal static string PrefabName(Component c)
	{
		if ((Object)(object)c == (Object)null)
		{
			return "";
		}
		string name = ((Object)c.gameObject).name;
		int num = name.IndexOf("(Clone)");
		return (num < 0) ? name : name.Substring(0, num);
	}

	internal static bool IsExcluded(MonsterAI ai, string prefab)
	{
		if (EffExcluded.Contains(prefab))
		{
			return true;
		}
		Character component = ((Component)ai).GetComponent<Character>();
		if ((Object)(object)component != (Object)null)
		{
			if (EffExclBoss && component.IsBoss())
			{
				return true;
			}
			if (EffExclTamed && component.IsTamed())
			{
				return true;
			}
		}
		return false;
	}

	internal static void Apply(MonsterAI ai)
	{
		if (!((Object)(object)ai == (Object)null))
		{
			string text = PrefabName((Component)(object)ai);
			if (!Originals.TryGetValue(text, out var value))
			{
				value = new Orig
				{
					view = ((BaseAI)ai).m_viewRange,
					hear = ((BaseAI)ai).m_hearRange,
					chase = ai.m_maxChaseDistance
				};
				Originals[text] = value;
			}
			if (!EffEnabled || IsExcluded(ai, text))
			{
				((BaseAI)ai).m_viewRange = value.view;
				((BaseAI)ai).m_hearRange = value.hear;
				ai.m_maxChaseDistance = value.chase;
				Remember(ai);
			}
			else
			{
				((BaseAI)ai).m_viewRange = value.view * EffView;
				((BaseAI)ai).m_hearRange = value.hear * EffHear;
				float effMaxChase = EffMaxChase;
				ai.m_maxChaseDistance = ((!(effMaxChase < 0f)) ? effMaxChase : value.chase);
				Remember(ai);
			}
		}
	}

	internal static void ApplyToAll()
	{
		try
		{
			List<BaseAI> baseAIInstances = BaseAI.BaseAIInstances;
			if (baseAIInstances == null)
			{
				return;
			}
			for (int i = 0; i < baseAIInstances.Count; i++)
			{
				BaseAI obj = baseAIInstances[i];
				MonsterAI val = (MonsterAI)(object)((obj is MonsterAI) ? obj : null);
				if (!((Object)(object)val == (Object)null) && !ForeignChange(val))
				{
					Apply(val);
				}
			}
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("ApplyToAll: " + ex.Message));
		}
	}

	public static float GiveUpTimeFor(MonsterAI ai)
	{
		if (!EffEnabled)
		{
			return 30f;
		}
		float effGiveUp = EffGiveUp;
		if (effGiveUp < 0f)
		{
			return 30f;
		}
		if ((Object)(object)ai != (Object)null && IsExcluded(ai, PrefabName((Component)(object)ai)))
		{
			return 30f;
		}
		return effGiveUp;
	}
}
[HarmonyPatch(typeof(MonsterAI), "Awake")]
internal static class Patch_MonsterAI_Awake
{
	private static void Postfix(MonsterAI __instance)
	{
		MobChasePlugin.Apply(__instance);
	}
}
[HarmonyPatch(typeof(MonsterAI), "MakeTame")]
internal static class Patch_MonsterAI_MakeTame
{
	private static void Postfix(MonsterAI __instance)
	{
		MobChasePlugin.Apply(__instance);
	}
}
[HarmonyPatch(typeof(MonsterAI), "UpdateTarget")]
internal static class Patch_MonsterAI_UpdateTarget
{
	private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
	{
		//IL_013c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0146: Expected O, but got Unknown
		FieldInfo fieldInfo = AccessTools.Field(typeof(MonsterAI), "m_timeSinceSensedTargetCreature");
		MethodInfo methodInfo = AccessTools.Method(typeof(MobChasePlugin), "GiveUpTimeFor", (Type[])null, (Type[])null);
		List<CodeInstruction> list = new List<CodeInstruction>(instructions);
		bool flag = false;
		for (int i = 1; i < list.Count; i++)
		{
			if (flag)
			{
				break;
			}
			if (!(list[i].opcode != OpCodes.Ldc_R4) && list[i].operand is float && !(Math.Abs((float)list[i].operand - 30f) > 0.001f) && !(list[i - 1].opcode != OpCodes.Ldfld) && list[i - 1].operand is FieldInfo && !((FieldInfo)list[i - 1].operand != fieldInfo))
			{
				list[i].opcode = OpCodes.Ldarg_0;
				list[i].operand = null;
				list.Insert(i + 1, new CodeInstruction(OpCodes.Call, (object)methodInfo));
				flag = true;
			}
		}
		if (!flag)
		{
			MobChasePlugin.Log.LogError((object)"Transpiler: GiveUpTime-Konstante in MonsterAI.UpdateTarget nicht gefunden - GiveUpTime bleibt bei 30s (Spielversion geändert?).");
		}
		else
		{
			MobChasePlugin.Log.LogInfo((object)"Transpiler: GiveUpTime patchbar.");
		}
		return list;
	}
}
[HarmonyPatch(/*Could not decode attribute arguments.*/)]
internal static class Patch_ZRoutedRpc_Ctor
{
	private static void Postfix(ZRoutedRpc __instance)
	{
		__instance.Register<ZPackage>("MobChaseControl_Cfg", (Action<long, ZPackage>)MobChasePlugin.RPC_Config);
		__instance.Register<ZPackage>("MobChaseControl_Set", (Action<long, ZPackage>)MobChasePlugin.RPC_SetConfig);
	}
}