Decompiled source of SeneaL Shared Pins v0.1.8

BepInEx/plugins/SeneaLSharedPins/SeneaLSharedPins.dll

Decompiled 18 minutes ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Splatform;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyCompany("SeneaLSharedPins")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.8.0")]
[assembly: AssemblyInformationalVersion("0.1.8")]
[assembly: AssemblyProduct("SeneaLSharedPins")]
[assembly: AssemblyTitle("SeneaLSharedPins")]
[assembly: AssemblyVersion("0.1.8.0")]
namespace SeneaLSharedPins;

[BepInPlugin("bananas4dragons.valheim.senealsharedpins", "SeneaL Shared Pins", "0.1.8")]
[BepInDependency("seneaL.valheim.ui", "1.2.8")]
[BepInDependency("eu.mydayyy.plugins.serversidemap", "1.3.14")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public sealed class SharedPinsPlugin : BaseUnityPlugin
{
	private sealed class View
	{
		internal PinData Pin;

		internal PinRecord Desired;

		internal PinRecord InFlight;

		internal PinRecord Displayed;

		internal bool Sending;

		internal bool Removing;
	}

	private sealed class DialogState
	{
		internal PinData Pin;

		internal string Icon;

		internal int Tint;

		internal bool Compass;
	}

	private enum Message : byte
	{
		Hello,
		SnapshotStart,
		SnapshotEnd,
		Upsert,
		Delete,
		Changes,
		Reject,
		CheckOre,
		OreAbsent
	}

	private const string Galleon = "ShipGalleon_DO";

	private const string NavigationId = "navigation_mark_ship_id";

	private const string NavigationActive = "navigation_mark_active";

	private const string NavigationIcon = "PinNavigationMark_DO";

	private const string WreckIcon = "PinShipwreckShip_DO";

	private const string CrateIcon = "PinShipwreckCrate_DO";

	private FieldInfo artisanNativeNavigation;

	private FieldInfo artisanShipId;

	private FieldInfo artisanWaterPin;

	private FieldInfo artisanWaterIcon;

	private FieldInfo artisanSprites;

	private MethodInfo artisanNavigationSprite;

	private readonly Dictionary<string, (Component Source, PinData Pin, PinRecord Record)> artisanWater = new Dictionary<string, (Component, PinData, PinRecord)>();

	private readonly List<ZDO> galleons = new List<ZDO>();

	private int galleonSector;

	private float nextGalleonScan;

	private readonly Dictionary<string, View> views = new Dictionary<string, View>();

	private readonly Dictionary<PinData, string> ownedPins = new Dictionary<PinData, string>();

	private readonly Dictionary<long, PinRecord> manualStyle = new Dictionary<long, PinRecord>();

	private readonly Dictionary<PinData, PinRecord> newManual = new Dictionary<PinData, PinRecord>();

	private readonly Dictionary<string, (int Percent, float Next)> observed = new Dictionary<string, (int, float)>();

	private readonly HashSet<string> absentNativeOre = new HashSet<string>(StringComparer.Ordinal);

	private readonly Dictionary<Vector2s, List<PinRecord>> sharedOreByZone = new Dictionary<Vector2s, List<PinRecord>>();

	private bool oreIndexDirty;

	private readonly HashSet<string> hiddenPins = new HashSet<string>(StringComparer.Ordinal);

	private readonly Dictionary<string, PinData> nativeLive = new Dictionary<string, PinData>();

	private Minimap clientMap;

	private float nextDiscoveryScan;

	private int internalMapChange;

	private int loadingMap;

	private int oreRefresh;

	private DialogState activeDialog;

	public const string Id = "bananas4dragons.valheim.senealsharedpins";

	private const string RpcName = "B4D_SeneaLSharedPins_2";

	private const int FileVersion = 2;

	private const int BatchSize = 48;

	private static SharedPinsPlugin Instance;

	private Harmony harmony;

	private SeneaLBindings ui;

	private ConfigEntry<int> oreThreshold;

	private ConfigEntry<bool> removeDepletedOre;

	private ConfigEntry<bool> liveMarkers;

	private ConfigEntry<bool> artisanMarkers;

	private readonly PinStore store = new PinStore();

	private readonly Dictionary<ZRpc, ZNetPeer> registered = new Dictionary<ZRpc, ZNetPeer>();

	private readonly HashSet<ZRpc> subscribers = new HashSet<ZRpc>();

	private readonly Dictionary<string, PinRecord> serverChanges = new Dictionary<string, PinRecord>();

	private readonly Dictionary<string, float> missingObjects = new Dictionary<string, float>();

	private readonly Dictionary<string, PinRecord> clientRecords = new Dictionary<string, PinRecord>();

	private ZNet currentNet;

	private ZRpc serverRpc;

	private long worldId;

	private string savePath;

	private bool serverActive;

	private bool saveDirty;

	private bool backupLegacySave;

	private bool clientReady;

	private bool snapshotStarted;

	private bool serverHidesDepletedOre;

	private float nextTick;

	private float nextHello;

	private float nextWorldScan;

	private float nextSave;

	private float nextFailureLog;

	private int SharedOreThreshold
	{
		get
		{
			if (oreThreshold.Value >= 0)
			{
				return oreThreshold.Value;
			}
			return ui.OreThreshold;
		}
	}

	private bool ClientActive
	{
		get
		{
			if (clientReady && serverRpc != null)
			{
				return (Object)(object)Minimap.instance != (Object)null;
			}
			return false;
		}
	}

	private void BindArtisan()
	{
		Type type = SeneaLBindings.FindType("Artisan_Mastery.NavigationMarkTracker");
		Type type2 = SeneaLBindings.FindType("Artisan_Mastery.WaterContainerSpawner");
		if (type == null || type2 == null)
		{
			return;
		}
		try
		{
			artisanNativeNavigation = SeneaLBindings.Field(type, "_pin");
			artisanShipId = SeneaLBindings.Field(type, "_trackedShipId");
			artisanNavigationSprite = SeneaLBindings.Method(type, "GetPinSprite");
			artisanSprites = SeneaLBindings.Field(type2, "PinSprites");
			Type type3 = type2.GetNestedType("WaterContainerPinComponent", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new TypeLoadException("WaterContainerPinComponent");
			artisanWaterPin = SeneaLBindings.Field(type3, "Pin");
			artisanWaterIcon = SeneaLBindings.Field(type3, "PinSprite");
			Hook(type3, "TryCreatePin", null, "ArtisanWaterCreated");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Artisan Mastery marker integration resolved.");
		}
		catch (Exception ex)
		{
			artisanWaterPin = null;
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Artisan Mastery marker hooks could not be resolved: " + ex.Message));
		}
	}

	private static void ArtisanWaterCreated(Component __instance)
	{
		//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
		SharedPinsPlugin instance = Instance;
		if ((Object)(object)instance == (Object)null || instance.artisanWaterPin == null || !instance.artisanMarkers.Value)
		{
			return;
		}
		object? value = instance.artisanWaterPin.GetValue(__instance);
		PinData val = (PinData)((value is PinData) ? value : null);
		if (val == null)
		{
			return;
		}
		ZNetView componentInParent = __instance.GetComponentInParent<ZNetView>();
		if (Object.op_Implicit((Object)(object)componentInParent) && componentInParent.IsValid())
		{
			string text = instance.artisanWaterIcon.GetValue(__instance) as string;
			if (!(text != "PinShipwreckShip_DO") || !(text != "PinShipwreckCrate_DO"))
			{
				PinRecord pinRecord = new PinRecord
				{
					Kind = ((text == "PinShipwreckShip_DO") ? PinKind.ArtisanWreck : PinKind.ArtisanCrate),
					Name = (val.m_name ?? ""),
					Icon = text
				};
				SetObject(pinRecord, componentInParent.GetZDO().m_uid);
				SetPosition(pinRecord, val.m_pos);
				instance.artisanWater[pinRecord.Id] = (__instance, val, pinRecord);
			}
		}
	}

	private void ScanArtisan(float now)
	{
		if (!artisanMarkers.Value)
		{
			return;
		}
		KeyValuePair<string, (Component, PinData, PinRecord)>[] array = artisanWater.ToArray();
		for (int i = 0; i < array.Length; i++)
		{
			KeyValuePair<string, (Component, PinData, PinRecord)> keyValuePair = array[i];
			if (!Object.op_Implicit((Object)(object)keyValuePair.Value.Item1) || artisanWaterPin.GetValue(keyValuePair.Value.Item1) != keyValuePair.Value.Item2)
			{
				artisanWater.Remove(keyValuePair.Key);
				continue;
			}
			nativeLive[keyValuePair.Key] = keyValuePair.Value.Item2;
			if (!clientRecords.ContainsKey(keyValuePair.Key))
			{
				Report(keyValuePair.Value.Item3, now);
			}
		}
		object? obj = artisanNativeNavigation?.GetValue(null);
		PinData val = (PinData)((obj is PinData) ? obj : null);
		if (val == null)
		{
			return;
		}
		string text = (string)artisanShipId.GetValue(null);
		if (string.IsNullOrEmpty(text))
		{
			return;
		}
		foreach (PinRecord value in clientRecords.Values)
		{
			if (value.Kind == PinKind.ArtisanNavigation && value.Icon == text)
			{
				nativeLive[value.Id] = val;
			}
		}
	}

	private Sprite ArtisanSprite(PinKind kind)
	{
		object obj;
		switch (kind)
		{
		case PinKind.ArtisanNavigation:
		{
			object? obj2 = artisanNavigationSprite?.Invoke(null, new object[1] { "PinNavigationMark_DO" });
			return (Sprite)(((obj2 is Sprite) ? obj2 : null) ?? ui.Sprite("ship"));
		}
		default:
			obj = "PinShipwreckCrate_DO";
			break;
		case PinKind.ArtisanWreck:
			obj = "PinShipwreckShip_DO";
			break;
		}
		string key = (string)obj;
		if (artisanSprites?.GetValue(null) is IDictionary<string, Sprite> dictionary && dictionary.TryGetValue(key, out var value))
		{
			return value;
		}
		return ui.Sprite((kind == PinKind.ArtisanWreck) ? "ship" : "bag");
	}

	private static bool ValidateArtisan(PinRecord pin, ZDO zdo, GameObject prefab)
	{
		string name = ((Object)prefab).name;
		switch (pin.Kind)
		{
		case PinKind.ArtisanNavigation:
			if (name != "ShipGalleon_DO" || !zdo.GetBool("navigation_mark_active", false))
			{
				return false;
			}
			pin.Icon = zdo.GetString("navigation_mark_ship_id", "");
			pin.Name = "";
			return pin.Icon.Length > 0;
		case PinKind.ArtisanWreck:
			if (!(name == "ShipwreckFloatingShipFull_DO") && !(name == "ShipwreckFloatingShipFront_DO"))
			{
				return name == "ShipwreckFloatingShipBack_DO";
			}
			return true;
		case PinKind.ArtisanCrate:
			if (!(name == "ShipwreckFloatingCrateLarge_DO") && !(name == "ShipwreckFloatingCrateMedium_DO"))
			{
				return name == "ShipwreckFloatingCrateSmall_DO";
			}
			return true;
		default:
			return false;
		}
	}

	private void ScanGalleonMarks(float now)
	{
		//IL_009c: Unknown result type (might be due to invalid IL or missing references)
		ZDOMan instance = ZDOMan.instance;
		ZNetScene instance2 = ZNetScene.instance;
		if (!artisanMarkers.Value || instance == null || !Object.op_Implicit((Object)(object)instance2) || !Object.op_Implicit((Object)(object)instance2.GetPrefab("ShipGalleon_DO")) || now < nextGalleonScan || !instance.GetAllZDOsWithPrefabIterative("ShipGalleon_DO", galleons, ref galleonSector))
		{
			return;
		}
		GameObject prefab = instance2.GetPrefab("ShipGalleon_DO");
		foreach (ZDO galleon in galleons)
		{
			if (galleon != null && galleon.IsValid())
			{
				PinRecord pinRecord = new PinRecord
				{
					Kind = PinKind.ArtisanNavigation
				};
				SetObject(pinRecord, galleon.m_uid);
				if (PrepareLive(pinRecord, galleon, prefab) && store.Upsert(pinRecord, SharedOreThreshold, out var current2))
				{
					RecordChange(pinRecord.Id, current2);
				}
			}
		}
		galleons.Clear();
		galleonSector = 0;
		nextGalleonScan = now + 2f;
	}

	private void ResetArtisanClient()
	{
		artisanWater.Clear();
	}

	private void ReceiveClient(Message message, BinaryReader reader)
	{
		switch (message)
		{
		case Message.SnapshotStart:
			if (!clientReady)
			{
				snapshotStarted = true;
				clientRecords.Clear();
				oreIndexDirty = true;
			}
			break;
		case Message.SnapshotEnd:
			if (snapshotStarted && !clientReady)
			{
				serverHidesDepletedOre = reader.BaseStream.Position < reader.BaseStream.Length && reader.ReadBoolean();
				snapshotStarted = false;
				clientReady = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Shared pin snapshot received. " + clientRecords.Count + " records."));
			}
			break;
		case Message.Changes:
			if (snapshotStarted || clientReady)
			{
				int num = reader.ReadInt32();
				if (num < 0 || num > 48)
				{
					throw new InvalidDataException("Invalid pin batch.");
				}
				for (int i = 0; i < num; i++)
				{
					ReadChange(reader, rejected: false);
				}
			}
			break;
		case Message.OreAbsent:
		{
			string text = reader.ReadString();
			if (clientReady && reader.BaseStream.Position == reader.BaseStream.Length && observed.ContainsKey(text))
			{
				absentNativeOre.Add(text);
			}
			break;
		}
		case Message.Reject:
			if (clientReady)
			{
				ReadChange(reader, rejected: true);
			}
			break;
		case Message.Upsert:
		case Message.Delete:
		case Message.CheckOre:
			break;
		}
	}

	private void ReadChange(BinaryReader reader, bool rejected)
	{
		if (!reader.ReadBoolean())
		{
			string text = reader.ReadString();
			if (clientRecords.TryGetValue(text, out var value) && value.Kind == PinKind.Ore)
			{
				oreIndexDirty = true;
			}
			clientRecords.Remove(text);
			RemoveView(text);
			return;
		}
		PinRecord pinRecord = PinRecord.Read(reader);
		clientRecords[pinRecord.Id] = pinRecord;
		if (pinRecord.Kind == PinKind.Ore)
		{
			oreIndexDirty = true;
		}
		if (pinRecord.Kind == PinKind.Manual && views.TryGetValue(pinRecord.Id, out var value2) && value2.Sending)
		{
			if (rejected || value2.Desired == null || value2.InFlight == null || value2.InFlight.SameDisplay(value2.Desired))
			{
				value2.Desired = null;
			}
			value2.Sending = (value2.Removing = false);
			value2.InFlight = null;
		}
	}

	private void TickClient(float now)
	{
		Minimap instance = Minimap.instance;
		if ((Object)(object)instance != (Object)(object)clientMap)
		{
			ClearViews();
			clientMap = instance;
		}
		HashSet<PinData> currentPins = new HashSet<PinData>(ui.MapPins(instance));
		CaptureNewManual(instance, currentPins);
		SendManualChanges(currentPins);
		if (now >= nextDiscoveryScan)
		{
			nextDiscoveryScan = now + 1f;
			ScanDiscoveries(now);
			ReconcileNativeOreMarkers(instance);
			ScanLive(now);
		}
		foreach (PinRecord value2 in clientRecords.Values)
		{
			if (value2.Kind == PinKind.Dungeon)
			{
				object entry = GetEntry(value2);
				if (entry != null && ui.EntryPin(entry) != null)
				{
					PinData pin = ui.EntryPin(entry);
					ui.ClearEntryPin(entry);
					DropPin(instance, pin);
				}
			}
			if (!ShouldDisplay(value2, currentPins))
			{
				RemoveView(value2.Id);
				continue;
			}
			if (!views.TryGetValue(value2.Id, out var value))
			{
				value = new View();
				views.Add(value2.Id, value);
			}
			if (!value.Removing)
			{
				PinRecord pinRecord = value.Desired ?? value2;
				if (value2.IsLive && (int)value2.Kind < 6 && ui.IsLiveHidden(value2))
				{
					pinRecord = value2.Copy();
					pinRecord.Checked = true;
				}
				if (value.Pin == null)
				{
					MakePin(instance, value, pinRecord);
				}
				else if (value.Displayed == null || !value.Displayed.SameDisplay(pinRecord))
				{
					ApplyPin(instance, value, pinRecord);
				}
			}
		}
	}

	private bool ShouldDisplay(PinRecord record, HashSet<PinData> currentPins)
	{
		if (hiddenPins.Contains(record.Id) || record.IsHiddenByOrePolicy(serverHidesDepletedOre))
		{
			return false;
		}
		if (record.IsDiscovery)
		{
			return ui.CanShowDiscoveries;
		}
		if (!record.IsLive)
		{
			return true;
		}
		if (nativeLive.TryGetValue(record.Id, out var value) && currentPins.Contains(value))
		{
			return false;
		}
		if ((int)record.Kind < 6)
		{
			return ui.ShowLive(record);
		}
		return artisanMarkers.Value;
	}

	private object GetEntry(PinRecord record)
	{
		if (!ui.OwnPlayerLoaded)
		{
			return null;
		}
		return ui.Known["d" + Mathf.RoundToInt(record.X) + "_" + Mathf.RoundToInt(record.Z)];
	}

	private void ReconcileNativeOreMarkers(Minimap map)
	{
		if (!ui.OwnPlayerLoaded)
		{
			return;
		}
		bool flag = false;
		foreach (DictionaryEntry item in ui.Known)
		{
			object value = item.Value;
			if (ui.EntryDungeon(value))
			{
				continue;
			}
			if (absentNativeOre.Count != 0 && !ui.EntryOff(value))
			{
				PinRecord pinRecord = NativeOreRecord(value);
				if (absentNativeOre.Contains(pinRecord.LegacyOreQueryKey) && ReadNativeOre(pinRecord, (Component)/*isinst with value type is only supported in some contexts*/) == null)
				{
					ui.DisableEntry(value);
					flag = true;
				}
			}
			PinData val = ui.EntryPin(value);
			if (val != null && HasSharedOreMarker(value))
			{
				ui.ClearEntryPin(value);
				DropPin(map, val);
			}
		}
		if (flag)
		{
			ui.SaveOreDiscoveries(Player.m_localPlayer);
		}
	}

	private bool HasSharedOreMarker(object entry)
	{
		//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
		//IL_0111: Unknown result type (might be due to invalid IL or missing references)
		//IL_0112: Unknown result type (might be due to invalid IL or missing references)
		//IL_0113: Unknown result type (might be due to invalid IL or missing references)
		//IL_0118: Unknown result type (might be due to invalid IL or missing references)
		//IL_011d: Unknown result type (might be due to invalid IL or missing references)
		//IL_011e: Unknown result type (might be due to invalid IL or missing references)
		//IL_011f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0124: Unknown result type (might be due to invalid IL or missing references)
		//IL_0129: Unknown result type (might be due to invalid IL or missing references)
		//IL_0134: Unknown result type (might be due to invalid IL or missing references)
		//IL_0209: Unknown result type (might be due to invalid IL or missing references)
		//IL_0141: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f5: Unknown result type (might be due to invalid IL or missing references)
		//IL_015a: Unknown result type (might be due to invalid IL or missing references)
		//IL_009a: Unknown result type (might be due to invalid IL or missing references)
		//IL_009f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a4: 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_00bd: Unknown result type (might be due to invalid IL or missing references)
		PinRecord pinRecord = NativeOreRecord(entry);
		object? obj = ui.Rocks[ui.EntryId(entry)];
		PinRecord pinRecord2 = ReadNativeOre(pinRecord, (Component)((obj is Component) ? obj : null));
		if (pinRecord2 != null)
		{
			return clientRecords.ContainsKey(pinRecord2.Id);
		}
		if (absentNativeOre.Contains(pinRecord.LegacyOreQueryKey))
		{
			return true;
		}
		if (oreIndexDirty)
		{
			sharedOreByZone.Clear();
			foreach (PinRecord value3 in clientRecords.Values)
			{
				if (value3.Kind == PinKind.Ore)
				{
					Vector2s zone = ZoneSystem.GetZone(SeneaLBindings.Position(value3));
					if (!sharedOreByZone.TryGetValue(zone, out var value))
					{
						value = (sharedOreByZone[zone] = new List<PinRecord>());
					}
					value.Add(value3);
				}
			}
			oreIndexDirty = false;
		}
		Vector3 val = SeneaLBindings.Position(pinRecord);
		Vector3 val2 = default(Vector3);
		((Vector3)(ref val2))..ctor(2f, 0f, 2f);
		Vector2s zone2 = ZoneSystem.GetZone(val - val2);
		Vector2s zone3 = ZoneSystem.GetZone(val + val2);
		PinRecord match = null;
		int bestRank = 0;
		bool ambiguous = false;
		for (int i = zone2.x; i <= zone3.x; i++)
		{
			for (int j = zone2.y; j <= zone3.y; j++)
			{
				if (!sharedOreByZone.TryGetValue(new Vector2s((short)i, (short)j), out var value2))
				{
					continue;
				}
				foreach (PinRecord item in value2)
				{
					if (!(pinRecord.DistanceSquared(item.X, item.Y, item.Z) > 4f))
					{
						int rank = pinRecord.LegacyOreMatchRank(item.X, item.Y, item.Z, item.Name);
						PinRecord.ConsiderOreMatch(item, rank, ref match, ref bestRank, ref ambiguous);
					}
				}
			}
		}
		if (match != null)
		{
			return !ambiguous;
		}
		return false;
	}

	private PinRecord NativeOreRecord(object entry)
	{
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		PinRecord obj = new PinRecord
		{
			Kind = PinKind.Ore,
			Name = ui.EntryName(entry)
		};
		SetPosition(obj, ui.EntryPos(entry));
		SetObject(obj, ui.EntryZdo(entry));
		return obj;
	}

	private PinRecord ReadNativeOre(PinRecord legacy, Component rock)
	{
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		//IL_006e: Unknown result type (might be due to invalid IL or missing references)
		ZNetView val = (Object.op_Implicit((Object)(object)rock) ? rock.GetComponent<ZNetView>() : null);
		if (!Object.op_Implicit((Object)(object)val) || !val.IsValid())
		{
			return null;
		}
		ZDO zDO = val.GetZDO();
		Vector3 position = zDO.GetPosition();
		if (legacy.DistanceSquared(position.x, position.y, position.z) > 1f / 64f)
		{
			return null;
		}
		PinRecord pinRecord = legacy.Copy();
		pinRecord.OrePrefab = zDO.GetPrefab();
		SetPosition(pinRecord, position);
		SetObject(pinRecord, zDO.m_uid);
		return pinRecord;
	}

	private void CaptureNewManual(Minimap map, HashSet<PinData> currentPins)
	{
		//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
		KeyValuePair<PinData, PinRecord>[] array = newManual.ToArray();
		for (int i = 0; i < array.Length; i++)
		{
			KeyValuePair<PinData, PinRecord> keyValuePair = array[i];
			PinData key = keyValuePair.Key;
			if (key == ui.NamePin(map))
			{
				continue;
			}
			newManual.Remove(key);
			if (currentPins.Contains(key))
			{
				PinRecord value = keyValuePair.Value;
				value.Id = ManualId(key);
				value.Kind = PinKind.Manual;
				ReadManual(key, value);
				if (views.ContainsKey(value.Id) || clientRecords.ContainsKey(value.Id))
				{
					DropPin(map, key);
					continue;
				}
				View value2 = new View
				{
					Pin = key,
					Desired = value,
					Displayed = value
				};
				views.Add(value.Id, value2);
				ownedPins.Add(key, value.Id);
				manualStyle[SeneaLBindings.PositionKey(key.m_pos)] = value;
			}
		}
	}

	private static string ManualId(PinData pin)
	{
		//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)
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_000a: Invalid comparison between Unknown and I4
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Invalid comparison between Unknown and I4
		PinType type = pin.m_type;
		if ((int)type == 9 || type - 14 <= 3)
		{
			string s = string.Format(CultureInfo.InvariantCulture, "{0}:{1:R}:{2:R}:{3:R}", pin.m_type, pin.m_pos.x, pin.m_pos.y, pin.m_pos.z);
			using SHA256 sHA = SHA256.Create();
			byte[] array = sHA.ComputeHash(Encoding.UTF8.GetBytes(s));
			Array.Resize(ref array, 16);
			return new Guid(array).ToString("N");
		}
		return Guid.NewGuid().ToString("N");
	}

	private void SendManualChanges(HashSet<PinData> currentPins)
	{
		KeyValuePair<string, View>[] array = views.ToArray();
		for (int i = 0; i < array.Length; i++)
		{
			KeyValuePair<string, View> pair = array[i];
			View value = pair.Value;
			if (value.Pin == null)
			{
				continue;
			}
			clientRecords.TryGetValue(pair.Key, out var accepted);
			PinRecord pinRecord = value.Desired ?? accepted;
			if (pinRecord == null || pinRecord.Kind != PinKind.Manual)
			{
				continue;
			}
			if (!currentPins.Contains(value.Pin))
			{
				if (value.Sending)
				{
					continue;
				}
				if (accepted == null)
				{
					RemoveView(pair.Key);
					continue;
				}
				value.Sending = (value.Removing = true);
				Send(serverRpc, Message.Delete, delegate(BinaryWriter writer)
				{
					writer.Write(pair.Key);
					writer.Write(accepted.Revision);
				});
			}
			else
			{
				if (!value.Sending && value.Desired == null && value.Displayed != null && !value.Displayed.SameDisplay(pinRecord))
				{
					continue;
				}
				PinRecord pinRecord2 = pinRecord.Copy();
				ReadManual(value.Pin, pinRecord2);
				if (value.Desired == null && value.Displayed != null && !value.Displayed.SameDisplay(pinRecord2))
				{
					value.Desired = pinRecord2;
				}
				else if (value.Desired != null)
				{
					value.Desired = pinRecord2;
				}
				if (value.Sending || value.Desired == null)
				{
					continue;
				}
				if (accepted != null && accepted.SameDisplay(value.Desired))
				{
					value.Desired = null;
					continue;
				}
				pinRecord2 = value.Desired.Copy();
				pinRecord2.Revision = accepted?.Revision ?? 0;
				if (pinRecord2.IsValid())
				{
					value.Sending = true;
					value.InFlight = pinRecord2;
					Send(serverRpc, Message.Upsert, pinRecord2.Write);
				}
			}
		}
	}

	private static void ReadManual(PinData pin, PinRecord record)
	{
		//IL_0088: Unknown result type (might be due to invalid IL or missing references)
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		string text = pin.m_name ?? "";
		record.Name = text.Substring(0, Math.Min(text.Length, 200));
		if (Array.IndexOf(SeneaLBindings.CustomIcons, record.Icon) <= 0)
		{
			record.Icon = (Enum.IsDefined(typeof(PinType), pin.m_type) ? ((object)Unsafe.As<PinType, PinType>(ref pin.m_type)/*cast due to .constrained prefix*/).ToString() : "Icon3");
		}
		record.Checked = pin.m_checked;
		SetPosition(record, pin.m_pos);
	}

	private void ScanDiscoveries(float now)
	{
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_0071: Unknown result type (might be due to invalid IL or missing references)
		//IL_0076: Unknown result type (might be due to invalid IL or missing references)
		//IL_0078: Unknown result type (might be due to invalid IL or missing references)
		//IL_0079: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
		//IL_0117: Unknown result type (might be due to invalid IL or missing references)
		//IL_0157: Unknown result type (might be due to invalid IL or missing references)
		//IL_0234: Unknown result type (might be due to invalid IL or missing references)
		if (!ui.OwnPlayerLoaded || !ui.CanReportDiscoveries)
		{
			return;
		}
		Vector3 position = ((Component)Player.m_localPlayer).transform.position;
		if (ui.WantsDungeons)
		{
			ScanDungeonEntrances(position, now);
		}
		foreach (DictionaryEntry item in ui.Known)
		{
			object value = item.Value;
			Vector3 val = ui.EntryPos(value);
			if (Utils.DistanceXZ(position, val) > 120f)
			{
				continue;
			}
			PinRecord pinRecord = new PinRecord
			{
				Name = ui.EntryName(value)
			};
			SetPosition(pinRecord, val);
			if (ui.EntryDungeon(value))
			{
				if (!ui.EntryOff(value))
				{
					pinRecord.Kind = PinKind.Dungeon;
					pinRecord.Id = pinRecord.CanonicalId;
					if (!clientRecords.ContainsKey(pinRecord.Id))
					{
						Report(pinRecord, now);
					}
				}
				continue;
			}
			pinRecord.Kind = PinKind.Ore;
			SetObject(pinRecord, ui.EntryZdo(value));
			object? obj = ui.Rocks[ui.EntryId(value)];
			Component rock = (Component)((obj is Component) ? obj : null);
			PinRecord pinRecord2 = ReadNativeOre(pinRecord, rock);
			if (pinRecord2 == null)
			{
				ReportLegacyOre(pinRecord, position, now);
				continue;
			}
			pinRecord2.Percent = ui.ReadPercent(rock);
			if (!clientRecords.TryGetValue(pinRecord2.Id, out var value2) || value2.IsMissingOre || (pinRecord2.Percent >= 0 && (value2.Percent < 0 || pinRecord2.Percent < value2.Percent)))
			{
				Report(pinRecord2, now);
			}
		}
		foreach (PinRecord value3 in clientRecords.Values)
		{
			if (value3.Kind != PinKind.Ore || value3.OrePrefab == 0 || value3.IsMissingOre || value3.Percent == 0 || DistanceXZ(value3, position) > 120f || !IsSameOre(value3, FindObject(value3)))
			{
				continue;
			}
			object? obj2 = ui.Rocks[value3.ObjectKey];
			Component val2 = (Component)((obj2 is Component) ? obj2 : null);
			if (Object.op_Implicit((Object)(object)val2))
			{
				int num = ui.ReadPercent(val2);
				if (num >= 0 && (value3.Percent < 0 || num < value3.Percent))
				{
					PinRecord pinRecord3 = value3.Copy();
					pinRecord3.Percent = num;
					Report(pinRecord3, now);
				}
			}
		}
	}

	private void ReportLegacyOre(PinRecord ore, Vector3 playerPosition, float now)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		if (!(DistanceXZ(ore, playerPosition) > 45f) && ore.IsValid())
		{
			string legacyOreQueryKey = ore.LegacyOreQueryKey;
			if (!absentNativeOre.Contains(legacyOreQueryKey) && (!observed.TryGetValue(legacyOreQueryKey, out (int, float) value) || !(now < value.Item2)))
			{
				observed[legacyOreQueryKey] = (-1, now + 8f);
				Send(serverRpc, Message.CheckOre, ore.Write);
			}
		}
	}

	private void ScanDungeonEntrances(Vector3 playerPosition, float now)
	{
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		//IL_0050: Unknown result type (might be due to invalid IL or missing references)
		//IL_0056: Unknown result type (might be due to invalid IL or missing references)
		//IL_006c: Unknown result type (might be due to invalid IL or missing references)
		//IL_006d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0080: Unknown result type (might be due to invalid IL or missing references)
		//IL_0086: Unknown result type (might be due to invalid IL or missing references)
		//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
		float detectionRadius = ui.DetectionRadius;
		foreach (Location dungeonPlace in ui.DungeonPlaces)
		{
			if (!Object.op_Implicit((Object)(object)dungeonPlace))
			{
				continue;
			}
			Vector3 position = ((Component)dungeonPlace).transform.position;
			if (position.y > 3000f || Mathf.Abs(position.y - playerPosition.y) > 45f || Utils.DistanceXZ(position, playerPosition) > detectionRadius + dungeonPlace.m_exteriorRadius)
			{
				continue;
			}
			string text = PinRecord.DungeonKey(position.x, position.z);
			clientRecords.TryGetValue(text, out var value);
			int num = ((value != null) ? DungeonNames.Priority(value.Icon, value.Name) : 0);
			if (num == 3)
			{
				continue;
			}
			PinRecord pinRecord = new PinRecord
			{
				Id = text,
				Kind = PinKind.Dungeon
			};
			SetPosition(pinRecord, position);
			if (TryDungeonLabel(dungeonPlace, out var label, out var source))
			{
				if (DungeonNames.Priority(source, label) <= num)
				{
					continue;
				}
				pinRecord.Name = label;
				pinRecord.Icon = source;
			}
			else
			{
				object entry = GetEntry(pinRecord);
				if (value != null || entry == null)
				{
					continue;
				}
				pinRecord.Name = ui.EntryName(entry);
			}
			Report(pinRecord, now);
		}
	}

	private static bool TryDungeonLabel(Location location, out string label, out string source)
	{
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		//IL_006b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0070: Unknown result type (might be due to invalid IL or missing references)
		//IL_007e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0083: Unknown result type (might be due to invalid IL or missing references)
		//IL_0085: Unknown result type (might be due to invalid IL or missing references)
		//IL_0087: Unknown result type (might be due to invalid IL or missing references)
		//IL_0088: Unknown result type (might be due to invalid IL or missing references)
		//IL_008d: Unknown result type (might be due to invalid IL or missing references)
		//IL_009d: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
		label = (source = null);
		if (!Object.op_Implicit((Object)(object)location))
		{
			return false;
		}
		Vector3 position = ((Component)location).transform.position;
		float num = Mathf.Max(20f, location.m_exteriorRadius) + 8f;
		float num2 = num * num;
		Teleport[] componentsInChildren = ((Component)location).GetComponentsInChildren<Teleport>(true);
		foreach (Teleport val in componentsInChildren)
		{
			if (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)val.m_targetPoint))
			{
				continue;
			}
			Vector3 position2 = ((Component)val).transform.position;
			Vector3 position3 = ((Component)val.m_targetPoint).transform.position;
			Vector3 val2 = position2 - position;
			float sqrMagnitude = ((Vector3)(ref val2)).sqrMagnitude;
			if (!(sqrMagnitude >= num2) && (!(position2.y > 3000f) || !(position3.y < position2.y)))
			{
				string text = UsefulDungeonLabel(val.m_enterText);
				if (text != null)
				{
					num2 = sqrMagnitude;
					label = text;
				}
			}
		}
		if (label != null)
		{
			source = "dungeon:entrance";
			return true;
		}
		label = UsefulDungeonLabel(location.m_discoverLabel);
		if (label != null)
		{
			source = "dungeon:discovery";
			return true;
		}
		Transform val3 = ((Component)location).transform;
		while (Object.op_Implicit((Object)(object)val3))
		{
			label = DungeonNames.ForPrefab(((Object)val3).name);
			if (label != null)
			{
				source = ((Object)val3).name;
				return true;
			}
			val3 = val3.parent;
		}
		return false;
	}

	private static string UsefulDungeonLabel(string text)
	{
		string text2 = DungeonNames.SpecificLabel(text);
		if (text2 == null || Localization.instance == null)
		{
			return text2;
		}
		if (DungeonNames.SpecificLabel(Localization.instance.Localize(text2)) == null)
		{
			return null;
		}
		return text2;
	}

	private static void SetObject(PinRecord record, ZDOID id)
	{
		record.ObjectUser = ((ZDOID)(ref id)).UserID;
		record.ObjectId = ((ZDOID)(ref id)).ID;
		record.Id = record.CanonicalId;
	}

	private void Report(PinRecord record, float now)
	{
		if (record.IsValid() && (!observed.TryGetValue(record.Id, out (int, float) value) || record.Percent != value.Item1 || !(now < value.Item2)))
		{
			observed[record.Id] = (record.Percent, now + 8f);
			Send(serverRpc, Message.Upsert, record.Write);
		}
	}

	private void ScanLive(float now)
	{
		//IL_0055: Unknown result type (might be due to invalid IL or missing references)
		//IL_007e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0091: Unknown result type (might be due to invalid IL or missing references)
		nativeLive.Clear();
		foreach (DictionaryEntry track in ui.Tracks)
		{
			object value = track.Value;
			PinData val = ui.TrackPin(value);
			if (val != null)
			{
				PinRecord pinRecord = new PinRecord
				{
					Kind = ui.TrackKind(val.m_type),
					Name = (val.m_name ?? "")
				};
				SetPosition(pinRecord, val.m_pos);
				SetObject(pinRecord, ui.TrackId(value));
				nativeLive[pinRecord.Id] = val;
				if (!clientRecords.ContainsKey(pinRecord.Id) && !ui.TrackHidden(value))
				{
					Report(pinRecord, now);
				}
			}
		}
		ScanArtisan(now);
	}

	private void MakePin(Minimap map, View view, PinRecord record)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0014: 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_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0050: Unknown result type (might be due to invalid IL or missing references)
		PinType val = ui.TypeFor(record);
		ui.Extend(map, val);
		internalMapChange++;
		try
		{
			view.Pin = map.AddPin(SeneaLBindings.Position(record), val, Label(record), record.Kind == PinKind.Manual, record.Checked, 0L, default(PlatformUserID));
			ownedPins[view.Pin] = record.Id;
		}
		finally
		{
			internalMapChange--;
		}
		ApplyPin(map, view, record);
	}

	private void ApplyPin(Minimap map, View view, PinRecord record)
	{
		//IL_00a2: 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_0182: Unknown result type (might be due to invalid IL or missing references)
		//IL_0187: Unknown result type (might be due to invalid IL or missing references)
		//IL_0189: Unknown result type (might be due to invalid IL or missing references)
		//IL_018a: Unknown result type (might be due to invalid IL or missing references)
		//IL_016b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0194: Unknown result type (might be due to invalid IL or missing references)
		//IL_0125: Unknown result type (might be due to invalid IL or missing references)
		//IL_012f: Expected O, but got Unknown
		PinData pin = view.Pin;
		bool flag = view.Displayed == null || view.Displayed.Name != record.Name || view.Displayed.Icon != record.Icon || view.Displayed.Color != record.Color || view.Displayed.Percent != record.Percent || view.Displayed.Checked != record.Checked;
		if (view.Displayed != null && view.Displayed.Kind == PinKind.Manual)
		{
			ForgetManualStyle(view.Displayed);
		}
		pin.m_pos = SeneaLBindings.Position(record);
		pin.m_checked = record.Checked;
		string text = Label(record);
		if (pin.m_name != text)
		{
			pin.m_name = text;
			if (text.Length == 0 && pin.m_NamePinData != null)
			{
				if (Object.op_Implicit((Object)(object)pin.m_NamePinData.PinNameGameObject))
				{
					Object.Destroy((Object)(object)pin.m_NamePinData.PinNameGameObject);
				}
				pin.m_NamePinData = null;
			}
			else if (text.Length > 0)
			{
				if (pin.m_NamePinData == null)
				{
					pin.m_NamePinData = new PinNameData(pin);
				}
				if (Object.op_Implicit((Object)(object)pin.m_NamePinData.PinNameText))
				{
					pin.m_NamePinData.PinNameText.text = Localization.instance.Localize(text);
				}
			}
		}
		if (record.Kind == PinKind.Manual)
		{
			manualStyle[SeneaLBindings.PositionKey(pin.m_pos)] = record;
		}
		PinType type = (pin.m_type = ui.TypeFor(record));
		if (flag)
		{
			Sprite sprite = GetSprite(record, type);
			if (Object.op_Implicit((Object)(object)sprite))
			{
				pin.m_icon = sprite;
			}
		}
		view.Displayed = record;
		ui.Refresh(map, pin, flag);
	}

	private Sprite GetSprite(PinRecord record, PinType type)
	{
		//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
		switch (record.Kind)
		{
		case PinKind.Ore:
			return ui.Sprite("ore");
		case PinKind.Dungeon:
			return ui.Sprite("dungeon");
		case PinKind.Ship:
			return ui.Sprite("ship");
		case PinKind.Cart:
			return ui.Sprite("cart");
		case PinKind.Portal:
			return ui.Sprite("portal");
		case PinKind.ArtisanNavigation:
		case PinKind.ArtisanWreck:
		case PinKind.ArtisanCrate:
			return ArtisanSprite(record.Kind);
		default:
		{
			string key = ((record.Icon == "chest") ? "bag" : ((record.Icon == "danger") ? "alert" : record.Icon));
			if (Array.IndexOf(SeneaLBindings.CustomIcons, record.Icon) > 0)
			{
				return ui.Sprite(key);
			}
			return ui.VanillaSprite(Minimap.instance, type);
		}
		}
	}

	private static string Label(PinRecord pin)
	{
		if (pin.Kind != PinKind.Ore || pin.Percent < 0)
		{
			return pin.Name;
		}
		return pin.Name + " " + pin.Percent + "%";
	}

	private void ForgetManualStyle(PinRecord record)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		long key = SeneaLBindings.PositionKey(SeneaLBindings.Position(record));
		if (manualStyle.TryGetValue(key, out var value) && value.Id == record.Id)
		{
			manualStyle.Remove(key);
		}
	}

	private void RemoveView(string id)
	{
		if (views.TryGetValue(id, out var value))
		{
			views.Remove(id);
			PinRecord displayed = value.Displayed;
			if (displayed != null && displayed.Kind == PinKind.Manual)
			{
				ForgetManualStyle(value.Displayed);
			}
			if (value.Pin != null)
			{
				ownedPins.Remove(value.Pin);
				DropPin(clientMap, value.Pin);
			}
		}
	}

	private void DropPin(Minimap map, PinData pin)
	{
		if (!Object.op_Implicit((Object)(object)map) || pin == null)
		{
			return;
		}
		internalMapChange++;
		try
		{
			pin.m_save = false;
			map.RemovePin(pin);
		}
		finally
		{
			internalMapChange--;
		}
	}

	private void ClearViews()
	{
		string[] array = views.Keys.ToArray();
		foreach (string id in array)
		{
			RemoveView(id);
		}
		views.Clear();
		ownedPins.Clear();
		manualStyle.Clear();
		newManual.Clear();
	}

	private void ResetClient()
	{
		clientReady = (snapshotStarted = (serverHidesDepletedOre = false));
		serverRpc = null;
		if (ui != null)
		{
			ClearViews();
		}
		clientRecords.Clear();
		observed.Clear();
		absentNativeOre.Clear();
		sharedOreByZone.Clear();
		oreIndexDirty = false;
		nativeLive.Clear();
		hiddenPins.Clear();
		ResetArtisanClient();
		clientMap = null;
	}

	private void InstallHooks()
	{
		//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f8: Unknown result type (might be due to invalid IL or missing references)
		//IL_0200: Expected O, but got Unknown
		Hook(typeof(Minimap), "AddPin", null, "PinAdded");
		Hook(typeof(Minimap), "SetMapData", "BeginMapLoad", null, "EndMapLoad");
		Hook(typeof(Minimap), "GetMapData", "BeginMapExport", null, "EndMapExport");
		Hook(typeof(Minimap), "GetSharedMapData", "BeginMapExport", null, "EndMapExport");
		Hook(typeof(Minimap), "RemovePin", "BeforeRemovePin", null, null, new Type[1] { typeof(PinData) });
		Hook(typeof(ZNet), "Shutdown", "BeforeShutdown");
		Hook(ui.PinDialog, "Save", "BeginDialog", null, "EndDialog");
		Hook(ui.Pins, "CustomOf", null, "CustomStyle");
		Hook(ui.Pins, "ColorOf", null, "ColorStyle");
		Hook(ui.Pins, "IsMarked", null, "MarkedStyle");
		Hook(ui.Pins, "IsHidden", null, "HiddenStyle");
		Hook(ui.Pins, "SetCustom", "BeforeSetStyle");
		Hook(ui.Pins, "SetColor", "BeforeSetStyle");
		Hook(ui.Pins, "SetOnCompass", "BeforeSetStyle");
		Hook(ui.Pins, "RemoveCustom", "BeforeRemoveStyle");
		HarmonyMethod val = new HarmonyMethod(SeneaLBindings.Method(typeof(SharedPinsPlugin), "FillUnwrittenRockHealth"))
		{
			priority = 0
		};
		harmony.Patch((MethodBase)ui.ReadTargetHealth, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		Hook(ui.OreScout, "Refresh", "BeginOreRefresh", null, "EndOreRefresh");
		Hook(ui.OreScout, "Place", "BeforePlaceDiscovery");
		Hook(ui.OreScout, "Forget", "BeforeForgetDiscovery");
		Hook(ui.OreScout, "DungeonName", null, "NameDungeon");
		Hook(ui.OreScout, "NearKnown", "BeforeNearKnown");
		Hook(ui.LiveMarks, "Toggle", "BeforeToggleLive");
		InstallServerSideMapHooks();
		BindArtisan();
	}

	private static void FillUnwrittenRockHealth(Component __0, ref int __1, ref int __2, ref bool __result)
	{
		SharedPinsPlugin instance = Instance;
		if ((Object)(object)instance == (Object)null)
		{
			return;
		}
		MineRock5 val = (MineRock5)(object)((__0 is MineRock5) ? __0 : null);
		if (val != null && Object.op_Implicit((Object)(object)val) && (!__result || (__1 == 1 && __2 == 1)))
		{
			ZNetView component = ((Component)val).GetComponent<ZNetView>();
			if (Object.op_Implicit((Object)(object)component) && component.IsValid() && string.IsNullOrEmpty(component.GetZDO().GetString(ZDOVars.s_health, "")) && instance.ui.TryReadInitializedRock(val, out var health, out var maximum))
			{
				__1 = health;
				__2 = maximum;
				__result = true;
			}
		}
	}

	private void InstallServerSideMapHooks()
	{
		Type type = SeneaLBindings.RequireType("ServerSideMap.Store");
		Hook(type, "IsSharingPin", "DisableServerSideMapPinSharing");
		Type type2 = SeneaLBindings.RequireType("ServerSideMap.InitialPinSync");
		Hook(type2, "OnReceiveInitialDataPin", "SkipServerSideMapPinTransfer");
		Hook(type2, "SendPinsToServer", "SkipServerSideMapPinTransfer");
		Type type3 = SeneaLBindings.RequireType("ServerSideMap.UtilityPin");
		Hook(type3, "UploadAllPins", "SkipServerSideMapPinTransfer");
		Hook(type3, "DownloadPins", "SkipServerSideMapPinTransfer");
	}

	private static bool DisableServerSideMapPinSharing(ref bool __result)
	{
		__result = false;
		return false;
	}

	private static bool SkipServerSideMapPinTransfer()
	{
		return false;
	}

	private void Hook(Type type, string method, string prefix = null, string postfix = null, string finalizer = null, Type[] parameters = null)
	{
		harmony.Patch((MethodBase)SeneaLBindings.Method(type, method, parameters), Patch(prefix), Patch(postfix), (HarmonyMethod)null, Patch(finalizer), (HarmonyMethod)null);
		static HarmonyMethod Patch(string name)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			if (name != null)
			{
				return new HarmonyMethod(SeneaLBindings.Method(typeof(SharedPinsPlugin), name));
			}
			return null;
		}
	}

	private static void PinAdded(PinData __result)
	{
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		//IL_006e: Expected I4, but got Unknown
		SharedPinsPlugin instance = Instance;
		if (!((Object)(object)instance == (Object)null) && instance.ClientActive && __result != null && instance.loadingMap == 0 && instance.internalMapChange == 0 && __result.m_save && __result.m_ownerID == 0L)
		{
			PinType type = __result.m_type;
			switch (type - 5)
			{
			case 0:
			case 2:
			case 5:
			case 6:
			case 7:
			case 8:
				return;
			}
			DialogState dialogState = instance.activeDialog;
			instance.newManual[__result] = ((dialogState == null) ? new PinRecord
			{
				Compass = true
			} : new PinRecord
			{
				Icon = dialogState.Icon,
				Color = dialogState.Tint,
				Compass = dialogState.Compass
			});
		}
	}

	private static void BeginMapLoad()
	{
		if ((Object)(object)Instance != (Object)null)
		{
			Instance.loadingMap++;
		}
	}

	private static Exception EndMapLoad(Exception __exception)
	{
		SharedPinsPlugin instance = Instance;
		if ((Object)(object)instance != (Object)null && --instance.loadingMap == 0 && instance.ClientActive)
		{
			instance.ClearViews();
		}
		return __exception;
	}

	private static void BeginMapExport(out PinData[] __state)
	{
		SharedPinsPlugin instance = Instance;
		__state = (((Object)(object)instance == (Object)null) ? Array.Empty<PinData>() : instance.ownedPins.Keys.Where((PinData pin) => pin.m_save).ToArray());
		PinData[] array = __state;
		for (int num = 0; num < array.Length; num++)
		{
			array[num].m_save = false;
		}
	}

	private static Exception EndMapExport(PinData[] __state, Exception __exception)
	{
		if (__state != null)
		{
			for (int i = 0; i < __state.Length; i++)
			{
				__state[i].m_save = true;
			}
		}
		return __exception;
	}

	private static bool BeforeRemovePin(PinData __0)
	{
		SharedPinsPlugin instance = Instance;
		if ((Object)(object)instance == (Object)null || __0 == null || !instance.ClientActive || instance.internalMapChange != 0 || instance.loadingMap != 0 || !instance.ownedPins.TryGetValue(__0, out var value))
		{
			return true;
		}
		if (instance.oreRefresh != 0)
		{
			return false;
		}
		if (instance.clientRecords.TryGetValue(value, out var value2) && value2.Kind != PinKind.Manual)
		{
			if (value2.IsLive && (int)value2.Kind < 6)
			{
				instance.ui.ToggleLiveHidden(value2, hideOnly: true);
			}
			else
			{
				instance.hiddenPins.Add(value);
			}
		}
		return true;
	}

	private static void BeforeShutdown()
	{
		Instance?.SaveWorld();
	}

	private static void BeginDialog(object __instance, out DialogState __state)
	{
		__state = null;
		SharedPinsPlugin instance = Instance;
		if (!((Object)(object)instance == (Object)null) && instance.ClientActive && instance.ui.DialogOpen(__instance))
		{
			PinData val = instance.ui.Editing(__instance);
			if (val == null || instance.ownedPins.ContainsKey(val))
			{
				instance.ui.ReadDialog(__instance, out var icon, out var tint, out var onCompass);
				__state = new DialogState
				{
					Pin = val,
					Icon = icon,
					Tint = tint,
					Compass = onCompass
				};
				instance.activeDialog = __state;
			}
		}
	}

	private static Exception EndDialog(DialogState __state, Exception __exception)
	{
		SharedPinsPlugin instance = Instance;
		if ((Object)(object)instance == (Object)null || __state == null)
		{
			return __exception;
		}
		instance.activeDialog = null;
		if (__exception != null || __state.Pin == null || !instance.ownedPins.TryGetValue(__state.Pin, out var value) || !instance.views.TryGetValue(value, out var value2))
		{
			return __exception;
		}
		PinRecord pinRecord = (value2.Desired ?? value2.Displayed).Copy();
		pinRecord.Icon = __state.Icon;
		pinRecord.Color = __state.Tint;
		pinRecord.Compass = __state.Compass;
		ReadManual(__state.Pin, pinRecord);
		value2.Desired = pinRecord;
		return __exception;
	}

	private static bool BeforeSetStyle()
	{
		return Instance?.activeDialog == null;
	}

	private static bool BeforeRemoveStyle(Vector3 __1)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		PinRecord record;
		return !TryStyle(__1, out record);
	}

	private static bool TryStyle(Vector3 position, out PinRecord record)
	{
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		SharedPinsPlugin instance = Instance;
		record = null;
		if ((Object)(object)instance != (Object)null)
		{
			return instance.manualStyle.TryGetValue(SeneaLBindings.PositionKey(position), out record);
		}
		return false;
	}

	private static void CustomStyle(Vector3 __0, ref int __result)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		if (TryStyle(__0, out var record))
		{
			__result = Math.Max(0, Array.IndexOf(SeneaLBindings.CustomIcons, record.Icon));
		}
	}

	private static void ColorStyle(Vector3 __0, ref int __result)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		if (TryStyle(__0, out var record))
		{
			__result = record.Color;
		}
	}

	private static void MarkedStyle(Vector3 __0, ref bool __result)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		if (TryStyle(__0, out var record))
		{
			__result = record.Compass;
		}
	}

	private static void HiddenStyle(Vector3 __0, ref bool __result)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		if (TryStyle(__0, out var record))
		{
			__result = !record.Compass;
		}
	}

	private static void BeginOreRefresh()
	{
		if ((Object)(object)Instance != (Object)null)
		{
			Instance.oreRefresh++;
		}
	}

	private static Exception EndOreRefresh(Exception __exception)
	{
		if ((Object)(object)Instance != (Object)null)
		{
			Instance.oreRefresh--;
		}
		return __exception;
	}

	private static bool BeforePlaceDiscovery(object __1)
	{
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0033: 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_0040: Unknown result type (might be due to invalid IL or missing references)
		SharedPinsPlugin instance = Instance;
		if ((Object)(object)instance == (Object)null || !instance.ClientActive)
		{
			return true;
		}
		if (instance.ui.EntryDungeon(__1))
		{
			Vector3 val = instance.ui.EntryPos(__1);
			return !instance.clientRecords.ContainsKey(PinRecord.DungeonKey(val.x, val.z));
		}
		return !instance.HasSharedOreMarker(__1);
	}

	private static bool BeforeForgetDiscovery(PinData __0)
	{
		SharedPinsPlugin instance = Instance;
		if ((Object)(object)instance == (Object)null || __0 == null || !instance.ClientActive || !instance.ownedPins.TryGetValue(__0, out var value) || !instance.clientRecords.TryGetValue(value, out var value2) || !value2.IsDiscovery)
		{
			return true;
		}
		instance.hiddenPins.Add(value);
		instance.RemoveView(value);
		return false;
	}

	private static void NameDungeon(Location __0, ref string __result)
	{
		SharedPinsPlugin instance = Instance;
		if (instance != null && instance.ClientActive && TryDungeonLabel(__0, out var label, out var _))
		{
			__result = label;
		}
	}

	private static bool BeforeNearKnown(ref bool __result)
	{
		SharedPinsPlugin instance = Instance;
		if (instance == null || !instance.ClientActive)
		{
			return true;
		}
		__result = false;
		return false;
	}

	private static bool BeforeToggleLive(PinData __1)
	{
		SharedPinsPlugin instance = Instance;
		if ((Object)(object)instance == (Object)null || !instance.ClientActive || __1 == null || !instance.ownedPins.TryGetValue(__1, out var value) || !instance.clientRecords.TryGetValue(value, out var value2) || !value2.IsLive)
		{
			return true;
		}
		if ((int)value2.Kind < 6)
		{
			instance.ui.ToggleLiveHidden(value2, hideOnly: false);
		}
		else
		{
			instance.hiddenPins.Add(value);
		}
		return false;
	}

	private void AcceptOre(ZNetPeer peer, PinRecord pin)
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		if (pin.OrePrefab == 0 || DistanceXZ(pin, peer.m_refPos) > 150f)
		{
			return;
		}
		ZDO zdo = FindObject(pin);
		if (!IsSameOre(pin, zdo))
		{
			return;
		}
		GameObject val = (Object.op_Implicit((Object)(object)ZNetScene.instance) ? ZNetScene.instance.GetPrefab(pin.OrePrefab) : null);
		if (Object.op_Implicit((Object)(object)OreComponent(val)))
		{
			int percent;
			bool flag = TryReadOrePercent(zdo, val, out percent);
			if (flag)
			{
				pin.Percent = percent;
			}
			else if (pin.Percent == 0 || Object.op_Implicit((Object)(object)val.GetComponent<MineRock5>()))
			{
				pin.Percent = -1;
			}
			pin.Name = CleanOreName(pin.Name);
			pin.Icon = "";
			PublishOre(pin, flag);
		}
	}

	private void CheckLegacyOre(ZNetPeer peer, PinRecord report)
	{
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		ZDOMan instance = ZDOMan.instance;
		if (report.Kind != PinKind.Ore || report.OrePrefab != 0 || instance == null || DistanceXZ(report, peer.m_refPos) > 55f || !TryFindOreAt(report, ui.OreSectors(instance), out var match))
		{
			return;
		}
		if (match == null)
		{
			Send(peer.m_rpc, Message.OreAbsent, delegate(BinaryWriter writer)
			{
				writer.Write(report.LegacyOreQueryKey);
			});
		}
		else
		{
			PublishOre(ReadWorldOre(report, match));
		}
	}

	private void ReconcileSharedOre(PinRecord pin, List<ZDO>[] sectors)
	{
		if (pin.OrePrefab != 0 && pin.IsMissingOre)
		{
			return;
		}
		ZDO match = ((pin.OrePrefab == 0) ? null : FindObject(pin));
		if (!IsSameOre(pin, match) && !TryFindOreAt(pin, sectors, out match))
		{
			return;
		}
		if (pin.OrePrefab == 0)
		{
			PinRecord replacement = ((match == null) ? null : ReadWorldOre(pin, match));
			if (store.ReconcileLegacyOre(pin.Id, replacement, SharedOreThreshold, out var updated))
			{
				RecordChange(pin.Id, null);
				if (updated != null)
				{
					RecordChange(updated.Id, updated);
				}
			}
		}
		else if (match != null)
		{
			PublishOre(ReadWorldOre(pin, match));
		}
		else
		{
			PinRecord pinRecord = pin.Copy();
			pinRecord.Icon = "ore:missing";
			pinRecord.Percent = 0;
			pinRecord.ObjectUser = 0L;
			pinRecord.ObjectId = 0u;
			PublishOre(pinRecord);
		}
	}

	private PinRecord ReadWorldOre(PinRecord source, ZDO zdo)
	{
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		PinRecord pinRecord = source.Copy();
		pinRecord.OrePrefab = zdo.GetPrefab();
		SetPosition(pinRecord, zdo.GetPosition());
		SetObject(pinRecord, zdo.m_uid);
		pinRecord.Icon = "";
		GameObject prefab = ZNetScene.instance.GetPrefab(pinRecord.OrePrefab);
		if (!TryReadOrePercent(zdo, prefab, out pinRecord.Percent))
		{
			pinRecord.Percent = -1;
		}
		pinRecord.Name = CleanOreName(pinRecord.Name);
		return pinRecord;
	}

	private void PublishOre(PinRecord ore, bool authoritativePercent = true)
	{
		if (store.Upsert(ore, SharedOreThreshold, out var current, authoritativePercent, verifiedOreObject: true))
		{
			RecordChange(ore.Id, current);
		}
	}

	private static string CleanOreName(string name)
	{
		return name.Replace("<", " ").Replace(">", " ");
	}

	private static Component OreComponent(GameObject prefab)
	{
		if (!Object.op_Implicit((Object)(object)prefab))
		{
			return null;
		}
		Component component = (Component)(object)prefab.GetComponent<MineRock5>();
		if (!Object.op_Implicit((Object)(object)component))
		{
			component = (Component)(object)prefab.GetComponent<MineRock>();
		}
		if (!Object.op_Implicit((Object)(object)component))
		{
			component = (Component)(object)prefab.GetComponent<Destructible>();
		}
		return component;
	}

	private static bool IsSameOre(PinRecord pin, ZDO zdo)
	{
		//IL_000e: 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)
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		if (zdo == null || !zdo.IsValid())
		{
			return false;
		}
		Vector3 position = zdo.GetPosition();
		return pin.MatchesOreObject(zdo.GetPrefab(), position.x, position.y, position.z);
	}

	private bool TryFindOreAt(PinRecord pin, List<ZDO>[] sectors, out ZDO match)
	{
		//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_0051: Unknown result type (might be due to invalid IL or missing references)
		//IL_0052: Unknown result type (might be due to invalid IL or missing references)
		//IL_0053: Unknown result type (might be due to invalid IL or missing references)
		//IL_0058: Unknown result type (might be due to invalid IL or missing references)
		//IL_005d: Unknown result type (might be due to invalid IL or missing references)
		//IL_005e: 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_0060: Unknown result type (might be due to invalid IL or missing references)
		//IL_0065: Unknown result type (might be due to invalid IL or missing references)
		//IL_006a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0076: Unknown result type (might be due to invalid IL or missing references)
		//IL_0089: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f4: Unknown result type (might be due to invalid IL or missing references)
		//IL_0096: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e1: 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_011c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0121: Unknown result type (might be due to invalid IL or missing references)
		//IL_0124: Unknown result type (might be due to invalid IL or missing references)
		//IL_012b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0132: Unknown result type (might be due to invalid IL or missing references)
		//IL_0191: Unknown result type (might be due to invalid IL or missing references)
		//IL_0198: Unknown result type (might be due to invalid IL or missing references)
		//IL_019f: Unknown result type (might be due to invalid IL or missing references)
		match = null;
		if (sectors == null || !Object.op_Implicit((Object)(object)currentNet) || ZNet.m_loadError || !Object.op_Implicit((Object)(object)ZoneSystem.instance) || !Object.op_Implicit((Object)(object)ZNetScene.instance))
		{
			return false;
		}
		Vector3 val = SeneaLBindings.Position(pin);
		Vector3 val2 = default(Vector3);
		((Vector3)(ref val2))..ctor(2f, 0f, 2f);
		Vector2s zone = ZoneSystem.GetZone(val - val2);
		Vector2s zone2 = ZoneSystem.GetZone(val + val2);
		if (!ui.IsZoneGenerated(ZoneSystem.instance, val))
		{
			return false;
		}
		int bestRank = 0;
		bool ambiguous = false;
		bool flag = false;
		for (int i = zone.x; i <= zone2.x; i++)
		{
			for (int j = zone.y; j <= zone2.y; j++)
			{
				uint sector = ZoneSystem.SectorToIndex(i, j).Sector;
				if (sector == 0 || sector >= sectors.Length)
				{
					return false;
				}
				List<ZDO> list = sectors[sector];
				if (list == null)
				{
					continue;
				}
				foreach (ZDO item in list)
				{
					if (item == null || !item.IsValid())
					{
						continue;
					}
					if (pin.OrePrefab != 0)
					{
						if (IsSameOre(pin, item))
						{
							match = item;
							return true;
						}
						continue;
					}
					Vector3 position = item.GetPosition();
					if (pin.DistanceSquared(position.x, position.y, position.z) > 4f)
					{
						continue;
					}
					GameObject prefab = ZNetScene.instance.GetPrefab(item.GetPrefab());
					if (!Object.op_Implicit((Object)(object)prefab))
					{
						flag = true;
						continue;
					}
					Component val3 = OreComponent(prefab);
					if (Object.op_Implicit((Object)(object)val3))
					{
						string text = ui.OreName(val3);
						if (!string.IsNullOrEmpty(text))
						{
							int rank = pin.LegacyOreMatchRank(position.x, position.y, position.z, text);
							PinRecord.ConsiderOreMatch(item, rank, ref match, ref bestRank, ref ambiguous);
						}
					}
				}
			}
		}
		if (!ambiguous)
		{
			if (flag)
			{
				return bestRank >= 2;
			}
			return true;
		}
		return false;
	}

	private void Awake()
	{
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Expected O, but got Unknown
		//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ac: Expected O, but got Unknown
		Instance = this;
		oreThreshold = ((BaseUnityPlugin)this).Config.Bind<int>("Ore", "StrikeThroughThreshold", -1, new ConfigDescription("Server-wide percentage at or below which an ore pin is struck through. -1 uses SeneaL UI's OreThreshold on the server.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(-1, 50), Array.Empty<object>()));
		removeDepletedOre = ((BaseUnityPlugin)this).Config.Bind<bool>("Ore", "RemoveDepletedOrePins", true, "Hide automatic shared ore pins for every player once they reach the strike-through threshold.");
		liveMarkers = ((BaseUnityPlugin)this).Config.Bind<bool>("Live markers", "ShareShipsCartsAndPortals", true, "Share SeneaL live tracks discovered by connected players.");
		artisanMarkers = ((BaseUnityPlugin)this).Config.Bind<bool>("Live markers", "ShareArtisanMarkers", true, "Share supported Artisan Mastery navigation, wreck and crate markers.");
		harmony = new Harmony("bananas4dragons.valheim.senealsharedpins");
		try
		{
			ui = new SeneaLBindings();
			InstallHooks();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Shared pin hooks resolved. Protocol 2. ServerSideMap marker sharing is overridden to false.");
		}
		catch (Exception ex)
		{
			harmony.UnpatchSelf();
			((BaseUnityPlugin)this).Logger.LogError((object)("Shared pins disabled because a required hook could not be resolved: " + ex));
			((Behaviour)this).enabled = false;
		}
	}

	private void OnDestroy()
	{
		EndSession();
		Harmony obj = harmony;
		if (obj != null)
		{
			obj.UnpatchSelf();
		}
		Instance = null;
	}

	private void Update()
	{
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		if (realtimeSinceStartup < nextTick)
		{
			return;
		}
		nextTick = realtimeSinceStartup + 0.25f;
		try
		{
			TickNetwork(realtimeSinceStartup);
			if (ClientActive)
			{
				TickClient(realtimeSinceStartup);
			}
		}
		catch (Exception ex)
		{
			LogFailure("Shared pin update", ex);
		}
	}

	private void TickNetwork(float now)
	{
		ZNet instance = ZNet.instance;
		World val = (Object.op_Implicit((Object)(object)instance) ? instance.GetWorld() : null);
		long num = val?.m_uid ?? 0;
		if ((Object)(object)instance != (Object)(object)currentNet || worldId != num)
		{
			EndSession();
			currentNet = instance;
			worldId = num;
			if (Object.op_Implicit((Object)(object)instance) && num != 0L && instance.IsServer() && instance.IsDedicated())
			{
				LoadWorld(val);
			}
		}
		if (!Object.op_Implicit((Object)(object)instance) || worldId == 0L)
		{
			return;
		}
		List<ZNetPeer> peers = instance.GetPeers();
		foreach (ZNetPeer item in peers)
		{
			if (!registered.ContainsKey(item.m_rpc))
			{
				registered.Add(item.m_rpc, item);
				item.m_rpc.Register<ZPackage>("B4D_SeneaLSharedPins_2", (Action<ZRpc, ZPackage>)Receive);
			}
		}
		KeyValuePair<ZRpc, ZNetPeer>[] array = registered.ToArray();
		for (int i = 0; i < array.Length; i++)
		{
			KeyValuePair<ZRpc, ZNetPeer> keyValuePair = array[i];
			if (!peers.Contains(keyValuePair.Value))
			{
				registered.Remove(keyValuePair.Key);
				subscribers.Remove(keyValuePair.Key);
				if (serverRpc == keyValuePair.Key)
				{
					ResetClient();
				}
			}
		}
		if (serverActive)
		{
			ScanGalleonMarks(now);
			if (now >= nextWorldScan)
			{
				nextWorldScan = now + 1.5f;
				RefreshWorldObjects();
			}
			FlushChanges();
			if (saveDirty && now >= nextSave)
			{
				nextSave = now + 3f;
				SaveWorld();
			}
		}
		else if (!instance.IsServer() && Object.op_Implicit((Object)(object)Minimap.instance) && Object.op_Implicit((Object)(object)Player.m_localPlayer) && now >= nextHello)
		{
			ZNetPeer val2 = ((IEnumerable<ZNetPeer>)peers).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer peer) => peer.m_server && peer.IsReady()));
			if (val2 != null && !clientReady)
			{
				serverRpc = val2.m_rpc;
				snapshotStarted = false;
				nextHello = now + 6f;
				Send(serverRpc, Message.Hello);
			}
		}
	}

	private void Receive(ZRpc rpc, ZPackage package)
	{
		if (!registered.TryGetValue(rpc, out var value) || !value.IsReady() || (!serverActive && rpc != serverRpc))
		{
			return;
		}
		try
		{
			byte[] array = package.GetArray();
			if (array.Length == 0 || array.Length > 65536)
			{
				return;
			}
			using BinaryReader binaryReader = new BinaryReader(new MemoryStream(array, writable: false));
			Message message = (Message)binaryReader.ReadByte();
			if (serverActive)
			{
				ReceiveServer(value, message, binaryReader);
			}
			else
			{
				ReceiveClient(message, binaryReader);
			}
		}
		catch (Exception ex)
		{
			LogFailure("Shared pin packet rejected", ex);
		}
	}

	private void ReceiveServer(ZNetPeer peer, Message message, BinaryReader reader)
	{
		ZRpc rpc = peer.m_rpc;
		if (message == Message.Hello)
		{
			if (reader.BaseStream.Position == reader.BaseStream.Length)
			{
				subscribers.Add(rpc);
				RefreshWorldObjects();
				FlushChanges();
				Send(rpc, Message.SnapshotStart);
				(string, PinRecord)[] changes = store.Pins.Values.Select((PinRecord pinRecord) => (Id: pinRecord.Id, pin: pinRecord)).ToArray();
				SendChanges(rpc, changes);
				Send(rpc, Message.SnapshotEnd, delegate(BinaryWriter writer)
				{
					writer.Write(removeDepletedOre.Value);
				});
			}
		}
		else
		{
			if (!subscribers.Contains(rpc))
			{
				return;
			}
			switch (message)
			{
			case Message.Upsert:
			{
				PinRecord pin = PinRecord.Read(reader);
				if (reader.BaseStream.Position == reader.BaseStream.Length)
				{
					AcceptPin(peer, pin);
				}
				break;
			}
			case Message.CheckOre:
			{
				PinRecord report = PinRecord.Read(reader);
				if (reader.BaseStream.Position == reader.BaseStream.Length)
				{
					CheckLegacyOre(peer, report);
				}
				break;
			}
			case Message.Delete:
			{
				string text = reader.ReadString();
				int revision = reader.ReadInt32();
				if (reader.BaseStream.Position == reader.BaseStream.Length && Guid.TryParseExact(text, "N", out var _))
				{
					if (store.DeleteManual(text, revision, out var current))
					{
						RecordChange(text, null);
					}
					else
					{
						Reject(rpc, text, current);
					}
				}
				break;
			}
			}
		}
	}

	private void AcceptPin(ZNetPeer peer, PinRecord pin)
	{
		//IL_0088: Unknown result type (might be due to invalid IL or missing references)
		if (pin.Kind == PinKind.Manual)
		{
			pin.Name = pin.Name.Replace("<", " ").Replace(">", " ");
			if (store.Upsert(pin, SharedOreThreshold, out var current))
			{
				RecordChange(pin.Id, current);
			}
			else
			{
				Reject(peer.m_rpc, pin.Id, current);
			}
			return;
		}
		if (pin.Kind == PinKind.Ore)
		{
			AcceptOre(peer, pin);
			return;
		}
		if (pin.Kind == PinKind.Dungeon)
		{
			if (DistanceXZ(pin, peer.m_refPos) > 150f)
			{
				return;
			}
		}
		else
		{
			ZDO val = FindObject(pin);
			if (val == null)
			{
				return;
			}
			GameObject prefab = (Object.op_Implicit((Object)(object)ZNetScene.instance) ? ZNetScene.instance.GetPrefab(val.GetPrefab()) : null);
			if (!PrepareLive(pin, val, prefab))
			{
				return;
			}
		}
		pin.Name = pin.Name.Replace("<", " ").Replace(">", " ");
		if (store.Upsert(pin, SharedOreThreshold, out var current2))
		{
			RecordChange(pin.Id, current2);
		}
	}

	private void RefreshWorldObjects()
	{
		ZDOMan instance = ZDOMan.instance;
		if (instance == null || !Object.op_Implicit((Object)(object)ZNetScene.instance))
		{
			return;
		}
		List<ZDO>[] sectors = ui.OreSectors(instance);
		PinRecord[] array = store.Pins.Values.ToArray();
		foreach (PinRecord pinRecord in array)
		{
			if (!pinRecord.IsLive && pinRecord.Kind != PinKind.Ore)
			{
				continue;
			}
			if (pinRecord.Kind == PinKind.Ore)
			{
				ReconcileSharedOre(pinRecord, sectors);
				continue;
			}
			ZDO val = FindObject(pinRecord);
			if (val != null || ObjectMissing(pinRecord.Id))
			{
				if (val != null)
				{
					missingObjects.Remove(pinRecord.Id);
				}
				PinRecord pinRecord2 = pinRecord.Copy();
				GameObject prefab = ((val == null) ? null : ZNetScene.instance.GetPrefab(val.GetPrefab()));
				PinRecord current;
				if (val == null || !PrepareLive(pinRecord2, val, prefab))
				{
					store.Pins.Remove(pinRecord.Id);
					missingObjects.Remove(pinRecord.Id);
					RecordChange(pinRecord.Id, null);
				}
				else if (store.Upsert(pinRecord2, SharedOreThreshold, out current))
				{
					RecordChange(pinRecord.Id, current);
				}
			}
		}
	}

	private static bool TryReadOrePercent(ZDO zdo, GameObject prefab, out int percent)
	{
		percent = -1;
		if (!Object.op_Implicit((Object)(object)prefab))
		{
			return false;
		}
		MineRock5 component = prefab.GetComponent<MineRock5>();
		if (Object.op_Implicit((Object)(object)component))
		{
			percent = OreHealth.ReadRockAreas(zdo.GetString(ZDOVars.s_health, ""), ScaledOreHealth(component.m_health));
		}
		else if (!Object.op_Implicit((Object)(object)prefab.GetComponent<MineRock>()))
		{
			Destructible component2 = prefab.GetComponent<Destructible>();
			if (Object.op_Implicit((Object)(object)component2) && component2.m_health < 1000000f)
			{
				float num = ScaledOreHealth(component2.m_health);
				percent = OreHealth.Percent(zdo.GetFloat(ZDOVars.s_health, num), num);
			}
		}
		return percent >= 0;
	}

	internal static float ScaledOreHealth(float baseHealth)
	{
		Game instance = Game.instance;
		if (!Object.op_Implicit((Object)(object)instance))
		{
			return baseHealth;
		}
		return baseHealth * (1f + (float)Game.m_worldLevel * instance.m_worldLevelMineHPMultiplier);
	}

	private bool ObjectMissing(string id)
	{
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		if (missingObjects.TryGetValue(id, out var value))
		{
			return realtimeSinceStartup - value >= 4f;
		}
		missingObjects.Add(id, realtimeSinceStartup);
		return false;
	}

	private bool PrepareLive(PinRecord pin, ZDO zdo, GameObject prefab)
	{
		//IL_0108: Unknown result type (might be due to invalid IL or missing references)
		if (!Object.op_Implicit((Object)(object)prefab))
		{
			return false;
		}
		switch (pin.Kind)
		{
		case PinKind.Ship:
			if (!liveMarkers.Value || !Object.op_Implicit((Object)(object)prefab.GetComponent<Ship>()))
			{
				return false;
			}
			break;
		case PinKind.Cart:
			if (!liveMarkers.Value || !Object.op_Implicit((Object)(object)prefab.GetComponent<Vagon>()))
			{
				return false;
			}
			break;
		case PinKind.Portal:
		{
			if (!liveMarkers.Value || !Object.op_Implicit((Object)(object)prefab.GetComponent<TeleportWorld>()))
			{
				return false;
			}
			string text = zdo.GetString("tag", "");
			pin.Name = ((text.Length == 0) ? "$piece_portal" : text.Substring(0, Math.Min(text.Length, 200)));
			pin.Name = pin.Name.Replace("<", " ").Replace(">", " ");
			break;
		}
		default:
			if (!artisanMarkers.Value || !ValidateArtisan(pin, zdo, prefab))
			{
				return false;
			}
			break;
		}
		SetPosition(pin, zdo.GetPosition());
		pin.Checked = false;
		return true;
	}

	private ZDO FindObject(PinRecord pin)
	{
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		if (!ui.KnowsObjectUser(pin.ObjectUser))
		{
			return null;
		}
		ZDOMan instance = ZDOMan.instance;
		if (instance == null)
		{
			return null;
		}
		return instance.GetZDO(new ZDOID(pin.ObjectUser, pin.ObjectId));
	}

	private static void SetPosition(PinRecord pin, Vector3 position)
	{
		//IL_0001: 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_0019: Unknown result type (might be due to invalid IL or missing references)
		pin.X = position.x;
		pin.Y = position.y;
		pin.Z = position.z;
	}

	private static float DistanceXZ(PinRecord pin, Vector3 position)
	{
		//IL_0006: 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 = pin.X - position.x;
		float num2 = pin.Z - position.z;
		return Mathf.Sqrt(num * num + num2 * num2);
	}

	private static void Send(ZRpc rpc, Message message, Action<BinaryWriter> body = null)
	{
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_003e: Expected O, but got Unknown
		using MemoryStream memoryStream = new MemoryStream();
		using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
		binaryWriter.Write((byte)message);
		body?.Invoke(binaryWriter);
		binaryWriter.Flush();
		rpc.Invoke("B4D_SeneaLSharedPins_2", new object[1] { (object)new ZPackage(memoryStream.ToArray()) });
	}

	private static void SendChanges(ZRpc rpc, (string Id, PinRecord Pin)[] changes)
	{
		for (int start = 0; start < changes.Length; start += 48)
		{
			int count = Math.Min(48, changes.Length - start);
			Send(rpc, Message.Changes, delegate(BinaryWriter writer)
			{
				writer.Write(count);
				for (int i = start; i < start + count; i++)
				{
					WriteChange(writer, changes[i].Id, changes[i].Pin);
				}
			});
		}
	}

	private static void WriteChange(BinaryWriter writer, string id, PinRecord pin)
	{
		writer.Write(pin != null);
		if (pin != null)
		{
			pin.Write(writer);
		}
		else
		{
			writer.Write(id);
		}
	}

	private static void Reject(ZRpc rpc, string id, PinRecord current)
	{
		Send(rpc, Message.Reject, delegate(BinaryWriter writer)
		{
			WriteChange(writer, id, current);
		});
	}

	private void RecordChange(string id, PinRecord pin)
	{
		serverChanges[id] = pin;
		saveDirty = true;
	}

	private void FlushChanges()
	{
		if (serverChanges.Count == 0)
		{
			return;
		}
		(string, PinRecord)[] changes = serverChanges.Select((KeyValuePair<string, PinRecord> item) => (Key: item.Key, Value: item.Value)).ToArray();
		serverChanges.Clear();
		foreach (ZRpc subscriber in subscribers)
		{
			SendChanges(subscriber, changes);
		}
	}

	private void LoadWorld(World world)
	{
		savePath = Path.ChangeExtension(world.GetDBPath(), ".seneal-shared-pins");
		try
		{
			if (File.Exists(savePath))
			{
				using BinaryReader binaryReader = new BinaryReader(File.OpenRead(savePath));
				int num = binaryReader.ReadInt32();
				if (binaryReader.BaseStream.Length > 16777216 || (num != 1 && num != 2) || binaryReader.ReadInt64() != worldId)
				{
					throw new InvalidDataException("Shared pin file has an invalid header.");
				}
				int num2 = binaryReader.ReadInt32();
				if (num2 < 0 || num2 > 10000)
				{
					throw new InvalidDataException("Shared pin file has an invalid record count.");
				}
				for (int i = 0; i < num2; i++)
				{
					PinRecord pinRecord = PinRecord.Read(binaryReader, num >= 2);
					store.Pins.Add(pinRecord.Id, pinRecord);
				}
				if (binaryReader.BaseStream.Position != binaryReader.BaseStream.Length)
				{
					throw new InvalidDataException("Shared pin file has unexpected trailing data.");
				}
				backupLegacySave = num == 1;
			}
			serverActive = true;
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Shared pin server ready. Loaded " + store.Pins.Count + " records."));
		}
		catch (Exception ex)
		{
			store.Pins.Clear();
			((BaseUnityPlugin)this).Logger.LogError((object)("Shared pins disabled for this world. Existing pin data was left in place: " + ex));
		}
	}

	private void SaveWorld()
	{
		if (!serverActive || !saveDirty)
		{
			return;
		}
		try
		{
			Directory.CreateDirectory(Path.GetDirectoryName(savePath));
			string text = savePath + ".tmp";
			using (FileStream fileStream = new FileStream(text, FileMode.Create, FileAccess.Write, FileShare.None))
			{
				using BinaryWriter binaryWriter = new BinaryWriter(fileStream);
				binaryWriter.Write(2);
				binaryWriter.Write(worldId);
				binaryWriter.Write(store.Pins.Count);
				foreach (PinRecord value in store.Pins.Values)
				{
					value.Write(binaryWriter);
				}
				binaryWriter.Flush();
				fileStream.Flush(flushToDisk: true);
			}
			string text2 = savePath + ".v1.bak";
			if (File.Exists(savePath))
			{
				File.Replace(text, savePath, (backupLegacySave && !File.Exists(text2)) ? text2 : null);
			}
			else
			{
				File.Move(text, savePath);
			}
			backupLegacySave = false;
			saveDirty = false;
		}
		catch (Exception ex)
		{
			LogFailure("Shared pin save", ex);
		}
	}

	private void EndSession()
	{
		SaveWorld();
		ResetClient();
		registered.Clear();
		subscribers.Clear();
		store.Pins.Clear();
		serverChanges.Clear();
		missingObjects.Clear();
		serverActive = (saveDirty = (backupLegacySave = false));
		savePath = null;
		galleons.Clear();
		galleonSector = 0;
		nextGalleonScan = 0f;
		nextHello = (nextWorldScan = (nextSave = 0f));
	}

	private void LogFailure(string operation, Exception ex)
	{
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		if (!(realtimeSinceStartup < nextFailureLog))
		{
			nextFailureLog = realtimeSinceStartup + 10f;
			((BaseUnityPlugin)this).Logger.LogWarning((object)(operation + ": " + ex));
		}
	}
}
internal static class DungeonNames
{
	internal const string EntranceText = "dungeon:entrance";

	internal const string DiscoveryLabel = "dungeon:discovery";

	internal const int EntrancePriority = 3;

	private static readonly Regex RichText = new Regex("<[^<>]*>");

	private static readonly HashSet<string> Generic = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
	{
		"Dungeon", "Cave", "Crypt", "Mine", "Entrance", "Enter", "Exit", "Leave", "$location_enter", "$location_exit",
		"$location_leave", "$location_dungeon"
	};

	private static readonly Dictionary<string, string> Vanilla = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
	{
		["TrollCave02"] = "Troll Cave",
		["Crypt2"] = "Burial Chambers",
		["Crypt3"] = "Burial Chambers",
		["Crypt4"] = "Burial Chambers",
		["SunkenCrypt1"] = "Sunken Crypt",
		["SunkenCrypt2"] = "Sunken Crypt",
		["SunkenCrypt3"] = "Sunken Crypt",
		["SunkenCrypt4"] = "Sunken Crypt",
		["MountainCave01"] = "Frost Cave",
		["MountainCave02"] = "Frost Cave",
		["Mistlands_DvergrTownEntrance1"] = "Infested Mine",
		["Mistlands_DvergrTownEntrance2"] = "Infested Mine",
		["Mistlands_DvergrBossEntrance1"] = "Infested Citadel",
		["Hildir_crypt"] = "Smouldering Tomb",
		["Hildir_cave"] = "Howling Cavern",
		["Hildir_plainsfortress"] = "Sealed Tower"
	};

	private static readonly Dictionary<string, string> OreMines = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
	{
		["BOM_FlintMine"] = "Flint Mine",
		["BOM_CopperMine"] = "Copper Mine",
		["BOM_TinMine"] = "Tin Mine",
		["BOM_CoalMine"] = "Coal Mine",
		["BOM_IronMine"] = "Iron Mine",
		["BOM_SilverMine"] = "Silver Mine",
		["BOM_BlackMetalMine"] = "Black Metal Mine",
		["BOM_FlametalMine"] = "Flametal Mine"
	};

	internal static string SpecificLabel(string text)
	{
		if (string.IsNullOrWhiteSpace(text) || text.Length > 512)
		{
			return null;
		}
		string text2 = RichText.Replace(text, "").Trim();
		if (text2.Length == 0 || text2.Length > 200 || Generic.Contains(text2) || (text2.StartsWith("[", StringComparison.Ordinal) && text2.EndsWith("]", StringComparison.Ordinal)))
		{
			return null;
		}
		return text2;
	}

	internal static int Priority(string source, string label)
	{
		if (source == "dungeon:entrance" && SpecificLabel(label) != null)
		{
			return 3;
		}
		if (source == "dungeon:discovery" && SpecificLabel(label) != null)
		{
			return 2;
		}
		return (ForPrefab(source) != null) ? 1 : 0;
	}

	internal static string ForPrefab(string prefab)
	{
		if (string.IsNullOrEmpty(prefab))
		{
			return null;
		}
		if (prefab.EndsWith("(Clone)", StringComparison.Ordinal))
		{
			prefab = prefab.Substring(0, prefab.Length - "(Clone)".Length).TrimEnd();
		}
		if (Vanilla.TryGetValue(prefab, out var value))
		{
			return value;
		}
		if (!prefab.EndsWith("01", StringComparison.Ordinal) && !prefab.EndsWith("02", StringComparison.Ordinal))
		{
			return null;
		}
		if (!OreMines.TryGetValue(prefab.Substring(0, prefab.Length - 2), out var value2))
		{
			return null;
		}
		return value2;
	}
}
internal static class OreHealth
{
	internal static bool TryGetDisplayHealth(double health, double maximum, out int displayHealth, out int displayMaximum)
	{
		displayHealth = (displayMaximum = 0);
		if (health < 0.0 || double.IsNaN(health) || health > 2147483647.0 || maximum <= 0.0 || double.IsNaN(maximum) || maximum > 2147483647.0)
		{
			return false;
		}
		displayHealth = (int)Math.Ceiling(health);
		displayMaximum = (int)Math.Ceiling(maximum);
		return true;
	}

	internal static int Percent(double health, double maximum)
	{
		if (double.IsNaN(health) || double.IsInfinity(health) || maximum <= 0.0 || double.IsNaN(maximum) || double.IsInfinity(maximum))
		{
			return -1;
		}
		if (health <= 0.0)
		{
			return 0;
		}
		return Math.Max(1, (int)Math.Round(Math.Min(1.0, health / maximum) * 100.0));
	}

	internal static int ReadRockAreas(string encoded, float areaMaximum)
	{
		if (float.IsNaN(areaMaximum) || float.IsInfinity(areaMaximum) || areaMaximum <= 0f)
		{
			return -1;
		}
		if (string.IsNullOrEmpty(encoded))
		{
			return 100;
		}
		byte[] array;
		try
		{
			array = Convert.FromBase64String(encoded);
		}
		catch (FormatException)
		{
			return -1;
		}
		if (array.Length < 8 || (array.Length - 4) % 4 != 0)
		{
			return -1;
		}
		using BinaryReader binaryReader = new BinaryReader(new MemoryStream(array, writable: false));
		int num = binaryReader.ReadInt32();
		if (num != (array.Length - 4) / 4)
		{
			return -1;
		}
		double num2 = 0.0;
		for (int i = 0; i < num; i++)
		{
			float num3 = binaryReader.ReadSingle();
			if (float.IsNaN(num3) || float.IsInfinity(num3))
			{
				return -1;
			}
			num2 += (double)Math.Max(0f, num3);
		}
		return Percent(num2, (double)areaMaximum * (double)num);
	}
}
internal enum PinKind : byte
{
	Manual,
	Ore,
	Dungeon,
	Ship,
	Cart,
	Portal,
	ArtisanNavigation,
	ArtisanWreck,
	ArtisanCrate
}
internal sealed class PinRecord
{
	internal string Id = "";

	internal string Name = "";

	internal PinKind Kind;

	internal int Revision;

	internal float X;

	internal float Y;

	internal float Z;

	internal string Icon = "";

	internal int Color;

	internal bool Checked;

	internal bool Compass;

	internal int Percent = -1;

	internal long ObjectUser;

	internal uint ObjectId;

	internal int OrePrefab;

	internal const string MissingOreIcon = "ore:missing";

	internal const float LegacyOreRadius = 2f;

	internal bool IsMissingOre
	{
		get
		{
			if (Kind == PinKind.Ore)
			{
				return Icon == "ore:missing";
			}
			return false;
		}
	}

	internal bool IsLive => (int)Kind >= 3;

	internal bool IsDiscovery
	{
		get
		{
			if (Kind != PinKind.Ore)
			{
				return Kind == PinKind.Dungeon;
			}
			return true;
		}
	}

	internal string ObjectKey => ObjectUser.ToString(CultureInfo.InvariantCulture) + ":" + ObjectId.ToString(CultureInfo.InvariantCulture);

	internal string CanonicalId
	{
		get
		{
			if (Kind != PinKind.Dungeon)
			{
				if (OrePrefab == 0)
				{
					byte kind = (byte)Kind;
					return kind.ToString(CultureInfo.InvariantCulture) + ":" + ObjectKey;
				}
				return OreKey(OrePrefab, X, Y, Z);
			}
			return DungeonKey(X, Z);
		}
	}

	internal string LegacyOreQueryKey => Id + ":" + OreKey(0, X, Y, Z);

	internal PinRecord Copy()
	{
		return (PinRecord)MemberwiseClone();
	}

	internal bool SameDisplay(PinRecord other)
	{
		if (Name == other.Name && Kind == other.Kind && X == other.X && Y == other.Y && Z == other.Z && Icon == other.Icon && Color == other.Color && Checked == other.Checked && Compass == other.Compass)
		{
			return Percent == other.Percent;
		}
		return false;
	}

	internal bool IsHiddenByOrePolicy(bool removeDepleted)
	{
		if (Kind == PinKind.Ore)
		{
			if (!IsMissingOre)
			{
				if (removeDepleted)
				{
					return Checked;
				}
				return false;
			}
			return true;
		}
		return false;
	}

	internal float DistanceSquared(float x, float y, float z)
	{
		float num = X - x;
		float num2 = Y - y;
		float num3 = Z - z;
		return num * num + num2 * num2 + num3 * num3;
	}

	internal bool MatchesOreObject(int prefab, float x, float y, float z)
	{
		if (OrePrefab != 0 && OrePrefab == prefab && X == x && Y == y)
		{
			return Z == z;
		}
		return false;
	}

	internal int LegacyOreMatchRank(float x, float y, float z, string name)
	{
		float num = DistanceSquared(x, y, z);
		bool flag = !string.IsNullOrWhiteSpace(Name) && string.Equals(OreNameStem(Name), OreNameStem(name), StringComparison.OrdinalIgnoreCase);
		if (num <= 1f / 64f)
		{
			if (!flag)
			{
				return 2;
			}
			return 3;
		}
		if (!flag || !(num <= 4f))
		{
			return 0;
		}
		return 1;
	}

	internal static void ConsiderOreMatch<T>(T candidate, int rank, ref T match, ref int bestRank, ref bool ambiguous) where T : class
	{
		if (rank != 0 && rank >= bestRank)
		{
			if (rank == bestRank)
			{
				ambiguous = true;
				return;
			}
			match = candidate;
			bestRank = rank;
			ambiguous = false;
		}
	}

	private static string OreNameStem(string name)
	{
		string text = (name ?? "").Trim();
		if (text.EndsWith(" ore deposit", StringComparison.OrdinalIgnoreCase))
		{
			return text.Substring(0, text.Length - 12);
		}
		if (text.EndsWith(" deposit", StringComparison.OrdinalIgnoreCase))
		{
			return text.Substring(0, text.Length - 8);
		}
		if (text.EndsWith(" ore", StringComparison.OrdinalIgnoreCase))
		{
			return text.Substring(0, text.Length - 4);
		}
		return text;
	}

	internal static string OreKey(int prefab, float x, float y, float z)
	{
		return string.Format(CultureInfo.InvariantCulture, "o:{0:x8}:{1:R}:{2:R}:{3:R}", prefab, (x == 0f) ? 0f : x, (y == 0f) ? 0f : y, (z == 0f) ? 0f : z);
	}

	internal static string DungeonKey(float x, float z)
	{
		return "d:" + Math.Round(x).ToString(CultureInfo.InvariantCulture) + ":" + Math.Round(z).ToString(CultureInfo.InvariantCulture);
	}

	internal bool IsValid()
	{
		if (Name.Length > 200 || Id.Length > 90 || Icon.Length > 80 || Math.Abs(X) > 50000f || Math.Abs(Y) > 50000f || Math.Abs(Z) > 50000f || float.IsNaN(X) || float.IsNaN(Y) || float.IsNaN(Z) || Color < 0 || Color > 7 || Percent < -1 || Percent > 100 || (int)Kind > 8 || Revision < 0 || (OrePrefab != 0 && Kind != PinKind.Ore))
		{
			return false;
		}
		if (Kind == PinKind.Manual)
		{
			if (Guid.TryParseExact(Id, "N", out var _))
			{
				return Percent == -1;
			}
			return false;
		}
		if (Id == CanonicalId)
		{
			if (Kind != PinKind.Dungeon && OrePrefab == 0)
			{
				if (ObjectId != 0)
				{
					return ObjectUser != 0;
				}
				return false;
			}
			return true;
		}
		return false;
	}

	internal void Write(BinaryWriter writer)
	{
		Write(writer, includeOrePrefab: true);
	}

	internal void Write(BinaryWriter writer, bool includeOrePrefab)
	{
		writer.Write(Id);
		writer.Write((byte)Kind);
		writer.Write(Revision);
		writer.Write(Name);
		writer.Write(X);
		writer.Write(Y);
		writer.Write(Z);
		writer.Write(Icon);
		writer.Write(Color);
		writer.Write(Checked);
		writer.Write(Compass);
		writer.Write(Percent);
		writer.Write(ObjectUser);
		writer.Write(ObjectId);
		if (includeOrePrefab)
		{
			writer.Write(OrePrefab);
		}
	}

	internal static PinRecord Read(BinaryReader reader, bool includeOrePrefab = true)
	{
		PinRecord pinRecord = new PinRecord
		{
			Id = reader.ReadString(),
			Kind = (PinKind)reader.ReadByte(),
			Revision = reader.ReadInt32(),
			Name = reader.ReadString(),
			X = reader.ReadSingle(),
			Y = reader.ReadSingle(),
			Z = reader.ReadSingle(),
			Icon = reader.ReadString(),
			Color = reader.ReadInt32(),
			Checked = reader.ReadBoolean(),
			Compass = reader.ReadBoolean(),
			Percent = reader.ReadInt32(),
			ObjectUser = reader.ReadInt64(),
			ObjectId = reader.ReadUInt32()
		};
		if (includeOrePrefab)
		{
			pinRecord.OrePrefab = reader.ReadInt32();
		}
		if (!pinRecord.IsValid())
		{
			throw new InvalidDataException("Invalid shared pin record.");
		}
		return pinRecord;
	}
}
internal sealed class PinStore
{
	internal const int Capacity = 10000;

	internal readonly Dictionary<string, PinRecord> Pins = new Dictionary<string, PinRecord>(StringComparer.Ordinal);

	internal bool Upsert(PinRecord candidate, int threshold, out PinRecord current, bool authoritativeOre = false, bool verifiedOreObject = false)
	{
		Pins.TryGetValue(candidate.Id, out current);
		if (!candidate.IsValid() || (current == null && Pins.Count >= 10000))
		{
			return false;
		}
		PinRecord pinRecord = candidate.Copy();
		if (pinRecord.Kind == PinKind.Manual)
		{
			if (pinRecord.Revision != (current?.Revision ?? 0))
			{
				return false;
			}
		}
		else if (pinRecord.Kind == PinKind.Ore)
		{
			if (current != null)
			{
				pinRecord = current.Copy();
				if (authoritativeOre || verifiedOreObject)
				{
					pinRecord.Icon = candidate.Icon;
					pinRecord.ObjectUser = candidate.ObjectUser;
					pinRecord.ObjectId = candidate.ObjectId;
				}
				if (candidate.Percent >= 0 && (authoritativeOre || current.Percent < 0 || candidate.Percent < current.Percent))
				{
					pinRecord.Percent = candidate.Percent;
				}
				if ((authoritativeOre || verifiedOreObject) && current.IsMissingOre && !candidate.IsMissingOre)
				{
					pinRecord.Percent = candidate.Percent;
				}
			}
			pinRecord.Checked = pinRecord.Percent >= 0 && pinRecord.Percent <= threshold;
		}
		else if (pinRecord.Kind == PinKind.Dungeon)
		{
			int num = DungeonNames.Priority(pinRecord.Icon, pinRecord.Name);
			string text = DungeonNames.ForPrefab(pinRecord.Icon);
			if (num > 1)
			{
				text = DungeonNames.SpecificLabel(pinRecord.Name);
			}
			if (text != null)
			{
				pinRecord.Name = text;
			}
			else
			{
				pinRecord.Icon = "";
			}
			if (current != null)
			{
				if (num <= DungeonNames.Priority(current.Icon, current.Name))
				{
					return false;
				}
				pinRecord = current.Copy();
				pinRecord.Name = text;
				pinRecord.Icon = candidate.Icon;
			}
		}
		if (current != null && pinRecord.SameDisplay(current) && pinRecord.ObjectUser == current.ObjectUser && pinRecord.ObjectId == current.ObjectId)
		{
			return false;
		}
		pinRecord.Revision = (current?.Revision ?? 0) + 1;
		Pins[pinRecord.Id] = pinRecord;
		current = pinRecord;
		return true;
	}

	internal bool DeleteManual(string id, int revision, out PinRecord current)
	{
		if (!Pins.TryGetValue(id, out current) || current.Kind != PinKind.Manual || current.Revision != revision)
		{
			return false;
		}
		Pins.Remove(id);
		return true;
	}

	internal bool ReconcileLegacyOre(string id, PinRecord replacement, int threshold, out PinRecord updated)
	{
		updated = null;
		if (!Pins.TryGetValue(id, out var value) || value.Kind != PinKind.Ore || value.OrePrefab != 0 || (replacement != null && (replacement.OrePrefab == 0 || !replacement.IsValid())))
		{
			return false;
		}
		Pins.Remove(id);
		if (replacement != null && Upsert(replacement, threshold, out var current, authoritativeOre: true))
		{
			updated = current;
		}
		return true;
	}
}
internal sealed class SeneaLBindings
{
	internal static readonly string[] CustomIcons = new string[5] { "", "ore", "chest", "danger", "ship" };

	internal readonly Type Pins = RequireType("SeneaLUI.Map.MapPins");

	internal readonly Type OreScout = RequireType("SeneaLUI.Map.OreScout");

	internal readonly Type LiveMarks = RequireType("SeneaLUI.Map.LiveMarks");

	internal readonly Type PinDialog = RequireType("SeneaLUI.Map.PinDialog");

	internal readonly Type MapView = RequireType("SeneaLUI.Map.MapView");

	private readonly MethodInfo dungeonWanted;

	private readonly MethodInfo zoneGenerated;

	private readonly MethodInfo oreName;

	private readonly MethodInfo saveOreDiscoveries;

	private readonly PropertyInfo detectionRadius;

	private readonly MethodInfo extend;

	private readonly MethodInfo sprite;

	private readonly MethodInfo invalidate;

	private readonly MethodInfo request;

	private readonly MethodInfo percent;

	private readonly MethodInfo liveOn;

	private readonly MethodInfo liveKey;

	private readonly MethodInfo saveLiveHidden;

	private readonly MethodInfo vanillaSprite;

	private readonly FieldInfo liveHidden;

	private readonly FieldInfo rockAreas;

	private readonly FieldInfo rockAreaHealth;

	internal readonly MethodInfo ReadTargetHealth;

	private readonly FieldInfo known;

	private readonly FieldInfo rocks;

	private readonly FieldInfo places;

	private readonly FieldInfo tracked;

	private readonly FieldInfo player;

	private readonly FieldInfo entryPin;

	private readonly FieldInfo entryId;

	private readonly FieldInfo entryPos;

	private readonly FieldInfo entryZdo;

	private readonly FieldInfo entryDungeon;

	private readonly FieldInfo entryOff;

	private readonly FieldInfo entryName;

	private readonly FieldInfo trackPin;

	private readonly FieldInfo trackId;

	private readonly FieldInfo trackHidden;

	private readonly FieldInfo customIcons;

	private readonly FieldInfo selected;

	private readonly FieldInfo color;

	private readonly FieldInfo compass;

	private readonly FieldInfo editing;

	private readonly PropertyInfo dialogOpen;

	private readonly PropertyInfo compassValue;

	private readonly PropertyInfo oreDetect;

	private readonly PropertyInfo orePin;

	private readonly PropertyInfo oreThreshold;

	private readonly PropertyInfo allowOre;

	private readonly FieldInfo liveShowHidden;

	private readonly FieldInfo oreType;

	private readonly FieldInfo dungeonType;

	private readonly FieldInfo vehicleType;

	private readonly FieldInfo shipType;

	private readonly FieldInfo cartType;

	private readonly FieldInfo portalType;

	private readonly FieldInfo mapPins;

	private readonly FieldInfo namePin;

	private readonly FieldInfo mapUpdate;

	private readonly FieldInfo knownObjectUsers;

	private readonly FieldInfo oreSectors;

	internal const BindingFlags Members = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

	internal bool CanShowDiscoveries
	{
		get
		{
			if ((bool)allowOre.GetValue(null))
			{
				return ((ConfigEntry<bool>)orePin.GetValue(null)).Value;
			}
			return false;
		}
	}

	internal bool CanReportDiscoveries
	{
		get
		{
			if (CanShowDiscoveries)
			{
				return ((ConfigEntry<bool>)oreDetect.GetValue(null)).Value;
			}
			return false;
		}
	}

	internal float DetectionRadius => (float)detectionRadius.GetValue(null);

	internal bool WantsDungeons => (bool)dungeonWanted.Invoke(null, new object[1] { "dungeon" });

	internal IDictionary Known => (IDictionary)known.GetValue(null);

	internal int OreThreshold => (oreThreshold.GetValue(null) as ConfigEntry<int>)?.Value ?? 5;

	internal IEnumerable<Location> DungeonPlaces => (IEnumerable<Location>)places.GetValue(null);

	internal IDictionary Rocks => (IDictionary)rocks.GetValue(null);

	internal IDictionary Tracks => (IDictionary)tracked.GetValue(null);

	internal bool OwnPlayerLoaded => player.GetValue(null) == Player.m_localPlayer;

	internal SeneaLBindings()
	{
		Type nestedType = OreScout.GetNestedType("Entry", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		Type nestedType2 = LiveMarks.GetNestedType("Track", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		extend = Method(Pins, "Extend");
		sprite = Method(RequireType("SeneaLUI.Theme.Icons"), "Get", new Type[1] { typeof(string) });
		invalidate = Method(Pins, "Invalidate");
		request = Method(MapView, "RequestPins");
		percent = Method(OreScout, "Percent");
		oreName = Method(OreScout, "OreName", new Type[1] { typeof(Component) });
		saveOreDiscoveries = Method(OreScout, "Save", new Type[1] { typeof(Player) });
		Type type = RequireType("SeneaLUI.Hud.TargetHpView");
		Type type2 = typeof(int).MakeByRefType();
		ReadTargetHealth = Method(type, "Read", new Type[3]
		{
			typeof(Component),
			type2,
			type2
		});
		rockAreas = Field(typeof(MineRock5), "m_hitAreas");
		rockAreaHealth = Field(rockAreas.FieldType.GetGenericArguments()[0], "m_health");
		liveOn = Method(LiveMarks, "On");
		liveHidden = Field(LiveMarks, "Hidden");
		liveKey = Method(LiveMarks, "KeyOf");
		saveLiveHidden = Method(LiveMarks, "SaveHidden");
		vanillaSprite = Method(typeof(Minimap), "GetSprite");
		liveShowHidden = Field(LiveMarks, "ShowHidden");
		known = Field(OreScout, "Known");
		rocks = Field(OreScout, "Live");
		places = Field(OreScout, "Places");
		player = Field(OreScout, "_of");
		tracked = Field(LiveMarks, "Tracks");
		entryPin = Field(nestedType, "Pin");
		entryId = Field(nestedType, "Id");
		entryPos = Field(nestedType, "Pos");
		entryZdo = Field(nestedType, "Zdo");
		entryDungeon = Field(nestedType, "Dungeon");
		entryOff = Field(nestedType, "Off");
		entryName = Field(nestedType, "Name");
		trackPin = Field(nestedType2, "Pin");
		trackId = Field(nestedType2, "Id");
		trackHidden = Field(nestedType2, "Hid");
		customIcons = Field(PinDialog, "Customs");
		selected = Field(PinDialog, "_selected");
		color = Field(PinDialog, "_color");
		compass = Field(PinDialog, "_compass");
		editing = Field(PinDialog, "_edit");
		dialogOpen = PinDialog.GetProperty("Open", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingMemberException(PinDialog.FullName, "Open");
		compassValue = compass.FieldType.GetProperty("Value", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingMemberException(compass.FieldType.FullName, "Value");
		Type type3 = RequireType("SeneaLUI.Plugin");
		oreDetect = Property(type3, "OreDetect");
		orePin = Property(type3, "OrePin");
		oreThreshold = Property(type3, "OreThreshold");
		Type type4 = RequireType("SeneaLUI.Net.Policy");
		allowOre = Property(type4, "CanOreDetect");
		detectionRadius = Property(type4, "OreRadius");
		dungeonWanted = Method(OreScout, "Wanted", new Type[1] { typeof(string) });
		zoneGenerated = Method(typeof(ZoneSystem), "IsZoneGenerated", new Type[1] { typeof(Vector2s) });
		oreType = Field(Pins, "Ore");
		dungeonType = Field(Pins, "Dungeon");
		vehicleType = Field(Pins, "Vehicle");
		shipType = Field(LiveMarks, "PinShip");
		cartType = Field(LiveMarks, "PinCart");
		portalType = Field(LiveMarks, "PinPortal");
		mapPins = Field(typeof(Minimap), "m_pins");
		namePin = Field(typeof(Minimap), "m_namePin");
		mapUpdate = Field(typeof(Minimap), "m_pinUpdateRequired");
		knownObjectUsers = Field(typeof(ZDOID), "m_userIDs");
		oreSectors = Field(typeof(ZDOMan), "m_objectsBySector");
	}

	internal static Type FindType(string name)
	{
		Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
		for (int i = 0; i < assemblies.Length; i++)
		{
			Type type = assemblies[i