Decompiled source of WardZone v1.1.0

WardZone.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn.Entities;
using Jotunn.Managers;
using Microsoft.CodeAnalysis;
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("WardZone")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0")]
[assembly: AssemblyProduct("WardZone")]
[assembly: AssemblyTitle("WardZone")]
[assembly: AssemblyVersion("1.1.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[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 WardZone
{
	[HarmonyPatch]
	internal static class CfcBridge
	{
		public const string Guid = "aedenthorn.CraftFromContainers";

		private const string PluginType = "CraftFromContainers.BepInExPlugin";

		private static FieldInfo s_containerList;

		private static FieldInfo s_lastPosition;

		private static FieldInfo s_pullByDistance;

		private static Func<Container, bool> s_allowType;

		private static Func<Container, long, bool> s_checkAccess;

		private static List<Container> s_lastExtended;

		private static bool s_failed;

		public static bool Attached { get; private set; }

		private static bool PullByDistance
		{
			get
			{
				if (s_pullByDistance?.GetValue(null) is ConfigEntry<bool> val)
				{
					return val.Value;
				}
				return false;
			}
		}

		private static bool Prepare()
		{
			Type type = AccessTools.TypeByName("CraftFromContainers.BepInExPlugin");
			if (type == null)
			{
				return false;
			}
			MethodInfo methodInfo = Target(type);
			s_containerList = AccessTools.Field(type, "containerList");
			s_lastPosition = AccessTools.Field(type, "lastPosition");
			s_pullByDistance = AccessTools.Field(type, "pullByDistance");
			MethodInfo methodInfo2 = AccessTools.Method(type, "AllowContainerType", new Type[1] { typeof(Container) }, (Type[])null);
			MethodInfo methodInfo3 = AccessTools.Method(typeof(Container), "CheckAccess", new Type[1] { typeof(long) }, (Type[])null);
			if (methodInfo == null || methodInfo.ReturnType != typeof(List<Container>) || s_containerList == null || !s_containerList.IsStatic || !typeof(List<Container>).IsAssignableFrom(s_containerList.FieldType) || methodInfo3 == null)
			{
				ManualLogSource log = WardZonePlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)"CraftFromContainers has no GetNearbyContainers(Vector3)/containerList of the known shape: chests stay within its range");
				}
				return false;
			}
			if (s_lastPosition != null && (!s_lastPosition.IsStatic || s_lastPosition.FieldType != typeof(Vector3)))
			{
				s_lastPosition = null;
			}
			if (s_pullByDistance != null && (!s_pullByDistance.IsStatic || s_pullByDistance.FieldType != typeof(ConfigEntry<bool>)))
			{
				s_pullByDistance = null;
			}
			s_allowType = ((methodInfo2 != null && methodInfo2.IsStatic && methodInfo2.ReturnType == typeof(bool)) ? AccessTools.MethodDelegate<Func<Container, bool>>(methodInfo2, (object)null, true) : ((Func<Container, bool>)((Container container) => true)));
			s_checkAccess = AccessTools.MethodDelegate<Func<Container, long, bool>>(methodInfo3, (object)null, true);
			Attached = true;
			return true;
		}

		private static MethodBase TargetMethod()
		{
			return Target(AccessTools.TypeByName("CraftFromContainers.BepInExPlugin"));
		}

		private static MethodInfo Target(Type plugin)
		{
			if (!(plugin == null))
			{
				return AccessTools.Method(plugin, "GetNearbyContainers", new Type[1] { typeof(Vector3) }, (Type[])null);
			}
			return null;
		}

		public static void InvalidateCache()
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			if (Attached)
			{
				s_lastExtended = null;
				s_lastPosition?.SetValue(null, Vector3.positiveInfinity);
			}
		}

		private static void Postfix(Vector3 center, ref List<Container> __result)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			if (s_failed || __result == null || __result == s_lastExtended || !WardZonePlugin.ContainersFromZone.Value)
			{
				return;
			}
			s_lastExtended = __result;
			try
			{
				Extend(center, __result);
			}
			catch (Exception ex)
			{
				s_failed = true;
				WardZonePlugin.Log.LogError((object)("Chests from the zone are disabled after an error: " + ex));
			}
		}

		private static void Extend(Vector3 center, List<Container> result)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return;
			}
			Zone zone = ZoneMap.ZoneAt(center);
			if (zone == null || (WardZonePlugin.ContainersNeedAccess.Value && WardApi.Ready && !zone.Chain.HasLocalAccess()) || !(s_containerList.GetValue(null) is List<Container> list))
			{
				return;
			}
			HashSet<Container> hashSet = new HashSet<Container>(result);
			long playerID = localPlayer.GetPlayerID();
			int num = 0;
			for (int i = 0; i < list.Count; i++)
			{
				Container val = list[i];
				if (!((Object)(object)val == (Object)null) && !hashSet.Contains(val))
				{
					Vector3 position = ((Component)val).transform.position;
					if (zone.Contains(position) && s_allowType(val) && s_checkAccess(val, playerID) && !val.IsInUse() && val.GetInventory() != null && !((Object)(object)((Component)val).GetComponentInParent<Piece>() == (Object)null) && (!val.m_checkGuardStone || PrivateArea.CheckAccess(position, 0f, false, false)))
					{
						result.Add(val);
						num++;
					}
				}
			}
			if (num > 0 && PullByDistance)
			{
				result.Sort((Container a, Container b) => Vector3.Distance(center, ((Component)a).transform.position).CompareTo(Vector3.Distance(center, ((Component)b).transform.position)));
			}
		}
	}
	[HarmonyPatch(typeof(PrivateArea), "Interact")]
	internal static class PrivateArea_Interact_Patch
	{
		private static void Prefix(PrivateArea __instance, Humanoid human, bool hold)
		{
			try
			{
				ChainSync.OnInteract(__instance, human, hold);
			}
			catch (Exception ex)
			{
				WardZonePlugin.Log.LogError((object)("Chain sync failed: " + ex));
			}
		}
	}
	internal static class ChainSync
	{
		public static void OnInteract(PrivateArea area, Humanoid human, bool hold)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			if (hold || (int)area.m_ownerFaction != 0 || !WardApi.Ready)
			{
				return;
			}
			Player val = (Player)(object)((human is Player) ? human : null);
			if ((Object)(object)val == (Object)null || (Object)(object)val != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			ZNetView val2 = WardApi.View(area);
			if ((Object)(object)val2 == (Object)null || !val2.IsValid())
			{
				return;
			}
			Ward ward = ZoneMap.WardOf(area);
			if (ward != null && ward.Chain != null && ward.Chain.Wards.Count >= 2)
			{
				Piece val3 = WardApi.PieceOf(area);
				if ((Object)(object)val3 != (Object)null && val3.IsCreator())
				{
					SyncEnabled(ward, !WardApi.IsEnabled(area));
				}
				else if (!WardApi.IsEnabled(area))
				{
					SyncPermitted(ward, val, !WardApi.IsPermitted(area, val.GetPlayerID()));
				}
			}
		}

		private static void SyncEnabled(Ward ward, bool enabled)
		{
			if (!WardZonePlugin.SyncEnabled.Value)
			{
				return;
			}
			int num = 0;
			foreach (Ward ward2 in ward.Chain.Wards)
			{
				if (ward2 != ward && !((Object)(object)ward2.Area == (Object)null) && WardApi.IsEnabled(ward2.Area) != enabled && Claim(ward2))
				{
					WardApi.SetEnabled(ward2.Area, enabled);
					num++;
				}
			}
			if (num > 0)
			{
				ZoneText.Message(enabled ? "$q8wz_chain_on" : "$q8wz_chain_off", ward.Chain.Wards.Count);
			}
		}

		private static void SyncPermitted(Ward ward, Player player, bool add)
		{
			if (!WardZonePlugin.SyncPermitted.Value)
			{
				return;
			}
			long playerID = player.GetPlayerID();
			int num = 0;
			foreach (Ward ward2 in ward.Chain.Wards)
			{
				if (ward2 != ward && !((Object)(object)ward2.Area == (Object)null) && WardApi.IsPermitted(ward2.Area, playerID) != add && Claim(ward2))
				{
					if (add)
					{
						WardApi.AddPermitted(ward2.Area, playerID, player.GetPlayerName());
					}
					else
					{
						WardApi.RemovePermitted(ward2.Area, playerID);
					}
					num++;
				}
			}
			if (num > 0)
			{
				ZoneText.Message(add ? "$q8wz_chain_added" : "$q8wz_chain_removed", ward.Chain.Wards.Count);
			}
			ZoneMap.MarkDirty();
		}

		private static bool Claim(Ward other)
		{
			ZNetView view = other.View;
			if ((Object)(object)view == (Object)null || !view.IsValid())
			{
				return false;
			}
			if (!view.IsOwner())
			{
				view.ClaimOwnership();
			}
			return view.IsOwner();
		}
	}
	[HarmonyPatch(typeof(PrivateArea), "Awake")]
	internal static class PrivateArea_Awake_Patch
	{
		private static void Postfix(PrivateArea __instance)
		{
			ZoneMap.MarkDirty();
			Placement.OnWardAwake(__instance);
		}
	}
	[HarmonyPatch(typeof(PrivateArea), "OnDestroy")]
	internal static class PrivateArea_OnDestroy_Patch
	{
		private static void Postfix()
		{
			ZoneMap.MarkDirty();
		}
	}
	[HarmonyPatch(typeof(PrivateArea), "GetHoverText")]
	internal static class PrivateArea_GetHoverText_Patch
	{
		private static void Postfix(PrivateArea __instance, ref string __result)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			if (string.IsNullOrEmpty(__result) || (int)__instance.m_ownerFaction != 0)
			{
				return;
			}
			Ward ward = ZoneMap.WardOf(__instance);
			if (ward == null)
			{
				return;
			}
			if (ward.Zone != null && WardZonePlugin.ShowZoneOnHover.Value)
			{
				foreach (Ward ward2 in ward.Zone.Wards)
				{
					if (ward2 != ward && (Object)(object)ward2.Area != (Object)null)
					{
						ward2.Area.ShowAreaMarker();
					}
				}
			}
			if (WardZonePlugin.HoverText.Value)
			{
				__result = __result + "\n" + ZoneText.Hover(ward);
			}
		}
	}
	[HarmonyPatch(typeof(Piece), "GetAllComfortPiecesInRadius")]
	internal static class Piece_GetAllComfortPiecesInRadius_Patch
	{
		private static HashSet<Piece> s_allComfortPieces;

		private static int s_ghostLayer = -1;

		private static bool Prefix(Vector3 p, float radius, List<Piece> pieces)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			if (!WardZonePlugin.ComfortFromZone.Value)
			{
				return true;
			}
			Zone zone = ZoneMap.ZoneAt(p);
			if (zone == null)
			{
				return true;
			}
			if (s_allComfortPieces == null)
			{
				s_allComfortPieces = AccessTools.Field(typeof(Piece), "s_allComfortPieces")?.GetValue(null) as HashSet<Piece>;
				if (s_allComfortPieces == null)
				{
					WardZonePlugin.Log.LogWarning((object)"Piece.s_allComfortPieces not found: comfort stays within 10 m");
					return true;
				}
			}
			if (s_ghostLayer < 0)
			{
				s_ghostLayer = LayerMask.NameToLayer("ghost");
			}
			foreach (Piece s_allComfortPiece in s_allComfortPieces)
			{
				if (!((Object)(object)s_allComfortPiece == (Object)null) && ((Component)s_allComfortPiece).gameObject.layer != s_ghostLayer)
				{
					Vector3 position = ((Component)s_allComfortPiece).transform.position;
					if (Vector3.Distance(p, position) < radius || zone.Contains(position))
					{
						pieces.Add(s_allComfortPiece);
					}
				}
			}
			return false;
		}
	}
	internal static class Placement
	{
		public static void OnWardAwake(PrivateArea area)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			if (WardZonePlugin.PlacementMessage.Value && (int)area.m_ownerFaction == 0)
			{
				ZNetView component = ((Component)area).GetComponent<ZNetView>();
				if (!((Object)(object)component == (Object)null) && component.IsValid() && component.IsOwner() && component.GetZDO().GetLong(ZDOVars.s_creator, 0L) == 0L)
				{
					((MonoBehaviour)WardZonePlugin.Instance).StartCoroutine(Announce(area));
				}
			}
		}

		private static IEnumerator Announce(PrivateArea area)
		{
			yield return null;
			if (!((Object)(object)area == (Object)null) && !((Object)(object)Player.m_localPlayer == (Object)null))
			{
				ZoneMap.MarkDirty();
				Ward ward = ZoneMap.WardOf(area);
				if (ward != null)
				{
					((Character)Player.m_localPlayer).Message((MessageType)1, ZoneText.Placed(ward), 0, (Sprite)null, false);
				}
			}
		}
	}
	internal static class ZoneText
	{
		public static string Hover(Ward ward)
		{
			int value = WardZonePlugin.MaxWards.Value;
			if (ward.Zone == null)
			{
				return "<color=orange>" + Format("$q8wz_outside", value, Wards(value)) + "</color>";
			}
			int count = ward.Zone.Wards.Count;
			string text = ((value > 0) ? (count + " " + L("$q8wz_of") + " " + value) : (count + " " + Wards(count)));
			string text2 = "<color=#8fd3ff>" + L("$q8wz_zone") + ": " + text + " · " + L("$q8wz_coverage") + " ~" + Thousands(RoundArea(ward.Zone.AreaM2)) + " " + L("$q8wz_m2") + "</color>";
			bool flag = CfcBridge.Attached && WardZonePlugin.ContainersFromZone.Value && WardZonePlugin.ContainersNeedAccess.Value;
			bool value2 = WardZonePlugin.StationsFromZone.Value;
			if ((flag || value2) && WardApi.Ready && !ward.Chain.HasLocalAccess())
			{
				string token = ((flag && value2) ? "$q8wz_noaccess_both" : (value2 ? "$q8wz_noaccess_stations" : "$q8wz_noaccess"));
				text2 = text2 + "\n<color=orange>" + L(token) + "</color>";
			}
			return text2;
		}

		public static void Message(string token, int count)
		{
			if ((Object)(object)Player.m_localPlayer != (Object)null)
			{
				((Character)Player.m_localPlayer).Message((MessageType)1, Format(token, count), 0, (Sprite)null, false);
			}
		}

		public static string Placed(Ward ward)
		{
			int value = WardZonePlugin.MaxWards.Value;
			if (ward.Zone == null)
			{
				return Format("$q8wz_full", value, Wards(value));
			}
			int count = ward.Zone.Wards.Count;
			if (value <= 0)
			{
				return Format("$q8wz_joined_count", count, Wards(count));
			}
			return Format("$q8wz_joined", count, L("$q8wz_of"), value);
		}

		private static string L(string token)
		{
			return Localization.instance.Localize(token);
		}

		private static string Format(string token, params object[] args)
		{
			return string.Format(L(token), args);
		}

		private static string Wards(int count)
		{
			bool num = Localization.instance.GetSelectedLanguage() == "Russian";
			int num2 = count % 10;
			int num3 = count % 100;
			if (num)
			{
				if (num2 == 1 && num3 != 11)
				{
					return L("$q8wz_ward_1");
				}
				if (num2 >= 2 && num2 <= 4 && (num3 < 12 || num3 > 14))
				{
					return L("$q8wz_ward_2");
				}
				return L("$q8wz_ward_5");
			}
			return L((count == 1) ? "$q8wz_ward_1" : "$q8wz_ward_2");
		}

		private static int RoundArea(double area)
		{
			int num = ((area >= 1000.0) ? 100 : 10);
			return (int)Math.Round(area / (double)num) * num;
		}

		private static string Thousands(int value)
		{
			string text = value.ToString();
			if (text.Length <= 3)
			{
				return text;
			}
			StringBuilder stringBuilder = new StringBuilder();
			int num = text.Length % 3;
			if (num > 0)
			{
				stringBuilder.Append(text, 0, num);
			}
			for (int i = num; i < text.Length; i += 3)
			{
				if (stringBuilder.Length > 0)
				{
					stringBuilder.Append(' ');
				}
				stringBuilder.Append(text, i, 3);
			}
			return stringBuilder.ToString();
		}
	}
	[BepInPlugin("qua8ion.valheim.wardzone", "WardZone", "1.1.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class WardZonePlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "qua8ion.valheim.wardzone";

		public const string PluginName = "WardZone";

		public const string PluginVersion = "1.1.0";

		internal static WardZonePlugin Instance;

		internal static ManualLogSource Log;

		internal static ConfigEntry<int> MaxWards;

		internal static ConfigEntry<bool> ComfortFromZone;

		internal static ConfigEntry<bool> ContainersFromZone;

		internal static ConfigEntry<bool> ContainersNeedAccess;

		internal static ConfigEntry<bool> StationsFromZone;

		internal static ConfigEntry<bool> SyncEnabled;

		internal static ConfigEntry<bool> SyncPermitted;

		internal static ConfigEntry<bool> ShowZoneOnHover;

		internal static ConfigEntry<bool> HoverText;

		internal static ConfigEntry<bool> PlacementMessage;

		private void Awake()
		{
			//IL_0211: Unknown result type (might be due to invalid IL or missing references)
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			MaxWards = Rule("Zone", "MaxWards", 8, "How many wards one zone can hold. Wards whose circles touch form a chain; the zone grows from the oldest ward of the chain and stops at this many. The rest stay ordinary wards outside the zone. 0: no limit.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000));
			ComfortFromZone = Rule("Zone", "ComfortFromZone", value: true, "Inside a zone the comfort level counts every comfort piece in the zone, not only those within 10 m.");
			ContainersFromZone = Rule("Zone", "ContainersFromZone", value: true, "Inside a zone CraftFromContainers pulls from every chest in the zone, not only those within its range. Needs CraftFromContainers (aedenthorn / UnderHeiz).");
			ContainersNeedAccess = Rule("Zone", "ContainersNeedAccess", value: true, "Chests of the zone are pulled only for players written into a ward of the chain, or its builder. Comfort is for everyone regardless.");
			StationsFromZone = Rule("Zone", "StationsFromZone", value: true, "A workbench, forge or any other crafting station inside a zone counts as near everywhere in the zone: you can build, repair and remove pieces that need it anywhere in the zone. Only for players written into a ward of the chain, or its builder.");
			SyncEnabled = Rule("Zone", "SyncEnabled", value: true, "Enabling or disabling a ward does the same to every ward of its chain, whoever built them. As in the game, only the ward's builder can switch it.");
			SyncPermitted = Rule("Zone", "SyncPermitted", value: true, "Writing yourself into a ward, or out of it, does the same in every ward of its chain. As in the game, this is done on a disabled ward.");
			ShowZoneOnHover = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowZoneOnHover", true, "Looking at a ward shows the circles of every ward in its zone, not only its own.");
			HoverText = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "HoverText", true, "Add a line about the zone to the ward's hover text: how many wards, coverage in m2.");
			PlacementMessage = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "PlacementMessage", true, "After placing a ward, say whether it joined the zone or the zone is already full.");
			MaxWards.SettingChanged += delegate
			{
				ZoneMap.MarkDirty();
			};
			StationsFromZone.SettingChanged += delegate
			{
				ZoneMap.MarkDirty();
			};
			ComfortFromZone.SettingChanged += delegate
			{
				ZoneMap.MarkDirty();
			};
			ContainersFromZone.SettingChanged += delegate
			{
				ZoneMap.MarkDirty();
			};
			SynchronizationManager.OnConfigurationSynchronized += delegate
			{
				ZoneMap.MarkDirty();
			};
			AddLocalization();
			new Harmony("qua8ion.valheim.wardzone").PatchAll(Assembly.GetExecutingAssembly());
			if (!WardApi.Ready)
			{
				Log.LogWarning((object)"PrivateArea has changed: chain switching, chain permissions and the access check are off");
			}
			Log.LogInfo((object)(CfcBridge.Attached ? "Wards whose circles touch share one zone: comfort and CraftFromContainers reach the whole zone" : "Wards whose circles touch share one zone: comfort reaches the whole zone (CraftFromContainers not found, chests unchanged)"));
		}

		private void Update()
		{
			ZoneMap.Tick();
		}

		private ConfigEntry<T> Rule<T>(string section, string key, T value, string description, AcceptableValueBase range = null)
		{
			//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_0022: Expected O, but got Unknown
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			return ((BaseUnityPlugin)this).Config.Bind<T>(section, key, value, new ConfigDescription(description, range, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} }));
		}

		private static void AddLocalization()
		{
			CustomLocalization localization = LocalizationManager.Instance.GetLocalization();
			string text = "English";
			localization.AddTranslation(ref text, new Dictionary<string, string>
			{
				{ "q8wz_zone", "Ward zone" },
				{ "q8wz_of", "of" },
				{ "q8wz_coverage", "coverage" },
				{ "q8wz_m2", "m²" },
				{ "q8wz_ward_1", "ward" },
				{ "q8wz_ward_2", "wards" },
				{ "q8wz_ward_5", "wards" },
				{ "q8wz_outside", "Outside the ward zone: it already holds its limit of {0} {1}" },
				{ "q8wz_noaccess", "Chests of the zone: only for players written into a ward" },
				{ "q8wz_noaccess_stations", "Workbenches of the zone: only for players written into a ward" },
				{ "q8wz_noaccess_both", "Chests and workbenches of the zone: only for players written into a ward" },
				{ "q8wz_joined", "The ward joined the zone: {0} {1} {2}" },
				{ "q8wz_joined_count", "The ward joined the zone: now {0} {1}" },
				{ "q8wz_full", "The zone is full ({0} {1}): this ward stays outside it" },
				{ "q8wz_chain_on", "All wards of the chain are on: {0}" },
				{ "q8wz_chain_off", "All wards of the chain are off: {0}" },
				{ "q8wz_chain_added", "Written into every ward of the chain: {0}" },
				{ "q8wz_chain_removed", "Removed from every ward of the chain: {0}" }
			});
			text = "Russian";
			localization.AddTranslation(ref text, new Dictionary<string, string>
			{
				{ "q8wz_zone", "Зона тотемов" },
				{ "q8wz_of", "из" },
				{ "q8wz_coverage", "покрытие" },
				{ "q8wz_m2", "м²" },
				{ "q8wz_ward_1", "тотем" },
				{ "q8wz_ward_2", "тотема" },
				{ "q8wz_ward_5", "тотемов" },
				{ "q8wz_outside", "Вне зоны тотемов: в ней уже {0} {1}, это потолок" },
				{ "q8wz_noaccess", "Сундуки зоны — только вписанным в тотем" },
				{ "q8wz_noaccess_stations", "Верстаки зоны — только вписанным в тотем" },
				{ "q8wz_noaccess_both", "Сундуки и верстаки зоны — только вписанным в тотем" },
				{ "q8wz_joined", "Тотем вошёл в зону: {0} {1} {2}" },
				{ "q8wz_joined_count", "Тотем вошёл в зону: теперь {0} {1}" },
				{ "q8wz_full", "Зона уже полная ({0} {1}): этот тотем остался вне зоны" },
				{ "q8wz_chain_on", "Включены все тотемы цепи: {0}" },
				{ "q8wz_chain_off", "Выключены все тотемы цепи: {0}" },
				{ "q8wz_chain_added", "Вписан во все тотемы цепи: {0}" },
				{ "q8wz_chain_removed", "Выписан из всех тотемов цепи: {0}" }
			});
		}
	}
	[HarmonyPatch(typeof(CraftingStation), "HaveBuildStationInRange")]
	internal static class CraftingStation_HaveBuildStationInRange_Patch
	{
		private static List<CraftingStation> s_allStations;

		private static bool s_failed;

		private static bool Prepare()
		{
			s_allStations = AccessTools.Field(typeof(CraftingStation), "m_allStations")?.GetValue(null) as List<CraftingStation>;
			if (s_allStations == null)
			{
				ManualLogSource log = WardZonePlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)"CraftingStation.m_allStations not found: workbenches keep their own range");
				}
			}
			return s_allStations != null;
		}

		[HarmonyPriority(200)]
		private static void Postfix(string name, Vector3 point, ref CraftingStation __result)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)__result != (Object)null || s_failed || !WardZonePlugin.StationsFromZone.Value || (Object)(object)Player.m_localPlayer == (Object)null || !WardApi.Ready)
			{
				return;
			}
			try
			{
				__result = Find(name, point);
			}
			catch (Exception ex)
			{
				s_failed = true;
				WardZonePlugin.Log.LogError((object)("Workbenches from the zone are disabled after an error: " + ex));
			}
		}

		private static CraftingStation Find(string name, Vector3 point)
		{
			//IL_0000: 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_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			Zone zone = ZoneMap.ZoneAt(point);
			if (zone == null || !zone.Chain.HasLocalAccess())
			{
				return null;
			}
			CraftingStation result = null;
			float num = float.MaxValue;
			foreach (CraftingStation s_allStation in s_allStations)
			{
				if ((Object)(object)s_allStation == (Object)null || s_allStation.m_name != name)
				{
					continue;
				}
				Vector3 position = ((Component)s_allStation).transform.position;
				if (zone.Contains(position))
				{
					float num2 = Ward.DistanceXZ(position, point);
					if (num2 < num)
					{
						result = s_allStation;
						num = num2;
					}
				}
			}
			return result;
		}
	}
	internal static class WardApi
	{
		private static readonly Func<PrivateArea, bool> s_isEnabled = Method<Func<PrivateArea, bool>>("IsEnabled");

		private static readonly Action<PrivateArea, bool> s_setEnabled = Method<Action<PrivateArea, bool>>("SetEnabled");

		private static readonly Func<PrivateArea, long, bool> s_isPermitted = Method<Func<PrivateArea, long, bool>>("IsPermitted");

		private static readonly Action<PrivateArea, long, string> s_addPermitted = Method<Action<PrivateArea, long, string>>("AddPermitted");

		private static readonly Action<PrivateArea, long> s_removePermitted = Method<Action<PrivateArea, long>>("RemovePermitted");

		private static readonly Func<PrivateArea, bool> s_haveLocalAccess = Method<Func<PrivateArea, bool>>("HaveLocalAccess");

		private static readonly FieldRef<PrivateArea, ZNetView> s_nview = AccessTools.FieldRefAccess<PrivateArea, ZNetView>("m_nview");

		private static readonly FieldRef<PrivateArea, Piece> s_piece = AccessTools.FieldRefAccess<PrivateArea, Piece>("m_piece");

		public static bool Ready
		{
			get
			{
				if (s_isEnabled != null && s_setEnabled != null && s_isPermitted != null && s_addPermitted != null && s_removePermitted != null)
				{
					return s_haveLocalAccess != null;
				}
				return false;
			}
		}

		public static bool IsEnabled(PrivateArea area)
		{
			return s_isEnabled(area);
		}

		public static void SetEnabled(PrivateArea area, bool enabled)
		{
			s_setEnabled(area, enabled);
		}

		public static bool IsPermitted(PrivateArea area, long playerId)
		{
			return s_isPermitted(area, playerId);
		}

		public static void AddPermitted(PrivateArea area, long playerId, string name)
		{
			s_addPermitted(area, playerId, name);
		}

		public static void RemovePermitted(PrivateArea area, long playerId)
		{
			s_removePermitted(area, playerId);
		}

		public static ZNetView View(PrivateArea area)
		{
			return s_nview.Invoke(area);
		}

		public static Piece PieceOf(PrivateArea area)
		{
			return s_piece.Invoke(area);
		}

		public static bool HaveLocalAccess(PrivateArea area)
		{
			if (s_haveLocalAccess != null && (Object)(object)Player.m_localPlayer != (Object)null)
			{
				return s_haveLocalAccess(area);
			}
			return false;
		}

		private static T Method<T>(string name) where T : Delegate
		{
			MethodInfo methodInfo = AccessTools.Method(typeof(PrivateArea), name, (Type[])null, (Type[])null);
			if (!(methodInfo == null))
			{
				return AccessTools.MethodDelegate<T>(methodInfo, (object)null, true);
			}
			return null;
		}
	}
	public sealed class Ward
	{
		public PrivateArea Area;

		public ZNetView View;

		public Vector3 Pos;

		public float Radius;

		public long Stamp;

		public ZDOID Id;

		public Chain Chain;

		public Zone Zone;

		public int ChainSize
		{
			get
			{
				if (Chain == null)
				{
					return 1;
				}
				return Chain.Wards.Count;
			}
		}

		public bool Covers(Vector3 point)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			return DistanceXZ(Pos, point) < Radius;
		}

		public bool Touches(Ward other)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			return DistanceXZ(Pos, other.Pos) <= Radius + other.Radius;
		}

		public static float DistanceXZ(Vector3 a, Vector3 b)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			float num = b.x - a.x;
			float num2 = b.z - a.z;
			return (float)Math.Sqrt(num * num + num2 * num2);
		}
	}
	public sealed class Chain
	{
		public readonly List<Ward> Wards = new List<Ward>();

		public Zone Zone;

		private float m_accessCheckedAt = float.MinValue;

		private bool m_access;

		public bool HasLocalAccess()
		{
			float time = Time.time;
			if (time - m_accessCheckedAt < 0.5f)
			{
				return m_access;
			}
			m_accessCheckedAt = time;
			m_access = false;
			foreach (Ward ward in Wards)
			{
				if ((Object)(object)ward.Area != (Object)null && WardApi.HaveLocalAccess(ward.Area))
				{
					m_access = true;
					break;
				}
			}
			return m_access;
		}
	}
	public sealed class Zone
	{
		public Chain Chain;

		public readonly List<Ward> Wards = new List<Ward>();

		private double m_area = -1.0;

		public int ChainSize
		{
			get
			{
				if (Chain == null)
				{
					return Wards.Count;
				}
				return Chain.Wards.Count;
			}
		}

		public double AreaM2
		{
			get
			{
				if (m_area < 0.0)
				{
					m_area = EstimateArea();
				}
				return m_area;
			}
		}

		public bool Contains(Vector3 point)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			for (int i = 0; i < Wards.Count; i++)
			{
				if (Wards[i].Covers(point))
				{
					return true;
				}
			}
			return false;
		}

		private double EstimateArea()
		{
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			float num = float.MaxValue;
			float num2 = float.MinValue;
			float num3 = float.MaxValue;
			float num4 = float.MinValue;
			foreach (Ward ward in Wards)
			{
				num = Mathf.Min(num, ward.Pos.x - ward.Radius);
				num2 = Mathf.Max(num2, ward.Pos.x + ward.Radius);
				num3 = Mathf.Min(num3, ward.Pos.z - ward.Radius);
				num4 = Mathf.Max(num4, ward.Pos.z + ward.Radius);
			}
			int num5 = Mathf.CeilToInt((num2 - num) / 2f);
			int num6 = Mathf.CeilToInt((num4 - num3) / 2f);
			int num7 = 0;
			for (int i = 0; i < num5; i++)
			{
				float num8 = num + ((float)i + 0.5f) * 2f;
				for (int j = 0; j < num6; j++)
				{
					if (Contains(new Vector3(num8, 0f, num3 + ((float)j + 0.5f) * 2f)))
					{
						num7++;
					}
				}
			}
			return (double)num7 * 2.0 * 2.0;
		}
	}
	public static class ZoneMap
	{
		private const float RebuildInterval = 2f;

		private static readonly int StampHash = StringExtensionMethods.GetStableHashCode("q8wz_t");

		private static readonly FieldInfo s_allAreasField = AccessTools.Field(typeof(PrivateArea), "m_allAreas");

		private static readonly FieldRef<PrivateArea, ZNetView> s_nview = AccessTools.FieldRefAccess<PrivateArea, ZNetView>("m_nview");

		private static readonly Dictionary<PrivateArea, Ward> s_byArea = new Dictionary<PrivateArea, Ward>();

		private static readonly List<Chain> s_chains = new List<Chain>();

		private static readonly List<Zone> s_zones = new List<Zone>();

		private static bool s_dirty = true;

		private static float s_nextRebuild;

		public static int Version { get; private set; }

		public static IReadOnlyList<Zone> Zones
		{
			get
			{
				if (s_dirty)
				{
					Rebuild();
				}
				return s_zones;
			}
		}

		public static IReadOnlyList<Chain> Chains
		{
			get
			{
				if (s_dirty)
				{
					Rebuild();
				}
				return s_chains;
			}
		}

		public static void MarkDirty()
		{
			s_dirty = true;
		}

		public static void Tick()
		{
			if (s_dirty || Time.time >= s_nextRebuild)
			{
				Rebuild();
			}
		}

		public static Zone ZoneAt(Vector3 point)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			if (s_dirty)
			{
				Rebuild();
			}
			for (int i = 0; i < s_zones.Count; i++)
			{
				if (s_zones[i].Contains(point))
				{
					return s_zones[i];
				}
			}
			return null;
		}

		public static Ward WardOf(PrivateArea area)
		{
			if (s_dirty)
			{
				Rebuild();
			}
			if (!((Object)(object)area != (Object)null) || !s_byArea.TryGetValue(area, out var value))
			{
				return null;
			}
			return value;
		}

		public static void Rebuild()
		{
			s_dirty = false;
			s_nextRebuild = Time.time + 2f;
			s_chains.Clear();
			s_zones.Clear();
			s_byArea.Clear();
			Version++;
			List<Ward> list = CollectWards();
			s_chains.AddRange(Build(list, WardZonePlugin.MaxWards.Value));
			foreach (Chain s_chain in s_chains)
			{
				s_zones.Add(s_chain.Zone);
			}
			foreach (Ward item in list)
			{
				s_byArea[item.Area] = item;
			}
			CfcBridge.InvalidateCache();
		}

		public static List<Chain> Build(List<Ward> wards, int maxWards)
		{
			List<Chain> list = new List<Chain>();
			int cap = Math.Max(0, maxWards);
			foreach (List<Ward> item in Groups(wards))
			{
				item.Sort(ByAge);
				Chain chain = new Chain();
				chain.Wards.AddRange(item);
				foreach (Ward item2 in item)
				{
					item2.Chain = chain;
					item2.Zone = null;
				}
				chain.Zone = Grow(chain, cap);
				list.Add(chain);
			}
			return list;
		}

		private static List<Ward> CollectWards()
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: 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)
			List<Ward> list = new List<Ward>();
			if (!(s_allAreasField?.GetValue(null) is List<PrivateArea> list2))
			{
				return list;
			}
			long num = 0L;
			foreach (PrivateArea item in list2)
			{
				if ((Object)(object)item == (Object)null || (int)item.m_ownerFaction != 0)
				{
					continue;
				}
				ZNetView val = s_nview.Invoke(item);
				if ((Object)(object)val == (Object)null || !val.IsValid())
				{
					continue;
				}
				ZDO zDO = val.GetZDO();
				long num2 = zDO.GetLong(StampHash, 0L);
				if (num2 == 0L && val.IsOwner())
				{
					if (num == 0L)
					{
						num = Now();
					}
					num2 = num;
					zDO.Set(StampHash, num2);
				}
				list.Add(new Ward
				{
					Area = item,
					View = val,
					Pos = ((Component)item).transform.position,
					Radius = item.m_radius,
					Stamp = num2,
					Id = zDO.m_uid
				});
			}
			return list;
		}

		private static long Now()
		{
			double num = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTimeSeconds() : 0.0);
			return Math.Max(1L, (long)num);
		}

		private static List<List<Ward>> Groups(List<Ward> wards)
		{
			List<List<Ward>> list = new List<List<Ward>>();
			int[] array = new int[wards.Count];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = -1;
			}
			Queue<int> queue = new Queue<int>();
			for (int j = 0; j < wards.Count; j++)
			{
				if (array[j] >= 0)
				{
					continue;
				}
				List<Ward> list2 = new List<Ward>();
				array[j] = list.Count;
				queue.Enqueue(j);
				while (queue.Count > 0)
				{
					int index = queue.Dequeue();
					list2.Add(wards[index]);
					for (int k = 0; k < wards.Count; k++)
					{
						if (array[k] < 0 && wards[index].Touches(wards[k]))
						{
							array[k] = list.Count;
							queue.Enqueue(k);
						}
					}
				}
				list.Add(list2);
			}
			return list;
		}

		private static Zone Grow(Chain chain, int cap)
		{
			Zone zone = new Zone
			{
				Chain = chain
			};
			List<Ward> list = new List<Ward>(chain.Wards);
			Join(zone, list[0]);
			list.RemoveAt(0);
			while (list.Count > 0 && (cap == 0 || zone.Wards.Count < cap))
			{
				int num = -1;
				for (int i = 0; i < list.Count; i++)
				{
					if (num >= 0)
					{
						break;
					}
					foreach (Ward ward in zone.Wards)
					{
						if (list[i].Touches(ward))
						{
							num = i;
							break;
						}
					}
				}
				if (num < 0)
				{
					break;
				}
				Join(zone, list[num]);
				list.RemoveAt(num);
			}
			return zone;
		}

		private static void Join(Zone zone, Ward ward)
		{
			ward.Zone = zone;
			zone.Wards.Add(ward);
		}

		private static int ByAge(Ward a, Ward b)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			long num = ((a.Stamp == 0L) ? long.MaxValue : a.Stamp);
			long value = ((b.Stamp == 0L) ? long.MaxValue : b.Stamp);
			int num2 = num.CompareTo(value);
			if (num2 == 0)
			{
				return ((ZDOID)(ref a.Id)).CompareTo(b.Id);
			}
			return num2;
		}
	}
}