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 cfgEnabled; internal static ConfigEntry cfgServerSync; internal static ConfigEntry cfgMaxChaseDistance; internal static ConfigEntry cfgGiveUpTime; internal static ConfigEntry cfgViewRangeMult; internal static ConfigEntry cfgHearRangeMult; internal static ConfigEntry cfgExcludeBosses; internal static ConfigEntry cfgExcludeTamed; internal static ConfigEntry 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 sExcluded = new HashSet(StringComparer.OrdinalIgnoreCase); private static readonly List LockAttributes = new List(); private static HashSet sLocalExcludedCache; private static string sLocalExcludedRaw; private static readonly Dictionary Originals = new Dictionary(); private static readonly ConditionalWeakTable LastApplied = new ConditionalWeakTable(); 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 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 ParseList(string raw) { if (sLocalExcludedCache != null && sLocalExcludedRaw == raw) { return sLocalExcludedCache; } HashSet hashSet = new HashSet(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("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("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(-1f, 2000f))); cfgGiveUpTime = ((BaseUnityPlugin)this).Config.Bind("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(-1f, 3600f))); cfgViewRangeMult = ((BaseUnityPlugin)this).Config.Bind("3 - Wahrnehmung", "ViewRangeMultiplier", 1f, Desc("Multiplikator für die Sichtweite aller Mobs (1 = Vanilla).", (AcceptableValueBase)(object)new AcceptableValueRange(0.05f, 20f))); cfgHearRangeMult = ((BaseUnityPlugin)this).Config.Bind("3 - Wahrnehmung", "HearRangeMultiplier", 1f, Desc("Multiplikator für die Hörweite aller Mobs (1 = Vanilla).", (AcceptableValueBase)(object)new AcceptableValueRange(0.05f, 20f))); cfgExcludeBosses = ((BaseUnityPlugin)this).Config.Bind("4 - Ausnahmen", "ExcludeBosses", true, Desc("Bosse (Eikthyr, Elder, Bonemass, Moder, Yagluth, Queen, Fader) nicht verändern.", null)); cfgExcludeTamed = ((BaseUnityPlugin)this).Config.Bind("4 - Ausnahmen", "ExcludeTamed", true, Desc("Gezähmte Tiere nicht verändern.", null)); cfgExcludedPrefabs = ((BaseUnityPlugin)this).Config.Bind("4 - Ausnahmen", "ExcludedPrefabs", "", Desc("Zusätzliche Prefab-Namen, kommagetrennt, die unverändert bleiben. Beispiel: Greydwarf,Neck,Boar", null)); cfgServerSync = ((BaseUnityPlugin)this).Config.Bind("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 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(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 hashSet = new HashSet(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(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(); 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 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 Transpiler(IEnumerable 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 list = new List(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("MobChaseControl_Cfg", (Action)MobChasePlugin.RPC_Config); __instance.Register("MobChaseControl_Set", (Action)MobChasePlugin.RPC_SetConfig); } }