Decompiled source of ValheimArchipelagoRandomizer v0.2.35

ValheimRandomizer.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.WebSockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Archipelago.MultiClient.Net;
using Archipelago.MultiClient.Net.Enums;
using Archipelago.MultiClient.Net.Exceptions;
using Archipelago.MultiClient.Net.Helpers;
using Archipelago.MultiClient.Net.Models;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn.Configs;
using Jotunn.Entities;
using Jotunn.Managers;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("ValheimRandomizer")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+0660048b62c7e8f9cf251705139a0190370e03a5")]
[assembly: AssemblyProduct("ValheimRandomizer")]
[assembly: AssemblyTitle("ValheimRandomizer")]
[assembly: AssemblyMetadata("AI_Assisted_Creation", "This assembly was partially created with the assistance of generative AI in Cursor.")]
[assembly: AssemblyMetadata("AI_Model_Vendor", "xAI")]
[assembly: AssemblyVersion("1.0.0.0")]
internal static class ArchipelagoConnection
{
	public const string DefaultGoalDefeatKey = "defeated_frozenking_p3";

	public const string DefaultGoalLocationName = "Defeat Kall";

	public const string GoalItemName = "Victory";

	private static string goalDefeatKey = "defeated_frozenking_p3";

	private static string goalLocationName = "Defeat Kall";

	private static ArchipelagoSession session;

	private static bool connected;

	private static bool dropChecksResolved;

	private static int lost;

	private static int sendGeneration;

	private static Task inflight;

	private static DateTime inflightStartedUtc;

	private static Action inflightSuccess;

	private static readonly object noticeLock = new object();

	private static string pendingNotice;

	private static readonly TimeSpan SendTimeout = TimeSpan.FromSeconds(8.0);

	private static FieldInfo socketField;

	private static List<string> gottenItems = new List<string>();

	private static readonly HashSet<string> warnedMissingDrops = new HashSet<string>();

	public static bool IsConnected
	{
		get
		{
			if (connected)
			{
				return session != null;
			}
			return false;
		}
	}

	public static string DisconnectMessage { get; private set; }

	public static bool DropChecksEnabled { get; private set; }

	public static bool BossDropsEnabled { get; private set; }

	public static void Connect(string host, int port, string slot, string password)
	{
		//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b2: Expected O, but got Unknown
		//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d3: Expected O, but got Unknown
		//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ee: Expected O, but got Unknown
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_0037: Expected O, but got Unknown
		Disconnect();
		session = ArchipelagoSessionFactory.CreateSession(host, port);
		try
		{
			LoginResult val = session.TryConnectAndLogin("Valheim", slot, (ItemsHandlingFlags)7, (Version)null, (string[])null, (string)null, password, true);
			if (!val.Successful)
			{
				LoginFailure val2 = (LoginFailure)val;
				throw new Exception(string.Join("; ", val2.Errors ?? Array.Empty<string>()));
			}
			gottenItems.Clear();
			warnedMissingDrops.Clear();
			ResolveDropChecks(val);
			ResolveBossDrops(val);
			ApplyGoal(val);
			ValheimRandomizer.ApplySeedRules(ReadSlotInt(val, "research_access"), ReadSlotBool(val, "sequential_tiers"));
			session.Items.ItemReceived += new ItemReceivedHandler(OnApItemReceived);
			DisconnectMessage = null;
			session.Socket.SocketClosed += new SocketClosedHandler(OnSocketClosed);
			session.Socket.ErrorReceived += new ErrorReceivedHandler(OnSocketError);
			connected = true;
			PublishResearchCosts();
			if (successSlotData(val, out var slotData))
			{
				ManualLogSource log = ValheimRandomizer.Log;
				if (log != null)
				{
					log.LogInfo((object)("Slot data keys: " + string.Join(", ", slotData.Keys)));
				}
			}
		}
		catch
		{
			Disconnect();
			throw;
		}
	}

	public static void Disconnect()
	{
		ReleaseSession(closeSocket: true);
		DisconnectMessage = null;
		Interlocked.Exchange(ref lost, 0);
	}

	private static void LoseConnection(string message)
	{
		if (Interlocked.Exchange(ref lost, 1) == 0)
		{
			DisconnectMessage = message;
			lock (noticeLock)
			{
				pendingNotice = message;
			}
			ManualLogSource log = ValheimRandomizer.Log;
			if (log != null)
			{
				log.LogError((object)message);
			}
			ReleaseSession(closeSocket: false);
		}
	}

	private static void ReleaseSession(bool closeSocket)
	{
		//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ad: Expected O, but got Unknown
		//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f2: Expected O, but got Unknown
		//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
		//IL_0109: Expected O, but got Unknown
		ArchipelagoSession val = session;
		session = null;
		connected = false;
		sendGeneration++;
		Task task = inflight;
		inflight = null;
		inflightSuccess = null;
		if (task != null && !task.IsCompleted)
		{
			task.ContinueWith(delegate(Task t)
			{
				_ = t.Exception;
			}, TaskContinuationOptions.OnlyOnFaulted);
		}
		DropChecksEnabled = false;
		BossDropsEnabled = false;
		dropChecksResolved = false;
		goalDefeatKey = "defeated_frozenking_p3";
		goalLocationName = "Defeat Kall";
		ValheimRandomizer.ClearSeedRules();
		if (val == null)
		{
			return;
		}
		try
		{
			val.Items.ItemReceived -= new ItemReceivedHandler(OnApItemReceived);
		}
		catch (Exception ex)
		{
			ManualLogSource log = ValheimRandomizer.Log;
			if (log != null)
			{
				log.LogWarning((object)("Disconnect unhook failed: " + ex.Message));
			}
		}
		try
		{
			if (val.Socket != null)
			{
				val.Socket.SocketClosed -= new SocketClosedHandler(OnSocketClosed);
				val.Socket.ErrorReceived -= new ErrorReceivedHandler(OnSocketError);
			}
		}
		catch (Exception ex2)
		{
			ManualLogSource log2 = ValheimRandomizer.Log;
			if (log2 != null)
			{
				log2.LogWarning((object)("Disconnect unhook failed: " + ex2.Message));
			}
		}
		if (!closeSocket || val.Socket == null || !val.Socket.Connected)
		{
			return;
		}
		try
		{
			val.Socket.DisconnectAsync().ContinueWith(delegate(Task t)
			{
				ManualLogSource log4 = ValheimRandomizer.Log;
				if (log4 != null)
				{
					log4.LogWarning((object)("Disconnect failed: " + t.Exception.GetBaseException().Message));
				}
			}, TaskContinuationOptions.OnlyOnFaulted);
		}
		catch (Exception ex3)
		{
			ManualLogSource log3 = ValheimRandomizer.Log;
			if (log3 != null)
			{
				log3.LogWarning((object)("Disconnect failed: " + ex3.Message));
			}
		}
	}

	private static void OnSocketClosed(string reason)
	{
		LoseConnection(string.IsNullOrWhiteSpace(reason) ? "Archipelago server closed the connection. Reconnect to send checks." : ("Archipelago server closed the connection: " + reason.Trim() + ". Reconnect to send checks."));
	}

	private static void OnSocketError(Exception exception, string message)
	{
		if (!IsDeadSocket(exception))
		{
			ArchipelagoSession obj = session;
			if (SocketIsUsable((obj != null) ? obj.Socket : null))
			{
				ManualLogSource log = ValheimRandomizer.Log;
				if (log != null)
				{
					log.LogWarning((object)("Archipelago error: " + ((!string.IsNullOrEmpty(message)) ? message : exception?.Message)));
				}
				return;
			}
		}
		LoseConnection("Archipelago connection lost. Reconnect to send checks.");
	}

	private static bool IsDeadSocket(Exception exception)
	{
		for (Exception ex = exception; ex != null; ex = ex.InnerException)
		{
			if (ex is ArchipelagoSocketClosedException || ex is WebSocketException)
			{
				return true;
			}
		}
		return false;
	}

	private static bool SocketIsUsable(IArchipelagoSocketHelper socket)
	{
		if (socket == null)
		{
			return false;
		}
		try
		{
			if (socketField == null)
			{
				socketField = FindSocketField(((object)socket).GetType());
			}
			if (socketField?.GetValue(socket) is WebSocket webSocket)
			{
				return webSocket.State == WebSocketState.Open;
			}
		}
		catch (Exception ex)
		{
			ManualLogSource log = ValheimRandomizer.Log;
			if (log != null)
			{
				log.LogWarning((object)("Socket state check failed: " + ex.Message));
			}
		}
		return socket.Connected;
	}

	private static FieldInfo FindSocketField(Type type)
	{
		while (type != null)
		{
			FieldInfo field = type.GetField("Socket", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (field != null)
			{
				return field;
			}
			type = type.BaseType;
		}
		return null;
	}

	public static void PublishResearchCosts()
	{
		if (IsConnected && session != null)
		{
			IConnectionInfoProvider connectionInfo = session.ConnectionInfo;
			string text = "valheim_costs_" + connectionInfo.Team.ToString(CultureInfo.InvariantCulture) + "_" + connectionInfo.Slot.ToString(CultureInfo.InvariantCulture);
			session.DataStorage[text] = DataStorageElement.op_Implicit((ValheimRandomizer.ActiveResearchCosts == ValheimRandomizer.ResearchCostMode.Halved) ? "halved" : "default");
		}
	}

	public static void PublishPity(JObject payload)
	{
		if (IsConnected && session != null && payload != null)
		{
			IConnectionInfoProvider connectionInfo = session.ConnectionInfo;
			string text = "valheim_pity_" + connectionInfo.Team.ToString(CultureInfo.InvariantCulture) + "_" + connectionInfo.Slot.ToString(CultureInfo.InvariantCulture);
			session.DataStorage[text] = DataStorageElement.op_Implicit(((JToken)payload).ToString((Formatting)0, Array.Empty<JsonConverter>()));
		}
	}

	public static void Pump()
	{
		string text;
		lock (noticeLock)
		{
			if (pendingNotice == null || (Object)(object)MessageHud.instance == (Object)null)
			{
				return;
			}
			text = pendingNotice;
			pendingNotice = null;
		}
		MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false, true);
		ValheimRandomizer.RevealEligibleRecipes();
	}

	private static void ResolveDropChecks(LoginResult result)
	{
		bool flag = ReadSlotBool(result, "drop_checks");
		bool flag2 = SeedHasGatherLocations();
		if (flag || flag2)
		{
			DropChecksEnabled = true;
			dropChecksResolved = true;
			ManualLogSource log = ValheimRandomizer.Log;
			if (log != null)
			{
				log.LogInfo((object)(flag ? "Drop checks on from slot data." : "Drop checks on because this seed has Gather locations."));
			}
			return;
		}
		ArchipelagoSession obj = session;
		object obj2;
		if (obj == null)
		{
			obj2 = null;
		}
		else
		{
			ILocationCheckHelper locations = obj.Locations;
			obj2 = ((locations != null) ? locations.AllLocations : null);
		}
		if (obj2 != null && session.Locations.AllLocations.Count > 0)
		{
			DropChecksEnabled = false;
			dropChecksResolved = true;
			ManualLogSource log2 = ValheimRandomizer.Log;
			if (log2 != null)
			{
				log2.LogInfo((object)"Drop checks off.");
			}
		}
	}

	private static void ResolveBossDrops(LoginResult result)
	{
		bool flag = ReadSlotBool(result, "boss_drops");
		if (flag || SeedHasBossLocations())
		{
			BossDropsEnabled = true;
			ManualLogSource log = ValheimRandomizer.Log;
			if (log != null)
			{
				log.LogInfo((object)(flag ? "Boss drops on from slot data." : "Boss drops on because this seed has more than one boss location."));
			}
		}
		else
		{
			BossDropsEnabled = false;
			ManualLogSource log2 = ValheimRandomizer.Log;
			if (log2 != null)
			{
				log2.LogInfo((object)"Boss drops off. This seed still drops them from the boss.");
			}
		}
	}

	private static bool SeedHasBossLocations()
	{
		int num = 0;
		foreach (ValheimRandomizer.BossDrop bossDrop in ValheimRandomizer.BossDrops)
		{
			try
			{
				if (session.Locations.GetLocationIdFromName("Valheim", bossDrop.locationName) > 0)
				{
					num++;
				}
			}
			catch
			{
				return false;
			}
			if (num >= 2)
			{
				return true;
			}
		}
		return false;
	}

	public static void EnsureDropChecksResolved()
	{
		if (dropChecksResolved || session == null)
		{
			return;
		}
		if (SeedHasGatherLocations())
		{
			DropChecksEnabled = true;
			dropChecksResolved = true;
			ManualLogSource log = ValheimRandomizer.Log;
			if (log != null)
			{
				log.LogInfo((object)"Drop checks on because this seed has Gather locations.");
			}
			return;
		}
		ILocationCheckHelper locations = session.Locations;
		if (((locations != null) ? locations.AllLocations : null) != null && session.Locations.AllLocations.Count > 0)
		{
			DropChecksEnabled = false;
			dropChecksResolved = true;
			ManualLogSource log2 = ValheimRandomizer.Log;
			if (log2 != null)
			{
				log2.LogInfo((object)"Drop checks off.");
			}
		}
	}

	private static bool SeedHasGatherLocations()
	{
		ArchipelagoSession obj = session;
		object obj2;
		if (obj == null)
		{
			obj2 = null;
		}
		else
		{
			ILocationCheckHelper locations = obj.Locations;
			obj2 = ((locations != null) ? locations.AllLocations : null);
		}
		ReadOnlyCollection<long> readOnlyCollection = (ReadOnlyCollection<long>)obj2;
		if (readOnlyCollection == null)
		{
			return false;
		}
		foreach (long item in readOnlyCollection)
		{
			string locationNameFromId = session.Locations.GetLocationNameFromId(item, "Valheim");
			if (!string.IsNullOrEmpty(locationNameFromId) && locationNameFromId.StartsWith("Gather: ", StringComparison.Ordinal))
			{
				return true;
			}
		}
		return false;
	}

	private static bool successSlotData(LoginResult result, out Dictionary<string, object> slotData)
	{
		slotData = null;
		LoginSuccessful val = (LoginSuccessful)(object)((result is LoginSuccessful) ? result : null);
		if (val == null || val.SlotData == null)
		{
			return false;
		}
		slotData = val.SlotData;
		return true;
	}

	private static bool ReadSlotBool(LoginResult result, string key)
	{
		if (!TrySlotValue(result, key, out var raw))
		{
			return false;
		}
		if (TryCoerceBool(raw, out var flag))
		{
			return flag;
		}
		ManualLogSource log = ValheimRandomizer.Log;
		if (log != null)
		{
			log.LogWarning((object)$"Slot data '{key}' was {raw.GetType().Name} '{raw}' and was not read as on.");
		}
		return false;
	}

	private static bool TryCoerceBool(object raw, out bool flag)
	{
		flag = false;
		if (raw == null)
		{
			return false;
		}
		if (raw is bool flag2)
		{
			flag = flag2;
			return true;
		}
		if (raw is string text)
		{
			if (bool.TryParse(text, out flag))
			{
				return true;
			}
			if (int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
			{
				flag = result != 0;
				return true;
			}
			if (text.Equals("yes", StringComparison.OrdinalIgnoreCase))
			{
				flag = true;
				return true;
			}
			return false;
		}
		PropertyInfo property = raw.GetType().GetProperty("Value");
		if (property != null && property.PropertyType == typeof(object))
		{
			object value = property.GetValue(raw);
			if (value != null && value != raw)
			{
				return TryCoerceBool(value, out flag);
			}
		}
		try
		{
			flag = Convert.ToInt64(raw, CultureInfo.InvariantCulture) != 0;
			return true;
		}
		catch
		{
			return false;
		}
	}

	private static void ApplyGoal(LoginResult result)
	{
		goalDefeatKey = "defeated_frozenking_p3";
		goalLocationName = "Defeat Kall";
		string key = ReadSlotString(result, "goal_key");
		string name = ReadSlotString(result, "goal_location");
		if (IsGoalKey(key) && IsGoalLocation(name))
		{
			goalDefeatKey = key;
			goalLocationName = name;
		}
		ManualLogSource log = ValheimRandomizer.Log;
		if (log != null)
		{
			log.LogInfo((object)("Goal is " + goalLocationName + " (" + goalDefeatKey + ")."));
		}
	}

	private static string ReadSlotString(LoginResult result, string key)
	{
		if (!TrySlotValue(result, key, out var raw))
		{
			return null;
		}
		if (raw is string text)
		{
			return text.Trim();
		}
		try
		{
			return Convert.ToString(raw, CultureInfo.InvariantCulture)?.Trim();
		}
		catch
		{
			return null;
		}
	}

	private static bool IsGoalKey(string key)
	{
		if (string.IsNullOrEmpty(key) || key.Length > 64)
		{
			return false;
		}
		foreach (char c in key)
		{
			if ((c < 'a' || c > 'z') && (c < '0' || c > '9') && c != '_')
			{
				return false;
			}
		}
		return true;
	}

	private static bool IsGoalLocation(string name)
	{
		if (string.IsNullOrWhiteSpace(name) || name.Length > 80)
		{
			return false;
		}
		if (name.IndexOf('\n') < 0)
		{
			return name.IndexOf('\r') < 0;
		}
		return false;
	}

	private static int? ReadSlotInt(LoginResult result, string key)
	{
		if (!TrySlotValue(result, key, out var raw))
		{
			return null;
		}
		if (raw is string text)
		{
			string text2 = text.Trim();
			if (int.TryParse(text2, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2))
			{
				return result2;
			}
			if (text2.Equals("default", StringComparison.OrdinalIgnoreCase))
			{
				return 0;
			}
			if (text2.Equals("tier", StringComparison.OrdinalIgnoreCase))
			{
				return 1;
			}
			if (text2.Equals("materials", StringComparison.OrdinalIgnoreCase) || text2.Equals("mats", StringComparison.OrdinalIgnoreCase))
			{
				return 2;
			}
			if (text2.Equals("natural", StringComparison.OrdinalIgnoreCase))
			{
				return 3;
			}
			return null;
		}
		try
		{
			return Convert.ToInt32(raw, CultureInfo.InvariantCulture);
		}
		catch
		{
			return null;
		}
	}

	private static bool TrySlotValue(LoginResult result, string key, out object raw)
	{
		raw = null;
		LoginSuccessful val = (LoginSuccessful)(object)((result is LoginSuccessful) ? result : null);
		if (val == null || val.SlotData == null)
		{
			return false;
		}
		if (val.SlotData.TryGetValue(key, out raw))
		{
			return raw != null;
		}
		return false;
	}

	public static void SendLocation(string researchId)
	{
		if (!ValheimRandomizer.randomized.Value || !WorldLoaded() || string.IsNullOrWhiteSpace(researchId))
		{
			return;
		}
		if (researchId.IndexOf(' ') >= 0)
		{
			ValheimRandomizer.Log.LogWarning((object)("Refusing location key with a space: " + researchId));
			return;
		}
		if (!ValheimRandomizer.researchToArchipelago.TryGetValue(researchId, out var value))
		{
			ValheimRandomizer.Log.LogWarning((object)("No Archipelago name for research " + researchId));
			return;
		}
		ValheimRandomizer.Log.LogInfo((object)("Queue location " + value));
		if (!HasGlobal("ap_loc_sent_" + researchId) && !HasGlobal("ap_loc_pending_" + researchId))
		{
			SetGlobal("ap_loc_pending_" + researchId);
			TryFlushPendingLocations();
		}
	}

	public static void SendGoal()
	{
		if (connected && session != null && ValheimRandomizer.randomized.Value && WorldLoaded() && !HasGlobal("ap_goal_sent") && !HasGlobal("ap_goal_pending"))
		{
			ValheimRandomizer.Log.LogInfo((object)("Queue goal " + goalLocationName));
			SetGlobal("ap_goal_pending");
			TryFlushPendingLocations();
		}
	}

	public static void SendDropLocation(string itemID, string locationName)
	{
		if (DropChecksEnabled && ValheimRandomizer.randomized.Value && WorldLoaded() && !string.IsNullOrWhiteSpace(itemID) && !HasGlobal("ap_drop_sent_" + itemID) && !HasGlobal("ap_drop_pending_" + itemID))
		{
			ValheimRandomizer.Log.LogInfo((object)("Queue drop location " + locationName));
			SetGlobal("ap_drop_pending_" + itemID);
			TryFlushPendingLocations();
		}
	}

	private static void OnApItemReceived(ReceivedItemsHelper helper)
	{
		if (!WorldLoaded())
		{
			return;
		}
		while (true)
		{
			ItemInfo val = helper.DequeueItem();
			if (val != null)
			{
				ApplyReceivedItem(val.ItemName);
				continue;
			}
			break;
		}
	}

	private static void TryGetAllItems()
	{
		if (!WorldLoaded())
		{
			return;
		}
		foreach (ItemInfo item in session.Items.AllItemsReceived)
		{
			ApplyReceivedItem(item.ItemName);
		}
	}

	private static void ApplyReceivedItem(string name)
	{
		if (string.IsNullOrEmpty(name) || gottenItems.Contains(name))
		{
			return;
		}
		ValheimRandomizer.BossDrop value3;
		if (ValheimRandomizer.archipelagoToResearch.TryGetValue(name, out var value))
		{
			gottenItems.Add(name);
			ValheimRandomizer.DoUnlockResearch(value);
		}
		else if (name == "Victory")
		{
			gottenItems.Add(name);
		}
		else if (name.StartsWith("Gather: ", StringComparison.Ordinal))
		{
			if (ValheimRandomizer.dropByLocationName.TryGetValue(name, out var value2))
			{
				string text = "ap_gather_got_" + value2.itemID;
				if (HasGlobal(text))
				{
					gottenItems.Add(name);
				}
				else if (ValheimRandomizer.GrantDropBundle(value2.itemID, value2.amount))
				{
					gottenItems.Add(name);
					SetGlobal(text);
				}
			}
		}
		else if (ValheimRandomizer.bossByItemName.TryGetValue(name, out value3))
		{
			string text2 = "ap_boss_got_" + value3.itemID;
			if (HasGlobal(text2))
			{
				gottenItems.Add(name);
				return;
			}
			int amount = ValheimRandomizer.BossGrantAmount(value3);
			if (ValheimRandomizer.GrantDropBundle(value3.itemID, amount))
			{
				gottenItems.Add(name);
				SetGlobal(text2);
			}
		}
		else
		{
			gottenItems.Add(name);
			ValheimRandomizer.Log.LogWarning((object)("Received AP item '" + name + "' but no research mapping was found."));
		}
	}

	public static void TryFlushPendingLocations()
	{
		if (!ReadyToSend())
		{
			return;
		}
		if (!SocketIsUsable(session.Socket))
		{
			LoseConnection("Archipelago connection lost. Reconnect to send checks.");
		}
		else
		{
			if (!WorldLoaded())
			{
				return;
			}
			EnsureDropChecksResolved();
			TryGetAllItems();
			QueueBossKills();
			QueueGoalIfBossDown();
			List<long> list = new List<long>();
			List<string> list2 = new List<string>();
			List<long> list3 = new List<long>();
			List<string> list4 = new List<string>();
			List<long> list5 = new List<long>();
			List<string> list6 = new List<string>();
			string[] array;
			try
			{
				array = ZoneSystem.instance.GetGlobalKeys().ToArray();
			}
			catch (Exception ex)
			{
				ManualLogSource log = ValheimRandomizer.Log;
				if (log != null)
				{
					log.LogWarning((object)("Pending location scan failed: " + ex.Message));
				}
				return;
			}
			string[] array2 = array;
			foreach (string text in array2)
			{
				if (text.StartsWith("ap_pending:", StringComparison.Ordinal) || text.StartsWith("ap_sent:", StringComparison.Ordinal))
				{
					RemoveGlobal(text);
					if (warnedMissingDrops.Add("legacy-spaced-keys"))
					{
						ValheimRandomizer.Log.LogWarning((object)"Cleared old spaced Archipelago keys. Those checks were not reliable and will not be resent.");
					}
				}
				else
				{
					if (!text.StartsWith("ap_loc_pending_", StringComparison.Ordinal))
					{
						continue;
					}
					string text2 = text.Substring("ap_loc_pending_".Length);
					if (!string.IsNullOrWhiteSpace(text2) && !HasGlobal("ap_loc_sent_" + text2) && ValheimRandomizer.researchToArchipelago.TryGetValue(text2, out var value))
					{
						long num = LookupLocationId(value);
						if (num > 0)
						{
							list.Add(num);
							list2.Add(text2);
						}
					}
				}
			}
			bool sendGoal = HasGlobal("ap_goal_pending") && !HasGlobal("ap_goal_sent");
			long goalId = 0L;
			if (sendGoal)
			{
				goalId = LookupLocationId(goalLocationName, "Goal location '" + goalLocationName + "' is not in this seed. Sending goal status anyway.");
			}
			array2 = array;
			foreach (string text3 in array2)
			{
				if (!text3.StartsWith("ap_drop_pending_", StringComparison.Ordinal))
				{
					continue;
				}
				string text4 = text3.Substring("ap_drop_pending_".Length);
				if (ValheimRandomizer.dropByItemID.TryGetValue(text4, out var value2) && !HasGlobal("ap_drop_sent_" + text4))
				{
					long num2 = LookupLocationId(value2.locationName);
					if (num2 > 0)
					{
						list3.Add(num2);
						list4.Add(text4);
					}
				}
			}
			if (BossDropsEnabled)
			{
				array2 = array;
				foreach (string text5 in array2)
				{
					if (!text5.StartsWith("ap_boss_pending_", StringComparison.Ordinal))
					{
						continue;
					}
					string text6 = text5.Substring("ap_boss_pending_".Length);
					if (ValheimRandomizer.bossByDefeatKey.TryGetValue(text6, out var value3) && !HasGlobal("ap_boss_sent_" + text6))
					{
						long num3 = LookupLocationId(value3.locationName);
						if (num3 > 0)
						{
							list5.Add(num3);
							list6.Add(text6);
						}
					}
				}
			}
			if (list.Count == 0 && list3.Count == 0 && list5.Count == 0 && !sendGoal)
			{
				return;
			}
			ArchipelagoSession captured = session;
			string[] researchToMark = list2.ToArray();
			string[] dropsToMark = list4.ToArray();
			string[] bossesToMark = list6.ToArray();
			int researchNames = list.Count;
			int dropNames = list3.Count;
			int bossNames = list5.Count;
			string goalName = goalLocationName;
			long[] locationIds = list.Concat(list3).Concat(list5).ToArray();
			int generation = sendGeneration;
			inflightStartedUtc = DateTime.UtcNow;
			inflightSuccess = delegate
			{
				if (generation == sendGeneration)
				{
					for (int j = 0; j < researchToMark.Length; j++)
					{
						SetGlobal("ap_loc_sent_" + researchToMark[j]);
						RemoveGlobal("ap_loc_pending_" + researchToMark[j]);
					}
					if (researchNames > 0)
					{
						ManualLogSource log2 = ValheimRandomizer.Log;
						if (log2 != null)
						{
							log2.LogInfo((object)("Unlocked " + researchNames + " location(s)."));
						}
					}
					for (int k = 0; k < dropsToMark.Length; k++)
					{
						SetGlobal("ap_drop_sent_" + dropsToMark[k]);
						RemoveGlobal("ap_drop_pending_" + dropsToMark[k]);
					}
					if (dropNames > 0)
					{
						ManualLogSource log3 = ValheimRandomizer.Log;
						if (log3 != null)
						{
							log3.LogInfo((object)("Unlocked " + dropNames + " drop location(s)."));
						}
					}
					for (int l = 0; l < bossesToMark.Length; l++)
					{
						SetGlobal("ap_boss_sent_" + bossesToMark[l]);
						RemoveGlobal("ap_boss_pending_" + bossesToMark[l]);
					}
					if (bossNames > 0)
					{
						ManualLogSource log4 = ValheimRandomizer.Log;
						if (log4 != null)
						{
							log4.LogInfo((object)("Unlocked " + bossNames + " boss location(s)."));
						}
					}
					if (sendGoal)
					{
						SetGlobal("ap_goal_sent");
						RemoveGlobal("ap_goal_pending");
						ManualLogSource log5 = ValheimRandomizer.Log;
						if (log5 != null)
						{
							log5.LogInfo((object)("Goal sent: " + goalName));
						}
					}
				}
			};
			inflight = Task.Run(delegate
			{
				SendPending(captured, locationIds, sendGoal, goalId);
			});
		}
	}

	private static long LookupLocationId(string locationName, string missingWarning = null)
	{
		try
		{
			long locationIdFromName = session.Locations.GetLocationIdFromName("Valheim", locationName);
			if (locationIdFromName > 0)
			{
				return locationIdFromName;
			}
			if (warnedMissingDrops.Add(locationName))
			{
				ValheimRandomizer.Log.LogWarning((object)(missingWarning ?? ("Location '" + locationName + "' is not in this seed.")));
			}
		}
		catch (Exception ex)
		{
			ValheimRandomizer.Log.LogWarning((object)("AP location lookup failed for '" + locationName + "'. Error: " + ex.Message));
		}
		return 0L;
	}

	private static void SendPending(ArchipelagoSession target, long[] locationIds, bool sendGoal, long goalId)
	{
		if (locationIds.Length != 0)
		{
			target.Locations.CompleteLocationChecks(locationIds);
		}
		if (sendGoal)
		{
			if (goalId > 0)
			{
				target.Locations.CompleteLocationChecks(new long[1] { goalId });
			}
			target.SetGoalAchieved();
		}
	}

	private static bool ReadyToSend()
	{
		Task task = inflight;
		if (task == null)
		{
			if (connected)
			{
				return session != null;
			}
			return false;
		}
		if (!task.IsCompleted)
		{
			if (DateTime.UtcNow - inflightStartedUtc > SendTimeout)
			{
				LoseConnection("Archipelago stopped answering. Reconnect to send checks.");
			}
			return false;
		}
		if (inflight != task)
		{
			if (connected)
			{
				return session != null;
			}
			return false;
		}
		Action action = inflightSuccess;
		inflight = null;
		inflightSuccess = null;
		if (task.IsFaulted || task.IsCanceled)
		{
			string text = task.Exception?.GetBaseException()?.Message;
			LoseConnection(string.IsNullOrEmpty(text) ? "Archipelago connection lost. Reconnect to send checks." : ("Archipelago connection lost. Reconnect to send checks. " + text));
			return false;
		}
		try
		{
			action?.Invoke();
		}
		catch (Exception ex)
		{
			ManualLogSource log = ValheimRandomizer.Log;
			if (log != null)
			{
				log.LogWarning((object)("Saving sent checks failed: " + ex.Message));
			}
		}
		if (connected)
		{
			return session != null;
		}
		return false;
	}

	private static bool WorldLoaded()
	{
		if ((Object)(object)ZoneSystem.instance != (Object)null)
		{
			return (Object)(object)Player.m_localPlayer != (Object)null;
		}
		return false;
	}

	private static bool HasGlobal(string k)
	{
		if ((Object)(object)ZoneSystem.instance != (Object)null)
		{
			return ZoneSystem.instance.GetGlobalKey(k);
		}
		return false;
	}

	private static void SetGlobal(string k)
	{
		if (!((Object)(object)ZoneSystem.instance == (Object)null))
		{
			ZoneSystem.instance.SetGlobalKey(k);
		}
	}

	private static void RemoveGlobal(string k)
	{
		if (!((Object)(object)ZoneSystem.instance == (Object)null))
		{
			ZoneSystem.instance.RemoveGlobalKey(k);
		}
	}

	private static void QueueBossKills()
	{
		if (!BossDropsEnabled || !ValheimRandomizer.randomized.Value)
		{
			return;
		}
		foreach (ValheimRandomizer.BossDrop bossDrop in ValheimRandomizer.BossDrops)
		{
			if (HasGlobal(bossDrop.defeatKey) && !HasGlobal("ap_boss_sent_" + bossDrop.defeatKey) && !HasGlobal("ap_boss_pending_" + bossDrop.defeatKey))
			{
				ManualLogSource log = ValheimRandomizer.Log;
				if (log != null)
				{
					log.LogInfo((object)("Queue boss location " + bossDrop.locationName));
				}
				SetGlobal("ap_boss_pending_" + bossDrop.defeatKey);
			}
		}
	}

	private static void QueueGoalIfBossDown()
	{
		if (ValheimRandomizer.randomized.Value && !HasGlobal("ap_goal_sent") && !HasGlobal("ap_goal_pending") && HasGlobal(goalDefeatKey))
		{
			SetGlobal("ap_goal_pending");
		}
	}

	public static void CompleteGame()
	{
		if (connected && session != null)
		{
			if (!HasGlobal("ap_goal_sent") && !HasGlobal("ap_goal_pending"))
			{
				SetGlobal("ap_goal_pending");
			}
			TryFlushPendingLocations();
		}
	}
}
public class ArchipelagoUI : MonoBehaviour
{
	private Rect _win = new Rect(30f, 80f, 460f, 620f);

	private int _trophyDrops;

	private int _researchCosts;

	private bool _randomized;

	private bool _anyMaterial;

	private bool _showUnlocks;

	private string _host;

	private string _slot;

	private string _password;

	private int _port;

	private string _status = "Not connected";

	private bool _connecting;

	private float _fejdCheckTimer;

	private bool _inMainMenu;

	public void Init()
	{
		_randomized = ValheimRandomizer.randomized.Value;
		_anyMaterial = ValheimRandomizer.anyResearchMaterial.Value;
		_showUnlocks = ValheimRandomizer.ShowResearchUnlocks;
		_host = ValheimRandomizer.archipelagoHostname.Value;
		_port = ValheimRandomizer.archipelagoPort.Value;
		_slot = ValheimRandomizer.archipelagoSlot.Value;
		_password = ValheimRandomizer.archipelagoPassword.Value;
		_trophyDrops = (int)ValheimRandomizer.ActiveTrophyDrops;
		_researchCosts = (int)ValheimRandomizer.ActiveResearchCosts;
	}

	private void Update()
	{
		_fejdCheckTimer -= Time.unscaledDeltaTime;
		if (_fejdCheckTimer <= 0f)
		{
			_fejdCheckTimer = 0.5f;
			_inMainMenu = IsInMainMenu();
		}
		if (!_connecting && !string.IsNullOrEmpty(ArchipelagoConnection.DisconnectMessage))
		{
			_status = ArchipelagoConnection.DisconnectMessage;
		}
	}

	private static bool IsInMainMenu()
	{
		return (Object)(object)Object.FindFirstObjectByType<FejdStartup>() != (Object)null;
	}

	private void OnGUI()
	{
		//IL_0009: Unknown result type (might be due to invalid IL or missing references)
		//IL_000e: 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_0021: Unknown result type (might be due to invalid IL or missing references)
		//IL_002d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Expected O, but got Unknown
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		if (_inMainMenu)
		{
			Color color = GUI.color;
			GUI.color = Color.white;
			_win = GUILayout.Window(((Object)this).GetInstanceID(), _win, new WindowFunction(DrawWindow), "Archipelago – Valheim Randomizer", (GUILayoutOption[])(object)new GUILayoutOption[2]
			{
				GUILayout.ExpandHeight(true),
				GUILayout.ExpandWidth(true)
			});
			GUI.color = color;
		}
	}

	private void DrawWindow(int id)
	{
		//IL_0451: Unknown result type (might be due to invalid IL or missing references)
		GUILayout.BeginVertical(Array.Empty<GUILayoutOption>());
		GUILayout.Label("Connect to an Archipelago room to enable item/location randomization.", Array.Empty<GUILayoutOption>());
		GUILayout.Space(6f);
		_randomized = GUILayout.Toggle(_randomized, "Randomized?", Array.Empty<GUILayoutOption>());
		bool flag = GUILayout.Toggle(_anyMaterial, "Any one found cost item reveals a research", Array.Empty<GUILayoutOption>());
		if (flag != _anyMaterial)
		{
			_anyMaterial = flag;
			ValheimRandomizer.SetAnyResearchMaterial(_anyMaterial);
		}
		bool flag2 = GUILayout.Toggle(_showUnlocks, "Show what each research unlocks", Array.Empty<GUILayoutOption>());
		if (flag2 != _showUnlocks)
		{
			_showUnlocks = flag2;
			ValheimRandomizer.SetShowResearchUnlocks(_showUnlocks);
		}
		GUILayout.Space(4f);
		GUILayout.Label("Research access", Array.Empty<GUILayoutOption>());
		bool seedControlsAccess = ValheimRandomizer.SeedControlsAccess;
		int activeResearchAccess = (int)ValheimRandomizer.ActiveResearchAccess;
		GUI.enabled = !seedControlsAccess && !_connecting;
		int num = GUILayout.SelectionGrid(activeResearchAccess, ValheimRandomizer.ResearchAccessLabels, 1, Array.Empty<GUILayoutOption>());
		if (!seedControlsAccess && num != activeResearchAccess)
		{
			ValheimRandomizer.SetLocalResearchAccess((ValheimRandomizer.ResearchAccessMode)num);
		}
		GUI.enabled = true;
		GUILayout.Label(seedControlsAccess ? "Research access comes from this seed." : "Research access is local until a seed connects.", Array.Empty<GUILayoutOption>());
		GUILayout.Space(4f);
		GUILayout.Label("Trophy drops", Array.Empty<GUILayoutOption>());
		int num2 = GUILayout.SelectionGrid(_trophyDrops, ValheimRandomizer.TrophyDropLabels, 3, Array.Empty<GUILayoutOption>());
		if (num2 != _trophyDrops)
		{
			_trophyDrops = num2;
			ValheimRandomizer.SetTrophyDrops((ValheimRandomizer.TrophyDropMode)num2);
		}
		GUILayout.Label("x2 doubles trophy chance and caps pity at 10. Easy drops the trophy on the first kill, then uses the normal rate. The host rolls these.", Array.Empty<GUILayoutOption>());
		GUILayout.Space(4f);
		GUILayout.Label("Research costs", Array.Empty<GUILayoutOption>());
		int num3 = GUILayout.SelectionGrid(_researchCosts, ValheimRandomizer.ResearchCostLabels, 2, Array.Empty<GUILayoutOption>());
		if (num3 != _researchCosts)
		{
			_researchCosts = num3;
			ValheimRandomizer.SetResearchCosts((ValheimRandomizer.ResearchCostMode)num3);
		}
		GUILayout.Label("Halved spends half, rounded up, and never less than 1.", Array.Empty<GUILayoutOption>());
		GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
		GUILayout.Label("Host", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) });
		_host = GUILayout.TextField(_host ?? "", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(220f) });
		GUILayout.Label("Port", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(40f) });
		if (int.TryParse(GUILayout.TextField((_port <= 0) ? "" : _port.ToString(), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) }), out var result))
		{
			_port = result;
		}
		GUILayout.EndHorizontal();
		GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
		GUILayout.Label("Slot", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) });
		_slot = GUILayout.TextField(_slot ?? "", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(140f) });
		GUILayout.Label("Password", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) });
		_password = GUILayout.PasswordField(_password ?? "", '*', (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(140f) });
		GUILayout.EndHorizontal();
		GUILayout.Space(6f);
		GUILayout.Label("Status: " + _status, Array.Empty<GUILayoutOption>());
		GUILayout.Label(ArchipelagoConnection.DropChecksEnabled ? "Drop checks: on for this seed" : "Drop checks: off unless the seed enables them", Array.Empty<GUILayoutOption>());
		GUILayout.Label(ValheimRandomizer.SequentialTiers ? "Sequential tiers: on for this seed" : "Sequential tiers: off unless the seed enables them", Array.Empty<GUILayoutOption>());
		GUILayout.Space(6f);
		GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
		bool isConnected = ArchipelagoConnection.IsConnected;
		GUI.enabled = !_connecting && !isConnected && ValidInputs();
		if (GUILayout.Button("Connect", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(28f) }))
		{
			SaveInputs();
			StartConnect();
		}
		GUI.enabled = isConnected && !_connecting;
		if (GUILayout.Button("Disconnect", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(28f) }))
		{
			Disconnect();
		}
		GUI.enabled = true;
		GUILayout.FlexibleSpace();
		GUILayout.EndHorizontal();
		GUILayout.Space(6f);
		GUILayout.EndVertical();
		GUI.DragWindow(new Rect(0f, 0f, 10000f, 20f));
	}

	private bool ValidInputs()
	{
		if (!string.IsNullOrWhiteSpace(_host) && _port > 0 && _port < 65536)
		{
			return !string.IsNullOrWhiteSpace(_slot);
		}
		return false;
	}

	private void SaveInputs()
	{
		ValheimRandomizer.randomized.Value = _randomized;
		ValheimRandomizer.archipelagoHostname.Value = _host ?? "";
		ValheimRandomizer.archipelagoPort.Value = _port;
		ValheimRandomizer.archipelagoSlot.Value = _slot ?? "";
		ValheimRandomizer.archipelagoPassword.Value = _password ?? "";
	}

	private async void StartConnect()
	{
		if (_connecting || ArchipelagoConnection.IsConnected)
		{
			return;
		}
		_connecting = true;
		_status = "Connecting...";
		string host = _host;
		int port = _port;
		string slot = _slot;
		string password = _password;
		try
		{
			await Task.Run(delegate
			{
				ArchipelagoConnection.Connect(host, port, slot, password);
			});
			_status = "Connected";
			ValheimRandomizer.RevealEligibleRecipes();
		}
		catch (Exception ex)
		{
			_status = "Error: " + ex.Message;
			ValheimRandomizer.Log.LogError((object)$"[AP] Connect error: {ex}");
		}
		finally
		{
			_connecting = false;
		}
	}

	private void Disconnect()
	{
		ArchipelagoConnection.Disconnect();
		_status = "Not connected";
		ValheimRandomizer.RevealEligibleRecipes();
	}
}
public static class Patches
{
	[HarmonyPatch]
	private static class Player_HaveRequirements_Piece_Patch
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			return (from m in typeof(Player).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
				where m.Name == "HaveRequirements"
				select m).Where(delegate(MethodInfo m)
			{
				ParameterInfo[] parameters = m.GetParameters();
				return parameters.Length != 0 && parameters[0].ParameterType == typeof(Piece);
			});
		}

		private static bool Prefix(Piece piece, ref bool __result)
		{
			if ((Object)(object)piece == (Object)null)
			{
				return true;
			}
			string name = ((Object)piece).name;
			if (!string.IsNullOrEmpty(name) && RandomizerUtils.CanResearchBeCrafted(name))
			{
				ManualLogSource log = ValheimRandomizer.Log;
				if (log != null)
				{
					log.LogDebug((object)("Blocked building '" + name + "' (missing research)."));
				}
				__result = false;
				return false;
			}
			return true;
		}
	}

	[HarmonyPatch]
	private static class Player_HaveRequirements_Recipe_Patch
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			return (from m in typeof(Player).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
				where m.Name == "HaveRequirements"
				select m).Where(delegate(MethodInfo m)
			{
				ParameterInfo[] parameters = m.GetParameters();
				return parameters.Length != 0 && parameters[0].ParameterType == typeof(Recipe);
			});
		}

		private static bool Prefix(Recipe recipe, ref bool __result)
		{
			if ((Object)(object)recipe == (Object)null)
			{
				return true;
			}
			string text = null;
			try
			{
				text = (Object.op_Implicit((Object)(object)recipe.m_item) ? ((Object)recipe.m_item).name : null);
			}
			catch
			{
			}
			if (!string.IsNullOrEmpty(text) && RandomizerUtils.CanResearchBeCrafted(text))
			{
				ManualLogSource log = ValheimRandomizer.Log;
				if (log != null)
				{
					log.LogDebug((object)("Blocked crafting '" + text + "' (missing research)."));
				}
				__result = false;
				return false;
			}
			return true;
		}
	}

	[HarmonyPatch(typeof(InventoryGui), "UpdateRecipeList", new Type[] { typeof(List<Recipe>) })]
	public static class InventoryGui_UpdateRecipeList_Patch
	{
		private static bool Prefix(InventoryGui __instance, List<Recipe> recipes)
		{
			try
			{
				ResearchBenchList.ApplySortWeights();
				bool flag = ResearchBenchList.IsOpen();
				ResearchBenchList.SetUpgradeTabLabel(__instance, flag);
				if (!flag)
				{
					return true;
				}
				if (__instance.InCraftTab())
				{
					recipes.RemoveAll(ResearchBenchList.IsCompleted);
					return true;
				}
				return !ResearchBenchList.FillDoneTab(__instance);
			}
			catch (Exception ex)
			{
				ManualLogSource log = ValheimRandomizer.Log;
				if (log != null)
				{
					log.LogWarning((object)("Research list update failed: " + ex.Message));
				}
				return true;
			}
		}
	}

	[HarmonyPatch(typeof(InventoryGui), "UpdateRecipe")]
	public static class InventoryGui_UpdateRecipe_Patch
	{
		private static void Postfix(InventoryGui __instance)
		{
			try
			{
				bool flag = ResearchBenchList.IsOpen();
				ResearchBenchList.SetUpgradeTabLabel(__instance, flag);
				if (flag && !__instance.InCraftTab())
				{
					ResearchBenchList.LockCompletedButton(__instance);
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = ValheimRandomizer.Log;
				if (log != null)
				{
					log.LogWarning((object)("Research bench tab label failed: " + ex.Message));
				}
			}
		}
	}

	private static class ResearchBenchList
	{
		private const string DoneLabel = "Done";

		private static readonly FieldInfo AvailableRecipesField = AccessTools.Field(typeof(InventoryGui), "m_availableRecipes");

		private static readonly FieldInfo SelectedRecipeField = AccessTools.Field(typeof(InventoryGui), "m_selectedRecipe");

		private static readonly FieldRef<InventoryGui, float> ListBaseSize = AccessTools.FieldRefAccess<InventoryGui, float>("m_recipeListBaseSize");

		private static readonly MethodInfo AddRecipeToListMethod = AccessTools.Method(typeof(InventoryGui), "AddRecipeToList", new Type[4]
		{
			typeof(Player),
			typeof(Recipe),
			typeof(ItemData),
			typeof(bool)
		}, (Type[])null);

		private static readonly MethodInfo UpdateCraftingPanelMethod = AccessTools.Method(typeof(InventoryGui), "UpdateCraftingPanel", new Type[1] { typeof(bool) }, (Type[])null);

		private static string savedUpgradeLabel;

		private static bool weightsApplied;

		private static Dictionary<string, Recipe> researchRecipes;

		private static TMP_Text[] upgradeTabLabels;

		private static TMP_Text craftButtonLabel;

		private static FieldInfo craftTooltipText;

		private static MonoBehaviour craftTooltipTarget;

		public static void RefreshOpenList()
		{
			if (IsOpen() && !(UpdateCraftingPanelMethod == null))
			{
				InventoryGui instance = InventoryGui.instance;
				if (Object.op_Implicit((Object)(object)instance))
				{
					UpdateCraftingPanelMethod.Invoke(instance, new object[1] { false });
				}
			}
		}

		public static bool IsOpen()
		{
			Player localPlayer = Player.m_localPlayer;
			CraftingStation val = (Object.op_Implicit((Object)(object)localPlayer) ? localPlayer.GetCurrentCraftingStation() : null);
			if (!Object.op_Implicit((Object)(object)val))
			{
				return false;
			}
			if (val.m_name == "Research Bench")
			{
				return true;
			}
			string text = (Object.op_Implicit((Object)(object)((Component)val).gameObject) ? ((Object)((Component)val).gameObject).name : null);
			if (!(text == "piece_randomizer_researchbench"))
			{
				return text?.StartsWith("piece_randomizer_researchbench", StringComparison.Ordinal) ?? false;
			}
			return true;
		}

		public static bool IsCompleted(Recipe recipe)
		{
			string text = (Object.op_Implicit((Object)(object)recipe?.m_item) ? ((Object)recipe.m_item).name : null);
			if (!string.IsNullOrEmpty(text) && ValheimRandomizer.researchTier.ContainsKey(text))
			{
				return ValheimRandomizer.IsResearchCrafted(text);
			}
			return false;
		}

		public static void ApplySortWeights()
		{
			if (weightsApplied)
			{
				return;
			}
			List<Recipe> list = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.m_recipes : null);
			if (list == null)
			{
				return;
			}
			int num = 0;
			foreach (Recipe item in list)
			{
				string text = (Object.op_Implicit((Object)(object)item?.m_item) ? ((Object)item.m_item).name : null);
				if (!string.IsNullOrEmpty(text) && ValheimRandomizer.researchTier.TryGetValue(text, out var value))
				{
					num++;
					if (item.m_listSortWeight != value)
					{
						item.m_listSortWeight = value;
					}
				}
			}
			if (num > 0 && num >= ValheimRandomizer.research.Count)
			{
				weightsApplied = true;
			}
		}

		public static bool FillDoneTab(InventoryGui gui)
		{
			if (AddRecipeToListMethod == null || AvailableRecipesField == null || (Object)(object)gui.m_recipeListRoot == (Object)null)
			{
				return false;
			}
			if (!(AvailableRecipesField.GetValue(gui) is IList list))
			{
				return false;
			}
			list.Clear();
			RectTransform recipeListRoot = gui.m_recipeListRoot;
			for (int num = ((Transform)recipeListRoot).childCount - 1; num >= 0; num--)
			{
				Object.Destroy((Object)(object)((Component)((Transform)recipeListRoot).GetChild(num)).gameObject);
			}
			EnsureRecipeIndex();
			List<Recipe> list2 = new List<Recipe>();
			if (researchRecipes != null)
			{
				foreach (string item in ValheimRandomizer.research)
				{
					if (ValheimRandomizer.IsResearchCrafted(item) && researchRecipes.TryGetValue(item, out var value))
					{
						list2.Add(value);
					}
				}
			}
			list2.Sort(CompareTierThenName);
			Player localPlayer = Player.m_localPlayer;
			foreach (Recipe item2 in list2)
			{
				AddRecipeToListMethod.Invoke(gui, new object[4] { localPlayer, item2, null, true });
			}
			float num2 = Mathf.Max(ListBaseSize.Invoke(gui), (float)list2.Count * gui.m_recipeListSpace);
			gui.m_recipeListRoot.SetSizeWithCurrentAnchors((Axis)1, num2);
			return true;
		}

		public static void LockCompletedButton(InventoryGui gui)
		{
			if ((Object)(object)gui.m_craftButton == (Object)null || SelectedRecipeField == null)
			{
				return;
			}
			object value = SelectedRecipeField.GetValue(gui);
			object? obj = value?.GetType().GetProperty("Recipe")?.GetValue(value);
			if (IsCompleted((Recipe)((obj is Recipe) ? obj : null)))
			{
				((Selectable)gui.m_craftButton).interactable = false;
				if ((Object)(object)craftButtonLabel == (Object)null)
				{
					craftButtonLabel = ((Component)gui.m_craftButton).GetComponentInChildren<TMP_Text>();
				}
				if ((Object)(object)craftButtonLabel != (Object)null && craftButtonLabel.text != "Done")
				{
					craftButtonLabel.text = "Done";
				}
				ClearCraftTooltip((Component)(object)gui.m_craftButton);
			}
		}

		public static void SetUpgradeTabLabel(InventoryGui gui, bool doneTab)
		{
			if ((Object)(object)gui?.m_tabUpgrade == (Object)null)
			{
				return;
			}
			if (upgradeTabLabels == null || upgradeTabLabels.Length == 0)
			{
				TMP_Text[] componentsInChildren = ((Component)gui.m_tabUpgrade).GetComponentsInChildren<TMP_Text>(true);
				if (componentsInChildren != null && componentsInChildren.Length != 0)
				{
					upgradeTabLabels = componentsInChildren;
				}
			}
			TMP_Text[] array = upgradeTabLabels;
			if (array == null || array.Length == 0)
			{
				return;
			}
			TMP_Text[] array2;
			if (savedUpgradeLabel == null)
			{
				array2 = array;
				foreach (TMP_Text val in array2)
				{
					if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(val.text) && !(val.text == "Done"))
					{
						savedUpgradeLabel = val.text;
						break;
					}
				}
			}
			string text = (doneTab ? "Done" : (savedUpgradeLabel ?? "Upgrade"));
			array2 = array;
			foreach (TMP_Text val2 in array2)
			{
				if (!((Object)(object)val2 == (Object)null) && !(val2.text == text) && (savedUpgradeLabel == null || !(val2.text != savedUpgradeLabel) || !(val2.text != "Done")))
				{
					val2.text = text;
				}
			}
		}

		private static void EnsureRecipeIndex()
		{
			if (researchRecipes != null && researchRecipes.Count > 0)
			{
				return;
			}
			List<Recipe> list = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.m_recipes : null);
			if (list == null)
			{
				return;
			}
			researchRecipes = new Dictionary<string, Recipe>(StringComparer.Ordinal);
			foreach (Recipe item in list)
			{
				string text = (Object.op_Implicit((Object)(object)item?.m_item) ? ((Object)item.m_item).name : null);
				if (!string.IsNullOrEmpty(text) && !researchRecipes.ContainsKey(text) && ValheimRandomizer.researchTier.ContainsKey(text))
				{
					researchRecipes.Add(text, item);
				}
			}
			if (researchRecipes.Count == 0)
			{
				researchRecipes = null;
			}
		}

		private static void ClearCraftTooltip(Component button)
		{
			if ((Object)(object)button == (Object)null)
			{
				return;
			}
			if (craftTooltipText == null)
			{
				MonoBehaviour[] components = button.GetComponents<MonoBehaviour>();
				foreach (MonoBehaviour val in components)
				{
					if (!((Object)(object)val == (Object)null) && !(((object)val).GetType().Name != "UITooltip"))
					{
						FieldInfo field = ((object)val).GetType().GetField("m_text");
						if (field != null && field.FieldType == typeof(string))
						{
							craftTooltipText = field;
							craftTooltipTarget = val;
							break;
						}
					}
				}
			}
			if (craftTooltipText != null && (Object)(object)craftTooltipTarget != (Object)null)
			{
				craftTooltipText.SetValue(craftTooltipTarget, "");
			}
		}

		private static int CompareTierThenName(Recipe a, Recipe b)
		{
			int num = TierOf(a).CompareTo(TierOf(b));
			if (num != 0)
			{
				return num;
			}
			return string.Compare(DisplayName(a), DisplayName(b), StringComparison.OrdinalIgnoreCase);
		}

		private static int TierOf(Recipe recipe)
		{
			string text = (Object.op_Implicit((Object)(object)recipe?.m_item) ? ((Object)recipe.m_item).name : null);
			if (!string.IsNullOrEmpty(text) && ValheimRandomizer.researchTier.TryGetValue(text, out var value))
			{
				return value;
			}
			if (!((Object)(object)recipe != (Object)null))
			{
				return 0;
			}
			return recipe.m_listSortWeight;
		}

		private static string DisplayName(Recipe recipe)
		{
			return recipe?.m_item?.m_itemData?.m_shared?.m_name ?? "";
		}
	}

	[HarmonyPatch(typeof(Player), "GetMaxCarryWeight")]
	private static class Player_GetMaxCarryWeight_Patch
	{
		private static void Postfix(Player __instance, ref float __result)
		{
			if (!((Object)(object)__instance == (Object)null) && ((Character)__instance).IsPlayer())
			{
				int boost = ValheimRandomizer.GetBoost(ValheimRandomizer.TrophyResearch.TrophyBoost.Load);
				if (boost > 0)
				{
					__result += 10 * boost;
				}
			}
		}
	}

	[HarmonyPatch]
	internal static class Player_GetTotalFoodValue_Patch
	{
		private static readonly FieldRef<Player, List<Food>> FoodsRef = AccessTools.FieldRefAccess<Player, List<Food>>("m_foods");

		private static MethodBase TargetMethod()
		{
			Type[] array = new Type[3]
			{
				typeof(float).MakeByRefType(),
				typeof(float).MakeByRefType(),
				typeof(float).MakeByRefType()
			};
			return AccessTools.Method(typeof(Player), "GetTotalFoodValue", array, (Type[])null);
		}

		private static bool Prepare()
		{
			if (TargetMethod() != null)
			{
				return FoodsRef != null;
			}
			return false;
		}

		private static void Postfix(Player __instance, ref float hp, ref float stamina, ref float eitr)
		{
			if ((Object)(object)__instance == (Object)null)
			{
				return;
			}
			int num = FoodsRef.Invoke(__instance)?.Count ?? 0;
			if (num > 0)
			{
				int boost = ValheimRandomizer.GetBoost(ValheimRandomizer.TrophyResearch.TrophyBoost.HP);
				if (boost > 0)
				{
					hp += 2 * num * boost;
				}
				int boost2 = ValheimRandomizer.GetBoost(ValheimRandomizer.TrophyResearch.TrophyBoost.Stamina);
				if (boost2 > 0)
				{
					stamina += 2 * num * boost2;
				}
				int boost3 = ValheimRandomizer.GetBoost(ValheimRandomizer.TrophyResearch.TrophyBoost.Eitr);
				if (boost3 > 0)
				{
					eitr += 5 * num * boost3;
				}
			}
		}
	}

	[HarmonyPatch]
	internal static class Player_AddAdrenaline_Patch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.Method(typeof(Player), "AddAdrenaline", new Type[1] { typeof(float) }, (Type[])null);
		}

		private static bool Prepare()
		{
			return TargetMethod() != null;
		}

		private static void Prefix(Player __instance, ref float v)
		{
			if (!((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer))
			{
				int boost = ValheimRandomizer.GetBoost(ValheimRandomizer.TrophyResearch.TrophyBoost.Adrenaline);
				if (boost > 0)
				{
					v += 3 * boost;
				}
			}
		}
	}

	internal static class SEManPatchHelpers
	{
		public static readonly FieldRef<SEMan, Character> OwnerRef = AccessTools.FieldRefAccess<SEMan, Character>("m_character");

		public static readonly FieldRef<Player, List<Food>> FoodsRef = AccessTools.FieldRefAccess<Player, List<Food>>("m_foods");

		public static bool IsLocalPlayer(SEMan se)
		{
			Character obj = ((OwnerRef != null) ? OwnerRef.Invoke(se) : null);
			Player val = (Player)(object)((obj is Player) ? obj : null);
			if (val != null)
			{
				return (Object)(object)val == (Object)(object)Player.m_localPlayer;
			}
			return false;
		}

		public static int GetActiveFoodCount(Player p)
		{
			if (FoodsRef == null || (Object)(object)p == (Object)null)
			{
				return 0;
			}
			return FoodsRef.Invoke(p)?.Count ?? 0;
		}
	}

	[HarmonyPatch]
	internal static class SEMan_ModifyHealthRegen_RefOnly_Patch
	{
		private static MethodBase TargetMethod()
		{
			Type[] array = new Type[1] { typeof(float).MakeByRefType() };
			return AccessTools.Method(typeof(SEMan), "ModifyHealthRegen", array, (Type[])null);
		}

		private static bool Prepare()
		{
			if (TargetMethod() != null && SEManPatchHelpers.OwnerRef != null)
			{
				return SEManPatchHelpers.FoodsRef != null;
			}
			return false;
		}

		private static void Postfix(SEMan __instance, ref float regenMultiplier)
		{
			if (!SEManPatchHelpers.IsLocalPlayer(__instance))
			{
				return;
			}
			int activeFoodCount = SEManPatchHelpers.GetActiveFoodCount(Player.m_localPlayer);
			if (activeFoodCount > 0)
			{
				int boost = ValheimRandomizer.GetBoost(ValheimRandomizer.TrophyResearch.TrophyBoost.HPRegen);
				if (boost > 0)
				{
					regenMultiplier += 0.5f * (float)activeFoodCount * (float)boost;
				}
			}
		}
	}

	[HarmonyPatch]
	internal static class SEMan_ModifyStaminaRegen_RefOnly_Patch
	{
		private static MethodBase TargetMethod()
		{
			Type[] array = new Type[1] { typeof(float).MakeByRefType() };
			return AccessTools.Method(typeof(SEMan), "ModifyStaminaRegen", array, (Type[])null);
		}

		private static bool Prepare()
		{
			if (TargetMethod() != null && SEManPatchHelpers.OwnerRef != null)
			{
				return SEManPatchHelpers.FoodsRef != null;
			}
			return false;
		}

		private static void Postfix(SEMan __instance, ref float staminaMultiplier)
		{
			if (!SEManPatchHelpers.IsLocalPlayer(__instance))
			{
				return;
			}
			int activeFoodCount = SEManPatchHelpers.GetActiveFoodCount(Player.m_localPlayer);
			if (activeFoodCount > 0)
			{
				int boost = ValheimRandomizer.GetBoost(ValheimRandomizer.TrophyResearch.TrophyBoost.StaminaRegen);
				if (boost > 0)
				{
					staminaMultiplier += 0.5f * (float)activeFoodCount * (float)boost;
				}
			}
		}
	}

	[HarmonyPatch]
	internal static class SEMan_ModifyEitrRegen_RefOnly_Patch
	{
		private static MethodBase TargetMethod()
		{
			Type[] array = new Type[1] { typeof(float).MakeByRefType() };
			return AccessTools.Method(typeof(SEMan), "ModifyEitrRegen", array, (Type[])null);
		}

		private static bool Prepare()
		{
			if (TargetMethod() != null && SEManPatchHelpers.OwnerRef != null)
			{
				return SEManPatchHelpers.FoodsRef != null;
			}
			return false;
		}

		private static void Postfix(SEMan __instance, ref float eitrMultiplier)
		{
			if (!SEManPatchHelpers.IsLocalPlayer(__instance))
			{
				return;
			}
			int activeFoodCount = SEManPatchHelpers.GetActiveFoodCount(Player.m_localPlayer);
			if (activeFoodCount > 0)
			{
				int boost = ValheimRandomizer.GetBoost(ValheimRandomizer.TrophyResearch.TrophyBoost.EitrRegen);
				if (boost > 0)
				{
					eitrMultiplier += 0.5f * (float)activeFoodCount * (float)boost;
				}
			}
		}
	}

	[HarmonyPatch(typeof(Player), "GetJogSpeedFactor")]
	internal static class Player_GetJogSpeedFactor_Patch
	{
		private static bool Prepare()
		{
			return AccessTools.Method(typeof(Player), "GetJogSpeedFactor", (Type[])null, (Type[])null) != null;
		}

		private static void Postfix(Player __instance, ref float __result)
		{
			ApplyMoveSpeed(__instance, ref __result);
		}
	}

	[HarmonyPatch(typeof(Player), "GetRunSpeedFactor")]
	internal static class Player_GetRunSpeedFactor_Patch
	{
		private static bool Prepare()
		{
			return AccessTools.Method(typeof(Player), "GetRunSpeedFactor", (Type[])null, (Type[])null) != null;
		}

		private static void Postfix(Player __instance, ref float __result)
		{
			ApplyMoveSpeed(__instance, ref __result);
		}
	}

	[HarmonyPatch]
	internal static class SEMan_ModifyRaiseSkill_Patch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.Method(typeof(SEMan), "ModifyRaiseSkill", new Type[2]
			{
				typeof(SkillType),
				typeof(float).MakeByRefType()
			}, (Type[])null);
		}

		private static bool Prepare()
		{
			if (TargetMethod() != null)
			{
				return SEManPatchHelpers.OwnerRef != null;
			}
			return false;
		}

		private static void Postfix(SEMan __instance, SkillType skill, ref float multiplier)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			if ((int)skill != 0 && SEManPatchHelpers.IsLocalPlayer(__instance))
			{
				int boost = ValheimRandomizer.GetBoost(ValheimRandomizer.TrophyResearch.TrophyBoost.SkillXp);
				if (boost > 0)
				{
					multiplier += 0.25f * (float)boost;
				}
			}
		}
	}

	[HarmonyPatch(typeof(Player), "GetBodyArmor")]
	internal static class Player_GetBodyArmor_DefenseBoost_Patch
	{
		private static void Postfix(Player __instance, ref float __result)
		{
			if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer))
			{
				int boost = ValheimRandomizer.GetBoost(ValheimRandomizer.TrophyResearch.TrophyBoost.Defense);
				if (boost > 0)
				{
					float num = 1f + 0.05f * (float)boost;
					__result *= num;
				}
			}
		}
	}

	[HarmonyPatch(typeof(Character), "Damage")]
	internal static class Character_Damage_AttackBoost_Patch
	{
		private static void Prefix(Character __instance, HitData hit)
		{
			if (hit == null)
			{
				return;
			}
			Character attacker = hit.GetAttacker();
			Player val = (Player)(object)((attacker is Player) ? attacker : null);
			if (val != null && !((Object)(object)val != (Object)(object)Player.m_localPlayer))
			{
				int boost = ValheimRandomizer.GetBoost(ValheimRandomizer.TrophyResearch.TrophyBoost.Attack);
				if (boost > 0)
				{
					float mul = 1f + 0.05f * (float)boost;
					ScaleDamageTypes(ref hit.m_damage, mul);
				}
			}
		}

		private static void ScaleDamageTypes(ref DamageTypes dmg, float mul)
		{
			dmg.m_damage *= mul;
			dmg.m_blunt *= mul;
			dmg.m_slash *= mul;
			dmg.m_pierce *= mul;
			dmg.m_fire *= mul;
			dmg.m_frost *= mul;
			dmg.m_lightning *= mul;
			dmg.m_poison *= mul;
			dmg.m_spirit *= mul;
			dmg.m_pickaxe *= mul;
			dmg.m_chop *= mul;
		}
	}

	[HarmonyPatch(typeof(TextsDialog), "AddActiveEffects")]
	private static class TextsDialog_AddActiveEffects_Patch
	{
		private static readonly FieldRef<TextsDialog, List<TextInfo>> TextsRef = AccessTools.FieldRefAccess<TextsDialog, List<TextInfo>>("m_texts");

		private static void Prefix(TextsDialog __instance, out int __state)
		{
			__state = TextsRef.Invoke(__instance)?.Count ?? (-1);
		}

		private static void Postfix(TextsDialog __instance, int __state)
		{
			if (__state < 0)
			{
				return;
			}
			List<TextInfo> list = TextsRef.Invoke(__instance);
			if (list == null || list.Count != __state + 1 || list.Count == 0)
			{
				return;
			}
			string text = ValheimRandomizer.BuildTrophyStatusBlock();
			if (!string.IsNullOrEmpty(text))
			{
				TextInfo val = list[0];
				if (val != null)
				{
					val.m_text = (string.IsNullOrEmpty(val.m_text) ? text : (val.m_text.TrimEnd(Array.Empty<char>()) + "\n\n" + text));
				}
			}
		}
	}

	[HarmonyPatch(typeof(CharacterDrop), "GenerateDropList")]
	private static class CharacterDrop_Pity_Patch
	{
		private static void Postfix()
		{
			ValheimRandomizer.MarkTrophyPityDirty();
		}
	}

	[HarmonyPatch(typeof(CharacterDrop), "GenerateDropList")]
	private static class CharacterDrop_GenerateDropList_Patch
	{
		private static void Postfix(List<KeyValuePair<GameObject, int>> __result)
		{
			if (ArchipelagoConnection.BossDropsEnabled && __result != null && __result.Count != 0)
			{
				__result.RemoveAll((KeyValuePair<GameObject, int> pair) => (Object)(object)pair.Key != (Object)null && ValheimRandomizer.IsBossDropPrefab(((Object)pair.Key).name));
			}
		}
	}

	[HarmonyPatch(typeof(CharacterDrop), "GenerateDropList")]
	private static class CharacterDrop_TrophyHelp_Patch
	{
		private const int PityCap = 10;

		private static void Prefix(CharacterDrop __instance, out float[] __state)
		{
			__state = null;
			if (ValheimRandomizer.ActiveTrophyDrops != ValheimRandomizer.TrophyDropMode.Double || (Object)(object)__instance == (Object)null || __instance.m_drops == null)
			{
				return;
			}
			float[] array = new float[__instance.m_drops.Count];
			bool flag = false;
			for (int i = 0; i < __instance.m_drops.Count; i++)
			{
				Drop val = __instance.m_drops[i];
				array[i] = val?.m_chance ?? 0f;
				if (IsTrophy(val))
				{
					flag = true;
					val.m_chance *= 2f;
					ClampPity(val);
				}
			}
			if (flag)
			{
				__state = array;
			}
		}

		private static void Postfix(CharacterDrop __instance, List<KeyValuePair<GameObject, int>> __result, float[] __state)
		{
			if (__state != null && (Object)(object)__instance != (Object)null && __instance.m_drops != null)
			{
				int num = Math.Min(__state.Length, __instance.m_drops.Count);
				for (int i = 0; i < num; i++)
				{
					Drop val = __instance.m_drops[i];
					if (IsTrophy(val))
					{
						ClampPity(val);
						val.m_chance = __state[i];
					}
				}
			}
			if (ValheimRandomizer.ActiveTrophyDrops == ValheimRandomizer.TrophyDropMode.Easy)
			{
				ForceFirstTrophy(__instance, __result);
			}
		}

		private static bool IsTrophy(Drop drop)
		{
			if (drop != null && (Object)(object)drop.m_prefab != (Object)null)
			{
				return ValheimRandomizer.trophyByItemID.ContainsKey(((Object)drop.m_prefab).name);
			}
			return false;
		}

		private static void ClampPity(Drop drop)
		{
			if (!((Object)(object)drop.m_prefab == (Object)null) && CharacterDrop.s_pseudoCounter != null)
			{
				string name = ((Object)drop.m_prefab).name;
				ClampOne(name, drop.m_chance);
				if (drop.m_levelMultiplier)
				{
					ClampOne(name, drop.m_chance * 2f);
					ClampOne(name, drop.m_chance * 4f);
				}
			}
		}

		private static void ClampOne(string name, float chance)
		{
			if (!(chance <= 0f) && !(chance > 0.3f))
			{
				int key = StringExtensionMethods.GetStableHashCode(name) + (int)(chance * 1000f);
				if (CharacterDrop.s_pseudoCounter.TryGetValue(key, out var value) && value > 10)
				{
					CharacterDrop.s_pseudoCounter[key] = 10;
				}
			}
		}

		private static void ForceFirstTrophy(CharacterDrop dropper, List<KeyValuePair<GameObject, int>> result)
		{
			if ((Object)(object)dropper == (Object)null || dropper.m_drops == null || result == null || (Object)(object)ZoneSystem.instance == (Object)null)
			{
				return;
			}
			for (int i = 0; i < dropper.m_drops.Count; i++)
			{
				Drop val = dropper.m_drops[i];
				if (!IsTrophy(val))
				{
					continue;
				}
				string text = "ap_trophy_first_" + ((Object)val.m_prefab).name;
				if (ZoneSystem.instance.GetGlobalKey(text))
				{
					continue;
				}
				ZoneSystem.instance.SetGlobalKey(text);
				bool flag = false;
				for (int j = 0; j < result.Count; j++)
				{
					if ((Object)(object)result[j].Key == (Object)(object)val.m_prefab)
					{
						flag = true;
						break;
					}
				}
				if (!flag)
				{
					result.Add(new KeyValuePair<GameObject, int>(val.m_prefab, 1));
				}
			}
		}
	}

	public static void RefreshResearchBench()
	{
		try
		{
			ResearchBenchList.RefreshOpenList();
		}
		catch (Exception ex)
		{
			ManualLogSource log = ValheimRandomizer.Log;
			if (log != null)
			{
				log.LogWarning((object)("Research bench refresh failed: " + ex.Message));
			}
		}
	}

	private static void ApplyMoveSpeed(Player player, ref float factor)
	{
		if (!((Object)(object)player != (Object)(object)Player.m_localPlayer))
		{
			int boost = ValheimRandomizer.GetBoost(ValheimRandomizer.TrophyResearch.TrophyBoost.Speed);
			if (boost > 0)
			{
				factor *= 1f + 0.05f * (float)boost;
			}
		}
	}
}
public static class RandomizerUtils
{
	public static void RegisterResearchBench()
	{
		//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e9: Expected O, but got Unknown
		//IL_012c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0132: Expected O, but got Unknown
		//IL_013d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0143: Expected O, but got Unknown
		//IL_0153: Unknown result type (might be due to invalid IL or missing references)
		//IL_015d: Expected O, but got Unknown
		ValheimRandomizer.Log.LogInfo((object)"RegisterResearchBench: Vanilla prefabs available, cloning Forge…");
		GameObject val = PrefabManager.Instance.CreateClonedPrefab("piece_randomizer_researchbench", "forge");
		if (!Object.op_Implicit((Object)(object)val))
		{
			ValheimRandomizer.Log.LogError((object)"Failed to clone 'forge'");
			return;
		}
		(val.GetComponent<Piece>() ?? val.AddComponent<Piece>()).m_craftingStation = null;
		CraftingStation val2 = val.GetComponent<CraftingStation>() ?? val.AddComponent<CraftingStation>();
		GameObject prefab = PrefabManager.Instance.GetPrefab("forge");
		if ((Object)(object)prefab != (Object)null)
		{
			CraftingStation component = prefab.GetComponent<CraftingStation>();
			if ((Object)(object)component != (Object)null)
			{
				val2.m_name = "Research Bench";
				val2.m_useDistance = component.m_useDistance;
				val2.m_rangeBuild = component.m_rangeBuild;
				val2.m_areaMarker = component.m_areaMarker;
			}
		}
		val2.m_craftRequireRoof = false;
		val2.m_craftRequireFire = false;
		val2.m_hasCraftTab = true;
		val2.m_upgrader = false;
		ValheimRandomizer.ResearchBenchCraftingStation = val2;
		PieceConfig val3 = new PieceConfig();
		val3.Name = "Research Bench";
		val3.Description = "Randomizer Research Bench (Forge-like, own recipes)";
		val3.PieceTable = "Hammer";
		val3.Category = "Randomizer";
		val3.Requirements = (RequirementConfig[])(object)new RequirementConfig[2]
		{
			new RequirementConfig("Wood", 20, 0, true),
			new RequirementConfig("Stone", 10, 0, true)
		};
		PieceConfig val4 = val3;
		PieceManager.Instance.AddPiece(new CustomPiece(val, true, val4));
		ValheimRandomizer.Log.LogInfo((object)"Research Bench registered");
	}

	public static void CreateResearch(string iconFromItemPrefab, string researchItemID, string displayName, string description, string[] requiredResearchIDs, RequirementConfig[] researchRecipeRequirements, params string[] gatedItemPrefabIDs)
	{
		//IL_025d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0267: Expected O, but got Unknown
		//IL_0268: Unknown result type (might be due to invalid IL or missing references)
		//IL_026d: Unknown result type (might be due to invalid IL or missing references)
		//IL_027e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0285: Unknown result type (might be due to invalid IL or missing references)
		//IL_028c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0297: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a1: Expected O, but got Unknown
		//IL_02a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b2: Expected O, but got Unknown
		ValheimRandomizer.Log.LogInfo((object)("Registering research: " + displayName));
		if (!Object.op_Implicit((Object)(object)PrefabManager.Instance.GetPrefab("piece_randomizer_researchbench")))
		{
			ValheimRandomizer.Log.LogError((object)"Research bench 'piece_randomizer_researchbench' not found; aborting CreateResearchAndGateRecipes.");
			return;
		}
		string text = "Amber";
		if (!Object.op_Implicit((Object)(object)PrefabManager.Instance.GetPrefab(text)))
		{
			ValheimRandomizer.Log.LogError((object)"Base item 'Amber' missing; cannot clone research item.");
			return;
		}
		GameObject val = PrefabManager.Instance.CreateClonedPrefab(researchItemID, text);
		if (!Object.op_Implicit((Object)(object)val))
		{
			ValheimRandomizer.Log.LogError((object)("Failed to clone base item '" + text + "' as '" + researchItemID + "'."));
			return;
		}
		ItemDrop component = val.GetComponent<ItemDrop>();
		if ((Object)(object)component == (Object)null || component.m_itemData == null)
		{
			ValheimRandomizer.Log.LogError((object)"Cloned research item is missing ItemDrop/ItemData.");
			return;
		}
		component.m_itemData.m_shared.m_name = displayName;
		if (requiredResearchIDs != null && requiredResearchIDs.Length != 0)
		{
			description = description + "\r\n\r\nRequires: " + string.Join(", ", requiredResearchIDs);
		}
		ValheimRandomizer.researchBody[researchItemID] = description;
		ValheimRandomizer.researchUnlockPrefabs[researchItemID] = ((gatedItemPrefabIDs == null) ? Array.Empty<string>() : ((string[])gatedItemPrefabIDs.Clone()));
		component.m_itemData.m_shared.m_description = ValheimRandomizer.BuildResearchDescription(researchItemID);
		GameObject prefab = PrefabManager.Instance.GetPrefab(iconFromItemPrefab);
		if (Object.op_Implicit((Object)(object)prefab))
		{
			ItemDrop component2 = prefab.GetComponent<ItemDrop>();
			if (Object.op_Implicit((Object)(object)component2) && component2.m_itemData?.m_shared?.m_icons != null && component2.m_itemData.m_shared.m_icons.Length != 0)
			{
				component.m_itemData.m_shared.m_icons = (Sprite[])(object)new Sprite[1] { component2.m_itemData.m_shared.m_icons[0] };
			}
			else
			{
				ValheimRandomizer.Log.LogWarning((object)("Icon source '" + iconFromItemPrefab + "' has no icons; keeping cloned icon."));
			}
		}
		else
		{
			ValheimRandomizer.Log.LogWarning((object)("Icon source item '" + iconFromItemPrefab + "' not found; keeping cloned icon."));
		}
		component.m_itemData.m_shared.m_weight = 0.1f;
		component.m_itemData.m_shared.m_teleportable = true;
		component.m_itemData.m_shared.m_maxStackSize = 1;
		component.m_itemData.m_shared.m_maxQuality = 1;
		ItemManager.Instance.AddItem(new CustomItem(val, true));
		RecipeConfig val2 = new RecipeConfig
		{
			Name = "Recipe_" + researchItemID,
			Item = researchItemID,
			Amount = 1,
			CraftingStation = "piece_randomizer_researchbench",
			Requirements = researchRecipeRequirements
		};
		ItemManager.Instance.AddRecipe(new CustomRecipe(val2));
		ValheimRandomizer.Log.LogInfo((object)("Created research item '" + researchItemID + "' and its recipe at 'piece_randomizer_researchbench'."));
		ValheimRandomizer.AddResearchRequirement(researchItemID, requiredResearchIDs);
		for (int i = 0; i < gatedItemPrefabIDs.Length; i++)
		{
			ValheimRandomizer.AddResearchRequirement(gatedItemPrefabIDs[i], researchItemID);
		}
		ValheimRandomizer.research.Add(researchItemID);
		ValheimRandomizer.researchToArchipelago.Add(researchItemID, displayName);
		ValheimRandomizer.archipelagoToResearch.Add(displayName, researchItemID);
	}

	public static void CreateTrophyResearch(string itemID, string researchID, ValheimRandomizer.TrophyResearch.TrophyBoost boost)
	{
		ValheimRandomizer.TrophyResearch value = new ValheimRandomizer.TrophyResearch(itemID, researchID, boost);
		ValheimRandomizer.trophyByItemID[itemID] = value;
		ValheimRandomizer.trophyByResearchID[researchID] = value;
		ManualLogSource log = ValheimRandomizer.Log;
		if (log != null)
		{
			log.LogInfo((object)$"Registered trophy research: {itemID} -> {researchID} ({boost}).");
		}
		ValheimRandomizer.researchToArchipelago.Add(researchID, "Trophy: " + itemID);
		ValheimRandomizer.archipelagoToResearch.Add("Trophy: " + itemID, researchID);
	}

	public static bool CanResearchBeCrafted(string itemID)
	{
		if (ValheimRandomizer.research.Contains(itemID))
		{
			if (ValheimRandomizer.IsResearchCrafted(itemID))
			{
				return true;
			}
			return !ValheimRandomizer.ResearchRecipeVisible(itemID);
		}
		foreach (string item in ValheimRandomizer.GetResearchRequired(itemID))
		{
			if (!ValheimRandomizer.IsResearchUnlocked(item))
			{
				return true;
			}
		}
		return false;
	}
}
[BepInPlugin("com.samupo.randomizer", "Randomizer", "0.2.35")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class ValheimRandomizer : BaseUnityPlugin
{
	public enum ResearchAccessMode
	{
		Default,
		Tier,
		Materials,
		Natural
	}

	public enum TrophyDropMode
	{
		Default,
		Double,
		Easy
	}

	public enum ResearchCostMode
	{
		Default,
		Halved
	}

	public class TrophyResearch
	{
		public enum TrophyBoost
		{
			Load,
			HP,
			Stamina,
			Eitr,
			HPRegen,
			StaminaRegen,
			Attack,
			Defense,
			Adrenaline,
			EitrRegen,
			Speed,
			SkillXp
		}

		public const int LoadPerTrophy = 10;

		public const int HpPerFood = 2;

		public const int StaminaPerFood = 2;

		public const int EitrPerFood = 5;

		public const int RegenPercentPerFood = 50;

		public const int AdrenalinePerTrophy = 3;

		public const int AttackPercentPerTrophy = 5;

		public const int DefensePercentPerTrophy = 5;

		public const int SpeedPercentPerTrophy = 5;

		public const int SkillXpPercentPerTrophy = 25;

		public string itemID;

		public string researchID;

		public TrophyBoost boost;

		public TrophyResearch(string itemID, string researchID, TrophyBoost boost)
		{
			this.itemID = itemID;
			this.researchID = researchID;
			this.boost = boost;
		}
	}

	public class DropCheck
	{
		public string itemID;

		public string locationName;

		public int amount;

		public DropCheck(string itemID, string locationName, int amount)
		{
			this.itemID = itemID;
			this.locationName = locationName;
			this.amount = amount;
		}
	}

	public class BossDrop
	{
		public string itemID;

		public string itemName;

		public string locationName;

		public string defeatKey;

		public string creature;

		public int amount;

		public bool onePerPlayer;

		public BossDrop(string itemID, string itemName, string locationName, string defeatKey, string creature, int amount, bool onePerPlayer)
		{
			this.itemID = itemID;
			this.itemName = itemName;
			this.locationName = locationName;
			this.defeatKey = defeatKey;
			this.creature = creature;
			this.amount = amount;
			this.onePerPlayer = onePerPlayer;
		}
	}

	private class TrophyPityInfo
	{
		public float baseChance;

		public readonly List<PityChance> chances = new List<PityChance>();
	}

	private class PityChance
	{
		public float chance;

		public int key;

		public bool starred;
	}

	public const string ModGuid = "com.samupo.randomizer";

	public const string ModName = "Randomizer";

	public const string ModVersion = "0.2.35";

	internal static ConfigEntry<bool> randomized;

	internal static ConfigEntry<string> archipelagoHostname;

	internal static ConfigEntry<int> archipelagoPort;

	internal static ConfigEntry<string> archipelagoSlot;

	internal static ConfigEntry<string> archipelagoPassword;

	internal static ConfigEntry<bool> anyResearchMaterial;

	internal static ConfigEntry<bool> showResearchUnlocks;

	internal static ConfigEntry<string> researchAccess;

	internal static ConfigEntry<string> trophyDrops;

	internal static ConfigEntry<string> researchCosts;

	public static readonly string[] ResearchAccessLabels = new string[4] { "Default: parent chain", "Tier doors", "Materials only", "Natural: craft opens the next" };

	public static readonly string[] TrophyDropLabels = new string[3] { "Default", "x2", "Easy" };

	public static readonly string[] ResearchCostLabels = new string[2] { "Default", "Halved" };

	public static TrophyDropMode ActiveTrophyDrops = TrophyDropMode.Default;

	public static ResearchCostMode ActiveResearchCosts = ResearchCostMode.Default;

	private static readonly Dictionary<int, string[]> TierDoorItems = new Dictionary<int, string[]>
	{
		{
			1,
			new string[1] { "Crafting" }
		},
		{
			2,
			new string[5] { "Axes Tier 1", "Troll Armor", "Cooking Station", "Bed Tier 1", "Warmth Tier 1" }
		},
		{
			3,
			new string[4] { "Pickaxes Tier 1", "Boat Tier 1", "Smelter", "Forge" }
		},
		{
			4,
			new string[1] { "Bronze" }
		},
		{
			5,
			new string[1] { "Axes Tier 2" }
		},
		{
			6,
			new string[1] { "Pickaxes Tier 2" }
		},
		{
			7,
			new string[3] { "Pickaxes Tier 3", "Forge Upgrade 1", "Nails Tier 2" }
		},
		{
			8,
			new string[2] { "Artisan Table", "Blast Furnace" }
		},
		{
			9,
			new string[6] { "Axes Tier 4", "Wisp Fountain", "Wisplight", "Forge Upgrade 2", "Forge Upgrade 3", "Spinning Wheel" }
		},
		{
			10,
			new string[1] { "Pickaxes Tier 4" }
		},
		{
			11,
			new string[3] { "Ceramic Plates", "Boat Tier 4", "Artisan Press" }
		},
		{
			12,
			new string[2] { "Siege", "Snow Shovel" }
		}
	};

	public const string ResearchBenchPrefabName = "piece_randomizer_researchbench";

	private Player currentPlayer;

	public static List<string> research = new List<string>();

	public static Dictionary<string, List<string>> recipeRequirements = new Dictionary<string, List<string>>();

	public static Dictionary<string, TrophyResearch> trophyByItemID = new Dictionary<string, TrophyResearch>();

	public static Dictionary<string, TrophyResearch> trophyByResearchID = new Dictionary<string, TrophyResearch>(StringComparer.Ordinal);

	public static Dictionary<string, string> researchToArchipelago = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

	public static Dictionary<string, string> archipelagoToResearch = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

	public static Dictionary<string, int> researchTier = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

	public static Dictionary<string, string> researchBody = new Dictionary<string, string>(StringComparer.Ordinal);

	public static Dictionary<string, string[]> researchUnlockPrefabs = new Dictionary<string, string[]>(StringComparer.Ordinal);

	public static Dictionary<string, DropCheck> dropByItemID = new Dictionary<string, DropCheck>(StringComparer.OrdinalIgnoreCase);

	public static Dictionary<string, DropCheck> dropByLocationName = new Dictionary<string, DropCheck>(StringComparer.OrdinalIgnoreCase);

	public static Dictionary<string, BossDrop> bossByItemName = new Dictionary<string, BossDrop>(StringComparer.Ordinal);

	public static Dictionary<string, BossDrop> bossByDefeatKey = new Dictionary<string, BossDrop>(StringComparer.Ordinal);

	public static readonly List<BossDrop> BossDrops = new List<BossDrop>();

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

	public static bool SuppressDropChecks;

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

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

	private static readonly List<string> dropMissing = new List<string>();

	private static readonly HashSet<string> sentDrops = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

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

	private static readonly Dictionary<string, int[]> researchBaseCosts = new Dictionary<string, int[]>(StringComparer.Ordinal);

	private static readonly List<ItemData> researchConsume = new List<ItemData>();

	private static HashSet<string> researchIds;

	private static int researchIdCount = -1;

	private static int knownMaterialCount = -1;

	private static int revealSuppressed;

	private static bool revealQueued;

	private static bool inventoryDirty;

	private static bool scanningInventory;

	private static bool revealedForPlayer;

	private static bool dropCatchUpDone;

	private static bool pityDirty = true;

	private static bool pityWasConnected;

	private static bool pityChancesFrozen;

	private static float pityWait;

	private static string pitySent;

	private static Dictionary<string, TrophyPityInfo> pityChances;

	private static readonly FieldRef<Player, HashSet<string>> KnownRecipesRef = AccessTools.FieldRefAccess<Player, HashSet<string>>("m_knownRecipes");

	private static readonly MethodInfo AddKnownRecipeMethod = AccessTools.Method(typeof(Player), "AddKnownRecipe", (Type[])null, (Type[])null);

	private static readonly MethodInfo AddKnownPieceMethod = AccessTools.Method(typeof(Player), "AddKnownPiece", (Type[])null, (Type[])null);

	private static readonly MethodInfo UpdatePiecesMethod = AccessTools.Method(typeof(Player), "UpdateAvailablePiecesList", (Type[])null, (Type[])null);

	private static readonly FieldRef<Player, HashSet<string>> KnownMaterialsRef = AccessTools.FieldRefAccess<Player, HashSet<string>>("m_knownMaterial");

	private float time;

	public static CraftingStation ResearchBenchCraftingStation { get; set; }

	public static ResearchAccessMode ActiveResearchAccess { get; private set; } = ResearchAccessMode.Default;

	public static bool SeedControlsAccess { get; private set; }

	public static bool SequentialTiers { get; private set; }

	public static ManualLogSource Log { get; private set; }

	private static string DataFolder => Path.GetDirectoryName(typeof(ValheimRandomizer).Assembly.Location);

	public static bool ShowResearchUnlocks
	{
		get
		{
			if (showResearchUnlocks != null)
			{
				return showResearchUnlocks.Value;
			}
			return false;
		}
	}

	private void Awake()
	{
		//IL_01b9: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
		//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d3: Expected O, but got Unknown
		randomized = ((BaseUnityPlugin)this).Config.Bind<bool>("Archipelago", "Randomized", false, "Uses Archipelago?");
		archipelagoHostname = ((BaseUnityPlugin)this).Config.Bind<string>("Archipelago", "Host", "localhost", "Archipelago server host");
		archipelagoPort = ((BaseUnityPlugin)this).Config.Bind<int>("Archipelago", "Port", 38281, "Archipelago server port");
		archipelagoSlot = ((BaseUnityPlugin)this).Config.Bind<string>("Archipelago", "Slot", "Player", "Your Archipelago slot name");
		archipelagoPassword = ((BaseUnityPlugin)this).Config.Bind<string>("Archipelago", "Password", "", "Archipelago password (if any)");
		anyResearchMaterial = ((BaseUnityPlugin)this).Config.Bind<bool>("Archipelago", "AnyResearchMaterial", false, "Off: a research recipe appears after every cost item has been found. On: finding any one cost item reveals the recipe. Crafting always spends the full cost.");
		showResearchUnlocks = ((BaseUnityPlugin)this).Config.Bind<bool>("Archipelago", "ShowResearchUnlocks", false, "Off: a randomized research says Unlocks: Something?. On: the description lists what that research unlocks.");
		researchAccess = ((BaseUnityPlugin)this).Config.Bind<string>("Archipelago", "ResearchAccess", "default", "Used until a seed connects. default, tier, materials, or natural.");
		trophyDrops = ((BaseUnityPlugin)this).Config.Bind<string>("Archipelago", "TrophyDrops", "default", "Trophy drop help on the host that rolls the corpse. default, x2, or easy.");
		researchCosts = ((BaseUnityPlugin)this).Config.Bind<string>("Archipelago", "ResearchCosts", "default", "Research bench costs. default, or halved.");
		ActiveResearchAccess = ParseResearchAccess(researchAccess.Value);
		ActiveTrophyDrops = ParseTrophyDrops(trophyDrops.Value);
		ActiveResearchCosts = ParseResearchCosts(researchCosts.Value);
		Log = ((BaseUnityPlugin)this).Logger;
		PrefabManager.OnVanillaPrefabsAvailable += DoOnPrefabsAvailable;
		new Harmony("com.samupo.randomizer").PatchAll();
		GameObject val = new GameObject("ValheimRandomizer.AP_UI");
		Object.DontDestroyOnLoad((Object)val);
		val.AddComponent<ArchipelagoUI>().Init();
	}

	private void DoOnPrefabsAvailable()
	{
		PrefabManager.OnVanillaPrefabsAvailable -= DoOnPrefabsAvailable;
		RandomizerUtils.RegisterResearchBench();
		AddDropChecks();
		((MonoBehaviour)this).Invoke("AddResearchRecipes", 5f);
		((MonoBehaviour)this).Invoke("AddTrophyResearches", 5.5f);
		((MonoBehaviour)this).Invoke("CheckRecipesAndPiecesWithoutResearch", 10f);
	}

	private void Update()
	{
		ArchipelagoConnection.Pump();
		if ((Object)(object)currentPlayer != (Object)(object)Player.m_localPlayer)
		{
			if ((Object)(object)currentPlayer != (Object)null && ((Humanoid)currentPlayer).GetInventory() != null)
			{
				Inventory inventory = ((Humanoid)currentPlayer).GetInventory();
				inventory.m_onChanged = (Action)Delegate.Remove(inventory.m_onChanged, new Action(OnInventoryChanged));
			}
			currentPlayer = Player.m_localPlayer;
			revealedForPlayer = false;
			dropCatchUpDone = false;
			inventoryDirty = false;
			knownMaterialCount = MaterialCount(currentPlayer);
			sentDrops.Clear();
			craftedResearch.Clear();
			if ((Object)(object)currentPlayer != (Object)null && ((Humanoid)currentPlayer).GetInventory() != null)
			{
				Inventory inventory2 = ((Humanoid)currentPlayer).GetInventory();
				inventory2.m_onChanged = (Action)Delegate.Combine(inventory2.m_onChanged, new Action(OnInventoryChanged));
				SnapshotDropCounts();
			}
		}
		CatchUpHeldDrops();
		if (!revealedForPlayer && (Object)(object)currentPlayer != (Object)null && research.Count > 0)
		{
			RevealEligibleRecipes();
			revealedForPlayer = true;
		}
		if (inventoryDirty)
		{
			FlushInventoryChecks();
		}
		time += Time.unscaledDeltaTime;
		if (time > 2f)
		{
			time = 0f;
			ArchipelagoConnection.TryFlushPendingLocations();
		}
		FlushTrophyPity();
	}

	public static void MarkTrophyPityDirty()
	{
		pityDirty = true;
	}

	private void FlushTrophyPity()
	{
		if (!ArchipelagoConnection.IsConnected)
		{
			pityWasConnected = false;
			pityWait = 0f;
			return;
		}
		if (!pityWasConnected)
		{
			pityWasConnected = true;
			pityDirty = true;
			pitySent = null;
		}
		if (!pityDirty)
		{
			return;
		}
		pityWait += Time.unscaledDeltaTime;
		if (pityWait < 0.4f)
		{
			return;
		}
		pityWait = 0f;
		pityDirty = false;
		try
		{
			JObject val = SnapshotTrophyPity();
			string text = ((JToken)val).ToString((Formatting)0, Array.Empty<JsonConverter>());
			if (!(text == pitySent))
			{
				ArchipelagoConnection.PublishPity(val);
				pitySent = text;
			}
		}
		catch (Exception ex)
		{
			pityDirty = true;
			ManualLogSource log = Log;
			if (log != null)
			{
				log.LogWarning((object)("Trophy pity publish failed: " + ex.Message));
			}
		}
	}

	private static JObject SnapshotTrophyPity()
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Expected O, but got Unknown
		//IL_0061: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: Expected O, but got Unknown
		//IL_017e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0185: Expected O, but got Unknown
		//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
		//IL_0104: Expected O, but got Unknown
		//IL_0143: Unknown result type (might be due to invalid IL or missing references)
		//IL_0148: Unknown result type (might be due to invalid IL or missing references)
		//IL_0159: Unknown result type (might be due to invalid IL or missing references)
		//IL_017c: Expected O, but got Unknown
		JObject val = new JObject();
		EnsurePityChances();
		Dictionary<int, int> s_pseudoCounter = CharacterDrop.s_pseudoCounter;
		if (s_pseudoCounter == null || pityChances == null)
		{
			return val;
		}
		List<string> list = new List<string>(pityChances.Keys);
		list.Sort(StringComparer.Ordinal);
		for (int i = 0; i < list.Count; i++)
		{
			TrophyPityInfo trophyPityInfo = pityChances[list[i]];
			int num = 0;
			float num2 = trophyPityInfo.baseChance;
			bool flag = false;
			JArray val2 = new JArray();
			for (int j = 0; j < trophyPityInfo.chances.Count; j++)
			{
				PityChance pityChance = trophyPityInfo.chances[j];
				if (s_pseudoCounter.TryGetValue(pityChance.key, out var value))
				{
					int num3 = Math.Max(1, value);
					if (ActiveTrophyDrops == TrophyDropMode.Double && num3 > 10)
					{
						num3 = 10;
					}
					if (!pityChance.starred)
					{
						flag = true;
						num = num3;
						num2 = pityChance.chance;
					}
					else
					{
						val2.Add((JToken)new JObject
						{
							["left"] = JToken.op_Implicit(num3),
							["chance"] = JToken.op_Implicit(Math.Round(pityChance.chance, 4))
						});
					}
				}
			}
			if (!flag && ((JContainer)val2).Count == 0)
			{
				if (!(trophyPityInfo.baseChance <= 0.3f))
				{
					val[list[i]] = (JToken)new JObject
					{
						["pity"] = JToken.op_Implicit(false),
						["chance"] = JToken.op_Implicit(Math.Round(trophyPityInfo.baseChance, 4))
					};
				}
				continue;
			}
			JObject val3 = new JObject();
			if (flag)
			{
				val3["left"] = JToken.op_Implicit(num);
				val3["chance"] = JToken.op_Implicit(Math.Round(num2, 4));
			}
			if (((JContainer)val2).Count > 0)
			{
				val3["stars"] = (JToken)(object)val2;
			}
			val[list[i]] = (JToken)(object)val3;
		}
		return val;
	}

	private static void EnsurePityChances()
	{
		if (pityChancesFrozen || trophyByItemID.Count == 0)
		{
			return;
		}
		CharacterDrop[] array = Resources.FindObjectsOfTypeAll<CharacterDrop>();
		if (array == null || array.Length == 0)
		{
			return;
		}
		Dictionary<string, TrophyPityInfo> dictionary = new Dictionary<string, TrophyPityInfo>(StringComparer.Ordinal);
		foreach (CharacterDrop val in array)
		{
			if ((Object)(object)val == (Object)null || val.m_drops == null)
			{
				continue;
			}
			for (int j = 0; j < val.m_drops.Count; j++)
			{
				Drop val2 = val.m_drops[j];
				if (val2 == null || (Object)(object)val2.m_prefab == (Object)null)
				{
					continue;
				}
				string name = ((Object)val2.m_prefab).name;
				if (!string.IsNullOrEmpty(name) && trophyByItemID.ContainsKey(name))
				{
					if (!dictionary.TryGetValue(name, out var value))
					{
						value = (dictionary[name] = new TrophyPityInfo());
					}
					float num = val2.m_chance;
					if (ActiveTrophyDrops == TrophyDropMode.Double)
					{
						num *= 2f;
					}
					value.baseChance = num;
					AddPityChance(value, name, num, starred: false);
					if (val2.m_levelMultiplier)
					{
						AddPityChance(value, name, num * 2f, starred: true);
						AddPityChance(value, name, num * 4f, starred: true);
					}
				}
			}
		}
		if (dictionary.Count != 0)
		{
			pityChances = dictionary;
			if ((Object)(object)ZNetScene.instance != (Object)null)
			{
				pityChancesFrozen = true;
			}
		}
	}

	private static void AddPityChance(TrophyPityInfo info, string name, float chance, bool starred)
	{
		if (chance <= 0f || chance > 0.3f)
		{
			return;
		}
		int num = StringExtensionMethods.GetStableHashCode(name) + (int)(chance * 1000f);
		for (int i = 0; i < info.chances.Count; i++)
		{
			if (info.chances[i].key == num)
			{
				return;
			}
		}
		info.chances.Add(new PityChance
		{
			chance = chance,
			key = num,
			starred = starred
		});
	}

	private void OnInventoryChanged()
	{
		if (!scanningInventory)
		{
			inventoryDirty = true;
		}
	}

	private void FlushInventoryChecks()
	{
		if (scanningInventory)
		{
			return;
		}
		Player val = currentPlayer;
		Inventory val2 = (((Object)(object)val != (Object)null) ? ((Humanoid)val).GetInventory() : null);
		if (val2 == null)
		{
			inventoryDirty = false;
			return;
		}
		scanningInventory = true;
		inventoryDirty = false;
		revealSuppressed++;
		bool flag = false;
		bool flag2 = false;
		try
		{
			List<ItemData> allItems = val2.GetAllItems();
			HashSet<string> hashSet = ResearchIdSet();
			researchConsume.Clear();
			bool flag3 = ArchipelagoConnection.DropChecksEnabled && !SuppressDropChecks && dropByItemID.Count > 0;
			if (flag3)
			{
				dropCounts.Clear();
			}
			if (allItems != null)
			{
				for (int i = 0; i < allItems.Count; i++)
				{
					ItemData val3 = allItems[i];
					GameObject val4 = val3?.m_dropPrefab;
					if ((Object)(object)val4 == (Object)null)
					{
						continue;
					}
					string name = ((Object)val4).name;
					if (string.IsNullOrEmpty(name))
					{
						continue;
					}
					if (hashSet.Contains(name))
					{
						researchConsume.Add(val3);
					}
					if (trophyByItemID.TryGetValue(name, out var value) && !AlreadyCrafted(value.researchID))
					{
						craftedResearch.Add(value.researchID);
						UnlockResearch(value.researchID);
						flag = true;
						ManualLogSource log = Log;
						if (log != null)
						{
							log.LogInfo((object)("Unlocked trophy research '" + value.researchID + "' from '" + name + "'."));
						}
					}
					if (flag3 && dropByItemID.ContainsKey(name) && !DropAlreadySent(name))
					{
						dropCounts.TryGetValue(name, out var value2);
						dropCounts[name] = value2 + val3.m_stack;
					}
				}
			}
			for (int j = 0; j < researchConsume.Count; j++)
			{
				ItemData val5 = researchConsume[j];
				if (!((Object)(object)val5?.m_dropPrefab == (Object)null))
				{
					string name2 = ((Object)val5.m_dropPrefab).name;
					bool num = !AlreadyCrafted(name2);
					craftedResearch.Add(name2);
					if (num)
					{
						UnlockResearch(name2);
						flag = true;
					}
					val2.RemoveItem(val5, 1);
					flag2 = true;
				}
			}
			researchConsume.Clear();
			if (flag3)
			{
				ApplyDropCounts();
			}
			if (MaterialsGrew(val))
			{
				flag = true;
			}
		}
		finally
		{
			revealSuppressed--;
			scanningInventory = false;
		}
		if (flag || revealQueued)
		{
			revealQueued = false;
			RevealEligibleRecipes();
		}
		if (flag2)
		{
			Patches.RefreshResearchBench();
		}
	}

	private static HashSet<string> ResearchIdSet()
	{
		if (researchIds == null || researchIdCount != research.Count)
		{
			researchIds = new HashSet<string>(research, StringComparer.Ordinal);
			researchIdCount = research.Count;
		}
		return researchIds;
	}

	private static bool AlreadyCrafted(string researchId)
	{
		if (string.IsNullOrEmpty(researchId))
		{
			return true;
		}
		if (craftedResearch.Contains(researchId))
		{
			return true;
		}
		if (!IsResearchCrafted(researchId))
		{
			return false;
		}
		craftedResearch.Add(researchId);
		return true;
	}

	private static bool DropAlreadySent(string itemID)
	{
		if (sentDrops.Contains(itemID))
		{
			return true;
		}
		if ((Object)(object)ZoneSystem.instance == (Object)null)
		{
			return false;
		}
		if (!ZoneSystem.instance.GetGlobalKey("ap_drop_sent_" + itemID) && !ZoneSystem.instance.GetGlobalKey("ap_drop_pending_" + itemID))
		{
			return false;
		}
		sentDrops.Add(itemID);
		return true;
	}

	private static int MaterialCount(Player player)
	{
		return (((Object)(object)player != (Object)null) ? KnownMaterialsRef.Invoke(player) : null)?.Count ?? 0;
	}

	private static bool MaterialsGrew(Player player)
	{
		int num = MaterialCount(player);
		if (num <= knownMaterialCount)
		{
			knownMaterialCount = num;
			return false;
		}
		knownMaterialCount = num;
		return true;
	}

	private static void TallyUnsentDrops(List<ItemData> items)
	{
		dropCounts.Clear();
		if (items == null)
		{
			return;
		}
		for (int i = 0; i < items.Count; i++)
		{
			ItemData val = items[i];
			GameObject val2 = val?.m_dropPrefab;
			if (!((Object)(object)val2 == (Object)null))
			{
				string name = ((Object)val2).name;
				if (!string.IsNullOrEmpty(name) && dropByItemID.ContainsKey(name) && !DropAlreadySent(name))
				{
					dropCounts.TryGetValue(name, out var value);
					dropCounts[name] = value + val.m_stack;
				}
			}
		}
	}

	private static void ApplyDropCounts()
	{
		foreach (KeyValuePair<string, int> dropCount in dropCounts)
		{
			dropSeen.TryGetValue(dropCount.Key, out var value);
			if (dropCount.Value > value && dropByItemID.TryGetValue(dropCount.Key, out var value2))
			{
				ArchipelagoConnection.SendDropLocation(value2.itemID, value2.locationName);
			}
			dropSeen[dropCount.Key] = dropCount.Value;
		}
		dropMissing.Clear();
		foreach (KeyValuePair<string, int> item in dropSeen)
		{
			if (!dropCounts.ContainsKey(item.Key) && !DropAlreadySent(item.Key))
			{
				dropMissing.Add(item.Key);
			}
		}
		for (int i = 0; i < dropMissing.Count; i++)
		{
			dropSeen[dropMissing[i]] = 0;
		}
		dropMissing.Clear();
	}

	public static void SnapshotDropCounts()
	{
		dropSeen.Clear();
		Player localPlayer = Player.m_localPlayer;
		if (((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null) == null || dropByItemID.Count == 0)
		{
			return;
		}
		TallyUnsentDrops(((Humanoid)localPlayer).GetInventory().GetAllItems());
		foreach (KeyValuePair<string, int> dropCount in dropCounts)
		{
			dropSeen[dropCount.Key] = dropCount.Value;
		}
	}

	private void CatchUpHeldDrops()
	{
		if (dropCatchUpDone || (Object)(object)currentPlayer == (Object)null || (Object)(object)ZoneSystem.instance == (Object)null || dropByItemID.Count == 0 || !ArchipelagoConnection.DropChecksEnabled || randomized == null || !randomized.Value)
		{
			return;
		}
		dropCatchUpDone = true;
		TallyUnsentDrops(((Humanoid)currentPlayer).GetInventory().GetAllItems());
		int num = 0;
		foreach (KeyValuePair<string, int> dropCount in dropCounts)
		{
			dropSeen[dropCount.Key] = dropCount.Value;
			if (dropCount.Value > 0)
			{
				num++;
				if (dropByItemID.TryGetValue(dropCount.Key, out var value))
				{
					ArchipelagoConnection.SendDropLocation(value.itemID, value.locationName);
				}
			}
		}
		ManualLogSource log = Log;
		if (log != null)
		{
			log.LogInfo((object)$"Drop catch-up: {num} listed stacks already held.");
		}
	}

	public static bool IsBossDropPrefab(string prefabName)
	{
		if (!string.IsNullOrEmpty(prefabName))
		{
			return bossPrefabs.Contains(prefabName);
		}
		return false;
	}

	public static int BossGrantAmount(BossDrop boss)
	{
		int num = boss.amount;
		int num2 = boss.amount;
		bool onePerPlayer = boss.onePerPlayer;
		GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(boss.creature) : null);
		CharacterDrop val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<CharacterDrop>() : null);
		if ((Object)(object)val2 != (Object)null && val2.m_drops != null)
		{
			for (int i = 0; i < val2.m_drops.Count; i++)
			{
				Drop val3 = val2.m_drops[i];
				if (val3 != null && !((Object)(object)val3.m_prefab == (Object)null) && string.Equals(((Object)val3.m_prefab).name, boss.itemID, StringComparison.Ordinal))
				{
					num = val3.m_amountMin;
					num2 = val3.m_amountMax;
					onePerPlayer = val3.m_onePerPlayer;
					break;
				}
			}
		}
		if (num2 < num)
		{
			num2 = num;
		}
		int num3 = ((num == num2) ? num : Random.Range(num, num2 + 1));
		float num4 = ((Game.m_resourceRate > 0f) ? Game.m_resourceRate : 1f);
		int num5 = Mathf.Max(1, Mathf.RoundToInt((float)num3 * num4));
		if (onePerPlayer)
		{
			int num6 = 1;
			if ((Object)(object)ZNet.instance != (Object)null)
			{
				num6 = Mathf.Max(1, ZNet.instance.GetNrOfPlayers());
			}
			num5 *= num6;
		}
		if (num5 > 100)
		{
			num5 = 100;
		}
		return num5;
	}

	public static bool GrantDropBundle(string itemID, int amount)
	{
		Player localPlayer = Player.m_localPlayer;
		GameObject val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectD

Archipelago.MultiClient.Net.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.WebSockets;
using System.Numerics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Archipelago.MultiClient.Net.Colors;
using Archipelago.MultiClient.Net.ConcurrentCollection;
using Archipelago.MultiClient.Net.Converters;
using Archipelago.MultiClient.Net.DataPackage;
using Archipelago.MultiClient.Net.Enums;
using Archipelago.MultiClient.Net.Exceptions;
using Archipelago.MultiClient.Net.Extensions;
using Archipelago.MultiClient.Net.Helpers;
using Archipelago.MultiClient.Net.MessageLog.Messages;
using Archipelago.MultiClient.Net.MessageLog.Parts;
using Archipelago.MultiClient.Net.Models;
using Archipelago.MultiClient.Net.Packets;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: ComVisible(false)]
[assembly: Guid("35a803ad-85ed-42e9-b1e3-c6b72096f0c1")]
[assembly: InternalsVisibleTo("Archipelago.MultiClient.Net.Tests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
[assembly: TargetFramework(".NETFramework,Version=v4.5", FrameworkDisplayName = ".NET Framework 4.5")]
[assembly: AssemblyCompany("Jarno Westhof, Hussein Farran, Zach Parks")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: AssemblyDescription("A client library for use with .NET based prog-langs for interfacing with Archipelago hosts.")]
[assembly: AssemblyFileVersion("6.7.1.0")]
[assembly: AssemblyInformationalVersion("6.7.1+0c57591db30f2497b0b4fef87164aa2bbe2e51b2")]
[assembly: AssemblyProduct("Archipelago.MultiClient.Net")]
[assembly: AssemblyTitle("Archipelago.MultiClient.Net")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/ArchipelagoMW/Archipelago.MultiClient.Net")]
[assembly: AssemblyVersion("6.7.1.0")]
internal interface IConcurrentHashSet<T>
{
	bool TryAdd(T item);

	bool Contains(T item);

	void UnionWith(T[] otherSet);

	T[] ToArray();

	ReadOnlyCollection<T> AsToReadOnlyCollection();

	ReadOnlyCollection<T> AsToReadOnlyCollectionExcept(IConcurrentHashSet<T> otherSet);
}
public class AttemptingStringEnumConverter : StringEnumConverter
{
	public AttemptingStringEnumConverter()
	{
	}

	public AttemptingStringEnumConverter(Type namingStrategyType)
		: base(namingStrategyType)
	{
	}

	public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
	{
		try
		{
			return ((StringEnumConverter)this).ReadJson(reader, objectType, existingValue, serializer);
		}
		catch (JsonSerializationException)
		{
			return objectType.IsValueType ? Activator.CreateInstance(objectType) : null;
		}
	}
}
namespace Archipelago.MultiClient.Net
{
	[Serializable]
	public abstract class ArchipelagoPacketBase
	{
		[JsonIgnore]
		internal JObject jobject;

		[JsonProperty("cmd")]
		[JsonConverter(typeof(StringEnumConverter))]
		public abstract ArchipelagoPacketType PacketType { get; }

		public JObject ToJObject()
		{
			return jobject;
		}
	}
	public interface IArchipelagoSession : IArchipelagoSessionActions
	{
		IArchipelagoSocketHelper Socket { get; }

		IReceivedItemsHelper Items { get; }

		ILocationCheckHelper Locations { get; }

		IPlayerHelper Players { get; }

		IDataStorageHelper DataStorage { get; }

		IConnectionInfoProvider ConnectionInfo { get; }

		IRoomStateHelper RoomState { get; }

		IMessageLogHelper MessageLog { get; }

		IHintsHelper Hints { get; }

		Task<RoomInfoPacket> ConnectAsync();

		Task<LoginResult> LoginAsync(string game, string name, ItemsHandlingFlags itemsHandlingFlags, Version version = null, string[] tags = null, string uuid = null, string password = null, bool requestSlotData = true);

		LoginResult TryConnectAndLogin(string game, string name, ItemsHandlingFlags itemsHandlingFlags, Version version = null, string[] tags = null, string uuid = null, string password = null, bool requestSlotData = true);
	}
	public class ArchipelagoSession : IArchipelagoSession, IArchipelagoSessionActions
	{
		private const int ArchipelagoConnectionTimeoutInSeconds = 4;

		private ConnectionInfoHelper connectionInfo;

		private TaskCompletionSource<LoginResult> loginResultTask = new TaskCompletionSource<LoginResult>();

		private TaskCompletionSource<RoomInfoPacket> roomInfoPacketTask = new TaskCompletionSource<RoomInfoPacket>();

		public IArchipelagoSocketHelper Socket { get; }

		public IReceivedItemsHelper Items { get; }

		public ILocationCheckHelper Locations { get; }

		public IPlayerHelper Players { get; }

		public IDataStorageHelper DataStorage { get; }

		public IConnectionInfoProvider ConnectionInfo => connectionInfo;

		public IRoomStateHelper RoomState { get; }

		public IMessageLogHelper MessageLog { get; }

		public IHintsHelper Hints { get; }

		internal ArchipelagoSession(IArchipelagoSocketHelper socket, IReceivedItemsHelper items, ILocationCheckHelper locations, IPlayerHelper players, IRoomStateHelper roomState, ConnectionInfoHelper connectionInfoHelper, IDataStorageHelper dataStorage, IMessageLogHelper messageLog, IHintsHelper createHints)
		{
			Socket = socket;
			Items = items;
			Locations = locations;
			Players = players;
			RoomState = roomState;
			connectionInfo = connectionInfoHelper;
			DataStorage = dataStorage;
			MessageLog = messageLog;
			Hints = createHints;
			socket.PacketReceived += Socket_PacketReceived;
		}

		private void Socket_PacketReceived(ArchipelagoPacketBase packet)
		{
			if (!(packet is ConnectedPacket) && !(packet is ConnectionRefusedPacket))
			{
				if (packet is RoomInfoPacket result)
				{
					roomInfoPacketTask.TrySetResult(result);
				}
			}
			else
			{
				loginResultTask.TrySetResult(LoginResult.FromPacket(packet));
			}
		}

		public Task<RoomInfoPacket> ConnectAsync()
		{
			roomInfoPacketTask = new TaskCompletionSource<RoomInfoPacket>();
			Task.Factory.StartNew(delegate
			{
				try
				{
					Task task = Socket.ConnectAsync();
					task.Wait(TimeSpan.FromSeconds(4.0));
					if (!task.IsCompleted)
					{
						roomInfoPacketTask.TrySetCanceled();
					}
				}
				catch (AggregateException)
				{
					roomInfoPacketTask.TrySetCanceled();
				}
			});
			return roomInfoPacketTask.Task;
		}

		public Task<LoginResult> LoginAsync(string game, string name, ItemsHandlingFlags itemsHandlingFlags, Version version = null, string[] tags = null, string uuid = null, string password = null, bool requestSlotData = true)
		{
			loginResultTask = new TaskCompletionSource<LoginResult>();
			if (!roomInfoPacketTask.Task.IsCompleted)
			{
				loginResultTask.TrySetResult(new LoginFailure("You are not connected, run ConnectAsync() first"));
				return loginResultTask.Task;
			}
			connectionInfo.SetConnectionParameters(game, tags, itemsHandlingFlags, uuid);
			try
			{
				Socket.SendPacket(BuildConnectPacket(name, password, version, requestSlotData));
			}
			catch (ArchipelagoSocketClosedException)
			{
				loginResultTask.TrySetResult(new LoginFailure("You are not connected, run ConnectAsync() first"));
				return loginResultTask.Task;
			}
			SetResultAfterTimeout(loginResultTask, 4, new LoginFailure("Connection timed out."));
			return loginResultTask.Task;
		}

		private static void SetResultAfterTimeout<T>(TaskCompletionSource<T> task, int timeoutInSeconds, T result)
		{
			new CancellationTokenSource(TimeSpan.FromSeconds(timeoutInSeconds)).Token.Register(delegate
			{
				task.TrySetResult(result);
			});
		}

		public LoginResult TryConnectAndLogin(string game, string name, ItemsHandlingFlags itemsHandlingFlags, Version version = null, string[] tags = null, string uuid = null, string password = null, bool requestSlotData = true)
		{
			Task<RoomInfoPacket> task = ConnectAsync();
			try
			{
				task.Wait(TimeSpan.FromSeconds(4.0));
			}
			catch (AggregateException ex)
			{
				if (ex.GetBaseException() is OperationCanceledException)
				{
					return new LoginFailure("Connection timed out.");
				}
				return new LoginFailure(ex.GetBaseException().Message);
			}
			if (!task.IsCompleted)
			{
				return new LoginFailure("Connection timed out.");
			}
			return LoginAsync(game, name, itemsHandlingFlags, version, tags, uuid, password, requestSlotData).Result;
		}

		private ConnectPacket BuildConnectPacket(string name, string password, Version version, bool requestSlotData)
		{
			return new ConnectPacket
			{
				Game = ConnectionInfo.Game,
				Name = name,
				Password = password,
				Tags = ConnectionInfo.Tags,
				Uuid = ConnectionInfo.Uuid,
				Version = ((version != null) ? new NetworkVersion(version) : new NetworkVersion(0, 6, 0)),
				ItemsHandling = ConnectionInfo.ItemsHandlingFlags,
				RequestSlotData = requestSlotData
			};
		}

		public void Say(string message)
		{
			Socket.SendPacket(new SayPacket
			{
				Text = message
			});
		}

		public void SetClientState(ArchipelagoClientState state)
		{
			Socket.SendPacket(new StatusUpdatePacket
			{
				Status = state
			});
		}

		public void SetGoalAchieved()
		{
			SetClientState(ArchipelagoClientState.ClientGoal);
		}
	}
	public interface IArchipelagoSessionActions
	{
		void Say(string message);

		void SetClientState(ArchipelagoClientState state);

		void SetGoalAchieved();
	}
	public static class ArchipelagoSessionFactory
	{
		public static ArchipelagoSession CreateSession(Uri uri)
		{
			ArchipelagoSocketHelper socket = new ArchipelagoSocketHelper(uri);
			DataPackageCache cache = new DataPackageCache(socket);
			ConnectionInfoHelper connectionInfoHelper = new ConnectionInfoHelper(socket);
			PlayerHelper playerHelper = new PlayerHelper(socket, connectionInfoHelper);
			ItemInfoResolver itemInfoResolver = new ItemInfoResolver(cache, connectionInfoHelper);
			LocationCheckHelper locationCheckHelper = new LocationCheckHelper(socket, itemInfoResolver, connectionInfoHelper, playerHelper);
			ReceivedItemsHelper items = new ReceivedItemsHelper(socket, locationCheckHelper, itemInfoResolver, connectionInfoHelper, playerHelper);
			RoomStateHelper roomStateHelper = new RoomStateHelper(socket, locationCheckHelper);
			DataStorageHelper dataStorageHelper = new DataStorageHelper(socket, connectionInfoHelper);
			MessageLogHelper messageLog = new MessageLogHelper(socket, itemInfoResolver, playerHelper, connectionInfoHelper);
			HintsHelper createHints = new HintsHelper(socket, playerHelper, locationCheckHelper, roomStateHelper, dataStorageHelper);
			return new ArchipelagoSession(socket, items, locationCheckHelper, playerHelper, roomStateHelper, connectionInfoHelper, dataStorageHelper, messageLog, createHints);
		}

		public static ArchipelagoSession CreateSession(string hostname, int port = 38281)
		{
			return CreateSession(ParseUri(hostname, port));
		}

		internal static Uri ParseUri(string hostname, int port)
		{
			string text = hostname;
			if (!text.StartsWith("ws://") && !text.StartsWith("wss://"))
			{
				text = "unspecified://" + text;
			}
			if (!text.Substring(text.IndexOf("://", StringComparison.Ordinal) + 3).Contains(":"))
			{
				text += $":{port}";
			}
			if (text.EndsWith(":"))
			{
				text += port;
			}
			return new Uri(text);
		}
	}
	public abstract class LoginResult
	{
		public abstract bool Successful { get; }

		public static LoginResult FromPacket(ArchipelagoPacketBase packet)
		{
			if (!(packet is ConnectedPacket connectedPacket))
			{
				if (packet is ConnectionRefusedPacket connectionRefusedPacket)
				{
					return new LoginFailure(connectionRefusedPacket);
				}
				throw new ArgumentOutOfRangeException("packet", "packet is not a connection result packet");
			}
			return new LoginSuccessful(connectedPacket);
		}
	}
	public class LoginSuccessful : LoginResult
	{
		public override bool Successful => true;

		public int Team { get; }

		public int Slot { get; }

		public Dictionary<string, object> SlotData { get; }

		public LoginSuccessful(ConnectedPacket connectedPacket)
		{
			Team = connectedPacket.Team;
			Slot = connectedPacket.Slot;
			SlotData = connectedPacket.SlotData;
		}
	}
	public class LoginFailure : LoginResult
	{
		public override bool Successful => false;

		public ConnectionRefusedError[] ErrorCodes { get; }

		public string[] Errors { get; }

		public LoginFailure(ConnectionRefusedPacket connectionRefusedPacket)
		{
			if (connectionRefusedPacket.Errors != null)
			{
				ErrorCodes = connectionRefusedPacket.Errors.ToArray();
				Errors = ErrorCodes.Select(GetErrorMessage).ToArray();
			}
			else
			{
				ErrorCodes = new ConnectionRefusedError[0];
				Errors = new string[0];
			}
		}

		public LoginFailure(string message)
		{
			ErrorCodes = new ConnectionRefusedError[0];
			Errors = new string[1] { message };
		}

		private static string GetErrorMessage(ConnectionRefusedError errorCode)
		{
			return errorCode switch
			{
				ConnectionRefusedError.InvalidSlot => "The slot name did not match any slot on the server.", 
				ConnectionRefusedError.InvalidGame => "The slot is set to a different game on the server.", 
				ConnectionRefusedError.SlotAlreadyTaken => "The slot already has a connection with a different uuid established.", 
				ConnectionRefusedError.IncompatibleVersion => "The client and server version mismatch.", 
				ConnectionRefusedError.InvalidPassword => "The password is invalid.", 
				ConnectionRefusedError.InvalidItemsHandling => "The item handling flags provided are invalid.", 
				_ => $"Unknown error: {errorCode}.", 
			};
		}
	}
	internal class TwoWayLookup<TA, TB> : IEnumerable<KeyValuePair<TB, TA>>, IEnumerable
	{
		private readonly Dictionary<TA, TB> aToB = new Dictionary<TA, TB>();

		private readonly Dictionary<TB, TA> bToA = new Dictionary<TB, TA>();

		public TA this[TB b] => bToA[b];

		public TB this[TA a] => aToB[a];

		public void Add(TA a, TB b)
		{
			aToB[a] = b;
			bToA[b] = a;
		}

		public void Add(TB b, TA a)
		{
			Add(a, b);
		}

		public bool TryGetValue(TA a, out TB b)
		{
			return aToB.TryGetValue(a, out b);
		}

		public bool TryGetValue(TB b, out TA a)
		{
			return bToA.TryGetValue(b, out a);
		}

		public IEnumerator<KeyValuePair<TB, TA>> GetEnumerator()
		{
			return bToA.GetEnumerator();
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}
	}
}
namespace Archipelago.MultiClient.Net.Packets
{
	public class BouncedPacket : BouncePacket
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Bounced;
	}
	public class BouncePacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Bounce;

		[JsonProperty("games")]
		public List<string> Games { get; set; } = new List<string>();


		[JsonProperty("slots")]
		public List<int> Slots { get; set; } = new List<int>();


		[JsonProperty("tags")]
		public List<string> Tags { get; set; } = new List<string>();


		[JsonProperty("data")]
		public Dictionary<string, JToken> Data { get; set; }
	}
	public class ConnectedPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Connected;

		[JsonProperty("team")]
		public int Team { get; set; }

		[JsonProperty("slot")]
		public int Slot { get; set; }

		[JsonProperty("players")]
		public NetworkPlayer[] Players { get; set; }

		[JsonProperty("missing_locations")]
		public long[] MissingChecks { get; set; }

		[JsonProperty("checked_locations")]
		public long[] LocationsChecked { get; set; }

		[JsonProperty("slot_data")]
		public Dictionary<string, object> SlotData { get; set; }

		[JsonProperty("slot_info")]
		public Dictionary<int, NetworkSlot> SlotInfo { get; set; }

		[JsonProperty("hint_points")]
		public int? HintPoints { get; set; }
	}
	public class ConnectionRefusedPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.ConnectionRefused;

		[JsonProperty("errors", ItemConverterType = typeof(AttemptingStringEnumConverter))]
		public ConnectionRefusedError[] Errors { get; set; }
	}
	public class ConnectPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Connect;

		[JsonProperty("password")]
		public string Password { get; set; }

		[JsonProperty("game")]
		public string Game { get; set; }

		[JsonProperty("name")]
		public string Name { get; set; }

		[JsonProperty("uuid")]
		public string Uuid { get; set; }

		[JsonProperty("version")]
		public NetworkVersion Version { get; set; }

		[JsonProperty("tags")]
		public string[] Tags { get; set; }

		[JsonProperty("items_handling")]
		public ItemsHandlingFlags ItemsHandling { get; set; }

		[JsonProperty("slot_data")]
		public bool RequestSlotData { get; set; }
	}
	public class ConnectUpdatePacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.ConnectUpdate;

		[JsonProperty("tags")]
		public string[] Tags { get; set; }

		[JsonProperty("items_handling")]
		public ItemsHandlingFlags? ItemsHandling { get; set; }
	}
	public class CreateHintsPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.CreateHints;

		[JsonProperty("locations")]
		public long[] Locations { get; set; }

		[JsonProperty("player")]
		public int Player { get; set; }

		[JsonProperty("status")]
		public HintStatus Status { get; set; }
	}
	public class DataPackagePacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.DataPackage;

		[JsonProperty("data")]
		public Archipelago.MultiClient.Net.Models.DataPackage DataPackage { get; set; }
	}
	public class GetDataPackagePacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.GetDataPackage;

		[JsonProperty("games")]
		public string[] Games { get; set; }
	}
	public class GetPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Get;

		[JsonProperty("keys")]
		public string[] Keys { get; set; }
	}
	public class InvalidPacketPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.InvalidPacket;

		[JsonProperty("type")]
		public InvalidPacketErrorType ErrorType { get; set; }

		[JsonProperty("text")]
		public string ErrorText { get; set; }

		[JsonProperty("original_cmd")]
		public ArchipelagoPacketType OriginalCmd { get; set; }
	}
	public class LocationChecksPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.LocationChecks;

		[JsonProperty("locations")]
		public long[] Locations { get; set; }
	}
	public class LocationInfoPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.LocationInfo;

		[JsonProperty("locations")]
		public NetworkItem[] Locations { get; set; }
	}
	public class LocationScoutsPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.LocationScouts;

		[JsonProperty("locations")]
		public long[] Locations { get; set; }

		[JsonProperty("create_as_hint")]
		public int CreateAsHint { get; set; }
	}
	public class PrintJsonPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.PrintJSON;

		[JsonProperty("data")]
		public JsonMessagePart[] Data { get; set; }

		[JsonProperty("type")]
		[JsonConverter(typeof(AttemptingStringEnumConverter))]
		public JsonMessageType? MessageType { get; set; }
	}
	public class ItemPrintJsonPacket : PrintJsonPacket
	{
		[JsonProperty("receiving")]
		public int ReceivingPlayer { get; set; }

		[JsonProperty("item")]
		public NetworkItem Item { get; set; }
	}
	public class ItemCheatPrintJsonPacket : PrintJsonPacket
	{
		[JsonProperty("receiving")]
		public int ReceivingPlayer { get; set; }

		[JsonProperty("item")]
		public NetworkItem Item { get; set; }

		[JsonProperty("team")]
		public int Team { get; set; }
	}
	public class HintPrintJsonPacket : PrintJsonPacket
	{
		[JsonProperty("receiving")]
		public int ReceivingPlayer { get; set; }

		[JsonProperty("item")]
		public NetworkItem Item { get; set; }

		[JsonProperty("found")]
		public bool? Found { get; set; }
	}
	public class JoinPrintJsonPacket : PrintJsonPacket
	{
		[JsonProperty("team")]
		public int Team { get; set; }

		[JsonProperty("slot")]
		public int Slot { get; set; }

		[JsonProperty("tags")]
		public string[] Tags { get; set; }
	}
	public class LeavePrintJsonPacket : PrintJsonPacket
	{
		[JsonProperty("team")]
		public int Team { get; set; }

		[JsonProperty("slot")]
		public int Slot { get; set; }
	}
	public class ChatPrintJsonPacket : PrintJsonPacket
	{
		[JsonProperty("team")]
		public int Team { get; set; }

		[JsonProperty("slot")]
		public int Slot { get; set; }

		[JsonProperty("message")]
		public string Message { get; set; }
	}
	public class ServerChatPrintJsonPacket : PrintJsonPacket
	{
		[JsonProperty("message")]
		public string Message { get; set; }
	}
	public class TutorialPrintJsonPacket : PrintJsonPacket
	{
	}
	public class TagsChangedPrintJsonPacket : PrintJsonPacket
	{
		[JsonProperty("team")]
		public int Team { get; set; }

		[JsonProperty("slot")]
		public int Slot { get; set; }

		[JsonProperty("tags")]
		public string[] Tags { get; set; }
	}
	public class CommandResultPrintJsonPacket : PrintJsonPacket
	{
	}
	public class AdminCommandResultPrintJsonPacket : PrintJsonPacket
	{
	}
	public class GoalPrintJsonPacket : PrintJsonPacket
	{
		[JsonProperty("team")]
		public int Team { get; set; }

		[JsonProperty("slot")]
		public int Slot { get; set; }
	}
	public class ReleasePrintJsonPacket : PrintJsonPacket
	{
		[JsonProperty("team")]
		public int Team { get; set; }

		[JsonProperty("slot")]
		public int Slot { get; set; }
	}
	public class CollectPrintJsonPacket : PrintJsonPacket
	{
		[JsonProperty("team")]
		public int Team { get; set; }

		[JsonProperty("slot")]
		public int Slot { get; set; }
	}
	public class CountdownPrintJsonPacket : PrintJsonPacket
	{
		[JsonProperty("countdown")]
		public int RemainingSeconds { get; set; }
	}
	public class ReceivedItemsPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.ReceivedItems;

		[JsonProperty("index")]
		public int Index { get; set; }

		[JsonProperty("items")]
		public NetworkItem[] Items { get; set; }
	}
	public class RetrievedPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Retrieved;

		[JsonProperty("keys")]
		public Dictionary<string, JToken> Data { get; set; }
	}
	public class RoomInfoPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.RoomInfo;

		[JsonProperty("version")]
		public NetworkVersion Version { get; set; }

		[JsonProperty("generator_version")]
		public NetworkVersion GeneratorVersion { get; set; }

		[JsonProperty("tags")]
		public string[] Tags { get; set; }

		[JsonProperty("password")]
		public bool Password { get; set; }

		[JsonProperty("permissions")]
		public Dictionary<string, Permissions> Permissions { get; set; }

		[JsonProperty("hint_cost")]
		public int HintCostPercentage { get; set; }

		[JsonProperty("location_check_points")]
		public int LocationCheckPoints { get; set; }

		[JsonProperty("players")]
		public NetworkPlayer[] Players { get; set; }

		[JsonProperty("games")]
		public string[] Games { get; set; }

		[JsonProperty("datapackage_checksums")]
		public Dictionary<string, string> DataPackageChecksums { get; set; }

		[JsonProperty("seed_name")]
		public string SeedName { get; set; }

		[JsonProperty("time")]
		public double Timestamp { get; set; }
	}
	public class RoomUpdatePacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.RoomUpdate;

		[JsonProperty("tags")]
		public string[] Tags { get; set; }

		[JsonProperty("password")]
		public bool? Password { get; set; }

		[JsonProperty("permissions")]
		public Dictionary<string, Permissions> Permissions { get; set; } = new Dictionary<string, Permissions>();


		[JsonProperty("hint_cost")]
		public int? HintCostPercentage { get; set; }

		[JsonProperty("location_check_points")]
		public int? LocationCheckPoints { get; set; }

		[JsonProperty("players")]
		public NetworkPlayer[] Players { get; set; }

		[JsonProperty("hint_points")]
		public int? HintPoints { get; set; }

		[JsonProperty("checked_locations")]
		public long[] CheckedLocations { get; set; }
	}
	public class SayPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Say;

		[JsonProperty("text")]
		public string Text { get; set; }
	}
	public class SetNotifyPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.SetNotify;

		[JsonProperty("keys")]
		public string[] Keys { get; set; }
	}
	public class SetPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Set;

		[JsonProperty("key")]
		public string Key { get; set; }

		[JsonProperty("default")]
		public JToken DefaultValue { get; set; }

		[JsonProperty("operations")]
		public OperationSpecification[] Operations { get; set; }

		[JsonProperty("want_reply")]
		public bool WantReply { get; set; }

		[JsonExtensionData]
		public Dictionary<string, JToken> AdditionalArguments { get; set; }

		[OnDeserialized]
		internal void OnDeserializedMethod(StreamingContext context)
		{
			AdditionalArguments?.Remove("cmd");
		}
	}
	public class SetReplyPacket : SetPacket
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.SetReply;

		[JsonProperty("value")]
		public JToken Value { get; set; }

		[JsonProperty("original_value")]
		public JToken OriginalValue { get; set; }
	}
	public class StatusUpdatePacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.StatusUpdate;

		[JsonProperty("status")]
		public ArchipelagoClientState Status { get; set; }
	}
	public class SyncPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Sync;
	}
	internal class UnknownPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.Unknown;
	}
	public class UpdateHintPacket : ArchipelagoPacketBase
	{
		public override ArchipelagoPacketType PacketType => ArchipelagoPacketType.UpdateHint;

		[JsonProperty("player")]
		public int Player { get; set; }

		[JsonProperty("location")]
		public long Location { get; set; }

		[JsonProperty("status")]
		public HintStatus Status { get; set; }
	}
}
namespace Archipelago.MultiClient.Net.Models
{
	public struct Color : IEquatable<Color>
	{
		public static Color Red = new Color(byte.MaxValue, 0, 0);

		public static Color Green = new Color(0, 128, 0);

		public static Color Yellow = new Color(byte.MaxValue, byte.MaxValue, 0);

		public static Color Blue = new Color(0, 0, byte.MaxValue);

		public static Color Magenta = new Color(byte.MaxValue, 0, byte.MaxValue);

		public static Color Cyan = new Color(0, byte.MaxValue, byte.MaxValue);

		public static Color Black = new Color(0, 0, 0);

		public static Color White = new Color(byte.MaxValue, byte.MaxValue, byte.MaxValue);

		public static Color SlateBlue = new Color(106, 90, 205);

		public static Color Salmon = new Color(250, 128, 114);

		public static Color Plum = new Color(221, 160, 221);

		public byte R { get; set; }

		public byte G { get; set; }

		public byte B { get; set; }

		public Color(byte r, byte g, byte b)
		{
			R = r;
			G = g;
			B = b;
		}

		public override bool Equals(object obj)
		{
			if (obj is Color color && R == color.R && G == color.G)
			{
				return B == color.B;
			}
			return false;
		}

		public bool Equals(Color other)
		{
			if (R == other.R && G == other.G)
			{
				return B == other.B;
			}
			return false;
		}

		public override int GetHashCode()
		{
			return ((-1520100960 * -1521134295 + R.GetHashCode()) * -1521134295 + G.GetHashCode()) * -1521134295 + B.GetHashCode();
		}

		public static bool operator ==(Color left, Color right)
		{
			return left.Equals(right);
		}

		public static bool operator !=(Color left, Color right)
		{
			return !(left == right);
		}
	}
	public class DataPackage
	{
		[JsonProperty("games")]
		public Dictionary<string, GameData> Games { get; set; } = new Dictionary<string, GameData>();

	}
	public class DataStorageElement
	{
		internal DataStorageElementContext Context;

		internal List<OperationSpecification> Operations = new List<OperationSpecification>(0);

		internal DataStorageHelper.DataStorageUpdatedHandler Callbacks;

		internal Dictionary<string, JToken> AdditionalArguments = new Dictionary<string, JToken>(0);

		private JToken cachedValue;

		public event DataStorageHelper.DataStorageUpdatedHandler OnValueChanged
		{
			add
			{
				Context.AddHandler(Context.Key, value);
			}
			remove
			{
				Context.RemoveHandler(Context.Key, value);
			}
		}

		internal DataStorageElement(DataStorageElementContext context)
		{
			Context = context;
		}

		internal DataStorageElement(OperationType operationType, JToken value)
		{
			Operations = new List<OperationSpecification>(1)
			{
				new OperationSpecification
				{
					OperationType = operationType,
					Value = value
				}
			};
		}

		internal DataStorageElement(DataStorageElement source, OperationType operationType, JToken value)
			: this(source.Context)
		{
			Operations = source.Operations.ToList();
			Callbacks = source.Callbacks;
			AdditionalArguments = source.AdditionalArguments;
			Operations.Add(new OperationSpecification
			{
				OperationType = operationType,
				Value = value
			});
		}

		internal DataStorageElement(DataStorageElement source, Callback callback)
			: this(source.Context)
		{
			Operations = source.Operations.ToList();
			Callbacks = source.Callbacks;
			AdditionalArguments = source.AdditionalArguments;
			Callbacks = (DataStorageHelper.DataStorageUpdatedHandler)Delegate.Combine(Callbacks, callback.Method);
		}

		internal DataStorageElement(DataStorageElement source, AdditionalArgument additionalArgument)
			: this(source.Context)
		{
			Operations = source.Operations.ToList();
			Callbacks = source.Callbacks;
			AdditionalArguments = source.AdditionalArguments;
			AdditionalArguments[additionalArgument.Key] = additionalArgument.Value;
		}

		public static DataStorageElement operator ++(DataStorageElement a)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(1));
		}

		public static DataStorageElement operator --(DataStorageElement a)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(-1));
		}

		public static DataStorageElement operator +(DataStorageElement a, int b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator +(DataStorageElement a, long b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator +(DataStorageElement a, float b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator +(DataStorageElement a, double b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator +(DataStorageElement a, decimal b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator +(DataStorageElement a, string b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator +(DataStorageElement a, JToken b)
		{
			return new DataStorageElement(a, OperationType.Add, b);
		}

		public static DataStorageElement operator +(DataStorageElement a, IEnumerable b)
		{
			return new DataStorageElement(a, OperationType.Add, (JToken)(object)JArray.FromObject((object)b));
		}

		public static DataStorageElement operator +(DataStorageElement a, OperationSpecification s)
		{
			return new DataStorageElement(a, s.OperationType, s.Value);
		}

		public static DataStorageElement operator +(DataStorageElement a, Callback c)
		{
			return new DataStorageElement(a, c);
		}

		public static DataStorageElement operator +(DataStorageElement a, AdditionalArgument arg)
		{
			return new DataStorageElement(a, arg);
		}

		public static DataStorageElement operator *(DataStorageElement a, int b)
		{
			return new DataStorageElement(a, OperationType.Mul, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator *(DataStorageElement a, long b)
		{
			return new DataStorageElement(a, OperationType.Mul, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator *(DataStorageElement a, float b)
		{
			return new DataStorageElement(a, OperationType.Mul, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator *(DataStorageElement a, double b)
		{
			return new DataStorageElement(a, OperationType.Mul, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator *(DataStorageElement a, decimal b)
		{
			return new DataStorageElement(a, OperationType.Mul, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator %(DataStorageElement a, int b)
		{
			return new DataStorageElement(a, OperationType.Mod, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator %(DataStorageElement a, long b)
		{
			return new DataStorageElement(a, OperationType.Mod, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator %(DataStorageElement a, float b)
		{
			return new DataStorageElement(a, OperationType.Mod, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator %(DataStorageElement a, double b)
		{
			return new DataStorageElement(a, OperationType.Mod, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator %(DataStorageElement a, decimal b)
		{
			return new DataStorageElement(a, OperationType.Mod, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator ^(DataStorageElement a, int b)
		{
			return new DataStorageElement(a, OperationType.Pow, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator ^(DataStorageElement a, long b)
		{
			return new DataStorageElement(a, OperationType.Pow, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator ^(DataStorageElement a, float b)
		{
			return new DataStorageElement(a, OperationType.Pow, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator ^(DataStorageElement a, double b)
		{
			return new DataStorageElement(a, OperationType.Pow, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator ^(DataStorageElement a, decimal b)
		{
			return new DataStorageElement(a, OperationType.Pow, JToken.op_Implicit(b));
		}

		public static DataStorageElement operator -(DataStorageElement a, int b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.FromObject((object)(-b)));
		}

		public static DataStorageElement operator -(DataStorageElement a, long b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.FromObject((object)(-b)));
		}

		public static DataStorageElement operator -(DataStorageElement a, float b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.FromObject((object)(0f - b)));
		}

		public static DataStorageElement operator -(DataStorageElement a, double b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.FromObject((object)(0.0 - b)));
		}

		public static DataStorageElement operator -(DataStorageElement a, decimal b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.FromObject((object)(-b)));
		}

		public static DataStorageElement operator /(DataStorageElement a, int b)
		{
			return new DataStorageElement(a, OperationType.Mul, JToken.FromObject((object)(1m / (decimal)b)));
		}

		public static DataStorageElement operator /(DataStorageElement a, long b)
		{
			return new DataStorageElement(a, OperationType.Mul, JToken.FromObject((object)(1m / (decimal)b)));
		}

		public static DataStorageElement operator /(DataStorageElement a, float b)
		{
			return new DataStorageElement(a, OperationType.Mul, JToken.FromObject((object)(1.0 / (double)b)));
		}

		public static DataStorageElement operator /(DataStorageElement a, double b)
		{
			return new DataStorageElement(a, OperationType.Mul, JToken.FromObject((object)(1.0 / b)));
		}

		public static DataStorageElement operator /(DataStorageElement a, decimal b)
		{
			return new DataStorageElement(a, OperationType.Mul, JToken.FromObject((object)(1m / b)));
		}

		public static implicit operator DataStorageElement(bool b)
		{
			return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(b));
		}

		public static implicit operator DataStorageElement(int i)
		{
			return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(i));
		}

		public static implicit operator DataStorageElement(long l)
		{
			return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(l));
		}

		public static implicit operator DataStorageElement(decimal m)
		{
			return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(m));
		}

		public static implicit operator DataStorageElement(double d)
		{
			return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(d));
		}

		public static implicit operator DataStorageElement(float f)
		{
			return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(f));
		}

		public static implicit operator DataStorageElement(string s)
		{
			if (s != null)
			{
				return new DataStorageElement(OperationType.Replace, JToken.op_Implicit(s));
			}
			return new DataStorageElement(OperationType.Replace, (JToken)(object)JValue.CreateNull());
		}

		public static implicit operator DataStorageElement(JToken o)
		{
			return new DataStorageElement(OperationType.Replace, o);
		}

		public static implicit operator DataStorageElement(Array a)
		{
			return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)a));
		}

		public static implicit operator DataStorageElement(List<bool> l)
		{
			return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l));
		}

		public static implicit operator DataStorageElement(List<int> l)
		{
			return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l));
		}

		public static implicit operator DataStorageElement(List<long> l)
		{
			return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l));
		}

		public static implicit operator DataStorageElement(List<decimal> l)
		{
			return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l));
		}

		public static implicit operator DataStorageElement(List<double> l)
		{
			return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l));
		}

		public static implicit operator DataStorageElement(List<float> l)
		{
			return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l));
		}

		public static implicit operator DataStorageElement(List<string> l)
		{
			return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l));
		}

		public static implicit operator DataStorageElement(List<object> l)
		{
			return new DataStorageElement(OperationType.Replace, (JToken)(object)JArray.FromObject((object)l));
		}

		public static implicit operator bool(DataStorageElement e)
		{
			return RetrieveAndReturnBoolValue<bool>(e);
		}

		public static implicit operator bool?(DataStorageElement e)
		{
			return RetrieveAndReturnBoolValue<bool?>(e);
		}

		public static implicit operator int(DataStorageElement e)
		{
			return RetrieveAndReturnDecimalValue<int>(e);
		}

		public static implicit operator int?(DataStorageElement e)
		{
			return RetrieveAndReturnDecimalValue<int?>(e);
		}

		public static implicit operator long(DataStorageElement e)
		{
			return RetrieveAndReturnDecimalValue<long>(e);
		}

		public static implicit operator long?(DataStorageElement e)
		{
			return RetrieveAndReturnDecimalValue<long?>(e);
		}

		public static implicit operator float(DataStorageElement e)
		{
			return RetrieveAndReturnDecimalValue<float>(e);
		}

		public static implicit operator float?(DataStorageElement e)
		{
			return RetrieveAndReturnDecimalValue<float?>(e);
		}

		public static implicit operator double(DataStorageElement e)
		{
			return RetrieveAndReturnDecimalValue<double>(e);
		}

		public static implicit operator double?(DataStorageElement e)
		{
			return RetrieveAndReturnDecimalValue<double?>(e);
		}

		public static implicit operator decimal(DataStorageElement e)
		{
			return RetrieveAndReturnDecimalValue<decimal>(e);
		}

		public static implicit operator decimal?(DataStorageElement e)
		{
			return RetrieveAndReturnDecimalValue<decimal?>(e);
		}

		public static implicit operator string(DataStorageElement e)
		{
			return RetrieveAndReturnStringValue(e);
		}

		public static implicit operator bool[](DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<bool[]>(e);
		}

		public static implicit operator int[](DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<int[]>(e);
		}

		public static implicit operator long[](DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<long[]>(e);
		}

		public static implicit operator decimal[](DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<decimal[]>(e);
		}

		public static implicit operator double[](DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<double[]>(e);
		}

		public static implicit operator float[](DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<float[]>(e);
		}

		public static implicit operator string[](DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<string[]>(e);
		}

		public static implicit operator object[](DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<object[]>(e);
		}

		public static implicit operator List<bool>(DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<List<bool>>(e);
		}

		public static implicit operator List<int>(DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<List<int>>(e);
		}

		public static implicit operator List<long>(DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<List<long>>(e);
		}

		public static implicit operator List<decimal>(DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<List<decimal>>(e);
		}

		public static implicit operator List<double>(DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<List<double>>(e);
		}

		public static implicit operator List<float>(DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<List<float>>(e);
		}

		public static implicit operator List<string>(DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<List<string>>(e);
		}

		public static implicit operator List<object>(DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<List<object>>(e);
		}

		public static implicit operator Array(DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<Array>(e);
		}

		public static implicit operator JArray(DataStorageElement e)
		{
			return RetrieveAndReturnArrayValue<JArray>(e);
		}

		public static implicit operator JToken(DataStorageElement e)
		{
			return e.Context.GetData(e.Context.Key);
		}

		public static DataStorageElement operator +(DataStorageElement a, BigInteger b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.Parse(b.ToString()));
		}

		public static DataStorageElement operator *(DataStorageElement a, BigInteger b)
		{
			return new DataStorageElement(a, OperationType.Mul, JToken.Parse(b.ToString()));
		}

		public static DataStorageElement operator %(DataStorageElement a, BigInteger b)
		{
			return new DataStorageElement(a, OperationType.Mod, JToken.Parse(b.ToString()));
		}

		public static DataStorageElement operator ^(DataStorageElement a, BigInteger b)
		{
			return new DataStorageElement(a, OperationType.Pow, JToken.Parse(b.ToString()));
		}

		public static DataStorageElement operator -(DataStorageElement a, BigInteger b)
		{
			return new DataStorageElement(a, OperationType.Add, JToken.Parse((-b).ToString()));
		}

		public static DataStorageElement operator /(DataStorageElement a, BigInteger b)
		{
			throw new InvalidOperationException("DataStorage[Key] / BigInterger is not supported, due to loss of precision when using integer division");
		}

		public static implicit operator DataStorageElement(BigInteger bi)
		{
			return new DataStorageElement(OperationType.Replace, JToken.Parse(bi.ToString()));
		}

		public static implicit operator BigInteger(DataStorageElement e)
		{
			return RetrieveAndReturnBigIntegerValue<BigInteger>(e);
		}

		public static implicit operator BigInteger?(DataStorageElement e)
		{
			return RetrieveAndReturnBigIntegerValue<BigInteger?>(e);
		}

		private static T RetrieveAndReturnBigIntegerValue<T>(DataStorageElement e)
		{
			if (e.cachedValue != null)
			{
				if (!BigInteger.TryParse(((object)e.cachedValue).ToString(), out var result))
				{
					return default(T);
				}
				return (T)Convert.ChangeType(result, IsNullable<T>() ? Nullable.GetUnderlyingType(typeof(T)) : typeof(T));
			}
			BigInteger result2;
			BigInteger? bigInteger = (BigInteger.TryParse(((object)e.Context.GetData(e.Context.Key)).ToString(), out result2) ? new BigInteger?(result2) : null);
			if (!bigInteger.HasValue && !IsNullable<T>())
			{
				bigInteger = Activator.CreateInstance<BigInteger>();
			}
			foreach (OperationSpecification operation in e.Operations)
			{
				if (operation.OperationType == OperationType.Floor || operation.OperationType == OperationType.Ceil)
				{
					continue;
				}
				if (!BigInteger.TryParse(((object)operation.Value).ToString(), NumberStyles.AllowLeadingSign, null, out var result3))
				{
					throw new InvalidOperationException($"DataStorage[Key] cannot be converted to BigInterger as its value its not an integer number, value: {operation.Value}");
				}
				switch (operation.OperationType)
				{
				case OperationType.Replace:
					bigInteger = result3;
					break;
				case OperationType.Add:
					bigInteger += result3;
					break;
				case OperationType.Mul:
					bigInteger *= result3;
					break;
				case OperationType.Mod:
					bigInteger %= result3;
					break;
				case OperationType.Pow:
					bigInteger = BigInteger.Pow(bigInteger.Value, (int)operation.Value);
					break;
				case OperationType.Max:
				{
					BigInteger value = result3;
					BigInteger? bigInteger2 = bigInteger;
					if (value > bigInteger2)
					{
						bigInteger = result3;
					}
					break;
				}
				case OperationType.Min:
				{
					BigInteger value = result3;
					BigInteger? bigInteger2 = bigInteger;
					if (value < bigInteger2)
					{
						bigInteger = result3;
					}
					break;
				}
				case OperationType.Xor:
					bigInteger ^= result3;
					break;
				case OperationType.Or:
					bigInteger |= result3;
					break;
				case OperationType.And:
					bigInteger &= result3;
					break;
				case OperationType.LeftShift:
					bigInteger <<= (int)operation.Value;
					break;
				case OperationType.RightShift:
					bigInteger >>= (int)operation.Value;
					break;
				}
			}
			e.cachedValue = JToken.Parse(bigInteger.ToString());
			if (!bigInteger.HasValue)
			{
				return default(T);
			}
			return (T)Convert.ChangeType(bigInteger.Value, IsNullable<T>() ? Nullable.GetUnderlyingType(typeof(T)) : typeof(T));
		}

		public void Initialize(JToken value)
		{
			Context.Initialize(Context.Key, value);
		}

		public void Initialize(IEnumerable value)
		{
			Context.Initialize(Context.Key, (JToken)(object)JArray.FromObject((object)value));
		}

		public Task<T> GetAsync<T>()
		{
			return GetAsync().ContinueWith((Task<JToken> r) => r.Result.ToObject<T>());
		}

		public Task<JToken> GetAsync()
		{
			return Context.GetAsync(Context.Key);
		}

		private static T RetrieveAndReturnArrayValue<T>(DataStorageElement e)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Invalid comparison between Unknown and I4
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Invalid comparison between Unknown and I4
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			if (e.cachedValue != null)
			{
				return ((JToken)(JArray)e.cachedValue).ToObject<T>();
			}
			JArray val = (JArray)(((object)e.Context.GetData(e.Context.Key).ToObject<JArray>()) ?? ((object)new JArray()));
			foreach (OperationSpecification operation in e.Operations)
			{
				switch (operation.OperationType)
				{
				case OperationType.Add:
					if ((int)operation.Value.Type != 2)
					{
						throw new InvalidOperationException($"Cannot perform operation {OperationType.Add} on Array value, with a non Array value: {operation.Value}");
					}
					((JContainer)val).Merge((object)operation.Value);
					break;
				case OperationType.Replace:
					if ((int)operation.Value.Type != 2)
					{
						throw new InvalidOperationException($"Cannot replace Array value, with a non Array value: {operation.Value}");
					}
					val = (JArray)(((object)operation.Value.ToObject<JArray>()) ?? ((object)new JArray()));
					break;
				default:
					throw new InvalidOperationException($"Cannot perform operation {operation.OperationType} on Array value");
				}
			}
			e.cachedValue = (JToken)(object)val;
			return ((JToken)val).ToObject<T>();
		}

		private static string RetrieveAndReturnStringValue(DataStorageElement e)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Invalid comparison between Unknown and I4
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Invalid comparison between Unknown and I4
			if (e.cachedValue != null)
			{
				return (string)e.cachedValue;
			}
			JToken val = e.Context.GetData(e.Context.Key);
			string text = (((int)val.Type == 10) ? null : ((object)val).ToString());
			foreach (OperationSpecification operation in e.Operations)
			{
				switch (operation.OperationType)
				{
				case OperationType.Add:
					text += (string)operation.Value;
					break;
				case OperationType.Mul:
					if ((int)operation.Value.Type != 6)
					{
						throw new InvalidOperationException($"Cannot perform operation {OperationType.Mul} on string value, with a non interger value: {operation.Value}");
					}
					text = string.Concat(Enumerable.Repeat(text, (int)operation.Value));
					break;
				case OperationType.Replace:
					text = (string)operation.Value;
					break;
				default:
					throw new InvalidOperationException($"Cannot perform operation {operation.OperationType} on string value");
				}
			}
			if (text == null)
			{
				e.cachedValue = (JToken)(object)JValue.CreateNull();
			}
			else
			{
				e.cachedValue = JToken.op_Implicit(text);
			}
			return (string)e.cachedValue;
		}

		private static T RetrieveAndReturnBoolValue<T>(DataStorageElement e)
		{
			if (e.cachedValue != null)
			{
				return e.cachedValue.ToObject<T>();
			}
			bool? flag = e.Context.GetData(e.Context.Key).ToObject<bool?>() ?? ((bool?)Activator.CreateInstance(typeof(T)));
			foreach (OperationSpecification operation in e.Operations)
			{
				if (operation.OperationType == OperationType.Replace)
				{
					flag = (bool?)operation.Value;
					continue;
				}
				throw new InvalidOperationException($"Cannot perform operation {operation.OperationType} on boolean value");
			}
			e.cachedValue = JToken.op_Implicit(flag);
			if (!flag.HasValue)
			{
				return default(T);
			}
			return (T)Convert.ChangeType(flag.Value, IsNullable<T>() ? Nullable.GetUnderlyingType(typeof(T)) : typeof(T));
		}

		private static T RetrieveAndReturnDecimalValue<T>(DataStorageElement e)
		{
			if (e.cachedValue != null)
			{
				return e.cachedValue.ToObject<T>();
			}
			decimal? num = e.Context.GetData(e.Context.Key).ToObject<decimal?>();
			if (!num.HasValue && !IsNullable<T>())
			{
				num = Activator.CreateInstance<decimal>();
			}
			foreach (OperationSpecification operation in e.Operations)
			{
				switch (operation.OperationType)
				{
				case OperationType.Replace:
					num = (decimal)operation.Value;
					break;
				case OperationType.Add:
					num += (decimal?)(decimal)operation.Value;
					break;
				case OperationType.Mul:
					num *= (decimal?)(decimal)operation.Value;
					break;
				case OperationType.Mod:
					num %= (decimal?)(decimal)operation.Value;
					break;
				case OperationType.Pow:
					num = (decimal)Math.Pow((double)num.Value, (double)operation.Value);
					break;
				case OperationType.Max:
					num = Math.Max(num.Value, (decimal)operation.Value);
					break;
				case OperationType.Min:
					num = Math.Min(num.Value, (decimal)operation.Value);
					break;
				case OperationType.Xor:
					num = (long)num.Value ^ (long)operation.Value;
					break;
				case OperationType.Or:
					num = (long)num.Value | (long)operation.Value;
					break;
				case OperationType.And:
					num = (long)num.Value & (long)operation.Value;
					break;
				case OperationType.LeftShift:
					num = (long)num.Value << (int)operation.Value;
					break;
				case OperationType.RightShift:
					num = (long)num.Value >> (int)operation.Value;
					break;
				case OperationType.Floor:
					num = Math.Floor(num.Value);
					break;
				case OperationType.Ceil:
					num = Math.Ceiling(num.Value);
					break;
				}
			}
			e.cachedValue = JToken.op_Implicit(num);
			if (!num.HasValue)
			{
				return default(T);
			}
			return (T)Convert.ChangeType(num.Value, IsNullable<T>() ? Nullable.GetUnderlyingType(typeof(T)) : typeof(T));
		}

		private static bool IsNullable<T>()
		{
			if (typeof(T).IsGenericType)
			{
				return typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>).GetGenericTypeDefinition();
			}
			return false;
		}

		public T To<T>()
		{
			if (Operations.Count != 0)
			{
				throw new InvalidOperationException("DataStorageElement.To<T>() cannot be used together with other operations on the DataStorageElement");
			}
			return Context.GetData(Context.Key).ToObject<T>();
		}

		public override string ToString()
		{
			return (Context?.ToString() ?? "(null)") + ", (" + ListOperations() + ")";
		}

		private string ListOperations()
		{
			if (Operations != null)
			{
				return string.Join(", ", Operations.Select((OperationSpecification o) => o.ToString()).ToArray());
			}
			return "none";
		}
	}
	internal class DataStorageElementContext
	{
		internal string Key { get; set; }

		internal Action<string, DataStorageHelper.DataStorageUpdatedHandler> AddHandler { get; set; }

		internal Action<string, DataStorageHelper.DataStorageUpdatedHandler> RemoveHandler { get; set; }

		internal Func<string, JToken> GetData { get; set; }

		internal Action<string, JToken> Initialize { get; set; }

		internal Func<string, Task<JToken>> GetAsync { get; set; }

		public override string ToString()
		{
			return "Key: " + Key;
		}
	}
	public class GameData
	{
		[JsonProperty("location_name_to_id")]
		public Dictionary<string, long> LocationLookup { get; set; } = new Dictionary<string, long>();


		[JsonProperty("item_name_to_id")]
		public Dictionary<string, long> ItemLookup { get; set; } = new Dictionary<string, long>();


		[Obsolete("use Checksum instead")]
		[JsonProperty("version")]
		public int Version { get; set; }

		[JsonProperty("checksum")]
		public string Checksum { get; set; }
	}
	public class Hint
	{
		[JsonProperty("receiving_player")]
		public int ReceivingPlayer { get; set; }

		[JsonProperty("finding_player")]
		public int FindingPlayer { get; set; }

		[JsonProperty("item")]
		public long ItemId { get; set; }

		[JsonProperty("location")]
		public long LocationId { get; set; }

		[JsonProperty("item_flags")]
		public ItemFlags ItemFlags { get; set; }

		[JsonProperty("found")]
		public bool Found { get; set; }

		[JsonProperty("entrance")]
		public string Entrance { get; set; }

		[JsonProperty("status")]
		public HintStatus Status { get; set; }
	}
	public class ItemInfo
	{
		private readonly IItemInfoResolver itemInfoResolver;

		public long ItemId { get; }

		public long LocationId { get; }

		public PlayerInfo Player { get; }

		public ItemFlags Flags { get; }

		public string ItemName => itemInfoResolver.GetItemName(ItemId, ItemGame);

		public string ItemDisplayName => ItemName ?? $"Item: {ItemId}";

		public string LocationName => itemInfoResolver.GetLocationName(LocationId, LocationGame);

		public string LocationDisplayName => LocationName ?? $"Location: {LocationId}";

		public string ItemGame { get; }

		public string LocationGame { get; }

		public ItemInfo(NetworkItem item, string receiverGame, string senderGame, IItemInfoResolver itemInfoResolver, PlayerInfo player)
		{
			this.itemInfoResolver = itemInfoResolver;
			ItemGame = receiverGame;
			LocationGame = senderGame;
			ItemId = item.Item;
			LocationId = item.Location;
			Flags = item.Flags;
			Player = player;
		}

		public SerializableItemInfo ToSerializable()
		{
			return new SerializableItemInfo
			{
				IsScout = (GetType() == typeof(ScoutedItemInfo)),
				ItemId = ItemId,
				LocationId = LocationId,
				PlayerSlot = Player,
				Player = Player,
				Flags = Flags,
				ItemGame = ItemGame,
				ItemName = ItemName,
				LocationGame = LocationGame,
				LocationName = LocationName
			};
		}
	}
	public class ScoutedItemInfo : ItemInfo
	{
		public new PlayerInfo Player => base.Player;

		public bool IsReceiverRelatedToActivePlayer { get; }

		public ScoutedItemInfo(NetworkItem item, string receiverGame, string senderGame, IItemInfoResolver itemInfoResolver, IPlayerHelper players, PlayerInfo player)
			: base(item, receiverGame, senderGame, itemInfoResolver, player)
		{
			IsReceiverRelatedToActivePlayer = (players.ActivePlayer ?? new PlayerInfo()).IsRelatedTo(player);
		}
	}
	public class JsonMessagePart
	{
		[JsonProperty("type")]
		[JsonConverter(typeof(AttemptingStringEnumConverter), new object[] { typeof(SnakeCaseNamingStrategy) })]
		public JsonMessagePartType? Type { get; set; }

		[JsonProperty("color")]
		[JsonConverter(typeof(AttemptingStringEnumConverter), new object[] { typeof(SnakeCaseNamingStrategy) })]
		public JsonMessagePartColor? Color { get; set; }

		[JsonProperty("text")]
		public string Text { get; set; }

		[JsonProperty("player")]
		public int? Player { get; set; }

		[JsonProperty("flags")]
		public ItemFlags? Flags { get; set; }

		[JsonProperty("hint_status")]
		public HintStatus? HintStatus { get; set; }
	}
	public struct NetworkItem
	{
		[JsonProperty("item")]
		public long Item { get; set; }

		[JsonProperty("location")]
		public long Location { get; set; }

		[JsonProperty("player")]
		public int Player { get; set; }

		[JsonProperty("flags")]
		public ItemFlags Flags { get; set; }
	}
	public struct NetworkPlayer
	{
		[JsonProperty("team")]
		public int Team { get; set; }

		[JsonProperty("slot")]
		public int Slot { get; set; }

		[JsonProperty("alias")]
		public string Alias { get; set; }

		[JsonProperty("name")]
		public string Name { get; set; }
	}
	public struct NetworkSlot
	{
		[JsonProperty("name")]
		public string Name { get; set; }

		[JsonProperty("game")]
		public string Game { get; set; }

		[JsonProperty("type")]
		public SlotType Type { get; set; }

		[JsonProperty("group_members")]
		public int[] GroupMembers { get; set; }
	}
	public class NetworkVersion
	{
		[JsonProperty("major")]
		public int Major { get; set; }

		[JsonProperty("minor")]
		public int Minor { get; set; }

		[JsonProperty("build")]
		public int Build { get; set; }

		[JsonProperty("class")]
		public string Class => "Version";

		public NetworkVersion()
		{
		}

		public NetworkVersion(int major, int minor, int build)
		{
			Major = major;
			Minor = minor;
			Build = build;
		}

		public NetworkVersion(Version version)
		{
			Major = version.Major;
			Minor = version.Minor;
			Build = version.Build;
		}

		public Version ToVersion()
		{
			return new Version(Major, Minor, Build);
		}
	}
	public class OperationSpecification
	{
		[JsonProperty("operation")]
		[JsonConverter(typeof(AttemptingStringEnumConverter), new object[] { typeof(SnakeCaseNamingStrategy) })]
		public OperationType OperationType;

		[JsonProperty("value")]
		public JToken Value { get; set; }

		public override string ToString()
		{
			return $"{OperationType}: {Value}";
		}
	}
	public static class Operation
	{
		public static OperationSpecification Min(int i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Min,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification Min(long i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Min,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification Min(float i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Min,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification Min(double i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Min,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification Min(decimal i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Min,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification Min(JToken i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Min,
				Value = i
			};
		}

		public static OperationSpecification Min(BigInteger i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Min,
				Value = JToken.Parse(i.ToString())
			};
		}

		public static OperationSpecification Max(int i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Max,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification Max(long i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Max,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification Max(float i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Max,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification Max(double i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Max,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification Max(decimal i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Max,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification Max(JToken i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Max,
				Value = i
			};
		}

		public static OperationSpecification Max(BigInteger i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Max,
				Value = JToken.Parse(i.ToString())
			};
		}

		public static OperationSpecification Remove(JToken value)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Remove,
				Value = value
			};
		}

		public static OperationSpecification Pop(int value)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Pop,
				Value = JToken.op_Implicit(value)
			};
		}

		public static OperationSpecification Pop(JToken value)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Pop,
				Value = value
			};
		}

		public static OperationSpecification Update(IDictionary dictionary)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Update,
				Value = (JToken)(object)JObject.FromObject((object)dictionary)
			};
		}

		public static OperationSpecification Floor()
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Floor,
				Value = null
			};
		}

		public static OperationSpecification Ceiling()
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Ceil,
				Value = null
			};
		}
	}
	public static class Bitwise
	{
		public static OperationSpecification Xor(long i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Xor,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification Xor(BigInteger i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Xor,
				Value = JToken.Parse(i.ToString())
			};
		}

		public static OperationSpecification Or(long i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Or,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification Or(BigInteger i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.Or,
				Value = JToken.Parse(i.ToString())
			};
		}

		public static OperationSpecification And(long i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.And,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification And(BigInteger i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.And,
				Value = JToken.Parse(i.ToString())
			};
		}

		public static OperationSpecification LeftShift(long i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.LeftShift,
				Value = JToken.op_Implicit(i)
			};
		}

		public static OperationSpecification RightShift(long i)
		{
			return new OperationSpecification
			{
				OperationType = OperationType.RightShift,
				Value = JToken.op_Implicit(i)
			};
		}
	}
	public class Callback
	{
		internal DataStorageHelper.DataStorageUpdatedHandler Method { get; set; }

		private Callback()
		{
		}

		public static Callback Add(DataStorageHelper.DataStorageUpdatedHandler callback)
		{
			return new Callback
			{
				Method = callback
			};
		}
	}
	public class AdditionalArgument
	{
		internal string Key { get; set; }

		internal JToken Value { get; set; }

		private AdditionalArgument()
		{
		}

		public static AdditionalArgument Add(string name, JToken value)
		{
			return new AdditionalArgument
			{
				Key = name,
				Value = value
			};
		}
	}
	public class MinimalSerializableItemInfo
	{
		public long ItemId { get; set; }

		public long LocationId { get; set; }

		public int PlayerSlot { get; set; }

		public ItemFlags Flags { get; set; }

		public string ItemGame { get; set; }

		public string LocationGame { get; set; }
	}
	public class SerializableItemInfo : MinimalSerializableItemInfo
	{
		public bool IsScout { get; set; }

		public PlayerInfo Player { get; set; }

		public string ItemName { get; set; }

		public string LocationName { get; set; }

		[JsonIgnore]
		public string ItemDisplayName => ItemName ?? $"Item: {base.ItemId}";

		[JsonIgnore]
		public string LocationDisplayName => LocationName ?? $"Location: {base.LocationId}";

		public string ToJson(bool full = false)
		{
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Expected O, but got Unknown
			MinimalSerializableItemInfo minimalSerializableItemInfo = this;
			if (!full)
			{
				minimalSerializableItemInfo = new MinimalSerializableItemInfo
				{
					ItemId = base.ItemId,
					LocationId = base.LocationId,
					PlayerSlot = base.PlayerSlot,
					Flags = base.Flags
				};
				if (IsScout)
				{
					minimalSerializableItemInfo.ItemGame = base.ItemGame;
				}
				else
				{
					minimalSerializableItemInfo.LocationGame = base.LocationGame;
				}
			}
			JsonSerializerSettings val = new JsonSerializerSettings
			{
				NullValueHandling = (NullValueHandling)1,
				Formatting = (Formatting)0
			};
			return JsonConvert.SerializeObject((object)minimalSerializableItemInfo, val);
		}

		public static SerializableItemInfo FromJson(string json, IArchipelagoSession session = null)
		{
			//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_0050: Expected O, but got Unknown
			ItemInfoStreamingContext additional = ((session != null) ? new ItemInfoStreamingContext
			{
				Items = session.Items,
				Locations = session.Locations,
				PlayerHelper = session.Players,
				ConnectionInfo = session.ConnectionInfo
			} : null);
			JsonSerializerSettings val = new JsonSerializerSettings
			{
				Context = new StreamingContext(StreamingContextStates.Other, additional)
			};
			return JsonConvert.DeserializeObject<SerializableItemInfo>(json, val);
		}

		[OnDeserialized]
		internal void OnDeserializedMethod(StreamingContext streamingContext)
		{
			if (base.ItemGame == null && base.LocationGame != null)
			{
				IsScout = false;
			}
			else if (base.ItemGame != null && base.LocationGame == null)
			{
				IsScout = true;
			}
			if (streamingContext.Context is ItemInfoStreamingContext itemInfoStreamingContext)
			{
				if (IsScout && base.LocationGame == null)
				{
					base.LocationGame = itemInfoStreamingContext.ConnectionInfo.Game;
				}
				else if (!IsScout && base.ItemGame == null)
				{
					base.ItemGame = itemInfoStreamingContext.ConnectionInfo.Game;
				}
				if (ItemName == null)
				{
					ItemName = itemInfoStreamingContext.Items.GetItemName(base.ItemId, base.ItemGame);
				}
				if (LocationName == null)
				{
					LocationName = itemInfoStreamingContext.Locations.GetLocationNameFromId(base.LocationId, base.LocationGame);
				}
				if (Player == null)
				{
					Player = itemInfoStreamingContext.PlayerHelper.GetPlayerInfo(base.PlayerSlot);
				}
			}
		}
	}
	internal class ItemInfoStreamingContext
	{
		public IReceivedItemsHelper Items { get; set; }

		public ILocationCheckHelper Locations { get; set; }

		public IPlayerHelper PlayerHelper { get; set; }

		public IConnectionInfoProvider ConnectionInfo { get; set; }
	}
}
namespace Archipelago.MultiClient.Net.MessageLog.Parts
{
	public class EntranceMessagePart : MessagePart
	{
		internal EntranceMessagePart(JsonMessagePart messagePart)
			: base(MessagePartType.Entrance, messagePart, Archipelago.MultiClient.Net.Colors.PaletteColor.Blue)
		{
			base.Text = messagePart.Text;
		}
	}
	public class HintStatusMessagePart : MessagePart
	{
		internal HintStatusMessagePart(JsonMessagePart messagePart)
			: base(MessagePartType.HintStatus, messagePart)
		{
			base.Text = messagePart.Text;
			if (messagePart.HintStatus.HasValue)
			{
				base.PaletteColor = ColorUtils.GetColor(messagePart.HintStatus.Value);
			}
		}
	}
	public class ItemMessagePart : MessagePart
	{
		public ItemFlags Flags { get; }

		public long ItemId { get; }

		public int Player { get; }

		internal ItemMessagePart(IPlayerHelper players, IItemInfoResolver items, JsonMessagePart part)
			: base(MessagePartType.Item, part)
		{
			Flags = part.Flags.GetValueOrDefault();
			base.PaletteColor = ColorUtils.GetColor(Flags);
			Player = part.Player.GetValueOrDefault();
			string game = (players.GetPlayerInfo(Player) ?? new PlayerInfo()).Game;
			JsonMessagePartType? type = part.Type;
			if (type.HasValue)
			{
				switch (type.GetValueOrDefault())
				{
				case JsonMessagePartType.ItemId:
					ItemId = long.Parse(part.Text);
					base.Text = items.GetItemName(ItemId, game) ?? $"Item: {ItemId}";
					break;
				case JsonMessagePartType.ItemName:
					ItemId = 0L;
					base.Text = part.Text;
					break;
				}
			}
		}
	}
	public class LocationMessagePart : MessagePart
	{
		public long LocationId { get; }

		public int Player { get; }

		internal LocationMessagePart(IPlayerHelper players, IItemInfoResolver itemInfoResolver, JsonMessagePart part)
			: base(MessagePartType.Location, part, Archipelago.MultiClient.Net.Colors.PaletteColor.Green)
		{
			Player = part.Player.GetValueOrDefault();
			string game = (players.GetPlayerInfo(Player) ?? new PlayerInfo()).Game;
			JsonMessagePartType? type = part.Type;
			if (type.HasValue)
			{
				switch (type.GetValueOrDefault())
				{
				case JsonMessagePartType.LocationId:
					LocationId = long.Parse(part.Text);
					base.Text = itemInfoResolver.GetLocationName(LocationId, game) ?? $"Location: {LocationId}";
					break;
				case JsonMessagePartType.LocationName:
					LocationId = itemInfoResolver.GetLocationId(part.Text, game);
					base.Text = part.Text;
					break;
				}
			}
		}
	}
	public class MessagePart
	{
		public string Text { get; internal set; }

		public MessagePartType Type { get; internal set; }

		public Color Color => GetColor(BuiltInPalettes.Dark);

		public PaletteColor? PaletteColor { get; protected set; }

		public bool IsBackgroundColor { get; internal set; }

		internal MessagePart(MessagePartType type, JsonMessagePart messagePart, PaletteColor? color = null)
		{
			Type = type;
			Text = messagePart.Text;
			if (color.HasValue)
			{
				PaletteColor = color.Value;
			}
			else if (messagePart.Color.HasValue)
			{
				PaletteColor = ColorUtils.GetColor(messagePart.Color.Value);
				IsBackgroundColor = messagePart.Color.Value >= JsonMessagePartColor.BlackBg;
			}
			else
			{
				PaletteColor = null;
			}
		}

		public T GetColor<T>(Palette<T> palette)
		{
			return palette[PaletteColor];
		}

		public override string ToString()
		{
			return Text;
		}
	}
	public enum MessagePartType
	{
		Text,
		Player,
		Item,
		Location,
		Entrance,
		HintStatus
	}
	public class PlayerMessagePart : MessagePart
	{
		public bool IsActivePlayer { get; }

		public int SlotId { get; }

		internal PlayerMessagePart(IPlayerHelper players, IConnectionInfoProvider connectionInfo, JsonMessagePart part)
			: base(MessagePartType.Player, part)
		{
			switch (part.Type)
			{
			case JsonMessagePartType.PlayerId:
				SlotId = int.Parse(part.Text);
				IsActivePlayer = SlotId == connectionInfo.Slot;
				base.Text = players.GetPlayerAlias(SlotId) ?? $"Player {SlotId}";
				break;
			case JsonMessagePartType.PlayerName:
				SlotId = 0;
				IsActivePlayer = false;
				base.Text = part.Text;
				break;
			}
			base.PaletteColor = (IsActivePlayer ? Archipelago.MultiClient.Net.Colors.PaletteColor.Magenta : Archipelago.MultiClient.Net.Colors.PaletteColor.Yellow);
		}
	}
}
namespace Archipelago.MultiClient.Net.MessageLog.Messages
{
	public class AdminCommandResultLogMessage : LogMessage
	{
		internal AdminCommandResultLogMessage(MessagePart[] parts)
			: base(parts)
		{
		}
	}
	public class ChatLogMessage : PlayerSpecificLogMessage
	{
		public string Message { get; }

		internal ChatLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot, string message)
			: base(parts, players, team, slot)
		{
			Message = message;
		}
	}
	public class CollectLogMessage : PlayerSpecificLogMessage
	{
		internal CollectLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot)
			: base(parts, players, team, slot)
		{
		}
	}
	public class CommandResultLogMessage : LogMessage
	{
		internal CommandResultLogMessage(MessagePart[] parts)
			: base(parts)
		{
		}
	}
	public class CountdownLogMessage : LogMessage
	{
		public int RemainingSeconds { get; }

		internal CountdownLogMessage(MessagePart[] parts, int remainingSeconds)
			: base(parts)
		{
			RemainingSeconds = remainingSeconds;
		}
	}
	public class GoalLogMessage : PlayerSpecificLogMessage
	{
		internal GoalLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot)
			: base(parts, players, team, slot)
		{
		}
	}
	public class HintItemSendLogMessage : ItemSendLogMessage
	{
		public bool IsFound { get; }

		internal HintItemSendLogMessage(MessagePart[] parts, IPlayerHelper players, int receiver, int sender, NetworkItem item, bool found, IItemInfoResolver itemInfoResolver)
			: base(parts, players, receiver, sender, item, itemInfoResolver)
		{
			IsFound = found;
		}
	}
	public class ItemCheatLogMessage : ItemSendLogMessage
	{
		internal ItemCheatLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot, NetworkItem item, IItemInfoResolver itemInfoResolver)
			: base(parts, players, slot, 0, item, team, itemInfoResolver)
		{
		}
	}
	public class ItemSendLogMessage : LogMessage
	{
		private PlayerInfo ActivePlayer { get; }

		public PlayerInfo Receiver { get; }

		public PlayerInfo Sender { get; }

		public bool IsReceiverTheActivePlayer => Receiver == ActivePlayer;

		public bool IsSenderTheActivePlayer => Sender == ActivePlayer;

		public bool IsRelatedToActivePlayer
		{
			get
			{
				if (!ActivePlayer.IsRelatedTo(Receiver))
				{
					return ActivePlayer.IsRelatedTo(Sender);
				}
				return true;
			}
		}

		public ItemInfo Item { get; }

		internal ItemSendLogMessage(MessagePart[] parts, IPlayerHelper players, int receiver, int sender, NetworkItem item, IItemInfoResolver itemInfoResolver)
			: this(parts, players, receiver, sender, item, players.ActivePlayer.Team, itemInfoResolver)
		{
		}

		internal ItemSendLogMessage(MessagePart[] parts, IPlayerHelper players, int receiver, int sender, NetworkItem item, int team, IItemInfoResolver itemInfoResolver)
			: base(parts)
		{
			ActivePlayer = players.ActivePlayer ?? new PlayerInfo();
			Receiver = players.GetPlayerInfo(team, receiver) ?? new PlayerInfo();
			Sender = players.GetPlayerInfo(team, sender) ?? new PlayerInfo();
			PlayerInfo player = players.GetPlayerInfo(team, item.Player) ?? new PlayerInfo();
			Item = new ItemInfo(item, Receiver.Game, Sender.Game, itemInfoResolver, player);
		}
	}
	public class JoinLogMessage : PlayerSpecificLogMessage
	{
		public string[] Tags { get; }

		internal JoinLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot, string[] tags)
			: base(parts, players, team, slot)
		{
			Tags = tags;
		}
	}
	public class LeaveLogMessage : PlayerSpecificLogMessage
	{
		internal LeaveLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot)
			: base(parts, players, team, slot)
		{
		}
	}
	public class LogMessage
	{
		public MessagePart[] Parts { get; }

		internal LogMessage(MessagePart[] parts)
		{
			Parts = parts;
		}

		public override string ToString()
		{
			if (Parts.Length == 1)
			{
				return Parts[0].Text;
			}
			StringBuilder stringBuilder = new StringBuilder();
			MessagePart[] parts = Parts;
			foreach (MessagePart messagePart in parts)
			{
				stringBuilder.Append(messagePart.Text);
			}
			return stringBuilder.ToString();
		}
	}
	public abstract class PlayerSpecificLogMessage : LogMessage
	{
		private PlayerInfo ActivePlayer { get; }

		public PlayerInfo Player { get; }

		public bool IsActivePlayer => Player == ActivePlayer;

		public bool IsRelatedToActivePlayer => ActivePlayer.IsRelatedTo(Player);

		internal PlayerSpecificLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot)
			: base(parts)
		{
			ActivePlayer = players.ActivePlayer ?? new PlayerInfo();
			Player = players.GetPlayerInfo(team, slot) ?? new PlayerInfo();
		}
	}
	public class ReleaseLogMessage : PlayerSpecificLogMessage
	{
		internal ReleaseLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot)
			: base(parts, players, team, slot)
		{
		}
	}
	public class ServerChatLogMessage : LogMessage
	{
		public string Message { get; }

		internal ServerChatLogMessage(MessagePart[] parts, string message)
			: base(parts)
		{
			Message = message;
		}
	}
	public class TagsChangedLogMessage : PlayerSpecificLogMessage
	{
		public string[] Tags { get; }

		internal TagsChangedLogMessage(MessagePart[] parts, IPlayerHelper players, int team, int slot, string[] tags)
			: base(parts, players, team, slot)
		{
			Tags = tags;
		}
	}
	public class TutorialLogMessage : LogMessage
	{
		internal TutorialLogMessage(MessagePart[] parts)
			: base(parts)
		{
		}
	}
}
namespace Archipelago.MultiClient.Net.Helpers
{
	public class ArchipelagoSocketHelper : BaseArchipelagoSocketHelper<ClientWebSocket>, IArchipelagoSocketHelper
	{
		public Uri Uri { get; }

		internal ArchipelagoSocketHelper(Uri hostUri)
			: base(CreateWebSocket(), 1024)
		{
			Uri = hostUri;
			SecurityProtocolType securityProtocolType = SecurityProtocolType.Tls13;
			ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | securityProtocolType;
		}

		private static ClientWebSocket CreateWebSocket()
		{
			return new ClientWebSocket();
		}

		public async Task ConnectAsync()
		{
			await ConnectToProvidedUri(Uri);
			StartPolling();
		}

		private async Task ConnectToProvidedUri(Uri uri)
		{
			if (uri.Scheme != "unspecified")
			{
				try
				{
					await Socket.ConnectAsync(uri, CancellationToken.None);
					return;
				}
				catch (Exception e)
				{
					OnError(e);
					throw;
				}
			}
			List<Exception> errors = new List<Exception>(0);
			try
			{
				await Socket.ConnectAsync(uri.AsWss(), CancellationToken.None);
				if (Socket.State == WebSocketState.Open)
				{
					return;
				}
			}
			catch (Exception item)
			{
				errors.Add(item);
				Socket = CreateWebSocket();
			}
			try
			{
				await Socket.ConnectAsync(uri.AsWs(), CancellationToken.None);
			}
			catch (Exception item2)
			{
				errors.Add(item2);
				OnError(new AggregateException(errors));
				throw;
			}
		}
	}
	public class BaseArchipelagoSocketHelper<T> where T : WebSocket
	{
		private static readonly ArchipelagoPacketConverter Converter = new ArchipelagoPacketConverter();

		private readonly BlockingCollection<Tuple<ArchipelagoPacketBase, TaskCompletionSource<bool>>> sendQueue = new BlockingCollection<Tuple<ArchipelagoPacketBase, TaskCompletionSource<bool>>>();

		internal T Socket;

		private readonly int bufferSize;

		public bool Connected
		{
			get
			{
				if (Socket.State != WebSocketState.Open)
				{
					return Socket.State == WebSocketState.CloseReceived;
				}
				return true;
			}
		}

		public event ArchipelagoSocketHelperDelagates.PacketReceivedHandler PacketReceived;

		public event ArchipelagoSocketHelperDelagates.PacketsSentHandler PacketsSent;

		public event ArchipelagoSocketHelperDelagates.ErrorReceivedHandler ErrorReceived;

		public event ArchipelagoSocketHelperDelagates.SocketClosedHandler SocketClosed;

		public event ArchipelagoSocketHelperDelagates.SocketOpenedHandler SocketOpened;

		internal BaseArchipelagoSocketHelper(T socket, int bufferSize = 1024)
		{
			Socket = socket;
			this.bufferSize = bufferSize;
		}

		internal void StartPolling()
		{
			if (this.SocketOpened != null)
			{
				this.SocketOpened();
			}
			Task.Run((Func<Task?>)PollingLoop);
			Task.Run((Func<Task?>)SendLoop);
		}

		private async Task PollingLoop()
		{
			byte[] buffer = new byte[bufferSize];
			while (Socket.State == WebSocketState.Open)
			{
				string message = null;
				try
				{
					message = await ReadMessageAsync(buffer);
				}
				catch (Exception e)
				{
					OnError(e);
				}
				OnMessageReceived(message);
			}
		}

		private async Task SendLoop()
		{
			while (Socket.State == WebSocketState.Open)
			{
				try
				{
					await HandleSendBuffer();
				}
				catch (Exception e)
				{
					OnError(e);
				}
				await Task.Delay(20);
			}
		}

		private async Task<string> ReadMessageAsync(byte[] buffer)
		{
			using MemoryStream readStream = new MemoryStream(buffer.Length);
			WebSocketReceiveResult result;
			do
			{
				result = await Socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
				if (result.MessageType == WebSocketMessageType.Close)
				{
					try
					{
						await Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
					}
					catch
					{
					}
					OnSocketClosed();
				}
				else
				{
					readStream.Write(buffer, 0, result.Count);
				}
			}
			while (!result.EndOfMessage);
			return Encoding.UTF8.GetString(readStream.ToArray());
		}

		public async Task DisconnectAsync()
		{
			await Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closure requested by client", CancellationToken.None);
			OnSocketClosed();
		}

		public void SendPacket(ArchipelagoPacketBase packet)
		{
			SendMultiplePackets(new List<ArchipelagoPacketBase> { packet });
		}

		public void SendMultiplePackets(List<ArchipelagoPacketBase> packets)
		{
			SendMultiplePackets(packets.ToArray());
		}

		public void SendMultiplePackets(params ArchipelagoPacketBase[] packets)
		{
			SendMultiplePacketsAsync(packets).Wait();
		}

		public Task SendPacketAsync(ArchipelagoPacketBase packet)
		{
			return SendMultiplePacketsAsync(new List<ArchipelagoPacketBase> { packet });
		}

		public Task SendMultiplePacketsAsync(List<ArchipelagoPacketBase> packets)
		{
			return SendMultiplePacketsAsync(packets.ToArray());
		}

		public Task SendMultiplePacketsAsync(params ArchipelagoPacketBase[] packets)
		{
			TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
			foreach (ArchipelagoPacketBase item in packets)
			{
				sendQueue.Add(new Tuple<ArchipelagoPacketBase, TaskCompletionSource<bool>>(item, taskCompletionSource));
			}
			return taskCompletionSource.Task;
		}

		private async Task HandleSendBuffer()
		{
			List<ArchipelagoPacketBase> list = new List<ArchipelagoPacketBase>();
			List<TaskCompletionSource<bool>> tasks = new List<TaskCompletionSource<bool>>();
			Tuple<ArchipelagoPacketBase, TaskCompletionSource<bool>> tuple = sendQueue.Take();
			list.Add(tuple.Item1);
			tasks.Add(tuple.Item2);
			Tuple<ArchipelagoPacketBase, TaskCompletionSource<bool>> item;
			while (sendQueue.TryTake(out item))
			{
				list.Add(item.Item1);
				tasks.Add(item.Item2);
			}
			if (!list.Any())
			{
				return;
			}
			if (Socket.State != WebSocketState.Open)
			{
				throw new ArchipelagoSocketClosedException();
			}
			ArchipelagoPacketBase[] packets = list.ToArray();
			string s = JsonConvert.SerializeObject((object)packets);
			byte[] messageBuffer = Encoding.UTF8.GetBytes(s);
			int messagesCount = (int)Math.Ceiling((double)messageBuffer.Length / (double)bufferSize);
			for (int i = 0; i < messagesCount; i++)
			{
				int num = bufferSize * i;
				int num2 = bufferSize;
				bool endOfMessage = i + 1 == messagesCount;
				if (num2 * (i + 1) > messageBuffer.Length)
				{
					num2 = messageBuffer.Length - num;
				}
				await Socket.SendAsync(new ArraySegment<byte>(messageBuffer, num, num2), WebSocketMessageType.Text, endOfMessage, CancellationToken.None);
			}
			foreach (TaskCompletionSource<bool> item2 in tasks)
			{
				item2.TrySetResult(result: true);
			}
			OnPacketSend(packets);
		}

		private void OnPacketSend(ArchipelagoPacketBase[] packets)
		{
			try
			{
				if (this.PacketsSent != null)
				{
					this.PacketsSent(packets);
				}
			}
			catch (Exception e)
			{
				OnError(e);
			}
		}

		private void OnSocketClosed()
		{
			try
			{
				if (this.SocketClosed != null)
				{
					this.SocketClosed("");
				}
			}
			catch (Exception e)
			{
				OnError(e);
			}
		}

		private void OnMessageReceived(string message)
		{
			try
			{
				if (string.IsNullOrEmpty(message) || this.PacketReceived == null)
				{
					return;
				}
				List<ArchipelagoPacketBase> list = null;
				try
				{
					list = JsonConvert.DeserializeObject<List<ArchipelagoPacketBase>>(message, (JsonConverter[])(object)new JsonConverter[1] { Converter });
				}
				catch (Exception e)
				{
					OnError(e);
				}
				if (list == null)
				{
					return;
				}
				foreach (ArchipelagoPacketBase item in list)
				{
					this.PacketReceived(item);
				}
			}
			catch (Exception e2)
			{
				OnError(e2);
			}
		}

		protected void OnError(Exception e)
		{
			try
			{
				if (this.ErrorReceived != null)
				{
					this.ErrorReceived(e, e.Message);
				}
			}
			catch (Exception ex)
			{
				Console.Out.WriteLine("Error occured during reporting of errorOuter Errror: " + e.Message + " " + e.StackTrace + "Inner Errror: " + ex.Message + " " + ex.StackTrace);
			}
		}
	}
	public interface IConnectionInfoProvider
	{
		string Game { get; }

		int Team { get; }

		int Slot { get; }

		string[] Tags { get; }

		ItemsHandlingFlags ItemsHandlingFlags { get; }

		string Uuid { get; }

		void UpdateConnectionOptions(string[] tags);

		void UpdateConnectionOptions(ItemsHandlingFlags itemsHandlingFlags);

		void UpdateConnectionOptions(string[] tags, ItemsHandlingFlags itemsHandlingFlags);
	}
	public class ConnectionInfoHelper : IConnectionInfoProvider
	{
		private readonly IArchipelagoSocketHelper socket;

		public string Game { get; private set; }

		public int Team { get; private set; }

		public int Slot { get; private set; }

		public string[] Tags { get; internal set; }

		public ItemsHandlingFlags ItemsHandlingFlags { get; internal set; }

		public string Uuid { get; private set; }

		internal ConnectionInfoHelper(IArchipelagoSocketHelper socket)
		{
			this.socket = socket;
			Reset();
			socket.PacketReceived += PacketReceived;
		}

		private void PacketReceived(ArchipelagoPacketBase packet)
		{
			if (!(packet is ConnectedPacket connectedPacket))
			{
				if (packet is ConnectionRefusedPacket)
				{
					Reset();
				}
				return;
			}
			Team = connectedPacket.Team;
			Slot = connectedPacket.Slot;
			if (connectedPacket.SlotInfo != null && connectedPacket.SlotInfo.ContainsKey(Slot))
			{
				Game = connectedPacket.SlotInfo[Slot].Game;
			}
		}

		internal void SetConnectionParameters(string game, string[] tags, ItemsHandlingFlags itemsHandlingFlags, string uuid)
		{
			Game = game;
			Tags = tags ?? new string[0];
			ItemsHandlingFlags = itemsHandlingFlags;
			Uuid = uuid ?? Guid.NewGuid().ToString();
		}

		private void Reset()
		{
			Game = null;
			Team = -1;
			Slot = -1;
			Tags = new string[0];
			ItemsHandlingFlags = ItemsHandlingFlags.NoItems;
			Uuid = null;
		}

		public void UpdateConnectionOptions(string[] tags)
		{
			UpdateConnectionOptions(tags, ItemsHandlingFlags);
		}

		public void UpdateConnectionOptions(ItemsHandlingFlags itemsHandlingFlags)
		{
			UpdateConnectionOptions(Tags, itemsHandlingFlags);
		}

		public void UpdateConnectionOptions(string[] tags, ItemsHandlingFlags itemsHandlingFlags)
		{
			SetConnectionParameters(Game, tags, itemsHandlingFlags, Uuid);
			socket.SendPacket(new ConnectUpdatePacket
			{
				Tags = Tags,
				ItemsHandling = ItemsHandlingFlags
			});
		}
	}
	public interface IDataStorageHelper : IDataStorageWrapper
	{
		DataStorageElement this[Scope scope, string key] { get; set; }

		DataStorageElement this[string key] { get; set; }
	}
	public class DataStorageHelper : IDataStorageHelper, IDataStorageWrapper
	{
		public delegate void DataStorageUpdatedHandler(JToken originalValue, JToken newValue, Dictionary<string, JToken> additionalArguments);

		private readonly Dictionary<string, DataStorageUpdatedHandler> onValueChangedEventHandlers = new Dictionary<string, DataStorageUpdatedHandler>();

		private readonly Dictionary<Guid, DataStorageUpdatedHandler> operationSpecificCallbacks = new Dictionary<Guid, DataStorageUpdatedHandler>();

		private readonly Dictionary<string, TaskCompletionSource<JToken>> asyncRetrievalTasks = new Dictionary<string, TaskCompletionSource<JToken>>();

		private readonly IArchipelagoSocketHelper socket;

		private readonly IConnectionInfoProvider connectionInfoProvider;

		public DataStorageElement this[Scope scope, string key]
		{
			get
			{
				return this[AddScope(scope, key)];
			}
			set
			{
				this[AddScope(scope, key)] = value;
			}
		}

		public DataStorageElement this[string key]
		{
			get
			{
				return new DataStorageElement(GetContextForKey(key));
			}
			set
			{
				SetValue(key, value);
			}
		}

		internal DataStorageHelper(IArchipelagoSocketHelper socket, IConnectionInfoProvider connectionInfoProvider)
		{
			this.socket = socket;
			this.connectionInfoProvider = connectionInfoProvider;
			socket.PacketReceived += OnPacketReceived;
		}

		private void OnPacketReceived(ArchipelagoPacketBase packet)
		{
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Invalid comparison between Unknown and I4
			if (!(packet is RetrievedPacket retrievedPacket))
			{
				if (packet is SetReplyPacket setReplyPacket)
				{
					if (setReplyPacket.AdditionalArguments != null && setReplyPacket.AdditionalArguments.ContainsKey("Reference") && (int)setReplyPacket.AdditionalArguments["Reference"].Type == 8 && ((string)setReplyPacket.AdditionalArguments["Reference"]).TryParseNGuid(out var g) && operationSpecificCallbacks.TryGetValue(g, out var value))
					{
						value(setReplyPacket.OriginalValue, setReplyPacket.Value, setReplyPacket.AdditionalArguments);
						operationSpecificCallbacks.Remove(g);
					}
					if (onValueChangedEventHandlers.TryGetValue(setReplyPacket.Key, out var value2))
					{
						value2(setReplyPacket.OriginalValue, setReplyPacket.Value, setReplyPacket.AdditionalArguments);
					}
				}
				return;
			}
			foreach (KeyValuePair<string, JToken> datum in retrievedPacket.Data)
			{
				if (asyncRetrievalTasks.TryGetValue(datum.Key, out var value3))
				{
					value3.TrySetResult(datum.Value);
					asyncRetrievalTasks.Remove(datum.Key);
				}
			}
		}

		private Task<JToken> GetAsync(string key)
		{
			if (asyncRetrievalTasks.TryGetValue(key, out var value))
			{
				return value.Task;
			}
			TaskCompletionSource<JToken> taskCompletionSource = new TaskCompletionSource<JToken>();
			asyncRetrievalTasks[key] = taskCompletionSource;
			socket.SendPacketAsync(new GetPacket
			{
				Keys = new string[1] { key }
			});
			return taskCompletionSource.Task;
		}

		private void Initialize(string key, JToken value)
		{
			socket.SendPacketAsync(new SetPacket
			{
				Key = key,
				DefaultValue = value,
				Operations = new OperationSpecification[1]
				{
					new OperationSpecification
					{
						OperationType = OperationType.Default
					}
				}
			});
		}

		private JToken GetValue(string key)
		{
			Task<JToken> async = GetAsync(key);
			if (!async.Wait(TimeSpan.FromSeconds(2.0)))
			{
				throw new TimeoutException("Timed out retrieving data for key `" + key + "`. This may be due to an attempt to retrieve a value from the DataStorageHelper in a synchronous fashion from within a PacketReceived handler. When using the DataStorageHelper from within code which runs on the websocket thread then use the asynchronous getters. Ex: `DataStorageHelper[\"" + key + "\"].GetAsync().ContinueWith(x => {});`Be aware that DataStorageHelper calls tend to cause packet responses, so making a call from within a PacketReceived handler may cause an infinite loop.");
			}
			return async.Result;
		}

		private void SetValue(string key, DataStorageElement e)
		{
			if (key.StartsWith("_read_"))
			{
				throw new InvalidOperationException("DataStorage write operation on readonly key '" + key + "' is not allowed");
			}
			if (e == null)
			{
				e = new DataStorageElement(OperationType.Replace, (JToken)(object)JValue.CreateNull());
			}
			if (e.Context == null)
			{
				e.Context = GetContextForKey(key);
			}
			else if (e.Context.Key != key)
			{
				e.Operations.Insert(0, new OperationSpecification
				{
					OperationType = OperationType.Replace,
					Value = GetValue(e.Context.Key)
				});
			}
			Dictionary<string, JToken> dictionary = e.AdditionalArguments ?? new Dictionary<string, JToken>(0);
			if (e.Callbacks != null)
			{
				Guid key2 = Guid.NewGuid();
				operationSpecificCallbacks[key2] = e.Callbacks;
				dictionary["Reference"] = JToken.op_Implicit(key2.ToString("N"));
				socket.SendPacketAsync(new SetPacket
				{
					Key = key,
					Operations = e.Operations.ToArray(),
					WantReply = true,
					AdditionalArguments = dictionary
				});
			}
			else
			{
				socket.SendPacketAsync(new SetPacket
				{
					Key = key,
					Operations = e.Operations.ToArray(),
					AdditionalArguments = dictionary
				});
			}
		}

		private DataStorageElementContext GetContextForKey(string key)
		{
			return new DataStorageElementContext
			{
				Key = key,
				GetData = GetValue,
				GetAsync = GetAsync,
				Initialize = Initialize,
				AddHandler = AddHandler,
				RemoveHandler = RemoveHandler
			};
		}

		private void AddHandler(string key, DataStorageUpdatedHandler handler)
		{
			if (onValueChangedEventHandlers.ContainsKey(key))
			{
				Dictionary<string, DataStorageUpdatedHandler> dictionary = onValueChangedEventHandlers;
				dictionary[key] = (DataStorageUpdatedHandler)Delegate.Combine(dictionary[key], handler);
			}
			else
			{
				onValueChangedEventHandlers[key] = handler;
			}
			socket.SendPacketAsync(new SetNotifyPacket
			{
				Keys = new string[1] { key }
			});
		}

		private void RemoveHandler(string key, DataStorageUpdatedHandler handler)
		{
			if (onValueChangedEventHandlers.ContainsKey(key))
			{
				Dictionary<string, DataStorageUpdatedHandler> dictionary = onValueChangedEventHandlers;
				dictionary[key] = (DataStorageUpdatedHandler)Delegate.Remove(dictionary[key], handler);
				if (onValueChangedEventHandlers[key] == null)
				{
					onValueChangedEventHandlers.Remove(key);
				}
			}
		}

		private string AddScope(Scope scope, string key)
		{
			return scope switch
			{
				Scope.Global => key, 
				Scope.Game => $"{scope}:{connectionInfoProvider.Game}:{key}", 
				Scope.Team => $"{scope}:{connectionInfoProvider.Team}:{key}", 
				Scope.Slot => $"{scope}:{connectionInfoProvider.Slot}:{key}", 
				Scope.ReadOnly => "_read_" + key, 
				_ => throw new ArgumentOutOfRangeException("scope", scope, "Invalid scope for key " + key), 
			};
		}

		private DataStorageElement GetHintsElement(int? slot = null, int? team = null)
		{
			return this[Scope.ReadOnly, $"hints_{team ?? connectionInfoProvider.Team}_{slot ?? connectionInfoProvider.Slot}"];
		}

		private DataStorageElement GetSlotDataElement(int? slot = null)
		{
			return this[Scope.ReadOnly, $"slot_data_{slot ?? connectionInfoProvider.Slot}"];
		}

		private DataStorageElement GetItemNameGroupsElement(string game = null)
		{
			return this[Scope.ReadOnly, "item_name_groups_" + (game ?? connectionInfoProvider.Game)];
		}

		private DataStorageElement GetLocationNameGroupsElement(string game = null)
		{
			return this[Scope.ReadOnly, "location_name_groups_" + (game ?? connectionInfoProvider.Game)];
		}

		private DataStorageElement GetClientStatusElement(int? slot = null, int? team = null)
		{
			return this[Scope.ReadOnly, $"client_status_{team ?? connectionInfoProvider.Team}_{slot ?? connectionInfoProvider.Slot}"];
		}

		private DataStorageElement GetRaceModeElement()
		{
			return this[Scope.ReadOnly, "race_mode"];
		}

		public Hint[] GetHints(int? slot = null, int? team = null)
		{
			return GetHintsElement(slot, team).To<Hint[]>();
		}

		public Task<Hint[]> GetHintsAsync(int? slot = null, int? team = null)
		{
			return GetHintsElement(slot, team).GetAsync<Hint[]>();
		}

		public void TrackHints(Action<Hint[]> onHintsUpdated, bool retrieveCurrentlyUnlockedHints = true, int? slot = null, int? team = null)
		{
			GetHintsElement(slot, team).OnValueChanged += delegate(JToken _, JToken newValue, Dictionary<string, JToken> x)
			{
				onHintsUpdated(newValue.ToObject<Hint[]>());
			};
			if (retrieveCurrentlyUnlockedHints)
			{
				GetHintsAsync(slot, team).ContinueWith(delegate(Task<Hint[]> t)
				{
					onHintsUpdated(t.Result);
				});
			}
		}

		public Dictionary<string, object> GetSlotData(int? slot = null)
		{
			return GetSlotData<Dictionary<string, object>>(slot);
		}

		public T GetSlotData<T>(int? slot = null) where T : class
		{
			return GetSlotDataElement(slot).To<T>();
		}

		public Task<Dictionary<string, object>> GetSlotDataAsync(int? slot = null)
		{
			return GetSlotDataAsync<Dictionary<string, object>>(slot);
		}

		public Task<T> GetSlotDataAsync<T>(int? slot = null) where T : class
		{
			return GetSlotDataElement(slot).GetAsync<T>();
		}

		public Dictionary<string, string[]> GetItemNameGroups(string game = null)
		{
			return GetItemNameGroupsElement(game).To<Dictionary<string, string[]>>();
		}

		public Task<Dictionary<string, string[]>> GetItemNameGroupsAsync(string game = null)
		{
			return GetItemNameGroupsElement(game).GetAsync<Dictionary<string, string[]>>();
		}

		public Dictionary<string, string[]> GetLocationNameGroups(string game = null)
		{
			return GetLocationNameGroupsElement(game).To<Dictionary<string, string[]>>();
		}

		public Task<Dictionary<string, string[]>> GetLocationNameGroupsAsync(string game = null)
		{
			return GetLocationNameGroupsElement(game).GetAsync<Dictionary<string, string[]>>();
		}

		public ArchipelagoClientState GetClientStatus(int? slot = null, int? team = null)
		{
			return GetClientStatusElement(slot, team).To<ArchipelagoClientState?>().GetValueOrDefault();
		}

		public Task<ArchipelagoClientState> GetClientStatusAsync(int? slot = null, int? team = null)
		{
			return GetClientStatusElement(slot, team).GetAsync<ArchipelagoClientState?>().ContinueWith((Task<ArchipelagoClientState?> r) => r.Result.GetValueOrDefault());
		}

		public void TrackClientStatus(Action<ArchipelagoClientState> onStatusUpdated, bool retrieveCurrentClientStatus = true, int? slot = null, int? team = null)
		{
			Get