Decompiled source of FehuTameLimit v420.101.2

BepInEx/plugins/FehuTameLimit/FehuTameLimit.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("FehuTameLimit")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("420.101.1.0")]
[assembly: AssemblyInformationalVersion("420.101.1+FEHU_TAME_RELEASE_20260922")]
[assembly: AssemblyProduct("FehuTameLimit")]
[assembly: AssemblyTitle("FehuTameLimit")]
[assembly: AssemblyVersion("420.101.1.0")]
namespace Forteca.FehuTameLimit;

internal sealed class PendingBirth
{
	internal Vector3 Position;

	internal HashSet<string> BeforeKeys;

	internal long OwnerId;

	internal string OwnerName;

	internal string ClanName;

	internal float Expires;
}
internal static class BreedingService
{
	private static readonly List<PendingBirth> Pending = new List<PendingBirth>();

	private static float _nextSearch;

	internal static HashSet<string> CaptureNearbyKeys(Vector3 pos, float radius)
	{
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: 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)
		HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
		float num = radius * radius;
		Character[] array = Object.FindObjectsByType<Character>((FindObjectsSortMode)0);
		foreach (Character val in array)
		{
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			Vector3 val2 = ((Component)val).transform.position - pos;
			if (((Vector3)(ref val2)).sqrMagnitude > num)
			{
				continue;
			}
			ZNetView component = ((Component)val).GetComponent<ZNetView>();
			if (!((Object)(object)component == (Object)null) && component.IsValid())
			{
				string stableZdoKey = TameOwnership.GetStableZdoKey(component.GetZDO());
				if (!string.IsNullOrEmpty(stableZdoKey))
				{
					hashSet.Add(stableZdoKey);
				}
			}
		}
		return hashSet;
	}

	internal static void Add(PendingBirth birth)
	{
		if (birth != null)
		{
			Pending.Add(birth);
		}
	}

	internal static void Clear()
	{
		Pending.Clear();
		_nextSearch = 0f;
	}

	internal static void UpdatePending()
	{
		if (!FehuTameLimitPlugin.IsServer() || Pending.Count == 0)
		{
			return;
		}
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		if (realtimeSinceStartup < _nextSearch)
		{
			return;
		}
		_nextSearch = realtimeSinceStartup + 0.25f;
		Character[] candidates = Object.FindObjectsByType<Character>((FindObjectsSortMode)0);
		for (int num = Pending.Count - 1; num >= 0; num--)
		{
			PendingBirth pendingBirth = Pending[num];
			if (TryResolveChild(pendingBirth, candidates) || realtimeSinceStartup >= pendingBirth.Expires)
			{
				Pending.RemoveAt(num);
			}
		}
	}

	private static bool TryResolveChild(PendingBirth birth, Character[] candidates)
	{
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		float num = 144f;
		foreach (Character val in candidates)
		{
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			Vector3 val2 = ((Component)val).transform.position - birth.Position;
			if (((Vector3)(ref val2)).sqrMagnitude > num)
			{
				continue;
			}
			GameObject gameObject = ((Component)val).gameObject;
			if (!TameOwnership.IsTameCandidate(gameObject))
			{
				continue;
			}
			ZNetView component = gameObject.GetComponent<ZNetView>();
			if ((Object)(object)component == (Object)null || !component.IsValid())
			{
				continue;
			}
			string stableZdoKey = TameOwnership.GetStableZdoKey(component.GetZDO());
			if (string.IsNullOrEmpty(stableZdoKey) || birth.BeforeKeys.Contains(stableZdoKey))
			{
				continue;
			}
			if (TameOwnership.TryRead(gameObject, out var identity) && identity.HasOwner)
			{
				if (identity.OwnerId == birth.OwnerId)
				{
					TameOwnership.WriteState(identity.Zdo, TameRuntimeState.OwnedInactive);
					identity.State = TameRuntimeState.OwnedInactive;
					identity.HasExplicitState = true;
					TameRegistry.Upsert(identity);
					TameRegistry.FlushIfDirty();
					return true;
				}
				continue;
			}
			OwnershipService.TryAssign(gameObject, birth.OwnerId, birth.OwnerName, birth.ClanName, "breeding", preferRoster: false);
			if (TameOwnership.TryRead(gameObject, out var identity2))
			{
				TameOwnership.WriteState(identity2.Zdo, TameRuntimeState.OwnedInactive);
				identity2.State = TameRuntimeState.OwnedInactive;
				identity2.HasExplicitState = true;
				TameRegistry.Upsert(identity2);
			}
			TameRegistry.FlushIfDirty();
			return true;
		}
		return false;
	}
}
internal static class ClanBridge
{
	private sealed class CacheEntry
	{
		internal string Clan;

		internal bool ClanResolved;

		internal bool Supporter;

		internal bool SupporterResolved;

		internal float Time;
	}

	private static readonly Dictionary<long, CacheEntry> Cache = new Dictionary<long, CacheEntry>();

	private const float CacheSeconds = 15f;

	private static bool _fcwTypeResolved;

	private static Type _fcwType;

	private static bool _guildsTypeResolved;

	private static Type _guildsType;

	internal static void Reset()
	{
		Cache.Clear();
	}

	internal static bool TryResolve(long ownerId, string ownerName, out string clan)
	{
		CacheEntry cacheEntry = Resolve(ownerId, ownerName);
		clan = cacheEntry.Clan ?? string.Empty;
		return cacheEntry.ClanResolved;
	}

	internal static bool IsSupporter(long ownerId, string ownerName)
	{
		CacheEntry cacheEntry = Resolve(ownerId, ownerName);
		if (cacheEntry.SupporterResolved)
		{
			return cacheEntry.Supporter;
		}
		return false;
	}

	private static CacheEntry Resolve(long ownerId, string ownerName)
	{
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		if (ownerId != 0L && Cache.TryGetValue(ownerId, out var value) && realtimeSinceStartup - value.Time < 15f)
		{
			return value;
		}
		CacheEntry cacheEntry = new CacheEntry
		{
			Clan = string.Empty,
			Time = realtimeSinceStartup
		};
		if (ownerId != 0L && TryResolveFcw(ownerId, out var clan, out var supporter, out var supporterResolved))
		{
			cacheEntry.Clan = TameOwnership.NormalizeClan(clan);
			cacheEntry.ClanResolved = true;
			cacheEntry.Supporter = supporter;
			cacheEntry.SupporterResolved = supporterResolved;
		}
		else
		{
			cacheEntry.ClanResolved = TryResolveGuilds(ownerId, ownerName, out var clan2);
			cacheEntry.Clan = TameOwnership.NormalizeClan(clan2);
		}
		if (ownerId != 0L)
		{
			Cache[ownerId] = cacheEntry;
		}
		return cacheEntry;
	}

	private static bool TryResolveFcw(long ownerId, out string clan, out bool supporter, out bool supporterResolved)
	{
		clan = string.Empty;
		supporter = false;
		supporterResolved = false;
		Type type = ResolveOptionalType("FortecaClanWarfare.Core.WarfareManager", ref _fcwTypeResolved, ref _fcwType);
		if (type == null)
		{
			return false;
		}
		object obj = GetStaticMember(type, "Instance") ?? GetStaticMember(type, "instance");
		if (obj == null)
		{
			return false;
		}
		object instanceMember = GetInstanceMember(obj, "Data");
		if (instanceMember == null)
		{
			return false;
		}
		object obj2 = InvokeSingleArg(instanceMember, "GetPlayer", ownerId);
		if (obj2 == null)
		{
			return false;
		}
		long num = ToInt64(GetInstanceMember(obj2, "ClanId"));
		if (num != 0L)
		{
			object obj3 = InvokeSingleArg(instanceMember, "GetClan", num);
			clan = ((obj3 != null) ? GetInstanceMember(obj3, "Name") : null)?.ToString() ?? num.ToString();
		}
		object instanceMember2 = GetInstanceMember(obj2, "SteamId64");
		if (instanceMember2 != null)
		{
			object obj4 = InvokeSingleArg(obj, "GetSupporterBadges", instanceMember2);
			if (obj4 != null)
			{
				supporterResolved = true;
				supporter = ContainsSupporterBadge(obj4);
			}
		}
		return true;
	}

	private static bool ContainsSupporterBadge(object badges)
	{
		if (badges == null)
		{
			return false;
		}
		if (badges is string text)
		{
			if (!string.Equals(text.Trim(), "Supporter", StringComparison.OrdinalIgnoreCase))
			{
				return text.IndexOf("Supporter", StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return true;
		}
		if (badges is IEnumerable enumerable)
		{
			foreach (object item in enumerable)
			{
				if (item != null)
				{
					if (string.Equals(item.ToString(), "Supporter", StringComparison.OrdinalIgnoreCase))
					{
						return true;
					}
					object obj = GetInstanceMember(item, "Name") ?? GetInstanceMember(item, "Id") ?? GetInstanceMember(item, "Key");
					if (obj != null && string.Equals(obj.ToString(), "Supporter", StringComparison.OrdinalIgnoreCase))
					{
						return true;
					}
				}
			}
		}
		return false;
	}

	private static bool TryResolveGuilds(long ownerId, string ownerName, out string clan)
	{
		clan = string.Empty;
		Type type = ResolveOptionalType("Guilds.API", ref _guildsTypeResolved, ref _guildsType);
		if (type == null)
		{
			return false;
		}
		string[] array = new string[3] { "GetPlayerGuild", "GetGuildForPlayer", "GetPlayerGuildName" };
		foreach (string name in array)
		{
			foreach (MethodInfo item in from m in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
				where m.Name == name
				select m)
			{
				ParameterInfo[] parameters = item.GetParameters();
				if (parameters.Length != 1)
				{
					continue;
				}
				object obj = ConvertArg(parameters[0].ParameterType, ownerId, ownerName);
				if (obj == null)
				{
					continue;
				}
				try
				{
					object obj2 = item.Invoke(null, new object[1] { obj });
					if (TryExtractClan(obj2, out clan))
					{
						return true;
					}
					if (obj2 == null)
					{
						return true;
					}
				}
				catch
				{
				}
			}
		}
		return false;
	}

	private static Type ResolveOptionalType(string fullName, ref bool resolved, ref Type cached)
	{
		if (resolved)
		{
			return cached;
		}
		resolved = true;
		Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
		foreach (Assembly assembly in assemblies)
		{
			try
			{
				Type type = assembly.GetType(fullName, throwOnError: false);
				if (type != null)
				{
					cached = type;
					break;
				}
			}
			catch
			{
			}
		}
		return cached;
	}

	private static object InvokeSingleArg(object target, string name, object value)
	{
		if (target == null)
		{
			return null;
		}
		foreach (MethodInfo item in from m in target.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
			where m.Name == name
			select m)
		{
			ParameterInfo[] parameters = item.GetParameters();
			if (parameters.Length != 1)
			{
				continue;
			}
			object obj = ConvertArg(parameters[0].ParameterType, value);
			if (obj != null || value == null || !parameters[0].ParameterType.IsValueType)
			{
				try
				{
					return item.Invoke(target, new object[1] { obj });
				}
				catch
				{
				}
			}
		}
		return null;
	}

	private static object ConvertArg(Type targetType, object value)
	{
		if (value == null)
		{
			return null;
		}
		if (targetType.IsInstanceOfType(value))
		{
			return value;
		}
		try
		{
			if (targetType == typeof(string))
			{
				return value.ToString();
			}
			if (targetType == typeof(long))
			{
				return Convert.ToInt64(value);
			}
			if (targetType == typeof(ulong))
			{
				return Convert.ToUInt64(value);
			}
			if (targetType == typeof(int))
			{
				return Convert.ToInt32(value);
			}
			return Convert.ChangeType(value, targetType);
		}
		catch
		{
			return null;
		}
	}

	private static object ConvertArg(Type targetType, long ownerId, string ownerName)
	{
		if (targetType == typeof(string))
		{
			return ownerName ?? string.Empty;
		}
		return ConvertArg(targetType, ownerId);
	}

	private static object GetStaticMember(Type type, string name)
	{
		try
		{
			PropertyInfo property = type.GetProperty(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (property != null)
			{
				return property.GetValue(null, null);
			}
			return type.GetField(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null);
		}
		catch
		{
			return null;
		}
	}

	private static object GetInstanceMember(object target, string name)
	{
		if (target == null)
		{
			return null;
		}
		try
		{
			Type type = target.GetType();
			PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (property != null)
			{
				return property.GetValue(target, null);
			}
			return type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(target);
		}
		catch
		{
			return null;
		}
	}

	private static bool TryExtractClan(object value, out string clan)
	{
		clan = string.Empty;
		if (value == null)
		{
			return false;
		}
		if (value is string text)
		{
			clan = text;
			return true;
		}
		string[] array = new string[7] { "ClanName", "Name", "Tag", "ClanId", "ClanID", "Id", "ID" };
		foreach (string name in array)
		{
			object instanceMember = GetInstanceMember(value, name);
			if (instanceMember != null)
			{
				clan = instanceMember.ToString();
				return true;
			}
		}
		return false;
	}

	private static long ToInt64(object value)
	{
		try
		{
			return (value == null) ? 0 : Convert.ToInt64(value);
		}
		catch
		{
			return 0L;
		}
	}
}
internal sealed class FehuConfig
{
	internal readonly ConfigEntry<bool> Enabled;

	internal readonly ConfigEntry<bool> EnforceRosterLimit;

	internal readonly ConfigEntry<int> NormalPlayerRoster;

	internal readonly ConfigEntry<bool> EnforceActiveLimit;

	internal readonly ConfigEntry<int> NormalPlayerActive;

	internal readonly ConfigEntry<int> SupporterActive;

	internal readonly ConfigEntry<bool> EnforceClanActiveLimit;

	internal readonly ConfigEntry<int> ClanActive;

	internal readonly ConfigEntry<bool> NewTamesEnterRoster;

	internal readonly ConfigEntry<float> OwnershipAssignRadius;

	internal readonly ConfigEntry<bool> AdoptOnTamedInteract;

	internal readonly ConfigEntry<bool> GuardWardCountsAsTameGuard;

	internal readonly ConfigEntry<bool> BootstrapGlobalZdoScan;

	internal readonly ConfigEntry<float> ReconcileSeconds;

	internal readonly ConfigEntry<float> ClanRefreshSeconds;

	internal readonly ConfigEntry<float> SnapshotSeconds;

	internal readonly ConfigEntry<bool> DebugLogging;

	internal int RosterLimit => NonNegativeOrDefault(NormalPlayerRoster.Value, 6);

	internal int NormalActiveLimit => NonNegativeOrDefault(NormalPlayerActive.Value, 3);

	internal int SupporterActiveLimit => NonNegativeOrDefault(SupporterActive.Value, 6);

	internal int ClanActiveLimit => NonNegativeOrDefault(ClanActive.Value, 50);

	private static int NonNegativeOrDefault(int value, int fallback)
	{
		if (value >= 0)
		{
			return value;
		}
		return fallback;
	}

	internal static float PositiveOrDefault(float value, float minimum, float fallback)
	{
		if (float.IsNaN(value) || float.IsInfinity(value))
		{
			return fallback;
		}
		return Math.Max(minimum, value);
	}

	internal FehuConfig(ConfigFile config)
	{
		Enabled = config.Bind<bool>("General", "Enabled", true, "Master switch.");
		EnforceRosterLimit = config.Bind<bool>("Limits", "EnforceRosterLimit", true, "Limit how many non-guard tames may be in a player's roster at once.");
		NormalPlayerRoster = config.Bind<int>("Limits", "NormalPlayerRoster", 6, "Maximum roster size per player. Ownership outside the roster remains unlimited. 0 means unlimited; negative values use the default.");
		EnforceActiveLimit = config.Bind<bool>("Limits", "EnforceActiveLimit", true, "Limit released/active tames per player.");
		NormalPlayerActive = config.Bind<int>("Limits", "NormalPlayerActive", 3, "Maximum released/active tames for a normal player. 0 means unlimited; negative values use the default.");
		SupporterActive = config.Bind<int>("Limits", "SupporterActive", 6, "Maximum released/active tames for a Supporter. 0 means unlimited; negative values use the default.");
		EnforceClanActiveLimit = config.Bind<bool>("Limits", "EnforceClanActiveLimit", true, "Limit released/active tames across an FCW clan.");
		ClanActive = config.Bind<int>("Limits", "ClanActive", 50, "Maximum released/active non-guard tames across an FCW clan. TameGuards are excluded. 0 means unlimited; negative values use the default.");
		NewTamesEnterRoster = config.Bind<bool>("State", "NewTamesEnterRoster", true, "Newly tamed/adopted creatures enter the roster when a roster slot exists; otherwise they remain owned/inactive.");
		OwnershipAssignRadius = config.Bind<float>("Ownership", "OwnershipAssignRadius", 100f, "Maximum radius used to attribute a newly tamed/legacy breeding animal to the nearest online player.");
		AdoptOnTamedInteract = config.Bind<bool>("Ownership", "AdoptOnTamedInteract", true, "When server-side interaction is observed, stamp an unowned legacy tame to that player without limiting total ownership.");
		GuardWardCountsAsTameGuard = config.Bind<bool>("Compatibility", "GuardWardCountsAsTameGuard", true, "Treat TGW GuardWard stance and Fehu TCW guard metadata as TameGuard state, excluded from roster/active/clan caps.");
		BootstrapGlobalZdoScan = config.Bind<bool>("Persistence", "BootstrapGlobalZdoScan", true, "One-time incremental ZDO bootstrap for FTL/TGW-owned animals after world load.");
		ReconcileSeconds = config.Bind<float>("Performance", "ReconcileSeconds", 20f, "Loaded tame state reconciliation interval.");
		ClanRefreshSeconds = config.Bind<float>("Performance", "ClanRefreshSeconds", 30f, "How often stored FCW clan assignments are refreshed.");
		SnapshotSeconds = config.Bind<float>("Performance", "SnapshotSeconds", 2f, "Authoritative roster/active count snapshot interval to modded clients.");
		DebugLogging = config.Bind<bool>("Debug", "DebugLogging", false, "Verbose diagnostic logging.");
	}
}
internal readonly struct LimitDecision
{
	internal readonly bool Allowed;

	internal readonly string Message;

	internal readonly int RosterCount;

	internal readonly int RosterLimit;

	internal readonly int ActiveCount;

	internal readonly int ActiveLimit;

	internal readonly int ClanActiveCount;

	internal readonly int ClanActiveLimit;

	internal readonly bool Supporter;

	internal LimitDecision(bool allowed, string message, int rosterCount, int rosterLimit, int activeCount, int activeLimit, int clanActiveCount, int clanActiveLimit, bool supporter)
	{
		Allowed = allowed;
		Message = message;
		RosterCount = rosterCount;
		RosterLimit = rosterLimit;
		ActiveCount = activeCount;
		ActiveLimit = activeLimit;
		ClanActiveCount = clanActiveCount;
		ClanActiveLimit = clanActiveLimit;
		Supporter = supporter;
	}
}
internal static class LimitService
{
	internal static LimitDecision CanTransition(in TameIdentity identity, TameRuntimeState target)
	{
		int rosterLimit = FehuTameLimitPlugin.Settings.RosterLimit;
		bool flag = ClanBridge.IsSupporter(identity.OwnerId, identity.OwnerName);
		int num = (flag ? FehuTameLimitPlugin.Settings.SupporterActiveLimit : FehuTameLimitPlugin.Settings.NormalActiveLimit);
		int clanActiveLimit = FehuTameLimitPlugin.Settings.ClanActiveLimit;
		int num2 = TameRegistry.CountPlayerRoster(identity.OwnerId, identity.Key);
		int num3 = TameRegistry.CountPlayerActive(identity.OwnerId, identity.Key);
		int num4 = (IsRealClan(identity.ClanName) ? TameRegistry.CountClanActive(identity.ClanName, identity.Key) : 0);
		if (!FehuTameLimitPlugin.Settings.Enabled.Value || target == identity.State || target == TameRuntimeState.OwnedInactive || target == TameRuntimeState.TameGuard)
		{
			return new LimitDecision(allowed: true, string.Empty, num2, rosterLimit, num3, num, num4, clanActiveLimit, flag);
		}
		bool flag2 = identity.State == TameRuntimeState.Roster || identity.State == TameRuntimeState.Active;
		if ((target == TameRuntimeState.Roster || target == TameRuntimeState.Active) && !flag2 && FehuTameLimitPlugin.Settings.EnforceRosterLimit.Value && rosterLimit > 0 && num2 >= rosterLimit)
		{
			return new LimitDecision(allowed: false, $"Tame roster full: {num2}/{rosterLimit}", num2, rosterLimit, num3, num, num4, clanActiveLimit, flag);
		}
		if (target == TameRuntimeState.Active)
		{
			if (FehuTameLimitPlugin.Settings.EnforceActiveLimit.Value && num > 0 && num3 >= num)
			{
				return new LimitDecision(allowed: false, string.Format("Active tame limit reached: {0}/{1}{2}", num3, num, flag ? " (Supporter)" : string.Empty), num2, rosterLimit, num3, num, num4, clanActiveLimit, flag);
			}
			if (FehuTameLimitPlugin.Settings.EnforceClanActiveLimit.Value && clanActiveLimit > 0 && IsRealClan(identity.ClanName) && num4 >= clanActiveLimit)
			{
				return new LimitDecision(allowed: false, $"Clan active tame limit reached: {num4}/{clanActiveLimit}", num2, rosterLimit, num3, num, num4, clanActiveLimit, flag);
			}
		}
		return new LimitDecision(allowed: true, string.Empty, num2, rosterLimit, num3, num, num4, clanActiveLimit, flag);
	}

	internal static bool IsRealClan(string clan)
	{
		return !string.IsNullOrWhiteSpace(TameOwnership.NormalizeClan(clan));
	}
}
internal static class NetworkSync
{
	private const string RpcSnapshot = "FehuTameLimit_Snapshot_v3";

	private const string RpcNotice = "FehuTameLimit_Notice_v3";

	private static ZRoutedRpc _registeredRpc;

	private static bool _haveSnapshot;

	private static int _lastSnapshotRevision = -1;

	private static float _nextHeartbeat;

	private static readonly Dictionary<long, int> PlayerRoster = new Dictionary<long, int>();

	private static readonly Dictionary<long, int> PlayerActive = new Dictionary<long, int>();

	private static readonly Dictionary<string, int> ClanActive = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

	internal static bool HaveSnapshot => _haveSnapshot;

	internal static int ServerRosterLimit { get; private set; } = 6;

	internal static int ServerNormalActiveLimit { get; private set; } = 3;

	internal static int ServerSupporterActiveLimit { get; private set; } = 6;

	internal static int ServerClanActiveLimit { get; private set; } = 50;

	internal static void TryRegister()
	{
		if (ZRoutedRpc.instance == null || _registeredRpc == ZRoutedRpc.instance)
		{
			return;
		}
		try
		{
			ZRoutedRpc.instance.Register<ZPackage>("FehuTameLimit_Snapshot_v3", (Action<long, ZPackage>)OnSnapshot);
			ZRoutedRpc.instance.Register<ZPackage>("FehuTameLimit_Notice_v3", (Action<long, ZPackage>)OnNotice);
			_registeredRpc = ZRoutedRpc.instance;
		}
		catch (Exception ex)
		{
			if (FehuTameLimitPlugin.Settings.DebugLogging.Value)
			{
				FehuTameLimitPlugin.Log.LogDebug((object)("RPC registration deferred: " + ex.Message));
			}
		}
	}

	internal static void BroadcastSnapshot()
	{
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Expected O, but got Unknown
		if (!FehuTameLimitPlugin.IsServer() || ZRoutedRpc.instance == null)
		{
			return;
		}
		int revision = TameRegistry.Revision;
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		if (revision == _lastSnapshotRevision && realtimeSinceStartup < _nextHeartbeat)
		{
			return;
		}
		ZPackage val = new ZPackage();
		val.Write(3);
		val.Write(FehuTameLimitPlugin.Settings.RosterLimit);
		val.Write(FehuTameLimitPlugin.Settings.NormalActiveLimit);
		val.Write(FehuTameLimitPlugin.Settings.SupporterActiveLimit);
		val.Write(FehuTameLimitPlugin.Settings.ClanActiveLimit);
		Dictionary<long, int> playerRosterCounts = TameRegistry.GetPlayerRosterCounts();
		val.Write(playerRosterCounts.Count);
		foreach (KeyValuePair<long, int> item in playerRosterCounts)
		{
			val.Write(item.Key);
			val.Write(item.Value);
		}
		Dictionary<long, int> playerActiveCounts = TameRegistry.GetPlayerActiveCounts();
		val.Write(playerActiveCounts.Count);
		foreach (KeyValuePair<long, int> item2 in playerActiveCounts)
		{
			val.Write(item2.Key);
			val.Write(item2.Value);
		}
		Dictionary<string, int> clanActiveCounts = TameRegistry.GetClanActiveCounts();
		val.Write(clanActiveCounts.Count);
		foreach (KeyValuePair<string, int> item3 in clanActiveCounts)
		{
			val.Write(item3.Key ?? string.Empty);
			val.Write(item3.Value);
		}
		ZRoutedRpc.instance.InvokeRoutedRPC(0L, "FehuTameLimit_Snapshot_v3", new object[1] { val });
		_lastSnapshotRevision = revision;
		_nextHeartbeat = realtimeSinceStartup + 10f;
	}

	internal static void SendNotice(long ownerId, string message)
	{
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Expected O, but got Unknown
		if (FehuTameLimitPlugin.IsServer() && ZRoutedRpc.instance != null && ownerId != 0L && !string.IsNullOrEmpty(message))
		{
			ZPackage val = new ZPackage();
			val.Write(ownerId);
			val.Write(message);
			ZRoutedRpc.instance.InvokeRoutedRPC(0L, "FehuTameLimit_Notice_v3", new object[1] { val });
		}
	}

	private static void OnSnapshot(long sender, ZPackage pkg)
	{
		if (!IsFromServer(sender))
		{
			return;
		}
		try
		{
			if (pkg.ReadInt() == 3)
			{
				ServerRosterLimit = pkg.ReadInt();
				ServerNormalActiveLimit = pkg.ReadInt();
				ServerSupporterActiveLimit = pkg.ReadInt();
				ServerClanActiveLimit = pkg.ReadInt();
				PlayerRoster.Clear();
				int num = pkg.ReadInt();
				for (int i = 0; i < num; i++)
				{
					PlayerRoster[pkg.ReadLong()] = pkg.ReadInt();
				}
				PlayerActive.Clear();
				int num2 = pkg.ReadInt();
				for (int j = 0; j < num2; j++)
				{
					PlayerActive[pkg.ReadLong()] = pkg.ReadInt();
				}
				ClanActive.Clear();
				int num3 = pkg.ReadInt();
				for (int k = 0; k < num3; k++)
				{
					ClanActive[pkg.ReadString()] = pkg.ReadInt();
				}
				_haveSnapshot = true;
			}
		}
		catch (Exception ex)
		{
			FehuTameLimitPlugin.Log.LogWarning((object)("Invalid tame-limit snapshot: " + ex.Message));
		}
	}

	private static void OnNotice(long sender, ZPackage pkg)
	{
		if (!IsFromServer(sender))
		{
			return;
		}
		try
		{
			long num = pkg.ReadLong();
			string text = pkg.ReadString();
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null && localPlayer.GetPlayerID() == num)
			{
				((Character)localPlayer).Message((MessageType)2, text, 0, (Sprite)null, false);
			}
		}
		catch
		{
		}
	}

	internal static int GetPlayerRoster(long id)
	{
		if (!PlayerRoster.TryGetValue(id, out var value))
		{
			return 0;
		}
		return value;
	}

	internal static int GetPlayerActive(long id)
	{
		if (!PlayerActive.TryGetValue(id, out var value))
		{
			return 0;
		}
		return value;
	}

	internal static int GetClanActive(string clan)
	{
		if (!ClanActive.TryGetValue(clan ?? string.Empty, out var value))
		{
			return 0;
		}
		return value;
	}

	internal static void ClearClientCache()
	{
		PlayerRoster.Clear();
		PlayerActive.Clear();
		ClanActive.Clear();
		_haveSnapshot = false;
	}

	private static bool IsFromServer(long sender)
	{
		ZNet instance = ZNet.instance;
		if ((Object)(object)instance == (Object)null)
		{
			return false;
		}
		if (instance.IsServer())
		{
			return sender == ZNet.GetUID();
		}
		ZNetPeer serverPeer = instance.GetServerPeer();
		if (serverPeer != null)
		{
			return sender == serverPeer.m_uid;
		}
		return false;
	}
}
internal static class OwnershipService
{
	internal static Player FindNearestPlayer(Vector3 position, float radius)
	{
		//IL_0035: 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_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)
		Player result = null;
		float num = radius * radius;
		Player[] array = Object.FindObjectsByType<Player>((FindObjectsSortMode)0);
		foreach (Player val in array)
		{
			if (!((Object)(object)val == (Object)null) && ((Component)val).gameObject.activeInHierarchy)
			{
				Vector3 val2 = ((Component)val).transform.position - position;
				float sqrMagnitude = ((Vector3)(ref val2)).sqrMagnitude;
				if (sqrMagnitude <= num)
				{
					num = sqrMagnitude;
					result = val;
				}
			}
		}
		return result;
	}

	internal static string ResolveClan(long ownerId, string ownerName, string fallback)
	{
		if (ClanBridge.TryResolve(ownerId, ownerName, out var clan))
		{
			return clan;
		}
		return TameOwnership.NormalizeClan(fallback);
	}

	internal static bool TryAssign(GameObject go, Player player, string source, bool preferRoster)
	{
		if ((Object)(object)go == (Object)null || (Object)(object)player == (Object)null)
		{
			return false;
		}
		string ownerName = SafeName(player);
		return TryAssign(go, player.GetPlayerID(), ownerName, ResolveClan(player.GetPlayerID(), ownerName, string.Empty), source, preferRoster);
	}

	internal static bool TryAssign(GameObject go, long ownerId, string ownerName, string clan, string source, bool preferRoster)
	{
		if ((Object)(object)go == (Object)null || ownerId == 0L)
		{
			return false;
		}
		ZNetView component = go.GetComponent<ZNetView>();
		if ((Object)(object)component == (Object)null || !component.IsValid() || component.GetZDO() == null)
		{
			return false;
		}
		string clan2 = ResolveClan(ownerId, ownerName, clan);
		if (TameOwnership.TryRead(go, out var identity) && identity.OwnerId == ownerId)
		{
			TameOwnership.WriteOwnershipOnly(component.GetZDO(), ownerId, ownerName, clan2, source);
			if (!identity.HasExplicitState)
			{
				TameOwnership.WriteState(component.GetZDO(), TameStateService.ChooseInitialState(go, ownerId, ownerName, clan2, preferRoster));
			}
			if (TameOwnership.TryRead(go, out var identity2))
			{
				TameRegistry.Upsert(identity2);
			}
			return true;
		}
		TameRuntimeState state = TameStateService.ChooseInitialState(go, ownerId, ownerName, clan2, preferRoster);
		TameOwnership.WriteFtl(component.GetZDO(), ownerId, ownerName, clan2, source, state);
		if (TameOwnership.TryRead(go, out var identity3))
		{
			TameRegistry.Upsert(identity3);
		}
		return true;
	}

	internal static bool TryEnsureBreedingOwner(GameObject go, out TameIdentity identity)
	{
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		if (TameOwnership.TryRead(go, out identity) && identity.HasOwner)
		{
			string text = ResolveClan(identity.OwnerId, identity.OwnerName, identity.ClanName);
			if (!string.Equals(text, identity.ClanName, StringComparison.OrdinalIgnoreCase) && identity.Zdo != null)
			{
				TameOwnership.UpdateClan(identity.Zdo, text);
				identity.ClanName = text;
				TameRegistry.Upsert(identity);
			}
			return true;
		}
		Player val = FindNearestPlayer(go.transform.position, FehuConfig.PositiveOrDefault(FehuTameLimitPlugin.Settings.OwnershipAssignRadius.Value, 1f, 100f));
		if ((Object)(object)val == (Object)null)
		{
			identity = default(TameIdentity);
			return false;
		}
		if (!TryAssign(go, val, "legacy-breeding", preferRoster: false))
		{
			identity = default(TameIdentity);
			return false;
		}
		return TameOwnership.TryRead(go, out identity);
	}

	internal static string SafeName(Player player)
	{
		try
		{
			return player.GetPlayerName() ?? string.Empty;
		}
		catch
		{
			return string.Empty;
		}
	}
}
[HarmonyPatch(typeof(Procreation), "Procreate")]
internal static class ProcreationOwnershipPatch
{
	private sealed class State
	{
		internal PendingBirth Birth;
	}

	private static void Prefix(Procreation __instance, ref object __state)
	{
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		//IL_005b: Unknown result type (might be due to invalid IL or missing references)
		__state = null;
		if (FehuTameLimitPlugin.IsServer() && FehuTameLimitPlugin.Settings.Enabled.Value && !((Object)(object)__instance == (Object)null))
		{
			GameObject gameObject = ((Component)__instance).gameObject;
			if (OwnershipService.TryEnsureBreedingOwner(gameObject, out var identity))
			{
				__state = new State
				{
					Birth = new PendingBirth
					{
						Position = gameObject.transform.position,
						BeforeKeys = BreedingService.CaptureNearbyKeys(gameObject.transform.position, 12f),
						OwnerId = identity.OwnerId,
						OwnerName = identity.OwnerName,
						ClanName = identity.ClanName,
						Expires = Time.realtimeSinceStartup + 8f
					}
				};
			}
		}
	}

	private static void Postfix(object __state)
	{
		State state = __state as State;
		if (state?.Birth != null)
		{
			BreedingService.Add(state.Birth);
		}
	}
}
[HarmonyPatch(typeof(Character), "OnDeath")]
internal static class CharacterDeathPatch
{
	private static void Prefix(Character __instance)
	{
		if (FehuTameLimitPlugin.IsServer() && !((Object)(object)__instance == (Object)null))
		{
			TameRegistry.Remove(((Component)__instance).gameObject);
			TameRegistry.FlushIfDirty();
		}
	}
}
[HarmonyPatch(typeof(Game), "Start")]
internal static class GameStartPatch
{
	private static void Postfix()
	{
		NetworkSync.TryRegister();
	}
}
[BepInPlugin("com.forteca.fehutamelimit", "Fehu Tame Limit", "420.101.1")]
public sealed class FehuTameLimitPlugin : BaseUnityPlugin
{
	public const string PluginGuid = "com.forteca.fehutamelimit";

	public const string PluginName = "Fehu Tame Limit";

	public const string PluginVersion = "420.101.1";

	public const string BuildMarker = "FEHU_TAME_RELEASE_20260922";

	internal static FehuTameLimitPlugin Instance;

	internal static ManualLogSource Log;

	internal static FehuConfig Settings;

	private Harmony _harmony;

	private long _worldUid = long.MinValue;

	private float _nextReconcile;

	private float _nextClanRefresh;

	private float _nextSnapshot;

	private void Awake()
	{
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		//IL_0031: Expected O, but got Unknown
		Instance = this;
		Log = ((BaseUnityPlugin)this).Logger;
		Settings = new FehuConfig(((BaseUnityPlugin)this).Config);
		_harmony = new Harmony("com.forteca.fehutamelimit");
		_harmony.PatchAll(typeof(FehuTameLimitPlugin).Assembly);
		VanillaIntegration.TryInstall(_harmony);
		Log.LogInfo((object)string.Format("{0} {1} [{2}] loaded. Ownership=unlimited, Roster={3}, Active={4}, SupporterActive={5}, ClanActive={6}, TameGuards excluded.", "Fehu Tame Limit", "420.101.1", "FEHU_TAME_RELEASE_20260922", Settings.RosterLimit, Settings.NormalActiveLimit, Settings.SupporterActiveLimit, Settings.ClanActiveLimit));
	}

	private IEnumerator Start()
	{
		yield return null;
		TgwIntegration.InitializeOnce(_harmony);
	}

	private void Update()
	{
		if (Settings == null || !Settings.Enabled.Value)
		{
			return;
		}
		NetworkSync.TryRegister();
		BreedingService.UpdatePending();
		if (IsServer() && EnsureWorld())
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (realtimeSinceStartup >= _nextReconcile)
			{
				_nextReconcile = realtimeSinceStartup + FehuConfig.PositiveOrDefault(Settings.ReconcileSeconds.Value, 2f, 20f);
				TameRegistry.ReconcileLoadedCharacters();
				TameRegistry.FlushIfDirty();
			}
			if (realtimeSinceStartup >= _nextClanRefresh)
			{
				_nextClanRefresh = realtimeSinceStartup + FehuConfig.PositiveOrDefault(Settings.ClanRefreshSeconds.Value, 5f, 30f);
				ClanBridge.Reset();
				TameRegistry.RefreshClanAssignments();
			}
			if (realtimeSinceStartup >= _nextSnapshot)
			{
				_nextSnapshot = realtimeSinceStartup + FehuConfig.PositiveOrDefault(Settings.SnapshotSeconds.Value, 0.5f, 2f);
				NetworkSync.BroadcastSnapshot();
			}
		}
	}

	private bool EnsureWorld()
	{
		if ((Object)(object)ZNet.instance == (Object)null)
		{
			return false;
		}
		long worldUID;
		try
		{
			worldUID = ZNet.instance.GetWorldUID();
		}
		catch
		{
			return false;
		}
		if (worldUID == 0L)
		{
			return false;
		}
		if (_worldUid == worldUID)
		{
			return true;
		}
		_worldUid = worldUID;
		BreedingService.Clear();
		TameRegistry.LoadWorld(worldUID, SafeWorldName());
		NetworkSync.ClearClientCache();
		ClanBridge.Reset();
		if (Settings.BootstrapGlobalZdoScan.Value)
		{
			((MonoBehaviour)this).StartCoroutine(TameRegistry.BootstrapWorldZdos());
		}
		else
		{
			TameRegistry.ReconcileLoadedCharacters();
		}
		return true;
	}

	internal static bool IsServer()
	{
		if ((Object)(object)ZNet.instance != (Object)null)
		{
			return ZNet.instance.IsServer();
		}
		return false;
	}

	internal static string SafeWorldName()
	{
		try
		{
			return ((Object)(object)ZNet.instance != (Object)null) ? (ZNet.instance.GetWorldName() ?? "world") : "world";
		}
		catch
		{
			return "world";
		}
	}

	private void OnDestroy()
	{
		try
		{
			if (IsServer())
			{
				TameRegistry.Flush(force: true);
			}
		}
		catch
		{
		}
		Harmony harmony = _harmony;
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
	}
}
internal enum OwnershipSource
{
	None,
	FehuTameLimit,
	TameGuildWars
}
internal enum TameRuntimeState
{
	OwnedInactive,
	Roster,
	Active,
	TameGuard
}
internal struct TameIdentity
{
	internal string Key;

	internal long OwnerId;

	internal string OwnerName;

	internal string ClanName;

	internal string PrefabName;

	internal OwnershipSource Source;

	internal TameRuntimeState State;

	internal bool HasExplicitState;

	internal ZDO Zdo;

	internal GameObject GameObject;

	internal bool HasOwner => OwnerId != 0;
}
internal static class TameOwnership
{
	internal const string FtlOwnerId = "FTL_OwnerID";

	internal const string FtlOwnerName = "FTL_OwnerName";

	internal const string FtlClan = "FTL_Clan";

	internal const string FtlState = "FTL_State";

	internal const string FtlActive = "FTL_Active";

	internal const string FtlSource = "FTL_Source";

	internal const string TgwOwnerId = "TGW_TamerID";

	internal const string TgwOwnerName = "TGW_TamerName";

	internal const string TgwClan = "TGW_GuildName";

	internal const string TgwObedience = "TGW_Obedience";

	internal const string TgwStance = "TGW_Stance";

	internal const string TcwClanId = "Fehu_TCW_ClanId";

	internal const string TcwPatrolMode = "Fehu_TCW_PatrolMode";

	internal const string TcwPatrolHomeX = "Fehu_TCW_PatrolHomeX";

	internal const string TcwPatrolHomeY = "Fehu_TCW_PatrolHomeY";

	private const int MissingState = -987654321;

	private static readonly FieldInfo ZdoUidField = AccessTools.Field(typeof(ZDO), "m_uid");

	internal static bool TryRead(GameObject go, out TameIdentity identity)
	{
		identity = default(TameIdentity);
		if ((Object)(object)go == (Object)null)
		{
			return false;
		}
		ZNetView component = go.GetComponent<ZNetView>();
		if ((Object)(object)component == (Object)null || !component.IsValid())
		{
			return false;
		}
		if (!TryRead(component.GetZDO(), out identity))
		{
			return false;
		}
		identity.GameObject = go;
		identity.PrefabName = NormalizePrefabName(((Object)go).name);
		return true;
	}

	internal static bool TryRead(ZDO zdo, out TameIdentity identity)
	{
		identity = default(TameIdentity);
		if (zdo == null)
		{
			return false;
		}
		long num = zdo.GetLong("FTL_OwnerID", 0L);
		long num2 = zdo.GetLong("TGW_TamerID", 0L);
		string a = zdo.GetString("FTL_Source", string.Empty);
		OwnershipSource ownershipSource = ((num == 0L) ? ((num2 != 0L) ? OwnershipSource.TameGuildWars : OwnershipSource.None) : ((!string.Equals(a, "TGW", StringComparison.OrdinalIgnoreCase)) ? OwnershipSource.FehuTameLimit : OwnershipSource.TameGuildWars));
		if (ownershipSource == OwnershipSource.None)
		{
			return false;
		}
		long ownerId = ((num != 0L) ? num : num2);
		string text = ((num != 0L) ? zdo.GetString("FTL_OwnerName", string.Empty) : zdo.GetString("TGW_TamerName", string.Empty));
		string text2 = zdo.GetString("FTL_Clan", string.Empty);
		if (string.IsNullOrWhiteSpace(text2))
		{
			text2 = zdo.GetString("TGW_GuildName", string.Empty);
		}
		int num3 = zdo.GetInt("FTL_State", -987654321);
		bool flag = num3 >= 0 && num3 <= 3;
		TameRuntimeState state = (flag ? ((TameRuntimeState)num3) : DeriveLegacyState(zdo));
		if (IsTameGuardMetadata(zdo))
		{
			state = TameRuntimeState.TameGuard;
		}
		identity = new TameIdentity
		{
			Key = GetStableZdoKey(zdo),
			OwnerId = ownerId,
			OwnerName = (text ?? string.Empty),
			ClanName = NormalizeClan(text2),
			PrefabName = "unknown",
			Source = ownershipSource,
			State = state,
			HasExplicitState = flag,
			Zdo = zdo
		};
		return !string.IsNullOrEmpty(identity.Key);
	}

	private static TameRuntimeState DeriveLegacyState(ZDO zdo)
	{
		if (IsTameGuardMetadata(zdo))
		{
			return TameRuntimeState.TameGuard;
		}
		switch (zdo.GetInt("TGW_Stance", -1))
		{
		case 0:
			return TameRuntimeState.Roster;
		case 3:
		case 4:
			return TameRuntimeState.Active;
		default:
			return TameRuntimeState.OwnedInactive;
		}
	}

	internal static TameRuntimeState DeriveExternalState(in TameIdentity identity)
	{
		ZDO zdo = identity.Zdo;
		if (zdo == null)
		{
			return identity.State;
		}
		if (IsTameGuardMetadata(zdo))
		{
			return TameRuntimeState.TameGuard;
		}
		int num = zdo.GetInt("TGW_Stance", -1);
		if (identity.State == TameRuntimeState.OwnedInactive)
		{
			return identity.State;
		}
		switch (num)
		{
		case 3:
		case 4:
			return TameRuntimeState.Active;
		case 0:
			return TameRuntimeState.Roster;
		case 1:
		case 5:
			return TameRuntimeState.OwnedInactive;
		default:
			return identity.State;
		}
	}

	internal static bool IsTameGuardMetadata(ZDO zdo)
	{
		if (zdo == null)
		{
			return false;
		}
		int num = zdo.GetInt("Fehu_TCW_PatrolMode", 0);
		if (num == 1 || num == 2)
		{
			return true;
		}
		if (FehuTameLimitPlugin.Settings != null && FehuTameLimitPlugin.Settings.GuardWardCountsAsTameGuard.Value)
		{
			return zdo.GetInt("TGW_Stance", -1) == 2;
		}
		return false;
	}

	internal static void WriteFtl(ZDO zdo, long ownerId, string ownerName, string clan, string source, TameRuntimeState state)
	{
		if (zdo != null && ownerId != 0L)
		{
			zdo.Set("FTL_OwnerID", ownerId);
			zdo.Set("FTL_OwnerName", ownerName ?? string.Empty);
			zdo.Set("FTL_Clan", NormalizeClan(clan));
			zdo.Set("FTL_Source", source ?? "FTL");
			WriteState(zdo, state);
		}
	}

	internal static void WriteOwnershipOnly(ZDO zdo, long ownerId, string ownerName, string clan, string source)
	{
		if (zdo != null && ownerId != 0L)
		{
			zdo.Set("FTL_OwnerID", ownerId);
			zdo.Set("FTL_OwnerName", ownerName ?? string.Empty);
			zdo.Set("FTL_Clan", NormalizeClan(clan));
			zdo.Set("FTL_Source", source ?? "FTL");
		}
	}

	internal static void WriteState(ZDO zdo, TameRuntimeState state)
	{
		if (zdo != null)
		{
			zdo.Set("FTL_State", (int)state);
			zdo.Set("FTL_Active", (state == TameRuntimeState.Active) ? 1 : 0);
		}
	}

	internal static void EnsureExplicitState(ZDO zdo)
	{
		if (zdo != null)
		{
			int num = zdo.GetInt("FTL_State", -987654321);
			if (num < 0 || num > 3)
			{
				WriteState(zdo, DeriveLegacyState(zdo));
			}
		}
	}

	internal static void UpdateClan(ZDO zdo, string clan)
	{
		if (zdo != null)
		{
			zdo.Set("FTL_Clan", NormalizeClan(clan));
		}
	}

	internal static string GetStableZdoKey(ZDO zdo)
	{
		try
		{
			return ZdoUidField?.GetValue(zdo)?.ToString() ?? string.Empty;
		}
		catch
		{
			return string.Empty;
		}
	}

	internal static bool IsTameCandidate(GameObject go)
	{
		if ((Object)(object)go != (Object)null && (Object)(object)go.GetComponent<Tameable>() != (Object)null)
		{
			return (Object)(object)go.GetComponent<Character>() != (Object)null;
		}
		return false;
	}

	internal static bool IsActuallyTamed(GameObject go)
	{
		Character val = (((Object)(object)go != (Object)null) ? go.GetComponent<Character>() : null);
		if ((Object)(object)val == (Object)null)
		{
			return false;
		}
		try
		{
			MethodInfo methodInfo = AccessTools.Method(((object)val).GetType(), "IsTamed", (Type[])null, (Type[])null) ?? AccessTools.Method(typeof(Character), "IsTamed", (Type[])null, (Type[])null);
			return methodInfo != null && methodInfo.ReturnType == typeof(bool) && (bool)methodInfo.Invoke(val, null);
		}
		catch
		{
			return false;
		}
	}

	internal static string NormalizeClan(string clan)
	{
		if (string.IsNullOrWhiteSpace(clan) || string.Equals(clan, "No Guild", StringComparison.OrdinalIgnoreCase) || string.Equals(clan, "None", StringComparison.OrdinalIgnoreCase) || clan == "0")
		{
			return string.Empty;
		}
		return clan.Trim();
	}

	internal static string NormalizePrefabName(string name)
	{
		if (string.IsNullOrWhiteSpace(name))
		{
			return "unknown";
		}
		if (!name.EndsWith("(Clone)", StringComparison.Ordinal))
		{
			return name.Trim();
		}
		return name.Substring(0, name.Length - "(Clone)".Length).Trim();
	}
}
[DataContract]
internal sealed class RegistryFile
{
	[DataMember(Order = 1)]
	public int DataVersion = 3;

	[DataMember(Order = 2)]
	public long WorldUid;

	[DataMember(Order = 3)]
	public string WorldName = string.Empty;

	[DataMember(Order = 4)]
	public List<TameRecord> Tames = new List<TameRecord>();
}
[DataContract]
internal sealed class TameRecord
{
	[DataMember(Order = 1)]
	public string Key = string.Empty;

	[DataMember(Order = 2)]
	public long OwnerId;

	[DataMember(Order = 3)]
	public string OwnerName = string.Empty;

	[DataMember(Order = 4)]
	public string ClanName = string.Empty;

	[DataMember(Order = 5)]
	public string PrefabName = "unknown";

	[DataMember(Order = 6)]
	public int State;

	[DataMember(Order = 7)]
	public string Source = string.Empty;

	[DataMember(Order = 8)]
	public long FirstSeenUtcTicks;

	[DataMember(Order = 9)]
	public long LastSeenUtcTicks;
}
internal static class TameRegistry
{
	private sealed class ReferenceEqualityComparer : IEqualityComparer<object>
	{
		internal static readonly ReferenceEqualityComparer Instance = new ReferenceEqualityComparer();

		public new bool Equals(object a, object b)
		{
			return a == b;
		}

		public int GetHashCode(object obj)
		{
			return RuntimeHelpers.GetHashCode(obj);
		}
	}

	private static readonly Dictionary<string, TameRecord> Records = new Dictionary<string, TameRecord>(StringComparer.Ordinal);

	private static readonly object Gate = new object();

	private static RegistryFile _file = new RegistryFile();

	private static string _filePath;

	private static bool _dirty;

	private static float _nextFlush;

	private static int _revision;

	internal static int RecordCount
	{
		get
		{
			lock (Gate)
			{
				return Records.Count;
			}
		}
	}

	internal static int Revision
	{
		get
		{
			lock (Gate)
			{
				return _revision;
			}
		}
	}

	internal static void LoadWorld(long uid, string worldName)
	{
		//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
		lock (Gate)
		{
			Records.Clear();
			_filePath = Path.Combine(Paths.ConfigPath, "FehuTameLimit." + SanitizeFileName(worldName) + "-" + uid + ".json");
			_file = new RegistryFile
			{
				WorldUid = uid,
				WorldName = (worldName ?? "world")
			};
			if (File.Exists(_filePath))
			{
				try
				{
					using FileStream fileStream = File.OpenRead(_filePath);
					if (((XmlObjectSerializer)new DataContractJsonSerializer(typeof(RegistryFile))).ReadObject((Stream)fileStream) is RegistryFile registryFile && registryFile.WorldUid == uid)
					{
						_file = registryFile;
						_file.DataVersion = 3;
						foreach (TameRecord item in registryFile.Tames ?? new List<TameRecord>())
						{
							if (item != null && !string.IsNullOrEmpty(item.Key))
							{
								if (item.State < 0 || item.State > 3)
								{
									item.State = 0;
								}
								Records[item.Key] = item;
							}
						}
					}
				}
				catch (Exception ex)
				{
					FehuTameLimitPlugin.Log.LogWarning((object)("Could not load tame registry; starting from bootstrap: " + ex.Message));
				}
			}
			_dirty = false;
			_revision++;
			FehuTameLimitPlugin.Log.LogInfo((object)$"Tame registry loaded for {worldName} ({uid}): {Records.Count} records.");
		}
	}

	internal static int CountPlayerRoster(long ownerId, string excludeKey = null)
	{
		if (ownerId == 0L)
		{
			return 0;
		}
		lock (Gate)
		{
			return Records.Values.Count((TameRecord r) => r.OwnerId == ownerId && r.Key != excludeKey && (r.State == 1 || r.State == 2));
		}
	}

	internal static int CountPlayerActive(long ownerId, string excludeKey = null)
	{
		if (ownerId == 0L)
		{
			return 0;
		}
		lock (Gate)
		{
			return Records.Values.Count((TameRecord r) => r.OwnerId == ownerId && r.Key != excludeKey && r.State == 2);
		}
	}

	internal static int CountClanActive(string clan, string excludeKey = null)
	{
		clan = TameOwnership.NormalizeClan(clan);
		if (string.IsNullOrEmpty(clan))
		{
			return 0;
		}
		lock (Gate)
		{
			return Records.Values.Count((TameRecord r) => r.Key != excludeKey && r.State == 2 && string.Equals(TameOwnership.NormalizeClan(r.ClanName), clan, StringComparison.OrdinalIgnoreCase));
		}
	}

	internal static Dictionary<long, int> GetPlayerRosterCounts()
	{
		lock (Gate)
		{
			return (from r in Records.Values
				where r.OwnerId != 0L && (r.State == 1 || r.State == 2)
				group r by r.OwnerId).ToDictionary((IGrouping<long, TameRecord> g) => g.Key, (IGrouping<long, TameRecord> g) => g.Count());
		}
	}

	internal static Dictionary<long, int> GetPlayerActiveCounts()
	{
		lock (Gate)
		{
			return (from r in Records.Values
				where r.OwnerId != 0L && r.State == 2
				group r by r.OwnerId).ToDictionary((IGrouping<long, TameRecord> g) => g.Key, (IGrouping<long, TameRecord> g) => g.Count());
		}
	}

	internal static Dictionary<string, int> GetClanActiveCounts()
	{
		lock (Gate)
		{
			return Records.Values.Where((TameRecord r) => r.State == 2 && LimitService.IsRealClan(r.ClanName)).GroupBy<TameRecord, string>((TameRecord r) => TameOwnership.NormalizeClan(r.ClanName), StringComparer.OrdinalIgnoreCase).ToDictionary<IGrouping<string, TameRecord>, string, int>((IGrouping<string, TameRecord> g) => g.Key, (IGrouping<string, TameRecord> g) => g.Count(), StringComparer.OrdinalIgnoreCase);
		}
	}

	internal static void Upsert(TameIdentity identity)
	{
		if (string.IsNullOrEmpty(identity.Key) || identity.OwnerId == 0L)
		{
			return;
		}
		lock (Gate)
		{
			long ticks = DateTime.UtcNow.Ticks;
			TameRecord value;
			bool num = !Records.TryGetValue(identity.Key, out value);
			if (num)
			{
				value = new TameRecord
				{
					Key = identity.Key,
					FirstSeenUtcTicks = ticks
				};
				Records[identity.Key] = value;
			}
			string text = identity.OwnerName ?? string.Empty;
			string text2 = TameOwnership.NormalizeClan(identity.ClanName);
			string text3 = identity.PrefabName ?? "unknown";
			string text4 = identity.Source.ToString();
			bool flag = num || value.OwnerId != identity.OwnerId || value.OwnerName != text || value.ClanName != text2 || value.PrefabName != text3 || value.State != (int)identity.State || value.Source != text4;
			if (flag || ticks - value.LastSeenUtcTicks >= TimeSpan.FromMinutes(5.0).Ticks)
			{
				value.OwnerId = identity.OwnerId;
				value.OwnerName = text;
				value.ClanName = text2;
				value.PrefabName = text3;
				value.State = (int)identity.State;
				value.Source = text4;
				value.LastSeenUtcTicks = ticks;
				_dirty = true;
				if (flag)
				{
					_revision++;
				}
			}
		}
	}

	internal static void Remove(string key)
	{
		if (string.IsNullOrEmpty(key))
		{
			return;
		}
		lock (Gate)
		{
			if (Records.Remove(key))
			{
				_dirty = true;
				_revision++;
			}
		}
	}

	internal static void Remove(GameObject go)
	{
		if (!((Object)(object)go == (Object)null))
		{
			ZNetView component = go.GetComponent<ZNetView>();
			if (!((Object)(object)component == (Object)null) && component.IsValid())
			{
				Remove(TameOwnership.GetStableZdoKey(component.GetZDO()));
			}
		}
	}

	internal static void ReconcileLoadedCharacters()
	{
		if (!FehuTameLimitPlugin.IsServer())
		{
			return;
		}
		int num = 0;
		Character[] array = Object.FindObjectsByType<Character>((FindObjectsSortMode)0);
		foreach (Character val in array)
		{
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			GameObject gameObject = ((Component)val).gameObject;
			if (TameOwnership.TryRead(gameObject, out var _))
			{
				TameStateService.ReconcileExternal(gameObject);
				num++;
				continue;
			}
			ZNetView component = gameObject.GetComponent<ZNetView>();
			if ((Object)(object)component != (Object)null && component.IsValid())
			{
				string stableZdoKey = TameOwnership.GetStableZdoKey(component.GetZDO());
				if (!string.IsNullOrEmpty(stableZdoKey) && Contains(stableZdoKey))
				{
					Remove(stableZdoKey);
				}
			}
		}
		if (FehuTameLimitPlugin.Settings.DebugLogging.Value)
		{
			FehuTameLimitPlugin.Log.LogDebug((object)$"Reconciled {num} owned tame records from loaded Characters.");
		}
	}

	internal static void RefreshClanAssignments()
	{
		if (!FehuTameLimitPlugin.IsServer())
		{
			return;
		}
		List<TameRecord> list;
		lock (Gate)
		{
			list = Records.Values.Select(Clone).ToList();
		}
		Dictionary<string, ZDO> dictionary = null;
		foreach (TameRecord item in list)
		{
			if (item.OwnerId == 0L || !ClanBridge.TryResolve(item.OwnerId, item.OwnerName, out var clan))
			{
				continue;
			}
			clan = TameOwnership.NormalizeClan(clan);
			if (string.Equals(clan, TameOwnership.NormalizeClan(item.ClanName), StringComparison.OrdinalIgnoreCase))
			{
				continue;
			}
			lock (Gate)
			{
				if (Records.TryGetValue(item.Key, out var value))
				{
					value.ClanName = clan;
					_dirty = true;
					_revision++;
				}
			}
			if (dictionary == null)
			{
				dictionary = GetLoadedZdos();
			}
			if (dictionary.TryGetValue(item.Key, out var value2))
			{
				TameOwnership.UpdateClan(value2, clan);
			}
		}
		FlushIfDirty();
	}

	private static Dictionary<string, ZDO> GetLoadedZdos()
	{
		Dictionary<string, ZDO> dictionary = new Dictionary<string, ZDO>(StringComparer.Ordinal);
		Character[] array = Object.FindObjectsByType<Character>((FindObjectsSortMode)0);
		foreach (Character val in array)
		{
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			ZNetView component = ((Component)val).GetComponent<ZNetView>();
			if (!((Object)(object)component == (Object)null) && component.IsValid())
			{
				ZDO zDO = component.GetZDO();
				string stableZdoKey = TameOwnership.GetStableZdoKey(zDO);
				if (!string.IsNullOrEmpty(stableZdoKey))
				{
					dictionary[stableZdoKey] = zDO;
				}
			}
		}
		return dictionary;
	}

	internal static IEnumerator BootstrapWorldZdos()
	{
		if (!FehuTameLimitPlugin.IsServer() || ZDOMan.instance == null)
		{
			yield break;
		}
		FehuTameLimitPlugin.Log.LogInfo((object)"Starting one-time FTL/TGW state ZDO bootstrap.");
		List<ZDO> list = DiscoverZdos(ZDOMan.instance);
		int relevant = 0;
		int processed = 0;
		foreach (ZDO item in list)
		{
			processed++;
			if (TameOwnership.TryRead(item, out var identity))
			{
				if (!identity.HasExplicitState)
				{
					TameOwnership.EnsureExplicitState(item);
					TameOwnership.TryRead(item, out identity);
				}
				Upsert(identity);
				relevant++;
			}
			if (processed % 500 == 0)
			{
				yield return null;
			}
		}
		ReconcileLoadedCharacters();
		RefreshClanAssignments();
		Flush(force: true);
		FehuTameLimitPlugin.Log.LogInfo((object)$"Bootstrap complete: inspected {processed} ZDOs, found {relevant} owned tame records, registry={RecordCount}.");
	}

	private static List<ZDO> DiscoverZdos(ZDOMan manager)
	{
		Dictionary<string, ZDO> dictionary = new Dictionary<string, ZDO>(StringComparer.Ordinal);
		HashSet<object> visited = new HashSet<object>(ReferenceEqualityComparer.Instance);
		FieldInfo[] fields = typeof(ZDOMan).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		foreach (FieldInfo fieldInfo in fields)
		{
			string text = fieldInfo.Name ?? string.Empty;
			if (text.IndexOf("zdo", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("sector", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("object", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				try
				{
					CollectZdos(fieldInfo.GetValue(manager), dictionary, visited, 0);
				}
				catch
				{
				}
			}
		}
		return dictionary.Values.ToList();
	}

	private static void CollectZdos(object value, Dictionary<string, ZDO> found, HashSet<object> visited, int depth)
	{
		if (value == null || depth > 5)
		{
			return;
		}
		ZDO val = (ZDO)((value is ZDO) ? value : null);
		if (val != null)
		{
			string stableZdoKey = TameOwnership.GetStableZdoKey(val);
			if (!string.IsNullOrEmpty(stableZdoKey))
			{
				found[stableZdoKey] = val;
			}
			return;
		}
		Type type = value.GetType();
		if (type.IsPrimitive || value is string || value is Object || (!type.IsValueType && !visited.Add(value)))
		{
			return;
		}
		if (value is IDictionary dictionary)
		{
			{
				foreach (DictionaryEntry item in dictionary)
				{
					CollectZdos(item.Key, found, visited, depth + 1);
					CollectZdos(item.Value, found, visited, depth + 1);
				}
				return;
			}
		}
		if (value is IEnumerable enumerable)
		{
			int num = 0;
			{
				foreach (object item2 in enumerable)
				{
					CollectZdos(item2, found, visited, depth + 1);
					if (++num > 200000)
					{
						break;
					}
				}
				return;
			}
		}
		PropertyInfo property = type.GetProperty("Value", BindingFlags.Instance | BindingFlags.Public);
		if (!(property != null) || property.GetIndexParameters().Length != 0)
		{
			return;
		}
		try
		{
			CollectZdos(property.GetValue(value, null), found, visited, depth + 1);
		}
		catch
		{
		}
	}

	internal static void FlushIfDirty()
	{
		if (_dirty && Time.realtimeSinceStartup >= _nextFlush)
		{
			Flush(force: false);
		}
	}

	internal static void Flush(bool force)
	{
		//IL_009a: Unknown result type (might be due to invalid IL or missing references)
		lock (Gate)
		{
			if ((!_dirty && !force) || string.IsNullOrEmpty(_filePath))
			{
				return;
			}
			try
			{
				_file.DataVersion = 3;
				_file.Tames = Records.Values.OrderBy<TameRecord, string>((TameRecord r) => r.Key, StringComparer.Ordinal).ToList();
				string text = _filePath + ".tmp";
				using (FileStream fileStream = File.Create(text))
				{
					((XmlObjectSerializer)new DataContractJsonSerializer(typeof(RegistryFile))).WriteObject((Stream)fileStream, (object)_file);
					fileStream.Flush(flushToDisk: true);
				}
				if (File.Exists(_filePath))
				{
					File.Copy(text, _filePath, overwrite: true);
					File.Delete(text);
				}
				else
				{
					File.Move(text, _filePath);
				}
				_dirty = false;
				_nextFlush = Time.realtimeSinceStartup + 5f;
			}
			catch (Exception ex)
			{
				FehuTameLimitPlugin.Log.LogWarning((object)("Could not save tame registry: " + ex.Message));
			}
		}
	}

	private static bool Contains(string key)
	{
		lock (Gate)
		{
			return Records.ContainsKey(key);
		}
	}

	private static TameRecord Clone(TameRecord r)
	{
		return new TameRecord
		{
			Key = r.Key,
			OwnerId = r.OwnerId,
			OwnerName = r.OwnerName,
			ClanName = r.ClanName,
			PrefabName = r.PrefabName,
			State = r.State,
			Source = r.Source,
			FirstSeenUtcTicks = r.FirstSeenUtcTicks,
			LastSeenUtcTicks = r.LastSeenUtcTicks
		};
	}

	private static string SanitizeFileName(string value)
	{
		char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
		foreach (char oldChar in invalidFileNameChars)
		{
			value = (value ?? "world").Replace(oldChar, '_');
		}
		if (!string.IsNullOrWhiteSpace(value))
		{
			return value;
		}
		return "world";
	}
}
internal static class TameStateService
{
	internal static bool TryTransition(GameObject go, TameRuntimeState target, Player actor, bool enforce, out LimitDecision decision)
	{
		decision = default(LimitDecision);
		if ((Object)(object)go == (Object)null || !TameOwnership.TryRead(go, out var identity) || !identity.HasOwner)
		{
			return false;
		}
		string text = OwnershipService.ResolveClan(identity.OwnerId, identity.OwnerName, identity.ClanName);
		if (!string.Equals(text, identity.ClanName, StringComparison.OrdinalIgnoreCase))
		{
			identity.ClanName = text;
			TameOwnership.UpdateClan(identity.Zdo, text);
		}
		decision = LimitService.CanTransition(in identity, target);
		if (enforce && !decision.Allowed)
		{
			if ((Object)(object)actor != (Object)null)
			{
				TgwIntegration.Show(actor, decision.Message);
			}
			else
			{
				NetworkSync.SendNotice(identity.OwnerId, decision.Message);
			}
			return false;
		}
		TameOwnership.WriteState(identity.Zdo, target);
		identity.State = target;
		identity.HasExplicitState = true;
		TameRegistry.Upsert(identity);
		TameRegistry.FlushIfDirty();
		if (FehuTameLimitPlugin.Settings.DebugLogging.Value)
		{
			FehuTameLimitPlugin.Log.LogDebug((object)$"State {identity.PrefabName} [{identity.Key}] => {target}; owner={identity.OwnerId}, clan='{identity.ClanName}'.");
		}
		return true;
	}

	internal static TameRuntimeState ChooseInitialState(GameObject go, long ownerId, string ownerName, string clan, bool preferRoster)
	{
		if ((Object)(object)go == (Object)null)
		{
			return TameRuntimeState.OwnedInactive;
		}
		ZNetView component = go.GetComponent<ZNetView>();
		if ((Object)(object)component != (Object)null && component.IsValid() && TameOwnership.IsTameGuardMetadata(component.GetZDO()))
		{
			return TameRuntimeState.TameGuard;
		}
		if (!preferRoster || !FehuTameLimitPlugin.Settings.NewTamesEnterRoster.Value)
		{
			return TameRuntimeState.OwnedInactive;
		}
		if (!LimitService.CanTransition(new TameIdentity
		{
			Key = (((Object)(object)component != (Object)null && component.IsValid()) ? TameOwnership.GetStableZdoKey(component.GetZDO()) : string.Empty),
			OwnerId = ownerId,
			OwnerName = (ownerName ?? string.Empty),
			ClanName = TameOwnership.NormalizeClan(clan),
			PrefabName = TameOwnership.NormalizePrefabName(((Object)go).name),
			Source = OwnershipSource.FehuTameLimit,
			State = TameRuntimeState.OwnedInactive,
			Zdo = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null),
			GameObject = go
		}, TameRuntimeState.Roster).Allowed)
		{
			return TameRuntimeState.OwnedInactive;
		}
		return TameRuntimeState.Roster;
	}

	internal static void ReconcileExternal(GameObject go)
	{
		if ((Object)(object)go == (Object)null || !TameOwnership.TryRead(go, out var identity))
		{
			return;
		}
		TameRuntimeState tameRuntimeState = TameOwnership.DeriveExternalState(in identity);
		if (!identity.HasExplicitState)
		{
			TameOwnership.WriteState(identity.Zdo, tameRuntimeState);
			identity.State = tameRuntimeState;
			identity.HasExplicitState = true;
			TameRegistry.Upsert(identity);
			return;
		}
		if (tameRuntimeState == identity.State)
		{
			TameRegistry.Upsert(identity);
			return;
		}
		TameOwnership.WriteState(identity.Zdo, tameRuntimeState);
		identity.State = tameRuntimeState;
		TameRegistry.Upsert(identity);
		if (FehuTameLimitPlugin.Settings.DebugLogging.Value)
		{
			FehuTameLimitPlugin.Log.LogDebug((object)$"Reconciled external state [{identity.Key}] => {tameRuntimeState}.");
		}
	}
}
internal static class TgwIntegration
{
	internal enum IntegrationState
	{
		UNINITIALIZED,
		READY,
		FAILED_SAFE
	}

	private sealed class StanceState
	{
		internal GameObject Go;

		internal TameRuntimeState Target;

		internal bool Commit;
	}

	private const string TypeName = "TameGuildWars.TamedGuildMob";

	private const string PluginGuid = "WackyMole.TameGuildWars";

	private static IntegrationState _state;

	private static Type _tgwType;

	internal static bool IsReady => _state == IntegrationState.READY;

	internal static IntegrationState State => _state;

	internal static void InitializeOnce(Harmony harmony)
	{
		//IL_0107: Unknown result type (might be due to invalid IL or missing references)
		//IL_011c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0129: Expected O, but got Unknown
		//IL_0129: Expected O, but got Unknown
		if (_state != IntegrationState.UNINITIALIZED)
		{
			return;
		}
		if (harmony == null)
		{
			FailSafe();
			return;
		}
		try
		{
			if (!Chainloader.PluginInfos.ContainsKey("WackyMole.TameGuildWars"))
			{
				FailSafe();
				return;
			}
			_tgwType = FindLoadedType("TameGuildWars.TamedGuildMob");
			if (_tgwType == null)
			{
				FailSafe();
				return;
			}
			MethodInfo[] array = (from m in _tgwType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
				where m.Name == "SetStance"
				select m).ToArray();
			if (array.Length == 0)
			{
				FailSafe();
				return;
			}
			PatchPostfix(harmony, AccessTools.Method(_tgwType, "TryClaim", (Type[])null, (Type[])null), "PostfixMutation");
			PatchPostfix(harmony, AccessTools.Method(_tgwType, "TryChangeTamer", (Type[])null, (Type[])null), "PostfixMutation");
			PatchPostfix(harmony, AccessTools.Method(_tgwType, "TryChangeGuild", (Type[])null, (Type[])null), "PostfixMutation");
			MethodInfo[] array2 = array;
			foreach (MethodInfo methodInfo in array2)
			{
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(TgwIntegration), "PrefixSetStance", (Type[])null), new HarmonyMethod(typeof(TgwIntegration), "PostfixSetStance", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			_state = IntegrationState.READY;
			FehuTameLimitPlugin.Log.LogInfo((object)"[FehuTameLimit] TGW integration: READY");
		}
		catch
		{
			FailSafe();
		}
	}

	private static void FailSafe()
	{
		if (_state == IntegrationState.UNINITIALIZED)
		{
			_state = IntegrationState.FAILED_SAFE;
			FehuTameLimitPlugin.Log.LogWarning((object)"[FehuTameLimit] TGW integration: FAILED-SAFE");
		}
	}

	private static Type FindLoadedType(string fullName)
	{
		Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
		foreach (Assembly assembly in assemblies)
		{
			try
			{
				Type type = assembly.GetType(fullName, throwOnError: false);
				if (type != null)
				{
					return type;
				}
			}
			catch
			{
			}
		}
		return null;
	}

	private static void PatchPostfix(Harmony harmony, MethodInfo original, string postfix)
	{
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Expected O, but got Unknown
		if (!(original == null))
		{
			harmony.Patch((MethodBase)original, (HarmonyMethod)null, new HarmonyMethod(typeof(TgwIntegration), postfix, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}
	}

	private static bool PrefixSetStance(object __instance, object[] __args, ref bool __result, ref object __state)
	{
		__state = null;
		if (!FehuTameLimitPlugin.IsServer() || !FehuTameLimitPlugin.Settings.Enabled.Value)
		{
			return true;
		}
		object obj = ((__instance is Component) ? __instance : null);
		GameObject val = ((obj != null) ? ((Component)obj).gameObject : null);
		if ((Object)(object)val == (Object)null)
		{
			return true;
		}
		if (!TryGetStance(__args, out var stance))
		{
			return true;
		}
		TameRuntimeState tameRuntimeState = StateFromStance(stance);
		Player val2 = FindPlayer(__args);
		if (!TameOwnership.TryRead(val, out var identity))
		{
			if ((Object)(object)val2 != (Object)null)
			{
				OwnershipService.TryAssign(val, val2, "TGW-stance", preferRoster: false);
			}
			if (!TameOwnership.TryRead(val, out identity))
			{
				return true;
			}
		}
		string clan = (identity.ClanName = OwnershipService.ResolveClan(identity.OwnerId, identity.OwnerName, identity.ClanName));
		if (identity.Zdo != null)
		{
			TameOwnership.UpdateClan(identity.Zdo, clan);
			if (identity.Zdo.GetLong("FTL_OwnerID", 0L) == 0L)
			{
				TameOwnership.WriteOwnershipOnly(identity.Zdo, identity.OwnerId, identity.OwnerName, clan, "TGW");
			}
		}
		if (tameRuntimeState != TameRuntimeState.TameGuard)
		{
			LimitDecision limitDecision = LimitService.CanTransition(in identity, tameRuntimeState);
			if (!limitDecision.Allowed)
			{
				if ((Object)(object)val2 != (Object)null)
				{
					Show(val2, limitDecision.Message);
				}
				else
				{
					NetworkSync.SendNotice(identity.OwnerId, limitDecision.Message);
				}
				__result = false;
				return false;
			}
		}
		__state = new StanceState
		{
			Go = val,
			Target = tameRuntimeState,
			Commit = true
		};
		return true;
	}

	private static void PostfixSetStance(bool __result, object __state)
	{
		if (__result && __state is StanceState { Commit: not false } stanceState && !((Object)(object)stanceState.Go == (Object)null))
		{
			TameStateService.TryTransition(stanceState.Go, stanceState.Target, null, enforce: false, out var _);
		}
	}

	private static void PostfixMutation(object __instance, bool __result)
	{
		if (!FehuTameLimitPlugin.IsServer() || !__result)
		{
			return;
		}
		object obj = ((__instance is Component) ? __instance : null);
		GameObject val = ((obj != null) ? ((Component)obj).gameObject : null);
		if ((Object)(object)val == (Object)null || !TameOwnership.TryRead(val, out var identity))
		{
			return;
		}
		string clan = OwnershipService.ResolveClan(identity.OwnerId, identity.OwnerName, identity.ClanName);
		if (identity.Zdo == null || identity.Zdo.GetLong("FTL_OwnerID", 0L) != identity.OwnerId)
		{
			OwnershipService.TryAssign(val, identity.OwnerId, identity.OwnerName, clan, "TGW", preferRoster: true);
		}
		else
		{
			TameOwnership.WriteOwnershipOnly(identity.Zdo, identity.OwnerId, identity.OwnerName, clan, "TGW");
			if (!identity.HasExplicitState)
			{
				TameOwnership.WriteState(identity.Zdo, TameStateService.ChooseInitialState(val, identity.OwnerId, identity.OwnerName, clan, preferRoster: true));
			}
			if (TameOwnership.TryRead(val, out var identity2))
			{
				TameRegistry.Upsert(identity2);
			}
		}
		TameRegistry.FlushIfDirty();
	}

	private static bool TryGetStance(object[] args, out int stance)
	{
		stance = -1;
		if (args == null)
		{
			return false;
		}
		foreach (object obj in args)
		{
			if (obj == null)
			{
				continue;
			}
			Type type = obj.GetType();
			if (type.IsEnum && (type.FullName ?? string.Empty).IndexOf("GuildMobStance", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				try
				{
					stance = Convert.ToInt32(obj);
					return true;
				}
				catch
				{
					return false;
				}
			}
		}
		return false;
	}

	private static Player FindPlayer(object[] args)
	{
		if (args != null)
		{
			foreach (object obj in args)
			{
				Player val = (Player)((obj is Player) ? obj : null);
				if (val != null)
				{
					return val;
				}
			}
		}
		return null;
	}

	private static TameRuntimeState StateFromStance(int stance)
	{
		switch (stance)
		{
		case 2:
			return TameRuntimeState.TameGuard;
		case 3:
		case 4:
			return TameRuntimeState.Active;
		case 0:
			return TameRuntimeState.Roster;
		case 1:
		case 5:
			return TameRuntimeState.OwnedInactive;
		default:
			return TameRuntimeState.OwnedInactive;
		}
	}

	internal static void Show(Player player, string message)
	{
		try
		{
			if ((Object)(object)player != (Object)null)
			{
				((Character)player).Message((MessageType)2, message, 0, (Sprite)null, false);
			}
		}
		catch
		{
		}
	}
}
internal static class VanillaIntegration
{
	private static bool _installed;

	internal static void TryInstall(Harmony harmony)
	{
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		//IL_004d: Expected O, but got Unknown
		//IL_009b: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a8: Expected O, but got Unknown
		if (_installed || harmony == null)
		{
			return;
		}
		try
		{
			Type? typeFromHandle = typeof(Tameable);
			MethodInfo methodInfo = AccessTools.Method(typeFromHandle, "Tame", (Type[])null, (Type[])null);
			if (methodInfo != null)
			{
				harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(VanillaIntegration), "PostfixTame", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			foreach (MethodInfo item in from m in typeFromHandle.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
				where m.Name == "Interact"
				select m)
			{
				harmony.Patch((MethodBase)item, (HarmonyMethod)null, new HarmonyMethod(typeof(VanillaIntegration), "PostfixInteract", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			_installed = true;
			FehuTameLimitPlugin.Log.LogInfo((object)"Vanilla Tameable ownership integration installed (ownership unlimited).");
		}
		catch (Exception arg)
		{
			FehuTameLimitPlugin.Log.LogError((object)$"Vanilla Tameable integration failed: {arg}");
		}
	}

	private static void PostfixTame(object __instance)
	{
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		if (!FehuTameLimitPlugin.IsServer() || !FehuTameLimitPlugin.Settings.Enabled.Value)
		{
			return;
		}
		object obj = ((__instance is Component) ? __instance : null);
		GameObject val = ((obj != null) ? ((Component)obj).gameObject : null);
		if (!((Object)(object)val == (Object)null) && !TameOwnership.TryRead(val, out var _))
		{
			Player val2 = OwnershipService.FindNearestPlayer(val.transform.position, FehuConfig.PositiveOrDefault(FehuTameLimitPlugin.Settings.OwnershipAssignRadius.Value, 1f, 100f));
			if (!((Object)(object)val2 == (Object)null))
			{
				OwnershipService.TryAssign(val, val2, "vanilla-tame", preferRoster: true);
				TameRegistry.FlushIfDirty();
			}
		}
	}

	private static void PostfixInteract(object __instance, object[] __args)
	{
		if (!FehuTameLimitPlugin.IsServer() || !FehuTameLimitPlugin.Settings.AdoptOnTamedInteract.Value)
		{
			return;
		}
		object obj = ((__instance is Component) ? __instance : null);
		GameObject val = ((obj != null) ? ((Component)obj).gameObject : null);
		if ((Object)(object)val == (Object)null || TameOwnership.TryRead(val, out var _) || !TameOwnership.IsActuallyTamed(val))
		{
			return;
		}
		Player val2 = null;
		if (__args != null)
		{
			foreach (object obj2 in __args)
			{
				Player val3 = (Player)((obj2 is Player) ? obj2 : null);
				if (val3 != null)
				{
					val2 = val3;
					break;
				}
			}
		}
		if (!((Object)(object)val2 == (Object)null))
		{
			OwnershipService.TryAssign(val, val2, "legacy-interact", preferRoster: true);
			TameRegistry.FlushIfDirty();
		}
	}
}