Decompiled source of ArchipelagoRandomizer v0.5.3

Archipelago.MultiClient.Net.dll

Decompiled 2 weeks 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.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(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")]
[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;
		}

		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)
		{
			GetClientStatusElement(slot, team).OnValueChanged += delegate(JToken _, JToken newValue, Dictionary<string, JToken> x)
			{
				onStatusUpdated(newValue.ToObject<ArchipelagoClientState>());
	

ArchipelagoRandomizer.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
using Archipelago.MultiClient.Net;
using Archipelago.MultiClient.Net.BounceFeatures.DeathLink;
using Archipelago.MultiClient.Net.Enums;
using Archipelago.MultiClient.Net.Helpers;
using Archipelago.MultiClient.Net.MessageLog.Messages;
using Archipelago.MultiClient.Net.MessageLog.Parts;
using Archipelago.MultiClient.Net.Models;
using ArchipelagoRandomizer.Features;
using ArchipelagoRandomizer.Items;
using ArchipelagoRandomizer.Items.ItemImpls;
using ArchipelagoRandomizer.Locations;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Com.LuisPedroFonseca.ProCamera2D;
using Cysharp.Threading.Tasks;
using Dialogue;
using HarmonyLib;
using I2.Loc;
using InControl;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NineSolsAPI;
using RCGFSM.Animation;
using RCGFSM.Items;
using RCGFSM.Map;
using RCGFSM.PlayerAbility;
using RCGFSM.PlayerAction;
using RCGFSM.Variable;
using RCGMaker.AddressableAssets;
using RCGMaker.Core;
using RCGMaker.Runtime;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using _3_Script._0_RedCandleGamesUtilities.UICanvas.ActivateChecker;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ArchipelagoRandomizer")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("A Nine Sols mod for the Archipelago multi-game randomizer system")]
[assembly: AssemblyFileVersion("0.5.3.0")]
[assembly: AssemblyInformationalVersion("0.5.3+8d18354668cbb01ba02024667c5e095a1159f395")]
[assembly: AssemblyProduct("ArchipelagoRandomizer")]
[assembly: AssemblyTitle("ArchipelagoRandomizer")]
[assembly: AssemblyVersion("0.5.3.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class RequiresLocationAttribute : Attribute
	{
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Archipelago.MultiClient.Net
{
	[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
	internal sealed class DataStoragePropertyAttribute : Attribute
	{
		public string? SessionVariable { get; }

		public Scope Scope { get; }

		public string Key { get; }

		public DataStoragePropertyAttribute(string sessionVariable, Scope scope, string key)
		{
			//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)
			SessionVariable = sessionVariable;
			Scope = scope;
			Key = key;
		}

		public DataStoragePropertyAttribute(string sessionVariable, string key)
			: this(sessionVariable, (Scope)0, key)
		{
		}
	}
}
namespace ArchipelagoRandomizer
{
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInPlugin("ArchipelagoRandomizer", "ArchipelagoRandomizer", "0.5.3")]
	public class APRandomizer : BaseUnityPlugin
	{
		public ConfigEntry<bool> ForceTrueEigongSetting;

		public ConfigEntry<bool> BossScalingSetting;

		public ConfigEntry<bool> ScaleDownEarlyBossesSetting;

		public ConfigEntry<bool> ScaleUpLateBossesSetting;

		private ConfigEntry<bool> DeathLinkSetting;

		public ConfigEntry<bool> FlowerlessDeathLinkSetting;

		public ConfigEntry<bool> ShowAPMessagesSetting;

		public ConfigEntry<bool> FilterAPMessagesByPlayerSetting;

		public ConfigEntry<bool> BatchSimultaneousMessagesSetting;

		private Harmony harmony;

		public static APRandomizer Instance;

		public static string SaveSlotsPath => Application.persistentDataPath;

		private void Awake()
		{
			//IL_02c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fd: Unknown result type (might be due to invalid IL or missing references)
			Log.Init(((BaseUnityPlugin)this).Logger);
			RCGLifeCycle.DontDestroyForever(((Component)this).gameObject);
			Instance = this;
			harmony = Harmony.CreateAndPatchAll(typeof(APRandomizer).Assembly, (string)null);
			ForceTrueEigongSetting = ((BaseUnityPlugin)this).Config.Bind<bool>("", "Force True Eigong", false, "Set the true ending flag when you first enter New Kunlun Control Hub, so you can fight all 3 phases of True Eigong without having to do all the usual sidequests first.\n\nIf you toggle this setting after unlocking the New Kunlun Control Hub node, then the true ending flag will be toggled immediately.");
			ForceTrueEigongSetting.SettingChanged += delegate
			{
				//IL_0061: Unknown result type (might be due to invalid IL or missing references)
				Log.Info($"ForceTrueEigongSetting changed to {ForceTrueEigongSetting.Value}");
				if (TeleportPoints.IsNodeUnlocked(TeleportPoints.TeleportPoint.NewKunlunControlHub))
				{
					InGameConsole.Add($"<color=orange>Changing the true ending flag to {ForceTrueEigongSetting.Value}</color> because the 'Force True Eigong' setting was changed.");
					((ScriptableDataBool)SingletonBehaviour<SaveManager>.Instance.allFlags.FlagDict["e78958a13315eb9418325caf25da9d4dScriptableDataBool"]).CurrentValue = ForceTrueEigongSetting.Value;
				}
			};
			BossScalingSetting = ((BaseUnityPlugin)this).Config.Bind<bool>("Boss Scaling", "Boss Scaling", true, "Edit the health and damage values of (non-Eigong) Battle Memories bosses so they scale with the actual order you end up fighting them in the randomizer, instead of the vanilla game's expected order.\n\nFor example: If you fight Ji first, he'll have lower stats than vanilla. If you fight Yanlao last, he'll have higher stats than vanilla. Exact adjustments will be shown when you encounter the boss.\n\nSince Battle Memories stats are used as a guide, this setting ignores all bosses not included in the Battle Memories mode.");
			ScaleDownEarlyBossesSetting = ((BaseUnityPlugin)this).Config.Bind<bool>("Boss Scaling", "Scale Down Early Bosses", true, "Lowers the health and damage values of (non-Eigong) Battle Memories bosses that you encounter earlier than the intended vanilla order.\n\nWhen lowering a boss' stats, the result is a simple fraction of their vanilla stats based on encounter order. For example: If you fight Lady E (boss #5) as your 2nd boss, she'll have 2/5th of her vanilla stats.\n\nHas no effect unless the main 'Boss Scaling' setting is enabled.");
			ScaleUpLateBossesSetting = ((BaseUnityPlugin)this).Config.Bind<bool>("Boss Scaling", "Scale Up Late Bosses", true, "Raises the health and damage values of (non-Eigong) Battle Memories bosses that you encounter later than the intended vanilla order.\n\nWhen raising a boss' stats, the result is somewhere between their vanilla and Battle Memories stats based on encounter order. For example: If you fight Goumang (boss #2) as your 5th boss, her stats will be exactly halfway between her vanilla and Battle Memories stats.\n\nHas no effect unless the main 'Boss Scaling' setting is enabled.");
			DeathLinkSetting = ((BaseUnityPlugin)this).Config.Bind<bool>("Death Link", "Death Link", false, "When you die, everyone who enabled death link dies. Of course, the reverse is true too.");
			Log.Info($"applying initial DeathLink setting of {DeathLinkSetting.Value}");
			DeathLinkManager.ApplyModSetting(DeathLinkSetting.Value);
			DeathLinkSetting.SettingChanged += delegate
			{
				Log.Info($"DeathLink setting changed to {DeathLinkSetting.Value}");
				DeathLinkManager.ApplyModSetting(DeathLinkSetting.Value);
			};
			FlowerlessDeathLinkSetting = ((BaseUnityPlugin)this).Config.Bind<bool>("Death Link", "Flowerless Death Link", true, "When you die from receiving a death link, no Tianhuo flower will be produced, and no jin or experience will be taken away.\n\nHas no effect on regular deaths.");
			ShowAPMessagesSetting = ((BaseUnityPlugin)this).Config.Bind<bool>("Message Display", "Show AP Messages", true, "Display all messages the Archipelago server sends to clients in both the main game window and the pause console. Turn this off if you find the AP messages too spammy, especially if they appear to be destabilizing the game.\n\nDropped messages can still be read in the BepInEx console window, or in any AP text client you have connected to the same slot.");
			FilterAPMessagesByPlayerSetting = ((BaseUnityPlugin)this).Config.Bind<bool>("Message Display", "Filter By Player", false, "Only display 'Player1 found Player2's Item' messsages in the main game window and pause console if you are one of those players. In larger multiworlds, this should hide the vast majority of AP messages not relevant to you, without disabling them completely.\n\nDropped messages can still be read in the BepInEx console window, or in any AP text client you have connected to the same slot.");
			BatchSimultaneousMessagesSetting = ((BaseUnityPlugin)this).Config.Bind<bool>("Message Display", "Batch Simultaneous Messages", true, "When receiving several messages within a fraction of a second, display only a single 'Received N messages' summary in the main window to prevent flooding the screen and lagging the game. All of the individual messages will still be added to the pause console.");
			Log.Info("trying to load Archipelago item and location IDs");
			try
			{
				Assembly assembly = typeof(APRandomizer).GetTypeInfo().Assembly;
				using (StreamReader streamReader = new StreamReader(assembly.GetManifestResourceStream("ArchipelagoRandomizer.items.jsonc")))
				{
					ItemNames.LoadArchipelagoIds(streamReader.ReadToEnd());
				}
				using (StreamReader streamReader2 = new StreamReader(assembly.GetManifestResourceStream("ArchipelagoRandomizer.locations.jsonc")))
				{
					LocationNames.LoadArchipelagoIds(streamReader2.ReadToEnd());
				}
				Log.Info("loaded Archipelago item and location IDs");
			}
			catch (Exception ex)
			{
				Log.Warning("id loading threw: " + ex.Message + " with stack:\n" + ex.StackTrace);
				if (ex.InnerException != null)
				{
					Log.Warning("id loading threw inner: " + ex.InnerException.Message + " with stack:\n" + ex.InnerException.StackTrace);
				}
			}
			Action obj = delegate
			{
				DebugTools.ShowDebugToolsPopup = !DebugTools.ShowDebugToolsPopup;
			};
			KeyCode[] array = new KeyCode[3];
			RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/);
			KeybindManager.Add((MonoBehaviour)(object)this, obj, new KeyboardShortcut((KeyCode)100, (KeyCode[])(object)array));
			Action obj2 = delegate
			{
				for (int i = 0; i < 30; i++)
				{
					Log.Warning("x");
				}
			};
			KeyCode[] array2 = new KeyCode[3];
			RuntimeHelpers.InitializeArray(array2, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/);
			KeybindManager.Add((MonoBehaviour)(object)this, obj2, new KeyboardShortcut((KeyCode)120, (KeyCode[])(object)array2));
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Plugin ArchipelagoRandomizer is loaded!");
		}

		private void OnDestroy()
		{
			Log.Info("APRandomizer::OnDestroy() called. Cleaning up AP server connection and Harmony patches.");
			ConnectionAndPopups.CleanupExistingAPServerConnection();
			harmony.UnpatchSelf();
		}

		private void Update()
		{
			ConnectionAndPopups.Update();
			DebugTools.Update();
			APServerAndSaveData.Update();
			DeathLinkManager.Update();
		}

		private void OnGUI()
		{
			ConnectionAndPopups.OnGUI();
			DebugTools.OnGUI();
			InGameConsole.OnGUI();
		}
	}
	[HarmonyPatch]
	internal class BossScaling
	{
		private static Dictionary<string, int> BossToVanillaOrder = new Dictionary<string, int>
		{
			{ "StealthGameMonster_SpearHorseMan", 1 },
			{ "StealthGameMonster_GouMang Variant", 2 },
			{ "StealthGameMonster_BossZombieSpear", 2 },
			{ "StealthGameMonster_BossZombieHammer", 2 },
			{ "Monster_GiantMechClaw", 3 },
			{ "StealthGameMonster_Boss_JieChuan", 4 },
			{ "StealthGameMonster_Boss_ButterFly Variant", 5 },
			{ "StealthGameMonster_伏羲_新", 6 },
			{ "StealthGameMonster_新女媧 Variant", 6 },
			{ "StealthGameMonster_Boss_Jee", 7 }
		};

		private static Dictionary<string, string> BossToDisplayName = new Dictionary<string, string>
		{
			{ "StealthGameMonster_SpearHorseMan", "Yingzhao" },
			{ "StealthGameMonster_GouMang Variant", "Goumang" },
			{ "StealthGameMonster_BossZombieSpear", "Goumang's Small Jiangshi" },
			{ "StealthGameMonster_BossZombieHammer", "Goumang's Big Jiangshi" },
			{ "Monster_GiantMechClaw", "Sky Rending Claw" },
			{ "StealthGameMonster_Boss_JieChuan", "Jiequan" },
			{ "StealthGameMonster_Boss_ButterFly Variant", "Lady Ethereal" },
			{ "StealthGameMonster_伏羲_新", "Fuxi" },
			{ "StealthGameMonster_新女媧 Variant", "Nuwa" },
			{ "StealthGameMonster_Boss_Jee", "Ji" }
		};

		private static string BossScaling_EncounteredBossesListName = "BossesEncounteredInGame";

		private static Dictionary<string, string> BossToSaveDataName = new Dictionary<string, string>
		{
			{ "StealthGameMonster_SpearHorseMan", "Yingzhao" },
			{ "StealthGameMonster_GouMang Variant", "Goumang" },
			{ "StealthGameMonster_BossZombieSpear", "Goumang" },
			{ "StealthGameMonster_BossZombieHammer", "Goumang" },
			{ "Monster_GiantMechClaw", "Yanlao" },
			{ "StealthGameMonster_Boss_JieChuan", "Jiequan" },
			{ "StealthGameMonster_Boss_ButterFly Variant", "Lady Ethereal" },
			{ "StealthGameMonster_伏羲_新", "Fengs" },
			{ "StealthGameMonster_新女媧 Variant", "Fengs" },
			{ "StealthGameMonster_Boss_Jee", "Ji" }
		};

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

		private static Dictionary<string, string> BossToAlreadyKilledFlag = new Dictionary<string, string>
		{
			{ "StealthGameMonster_GouMang Variant", "f5b26e3311ce4e84a961dc36a05e19b7ScriptableDataBool" },
			{ "StealthGameMonster_Boss_JieChuan", "9758240a82bf8472a884fe3123cd6a2cScriptableDataBool" }
		};

		private static FieldRef<MonsterStat, float> BaseAttackValueRef => AccessTools.FieldRefAccess<MonsterStat, float>("BaseAttackValue");

		private static FieldRef<MonsterStat, float> BaseHealthValueRef => AccessTools.FieldRefAccess<MonsterStat, float>("BaseHealthValue");

		[HarmonyPrefix]
		[HarmonyPatch(typeof(MonsterBase), "Awake")]
		private static void MonsterBase_Awake(MonsterBase __instance)
		{
			string name = ((Object)__instance).name;
			if (PlayerGamePlayData.Instance.memoryMode.CurrentValue)
			{
				Log.Debug("BossScaling ignoring " + name + " because this is Battle Memories mode");
				return;
			}
			if (!BossToVanillaOrder.ContainsKey(name))
			{
				Log.Debug("BossScaling ignoring " + name + " because it's not in our boss list");
				return;
			}
			if (name == "StealthGameMonster_Boss_JieChuan" && ((Object)SingletonBehaviour<GameCore>.Instance.gameLevel).name == "A5_S1")
			{
				Log.Info("BossScaling ignoring " + name + " because this is the unwinnable Jiequan 1 fight, not the 'real' Jiequan");
				return;
			}
			if (BossToAlreadyKilledFlag.ContainsKey(name))
			{
				GameFlagBase obj = SingletonBehaviour<SaveManager>.Instance.allFlags.FlagDict[BossToAlreadyKilledFlag[name]];
				GameFlagBase obj2 = ((obj is ScriptableDataBool) ? obj : null);
				if (obj2 != null && ((ScriptableDataBool)obj2).CurrentValue)
				{
					Log.Info("BossScaling ignoring " + name + " because they've already been killed");
					return;
				}
			}
			if (APSaveManager.CurrentAPSaveData == null)
			{
				Log.Error("BossScaling aborting because APSession is null. If you're the developer doing hot reloading, this is normal.");
				return;
			}
			APSaveManager.CurrentAPSaveData.persistentModStringLists.TryGetValue(BossScaling_EncounteredBossesListName, out List<string> value);
			bool flag = false;
			if (value == null)
			{
				value = new List<string>();
				flag = true;
			}
			string item = BossToSaveDataName[name];
			if (!value.Contains(item))
			{
				value.Add(item);
				flag = true;
			}
			int num = value.IndexOf(item) + 1;
			if (flag)
			{
				APSaveManager.CurrentAPSaveData.persistentModStringLists[BossScaling_EncounteredBossesListName] = value;
				APSaveManager.ScheduleWriteToCurrentSaveFile();
			}
			if (!APRandomizer.Instance.BossScalingSetting.Value)
			{
				Log.Debug("BossScaling doing nothing because the 'Boss Scaling' mod setting is off.");
				return;
			}
			int num2 = BossToVanillaOrder[name];
			if (num == num2)
			{
				InGameConsole.Add($"{BossToDisplayName.GetValueOrDefault(name)}'s health and damage have been left unchanged, since you encountered them as boss #{num} just like vanilla.");
				return;
			}
			float num3;
			float num4;
			if (num > num2)
			{
				if (!APRandomizer.Instance.ScaleUpLateBossesSetting.Value)
				{
					Log.Debug("BossScaling doing nothing because the 'Scale Up Late Bosses' mod setting is off.");
					return;
				}
				(num3, num4) = ScaleBetweenVanillaAndBattleMemories(__instance, num);
			}
			else
			{
				if (!APRandomizer.Instance.ScaleDownEarlyBossesSetting.Value)
				{
					Log.Debug("BossScaling doing nothing because the 'Scale Down Early Bosses' mod setting is off.");
					return;
				}
				(num3, num4) = ScaleBetweenZeroAndVanilla(__instance, num);
			}
			MonsterStat monsterStat = __instance.monsterStat;
			float num5 = BaseAttackValueRef.Invoke(monsterStat);
			float num6 = BaseHealthValueRef.Invoke(monsterStat);
			if (AlreadyScaledBosses.Contains(name))
			{
				Log.Info("BossScaling skipping " + name + " because we already scaled it this session");
			}
			else
			{
				Log.Info("BossScaling actually applying scaled stats to " + name);
				BaseAttackValueRef.Invoke(monsterStat) = num3;
				BaseHealthValueRef.Invoke(monsterStat) = num4;
				AlreadyScaledBosses.Add(name);
			}
			InGameConsole.Add(BossToDisplayName.GetValueOrDefault(name) + "'s " + $"health and damage have been set to <color=orange>{num4 / num6 * 100f}% and {num3 / num5 * 100f}%</color> of their vanilla values\n" + $"because you're encountering them as <color=orange>boss #{num} instead of #{num2}</color>");
		}

		private static (float, float) ScaleBetweenVanillaAndBattleMemories(MonsterBase __instance, int actualOrder)
		{
			string name = ((Object)__instance).name;
			int num = BossToVanillaOrder[name];
			MonsterStat monsterStat = __instance.monsterStat;
			float num2 = BaseAttackValueRef.Invoke(monsterStat);
			float num3 = BaseHealthValueRef.Invoke(monsterStat);
			float num4 = monsterStat.BossMemoryAttackScale * num2;
			float num5 = monsterStat.BossMemoryHealthScale * num3;
			float num6 = (num4 - num2) / (float)(8 - num);
			float num7 = num2 - num6 * (float)num;
			float num8 = (num5 - num3) / (float)(8 - num);
			float num9 = num3 - num8 * (float)num;
			float item = num6 * (float)actualOrder + num7;
			float item2 = num8 * (float)actualOrder + num9;
			return (item, item2);
		}

		private static (float, float) ScaleBetweenZeroAndVanilla(MonsterBase __instance, int actualOrder)
		{
			string name = ((Object)__instance).name;
			int num = BossToVanillaOrder[name];
			MonsterStat monsterStat = __instance.monsterStat;
			float num2 = BaseAttackValueRef.Invoke(monsterStat);
			float num3 = BaseHealthValueRef.Invoke(monsterStat);
			float num4 = num2 / (float)num;
			float num5 = num3 / (float)num;
			float item = num4 * (float)actualOrder;
			float item2 = num5 * (float)actualOrder;
			return (item, item2);
		}
	}
	[HarmonyPatch]
	public class DeathLinkManager
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static DeathLinkReceivedHandler <>9__8_0;

			internal void <CreateDeathLinkService>b__8_0(DeathLink deathLinkObject)
			{
				lastDeathLinkObjectReceived = deathLinkObject;
			}
		}

		public static bool DeathLinkSettingValue = false;

		private static DeathLinkService? service = null;

		private static bool manualDeathInProgress = false;

		private static DeathLink? lastDeathLinkObjectReceived = null;

		private static List<string> deathMessages = new List<string> { " became one with the Tao.", " wasn't smoking enough.", " could not parry death." };

		private static Random prng = new Random();

		public static void ApplyModSetting(bool enabled)
		{
			DeathLinkSettingValue = enabled;
			ConnectionAndPopups.OnSessionOpened -= CreateDLServiceForSession;
			if (ConnectionAndPopups.APSession == null)
			{
				ConnectionAndPopups.OnSessionOpened += CreateDLServiceForSession;
			}
			else
			{
				CreateDLServiceForSession(ConnectionAndPopups.APSession);
			}
		}

		private static void CreateDLServiceForSession(ArchipelagoSession _)
		{
			if (service == null)
			{
				CreateDeathLinkService();
			}
			else
			{
				ApplyModSettingToService(service);
			}
		}

		private static void ApplyModSettingToService(DeathLinkService deathLinkService)
		{
			if (DeathLinkSettingValue)
			{
				deathLinkService.EnableDeathLink();
			}
			else
			{
				deathLinkService.DisableDeathLink();
			}
		}

		public static void CleanupExistingDeathLinkService()
		{
			service = null;
		}

		private static void CreateDeathLinkService()
		{
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Expected O, but got Unknown
			if (service != null)
			{
				return;
			}
			if (ConnectionAndPopups.APSession == null)
			{
				Log.Error("EnableDeathLinkImplHelper unable to create death link service because APSession is null");
				return;
			}
			service = DeathLinkProvider.CreateDeathLinkService(ConnectionAndPopups.APSession);
			DeathLinkService? obj = service;
			object obj2 = <>c.<>9__8_0;
			if (obj2 == null)
			{
				DeathLinkReceivedHandler val = delegate(DeathLink deathLinkObject)
				{
					lastDeathLinkObjectReceived = deathLinkObject;
				};
				<>c.<>9__8_0 = val;
				obj2 = (object)val;
			}
			obj.OnDeathLinkReceived += (DeathLinkReceivedHandler)obj2;
			ApplyModSettingToService(service);
		}

		public static void Update()
		{
			if (lastDeathLinkObjectReceived != (DeathLink)null)
			{
				DeathLink? deathLinkObject = lastDeathLinkObjectReceived;
				lastDeathLinkObjectReceived = null;
				OnDeathLinkReceived(deathLinkObject);
			}
		}

		public static void OnDeathLinkReceived(DeathLink deathLinkObject)
		{
			Log.Info($"OnDeathLinkReceived() Timestamp={deathLinkObject.Timestamp}, Source={deathLinkObject.Source}, Cause={deathLinkObject.Cause}");
			GameObject obj = GameObject.Find("GameCore(Clone)/RCG LifeCycle/UIManager/GameplayUICamera/Always Canvas/DialoguePlayer(KeepThisEnable)");
			DialoguePlayer val = ((obj != null) ? obj.GetComponent<DialoguePlayer>() : null);
			if ((Object)(object)val != (Object)null && (Object)(object)AccessTools.FieldRefAccess<DialoguePlayer, DialogueGraph>("playingDialogueGraph").Invoke(val) != (Object)null)
			{
				InGameConsole.Add("<color=orange>Ignoring death link</orange> (" + deathLinkObject.Cause + ") because dying mid-dialogue can softlock.");
				return;
			}
			InGameConsole.Add(deathLinkObject.Cause);
			ActuallyKillThePlayer();
		}

		private static void ActuallyKillThePlayer()
		{
			manualDeathInProgress = true;
			Player.i.health.ReceiveDOT_Damage(9999f);
			manualDeathInProgress = false;
		}

		[HarmonyPostfix]
		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		private static void Player_get_NeedDeathPenalty(Player __instance, ref bool __result)
		{
			if (manualDeathInProgress && __result && APRandomizer.Instance.FlowerlessDeathLinkSetting.Value)
			{
				Log.Info("Player_get_NeedDeathPenalty preventing the usual death penalty because the Flowerless setting is on");
				__result = false;
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(GameLevel), "HandlePlayerKilled")]
		public static void GameLevel_HandlePlayerKilled(GameLevel __instance)
		{
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Expected O, but got Unknown
			Log.Info("GameLevel.HandlePlayerKilled called in " + ((Object)__instance).name);
			if (manualDeathInProgress)
			{
				Log.Info("GameLevel.HandlePlayerKilled patch ignoring death because this is a death we received from another player");
				return;
			}
			if (!DeathLinkSettingValue)
			{
				Log.Info("GameLevel.HandlePlayerKilled patch ignoring death since death_link is off");
				return;
			}
			if (service == null)
			{
				Log.Error("Unable to send death to AP server because death link service is null");
				return;
			}
			if (APSaveManager.CurrentAPSaveData == null)
			{
				Log.Error("Unable to send death to AP server because we don't have an established AP connection");
				return;
			}
			Log.Info("GameLevel.HandlePlayerKilled detected a death, sending to AP server");
			string slotName = APSaveManager.CurrentAPSaveData.apConnectionData.slotName;
			string text = slotName + deathMessages[prng.Next(0, deathMessages.Count)];
			InGameConsole.Add("Because death link is enabled, sending this death to other players with the message: \"" + text + "\"");
			service.SendDeathLink(new DeathLink(slotName, text));
		}
	}
	internal class DebugTools
	{
		public static bool ShowDebugToolsPopup = false;

		private static string DebugPopup_Item = "";

		private static string DebugPopup_Count = "";

		public static void Update()
		{
			if (ShowDebugToolsPopup)
			{
				Cursor.visible = true;
			}
		}

		public static void OnGUI()
		{
			ConnectionAndPopups.UpdateStyles();
			if (ShowDebugToolsPopup)
			{
				DrawDebugToolsPopup();
			}
		}

		private static void DrawDebugToolsPopup()
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Expected O, but got Unknown
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Expected O, but got Unknown
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			float num = (float)Screen.width * 0.6f;
			float num2 = (float)Screen.height * 0.75f;
			Rect windowRect = new Rect(((float)Screen.width - num) / 2f, ((float)Screen.height - num2) / 2f, num, num2);
			GUILayout.Width(((Rect)(ref windowRect)).width * 0.6f);
			GUIStyle windowStyle = ConnectionAndPopups.windowStyle;
			GUIStyle labelStyle = ConnectionAndPopups.labelStyle;
			GUIStyle textFieldStyle = ConnectionAndPopups.textFieldStyle;
			GUIStyle buttonStyle = ConnectionAndPopups.buttonStyle;
			GUIStyle centeredLabelStyle = new GUIStyle(labelStyle);
			centeredLabelStyle.alignment = (TextAnchor)1;
			GUI.Window(11261728, windowRect, (WindowFunction)delegate
			{
				//IL_016d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0177: Expected O, but got Unknown
				//IL_023d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0242: Unknown result type (might be due to invalid IL or missing references)
				//IL_0248: Expected O, but got Unknown
				//IL_0282: Unknown result type (might be due to invalid IL or missing references)
				//IL_0289: Expected O, but got Unknown
				//IL_0385: Unknown result type (might be due to invalid IL or missing references)
				//IL_038b: Expected O, but got Unknown
				//IL_039c: Unknown result type (might be due to invalid IL or missing references)
				//IL_03a2: Expected O, but got Unknown
				//IL_06b6: Unknown result type (might be due to invalid IL or missing references)
				//IL_06bc: Expected O, but got Unknown
				GUILayout.Label("", centeredLabelStyle, Array.Empty<GUILayoutOption>());
				GUILayout.Label("NPCs & Events", centeredLabelStyle, Array.Empty<GUILayoutOption>());
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				if (GUILayout.Button("Unlock Jiequan 1 Fight", buttonStyle, Array.Empty<GUILayoutOption>()))
				{
					ToastManager.Toast((object)"unlocking Jiequan 1 Fight");
					Jiequan1Fight.ActuallyTriggerJiequan1Fight();
				}
				if (GUILayout.Button("Unlock Lady E Soulscape", buttonStyle, Array.Empty<GUILayoutOption>()))
				{
					ToastManager.Toast((object)"unlocking Lady E Soulscape");
					LadyESoulscapeEntrance.ActuallyTriggerLadyESoulscape();
				}
				GUILayout.EndHorizontal();
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				if (GUILayout.Button("Move Chiyou into FSP", buttonStyle, Array.Empty<GUILayoutOption>()))
				{
					ShopUnlocks.ActuallyMoveChiyouToFSP();
				}
				if (GUILayout.Button("Move Kuafu into FSP", buttonStyle, Array.Empty<GUILayoutOption>()))
				{
					ShopUnlocks.ActuallyMoveKuafuToFSP();
				}
				if (GUILayout.Button("Unlock Kuafu's Extra Inventory", buttonStyle, Array.Empty<GUILayoutOption>()))
				{
					ShopUnlocks.ActuallyUnlockKuafuExtraInventory();
				}
				GUILayout.EndHorizontal();
				GUILayout.Label("", centeredLabelStyle, Array.Empty<GUILayoutOption>());
				GUILayout.Label("Miscellaneous", centeredLabelStyle, Array.Empty<GUILayoutOption>());
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				if (GUILayout.Button("Unlock Most Teleports", buttonStyle, Array.Empty<GUILayoutOption>()))
				{
					ToastManager.Toast((object)"unlocking most teleport points");
					TeleportPoints.UnlockAllNonPrisonTeleportPoints();
				}
				if (GUILayout.Button("Test Death Link", buttonStyle, Array.Empty<GUILayoutOption>()))
				{
					ToastManager.Toast((object)"triggering test death link");
					DeathLinkManager.OnDeathLinkReceived(new DeathLink("death link test player", "death link test cause"));
				}
				if (GUILayout.Button("Give 99999 Jin", buttonStyle, Array.Empty<GUILayoutOption>()))
				{
					ToastManager.Toast((object)"giving 99999 jin");
					SingletonBehaviour<GameCore>.Instance.playerGameData.AddGold(99999, (GoldSourceTag)4);
				}
				if (GUILayout.Button("Check 1 Unchecked Location", buttonStyle, Array.Empty<GUILayoutOption>()))
				{
					ToastManager.Toast((object)"triggering random unchecked location check");
					long key = ConnectionAndPopups.APSession.Locations.AllMissingLocations[0];
					LocationTriggers.CheckLocation(LocationNames.archipelagoIdToLocation[key]);
				}
				GUILayout.EndHorizontal();
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				if (GUILayout.Button("Enable Weakened Prison State", buttonStyle, Array.Empty<GUILayoutOption>()))
				{
					ToastManager.Toast((object)"enabling weakened prison state");
					PlayerAbilityScenarioModifyPack val = (PlayerAbilityScenarioModifyPack)SingletonBehaviour<SaveManager>.Instance.allFlags.FlagDict["df6a9a9f7748f4baba6207afdf10ea31PlayerAbilityScenarioModifyPack"];
					val.ApplyOverriding((IStatModifierOwner)val);
				}
				if (GUILayout.Button("Disable Weakened Prison State", buttonStyle, Array.Empty<GUILayoutOption>()))
				{
					ToastManager.Toast((object)"disabling weakened prison state");
					PlayerAbilityScenarioModifyPack val2 = (PlayerAbilityScenarioModifyPack)SingletonBehaviour<SaveManager>.Instance.allFlags.FlagDict["df6a9a9f7748f4baba6207afdf10ea31PlayerAbilityScenarioModifyPack"];
					val2.RevertApply((IStatModifierOwner)(object)val2);
					GameObject obj = GameObject.Find("A5_S2/[能力包] Player Ability Buff Debuff Override Pack FSM Variant/General FSM Object/--[States]/FSM/[State] Apply/[Action] PlayerAbilityModifyPackApplyAction");
					PlayerAbilityModifyPackApplyAction val3 = ((obj != null) ? obj.GetComponent<PlayerAbilityModifyPackApplyAction>() : null);
					if ((Object)(object)val3 != (Object)null)
					{
						val2.RevertApply((IStatModifierOwner)(object)val3);
					}
				}
				if (GUILayout.Button("Toggle Hat", buttonStyle, Array.Empty<GUILayoutOption>()))
				{
					ToastManager.Toast((object)"toggling hat");
					Player i = Player.i;
					if (Object.op_Implicit((Object)(object)i))
					{
						bool flag = AccessTools.FieldRefAccess<Player, bool>("_hasHat").Invoke(i);
						i.SetHasHat(!flag);
					}
				}
				if (GUILayout.Button("Toggle Story Walk", buttonStyle, Array.Empty<GUILayoutOption>()))
				{
					ToastManager.Toast((object)"toggling story walk");
					Player i2 = Player.i;
					if (i2 != null)
					{
						i2.SetStoryWalk(!i2.IsStoryWalk, 1f);
					}
				}
				GUILayout.EndHorizontal();
				GUILayout.Label("", centeredLabelStyle, Array.Empty<GUILayoutOption>());
				GUILayout.Label("Core Progression Items", centeredLabelStyle, Array.Empty<GUILayoutOption>());
				GUIStyle val4 = new GUIStyle(labelStyle);
				val4.fixedWidth = 200f;
				GUIStyle val5 = new GUIStyle(buttonStyle);
				val5.fixedWidth = 50f;
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				GUILayout.Label("MysticNymphScoutMode", val4, Array.Empty<GUILayoutOption>());
				if (GUILayout.Button("On", val5, Array.Empty<GUILayoutOption>()))
				{
					InMemoryInventory.UpdateItemCount(Item.MysticNymphScoutMode, 1);
				}
				if (GUILayout.Button("Off", val5, Array.Empty<GUILayoutOption>()))
				{
					InMemoryInventory.UpdateItemCount(Item.MysticNymphScoutMode, 0);
				}
				GUILayout.Label("TaiChiKick", val4, Array.Empty<GUILayoutOption>());
				if (GUILayout.Button("On", val5, Array.Empty<GUILayoutOption>()))
				{
					InMemoryInventory.UpdateItemCount(Item.TaiChiKick, 1);
				}
				if (GUILayout.Button("Off", val5, Array.Empty<GUILayoutOption>()))
				{
					InMemoryInventory.UpdateItemCount(Item.TaiChiKick, 0);
				}
				GUILayout.EndHorizontal();
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				GUILayout.Label("ChargedStrike", val4, Array.Empty<GUILayoutOption>());
				if (GUILayout.Button("On", val5, Array.Empty<GUILayoutOption>()))
				{
					InMemoryInventory.UpdateItemCount(Item.ChargedStrike, 1);
				}
				if (GUILayout.Button("Off", val5, Array.Empty<GUILayoutOption>()))
				{
					InMemoryInventory.UpdateItemCount(Item.ChargedStrike, 0);
				}
				GUILayout.Label("AirDash", val4, Array.Empty<GUILayoutOption>());
				if (GUILayout.Button("On", val5, Array.Empty<GUILayoutOption>()))
				{
					InMemoryInventory.UpdateItemCount(Item.AirDash, 1);
				}
				if (GUILayout.Button("Off", val5, Array.Empty<GUILayoutOption>()))
				{
					InMemoryInventory.UpdateItemCount(Item.AirDash, 0);
				}
				GUILayout.EndHorizontal();
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				GUILayout.Label("UnboundedCounter", val4, Array.Empty<GUILayoutOption>());
				if (GUILayout.Button("On", val5, Array.Empty<GUILayoutOption>()))
				{
					InMemoryInventory.UpdateItemCount(Item.UnboundedCounter, 1);
				}
				if (GUILayout.Button("Off", val5, Array.Empty<GUILayoutOption>()))
				{
					InMemoryInventory.UpdateItemCount(Item.UnboundedCounter, 0);
				}
				GUILayout.Label("CloudLeap", val4, Array.Empty<GUILayoutOption>());
				if (GUILayout.Button("On", val5, Array.Empty<GUILayoutOption>()))
				{
					InMemoryInventory.UpdateItemCount(Item.CloudLeap, 1);
				}
				if (GUILayout.Button("Off", val5, Array.Empty<GUILayoutOption>()))
				{
					InMemoryInventory.UpdateItemCount(Item.CloudLeap, 0);
				}
				GUILayout.EndHorizontal();
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				GUILayout.Label("SuperMutantBuster", val4, Array.Empty<GUILayoutOption>());
				if (GUILayout.Button("On", val5, Array.Empty<GUILayoutOption>()))
				{
					InMemoryInventory.UpdateItemCount(Item.SuperMutantBuster, 1);
				}
				if (GUILayout.Button("Off", val5, Array.Empty<GUILayoutOption>()))
				{
					InMemoryInventory.UpdateItemCount(Item.SuperMutantBuster, 0);
				}
				GUILayout.Label("Wall Climb", val4, Array.Empty<GUILayoutOption>());
				if (GUILayout.Button("On", val5, Array.Empty<GUILayoutOption>()))
				{
					InMemoryInventory.UpdateItemCount(Item.WallClimb, 1);
				}
				if (GUILayout.Button("Off", val5, Array.Empty<GUILayoutOption>()))
				{
					InMemoryInventory.UpdateItemCount(Item.WallClimb, 0);
				}
				GUILayout.EndHorizontal();
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				GUILayout.Label("Grapple", val4, Array.Empty<GUILayoutOption>());
				if (GUILayout.Button("On", val5, Array.Empty<GUILayoutOption>()))
				{
					InMemoryInventory.UpdateItemCount(Item.Grapple, 1);
				}
				if (GUILayout.Button("Off", val5, Array.Empty<GUILayoutOption>()))
				{
					InMemoryInventory.UpdateItemCount(Item.Grapple, 0);
				}
				GUILayout.Label("Ledge Grab", val4, Array.Empty<GUILayoutOption>());
				if (GUILayout.Button("On", val5, Array.Empty<GUILayoutOption>()))
				{
					InMemoryInventory.UpdateItemCount(Item.LedgeGrab, 1);
				}
				if (GUILayout.Button("Off", val5, Array.Empty<GUILayoutOption>()))
				{
					InMemoryInventory.UpdateItemCount(Item.LedgeGrab, 0);
				}
				GUILayout.EndHorizontal();
				GUIStyle val6 = new GUIStyle(buttonStyle);
				val6.fixedWidth = 80f;
				GUILayout.Label("", centeredLabelStyle, Array.Empty<GUILayoutOption>());
				GUILayout.Label("Arbitrary Item Update (using the mod's Item enum, not the AP names)", centeredLabelStyle, Array.Empty<GUILayoutOption>());
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				DebugPopup_Item = GUILayout.TextField(DebugPopup_Item, textFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(((Rect)(ref windowRect)).width * 0.3f) });
				DebugPopup_Count = GUILayout.TextField(DebugPopup_Count, textFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(((Rect)(ref windowRect)).width * 0.1f) });
				if (GUILayout.Button("Update", val6, Array.Empty<GUILayoutOption>()))
				{
					InMemoryInventory.UpdateItemCount(Enum.Parse<Item>(DebugPopup_Item), int.Parse(DebugPopup_Count));
				}
				GUILayout.EndHorizontal();
			}, "Archipelago Randomizer Debug Tools (Ctrl+Alt+Shift+D to show/hide)", windowStyle);
		}
	}
	[HarmonyPatch]
	internal class ForcedPurchases
	{
		private static long LogicDifficulty = 1L;

		private static List<Location> DarkSteelPurchases = new List<Location>(6)
		{
			Location.SHOP_KUAFU_DARK_STEEL_1,
			Location.SHOP_KUAFU_DARK_STEEL_2,
			Location.SHOP_KUAFU_DARK_STEEL_3,
			Location.SHOP_KUAFU_EXTRA_DARK_STEEL_1,
			Location.SHOP_KUAFU_EXTRA_DARK_STEEL_2,
			Location.SHOP_KUAFU_EXTRA_DARK_STEEL_3
		};

		private static List<Location> HerbCatalystPurchases = new List<Location>(8)
		{
			Location.SHOP_KUAFU_HERB_CATALYST_1,
			Location.SHOP_KUAFU_HERB_CATALYST_2,
			Location.SHOP_KUAFU_HERB_CATALYST_3,
			Location.SHOP_KUAFU_HERB_CATALYST_4,
			Location.SHOP_KUAFU_HERB_CATALYST_5,
			Location.SHOP_KUAFU_HERB_CATALYST_6,
			Location.SHOP_KUAFU_HERB_CATALYST_7,
			Location.SHOP_KUAFU_HERB_CATALYST_8
		};

		public static void ApplySlotData(long? logicDifficulty)
		{
			LogicDifficulty = logicDifficulty.GetValueOrDefault();
		}

		private static bool ShouldBlock_ShopRandoOff(MerchandiseData __instance)
		{
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Expected O, but got Unknown
			if (LogicDifficulty == 0L)
			{
				return false;
			}
			List<MaterialRequirementEntry> requireMaterialEntriesToBuy = __instance.requireMaterialEntriesToBuy;
			if (requireMaterialEntriesToBuy.Count != 1)
			{
				return false;
			}
			MaterialRequirementEntry val = requireMaterialEntriesToBuy[0];
			if (((val != null) ? val.item.Title : null) != "Dark Steel")
			{
				return false;
			}
			bool isAcquired = ((GameFlagDescriptable)Player.i.mainAbilities.ChargedAttackAbility).IsAcquired;
			if (isAcquired)
			{
				return false;
			}
			if (((Object)__instance).name == "Merchandise_2_1_貫穿箭LV2")
			{
				return false;
			}
			PlayerWeaponData val2 = (PlayerWeaponData)SingletonBehaviour<SaveManager>.Instance.allFlags.FlagDict["2f7009a00edd57c4fa4332ffcd15396aPlayerWeaponData"];
			if (((GameFlagDescriptable)val2).IsAcquired)
			{
				return false;
			}
			int remainingDarkSteelCount = GetRemainingDarkSteelCount();
			if (remainingDarkSteelCount > 1)
			{
				return false;
			}
			Log.Info($"ShouldBlock_ShopRandoOff: LogicDifficulty={LogicDifficulty}, entry.item.Title={val.item.Title}, csAcquired={isAcquired}, __instance.name={((Object)__instance).name}, cloudPiercerS.IsAcquired={((GameFlagDescriptable)val2).IsAcquired}, darkSteelCount={remainingDarkSteelCount}");
			return true;
		}

		public static bool IsDarkSteelPurchase(MerchandiseData __instance)
		{
			List<MaterialRequirementEntry> requireMaterialEntriesToBuy = __instance.requireMaterialEntriesToBuy;
			if (requireMaterialEntriesToBuy.Count != 1)
			{
				return false;
			}
			MaterialRequirementEntry obj = requireMaterialEntriesToBuy[0];
			return ((obj != null) ? obj.item.Title : null) == "Dark Steel";
		}

		public static bool IsHerbCatalystPurchase(MerchandiseData __instance)
		{
			List<MaterialRequirementEntry> requireMaterialEntriesToBuy = __instance.requireMaterialEntriesToBuy;
			if (requireMaterialEntriesToBuy.Count != 1)
			{
				return false;
			}
			MaterialRequirementEntry obj = requireMaterialEntriesToBuy[0];
			return ((obj != null) ? obj.item.Title : null) == "Herb Catalyst";
		}

		public static int GetRemainingDarkSteelCount()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			return ((FlagField<int>)(object)((ItemData)((AbstractGameFlagCollection)SingletonBehaviour<UIManager>.Instance.allItemCollections[2]).rawCollection[13]).ownNum).CurrentValue;
		}

		public static int GetRemainingHerbCatalystCount()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			return ((FlagField<int>)(object)((ItemData)((AbstractGameFlagCollection)SingletonBehaviour<UIManager>.Instance.allItemCollections[2]).rawCollection[14]).ownNum).CurrentValue;
		}

		private static SerializableItemInfo[]? GetScoutsForDarkSteelPurchases()
		{
			Dictionary<Location, SerializableItemInfo> scouts = APSaveManager.CurrentAPSaveData?.scoutedLocations;
			if (scouts == null)
			{
				return null;
			}
			return DarkSteelPurchases.Select((Location location) => scouts[location]).ToArray();
		}

		private static SerializableItemInfo[]? GetScoutsForHerbCatalystPurchases()
		{
			Dictionary<Location, SerializableItemInfo> scouts = APSaveManager.CurrentAPSaveData?.scoutedLocations;
			if (scouts == null)
			{
				return null;
			}
			return HerbCatalystPurchases.Select((Location location) => scouts[location]).ToArray();
		}

		public static string[]? ShouldBlock_ShopRandoOn(MerchandiseData __instance)
		{
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			if (!ShopRando.merchDataNameToLocation.TryGetValue(((Object)__instance).name, out var thisLocation))
			{
				return null;
			}
			bool flag = IsDarkSteelPurchase(__instance);
			bool flag2 = IsHerbCatalystPurchase(__instance);
			if (!flag && !flag2)
			{
				return null;
			}
			int valueOrDefault = InMemoryInventory.ApInventory.GetValueOrDefault(flag ? Item.DarkSteel : Item.HerbCatalyst, 0);
			IEnumerable<SerializableItemInfo> source = from scout in (flag ? GetScoutsForDarkSteelPurchases() : GetScoutsForHerbCatalystPurchases()).Take(valueOrDefault).Where(delegate(SerializableItemInfo scout)
				{
					Location location = LocationNames.archipelagoIdToLocation[((MinimalSerializableItemInfo)scout).LocationId];
					return !(APSaveManager.CurrentAPSaveData?.locationsChecked?.GetValueOrDefault(location.ToString(), defaultValue: false)).GetValueOrDefault();
				})
				where ((Enum)((MinimalSerializableItemInfo)scout).Flags).HasFlag((Enum)(object)(ItemFlags)1)
				select scout;
			int num = source.Count();
			if (num == 0)
			{
				return null;
			}
			int num2 = (flag ? GetRemainingDarkSteelCount() : GetRemainingHerbCatalystCount());
			if (num < num2)
			{
				return null;
			}
			SerializableItemInfo value = default(SerializableItemInfo);
			if ((APSaveManager.CurrentAPSaveData?.scoutedLocations?.TryGetValue(thisLocation, out value)).GetValueOrDefault())
			{
				bool flag3 = ((Enum)((MinimalSerializableItemInfo)value).Flags).HasFlag((Enum)(object)(ItemFlags)1);
				int num3 = (flag ? DarkSteelPurchases : HerbCatalystPurchases).FindIndex((Location loc) => loc == thisLocation);
				Log.Info($"ShouldBlock_ShopRandoOn: thisLocation={thisLocation}, thisIndex={num3}, thisIsProgression={flag3}, apReceivedCount={valueOrDefault}, unboughtProgInLogicCount={num}, remainingMaterialCount={num2}");
				string[] result = source.Select((SerializableItemInfo scout) => ShopRando.scoutInfoToShopTitle(scout)).ToArray();
				if (!flag3)
				{
					return result;
				}
				if (num3 >= valueOrDefault)
				{
					return result;
				}
			}
			return null;
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(MerchandiseData), "HasEnoughMaterial")]
		public static void MerchandiseData_HasEnoughMaterial(MerchandiseData __instance, ref bool __result)
		{
			if (ShopRando.RandomizeShops)
			{
				if (ShouldBlock_ShopRandoOn(__instance) != null)
				{
					Log.Info("MerchandiseData_HasEnoughMaterial patch blocking purchase of '" + ((Object)__instance).name + "' until more DS/HC items have been received");
					__result = false;
				}
			}
			else if (ShouldBlock_ShopRandoOff(__instance))
			{
				Log.Info("MerchandiseData_HasEnoughMaterial patch blocking purchase of '" + ((GameFlagDescriptable)__instance).Title + "' until Cloud Piercer S has been purchased");
				__result = false;
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		private static void MerchandiseData_get_Description(MerchandiseData __instance, ref string __result)
		{
			if (!ShopRando.RandomizeShops && ShouldBlock_ShopRandoOff(__instance))
			{
				__result = LoadingScreenTips.apRainbow + ": \nBecause you're playing on medium or higher logic difficulty, you don't have Charged Strike yet, and you only have one Dark Steel available right now, <color=orange>you must spend your last Dark Steel on Cloud Piercer S</color>. \n\n" + __result;
			}
		}
	}
	[HarmonyPatch]
	internal class InGameConsole
	{
		public static List<string> consoleMessages = new List<string>();

		private static readonly int backlogLimit = 1000;

		private static bool truncatingBacklog = false;

		private const int SHORT_DELAY_MS = 100;

		private const int LONG_DELAY_MS = 1000;

		private static int currentDelayTime = 100;

		private const int MAX_TOASTS_BEFORE_HIDING = 5;

		private static Task? displayToastsTask = null;

		public static ConcurrentStack<string> pendingToasts = new ConcurrentStack<string>();

		public static GUIStyle? windowStyle = null;

		public static GUIStyle? labelStyle = null;

		public static GUIStyle? textFieldStyle = null;

		public static GUIStyle? buttonStyle = null;

		public static GUIStyle? backlogStyle = null;

		public static Texture2D? grayBgColorTex = null;

		public static Texture2D? blackBgColorTex = null;

		public static bool drewConsoleInLastOnGUICall = false;

		private static string ConsoleInput = "";

		private static Vector2? scrollPos = null;

		public static void Add(string message, bool forceImmediateToast = false)
		{
			consoleMessages.Add(message);
			while (consoleMessages.Count > backlogLimit)
			{
				consoleMessages.RemoveAt(0);
				truncatingBacklog = true;
			}
			if (forceImmediateToast)
			{
				ToastManager.Toast((object)message);
			}
			else
			{
				ScheduleToast(message);
			}
		}

		private static void ScheduleToast(string message)
		{
			pendingToasts.Push(message);
			if (displayToastsTask == null)
			{
				StartNewToastTask();
			}
		}

		private static void StartNewToastTask()
		{
			displayToastsTask = Task.Delay(currentDelayTime).ContinueWith(delegate
			{
				DisplayToasts();
			}, TaskScheduler.Default);
		}

		private static void DisplayToasts()
		{
			if (pendingToasts.Count > 5)
			{
				if (APRandomizer.Instance.BatchSimultaneousMessagesSetting.Value)
				{
					int count = pendingToasts.Count;
					pendingToasts.Clear();
					ToastManager.Toast((object)string.Format("Received {0} messages within {1}. Pause to read them.", count, (currentDelayTime == 100) ? "100 ms" : "1 second"));
				}
				else
				{
					string result;
					while (pendingToasts.TryPop(out result))
					{
						ToastManager.Toast((object)result);
					}
				}
				currentDelayTime = 1000;
				StartNewToastTask();
			}
			else
			{
				string result2;
				while (pendingToasts.TryPop(out result2))
				{
					ToastManager.Toast((object)result2);
				}
				currentDelayTime = 100;
				displayToastsTask = null;
			}
		}

		public static void UpdateStyles()
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Expected O, but got Unknown
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Expected O, but got Unknown
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Expected O, but got Unknown
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Expected O, but got Unknown
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Expected O, but got Unknown
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Expected O, but got Unknown
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Expected O, but got Unknown
			//IL_0156: Unknown result type (might be due to invalid IL or missing references)
			if (windowStyle == null || labelStyle == null || textFieldStyle == null || buttonStyle == null || backlogStyle == null)
			{
				windowStyle = new GUIStyle(GUI.skin.window);
				grayBgColorTex = new Texture2D(1, 1, (TextureFormat)20, false);
				grayBgColorTex.SetPixel(0, 0, new Color(0.3f, 0.3f, 0.3f, 1f));
				grayBgColorTex.Apply();
				windowStyle.normal.background = grayBgColorTex;
				windowStyle.onActive.background = grayBgColorTex;
				windowStyle.onFocused.background = grayBgColorTex;
				windowStyle.onHover.background = grayBgColorTex;
				windowStyle.onNormal.background = grayBgColorTex;
				labelStyle = new GUIStyle(GUI.skin.label);
				textFieldStyle = new GUIStyle(GUI.skin.textField);
				buttonStyle = new GUIStyle(GUI.skin.button);
				backlogStyle = new GUIStyle(GUI.skin.scrollView);
				blackBgColorTex = new Texture2D(1, 1, (TextureFormat)20, false);
				blackBgColorTex.SetPixel(0, 0, new Color(0f, 0f, 0f, 1f));
				blackBgColorTex.Apply();
				backlogStyle.normal.background = blackBgColorTex;
				backlogStyle.onActive.background = blackBgColorTex;
				backlogStyle.onFocused.background = blackBgColorTex;
				backlogStyle.onHover.background = blackBgColorTex;
				backlogStyle.onNormal.background = blackBgColorTex;
			}
			float num = Mathf.Min((float)Screen.width / 1920f, (float)Screen.height / 1080f);
			int fontSize = Mathf.RoundToInt(24f * num);
			windowStyle.fontSize = fontSize;
			labelStyle.fontSize = fontSize;
			textFieldStyle.fontSize = fontSize;
			buttonStyle.fontSize = fontSize;
		}

		public static void OnGUI()
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Invalid comparison between Unknown and I4
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Invalid comparison between Unknown and I4
			if (SingletonBehaviour<UIManager>.IsAvailable())
			{
				UIRootPanel pausePanelUI = SingletonBehaviour<UIManager>.Instance.PausePanelUI;
				GameObject gameObject = ((Component)((Component)pausePanelUI).gameObject.transform.Find("Pause Menu")).gameObject;
				UIPanelState state = ((RCGUIPanel)pausePanelUI).state;
				if (gameObject.activeSelf && ((int)state == 3 || (int)state == 1))
				{
					DrawInGameConsole(!drewConsoleInLastOnGUICall);
					drewConsoleInLastOnGUICall = true;
				}
				else
				{
					drewConsoleInLastOnGUICall = false;
				}
			}
		}

		private static void DrawInGameConsole(bool resetScrollPosition)
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Expected O, but got Unknown
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			UpdateStyles();
			float num = (float)Screen.width * 0.5f;
			float num2 = (float)Screen.height * 0.63f;
			Rect windowRect = new Rect(((float)Screen.width - num) * 0.95f, ((float)Screen.height - num2) * 0.6f, num, num2);
			float scrollPanelHeight = ((Rect)(ref windowRect)).height * 0.87f;
			if (!scrollPos.HasValue || resetScrollPosition)
			{
				scrollPos = new Vector2(0f, float.MaxValue);
			}
			string text = "Archipelago Console";
			if (truncatingBacklog)
			{
				text += $" (last {backlogLimit} messages)";
			}
			GUI.Window(11261729, windowRect, (WindowFunction)delegate
			{
				//IL_001e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0028: Unknown result type (might be due to invalid IL or missing references)
				//IL_0098: Unknown result type (might be due to invalid IL or missing references)
				//IL_009e: Invalid comparison between Unknown and I4
				//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
				//IL_00af: Invalid comparison between Unknown and I4
				//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
				//IL_00bd: Invalid comparison between Unknown and I4
				GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(scrollPanelHeight) });
				scrollPos = GUILayout.BeginScrollView(scrollPos.Value, backlogStyle);
				foreach (string consoleMessage in consoleMessages)
				{
					GUILayout.Label(consoleMessage, Array.Empty<GUILayoutOption>());
				}
				GUILayout.EndScrollView();
				GUILayout.EndVertical();
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				if (GUI.GetNameOfFocusedControl() == "APCommandEntry" && (int)Event.current.type == 4 && ((int)Event.current.keyCode == 271 || (int)Event.current.keyCode == 13))
				{
					ExecuteAPCommand();
				}
				GUI.SetNextControlName("APCommandEntry");
				ConsoleInput = GUILayout.TextField(ConsoleInput, textFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(((Rect)(ref windowRect)).width * 0.97f) });
				GUILayout.EndHorizontal();
			}, text, windowStyle);
		}

		private static void ExecuteAPCommand()
		{
			string text = ConsoleInput;
			if (text == "")
			{
				return;
			}
			if (ConnectionAndPopups.APSession == null)
			{
				Add("<color=orange>Cannot send AP message '" + text + "' without a connection to the AP server</color>");
				return;
			}
			Task.Run(delegate
			{
				if (!Task.Run(delegate
				{
					ConnectionAndPopups.APSession.Say(text);
				}).Wait(TimeSpan.FromSeconds(2.0)))
				{
					Add("<color=orange>AP server timed out when we tried to send the message '" + text + "'. Did the connection go down?</color>");
				}
				ConsoleInput = "";
			});
		}
	}
	[HarmonyPatch]
	internal class JadeCosts
	{
		public static Dictionary<string, string?> JadeEnglishTitleToSaveFlag = new Dictionary<string, string>
		{
			{ "Immovable Jade", "b8fd8e42229824b788bc222b837382f2JadeData" },
			{ "Harness Force Jade", "a0a2cb6d037ee4d80a74fd447a21682eJadeData" },
			{ "Focus Jade", "36eb7e7b95e91467191b8f24dbbb5a3eJadeData" },
			{ "Swift Descent Jade", "1e635338961c24feb93798c36c07f128JadeData" },
			{ "Medical Jade", "8417398823dca444b924aa9e49e82385JadeData" },
			{ "Quick Dose Jade", "316728bf6fa814c8085a4ce094c6cabbJadeData" },
			{ "Steely Jade", "28837290da6d24917ad6c99213d99d3dJadeData" },
			{ "Stasis Jade", "1e983ace0eb874a3a883c5f1f50e2926JadeData" },
			{ "Mob Quell Jade - Yin", "45a17198c6bff4c42989f3e2d9cb583bJadeData" },
			{ "Mob Quell Jade - Yang", "8ff52186b5d2849f6930bd5bf5d86b8aJadeData" },
			{ "Bearing Jade", "fce2186e0ae684bde9548905d5ed5533JadeData" },
			{ "Divine Hand Jade", "ef792d1867d1a4a9c8ec6cd721ee5cb3JadeData" },
			{ "Iron Skin Jade", "ff5f58b8404514c11b7ec4166b294349JadeData" },
			{ "Pauper Jade", "562375e7a68ec42b28f3bdd5f45d7b72JadeData" },
			{ "Swift Blade Jade", "b4c7da472cfba425ba5d0b0309dc4f17JadeData" },
			{ "Last Stand Jade", "3411e0d523aec41f9be4e24ff81b6293JadeData" },
			{ "Recovery Jade", "e6f162e19282346db96145ee80b5ccc1JadeData" },
			{ "Breather Jade", "3ddbef7a7a579497b82fe3712177c089JadeData" },
			{ "Hedgehog Jade", "3c8fd0425b80a405a8fb9623094fcafcJadeData" },
			{ "Ricochet Jade", "dbda764ac569f4d6b871fb6c82f11adeJadeData" },
			{ "Revival Jade", "987349e8a21844d28a86853bb0e5de09JadeData" },
			{ "Soul Reaper Jade", "468a3373787c2443794e57f101b5f794JadeData" },
			{ "Health Thief Jade", "1796f5882076b4c7c859bc4b0747d8bbJadeData" },
			{ "Qi Blade Jade", "dfa6bbf26dfef4032a5287a7d9b27881JadeData" },
			{ "Qi Swipe Jade", "111a1eb49b6d0476488eba696f991e19JadeData" },
			{ "Reciprocation Jade", "589b90f2463944b95aeb6821385b3be6JadeData" },
			{ "Cultivation Jade", "cfcd9f0d330344e628e7d8742955c172JadeData" },
			{ "Avarice Jade", "88263fdff21bc8b4da3977c47ab02f03JadeData" },
			{ "Qi Thief Jade", null },
			{ "Killing Blow Jade", null }
		};

		private static Dictionary<string, int> JadeSaveFlagToVanillaCost = new Dictionary<string, int>();

		public static Dictionary<string, long> JadeSaveFlagToSlotDataCost = new Dictionary<string, long>();

		public static void ApplySlotData(object? jadeCosts)
		{
			JadeSaveFlagToSlotDataCost = new Dictionary<string, long>();
			if (jadeCosts is string && (string)jadeCosts == "vanilla")
			{
				return;
			}
			JObject val = (JObject)((jadeCosts is JObject) ? jadeCosts : null);
			if (val == null)
			{
				Log.Error("JadeCosts::ApplySlotData aborting because jadeCosts was neither 'vanilla' nor a JObject");
				return;
			}
			foreach (var (text2, val3) in val)
			{
				if (JadeEnglishTitleToSaveFlag.ContainsKey(text2))
				{
					string text3 = JadeEnglishTitleToSaveFlag[text2];
					if (text3 != null)
					{
						JadeSaveFlagToSlotDataCost[text3] = (long)(val3 ?? JToken.op_Implicit(0));
					}
				}
				else
				{
					Log.Error("Unrecognized jade in slot data: " + text2);
				}
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(GameLevel), "Awake")]
		private static void GameLevel_Awake(GameLevel __instance)
		{
			try
			{
				bool flag = JadeSaveFlagToSlotDataCost.Count == 0;
				if (PlayerGamePlayData.Instance.memoryMode.CurrentValue)
				{
					flag = true;
				}
				JadeDataCollection jadeDataColleciton = Player.i.mainAbilities.jadeDataColleciton;
				List<JadeData> gameFlagDataList = ((GameFlagBaseCollection<JadeData>)(object)jadeDataColleciton).gameFlagDataList;
				if (JadeSaveFlagToVanillaCost.Count == 0)
				{
					foreach (JadeData item in gameFlagDataList)
					{
						JadeSaveFlagToVanillaCost[((GameFlagBase)item).FinalSaveID] = item.Cost;
					}
				}
				int num = 0;
				foreach (JadeData item2 in gameFlagDataList)
				{
					string finalSaveID = ((GameFlagBase)item2).FinalSaveID;
					if (!JadeSaveFlagToVanillaCost.ContainsKey(finalSaveID))
					{
						Log.Error("jade cost application failed for " + ((GameFlagDescriptable)item2).Title + " / " + finalSaveID + ", somehow it was missing from JadeSaveFlagToVanillaCost");
					}
					else if (!flag && !JadeSaveFlagToSlotDataCost.ContainsKey(finalSaveID))
					{
						Log.Error("jade cost application failed for " + ((GameFlagDescriptable)item2).Title + " / " + finalSaveID + ", somehow it was missing from JadeSaveFlagToSlotDataCost");
					}
					else
					{
						int num2 = (int)(flag ? JadeSaveFlagToVanillaCost[finalSaveID] : JadeSaveFlagToSlotDataCost[finalSaveID]);
						if (item2.Cost != num2)
						{
							item2.Cost = num2;
							AccessTools.FieldRefAccess<JadeData, List<StatModifierEntry>>("EquipEffectModifierEntries").Invoke(item2)[0].value = num2;
							num++;
						}
					}
				}
				float value = ((AbstractStatData)jadeDataColleciton.PlayerCurrentJadePowerStat).Value;
				AccessTools.Method(typeof(JadeDataCollection), "InitCalculateCurrentJadePowerUsage", Array.Empty<Type>(), (Type[])null).Invoke(jadeDataColleciton, Array.Empty<object>());
				float value2 = ((AbstractStatData)jadeDataColleciton.PlayerCurrentJadePowerStat).Value;
				if (num == 0)
				{
					if (flag)
					{
						Log.Info("JadeCosts::GameLevel_Awake did nothing because all jades were already set to their vanilla costs");
					}
					else
					{
						Log.Info("JadeCosts::GameLevel_Awake did nothing because all jades were already set to this slot's custom jade costs");
					}
				}
				else if (flag)
				{
					Log.Info($"JadeCosts::GameLevel_Awake reset {num} jades to their vanilla costs; cost in use changed from {value} to {value2}");
				}
				else
				{
					Log.Info($"JadeCosts::GameLevel_Awake applied {num} custom jade costs; cost in use changed from {value} to {value2}");
				}
			}
			catch (Exception ex)
			{
				Log.Error("JadeCosts::GameLevel_Awake threw: " + ex.Message + "\nwith stack:\n" + ex.StackTrace + "\nand InnerException: " + ex.InnerException?.Message + "\nwith stack:\n" + ex.InnerException?.StackTrace);
			}
		}
	}
	[HarmonyPatch]
	internal class LoadingScreenTips
	{
		public static string apRainbow = "<color=#c97682>ARC</color><color=#75c275>HIP</color><color=#ca94c2>ELA</color><color=#d9a07d>GO R</color><color=#767ebd>AND</color><color=#eee391>OMI</color><color=#c97682>ZER</color>";

		private static List<string> randomizerTips = new List<string>(13)
		{
			"Press F1 to access settings for all your Nine Sols mods, including this randomizer.", "Reaching Eigong requires only Sol Seal items. There's no need to visit Tianhuo Research Institute.", "This randomizer depends on the TeleportFromAnywhere mod because an important item may end up randomly placed in a \"dead end\" you can only escape by teleporting.", "Shennong will become sick only after you acquire your first poison item.", "The randomizer's \"logic\" assumes:\n- Jiequan requires Charged Strike\n- Lady Ethereal requires Air Dash\n- Ji requires Tai-Chi Kick\n- Eigong requires Air Dash or Cloud Leap", "There are 5 mutants who drop an item when permanently killed with Super Mutant Buster.\n2 in ED (Living Area), 2 in ED (Sanctum), and 1 in TRC.", "The Peach Blossom Village rescue can be done as soon as you find the Abandoned Mines Access Token. It's no longer tied to escaping Prison and being rescued by Chiyou.", "Since talking to Ji at Daybreak Tower is a location, this randomizer makes Ji one of the few NPCs who can talk to you after his own death.", "Fighting Jiequan for real at the top of the Factory Zone can be done either before or after the Prison sequence. This makes Jiequan the only NPC who can kill you after he dies.", "All \"Limitless Realm\" segments are disabled and skipped in this randomizer.",
			"If Apeman Facility (Monitoring) was not your first root node, then that node will be automatically unlocked when you enter AF(M), because the upper part of AF(M) is unreachable without it.", "The large spike ball in Grotto (East) will never land in Grotto (Entry) in this randomizer, since it would block critical paths if we let it.", "This randomizer doesn't touch the items that are only reachable after the \"Point of no Return\", or after giving Shennong all poisons. You're free to replay that content or ignore it."
		};

		[HarmonyPostfix]
		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		private static void LoadingScreenTipDataCollection_get_FetchAcquiredTips(LoadingScreenTipDataCollection __instance, List<LoadingScreenTipData> __result)
		{
			if (__result == null)
			{
				Log.Error("LoadingScreenTipDataCollection_get_FetchAcquiredTips aborting because this tip collection is null");
				return;
			}
			if (__result.Count <= 0)
			{
				Log.Error("LoadingScreenTipDataCollection_get_FetchAcquiredTips aborting because this tip collection is empty");
				return;
			}
			LoadingScreenTipData item = __result[0];
			__result.Clear();
			foreach (string randomizerTip in randomizerTips)
			{
				_ = randomizerTip;
				__result.Add(item);
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(LoadingScreenPanel), "UpdateView")]
		private static void LoadingScreenPanel_UpdateView(LoadingScreenPanel __instance)
		{
			TextMeshProUGUI component = GameObject.Find("ApplicationCore(Clone) (RCGLifeCycle)/4 UIGroupManager/ApplicationUICam/[Canvas]LoadingScreenPanel/LoadingScreenPanel/TipPanel/PanelMask/DialoguePanel/Background/Outline/TitleText").GetComponent<TextMeshProUGUI>();
			if (((TMP_Text)component).text != apRainbow)
			{
				((TMP_Text)component).text = apRainbow;
				((TMP_Text)component).enableWordWrapping = false;
			}
			if (!((OneAxisInputControl)__instance.nextActionData.Action).WasPressed && !((OneAxisInputControl)__instance.previousActionData.Action).WasPressed)
			{
				AccessTools.FieldRefAccess<LoadingScreenPanel, int>("currentIndex").Invoke(__instance) = Random.Range(0, randomizerTips.Count);
			}
			int num = AccessTools.FieldRefAccess<LoadingScreenPanel, int>("currentIndex").Invoke(__instance);
			AccessTools.FieldRefAccess<LoadingScreenPanel, TMP_Text>("step").Invoke(__instance).text = $"{num + 1}/{randomizerTips.Count}";
			AccessTools.FieldRefAccess<LoadingScreenPanel, TMP_Text>("tipText").Invoke(__instance).text = randomizerTips[num];
		}
	}
	[HarmonyPatch]
	internal class PreventWeakenedPrisonState
	{
		private static bool ShouldPrevent;

		public static void ApplySlotData(long? rawPreventValue)
		{
			if (!rawPreventValue.HasValue)
			{
				ShouldPrevent = false;
			}
			else
			{
				ShouldPrevent = rawPreventValue == 1;
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(PlayerAbilityModifyPackApplyAction), "OnStateEnterImplement")]
		private static bool PlayerAbilityModifyPackApplyAction_OnStateEnterImplement(PlayerAbilityModifyPackApplyAction __instance)
		{
			if (!ShouldPrevent)
			{
				return true;
			}
			if (((Object)__instance.Pack).name == "A5 Jail Debuff Pack 虛弱監獄" && LocationTriggers.GetFullDisambiguatedPath(((Component)__instance).gameObject) == "A5_S2/[能力包] Player Ability Buff Debuff Override Pack FSM Variant/General FSM Object/--[States]/FSM/[State] Apply/[Action] PlayerAbilityModifyPackApplyAction")
			{
				Log.Info("PreventWeakenedPrisonState patch preventing Prison's PlayerAbilityModifyPackApplyAction from running");
				InGameConsole.Add("Prevented weakened Prison state from being applied to Yi, since this slot was generated with prevent_weakened_prison_state: true");
				return false;
			}
			return true;
		}
	}
	[HarmonyPatch]
	internal class ShopUnlocks
	{
		private enum ShopUnlockMethod
		{
			VanillaLikeLocations,
			SolSeals,
			UnlockItems
		}

		private static ShopUnlockMethod unlockMethod = ShopUnlockMethod.VanillaLikeLocations;

		private static long kuafuSeals = 0L;

		private static long chiyouSeals = 0L;

		private static long kuafuExtraSeals = 0L;

		private static string kuafuInFSPFlag = "e2ccc29dc8f187b45be6ce50e7f4174aScriptableDataBool";

		private static string chiyouInFSPFlag = "bf49eb7e251013c4cb62eca6e586b465ScriptableDataBool";

		public static string KuafuExtraInventory_ModSaveFlag = "UnlockedKuafuFSPShopExtraInventory";

		private static GameObject? kuafuShopPanel = null;

		public static void ApplySlotData(Dictionary<string, object> slotData)
		{
			if (!slotData.ContainsKey("shop_unlocks"))
			{
				unlockMethod = ShopUnlockMethod.VanillaLikeLocations;
				return;
			}
			long num = (long)slotData["shop_unlocks"];
			switch (num)
			{
			case 0L:
				unlockMethod = ShopUnlockMethod.VanillaLikeLocations;
				break;
			case 1L:
				unlockMethod = ShopUnlockMethod.SolSeals;
				kuafuSeals = (long)slotData["kuafu_shop_unlock_sol_seals"];
				chiyouSeals = (long)slotData["chiyou_shop_unlock_sol_seals"];
				kuafuExtraSeals = (long)slotData["kuafu_extra_inventory_unlock_sol_seals"];
				break;
			case 2L:
				unlockMethod = ShopUnlockMethod.UnlockItems;
				break;
			default:
				Log.Error($"ShopUnlocks::ApplySlotData aborting because shop_unlocks was {num}");
				unlockMethod = ShopUnlockMethod.VanillaLikeLocations;
				break;
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(GameLevel), "Awake")]
		private static void GameLevel_Awake(GameLevel __instance)
		{
			try
			{
				if (unlockMethod == ShopUnlockMethod.SolSeals && (kuafuSeals <= 0 || chiyouSeals <= 0 || kuafuExtraSeals <= 0) && InMemoryInventory.GetSolSealsCount() <= 0)
				{
					Log.Info("ShopUnlocks::GameLevel_Awake handling zero seal unlocks");
					if (kuafuSeals == 0L)
					{
						ActuallyMoveKuafuToFSP();
					}
					if (chiyouSeals == 0L)
					{
						ActuallyMoveChiyouToFSP();
					}
					if (kuafuExtraSeals == 0L)
					{
						ActuallyUnlockKuafuExtraInventory();
					}
				}
			}
			catch (Exception ex)
			{
				Log.Error("ShopUnlocks::GameLevel_Awake threw: " + ex.Message + "\nwith stack:\n" + ex.StackTrace + "\nand InnerException: " + ex.InnerException?.Message + "\nwith stack:\n" + ex.InnerException?.StackTrace);
			}
		}

		public static void OnItemUpdate(Item item)
		{
			if (unlockMethod == ShopUnlockMethod.VanillaLikeLocations)
			{
				return;
			}
			if (unlockMethod == ShopUnlockMethod.SolSeals)
			{
				int solSealsCount = InMemoryInventory.GetSolSealsCount();
				if (solSealsCount >= kuafuSeals)
				{
					ActuallyMoveKuafuToFSP();
				}
				if (solSealsCount >= chiyouSeals)
				{
					ActuallyMoveChiyouToFSP();
				}
				if (solSealsCount >= kuafuExtraSeals)
				{
					ActuallyUnlockKuafuExtraInventory();
				}
			}
			else if (unlockMethod == ShopUnlockMethod.UnlockItems)
			{
				int num = (InMemoryInventory.ApInventory.ContainsKey(Item.ProgressiveShopUnlock) ? InMemoryInventory.ApInventory[Item.ProgressiveShopUnlock] : 0);
				if (num >= 1)
				{
					ActuallyMoveKuafuToFSP();
				}
				if (num >= 2)
				{
					ActuallyMoveChiyouToFSP();
				}
				if (num >= 3)
				{
					ActuallyUnlockKuafuExtraInventory();
				}
			}
		}

		public static void OnLocationCheck(Location location)
		{
			if (unlockMethod == ShopUnlockMethod.VanillaLikeLocations)
			{
				switch (location)
				{
				case Location.RP_KUAFU_SANCTUM:
					ActuallyMoveKuafuToFSP();
					break;
				case Location.FGH_CHIYOU_BRIDGE:
					ActuallyMoveChiyouToFSP();
					ActuallyUnlockKuafuExtraInventory();
					break;
				}
			}
		}

		public static void ActuallyMoveKuafuToFSP()
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			ScriptableDataBool val = (ScriptableDataBool)SingletonBehaviour<SaveManager>.Instance.allFlags.FlagDict[kuafuInFSPFlag];
			if (!val.CurrentValue)
			{
				InGameConsole.Add("Moving Kuafu into FSP and unlocking his shop");
				val.CurrentValue = true;
				ShopRando.EnsureShopsScouted();
			}
		}

		public static void ActuallyMoveChiyouToFSP()
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			ScriptableDataBool val = (ScriptableDataBool)SingletonBehaviour<SaveManager>.Instance.allFlags.FlagDict[chiyouInFSPFlag];
			if (!val.CurrentValue)
			{
				InGameConsole.Add("Moving Chiyou into FSP and unlocking his shop");
				val.CurrentValue = true;
				ShopRando.EnsureShopsScouted();
			}
		}

		public static void ActuallyUnlockKuafuExtraInventory()
		{
			if (APSaveManager.CurrentAPSaveData == null)
			{
				Log.Error("ShopUnlocks::ActuallyUnlockKuafuExtraInventory() aborting because there's no AP connection/save file to write to");
				return;
			}
			APSaveManager.CurrentAPSaveData.otherPersistentModFlags.TryGetValue(KuafuExtraInventory_ModSaveFlag, out var value);
			if (!value)
			{
				InGameConsole.Add("Unlocking the extra inventory of Kuafu's FSP shop");
				APSaveManager.CurrentAPSaveData.otherPersistentModFlags[KuafuExtraInventory_ModSaveFlag] = true;
				ShopRando.EnsureShopsScouted();
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		private static bool FlagFieldBoolEntry_get_isValid(FlagFieldBoolEntry __instance, ref bool __result)
		{
			if (((Object)((FlagFieldEntry<bool>)(object)__instance).flagBase).name != "A6_S1_蚩尤救回羿")
			{
				return true;
			}
			if ((Object)(object)kuafuShopPanel == (Object)null)
			{
				kuafuShopPanel = GameObject.Find("AG_S2/Room/NPCs/議會演出相關Binding/NPC_KuaFoo_Base/NPC_KuaFoo_BaseFSM/FSM Animator/LogicRoot/NPC_KuaFoo/General FSM Object/Animator(FSM)/LogicRoot/NPC_Talking_Controller/Config/[Set] 中間層選項/Canvas/[中間層] UI Interact Options Root Panel/ConfirmProvider/UpgradeTable");
			}
			GameObject? obj = kuafuShopPanel;
			if (obj != null && obj.activeSelf)
			{
				bool value = default(bool);
				if (APSaveManager.CurrentAPSaveData != null && APSaveManager.CurrentAPSaveData.otherPersistentModFlags.TryGetValue(KuafuExtraInventory_ModSaveFlag, out value) && value)
				{
					__result = true;
					return true;
				}
				__result = false;
				return false;
			}
			return true;
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(SetVariableBoolAction), "OnStateEnterImplement")]
		private static bool SetVariableBoolAction_OnStateEnterImplement(SetVariableBoolAction __instance)
		{
			VariableBool targetFlag = __instance.targetFlag;
			object obj;
			if (targetFlag == null)
			{
				obj = null;
			}
			else
			{
				ScriptableDataBool boolFlag = targetFlag.boolFlag;
				obj = ((boolFlag != null) ? ((GameFlagBase)boolFlag).FinalSaveID : null);
			}
			if ((string?)obj == kuafuInFSPFlag)
			{
				string fullDisambiguatedPath = LocationTriggers.GetFullDisambiguatedPath(((Component)__instance).gameObject);
				if (fullDisambiguatedPath == "A2_S5_ BossHorseman_GameLevel/Room/Sleeppod  FSM/[CutScene]BackFromSleeppod/--[States]/FSM/[State] PlayCutScene/[Action] Get_BossKey = true" || fullDisambiguatedPath == "VR_Kuafu/VR_Kuafu_Skin/BG_DREAM/VRMemory FSM/PlayerSensor FSM Prototype/--[States]/FSM/[State] Play End 表演結束/[Action] GetBossKey")
				{
					Log.Info("ShopUnlocks::SetVariableBoolAction_OnStateEnterImplement preventing Kuafu from being moved into FSP by " + fullDisambiguatedPath);
					return false;
				}
			}
			return true;
		}

		[HarmonyPostfix]
		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		private static void AbstractConditionComp_get_FinalResult(AbstractConditionComp __instance, ref bool __result)
		{
			if (unlockMethod == ShopUnlockMethod.VanillaLikeLocations)
			{
				return;
			}
			Dictionary<string, bool> dictionary = APSaveManager.CurrentAPSaveData?.locationsChecked;
			string key = Location.RP_KUAFU_SANCTUM.ToString();
			bool flag = dictionary != null && dictionary.ContainsKey(key) && dictionary[key];
			if (((Object)__instance).name == "[Condition] GetBossKeyAuthority")
			{
				if (__result == flag)
				{
					return;
				}
				string fullDisambiguatedPath = LocationTriggers.GetFullDisambiguatedPath(((Component)__instance).gameObject);
				if (fullDisambiguatedPath == "A2_S5_ BossHorseman_GameLevel/Room/Simple Binding Tool/Boss_SpearHorse_Logic/[Mech]BossDoorx6_FSM Variant/--[States]/FSM/[State] Init/[Action] HasGotBossAuthority Transition/[Condition] GetBossKeyAuthority" || fullDisambiguatedPath == "A2_S5_ BossHorseman_GameLevel/Room/Simple Binding Tool/Boss_SpearHorse_Logic/[Mech]BossDoorx6_FSM Variant/--[States]/FSM/[State] Closed/[Action] HasGotBossAuthority Transition/[Condition] GetBossKeyAuthority")
				{
					__result = flag;
				}
				if (fullDisambiguatedPath == "A2_S5_ BossHorseman_GameLevel/Room/Simple Binding Tool/Boss_SpearHorse_Logic/[Mech]BossDoorx6_FSM Variant (1)/--[States]/FSM/[State] Init/[Action] HasGotBossAuthority Transition/[Condition] GetBossKeyAuthority" || fullDisambiguatedPath == "A2_S5_ BossHorseman_GameLevel/Room/Simple Binding Tool/Boss_SpearHorse_Logic/[Mech]BossDoorx6_FSM Variant (1)/--[States]/FSM/[State] Closed/[Action] HasGotBossAuthority Transition/[Condition] GetBossKeyAuthority")
				{
					__result = flag;
				}
			}
			if (((Object)__instance).name == "[Condition] GetBossKeyAuthority == false")
			{
				if (__result == !flag)
				{
					return;
				}
				string fullDisambiguatedPath2 = LocationTriggers.GetFullDisambiguatedPath(((Component)__instance).gameObject);
				if (fullDisambiguatedPath2 == "A2_S5_ BossHorseman_GameLevel/Room/Simple Binding Tool/Boss_SpearHorse_Logic/[Mech]BossDoorx6_FSM Variant (1)/--[States]/FSM/[State] HoloOpened/[Action] Bosskilled But No Authority -> ClosedDoor/[Condition] GetBossKeyAuthority == false")
				{
					__result = !flag;
				}
				if (fullDisambiguatedPath2 == "A2_S5_ BossHorseman_GameLevel/Room/Simple Binding Tool/Boss_SpearHorse_Logic/[Mech]BossDoorx6_FSM Variant/--[States]/FSM/[State] HoloOpened/[Action] Bosskilled But No Authority -> ClosedDoor/[Condition] GetBossKeyAuthority == false")
				{
					__result = !flag;
				}
			}
			if (((Object)__instance).name == "[Condition] FlagBoolCondition" && __result != flag && LocationTriggers.GetFullDisambiguatedPath(((Component)__instance).gameObject) == "A2_S5_ BossHorseman_GameLevel/Room/Sleeppod  FSM/EnterSleeppodFSM/--[States]/FSM/[State] Init/[Action] Entered Transition/[Condition] FlagBoolCondition")
			{
				Log.Info($"AbstractConditionComp_get_FinalResult changing the 'should Kuafu's vital sanctum be open' check, since the AP sanctum location ({flag}) doesn't match the Kuafu-in-FSP flag ({__result})");
				__result = flag;
			}
		}
	}
	[HarmonyPatch]
	internal class SkillTree
	{
		private static long LogicDifficulty;

		public static void ApplySlotData(long? logicDifficulty)
		{
			LogicDifficulty = logicDifficulty.GetValueOrDefault();
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(GameLevel), "Awake")]
		private static void GameLevel_Awake(GameLevel __instance)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Expected O, but got Unknown
			try
			{
				if (LogicDifficulty > 0)
				{
					SkillNodeData val = (SkillNodeData)SingletonBehaviour<SaveManager>.Instance.allFlags.FlagDict["ae3f7be7afb294d2eba0f6f4d129c6d0SkillNodeData"];
					if (!((GameFlagDescriptable)val).IsAcquired)
					{
						Log.Info($"SkillTree::GameLevel_Awake auto-unlocking Swift Runner since LogicDifficulty is {LogicDifficulty}");
						((GameFlagDescriptable)val).PlayerPicked();
						InGameConsole.Add("<color=orange>The Swift Runner skill has been automatically unlocked</color>\nbecause this slot was generated with a logic_difficulty of medium or higher");
					}
				}
			}
			catch (Exception ex)
			{
				Log.Error("SkillTree::GameLevel_Awake threw: " + ex.Message + "\nwith stack:\n" + ex.StackTrace + "\nand InnerException: " + ex.InnerException?.Message + "\nwith stack:\n" + ex.InnerException?.StackTrace);
			}
		}
	}
	[HarmonyPatch]
	internal class TrackerMapPage
	{
		private static Dictionary<string, string> GameLevelToTrackerPage = new Dictionary<string, string>
		{
			{ "A6_S3", "abandoned_mines" },
			{ "A3_S5_BossGouMang_GameLevel", "agrarian_hall" },
			{ "A10S5", "ancient_stone_pillar" },
			{ "A1_S3_GameLevel", "apeman_facility_depths" },
			{ "A1_S2_GameLevel", "apeman_facility_elevator" },
			{ "A1_S1_GameLevel", "apeman_facility_monitoring" },
			{ "A4_S3", "boundless_repository" },
			{ "AG_S1", "central_hall" },
			{ "A2_S6", "central_transport_hub" },
			{ "A7_S1", "cortex_center" },
			{ "A9_S2", "empyrean_district_living_area" },
			{ "A9_S1", "empyrean_district_passages" },
			{ "A9_S3", "empyrean_district_sanctum" },
			{ "A5_S1", "factory_great_hall" },
			{ "A5_S3", "factory_machine_room" },
			{ "A5_S4", "factory_production_area" },
			{ "A6_S1", "factory_underground" },
			{ "AG_S2", "four_seasons_pavilion" },
			{ "GameLevel", "galactic_dock_and_village" },
			{ "A3_S2", "greenhouse" },
			{ "A10_S3", "grotto_of_scriptures_east" },
			{ "A10_S1", "grotto_of_scriptures_entry" },
			{ "A10_S4", "grotto_of_scriptures_west" },
			{ "A4_S2", "inner_warehouse" },
			{ "A3_S1", "lake_yaochi_ruins" },
			{ "P2_R22_Savepoint_GameLevel", "nobility_hall" },
			{ "A4_S1", "outer_warehouse" },
			{ "A2_S1", "power_reservoir_central" },
			{ "A2_S2", "power_reservoir_east" },
			{ "A2_S3", "power_reservoir_west" },
			{ "A5_S2", "prison" },
			{ "A2_S5_ BossHorseman_GameLevel", "radiant_pagoda" },
			{ "A5_S5", "shengwu_hall" },
			{ "A9_S4", "sky_tower" },
			{ "A11_S1", "tiandao_research_center" },
			{ "A11_S2", "tianhuo_research_institute" },
			{ "A0_S7", "underground_cave" },
			{ "A3_S3", "water_and_oxygen_synthesis" },
			{ "A0_S6", "yangu_hall" },
			{ "A3_S7", "yinglong_canal" }
		};

		[HarmonyPrefix]
		[HarmonyPatch(typeof(GameLevel), "Awake")]
		private static void GameLevel_Awake(GameLevel __instance)
		{
			try
			{
				string name = ((Object)__instance).name;
				string value;
				if (ConnectionAndPopups.APSession == null)
				{
					Log.Error("TrackerMapPage::GameLevel_Awake aborting because APSession is null");
				}
				else if (GameLevelToTrackerPage.TryGetValue(name, out value))
				{
					ArchipelagoSession aPSession = ConnectionAndPopups.APSession;
					string text = $"{aPSession.ConnectionInfo.Slot}_{aPSession.ConnectionInfo.Team}_nine_sols_area";
					Log.Info("TrackerMapPage::GameLevel_Awake setting DataStorage key \"" + text + "\" to value \"" + value + "\"");
					aPSession.DataStorage[text] = DataStorageElement.op_Implicit(value);
				}
				else
				{
					Log.Info("TrackerMapPage::GameLevel_Awake called with unknown levelName = " + name);
				}
			}
			catch (Exception ex)
			{
				Log.Error("TrackerMapPage::GameLevel_Awake threw: " + ex.Message + "\nwith stack:\n" + ex.StackTrace + "\nand InnerException: " + ex.InnerException?.Message + "\nwith stack:\n" + ex.InnerException?.StackTrace);
			}
		}
	}
	[HarmonyPatch]
	internal class Goal
	{
		[HarmonyPrefix]
		[HarmonyPatch(typeof(SetVariableBoolAction), "OnStateEnterImplement")]
		private static void SetVariableBoolAction_OnStateEnterImplement(SetVariableBoolAction __instance)
		{
			VariableBool targetFlag = __instance.targetFlag;
			object obj;
			if (targetFlag == null)
			{
				obj = null;
			}
			else
			{
				ScriptableDataBool boolFlag = targetFlag.boolFlag;
				obj = ((boolFlag != null) ? ((GameFlagBase)boolFlag).FinalSaveID : null);
			}
			string text = (string)obj;
			if (text == null)
			{
				return;
			}
			bool flag = false;
			if (text == "85d361b9-7c43-4a1d-91c2-fd19e4bbb0b1_6b037c39982d34953a066792ab66783aScriptableDataBool")
			{
				flag = true;
			}
			else if (text == "e965aab1c014b4273b928b17fbcff379ScriptableDataBool")
			{
				flag = true;
			}
			if (!flag)
			{
				return;
			}
			if (PlayerGamePlayData.Instance.memoryMode.CurrentValue)
			{
				Log.Info("Goal code ignoring Eigong kill because this is Battle Memories mode");
				return;
			}
			Log.Info($"Eigong flag: id={text}, TargetValue={__instance.TargetValue}");
			InGameConsole.Add("Eigong defeat detected by SetVariableBoolAction_OnStateEnterImplement. Congratulations!", forceImmediateToast: true);
			InGameConsole.Add("Telling the AP server that you've achieved your goal.", forceImmediateToast: true);
			Task.Run(delegate
			{
				try
				{
					if (!Task.Run(delegate
					{
						ConnectionAndPopups.APSession.SetGoalAchieved();
					}).Wait(TimeSpan.FromSeconds(2.0)))
					{
						string text2 = "AP server timed out when we tried to tell it that you've achieved your goal. Did the connection go down?";
						Log.Warning(text2);
						InGameConsole.Add("<color=orange>" + text2 + "</color>");
					}
				}
				catch (Exception ex)
				{
					Log.Error("Caught error in SetGoalAchieved's timeout callback: '" + ex.Message + "'\n" + ex.StackTrace);
				}
			});
		}
	}
	internal static class Log
	{
		private static ManualLogSource? logSource;

		internal static void Init(ManualLogSource logSource)
		{
			Log.logSource = logSource;
		}

		internal static void Debug(object data)
		{
			ManualLogSource? obj = logSource;
			if (obj != null)
			{
				obj.LogDebug(data);
			}
		}

		internal static void Error(object data)
		{
			ManualLogSource? obj = logSource;
			if (obj != null)
			{
				obj.LogError(data);
			}
		}

		internal static void Fatal(object data)
		{
			ManualLogSource? obj = logSource;
			if (obj != null)
			{
				obj.LogFatal(data);
			}
		}

		internal static void Info(object data)
		{
			ManualLogSource? obj = logSource;
			if (obj != null)
			{
				obj.LogInfo(data);
			}
		}

		internal static void Message(object data)
		{
			ManualLogSource? obj = logSource;
			if (obj != null)
			{
				obj.LogMessage(data);
			}
		}

		internal static void Warning(object data)
		{
			ManualLogSource? obj = logSource;
			if (obj != null)
			{
				obj.LogWarning(data);
			}
		}
	}
	public class APConnectionData
	{
		public string hostname = "";

		public int port;

		public string slotName = "";

		public string password = "";

		public string? roomId;
	}
	public class APRandomizerSaveData
	{
		public APConnectionData apConnectionData = new APConnectionData();

		public Dictionary<string, bool> locationsChecked = new Dictionary<string, bool>();

		public Dictionary<string, int> itemsAcquired = new Dictionary<string, int>();

		public Dictionary<string, bool> otherPersistentModFlags = new Dictionary<string, bool>();

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

		public Dictionary<Location, SerializableItemInfo> scoutedLocations = new Dictionary<Location, SerializableItemInfo>();
	}
	[HarmonyPatch]
	internal class APSaveManager
	{
		public static APRandomizerSaveData?[] apSaveSlots = new APRandomizerSaveData[4];

		public static int selectedSlotIndex = -1;

		public static bool apSavesLoaded = false;

		public static Task? scheduledSaveFileWriteTCS = null;

		public static APRandomizerSaveData? CurrentAPSaveData
		{
			get
			{
				if (selectedSlotIndex < 0)
				{
					return null;
				}
				return apSaveSlots[selectedSlotIndex];
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(StartMenuLogic), "Start")]
		public static void StartMenuLogic_Start_Prefix(StartMenuLogic __instance)
		{
			apSavesLoaded = false;
			Log.Info("StartMenuLogic_Start_Prefix making sure any active AP session gets cleaned up");
			ConnectionAndPopups.CleanupExistingAPServerConnection();
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(SaveSlotUIButton), "UpdateUI")]
		public static async void SaveSlotUIButton_UpdateUI_Postfix(SaveSlotUIButton __instance)
		{
			if (!apSavesLoaded)
			{
				await LoadAPSaves();
				apSavesLoaded = true;
			}
			if (!__instance.SaveExist)
			{
				((Behaviour)__instance).enabled = true;
				return;
			}
			APRandomizerSaveData aPRandomizerSaveData = apSaveSlots[__instance.index];
			if (aPRandomizerSaveData != null)
			{
				((Behaviour)__instance).enabled = true;
				APConnectionData apConnectionData = aPRandomizerSaveData.apConnectionData;
				TMP_Text lastSceneText = __instance.lastSceneText;
				lastSceneText.text = lastSceneText.text + "\n" + apConnectionData.hostname + ":" + apConnectionData.port + " - " + apConnectionData.slotName;
			}
			else
			{
				((Behaviour)__instance).enabled = false;
				TMP_Text lastSceneText2 = __instance.lastSceneText;
				lastSceneText2.text += "\n[Vanilla Save]";
			}
		}

		private static string APSaveDataPathForSlot(int i)
		{
			string saveSlotsPath = APRandomizer.SaveSlotsPath;
			string text = SaveManager.GetSlotDirPath(i) + "_Ixrec_ArchipelagoRandomizer.json";
			return saveSlotsPath + "/" + text;
		}

		private static async Task LoadAPSaves()
		{
			Log.Info("Loading Archipelago save data");
			GameObject saveSlotButtonsGO = GameObject.Find("MenuLogic/MainMenuLogic/Providers/StartGame SaveSlotPanel/SlotGroup/SlotGroup H");
			for (int i = 0; i <= 3; i++)
			{
				UniTask<bool> val = SingletonBehaviour<SaveManager>.Instance.CheckSaveExist(i);
				string saveSlotVanillaFolderName = SaveManager.GetSlotDirPath(i);
				string saveSlotAPModFilePath = APSaveDataPathForSlot(i);
				bool apSaveFileExists = File.Exists(saveSlotAPModFilePath);
				Transform buttonsForThisSlotT = saveSlotButtonsGO.transform.GetChild(i * 2);
				if (!(await val))
				{
					apSaveSlots[i] = null;
					SetChangeButtonVisible(visible: false, buttonsForThisSlotT, i);
					continue;
				}
				if (apSaveFileExists)
				{
					Log.Info(saveSlotVanillaFolderName + " has AP save data");
					try
					{
						APRandomizerSaveData aPRandomizerSaveData = JsonConvert.DeserializeObject<APRandomizerSaveData>(File.ReadAllText(saveSlotAPModFilePath));
						if (aPRandomizerSaveData == null)
						{
							aPRandomizerSaveData = new APRandomizerSaveData();
						}
						if (aPRandomizerSaveData.persistentModStringLists == null)
						{
							aPRandomizerSaveData.persistentModStringLists = new Dictionary<string, List<string>>();
						}
						apSaveSlots[i] = aPRandomizerSaveData;
					}
					catch (Exception ex)
					{
						Log.Error("failed to deserialize randomizer save data in " + saveSlotAPModFilePath + "\nMessage: " + ex.Message + "\nStackTrace: " + ex.StackTrace);
					}
					SetChangeButtonVisible(visible: true, buttonsForThisSlotT, i);
					continue;
				}
				Log.Info(saveSlotVanillaFolderName + " is a vanilla save");
				Transform child = buttonsForThisSlotT.GetChild(0);
				((Behaviour)((Component)child).GetComponent<Button>()).enabled = false;
				((Behaviour)((Component)child).GetComponent<SelectableNavigationRemapping>()).enabled = false;
				foreach (TextMeshProUGUI item in MonoNodeExtension.GetComponentsInChildrenOfDepthOne<TextMeshProUGUI>((Component)(object)((Component)child).transform.Find("AnimationRoot/Grid")))
				{
					Color color = ((Graphic)item).color;
					color.r /= 2f;
					color.g /= 2f;
					color.b /= 2f;
					((Graphic)item).color = color;
				}
				SetChangeButtonVisible(visible: false, buttonsForThisSlotT, i);
			}
			Log.Info("Finished loading Archipelago save data");
		}

		private static void SetChangeButtonVisible(bool visible, Transform saveSlotButtonsT, int slotIndex)
		{
			Transform child = saveSlotButtonsT.GetChild(1);
			((Component)child).GetComponent<LayoutElement>().preferredWidth = (visible ? 70 : 105);
			Transform child2 = saveSlotButtonsT.GetChild(2);
			((Component)child2).GetComponent<LayoutElement>().preferredWidth = (visible ? 80 : 100);
			if (visible && saveSlotButtonsT.childCount == 3)
			{
				Transform obj = Object.Instantiate<Transform>(child);
				((Object)obj).name = $"APRandomizer_Padding_Slot{slotIndex}";
				((Component)obj).transform.SetParent(saveSlotButtonsT, false);
				((Component)obj).GetComponent<LayoutElement>().preferredWidth = 50f;
				Transform val = Object.Instantiate<Transform>(child2);
				((Object)val).name = $"APRandomizer_ChangeConnectionInfo_Slot{slotIndex}";
				((Component)val).transform.SetParent(saveSlotButtonsT, false);
				((Component)val).GetComponent<LayoutElement>().preferredWidth = 80f;
				Object.Destroy((Object)(object)((Component)val).GetComponent<UISubmitConfirmAddOn>());
				UIControlButton component = ((Component)val).GetComponent<UIControlButton>();
				component.onSubmit = null;
				AccessTools.FieldRefAccess<UIControlButton, UIControlGroup>("belongGroup").Invoke(component) = ((Component)val).GetComponentInParent<UIControlGroup>(true);
				AccessTools.FieldRefAccess<UIControlButton, AbstractConditionComp[]>("_activateConditions").Invoke(component) = Array.Empty<AbstractConditionComp>();
				AccessTools.FieldRefAccess<UIControlButton, AutoDisableAnimator>("_autoDisableAnimator").Invoke(component) = ((Component)val).GetComponent<AutoDisableAnimator>();
				AccessTools.FieldRefAccess<UIControlButton, Selectable>("_button").Invoke(component) = (Selectable)(object)((Component)val).GetComponent<Button>();
			}
			else if (saveSlotButtonsT.childCount == 5)
			{
				((Component)saveSlotButtonsT.GetChild(3)).gameObject.SetActive(visible);
				((Component)saveSlotButtonsT.GetChild(4)).gameObject.SetActive(visible);
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(UIControlButton), "OnBecomeInteractable")]
		public static void UIControlButton_OnBecomeInteractable(UIControlButton __instance)
		{
			if (((Object)__instance).name == "Delete Button")
			{
				Transform parent = ((Component)__instance).transform.parent;
				if (parent != null && ((Object)parent).name.StartsWith("SaveSlotContainer_Slot") && ((Component)__instance).transform.parent.childCount == 5)
				{
					Transform child = ((Component)__instance).transform.parent.GetChild(4);
					((Component)child).GetComponent<UIControlButton>().OnBecomeInteractable();
					((TMP_Text)((Component)child.Find("Text (TMP)")).GetComponent<TextMeshProUGUI>()).text = "Change\nConnection\nInformation";
				}
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(UIControlButton), "OnBecomeNotInteractable")]
		public static void UIControlButton_OnBecomeNotInteractable(UIControlButton __instance)
		{
			if (((Object)__instance).name == "Delete Button")
			{
				Transform parent = ((Component)__instance).transform.parent;
				if (parent != null && ((Object)parent).name.StartsWith("SaveSlotContainer_Slot") && ((Component)__instance).transform.parent.childCount == 5)
				{
					((Component)((Component)__instance).transform.parent.GetChild(4)).GetComponent<UIControlButton>().OnBecomeNotInteractable();
				}
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(UIControlButton), "SubmitImplementation")]
		public static async void UIControlButton_SubmitImplementation(UIControlButton __instance)
		{
			if (((Object)__instance).name.StartsWith("APRandomizer_ChangeConnectionInfo_Slot"))
			{
				int slotIndex = int.Parse(((Object)__instance).name.Substring("APRandomizer_ChangeConnectionInfo_Slot".Length));
				APRandomizerSaveData saveSlot = apSaveSlots[slotIndex];
				if (saveSlot == null)
				{
					Log.Error($"UIControlButton_SubmitImplementation for {((Object)__instance).name} aborting because save slot [{slotIndex}] is null");
					return;
				}
				Log.Info($"UIControlButton_SubmitImplementation for {((Object)__instance).name} parsed slotIndex={slotIndex} showing change info popup");
				APConnectionData apConnectionData = saveSlot.apConnectionData;
				HideSaveMenu();
				APConnectionData apConnectionData2 = await ConnectionAndPopups.ChangeConnectionInfo(apConnectionData);
				UnHideSaveMenu();
				saveSlot.apConnectionData = apConnectionData2;
				WriteSaveFileForSlot(slotIndex);
				GameObject.Find("MenuLogic/MainMenuLogic").GetComponent<StartMenuLogic>().UpdateSaveSlots();
			}
		}

		private static void HideSaveMenu()
		{
			GameObject obj = GameObject.Find("MenuLogic/MainMenuLogic/Providers/StartGame SaveSlotPanel");
			((Component)obj.transform.GetChild(1)).gameObject.SetActive(false);
			((Component)obj.transform.GetChild(3)).gameObject.SetActive(false);
			((Component)obj.transform.GetChild(4)).gameObject.SetActive(false);
		}

		private static void UnHideSaveMenu()
		{
			GameObject obj = GameObject.Find("MenuLogic/MainMenuLogic/Providers/StartGame SaveSlotPanel");
			((Component)obj.transform.GetChild(1)).gameObject.SetActive(true);
			((Component)obj.transform.GetChild(3)).gameObject.SetActive(true);
			((Component)obj.transform.GetChild(4)).gameObject.SetActive(true);
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(StartMenuLogic), "CreateOrLoadSaveSlotAndPlay")]
		public static bool StartMenuLogic_CreateOrLoadSaveSlotAndPlay_AllowOrSkipVanillaImpl(StartMenuLogic __instance, int slotIndex, bool SaveExists, bool LoadFromBackup = false, bool memoryChallengeMode = false)
		{
			if (memoryChallengeMode)
			{
				if (ConnectionAndPopups.APSession != null)
				{
					Log.Info("StartMenuLogic_CreateOrLoadSaveSlotAndPlay_AllowOrSkipVanillaImpl setting APSession to null since the player has switched to Battle Memories");
					ConnectionAndPopups.APSession = null;
				}
				return true;
			}
			bool flag = ConnectionAndPopups.APSession != null && slotIndex == selectedSlotIndex;
			Log.Info($"StartMenuLogic_CreateOrLoadSaveSlotAndPlay_AllowOrSkipVanillaImpl returning {flag}");
			return flag;
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(StartMenuLogic), "CreateOrLoadSaveSlotAndPlay")]
		public static async void StartMenuLogic_CreateOrLoadSaveSlotAndPlay_EnsureAPConnection(StartMenuLogic __instance, int slotIndex, bool SaveExists, bool LoadFromBackup = false, bool memoryChallengeMode = false)
		{
			if (memoryChallengeMode)
			{
				return;
			}
			if (ConnectionAndPopups.APSession != null && slotIndex == selectedSlotIndex)
			{
				Log.Info($"StartMenuLogic_CreateOrLoadSaveSlotAndPlay_EnsureAPConnection returning early because we're already connected to slot {slotIndex}");
				return;
			}
			HideSaveMenu();
			try
			{
				selectedSlotIndex = slotIndex;
				APRandomizerSaveData apSaveData = apSaveSlots[slotIndex];
				if (SaveExists)
				{
					Log.Info("StartMenuLogic_CreateOrLoadSaveSlotAndPlay_EnsureAPConnection calling ResumePreviousConnection");
					await ConnectionAndPopups.ResumePreviousConnection(apSaveData);
				}
				else
				{
					Log.Info("StartMenuLogic_CreateOrLoadSaveSlotAndPlay_EnsureAPConnection calling GetConnectionInfoFromUser");
					apSaveData = await ConnectionAndPopups.GetConnectionInfoFromUser(null);
					apSaveSlots[slotIndex] = apSaveData;
				}
				WriteCurrentSaveFile();
				Log.Info("StartMenuLogic_CreateOrLoadSaveSlotAndPlay_EnsureAPConnection re-calling CreateOrLoadSaveSlotAndPlay now that we're connected");
				await __instance.CreateOrLoadSaveSlotAndPlay(slotIndex, SaveExists, LoadFromBackup, memoryChallengeMode);
			}
			catch (Exception ex)
			{
				selectedSlotIndex = -1;
				Log.Warning("GetConnectionInfoFromUser threw: " + ex.Message + " with stack:\n" + ex.StackTrace);
			}
			finally
			{
				UnHideSaveMenu();
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(SaveManager), "DeleteSave")]
		public static async void SaveManager_DeleteSave(SaveManager __instance, int i)
		{
			bool num = apSaveSlots[i] != null;
			string text = APSaveDataPathForSlot(i);
			Log.Info("SaveManager_DeleteSave() deleting AP save file at " + text);
			File.Delete(text);
			apSaveSlots[i] = null;
			selectedSlotIndex = -1;
			ConnectionAndPopups.APSession = null;
			Transform child = GameObject.Find("MenuLogic/MainMenuLogic/Providers/StartGame SaveSlotPanel/SlotGroup/SlotGroup H").transform.GetChild(i * 2);
			if (child.childCount == 5)
			{
				((Component)child.GetChild(4)).gameObject.SetActive(false);
			}
			if (num)
			{
				return;
			}
			Transform child2 = child.GetChild(0);
			((Behaviour)((Component)child2).GetComponent<Button>()).enabled = true;
			((Behaviour)((Component)child2).GetComponent<SelectableNavigationRemapping>()).enabled = true;
			foreach (TextMeshProUGUI item in MonoNodeExtension.GetComponentsInChildrenOfDepthOne<TextMeshProUGUI>((Component)(object)((Component)child2).transform.Find("AnimationRoot/Grid")))
			{
				Color color = ((Graphic)item).color;
				color.r *= 2f;
				color.g *= 2f;
				color.b *= 2f;
				((Graphic)item).color = color;
			}
		}

		public static void WriteSaveFileForSlot(int i)
		{
			string text = APSaveDataPathForSlot(i);
			File.WriteAllText(text, JsonConvert.SerializeObject((object)apSaveSlots[i]));
			Log.Info("WriteCurrentSaveFile() wrote AP save file at " + text);
			if (scheduledSaveFileWriteTCS != null)
			{
				scheduledSaveFileWriteTCS = null;
			}
		}

		public static void WriteCurrentSaveFile()
		{
			WriteSaveFileForSlot(selectedSlotIndex);
		}

		public static void ScheduleWriteToCurrentSaveFile()
		{
			if (scheduledSaveFileWriteTCS == null)
			{
				Log.Info("ScheduleWriteToCurrentSaveFile() called with no pending write, so scheduling one");
				scheduledSaveFileWriteTCS = Task.Run(async delegate
				{
					await Task.Delay(1000);
					Log.Info("ScheduleWriteToCurrentSaveFile() task callback now actually callling WriteCurrentSaveFile()");
					WriteCurrentSaveFile();
				});
			}
		}
	}
	internal class ConnectionAndPopups
	{
		private enum ConnectionPopup
		{
			None,
			InputConnectionInfo,
			ChangeConnectionInfo,
			Connecting,
			ConnectionError,
			RoomIdMismatchWarning
		}

		[CompilerGenerated]
		private