Please disclose if your mod was created primarily using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of BetterTames v0.0.8
plugins/BetterTames.dll
Decompiled 4 months ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BetterTames.ConfigSynchronization; using BetterTames.DistanceTeleport; using BetterTames.MakeCommandable; using BetterTames.PetProtection; using BetterTames.Utils; using HarmonyLib; using JetBrains.Annotations; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using ServerSync; using TMPro; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("Valheim.BetterTames")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Atlas Playbook v0.4.1")] [assembly: AssemblyProduct("Valheim.BetterTames")] [assembly: AssemblyCopyright("Copyright © Atlas Playbook v0.4.1 2025")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("c7a9c4aa-0b32-47f7-a590-a68e94a2511e")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8.1", FrameworkDisplayName = ".NET Framework 4.8.1")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace BetterTames { public static class DistanceTeleportLogic { private static readonly int groundLayerMask = LayerMask.GetMask(new string[7] { "Default", "static_solid", "Default_small", "piece", "terrain", "blocker", "vehicle" }); public const float TELEPORT_CHECK_INTERVAL = 5f; public static void ExecuteTeleportBehindPlayer(Character characterToTeleport, GameObject followTarget) { //IL_0015: 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_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0043: 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_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_0213: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_0222: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_0263: Unknown result type (might be due to invalid IL or missing references) //IL_0248: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_02a9: Unknown result type (might be due to invalid IL or missing references) //IL_02ae: Unknown result type (might be due to invalid IL or missing references) //IL_02b3: Unknown result type (might be due to invalid IL or missing references) //IL_02a0: Unknown result type (might be due to invalid IL or missing references) //IL_02a1: Unknown result type (might be due to invalid IL or missing references) //IL_02b8: Unknown result type (might be due to invalid IL or missing references) //IL_02bd: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_02d3: Unknown result type (might be due to invalid IL or missing references) //IL_02dc: Unknown result type (might be due to invalid IL or missing references) //IL_02e5: Unknown result type (might be due to invalid IL or missing references) //IL_032b: Unknown result type (might be due to invalid IL or missing references) //IL_033e: Unknown result type (might be due to invalid IL or missing references) //IL_0345: Expected O, but got Unknown //IL_0347: Unknown result type (might be due to invalid IL or missing references) //IL_0353: Unknown result type (might be due to invalid IL or missing references) //IL_038a: Unknown result type (might be due to invalid IL or missing references) //IL_0391: Expected O, but got Unknown //IL_0394: Unknown result type (might be due to invalid IL or missing references) //IL_03a1: Unknown result type (might be due to invalid IL or missing references) //IL_03ab: Unknown result type (might be due to invalid IL or missing references) ZNetView component = ((Component)characterToTeleport).GetComponent<ZNetView>(); ZDO zDO = component.GetZDO(); Vector3 position = followTarget.transform.position; Quaternion rotation = followTarget.transform.rotation; BetterTamesPlugin.LogIfDebug("Attempting teleport for " + characterToTeleport.m_name + ".", DebugFeature.TeleportFollow); if (position.y > 1000f) { BetterTamesPlugin.LogIfDebug($"Player Y position {position.y:F1} is > 1000. Preventing pet teleport.", DebugFeature.TeleportFollow); return; } Vector3 val = rotation * Vector3.forward; Vector3 val2 = rotation * Vector3.right; float num = 1f; CapsuleCollider component2 = ((Component)characterToTeleport).GetComponent<CapsuleCollider>(); if ((Object)(object)component2 != (Object)null) { num = component2.radius * Mathf.Max(((Component)characterToTeleport).transform.localScale.x, ((Component)characterToTeleport).transform.localScale.z); } else { Collider component3 = ((Component)characterToTeleport).GetComponent<Collider>(); if ((Object)(object)component3 != (Object)null) { Bounds bounds = component3.bounds; float x = ((Bounds)(ref bounds)).extents.x; bounds = component3.bounds; num = Mathf.Max(x, ((Bounds)(ref bounds)).extents.z); } } float num2 = Mathf.Max(8f, num + 0.5f); float num3 = num2 + 3f; float num4 = Mathf.Max(10f, num * 1.5f); float num5 = Random.Range(num2, num3); float num6 = Random.Range((0f - num4) / 2f, num4 / 2f); try { if (zDO != null && zDO.GetBool("isRecoveringFromStun", false)) { BetterTamesPlugin.LogIfDebug("Pet " + characterToTeleport.m_name + " is in pet-protection stun phase — increasing teleport distance by 30f.", DebugFeature.TeleportFollow); num5 += 30f; } } catch (Exception arg) { BetterTamesPlugin.LogIfDebug($"Exception while checking stun flag for {characterToTeleport.m_name}: {arg}", DebugFeature.TeleportFollow); } Vector3 val3 = -val * num5; Vector3 val4 = val2 * num6; Vector3 val5 = position + val3 + val4; RaycastHit val6 = default(RaycastHit); if (Physics.Raycast(val5 + Vector3.up * 5f, Vector3.down, ref val6, 10f, groundLayerMask)) { val5.y = ((RaycastHit)(ref val6)).point.y + 1f; } else { val5.y = position.y; BetterTamesPlugin.LogIfDebug("No ground found via Raycast for auto-teleport of " + characterToTeleport.m_name + ", using target Y position.", DebugFeature.TeleportFollow); } Quaternion val7 = ((!(((Vector3)(ref val)).sqrMagnitude < 1E-06f)) ? Quaternion.LookRotation(((Vector3)(ref val)).normalized) : rotation); val7 = ((Quaternion)(ref val7)).normalized; ((Component)characterToTeleport).transform.position = val5; ((Component)characterToTeleport).transform.rotation = val7; zDO.SetPosition(val5); zDO.SetRotation(val7); Rigidbody component4 = ((Component)characterToTeleport).GetComponent<Rigidbody>(); if ((Object)(object)component4 != (Object)null) { component4.WakeUp(); } if (!component.IsOwner()) { BetterTamesPlugin.LogIfDebug($"Teleported {characterToTeleport.m_name} to {val5} (behind followTarget). Sending RPC.", DebugFeature.TeleportFollow); ZPackage val8 = new ZPackage(); val8.Write(val5); val8.Write(((Quaternion)(ref val7)).normalized); string text = $"{((ZDOID)(ref zDO.m_uid)).UserID}:{((ZDOID)(ref zDO.m_uid)).ID}"; ZPackage val9 = new ZPackage(); val9.Write(zDO.m_uid); val9.Write(val5); val9.Write(val7); RPCManager.TeleportSyncRPC.SendPackage(zDO.GetOwner(), val9); } } public static bool CheckDistanceAndTeleport(Character tame) { //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) BetterTames.ConfigSynchronization.ConfigSync configInstance = BetterTamesPlugin.ConfigInstance; if (configInstance == null || !(configInstance.Tames.TeleportFollowEnabled?.Value).GetValueOrDefault()) { return false; } if ((Object)(object)ZNet.instance == (Object)null) { return false; } ZNetView component = ((Component)tame).GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || !component.IsValid()) { return false; } ZDO zDO = component.GetZDO(); if (zDO == null) { return false; } if (!tame.IsTamed()) { return false; } GameObject followTarget = ((Component)tame).GetComponent<MonsterAI>().GetFollowTarget(); if ((Object)(object)followTarget == (Object)null) { return false; } Vector3 position = ((Component)tame).transform.position; Vector3 position2 = followTarget.transform.position; MonsterAI component2 = ((Component)tame).GetComponent<MonsterAI>(); Character targetCreature = ((BaseAI)component2).GetTargetCreature(); StaticTarget staticTarget = component2.GetStaticTarget(); bool flag = ((Object)(object)targetCreature != (Object)null && BaseAI.IsEnemy(tame, targetCreature)) || (Object)(object)staticTarget != (Object)null; if (Vector3.Distance(position, position2) < 5f && !flag) { ((BaseAI)component2).StopMoving(); } if (!component.IsOwner()) { return false; } float num = Mathf.Max((float)BetterTamesPlugin.ConfigInstance.Tames.TeleportOnDistanceMaxRange.Value, 10f); Vector3 val = position - position2; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; float num2 = num * num; if (sqrMagnitude <= num2) { BetterTamesPlugin.LogIfDebug($"Distance check for {tame.m_name}: {Mathf.Sqrt(sqrMagnitude):F1}m < {num}m threshold. No teleport.", DebugFeature.TeleportFollow); return false; } BetterTamesPlugin.LogIfDebug($"DistanceSqr {sqrMagnitude:F1} > {num2:F1}. Attempting teleport for {tame.m_name}.", DebugFeature.TeleportFollow); ExecuteTeleportBehindPlayer(tame, followTarget); return true; } public static List<Vector3> CalculateDistributedSpawnPositions(Vector3 playerPos, Quaternion playerRot, int petCount) { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: 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_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0077: 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: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) List<Vector3> list = new List<Vector3>(); if (petCount == 0) { return list; } float num = 3f; float num2 = 120f; float num3 = 0.2f; float num5 = default(float); for (int i = 0; i < petCount; i++) { float num4 = ((petCount > 1) ? ((0f - num2) / 2f + (float)i * (num2 / (float)(petCount - 1))) : 0f); Vector3 val = Quaternion.Euler(0f, num4, 0f) * (playerRot * Vector3.back); Vector3 val2 = playerPos + val * num; if (ZoneSystem.instance.FindFloor(val2 + Vector3.up * 2f, ref num5)) { val2.y = num5 + num3; } list.Add(val2); } return list; } } [HarmonyPatch] internal static class InitializationPatches { private static bool _zNetReadyCalled; private static bool _localPlayerReadyCalled; public static void ResetInitializationFlags() { BetterTamesPlugin.LogIfDebug("Resetting initialization flags."); _zNetReadyCalled = false; _localPlayerReadyCalled = false; } [HarmonyPatch(typeof(ZNet), "Awake")] [HarmonyPostfix] private static void ZNet_Awake_Postfix(ZNet __instance) { if (_zNetReadyCalled || (Object)(object)ZNet.instance != (Object)(object)__instance) { return; } try { _zNetReadyCalled = true; } catch (Exception arg) { BetterTamesPlugin.LogIfDebug($"Error in OnZNetReady from ZNet_Awake_Postfix: {arg}"); } } [HarmonyPatch(typeof(Player), "SetLocalPlayer")] [HarmonyPostfix] private static void Player_SetLocalPlayer_Postfix() { if (_localPlayerReadyCalled || (Object)(object)Player.m_localPlayer == (Object)null) { return; } BetterTamesPlugin.LogIfDebug("Player.SetLocalPlayer postfix triggered for " + Player.m_localPlayer.GetPlayerName() + ". Calling OnLocalPlayerReady..."); try { BetterTamesPlugin.OnLocalPlayerReady(); _localPlayerReadyCalled = true; } catch (Exception arg) { BetterTamesPlugin.LogIfDebug($"Error in OnLocalPlayerReady from Player_SetLocalPlayer_Postfix: {arg}"); } } } public enum DebugFeature { MakeCommandable, TeleportFollow, PetProtection, Initialization } [BepInPlugin("Koro.bettertames", "BetterTames", "0.0.8")] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public class BetterTamesPlugin : BaseUnityPlugin { public const string PluginId = "Koro.bettertames"; public const string PluginName = "BetterTames"; public const string PluginVersion = "0.0.8"; public const string RPC_TELEPORT_SYNC = "BT_TeleportSync"; public const string RPC_REQUEST_MERCY_KILL = "BT_RequestMercyKill"; public const string RPC_NOTIFY_MERCY_KILL = "BetterTames_NotifyMercyKill"; public static ServerSync.ConfigSync _configSync; private readonly Harmony _harmony = new Harmony("Koro.bettertames"); private static bool _corePatchesAppliedSession; private Coroutine _petMonitorCoroutine; public static BetterTamesPlugin Instance { get; private set; } public static BetterTames.ConfigSynchronization.ConfigSync ConfigInstance { get; private set; } private void Awake() { Instance = this; _configSync = new ServerSync.ConfigSync("Koro.bettertames") { DisplayName = "BetterTames", CurrentVersion = "0.0.8", MinimumRequiredVersion = "0.0.8", ModRequired = true }; ConfigInstance = new BetterTames.ConfigSynchronization.ConfigSync(this); LogIfDebug("AWAKE: Config instances initialized."); ConfigInstance.Tames.PetProtectionExceptionPrefabs.SettingChanged += OnExceptionPrefabsSettingChanged; RPCManager.RegisterRPCs(); ApplyInitialPatches(); } private void OnDestroy() { LogIfDebug("OnDestroy called. Unpatching Harmony..."); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } try { if (_petMonitorCoroutine != null && (Object)(object)Player.m_localPlayer != (Object)null) { ((MonoBehaviour)Player.m_localPlayer).StopCoroutine(_petMonitorCoroutine); _petMonitorCoroutine = null; LogIfDebug("Stopped PlayerPetMonitor coroutine on destroy.", DebugFeature.TeleportFollow); } } catch (Exception arg) { ((BaseUnityPlugin)this).Logger.LogWarning((object)$"Failed stopping PlayerPetMonitor coroutine: {arg}"); } } public static void OnLocalPlayerReady() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) LogIfDebug("Local player is ready."); PetProtectionPatch.Initialize(); if (!_corePatchesAppliedSession) { ApplyCorePatches(); _corePatchesAppliedSession = true; } new GameObject("BT_TeleportMonitor").AddComponent<TeleportMonitorBehaviour>(); } private void ApplyInitialPatches() { try { LogIfDebug("Applying initial patches (Initialization & PetProtection)..."); _harmony.PatchAll(typeof(PetProtectionPatch)); _harmony.PatchAll(typeof(StunBehaviorPatches)); _harmony.PatchAll(typeof(InitializationPatches)); _harmony.PatchAll(typeof(EnemyHud_TestShow_Patch)); LogIfDebug("Initial patches applied."); } catch (Exception arg) { ((BaseUnityPlugin)this).Logger.LogError((object)$"CRITICAL ERROR applying initial patches: {arg}"); } } private static void ApplyCorePatches() { try { LogIfDebug("Applying core feature patches..."); Instance._harmony.PatchAll(typeof(MakeCommandablePatch)); Instance._harmony.PatchAll(typeof(Player_UpdateTeleport_Patch)); Instance._harmony.PatchAll(typeof(ButcherKnifePatch)); LogIfDebug("Core feature patches applied."); } catch (Exception arg) { ((BaseUnityPlugin)Instance).Logger.LogError((object)$"Exception during core patching: {arg}"); } } private void OnExceptionPrefabsSettingChanged(object sender, EventArgs e) { PetProtectionPatch.UpdateExceptionPrefabs(ConfigInstance.Tames.PetProtectionExceptionPrefabs.Value); } public static void LogIfDebug(string message, DebugFeature feature = DebugFeature.Initialization) { if (ConfigInstance != null && feature switch { DebugFeature.MakeCommandable => ConfigInstance.Tames.DebugMakeCommandable.Value, DebugFeature.TeleportFollow => ConfigInstance.Tames.DebugTeleportFollow.Value, DebugFeature.PetProtection => ConfigInstance.Tames.DebugPetProtection.Value, DebugFeature.Initialization => true, _ => false, }) { ((BaseUnityPlugin)Instance).Logger.LogInfo((object)$"[{feature}] {message}"); } } } } namespace BetterTames.Utils { public static class RPCManager { [CompilerGenerated] private sealed class <RPC_NotifyMercyKill_Client>d__4 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public long sender; public ZPackage pkg; private ZDOID <targetZDOID>5__1; private GameObject <go>5__2; private Character <character>5__3; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <RPC_NotifyMercyKill_Client>d__4(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <go>5__2 = null; <character>5__3 = null; <>1__state = -2; } private bool MoveNext() { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: 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) switch (<>1__state) { default: return false; case 0: <>1__state = -1; <targetZDOID>5__1 = pkg.ReadZDOID(); <go>5__2 = ZNetScene.instance.FindInstance(<targetZDOID>5__1); if (Object.op_Implicit((Object)(object)<go>5__2) && <go>5__2.TryGetComponent<Character>(ref <character>5__3)) { <character>5__3.SetHealth(0f); BetterTamesPlugin.LogIfDebug("Client executed MercyKill on " + <character>5__3.m_name, DebugFeature.PetProtection); } <>2__current = null; <>1__state = 1; return true; case 1: <>1__state = -1; return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <RPC_RequestMercyKill_Server>d__3 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public long sender; public ZPackage pkg; private ZDOID <targetZDOID>5__1; private ZDO <zdo>5__2; private ZPackage <response>5__3; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <RPC_RequestMercyKill_Server>d__3(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <zdo>5__2 = null; <response>5__3 = null; <>1__state = -2; } private bool MoveNext() { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Expected O, but got Unknown //IL_0098: Unknown result type (might be due to invalid IL or missing references) switch (<>1__state) { default: return false; case 0: <>1__state = -1; <targetZDOID>5__1 = pkg.ReadZDOID(); BetterTamesPlugin.LogIfDebug($"Server received MercyKill request for {<targetZDOID>5__1}", DebugFeature.PetProtection); <zdo>5__2 = ZDOMan.instance.GetZDO(<targetZDOID>5__1); if (<zdo>5__2 != null) { <zdo>5__2.Set("BT_MercyKill", true); <response>5__3 = new ZPackage(); <response>5__3.Write(<targetZDOID>5__1); MercyKillRPC.SendPackage(ZRoutedRpc.Everybody, <response>5__3); <response>5__3 = null; } <>2__current = null; <>1__state = 1; return true; case 1: <>1__state = -1; return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <RPC_TeleportSync_Client>d__5 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public long sender; public ZPackage pkg; private ZDOID <id>5__1; private Vector3 <pos>5__2; private Quaternion <rot>5__3; private GameObject <go>5__4; private Character <character>5__5; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <RPC_TeleportSync_Client>d__5(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <go>5__4 = null; <character>5__5 = null; <>1__state = -2; } private bool MoveNext() { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: 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_0040: Unknown result type (might be due to invalid IL or missing references) //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_005d: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) switch (<>1__state) { default: return false; case 0: <>1__state = -1; <id>5__1 = pkg.ReadZDOID(); <pos>5__2 = pkg.ReadVector3(); <rot>5__3 = pkg.ReadQuaternion(); <go>5__4 = ZNetScene.instance.FindInstance(<id>5__1); if (Object.op_Implicit((Object)(object)<go>5__4) && <go>5__4.TryGetComponent<Character>(ref <character>5__5) && !<character>5__5.IsTeleporting()) { <go>5__4.transform.position = <pos>5__2; <go>5__4.transform.rotation = <rot>5__3; } <>2__current = null; <>1__state = 1; return true; case 1: <>1__state = -1; return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } public static CustomRPC MercyKillRPC; public static CustomRPC TeleportSyncRPC; public static void RegisterRPCs() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown //IL_0034: Expected O, but got Unknown //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Expected O, but got Unknown BetterTamesPlugin.LogIfDebug("Registering Modern Jötunn RPCs..."); MercyKillRPC = NetworkManager.Instance.AddRPC("BT_MercyKill", new CoroutineHandler(RPC_RequestMercyKill_Server), new CoroutineHandler(RPC_NotifyMercyKill_Client)); TeleportSyncRPC = NetworkManager.Instance.AddRPC("BT_TeleportSync", (CoroutineHandler)null, new CoroutineHandler(RPC_TeleportSync_Client)); } [IteratorStateMachine(typeof(<RPC_RequestMercyKill_Server>d__3))] private static IEnumerator RPC_RequestMercyKill_Server(long sender, ZPackage pkg) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <RPC_RequestMercyKill_Server>d__3(0) { sender = sender, pkg = pkg }; } [IteratorStateMachine(typeof(<RPC_NotifyMercyKill_Client>d__4))] private static IEnumerator RPC_NotifyMercyKill_Client(long sender, ZPackage pkg) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <RPC_NotifyMercyKill_Client>d__4(0) { sender = sender, pkg = pkg }; } [IteratorStateMachine(typeof(<RPC_TeleportSync_Client>d__5))] private static IEnumerator RPC_TeleportSync_Client(long sender, ZPackage pkg) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <RPC_TeleportSync_Client>d__5(0) { sender = sender, pkg = pkg }; } } } namespace BetterTames.PetProtection { [HarmonyPatch] public static class ButcherKnifePatch { [HarmonyPatch(typeof(Character), "Damage")] [HarmonyPrefix] [HarmonyPriority(800)] public static bool Prefix(Character __instance, HitData hit) { //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Expected O, but got Unknown //IL_013c: Unknown result type (might be due to invalid IL or missing references) if (!__instance.IsTamed()) { return true; } if (!(hit.GetAttacker() is Player)) { return true; } float num = __instance.GetHealth() - hit.GetTotalDamage(); if (num > 0f) { return true; } if (!CheckButcherKnifeBypass(__instance, hit)) { return true; } ZNetView component = ((Component)__instance).GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || !component.IsValid()) { BetterTamesPlugin.LogIfDebug("ZNetView is null or invalid, cannot process MercyKill.", DebugFeature.PetProtection); return true; } ZDOID uid = component.GetZDO().m_uid; BetterTamesPlugin.LogIfDebug($"Butcher Knife used on {__instance.m_name} (ZDOID: {uid}). IsOwner: {component.IsOwner()}", DebugFeature.PetProtection); if (component.IsOwner()) { BetterTamesPlugin.LogIfDebug("Owner setting BT_MercyKill flag for " + __instance.m_name + " locally.", DebugFeature.PetProtection); component.GetZDO().Set("BT_MercyKill", true); } else { BetterTamesPlugin.LogIfDebug($"Non-owner sending MercyKill RPC for ZDOID: {uid} to server.", DebugFeature.PetProtection); try { ZPackage val = new ZPackage(); val.Write(uid); RPCManager.MercyKillRPC.SendPackage(ZNet.instance.GetServerPeer().m_uid, val); } catch (Exception arg) { BetterTamesPlugin.LogIfDebug($"Exception sending MercyKill RPC: {arg}", DebugFeature.PetProtection); } } return true; } private static bool CheckButcherKnifeBypass(Character character, HitData hit) { if ((Object)(object)character == (Object)null || !character.IsTamed()) { return false; } Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val == null) { return false; } ItemData currentWeapon = ((Humanoid)val).GetCurrentWeapon(); object obj; if (currentWeapon == null) { obj = null; } else { GameObject dropPrefab = currentWeapon.m_dropPrefab; obj = ((dropPrefab != null) ? ((Object)dropPrefab).name : null); } return (string?)obj == "KnifeButcher"; } } [HarmonyPatch(typeof(EnemyHud), "TestShow")] public static class EnemyHud_TestShow_Patch { [HarmonyPostfix] public static void HideKnockedOutHud(Character c, ref bool __result) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) if (!__result || (Object)(object)c == (Object)null || !c.IsTamed()) { return; } ZNetView component = ((Component)c).GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null && component.IsValid()) { ZDOID uid = component.GetZDO().m_uid; if (PetProtectionPatch.IsPetKnockedOut(uid) || PetProtectionPatch.IsTransformedToWisp(uid)) { __result = false; } } } } [HarmonyPatch] public static class PetProtectionPatch { private static readonly HashSet<string> s_exceptionPrefabs = new HashSet<string>(); private static readonly Dictionary<ZDOID, GameObject> s_wispInstances = new Dictionary<ZDOID, GameObject>(); private static GameObject wispPrefab; public static bool IsPetKnockedOut(ZDOID petId) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) return s_wispInstances.ContainsKey(petId); } public static bool IsTransformedToWisp(ZDOID petId) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) ZDO zDO = ZDOMan.instance.GetZDO(petId); return zDO != null && zDO.GetBool("BT_TransformedToWisp", false); } public static void Initialize() { wispPrefab = ZNetScene.instance.GetPrefab("LuredWisp"); BetterTamesPlugin.LogIfDebug("Stunned Pets get Transformed into: " + (object)wispPrefab, DebugFeature.PetProtection); if ((Object)(object)wispPrefab != (Object)null) { BetterTamesPlugin.LogIfDebug("LuredWisp prefab cached successfully.", DebugFeature.PetProtection); } else { BetterTamesPlugin.LogIfDebug("ERROR: Could not cache LuredWisp prefab!", DebugFeature.PetProtection); } } public static void UpdateExceptionPrefabs(string exceptionPrefabString) { s_exceptionPrefabs.Clear(); if (!string.IsNullOrWhiteSpace(exceptionPrefabString)) { string[] array = exceptionPrefabString.Split(new char[1] { ',' }); string[] array2 = array; foreach (string text in array2) { s_exceptionPrefabs.Add(text.Trim().ToLowerInvariant()); } } } private static bool ShouldApplyPetProtection(Character character) { if ((Object)(object)character == (Object)null || !character.IsTamed()) { return false; } if ((Object)(object)((Component)character).GetComponent<Tameable>() == (Object)null) { return false; } if ((Object)(object)((Component)character).GetComponent<MonsterAI>() == (Object)null && (Object)(object)((Component)character).GetComponent<BaseAI>() == (Object)null) { return false; } ZNetView component = ((Component)character).GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || !component.IsValid()) { return false; } string item = ((Object)ZNetScene.instance.GetPrefab(component.GetZDO().GetPrefab())).name.ToLowerInvariant(); return !s_exceptionPrefabs.Contains(item); } [HarmonyPatch(typeof(Character), "ApplyDamage")] [HarmonyPrefix] public static bool ApplyDamagePrefix(Character __instance, HitData hit) { if (!__instance.IsTamed()) { return true; } float num = __instance.GetHealth() - hit.GetTotalDamage(); if (num > 0f) { return true; } ZNetView component = ((Component)__instance).GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || !component.IsValid()) { return true; } ZDO zDO = component.GetZDO(); if (zDO.GetBool("BT_MercyKill", false)) { zDO.Set("BT_MercyKill", false); BetterTamesPlugin.LogIfDebug("MercyKill executed for " + __instance.m_name, DebugFeature.PetProtection); return true; } if (!BetterTamesPlugin.ConfigInstance.Tames.PetProtectionEnabled.Value || !ShouldApplyPetProtection(__instance)) { return true; } if (component.IsOwner()) { if (zDO.GetBool("isRecoveringFromStun", false)) { return false; } ApplyWispTransform(__instance, component, zDO); return false; } return true; } [HarmonyPatch(typeof(MonsterAI), "UpdateAI")] [HarmonyPrefix] [HarmonyPriority(800)] public static void KnockoutTimerPrefix(MonsterAI __instance) { ZNetView component = ((Component)__instance).GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || !component.IsOwner()) { return; } ZDO zDO = component.GetZDO(); if (!zDO.GetBool("isRecoveringFromStun", false)) { return; } float @float = zDO.GetFloat("BT_RevivalTimestamp", 0f); if (@float > 0f && ZNet.instance.GetTimeSeconds() >= (double)@float) { Character component2 = ((Component)__instance).GetComponent<Character>(); if ((Object)(object)component2 != (Object)null) { RevertWispTransform(component2, component, zDO); } } } private static void ApplyWispTransform(Character character, ZNetView nview, ZDO zdo) { //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)character == (Object)null || (Object)(object)nview == (Object)null || zdo == null) { BetterTamesPlugin.LogIfDebug("ApplyWispTransform called with null parameter(s) - aborting.", DebugFeature.PetProtection); return; } zdo.Set("isRecoveringFromStun", true); zdo.Set(ZDOVars.s_health, 1f); zdo.Set("dead", true); float num = (float)ZNet.instance.GetTimeSeconds() + (float)BetterTamesPlugin.ConfigInstance.Tames.PetProtectionStunDuration.Value; zdo.Set("BT_RevivalTimestamp", num); if ((Object)(object)EnemyHud.instance != (Object)null) { EnemyHud.instance.RemoveCharacterHud(character); } float num2 = (float)ZNet.instance.GetTimeSeconds() + 2f; zdo.Set("BT_WispTeleportTimestamp", num2); zdo.Set("BT_TransformedToWisp", true); SetRenderersVisible(character, visible: false); if ((Object)(object)wispPrefab != (Object)null && (Object)(object)((Component)character).GetComponent<MonsterAI>() != (Object)null) { GameObject val = Object.Instantiate<GameObject>(wispPrefab, ((Component)character).transform.position, Quaternion.identity); try { Collider[] componentsInChildren = val.GetComponentsInChildren<Collider>(true); Collider[] array = componentsInChildren; foreach (Collider val2 in array) { if ((Object)(object)val2 != (Object)null) { val2.enabled = false; } } Rigidbody[] componentsInChildren2 = val.GetComponentsInChildren<Rigidbody>(true); Rigidbody[] array2 = componentsInChildren2; foreach (Rigidbody val3 in array2) { if ((Object)(object)val3 != (Object)null) { val3.isKinematic = true; } } } catch (Exception arg) { BetterTamesPlugin.LogIfDebug($"Exception while disabling colliders on wisp: {arg}", DebugFeature.PetProtection); } s_wispInstances[zdo.m_uid] = val; } try { MonsterAI val4 = ((character != null) ? ((Component)character).GetComponent<MonsterAI>() : null); GameObject followTarget = val4.GetFollowTarget(); Player val5 = ((followTarget != null) ? followTarget.GetComponent<Player>() : null); if ((Object)(object)val5 != (Object)null) { BetterTamesPlugin.LogIfDebug("Teleporting to a safe place (behind player).", DebugFeature.PetProtection); DistanceTeleportLogic.ExecuteTeleportBehindPlayer(character, followTarget); } } catch (Exception arg2) { BetterTamesPlugin.LogIfDebug($"Exception during teleport: {arg2}", DebugFeature.PetProtection); } } private static void RevertWispTransform(Character character, ZNetView nview, ZDO zdo) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) BetterTamesPlugin.LogIfDebug("Reverting " + character.m_name + " from wisp form.", DebugFeature.PetProtection); if (s_wispInstances.TryGetValue(zdo.m_uid, out var value)) { if ((Object)(object)value != (Object)null) { ZNetView component = value.GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null && (Object)(object)ZNetScene.instance != (Object)null) { ZNetScene.instance.Destroy(value); } else { Object.Destroy((Object)(object)value); } } s_wispInstances.Remove(zdo.m_uid); } SetRenderersVisible(character, visible: true); zdo.Set("BT_TransformedToWisp", false); zdo.Set("isRecoveringFromStun", false); zdo.Set("BT_RevivalTimestamp", 0f); zdo.Set("dead", false); float maxHealth = character.GetMaxHealth(); float num = BetterTamesPlugin.ConfigInstance.Tames.PetProtectionHealPercentage.Value; float num2 = Mathf.Clamp(maxHealth * (num / 100f), 1f, maxHealth); zdo.Set(ZDOVars.s_health, num2); } private static void SetRenderersVisible(Character character, bool visible) { Collider[] componentsInChildren = ((Component)character).GetComponentsInChildren<Collider>(); foreach (Collider val in componentsInChildren) { val.enabled = visible; } Renderer[] componentsInChildren2 = ((Component)character).GetComponentsInChildren<Renderer>(); foreach (Renderer val2 in componentsInChildren2) { val2.enabled = visible; } } } [HarmonyPatch] public static class StunBehaviorPatches { [HarmonyPatch(typeof(MonsterAI), "UpdateAI")] [HarmonyPrefix] public static bool PreventAIUpdateWhenStunned(MonsterAI __instance) { ZNetView component = ((Component)__instance).GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || !component.IsValid()) { return true; } SyncRenderersWithZDO(__instance); ZDO zDO = component.GetZDO(); if (zDO == null || !zDO.GetBool("isRecoveringFromStun", false)) { return true; } ((BaseAI)__instance).StopMoving(); Character component2 = ((Component)__instance).GetComponent<Character>(); if ((Object)(object)component2 != (Object)null) { ZSyncAnimation zAnim = component2.GetZAnim(); if (zAnim != null) { zAnim.SetBool("sleeping", true); } } return false; } [HarmonyPatch(typeof(Humanoid), "StartAttack")] [HarmonyPrefix] public static bool PreventAttackWhenStunned_Humanoid(Humanoid __instance) { if (!((Character)__instance).IsTamed()) { return true; } ZNetView component = ((Component)__instance).GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null && component.IsValid() && component.GetZDO().GetBool("isRecoveringFromStun", false)) { return false; } return true; } private static void SyncRenderersWithZDO(MonsterAI ai) { if ((Object)(object)ai == (Object)null) { return; } ZNetView component = ((Component)ai).GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || !component.IsValid()) { return; } ZDO zDO = component.GetZDO(); if (zDO == null) { return; } bool @bool = zDO.GetBool("BT_TransformedToWisp", false); Character component2 = ((Component)ai).GetComponent<Character>(); if ((Object)(object)component2 == (Object)null) { return; } bool flag = !@bool; bool flag2 = false; Renderer[] componentsInChildren = ((Component)component2).GetComponentsInChildren<Renderer>(true); foreach (Renderer val in componentsInChildren) { if ((Object)(object)val != (Object)null && val.enabled) { flag2 = true; break; } } if (flag2 != flag) { ApplyRendererVisibility(component2, flag); } } private static void ApplyRendererVisibility(Character character, bool visible) { if ((Object)(object)character == (Object)null) { return; } Collider[] componentsInChildren = ((Component)character).GetComponentsInChildren<Collider>(true); foreach (Collider val in componentsInChildren) { if ((Object)(object)val != (Object)null) { val.enabled = visible; } } Renderer[] componentsInChildren2 = ((Component)character).GetComponentsInChildren<Renderer>(true); foreach (Renderer val2 in componentsInChildren2) { if ((Object)(object)val2 != (Object)null) { val2.enabled = visible; } } } } } namespace BetterTames.MakeCommandable { [HarmonyPatch(typeof(Player), "UpdateTeleport")] public class Player_UpdateTeleport_Patch { [CompilerGenerated] private sealed class <PostTeleportCleanupCoroutine>d__2 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public Player player; public int maxPets; public float checkRadius; private float <waited>5__1; private float <timeout>5__2; private string <playerName>5__3; private List<Character> <nearbyFollowers>5__4; private List<Character> <sortedFollowers>5__5; private int <numberToUnfollow>5__6; private int <clearedCount>5__7; private Collider[] <>s__8; private int <>s__9; private Collider <col>5__10; private Character <c>5__11; private bool <isFollowing>5__12; private MonsterAI <monsterAI>5__13; private GameObject <followTarget>5__14; private ZNetView <zview>5__15; private ZDO <zdo>5__16; private int <i>5__17; private Character <follower>5__18; private Tameable <tameable>5__19; private MonsterAI <monsterAI>5__20; private Exception <ex>5__21; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <PostTeleportCleanupCoroutine>d__2(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <playerName>5__3 = null; <nearbyFollowers>5__4 = null; <sortedFollowers>5__5 = null; <>s__8 = null; <col>5__10 = null; <c>5__11 = null; <monsterAI>5__13 = null; <followTarget>5__14 = null; <zview>5__15 = null; <zdo>5__16 = null; <follower>5__18 = null; <tameable>5__19 = null; <monsterAI>5__20 = null; <ex>5__21 = null; <>1__state = -2; } private bool MoveNext() { //IL_0122: Unknown result type (might be due to invalid IL or missing references) switch (<>1__state) { default: return false; case 0: <>1__state = -1; BetterTamesPlugin.LogIfDebug("=== Coroutine STARTED ===", DebugFeature.MakeCommandable); <waited>5__1 = 0f; <timeout>5__2 = 8f; goto IL_007d; case 1: <>1__state = -1; goto IL_007d; case 2: { <>1__state = -1; BetterTamesPlugin.LogIfDebug($"Coroutine waited {<waited>5__1:F2}s. Player finished teleporting. Checking for pets now.", DebugFeature.MakeCommandable); if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer) { return false; } <playerName>5__3 = player.GetPlayerName(); <nearbyFollowers>5__4 = new List<Character>(); <>s__8 = Physics.OverlapSphere(((Component)player).transform.position, checkRadius); for (<>s__9 = 0; <>s__9 < <>s__8.Length; <>s__9++) { <col>5__10 = <>s__8[<>s__9]; <c>5__11 = ((Component)<col>5__10).GetComponent<Character>(); if (!((Object)(object)<c>5__11 == (Object)null) && <c>5__11.IsTamed() && !<nearbyFollowers>5__4.Contains(<c>5__11)) { <isFollowing>5__12 = false; <monsterAI>5__13 = ((Component)<c>5__11).GetComponent<MonsterAI>(); if ((Object)(object)<monsterAI>5__13 != (Object)null) { try { <followTarget>5__14 = <monsterAI>5__13.GetFollowTarget(); if ((Object)(object)<followTarget>5__14 != (Object)null && (Object)(object)<followTarget>5__14 == (Object)(object)player) { <isFollowing>5__12 = true; } <followTarget>5__14 = null; } catch { } } if (!<isFollowing>5__12) { <zview>5__15 = ((Component)<c>5__11).GetComponent<ZNetView>(); ZNetView obj2 = <zview>5__15; <zdo>5__16 = ((obj2 != null) ? obj2.GetZDO() : null); if (<zdo>5__16 != null && <zdo>5__16.IsValid() && <zdo>5__16.GetString(ZDOVars.s_follow, "") == <playerName>5__3) { <isFollowing>5__12 = true; } <zview>5__15 = null; <zdo>5__16 = null; } if (<isFollowing>5__12) { <nearbyFollowers>5__4.Add(<c>5__11); } <c>5__11 = null; <monsterAI>5__13 = null; <col>5__10 = null; } } <>s__8 = null; BetterTamesPlugin.LogIfDebug($"PostTeleport: found {<nearbyFollowers>5__4.Count} followers within {checkRadius}m (max {maxPets}).", DebugFeature.MakeCommandable); if (<nearbyFollowers>5__4.Count <= maxPets) { return false; } <sortedFollowers>5__5 = (from c in <nearbyFollowers>5__4 where (Object)(object)c != (Object)null select c into _ orderby Random.value select _).ToList(); <numberToUnfollow>5__6 = <sortedFollowers>5__5.Count - maxPets; <clearedCount>5__7 = 0; <i>5__17 = 0; while (<i>5__17 < <numberToUnfollow>5__6) { <follower>5__18 = <sortedFollowers>5__5[<i>5__17]; if (!((Object)(object)<follower>5__18 == (Object)null)) { try { <tameable>5__19 = ((Component)<follower>5__18).GetComponent<Tameable>(); <monsterAI>5__20 = ((Component)<follower>5__18).GetComponent<MonsterAI>(); if ((Object)(object)<tameable>5__19 != (Object)null) { <tameable>5__19.Command((Humanoid)(object)player, true); MonsterAI obj3 = <monsterAI>5__20; if ((Object)(object)((obj3 != null) ? obj3.GetFollowTarget() : null) == (Object)null) { BetterTamesPlugin.LogIfDebug("[Command] Successfully requested unfollow for " + <follower>5__18.GetHoverName() + ".", DebugFeature.MakeCommandable); <clearedCount>5__7++; } else { BetterTamesPlugin.LogIfDebug("[Command] Failed to set " + <follower>5__18.GetHoverName() + " to 'Stay' immediately (Network/Authority).", DebugFeature.MakeCommandable); <clearedCount>5__7++; } } <tameable>5__19 = null; <monsterAI>5__20 = null; } catch (Exception ex) { <ex>5__21 = ex; Character obj4 = <follower>5__18; BetterTamesPlugin.LogIfDebug($"Error during cleanup of {((obj4 != null) ? ((Object)obj4).name : null)}: {<ex>5__21}", DebugFeature.MakeCommandable); } <follower>5__18 = null; } <i>5__17++; } return false; } IL_007d: if (((Character)player).IsTeleporting() && <waited>5__1 < <timeout>5__2) { <waited>5__1 += Time.deltaTime; <>2__current = null; <>1__state = 1; return true; } <>2__current = null; <>1__state = 2; return true; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static bool wasTeleporting; [HarmonyPostfix] public static void Postfix(Player __instance) { if (wasTeleporting && !((Character)__instance).IsTeleporting()) { BetterTamesPlugin.LogIfDebug("=== TeleportTo Postfix TRIGGERED for player ===", DebugFeature.MakeCommandable); if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } int value = BetterTamesPlugin.ConfigInstance.Tames.MaxFollowingPets.Value; if (value == -1) { BetterTamesPlugin.LogIfDebug("MaxPets == -1, skipping cleanup.", DebugFeature.MakeCommandable); return; } float checkRadius = 64f; try { BetterTamesPlugin.LogIfDebug("Attempting to start PostTeleportCleanupCoroutine...", DebugFeature.MakeCommandable); ((MonoBehaviour)Player.m_localPlayer).StartCoroutine(PostTeleportCleanupCoroutine(Player.m_localPlayer, value, checkRadius)); BetterTamesPlugin.LogIfDebug("Started PostTeleportCleanupCoroutine successfully.", DebugFeature.MakeCommandable); } catch (Exception ex) { BetterTamesPlugin.LogIfDebug("Failed starting PostTeleportCleanupCoroutine: " + ex.Message + " | Stack: " + ex.StackTrace, DebugFeature.MakeCommandable); } } wasTeleporting = ((Character)__instance).IsTeleporting(); } [IteratorStateMachine(typeof(<PostTeleportCleanupCoroutine>d__2))] private static IEnumerator PostTeleportCleanupCoroutine(Player player, int maxPets, float checkRadius) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <PostTeleportCleanupCoroutine>d__2(0) { player = player, maxPets = maxPets, checkRadius = checkRadius }; } } [HarmonyPatch(typeof(Tameable), "Interact")] public static class MakeCommandablePatch { [HarmonyPrefix] public static bool Prefix(Tameable __instance, Humanoid user, bool hold, bool alt, ref bool __result) { if (hold || alt) { return true; } Player val = (Player)(object)((user is Player) ? user : null); if (val == null || (Object)(object)val != (Object)(object)Player.m_localPlayer) { return true; } Character component = ((Component)__instance).GetComponent<Character>(); MonsterAI component2 = ((Component)__instance).GetComponent<MonsterAI>(); ZNetView component3 = ((Component)__instance).GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || !component.IsTamed() || (Object)(object)component2 == (Object)null || (Object)(object)component3 == (Object)null || !component3.IsValid()) { return true; } ZDO zDO = component3.GetZDO(); string hoverName = __instance.GetHoverName(); string playerName = val.GetPlayerName(); if (!(zDO.GetString(ZDOVars.s_follow, "") == playerName)) { int value = BetterTamesPlugin.ConfigInstance.Tames.MaxFollowingPets.Value; if (value != -1 && CheckMaxFollowerLimit(val, value)) { ((Character)user).Message((MessageType)2, $"Too many companions. Maximum allowed: {value}", 0, (Sprite)null); __result = true; return false; } } __instance.Command(user, true); string text = ((zDO.GetString(ZDOVars.s_follow, "") == playerName) ? "follow." : "stay."); BetterTamesPlugin.LogIfDebug("Command issued to " + hoverName + ": " + text, DebugFeature.MakeCommandable); __result = true; return false; } private static bool CheckMaxFollowerLimit(Player player, int maxPets) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) if (maxPets <= 0) { return false; } int num = 0; string playerName = player.GetPlayerName(); float num2 = 64f; Collider[] array = Physics.OverlapSphere(((Component)player).transform.position, num2); foreach (Collider val in array) { Character component = ((Component)val).GetComponent<Character>(); if ((Object)(object)component != (Object)null && component.IsTamed()) { ZNetView component2 = ((Component)component).GetComponent<ZNetView>(); ZDO val2 = ((component2 != null) ? component2.GetZDO() : null); if (val2 != null && val2.GetString(ZDOVars.s_follow, "") == playerName) { num++; } } } return num >= maxPets; } } } namespace BetterTames.ConfigSynchronization { public class ConfigSync { public class TamesConfig { private const string SectionGeneral = "1. General"; private const string SectionMakeCommandable = "2. MakeCommandable"; private const string SectionTeleportFollow = "3. TeleportFollow"; private const string SectionPetProtection = "4. PetProtection"; public ConfigEntry<bool> ServerConfigLocked { get; private set; } public ConfigEntry<bool> DebugMakeCommandable { get; private set; } public ConfigEntry<int> MaxFollowingPets { get; private set; } public ConfigEntry<bool> TeleportFollowEnabled { get; private set; } public ConfigEntry<int> TeleportOnDistanceMaxRange { get; private set; } public ConfigEntry<bool> DebugTeleportFollow { get; private set; } public ConfigEntry<bool> PetProtectionEnabled { get; private set; } public ConfigEntry<int> PetProtectionStunDuration { get; private set; } public ConfigEntry<int> PetProtectionHealPercentage { get; private set; } public ConfigEntry<string> PetProtectionExceptionPrefabs { get; private set; } public ConfigEntry<bool> DebugPetProtection { get; private set; } public TamesConfig(ConfigFile cfg) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Expected O, but got Unknown //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Expected O, but got Unknown //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Expected O, but got Unknown TryRemoveDuplicateConfigEntriesFile(); ServerConfigLocked = BindAndSync(cfg, "1. General", "Lock Configuration", defaultValue: true, "If true on the server, this configuration file will be locked and synced to clients."); BetterTamesPlugin._configSync.AddLockingConfigEntry<bool>(ServerConfigLocked); MaxFollowingPets = BindAndSync(cfg, "2. MakeCommandable", "Max Following Pets", 5, new ConfigDescription("Maximum number of pets that can follow a player at the same time. -1 to disable.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(-1, 20), Array.Empty<object>())); DebugMakeCommandable = cfg.Bind<bool>("2. MakeCommandable", "Debug Logging", false, "Enables debug logging for this feature."); TeleportFollowEnabled = BindAndSync(cfg, "3. TeleportFollow", "Enable", defaultValue: true, "Enables pets to teleport to the player if they get too far or the player uses a portal/teleports."); TeleportOnDistanceMaxRange = BindAndSync(cfg, "3. TeleportFollow", "Max Distance For AutoTeleport", 64, new ConfigDescription("Maximum distance a pet can be from its owner before it attempts to teleport (if not in combat).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(20, 64), Array.Empty<object>())); DebugTeleportFollow = cfg.Bind<bool>("3. TeleportFollow", "Debug Logging", false, "Enables debug logging for teleport features."); PetProtectionEnabled = BindAndSync(cfg, "4. PetProtection", "Enable", defaultValue: true, "Prevents tamed creatures from dying by knocking them out instead. They recover after a set time."); PetProtectionStunDuration = BindAndSync(cfg, "4. PetProtection", "Stun Duration", 10, new ConfigDescription("How long the pet stays stunned/downed (seconds).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(5, 300), Array.Empty<object>())); PetProtectionHealPercentage = BindAndSync(cfg, "4. PetProtection", "Heal After Stun Pct", 25, new ConfigDescription("Percentage of max HP the pet recovers after being downed. (0 = 1HP).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>())); PetProtectionExceptionPrefabs = BindAndSync(cfg, "4. PetProtection", "Exception Prefabs", "SummonedGolem_TW,SummonedSurtling_TW,SummonedSeeker_TW,SummonedImp_TW,Troll_Summoned,Charred_Twitcher_Summoned,Skeleton_Friendly,JC_Skeleton,enemy_skeleton_summoned", "A comma-separated list of prefab names that should NOT receive pet protection."); DebugPetProtection = cfg.Bind<bool>("4. PetProtection", "Debug Logging", false, "Enables debug logging for this feature."); } private ConfigEntry<T> BindAndSync<T>(ConfigFile cfg, string section, string key, T defaultValue, string description) { ConfigEntry<T> val = cfg.Bind<T>(section, key, defaultValue, description); BetterTamesPlugin._configSync.AddConfigEntry<T>(val); return val; } private ConfigEntry<T> BindAndSync<T>(ConfigFile cfg, string section, string key, T defaultValue, ConfigDescription description) { ConfigEntry<T> val = cfg.Bind<T>(section, key, defaultValue, description); BetterTamesPlugin._configSync.AddConfigEntry<T>(val); return val; } private void TryRemoveDuplicateConfigEntriesFile() { try { string path = Path.Combine(Paths.ConfigPath, "Koro.bettertames.cfg"); if (!File.Exists(path)) { return; } string[] array = File.ReadAllLines(path); List<string> list = new List<string>(array.Length); HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); string text = ""; string[] array2 = array; foreach (string text2 in array2) { string text3 = text2; string text4 = text3.Trim(); if (text4.StartsWith("[") && text4.EndsWith("]")) { text = text4.Substring(1, text4.Length - 2).Trim(); list.Add(text3); continue; } if (string.IsNullOrWhiteSpace(text4) || text4.StartsWith("#")) { list.Add(text3); continue; } int num = text3.IndexOf('='); if (num > 0) { string text5 = text3.Substring(0, num).Trim(); if (string.IsNullOrEmpty(text5)) { list.Add(text3); continue; } string item = text + "|" + text5; if (!hashSet.Contains(item)) { hashSet.Add(item); list.Add(text3); } } else { list.Add(text3); } } bool flag = list.Count != array.Length; if (!flag) { for (int j = 0; j < array.Length; j++) { if (array[j] != list[j]) { flag = true; break; } } } if (flag) { File.WriteAllLines(path, list); } } catch (Exception) { } } } public TamesConfig Tames { get; private set; } public ConfigSync(BetterTamesPlugin pluginInstance) { Tames = new TamesConfig(((BaseUnityPlugin)pluginInstance).Config); } } } namespace BetterTames.DistanceTeleport { public static class PlayerPetMonitor { [CompilerGenerated] private sealed class <MonitorRoutine>d__1 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; private List<Character> <allCharacters>5__1; private List<Character> <followingPets>5__2; private List<Character>.Enumerator <>s__3; private Character <character>5__4; private ZNetView <nview>5__5; private MonsterAI <ai>5__6; private List<Character>.Enumerator <>s__7; private Character <pet>5__8; private bool <teleported>5__9; private Exception <ex>5__10; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <MonitorRoutine>d__1(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <allCharacters>5__1 = null; <followingPets>5__2 = null; <>s__3 = default(List<Character>.Enumerator); <character>5__4 = null; <nview>5__5 = null; <ai>5__6 = null; <>s__7 = default(List<Character>.Enumerator); <pet>5__8 = null; <ex>5__10 = null; <>1__state = -2; } private bool MoveNext() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; BetterTamesPlugin.LogIfDebug("PlayerPetMonitor: MonitorRoutine started.", DebugFeature.TeleportFollow); break; case 1: <>1__state = -1; if ((Object)(object)Player.m_localPlayer == (Object)null) { BetterTamesPlugin.LogIfDebug("PlayerPetMonitor: Player.m_localPlayer == null, skipping this tick.", DebugFeature.TeleportFollow); break; } BetterTamesPlugin.LogIfDebug("PlayerPetMonitor: Starting scan for owned following pets.", DebugFeature.TeleportFollow); <allCharacters>5__1 = Character.GetAllCharacters(); if (<allCharacters>5__1 == null || <allCharacters>5__1.Count == 0) { BetterTamesPlugin.LogIfDebug("PlayerPetMonitor: No characters found in the world. Skipping scan.", DebugFeature.TeleportFollow); break; } <followingPets>5__2 = new List<Character>(); <>s__3 = <allCharacters>5__1.GetEnumerator(); try { while (<>s__3.MoveNext()) { <character>5__4 = <>s__3.Current; if ((Object)(object)<character>5__4 == (Object)null || !<character>5__4.IsTamed()) { continue; } <nview>5__5 = ((Component)<character>5__4).GetComponent<ZNetView>(); if (!((Object)(object)<nview>5__5 == (Object)null) && <nview>5__5.IsOwner()) { <ai>5__6 = ((Component)<character>5__4).GetComponent<MonsterAI>(); if ((Object)(object)<ai>5__6 != (Object)null && (Object)(object)<ai>5__6.GetFollowTarget() != (Object)null) { <followingPets>5__2.Add(<character>5__4); } <nview>5__5 = null; <ai>5__6 = null; <character>5__4 = null; } } } finally { ((IDisposable)<>s__3).Dispose(); } <>s__3 = default(List<Character>.Enumerator); BetterTamesPlugin.LogIfDebug($"PlayerPetMonitor: Found {<followingPets>5__2.Count} owned pets with follow targets.", DebugFeature.TeleportFollow); <>s__7 = <followingPets>5__2.GetEnumerator(); try { while (<>s__7.MoveNext()) { <pet>5__8 = <>s__7.Current; try { <teleported>5__9 = DistanceTeleportLogic.CheckDistanceAndTeleport(<pet>5__8); if (<teleported>5__9) { BetterTamesPlugin.LogIfDebug("PlayerPetMonitor: Teleport triggered for " + <pet>5__8.m_name + ".", DebugFeature.TeleportFollow); } } catch (Exception ex) { <ex>5__10 = ex; BetterTamesPlugin.LogIfDebug($"PlayerPetMonitor: Exception while checking/teleporting pet {<pet>5__8?.m_name}: {<ex>5__10}", DebugFeature.TeleportFollow); } <pet>5__8 = null; } } finally { ((IDisposable)<>s__7).Dispose(); } <>s__7 = default(List<Character>.Enumerator); <allCharacters>5__1 = null; <followingPets>5__2 = null; break; } <>2__current = (object)new WaitForSeconds(5f); <>1__state = 1; return true; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private const float CheckInterval = 5f; [IteratorStateMachine(typeof(<MonitorRoutine>d__1))] public static IEnumerator MonitorRoutine() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <MonitorRoutine>d__1(0); } } public class TeleportMonitorBehaviour : MonoBehaviour { private Coroutine _monitorCoroutine; private bool _fallbackRunning; private void Awake() { if (Object.FindObjectsByType<TeleportMonitorBehaviour>((FindObjectsSortMode)0).Length > 1) { Object.Destroy((Object)(object)((Component)this).gameObject); return; } Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); StartMonitor(); } public void StartMonitor() { if (_monitorCoroutine == null) { BetterTamesPlugin.LogIfDebug("TeleportMonitorBehaviour: Starting MonitorRoutine...", DebugFeature.TeleportFollow); _monitorCoroutine = ((MonoBehaviour)this).StartCoroutine(PlayerPetMonitor.MonitorRoutine()); if (!_fallbackRunning) { ((MonoBehaviour)this).InvokeRepeating("EnsureCoroutineRunning", 10f, 10f); _fallbackRunning = true; } } } private void EnsureCoroutineRunning() { if (_monitorCoroutine == null) { BetterTamesPlugin.LogIfDebug("TeleportMonitorBehaviour: Coroutine not running, restarting.", DebugFeature.TeleportFollow); _monitorCoroutine = ((MonoBehaviour)this).StartCoroutine(PlayerPetMonitor.MonitorRoutine()); } } private void OnDestroy() { if (_monitorCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_monitorCoroutine); _monitorCoroutine = null; } ((MonoBehaviour)this).CancelInvoke(); } } } namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.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 ServerSync { [PublicAPI] public abstract class OwnConfigEntryBase { public object? LocalBaseValue; public bool SynchronizedConfig = true; public abstract ConfigEntryBase BaseConfig { get; } } [PublicAPI] public class SyncedConfigEntry<T> : OwnConfigEntryBase { public readonly ConfigEntry<T> SourceConfig; public override ConfigEntryBase BaseConfig => (ConfigEntryBase)(object)SourceConfig; public T Value { get { return SourceConfig.Value; } set { SourceConfig.Value = value; } } public SyncedConfigEntry(ConfigEntry<T> sourceConfig) { SourceConfig = sourceConfig; base..ctor(); } public void AssignLocalValue(T value) { if (LocalBaseValue == null) { Value = value; } else { LocalBaseValue = value; } } } internal abstract class CustomSyncedValueBase { public object? LocalBaseValue; public readonly string Identifier; public readonly Type Type; private object? boxedValue; protected bool localIsOwner; public readonly int Priority; public object? BoxedValue { get { return boxedValue; } set { boxedValue = value; this.ValueChanged?.Invoke(); } } public event Action? ValueChanged; protected CustomSyncedValueBase(ConfigSync configSync, string identifier, Type type, int priority) { Priority = priority; Identifier = identifier; Type = type; configSync.AddCustomValue(this); localIsOwner = configSync.IsSourceOfTruth; configSync.SourceOfTruthChanged += delegate(bool truth) { localIsOwner = truth; }; } } [PublicAPI] internal sealed class CustomSyncedValue<T> : CustomSyncedValueBase { public T Value { get { return (T)base.BoxedValue; } set { base.BoxedValue = value; } } public CustomSyncedValue(ConfigSync configSync, string identifier, T value = default(T), int priority = 0) : base(configSync, identifier, typeof(T), priority) { Value = value; } public void AssignLocalValue(T value) { if (localIsOwner) { Value = value; } else { LocalBaseValue = value; } } } internal class ConfigurationManagerAttributes { [UsedImplicitly] public bool? ReadOnly = false; } [PublicAPI] public class ConfigSync { [HarmonyPatch(typeof(ZRpc), "HandlePackage")] private static class SnatchCurrentlyHandlingRPC { public static ZRpc? currentRpc; [HarmonyPrefix] private static void Prefix(ZRpc __instance) { currentRpc = __instance; } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class RegisterRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance) { isServer = __instance.IsServer(); foreach (ConfigSync configSync2 in configSyncs) { ZRoutedRpc.instance.Register<ZPackage>(configSync2.Name + " ConfigSync", (Action<long, ZPackage>)configSync2.RPC_FromOtherClientConfigSync); if (isServer) { configSync2.InitialSyncDone = true; Debug.Log((object)("Registered '" + configSync2.Name + " ConfigSync' RPC - waiting for incoming connections")); } } if (isServer) { ((MonoBehaviour)__instance).StartCoroutine(WatchAdminListChanges()); } static void SendAdmin(List<ZNetPeer> peers, bool isAdmin) { ZPackage package = ConfigsToPackage(null, null, new PackageEntry[1] { new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = isAdmin } }); ConfigSync configSync = configSyncs.First(); if (configSync != null) { ((MonoBehaviour)ZNet.instance).StartCoroutine(configSync.sendZPackage(peers, package)); } } static IEnumerator WatchAdminListChanges() { MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); List<string> CurrentList = new List<string>(adminList.GetList()); while (true) { yield return (object)new WaitForSeconds(30f); if (!adminList.GetList().SequenceEqual(CurrentList)) { CurrentList = new List<string>(adminList.GetList()); List<ZNetPeer> adminPeer = ZNet.instance.GetPeers().Where(delegate(ZNetPeer p) { string hostName = p.m_rpc.GetSocket().GetHostName(); return ((object)listContainsId == null) ? adminList.Contains(hostName) : ((bool)listContainsId.Invoke(ZNet.instance, new object[2] { adminList, hostName })); }).ToList(); List<ZNetPeer> nonAdminPeer = ZNet.instance.GetPeers().Except(adminPeer).ToList(); SendAdmin(nonAdminPeer, isAdmin: false); SendAdmin(adminPeer, isAdmin: true); } } } } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class RegisterClientRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance, ZNetPeer peer) { if (__instance.IsServer()) { return; } foreach (ConfigSync configSync in configSyncs) { peer.m_rpc.Register<ZPackage>(configSync.Name + " ConfigSync", (Action<ZRpc, ZPackage>)configSync.RPC_FromServerConfigSync); } } } private class ParsedConfigs { public readonly Dictionary<OwnConfigEntryBase, object?> configValues = new Dictionary<OwnConfigEntryBase, object>(); public readonly Dictionary<CustomSyncedValueBase, object?> customValues = new Dictionary<CustomSyncedValueBase, object>(); } [HarmonyPatch(typeof(ZNet), "Shutdown")] private class ResetConfigsOnShutdown { [HarmonyPostfix] private static void Postfix() { ProcessingServerUpdate = true; foreach (ConfigSync configSync in configSyncs) { configSync.resetConfigsFromServer(); configSync.IsSourceOfTruth = true; configSync.InitialSyncDone = false; } ProcessingServerUpdate = false; } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private class SendConfigsAfterLogin { private class BufferingSocket : ZPlayFabSocket, ISocket { public volatile bool finished = false; public volatile int versionMatchQueued = -1; public readonly List<ZPackage> Package = new List<ZPackage>(); public readonly ISocket Original; public BufferingSocket(ISocket original) { Original = original; ((ZPlayFabSocket)this)..ctor(); } public bool IsConnected() { return Original.IsConnected(); } public ZPackage Recv() { return Original.Recv(); } public int GetSendQueueSize() { return Original.GetSendQueueSize(); } public int GetCurrentSendRate() { return Original.GetCurrentSendRate(); } public bool IsHost() { return Original.IsHost(); } public void Dispose() { Original.Dispose(); } public bool GotNewData() { return Original.GotNewData(); } public void Close() { Original.Close(); } public string GetEndPointString() { return Original.GetEndPointString(); } public void GetAndResetStats(out int totalSent, out int totalRecv) { Original.GetAndResetStats(ref totalSent, ref totalRecv); } public void GetConnectionQuality(out float localQuality, out float remoteQuality, out int ping, out float outByteSec, out float inByteSec) { Original.GetConnectionQuality(ref localQuality, ref remoteQuality, ref ping, ref outByteSec, ref inByteSec); } public ISocket Accept() { return Original.Accept(); } public int GetHostPort() { return Original.GetHostPort(); } public bool Flush() { return Original.Flush(); } public string GetHostName() { return Original.GetHostName(); } public void VersionMatch() { if (finished) { Original.VersionMatch(); } else { versionMatchQueued = Package.Count; } } public void Send(ZPackage pkg) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown int pos = pkg.GetPos(); pkg.SetPos(0); int num = pkg.ReadInt(); if ((num == StringExtensionMethods.GetStableHashCode("PeerInfo") || num == StringExtensionMethods.GetStableHashCode("RoutedRPC") || num == StringExtensionMethods.GetStableHashCode("ZDOData")) && !finished) { ZPackage val = new ZPackage(pkg.GetArray()); val.SetPos(pos); Package.Add(val); } else { pkg.SetPos(pos); Original.Send(pkg); } } } [HarmonyPriority(800)] [HarmonyPrefix] private static void Prefix(ref Dictionary<Assembly, BufferingSocket>? __state, ZNet __instance, ZRpc rpc) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Invalid comparison between Unknown and I4 if (!__instance.IsServer()) { return; } BufferingSocket bufferingSocket = new BufferingSocket(rpc.GetSocket()); AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket); object? obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (val != null && (int)ZNet.m_onlineBackend > 0) { FieldInfo fieldInfo = AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket"); object? value = fieldInfo.GetValue(val); ZPlayFabSocket val2 = (ZPlayFabSocket)((value is ZPlayFabSocket) ? value : null); if (val2 != null) { typeof(ZPlayFabSocket).GetField("m_remotePlayerId").SetValue(bufferingSocket, val2.m_remotePlayerId); } fieldInfo.SetValue(val, bufferingSocket); } if (__state == null) { __state = new Dictionary<Assembly, BufferingSocket>(); } __state[Assembly.GetExecutingAssembly()] = bufferingSocket; } [HarmonyPostfix] private static void Postfix(Dictionary<Assembly, BufferingSocket> __state, ZNet __instance, ZRpc rpc) { ZRpc rpc2 = rpc; ZNet __instance2 = __instance; Dictionary<Assembly, BufferingSocket> __state2 = __state; ZNetPeer peer; if (__instance2.IsServer()) { object obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance2, new object[1] { rpc2 }); peer = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (peer == null) { SendBufferedData(); } else { ((MonoBehaviour)__instance2).StartCoroutine(sendAsync()); } } void SendBufferedData() { if (rpc2.GetSocket() is BufferingSocket bufferingSocket) { AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc2, bufferingSocket.Original); object? obj2 = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance2, new object[1] { rpc2 }); ZNetPeer val = (ZNetPeer)((obj2 is ZNetPeer) ? obj2 : null); if (val != null) { AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket").SetValue(val, bufferingSocket.Original); } } BufferingSocket bufferingSocket2 = __state2[Assembly.GetExecutingAssembly()]; bufferingSocket2.finished = true; for (int i = 0; i < bufferingSocket2.Package.Count; i++) { if (i == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } bufferingSocket2.Original.Send(bufferingSocket2.Package[i]); } if (bufferingSocket2.Package.Count == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } } IEnumerator sendAsync() { foreach (ConfigSync configSync in configSyncs) { List<PackageEntry> entries = new List<PackageEntry>(); if (configSync.CurrentVersion != null) { entries.Add(new PackageEntry { section = "Internal", key = "serverversion", type = typeof(string), value = configSync.CurrentVersion }); } MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); entries.Add(new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = (((object)listContainsId == null) ? ((object)adminList.Contains(rpc2.GetSocket().GetHostName())) : listContainsId.Invoke(ZNet.instance, new object[2] { adminList, rpc2.GetSocket().GetHostName() })) }); ZPackage package = ConfigsToPackage(configSync.allConfigs.Select((OwnConfigEntryBase c) => c.BaseConfig), configSync.allCustomValues, entries, partial: false); yield return ((MonoBehaviour)__instance2).StartCoroutine(configSync.sendZPackage(new List<ZNetPeer> { peer }, package)); } SendBufferedData(); } } } private class PackageEntry { public string section = null; public string key = null; public Type type = null; public object? value; } [HarmonyPatch(typeof(ConfigEntryBase), "GetSerializedValue")] private static class PreventSavingServerInfo { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, ref string __result) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || isWritableConfig(ownConfigEntryBase)) { return true; } __result = TomlTypeConverter.ConvertToString(ownConfigEntryBase.LocalBaseValue, __instance.SettingType); return false; } } [HarmonyPatch(typeof(ConfigEntryBase), "SetSerializedValue")] private static class PreventConfigRereadChangingValues { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, string value) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || ownConfigEntryBase.LocalBaseValue == null) { return true; } try { ownConfigEntryBase.LocalBaseValue = TomlTypeConverter.ConvertToValue(value, __instance.SettingType); } catch (Exception ex) { Debug.LogWarning((object)$"Config value of setting \"{__instance.Definition}\" could not be parsed and will be ignored. Reason: {ex.Message}; Value: {value}"); } return false; } } private class InvalidDeserializationTypeException : Exception { public string expected = null; public string received = null; public string field = ""; } public static bool ProcessingServerUpdate; public readonly string Name; public string? DisplayName; public string? CurrentVersion; public string? MinimumRequiredVersion; public bool ModRequired = false; private bool? forceConfigLocking; private bool isSourceOfTruth = true; private static readonly HashSet<ConfigSync> configSyncs; private readonly HashSet<OwnConfigEntryBase> allConfigs = new HashSet<OwnConfigEntryBase>(); private HashSet<CustomSyncedValueBase> allCustomValues = new HashSet<CustomSyncedValueBase>(); private static bool isServer; private static bool lockExempt; private OwnConfigEntryBase? lockedConfig = null; private const byte PARTIAL_CONFIGS = 1; private const byte FRAGMENTED_CONFIG = 2; private const byte COMPRESSED_CONFIG = 4; private readonly Dictionary<string, SortedDictionary<int, byte[]>> configValueCache = new Dictionary<string, SortedDictionary<int, byte[]>>(); private readonly List<KeyValuePair<long, string>> cacheExpirations = new List<KeyValuePair<long, string>>(); private static long packageCounter; public bool IsLocked { get { bool? flag = forceConfigLocking; bool num; if (!flag.HasValue) { if (lockedConfig == null) { goto IL_0052; } num = ((IConvertible)lockedConfig.BaseConfig.BoxedValue).ToInt32(CultureInfo.InvariantCulture) != 0; } else { num = flag.GetValueOrDefault(); } if (!num) { goto IL_0052; } int result = ((!lockExempt) ? 1 : 0); goto IL_0053; IL_0053: return (byte)result != 0; IL_0052: result = 0; goto IL_0053; } set { forceConfigLocking = value; } } public bool IsAdmin => lockExempt || isSourceOfTruth; public bool IsSourceOfTruth { get { return isSourceOfTruth; } private set { if (value != isSourceOfTruth) { isSourceOfTruth = value; this.SourceOfTruthChanged?.Invoke(value); } } } public bool InitialSyncDone { get; private set; } = false; public event Action<bool>? SourceOfTruthChanged; private event Action? lockedConfigChanged; static ConfigSync() { ProcessingServerUpdate = false; configSyncs = new HashSet<ConfigSync>(); lockExempt = false; packageCounter = 0L; RuntimeHelpers.RunClassConstructor(typeof(VersionCheck).TypeHandle); } public ConfigSync(string name) { Name = name; configSyncs.Add(this); new VersionCheck(this); } public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigEntry<T> configEntry) { ConfigEntry<T> configEntry2 = configEntry; OwnConfigEntryBase ownConfigEntryBase = configData((ConfigEntryBase)(object)configEntry2); SyncedConfigEntry<T> syncedEntry = ownConfigEntryBase as SyncedConfigEntry<T>; if (syncedEntry == null) { syncedEntry = new SyncedConfigEntry<T>(configEntry2); AccessTools.DeclaredField(typeof(ConfigDescription), "<Tags>k__BackingField").SetValue(((ConfigEntryBase)configEntry2).Description, new object[1] { new ConfigurationManagerAttributes() }.Concat(((ConfigEntryBase)configEntry2).Description.Tags ?? Array.Empty<object>()).Concat(new SyncedConfigEntry<T>[1] { syncedEntry }).ToArray()); configEntry2.SettingChanged += delegate { if (!ProcessingServerUpdate && syncedEntry.SynchronizedConfig) { Broadcast(ZRoutedRpc.Everybody, (ConfigEntryBase)configEntry2); } }; allConfigs.Add(syncedEntry); } return syncedEntry; } public SyncedConfigEntry<T> AddLockingConfigEntry<T>(ConfigEntry<T> lockingConfig) where T : IConvertible { if (lockedConfig != null) { throw new Exception("Cannot initialize locking ConfigEntry twice"); } lockedConfig = AddConfigEntry<T>(lockingConfig); lockingConfig.SettingChanged += delegate { this.lockedConfigChanged?.Invoke(); }; return (SyncedConfigEntry<T>)lockedConfig; } internal void AddCustomValue(CustomSyncedValueBase customValue) { CustomSyncedValueBase customValue2 = customValue; if (allCustomValues.Select((CustomSyncedValueBase v) => v.Identifier).Concat(new string[1] { "serverversion" }).Contains(customValue2.Identifier)) { throw new Exception("Cannot have multiple settings with the same name or with a reserved name (serverversion)"); } allCustomValues.Add(customValue2); allCustomValues = new HashSet<CustomSyncedValueBase>(allCustomValues.OrderByDescending((CustomSyncedValueBase v) => v.Priority)); customValue2.ValueChanged += delegate { if (!ProcessingServerUpdate) { Broadcast(ZRoutedRpc.Everybody, customValue2); } }; } private void RPC_FromServerConfigSync(ZRpc rpc, ZPackage package) { lockedConfigChanged += serverLockedSettingChanged; IsSourceOfTruth = false; if (HandleConfigSyncRPC(0L, package, clientUpdate: false)) { InitialSyncDone = true; } } private void RPC_FromOtherClientConfigSync(long sender, ZPackage package) { HandleConfigSyncRPC(sender, package, clientUpdate: true); } private bool HandleConfigSyncRPC(long sender, ZPackage package, bool clientUpdate) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Expected O, but got Unknown //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Expected O, but got Unknown try { if (isServer && IsLocked) { ZRpc? currentRpc = SnatchCurrentlyHandlingRPC.currentRpc; object obj; if (currentRpc == null) { obj = null; } else { ISocket socket = currentRpc.GetSocket(); obj = ((socket != null) ? socket.GetHostName() : null); } string text = (string)obj; if (text != null) { MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList val = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); if (!(((object)methodInfo == null) ? val.Contains(text) : ((bool)methodInfo.Invoke(ZNet.instance, new object[2] { val, text })))) { return false; } } } cacheExpirations.RemoveAll(delegate(KeyValuePair<long, string> kv) { if (kv.Key < DateTimeOffset.Now.Ticks) { configValueCache.Remove(kv.Value); return true; } return false; }); byte b = package.ReadByte(); if ((b & 2u) != 0) { long num = package.ReadLong(); string text2 = sender.ToString() + num; if (!configValueCache.TryGetValue(text2, out SortedDictionary<int, byte[]> value)) { value = new SortedDictionary<int, byte[]>(); configValueCache[text2] = value; cacheExpirations.Add(new KeyValuePair<long, string>(DateTimeOffset.Now.AddSeconds(60.0).Ticks, text2)); } int key = package.ReadInt(); int num2 = package.ReadInt(); value.Add(key, package.ReadByteArray()); if (value.Count < num2) { return false; } configValueCache.Remove(text2); package = new ZPackage(value.Values.SelectMany((byte[] a) => a).ToArray()); b = package.ReadByte(); } ProcessingServerUpdate = true; if ((b & 4u) != 0) { byte[] buffer = package.ReadByteArray(); MemoryStream stream = new MemoryStream(buffer); MemoryStream memoryStream = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress)) { deflateStream.CopyTo(memoryStream); } package = new ZPackage(memoryStream.ToArray()); b = package.ReadByte(); } if ((b & 1) == 0) { resetConfigsFromServer(); } ParsedConfigs parsedConfigs = ReadConfigsFromPackage(package); ConfigFile val2 = null; bool saveOnConfigSet = false; foreach (KeyValuePair<OwnConfigEntryBase, object> configValue in parsedConfigs.configValues) { if (!isServer && configValue.Key.LocalBaseValue == null) { configValue.Key.LocalBaseValue = configValue.Key.BaseConfig.BoxedValue; } if (val2 == null) { val2 = configValue.Key.BaseConfig.ConfigFile; saveOnConfigSet = val2.SaveOnConfigSet; val2.SaveOnConfigSet = false; } configValue.Key.BaseConfig.BoxedValue = configValue.Value; } if (val2 != null) { val2.SaveOnConfigSet = saveOnConfigSet; val2.Save(); } foreach (KeyValuePair<CustomSyncedValueBase, object> customValue in parsedConfigs.customValues) { if (!isServer) { CustomSyncedValueBase key2 = customValue.Key; if (key2.LocalBaseValue == null) { key2.LocalBaseValue = customValue.Key.BoxedValue; } } customValue.Key.BoxedValue = customValue.Value; } Debug.Log((object)string.Format("Received {0} configs and {1} custom values from {2} for mod {3}", parsedConfigs.configValues.Count, parsedConfigs.customValues.Count, (isServer || clientUpdate) ? $"client {sender}" : "the server", DisplayName ?? Name)); if (!isServer) { serverLockedSettingChanged(); } return true; } finally { ProcessingServerUpdate = false; } } private ParsedConfigs ReadConfigsFromPackage(ZPackage package) { ParsedConfigs parsedConfigs = new ParsedConfigs(); Dictionary<string, OwnConfigEntryBase> dictionary = allConfigs.Where((OwnConfigEntryBase c) => c.SynchronizedConfig).ToDictionary((OwnConfigEntryBase c) => c.BaseConfig.Definition.Section + "_" + c.BaseConfig.Definition.Key, (OwnConfigEntryBase c) => c); Dictionary<string, CustomSyncedValueBase> dictionary2 = allCustomValues.ToDictionary((CustomSyncedValueBase c) => c.Identifier, (CustomSyncedValueBase c) => c); int num = package.ReadInt(); for (int i = 0; i < num; i++) { string text = package.ReadString(); string text2 = package.ReadString(); string text3 = package.ReadString(); Type type = Type.GetType(text3); if (text3 == "" || type != null) { object obj; try { obj = ((text3 == "") ? null : ReadValueWithTypeFromZPackage(package, type)); } catch (InvalidDeserializationTypeException ex) { Debug.LogWarning((object)("Got unexpected struct internal type " + ex.received + " for field " + ex.field + " struct " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + ex.expected)); continue; } OwnConfigEntryBase value2; if (text == "Internal") { CustomSyncedValueBase value; if (text2 == "serverversion") { if (obj?.ToString() != CurrentVersion) { Debug.LogWarning((object)("Received server version is not equal: server version = " + (obj?.ToString() ?? "null") + "; local version = " + (CurrentVersion ?? "unknown"))); } } else if (text2 == "lockexempt") { if (obj is bool flag) { lockExempt = flag; } } else if (dictionary2.TryGetValue(text2, out value)) { if ((text3 == "" && (!value.Type.IsValueType || Nullable.GetUnderlyingType(value.Type) != null)) || GetZPackageTypeString(value.Type) == text3) { parsedConfigs.customValues[value] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for internal value " + text2 + " for mod " + (DisplayName ?? Name) + ", expecting " + value.Type.AssemblyQualifiedName)); } } else if (dictionary.TryGetValue(text + "_" + text2, out value2)) { Type type2 = configType(value2.BaseConfig); if ((text3 == "" && (!type2.IsValueType || Nullable.GetUnderlyingType(type2) != null)) || GetZPackageTypeString(type2) == text3) { parsedConfigs.configValues[value2] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + type2.AssemblyQualifiedName)); } else { Debug.LogWarning((object)("Received unknown config entry " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ". This may happen if client and server versions of the mod do not match.")); } continue; } Debug.LogWarning((object)("Got invalid type " + text3 + ", abort reading of received configs")); return new ParsedConfigs(); } return parsedConfigs; } private static bool isWritableConfig(OwnConfigEntryBase config) { OwnConfigEntryBase config2 = config; ConfigSync configSync = configSyncs.FirstOrDefault((ConfigSync cs) => cs.allConfigs.Contains(config2)); if (configSync == null) { return true; } return configSync.IsSourceOfTruth || !config2.SynchronizedConfig || config2.LocalBaseValue == null || (!configSync.IsLocked && (config2 != configSync.lockedConfig || lockExempt)); } private void serverLockedSettingChanged() { foreach (OwnConfigEntryBase allConfig in allConfigs) { configAttribute<ConfigurationManagerAttributes>(allConfig.BaseConfig).ReadOnly = !isWritableConfig(allConfig); } } private void resetConfigsFromServer() { ConfigFile val = null; bool saveOnConfigSet = false; foreach (OwnConfigEntryBase item in allConfigs.Where((OwnConfigEntryBase config) => config.LocalBaseValue != null)) { if (val == null) { val = item.BaseConfig.ConfigFile; saveOnConfigSet = val.SaveOnConfigSet; val.SaveOnConfigSet = false; } item.BaseConfig.BoxedValue = item.LocalBaseValue; item.LocalBaseValue = null; } if (val != null) { val.SaveOnConfigSet = saveOnConfigSet; } foreach (CustomSyncedValueBase item2 in allCustomValues.Where((CustomSyncedValueBase config) => config.LocalBaseValue != null)) { item2.BoxedValue = item2.LocalBaseValue; item2.LocalBaseValue = null; } lockedConfigChanged -= serverLockedSettingChanged; serverLockedSettingChanged(); } private IEnumerator<bool> distributeConfigToPeers(ZNetPeer peer, ZPackage package) { ZNetPeer peer2 = peer; ZRoutedRpc rpc = ZRoutedRpc.instance; if (rpc == null) { yield break; } byte[] data = package.GetArray(); if (data != null &