Decompiled source of Roguenarok Extraction Modpack v0.3.0

BepInEx/plugins/Roguenarok-Foundation/Roguenarok.Foundation.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Xml;
using System.Xml.Linq;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Jotunn;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using UnityEngine;

[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("Roguenarok.Foundation")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+66087733cbceada476c9502a244f4a1829af82f0")]
[assembly: AssemblyProduct("Roguenarok.Foundation")]
[assembly: AssemblyTitle("Roguenarok.Foundation")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Roguenarok.Foundation
{
	public static class IslandTerrain
	{
		public const string WorldPrefix = "Roguenarok_Island";

		public const double Water = 30.0;

		public const double Radius = 1500.0;

		public static bool Applies(string name)
		{
			return name?.StartsWith("Roguenarok_Island", StringComparison.Ordinal) ?? false;
		}

		private static double Smooth(double t)
		{
			t = Math.Max(0.0, Math.Min(1.0, t));
			return t * t * (3.0 - 2.0 * t);
		}

		private static double Hash(int x, int z, int seed)
		{
			int num = seed ^ (x * 374761393) ^ (z * 668265263);
			int num2 = (num ^ (num >>> 13)) * 1274126177;
			return (double)(uint)(num2 ^ (num2 >>> 16)) / 4294967295.0;
		}

		public static double Noise(double x, double z, int seed)
		{
			int num = (int)Math.Floor(x);
			int num2 = (int)Math.Floor(z);
			double num3 = Smooth(x - (double)num);
			double num4 = Smooth(z - (double)num2);
			double num5 = Hash(num, num2, seed) * (1.0 - num3) + Hash(num + 1, num2, seed) * num3;
			double num6 = Hash(num, num2 + 1, seed) * (1.0 - num3) + Hash(num + 1, num2 + 1, seed) * num3;
			return num5 * (1.0 - num4) + num6 * num4;
		}

		public static double Height(double x, double z, int seed)
		{
			double num = Math.Sqrt(x * x + z * z);
			double num2 = Math.Atan2(z, x);
			double num3 = 1500.0 + 110.0 * Math.Sin(num2 * 3.0 + (double)(seed % 31)) + 70.0 * Math.Cos(num2 * 5.0 + (double)(seed % 17)) - num;
			if (num3 <= -200.0)
			{
				return -25.0;
			}
			if (num3 < 0.0)
			{
				return 30.0 - 55.0 * Smooth((0.0 - num3) / 200.0);
			}
			double num4 = Smooth(num3 / 200.0);
			double num5 = 14.0 + 36.0 * Noise(x / 450.0, z / 450.0, seed) + 10.0 * Noise(x / 100.0, z / 100.0, seed + 73) + 2.0 * Noise(x / 25.0, z / 25.0, seed + 97);
			double num6 = 30.0 + num4 * num5;
			double num7 = Math.Sqrt((x + 850.0) * (x + 850.0) + z * z);
			double num8 = 1.0 - Smooth((num7 - 95.0) / 65.0);
			return num6 * (1.0 - num8) + 44.0 * num8;
		}

		public static int Biome(double x, double z, int seed)
		{
			if (Height(x, z, seed) <= 30.0)
			{
				return 0;
			}
			if (!((x + 850.0) * (x + 850.0) + z * z < 48400.0) && !(x < -250.0))
			{
				return 2;
			}
			return 1;
		}
	}
	public sealed class Point
	{
		public double X;

		public double Y;

		public double Z;

		public bool Valid
		{
			get
			{
				if (!double.IsNaN(X) && !double.IsNaN(Y) && !double.IsNaN(Z) && !double.IsInfinity(X) && !double.IsInfinity(Y) && !double.IsInfinity(Z) && Math.Abs(X) < 10000.0 && Math.Abs(Z) < 10000.0)
				{
					return Math.Abs(Y) < 1000.0;
				}
				return false;
			}
		}

		public Point(double x = 0.0, double y = 0.0, double z = 0.0)
		{
			X = x;
			Y = y;
			Z = z;
		}

		public Point Copy()
		{
			return new Point(X, Y, Z);
		}

		public double Distance(Point p)
		{
			return Math.Sqrt((X - p.X) * (X - p.X) + (Z - p.Z) * (Z - p.Z));
		}
	}
	public sealed class RaidLayout
	{
		public bool Enabled;

		public Point Hub = new Point(-850.0);

		public Point Landing = new Point(450.0, 0.0, -100.0);

		public Point Exit = new Point(1080.0);

		public double ExitRadius = 14.0;

		public double HubRadius = 90.0;

		public RaidLayout Copy()
		{
			return new RaidLayout
			{
				Enabled = Enabled,
				Hub = Hub.Copy(),
				Landing = Landing.Copy(),
				Exit = Exit.Copy(),
				ExitRadius = ExitRadius,
				HubRadius = HubRadius
			};
		}
	}
	public enum MemberStatus
	{
		Preparing,
		Deploying,
		OnIsland,
		Extracted,
		Dead,
		Failed,
		Lobby
	}
	public sealed class Transfer
	{
		public string Id;

		public string Token;

		public string Target;

		public Transfer(string id, string target)
		{
			Id = id;
			Target = target;
			Token = Guid.NewGuid().ToString("N");
		}

		public Transfer Copy()
		{
			return new Transfer(Id, Target)
			{
				Token = Token
			};
		}
	}
	public sealed class PresentPlayer
	{
		public string Id;

		public Point Position;

		public bool Alive;

		public PresentPlayer(string id, Point position, bool alive = true)
		{
			Id = id;
			Position = position;
			Alive = alive;
		}
	}
	internal static class IslandWorld
	{
		internal static bool Active(World world)
		{
			if (world != null && !world.m_menu)
			{
				return IslandTerrain.Applies(world.m_name);
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(WorldGenerator), "Pregenerate")]
	internal static class IslandPregeneratePatch
	{
		private static bool Prefix(World ___m_world, ref List<Vector2> ___m_lakes)
		{
			if (!IslandWorld.Active(___m_world))
			{
				return true;
			}
			___m_lakes = new List<Vector2>();
			Logger.LogInfo((object)("Roguenarok: single-island generator v1, Meadows + BlackForest, seed=" + ___m_world.m_seed));
			return false;
		}
	}
	[HarmonyPatch(typeof(WorldGenerator), "GetBaseHeight")]
	internal static class IslandBaseHeightPatch
	{
		private static bool Prefix(World ___m_world, float wx, float wy, bool menuTerrain, ref float __result)
		{
			if (menuTerrain || !IslandWorld.Active(___m_world))
			{
				return true;
			}
			__result = (float)(IslandTerrain.Height(wx, wy, ___m_world.m_seed) / 200.0);
			return false;
		}
	}
	[HarmonyPatch(typeof(WorldGenerator), "GetBiome", new Type[]
	{
		typeof(float),
		typeof(float),
		typeof(float),
		typeof(bool)
	})]
	internal static class IslandBiomePatch
	{
		private static bool Prefix(World ___m_world, float wx, float wy, ref Biome __result)
		{
			if (!IslandWorld.Active(___m_world))
			{
				return true;
			}
			__result = (Biome)(IslandTerrain.Biome(wx, wy, ___m_world.m_seed) switch
			{
				1 => 1, 
				0 => 256, 
				_ => 8, 
			});
			return false;
		}
	}
	[HarmonyPatch(typeof(WorldGenerator), "GetBiomeHeight")]
	internal static class IslandBiomeHeightPatch
	{
		private static bool Prefix(World ___m_world, float wx, float wy, ref Color mask, ref float __result)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			if (!IslandWorld.Active(___m_world))
			{
				return true;
			}
			mask = Color.clear;
			__result = (float)IslandTerrain.Height(wx, wy, ___m_world.m_seed);
			return false;
		}
	}
	[HarmonyPatch(typeof(ZoneSystem), "GenerateLocations")]
	internal static class IslandLocationsPatch
	{
		private static void Prefix(List<ZoneLocation> ___m_locations)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			if (!IslandWorld.Active((WorldGenerator.instance == null) ? ((World)null) : ((World)AccessTools.Field(typeof(WorldGenerator), "m_world").GetValue(WorldGenerator.instance))))
			{
				return;
			}
			foreach (ZoneLocation ___m_location in ___m_locations)
			{
				if ((___m_location.m_biome & 9) == 0)
				{
					___m_location.m_quantity = 0;
				}
				else if (___m_location.m_quantity > 0)
				{
					___m_location.m_biome = (Biome)(___m_location.m_biome & 9);
					___m_location.m_centerFirst = true;
					___m_location.m_minDistance = Math.Min(___m_location.m_minDistance, 350f);
					___m_location.m_maxDistance = 1400f;
					___m_location.m_quantity = Math.Min(12, Math.Max(1, ___m_location.m_quantity / 20));
					___m_location.m_minDistanceFromCenter = Math.Min(___m_location.m_minDistanceFromCenter, 350f);
					___m_location.m_maxDistanceFromCenter = 1400f;
					___m_location.m_minDistanceFromSimilar = Math.Min(___m_location.m_minDistanceFromSimilar, 180f);
				}
			}
		}
	}
	[BepInPlugin("local.roguenarok.foundation", "Roguenarok Foundation", "0.3.0")]
	[BepInDependency("com.jotunn.jotunn", "2.30.2")]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		private sealed class View
		{
			public string Epoch;

			public string RaidId;

			public string Result;

			public long Sequence;

			public long Revision;

			public RaidPhase Phase;

			public double Remaining;

			public bool Admin;

			public RaidLayout Layout;

			public Transfer Transfer;

			public List<Member> Members = new List<Member>();
		}

		public const string Guid = "local.roguenarok.foundation";

		public const string Version = "0.3.0";

		private const int Protocol = 2;

		private const string RequestRpc = "Roguenarok.Foundation.Request.v2";

		private const string StateRpc = "Roguenarok.Foundation.State.v2";

		private const string ReplyRpc = "Roguenarok.Foundation.Reply.v2";

		private static Plugin instance;

		private Harmony harmony;

		private ConfigEntry<bool> hudEnabled;

		private ConfigEntry<string> stateDirectory;

		private RaidStore store;

		private bool storageBlocked;

		private long savedRevision = -1L;

		private ZNet network;

		private ZRpc serverRpc;

		private RaidState state;

		private string epoch;

		private long sequence;

		private double nextBroadcast;

		private double nextSync;

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

		private readonly Dictionary<ZRpc, double> lastRequest = new Dictionary<ZRpc, double>();

		private readonly SnapshotOrder order = new SnapshotOrder();

		private View view;

		private double receivedAt;

		private string reply = "Ожидание синхронизации с сервером.";

		private string input = "rogue start 300";

		private Rect menuRect = new Rect(35f, 140f, 550f, 380f);

		internal static bool MenuOpen;

		private CursorLockMode savedLock;

		private bool savedVisible;

		private Player travelPlayer;

		private bool needsSpawnNotification;

		private double nextTravelAttempt;

		private double teleportStartedAt;

		private string teleportToken;

		private string completedToken;

		private GameObject exitMarker;

		private static double Now => Time.realtimeSinceStartupAsDouble;

		private double DisplayRemaining
		{
			get
			{
				if (view != null)
				{
					return Math.Max(0.0, view.Remaining - (Now - receivedAt));
				}
				return 0.0;
			}
		}

		private void Awake()
		{
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Expected O, but got Unknown
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Expected O, but got Unknown
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			instance = this;
			hudEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Interface", "ShowHUD", true, "Show raid status and timer. F6 opens the test panel.");
			stateDirectory = ((BaseUnityPlugin)this).Config.Bind<string>("Server", "StateDirectory", Path.Combine(Paths.BepInExRootPath, "Roguenarok", "state"), "Server-only checkpoint directory; one subdirectory per world UID.");
			harmony = new Harmony("local.roguenarok.foundation");
			harmony.PatchAll(typeof(Plugin).Assembly);
			new ConsoleCommand("rogue", "Roguenarok: status | start [10..2700 seconds] | extract | finish | reset", new ConsoleEvent(OnCommand), false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("Roguenarok Foundation {0}: protocol {1}; exact client/server version required. F6 test panel. Server raid checkpoints enabled.", "0.3.0", 2));
		}

		private void OnDestroy()
		{
			SaveCheckpoint();
			ClearTravel();
			SetMenu(open: false);
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			instance = null;
		}

		private void ResetNetwork(ZNet net)
		{
			SaveCheckpoint();
			ClearTravel();
			SetMenu(open: false);
			network = net;
			registered.Clear();
			lastRequest.Clear();
			serverRpc = null;
			view = null;
			order.Reset();
			nextSync = (nextBroadcast = 0.0);
			state = null;
			store = null;
			storageBlocked = false;
			savedRevision = -1L;
			epoch = null;
			sequence = 0L;
		}

		private void Update()
		{
			ZNet val = ZNet.instance;
			if ((Object)(object)network != (Object)(object)val)
			{
				ResetNetwork(val);
			}
			if ((Object)(object)val == (Object)null)
			{
				SetMenu(open: false);
				return;
			}
			if ((Object)(object)Player.m_localPlayer != (Object)null)
			{
				if (Input.GetKeyDown((KeyCode)287))
				{
					SetMenu(!MenuOpen);
				}
				if (MenuOpen && Input.GetKeyDown((KeyCode)27))
				{
					SetMenu(open: false);
				}
			}
			else if (MenuOpen)
			{
				SetMenu(open: false);
			}
			if (val.IsServer())
			{
				UpdateServer(val);
			}
			else
			{
				UpdateClient(val);
			}
			if (!val.IsDedicated())
			{
				UpdateTravelClient();
			}
		}

		private void UpdateServer(ZNet net)
		{
			if (state == null)
			{
				if (net.GetWorldUID() == 0L)
				{
					return;
				}
				store = new RaidStore(Path.Combine(stateDirectory.Value, net.GetWorldUID().ToString(CultureInfo.InvariantCulture), "raid-state.xml"));
				epoch = System.Guid.NewGuid().ToString("N");
				try
				{
					state = store.Load(Now, DateTime.UtcNow, out var source);
					if (IslandTerrain.Applies(net.GetWorldName()) && !state.Layout.Enabled)
					{
						state.Layout.Enabled = true;
						state.Revision++;
					}
					TravelRules.Restore(state);
					((BaseUnityPlugin)this).Logger.LogInfo((object)$"Restored raid checkpoint: source={source}, phase={state.Phase}, id={state.Id}, remaining={state.Remaining(Now):0.0}s; file={store.FilePath}");
					SaveCheckpoint();
				}
				catch (Exception ex)
				{
					state = new RaidState();
					storageBlocked = true;
					((BaseUnityPlugin)this).Logger.LogError((object)("Raid checkpoint load failed; raid commands disabled. Files preserved. " + ex));
				}
				((BaseUnityPlugin)this).Logger.LogInfo((object)$"Server raid controller ready: {state.Phase}; persistent={!storageBlocked}.");
			}
			List<ZNetPeer> peers = (from p in net.GetPeers()
				where !p.m_server && p.m_rpc != null
				select p).ToList();
			foreach (ZNetPeer item in peers)
			{
				if (!registered.ContainsKey(item.m_rpc))
				{
					ZNetPeer captured = item;
					item.m_rpc.Register<ZPackage>("Roguenarok.Foundation.Request.v2", (Action<ZRpc, ZPackage>)delegate(ZRpc rpc, ZPackage package)
					{
						ReceiveRequest(captured, rpc, package);
					});
					registered.Add(item.m_rpc, item);
				}
			}
			foreach (ZRpc item2 in registered.Keys.Where((ZRpc r) => !peers.Any((ZNetPeer p) => p.m_rpc == r)).ToList())
			{
				registered.Remove(item2);
				lastRequest.Remove(item2);
			}
			RaidState raidState = state.Copy();
			bool flag = !storageBlocked && state.Tick(Now);
			if (flag)
			{
				TravelRules.Transition(state, raidState.Phase, PresentPlayers());
			}
			if (!storageBlocked)
			{
				CheckPlayerDeaths();
			}
			if (state.Revision != raidState.Revision && !SaveCheckpoint())
			{
				state = raidState;
				flag = false;
			}
			if (flag)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)$"Raid {state.Id}: {state.Phase}; {state.Result}");
			}
			if (Now < nextBroadcast && !flag)
			{
				return;
			}
			nextBroadcast = Now + 1.0;
			raidState = state.Copy();
			if (!storageBlocked && state.RefreshMembers(OnlineMembers(net)) && !SaveCheckpoint())
			{
				state = raidState;
			}
			sequence++;
			foreach (ZNetPeer item3 in peers.Where((ZNetPeer p) => p.IsReady() && p.m_rpc.IsConnected()))
			{
				SendState(item3);
			}
			if (!net.IsDedicated())
			{
				view = MakeView(admin: true);
				view.Transfer = state.Transfers.FirstOrDefault((Transfer t) => t.Id == "local-host")?.Copy();
				receivedAt = Now;
			}
		}

		private IEnumerable<Member> OnlineMembers(ZNet net)
		{
			foreach (ZNetPeer item in from p in net.GetPeers()
				where !p.m_server && p.IsReady() && p.m_rpc.IsConnected()
				select p)
			{
				string hostName = item.m_socket.GetHostName();
				if (!string.IsNullOrEmpty(hostName))
				{
					yield return new Member(hostName, CleanName(item.m_playerName));
				}
			}
			if (!net.IsDedicated() && (Object)(object)Player.m_localPlayer != (Object)null)
			{
				yield return new Member("local-host", CleanName(Player.m_localPlayer.GetPlayerName()));
			}
		}

		private static string CleanName(string name)
		{
			if (!string.IsNullOrWhiteSpace(name))
			{
				return name.Replace("<", "").Replace(">", "").Replace("\n", " ")
					.Replace("\r", " ")
					.Substring(0, Math.Min(48, name.Replace("<", "").Replace(">", "").Replace("\n", " ")
						.Replace("\r", " ")
						.Length));
				}
				return "Игрок";
			}

			private void UpdateClient(ZNet net)
			{
				ZRpc val = net.GetServerPeer()?.m_rpc;
				if (val == null || !val.IsConnected())
				{
					if (serverRpc != null)
					{
						serverRpc = null;
						view = null;
						order.Reset();
						reply = "Соединение с сервером потеряно.";
						SetMenu(open: false);
					}
					return;
				}
				if (serverRpc != val)
				{
					serverRpc = val;
					view = null;
					order.Reset();
					nextSync = 0.0;
					val.Register<ZPackage>("Roguenarok.Foundation.State.v2", (Action<ZRpc, ZPackage>)ReceiveState);
					val.Register<ZPackage>("Roguenarok.Foundation.Reply.v2", (Action<ZRpc, ZPackage>)ReceiveReply);
					((BaseUnityPlugin)this).Logger.LogInfo((object)"Server connection bound; requesting raid snapshot.");
				}
				if (Now >= nextSync)
				{
					nextSync = Now + (double)((view == null) ? 2 : 10);
					SendRequest("sync", 0);
				}
			}

			private void ReceiveRequest(ZNetPeer peer, ZRpc rpc, ZPackage package)
			{
				//IL_0139: Unknown result type (might be due to invalid IL or missing references)
				//IL_0140: Expected O, but got Unknown
				if ((Object)(object)network == (Object)null || !network.IsServer() || state == null || network.GetPeer(peer.m_uid) != peer || peer.m_rpc != rpc || !peer.IsReady() || (lastRequest.TryGetValue(rpc, out var value) && Now - value < 0.35))
				{
					return;
				}
				lastRequest[rpc] = Now;
				try
				{
					if (package.Size() <= 1024 && package.ReadInt() == 2 && !(package.ReadString() != "0.3.0"))
					{
						string text = package.ReadString();
						int seconds = package.ReadInt();
						bool admin = ZNetExtension.IsAdmin(network, peer.m_uid);
						string text2 = ExecuteServer(text, seconds, admin, peer.m_socket.GetHostName());
						if (text != "sync" && text != "status")
						{
							((BaseUnityPlugin)this).Logger.LogInfo((object)$"Command '{text}' from peer {peer.m_uid}: {text2}");
						}
						sequence++;
						if (text != "sync")
						{
							ZPackage val = new ZPackage();
							val.Write(2);
							val.Write(text2);
							rpc.Invoke("Roguenarok.Foundation.Reply.v2", new object[1] { val });
						}
						SendState(peer);
						nextBroadcast = 0.0;
					}
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Rejected malformed request: " + ex.GetType().Name));
				}
			}

			private string ExecuteServer(string action, int seconds, bool admin, string playerId = "local-host")
			{
				if (state == null)
				{
					return "Контроллер рейда ещё загружается.";
				}
				if (storageBlocked)
				{
					return "Ошибка хранилища рейда. Команды отключены до восстановления файлов и перезапуска сервера.";
				}
				RaidState raidState = state.Copy();
				string result = ExecuteTravelCommand(action, seconds, admin, playerId);
				TravelRules.Transition(state, raidState.Phase, PresentPlayers());
				if (!SaveCheckpoint())
				{
					state = raidState;
					return "Не удалось сохранить рейд. Изменение отменено, команды отключены. Проверь журнал сервера.";
				}
				return result;
			}

			private bool SaveCheckpoint()
			{
				if (store == null || state == null || storageBlocked)
				{
					return !storageBlocked;
				}
				if (savedRevision == state.Revision)
				{
					return true;
				}
				try
				{
					store.Save(state, Now, DateTime.UtcNow);
					savedRevision = state.Revision;
					return true;
				}
				catch (Exception ex)
				{
					storageBlocked = true;
					((BaseUnityPlugin)this).Logger.LogError((object)("Raid checkpoint write failed; state changes blocked. " + ex));
					return false;
				}
			}

			private View MakeView(bool admin)
			{
				return new View
				{
					Epoch = epoch,
					Sequence = sequence,
					Revision = state.Revision,
					RaidId = state.Id,
					Phase = state.Phase,
					Remaining = state.Remaining(Now),
					Result = (storageBlocked ? "Ошибка сохранения рейда: управление отключено." : state.Result),
					Admin = admin,
					Members = state.Members.Select((Member m) => m.Copy()).ToList(),
					Layout = state.Layout.Copy()
				};
			}

			private void SendState(ZNetPeer peer)
			{
				//IL_004c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0052: Expected O, but got Unknown
				View view = MakeView(ZNetExtension.IsAdmin(network, peer.m_uid));
				view.Transfer = state.Transfers.FirstOrDefault((Transfer t) => t.Id == peer.m_socket.GetHostName());
				ZPackage val = new ZPackage();
				val.Write(2);
				val.Write("0.3.0");
				val.Write(view.Epoch);
				val.Write(view.Sequence);
				val.Write(view.Revision);
				val.Write(view.RaidId);
				val.Write((int)view.Phase);
				val.Write(view.Remaining);
				val.Write(view.Result);
				val.Write(view.Admin);
				val.Write(view.Members.Count);
				foreach (Member member in view.Members)
				{
					val.Write(member.Id);
					val.Write(member.Name);
					val.Write(member.Connected);
					val.Write((int)member.Status);
				}
				WriteLayout(val, view.Layout);
				val.Write(view.Transfer?.Token ?? "");
				val.Write(view.Transfer?.Target ?? "");
				peer.m_rpc.Invoke("Roguenarok.Foundation.State.v2", new object[1] { val });
			}

			private void ReceiveState(ZRpc rpc, ZPackage p)
			{
				if (rpc != serverRpc || (Object)(object)network == (Object)null || network.IsServer())
				{
					return;
				}
				try
				{
					if (p.Size() > 16384 || p.ReadInt() != 2 || p.ReadString() != "0.3.0")
					{
						return;
					}
					View view = new View
					{
						Epoch = p.ReadString(),
						Sequence = p.ReadLong(),
						Revision = p.ReadLong(),
						RaidId = p.ReadString(),
						Phase = (RaidPhase)p.ReadInt(),
						Remaining = p.ReadDouble(),
						Result = p.ReadString(),
						Admin = p.ReadBool()
					};
					int num = p.ReadInt();
					if (!Enum.IsDefined(typeof(RaidPhase), view.Phase) || num < 0 || num > 64 || double.IsNaN(view.Remaining) || double.IsInfinity(view.Remaining) || view.Remaining < 0.0 || view.Remaining > 3000.0)
					{
						return;
					}
					for (int i = 0; i < num; i++)
					{
						Member member = new Member(p.ReadString(), CleanName(p.ReadString()), p.ReadBool())
						{
							Status = (MemberStatus)p.ReadInt()
						};
						if (!Enum.IsDefined(typeof(MemberStatus), member.Status))
						{
							return;
						}
						view.Members.Add(member);
					}
					view.Layout = ReadLayout(p);
					string text = p.ReadString();
					string text2 = p.ReadString();
					if (text != "")
					{
						if (!System.Guid.TryParseExact(text, "N", out var _) || (text2 != "hub" && text2 != "landing"))
						{
							return;
						}
						view.Transfer = new Transfer("self", text2)
						{
							Token = text
						};
					}
					if (order.Accept(view.Epoch, view.Sequence))
					{
						bool num2 = this.view == null;
						this.view = view;
						receivedAt = Now;
						if (num2)
						{
							reply = "Синхронизация выполнена.";
							((BaseUnityPlugin)this).Logger.LogInfo((object)$"Raid snapshot received: {view.Phase}, revision {view.Revision}, admin={view.Admin}");
						}
					}
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Rejected malformed snapshot: " + ex.GetType().Name));
				}
			}

			private void ReceiveReply(ZRpc rpc, ZPackage p)
			{
				if (rpc != serverRpc)
				{
					return;
				}
				try
				{
					if (p.Size() <= 4096 && p.ReadInt() == 2)
					{
						reply = p.ReadString();
						((BaseUnityPlugin)this).Logger.LogInfo((object)reply);
					}
				}
				catch
				{
				}
			}

			private void SendRequest(string action, int seconds)
			{
				//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e3: Expected O, but got Unknown
				if ((Object)(object)network == (Object)null)
				{
					reply = "Сначала подключись к серверу.";
				}
				else if (network.IsServer())
				{
					reply = ExecuteServer(action, seconds, admin: true);
					sequence++;
					nextBroadcast = 0.0;
					if (!network.IsDedicated())
					{
						view = MakeView(admin: true);
						view.Transfer = state.Transfers.FirstOrDefault((Transfer t) => t.Id == "local-host");
						receivedAt = Now;
					}
				}
				else if (serverRpc == null || !serverRpc.IsConnected())
				{
					reply = "Нет соединения с сервером.";
				}
				else
				{
					ZPackage val = new ZPackage();
					val.Write(2);
					val.Write("0.3.0");
					val.Write(action);
					val.Write(seconds);
					serverRpc.Invoke("Roguenarok.Foundation.Request.v2", new object[1] { val });
				}
			}

			private void ExecuteLine(string line)
			{
				string[] array = line.Trim().Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
				if (array.Length != 0 && array[0].Equals("rogue", StringComparison.OrdinalIgnoreCase))
				{
					array = array.Skip(1).ToArray();
				}
				if (array.Length == 2 && array[1] == "set" && (array[0] == "hub" || array[0] == "landing" || array[0] == "exit"))
				{
					SendRequest("set:" + array[0], 0);
					return;
				}
				if (array.Length == 0)
				{
					reply = "rogue status | start [10..2700] | extract | finish | reset";
					return;
				}
				string text = array[0].ToLowerInvariant();
				int result = 300;
				if ((text == "start" && array.Length > 2) || (text != "start" && array.Length > 1) || (array.Length == 2 && !int.TryParse(array[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out result)))
				{
					reply = "Неверные аргументы команды.";
				}
				else
				{
					SendRequest(text, result);
				}
			}

			private void OnCommand(ConsoleEventArgs args)
			{
				ExecuteLine(args.FullLine);
				args.Context.AddString("[Roguenarok] " + reply);
			}

			private static string PhaseName(RaidPhase p)
			{
				return p switch
				{
					RaidPhase.Waiting => "ОЖИДАНИЕ", 
					RaidPhase.Preparing => "ПОДГОТОВКА", 
					RaidPhase.Active => "РЕЙД", 
					RaidPhase.Evacuating => "ЭВАКУАЦИЯ", 
					_ => "ЗАВЕРШЁН", 
				};
			}

			private void SetMenu(bool open)
			{
				//IL_000d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0012: Unknown result type (might be due to invalid IL or missing references)
				//IL_002c: Unknown result type (might be due to invalid IL or missing references)
				if (MenuOpen != open)
				{
					if (open)
					{
						savedLock = Cursor.lockState;
						savedVisible = Cursor.visible;
					}
					MenuOpen = open;
					if (!open)
					{
						Cursor.lockState = savedLock;
						Cursor.visible = savedVisible;
					}
				}
			}

			private void LateUpdate()
			{
				if (MenuOpen)
				{
					Cursor.lockState = (CursorLockMode)0;
					Cursor.visible = true;
				}
			}

			private void OnGUI()
			{
				//IL_0028: Unknown result type (might be due to invalid IL or missing references)
				//IL_002d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0034: Unknown result type (might be due to invalid IL or missing references)
				//IL_003c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0044: Expected O, but got Unknown
				//IL_0187: Unknown result type (might be due to invalid IL or missing references)
				//IL_0193: Unknown result type (might be due to invalid IL or missing references)
				//IL_01a7: Expected O, but got Unknown
				//IL_01a2: Unknown result type (might be due to invalid IL or missing references)
				//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
				//IL_012d: Unknown result type (might be due to invalid IL or missing references)
				//IL_015d: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)Player.m_localPlayer == (Object)null)
				{
					return;
				}
				if (hudEnabled.Value)
				{
					GUIStyle val = new GUIStyle(GUI.skin.label)
					{
						alignment = (TextAnchor)4,
						fontSize = 16,
						richText = false
					};
					string text = ((view == null) ? "ROGUENAROK · синхронизация…" : $"ROGUENAROK · {PhaseName(view.Phase)}\n{TimeSpan.FromSeconds(Math.Ceiling(DisplayRemaining)):mm\\:ss} · {view.Members.Count((Member m) => m.Connected)}/{view.Members.Count} игроков");
					if (view != null && Now - receivedAt > 5.0)
					{
						text += "\nНет свежих данных сервера";
					}
					GUI.Box(new Rect((float)Screen.width / 2f - 170f, 20f, 340f, 90f), "");
					GUI.Label(new Rect((float)Screen.width / 2f - 165f, 23f, 330f, 80f), text + "\nF6 · управление", val);
				}
				DrawExitPrompt();
				if (MenuOpen)
				{
					menuRect = GUILayout.Window(812941, menuRect, new WindowFunction(DrawMenu), "Roguenarok Foundation 0.3.0 · F6", Array.Empty<GUILayoutOption>());
				}
			}

			private void DrawMenu(int id)
			{
				//IL_02b9: Unknown result type (might be due to invalid IL or missing references)
				GUILayout.Label((view == null) ? "Ожидание сервера" : string.Format("{0} · ревизия {1} · права: {2}", PhaseName(view.Phase), view.Revision, view.Admin ? "администратор" : "игрок"), Array.Empty<GUILayoutOption>());
				if (view != null)
				{
					GUILayout.Label("ID рейда: " + ((view.RaidId == "") ? "—" : view.RaidId), Array.Empty<GUILayoutOption>());
					foreach (Member member in view.Members)
					{
						GUILayout.Label(member.Name + (member.Connected ? " · подключён" : " · отключён") + " · " + member.Status, Array.Empty<GUILayoutOption>());
					}
				}
				if (view != null && !string.IsNullOrEmpty(view.Result))
				{
					GUILayout.Label(view.Result, Array.Empty<GUILayoutOption>());
				}
				GUILayout.Space(8f);
				GUILayout.Label("Остров: высадка и возврат работают. Учёт добычи ещё не добавлен.", Array.Empty<GUILayoutOption>());
				GUILayout.Label("Рейд сохраняется. Таймер учитывает время простоя сервера.", Array.Empty<GUILayoutOption>());
				GUI.enabled = view != null && view.Admin;
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				if (GUILayout.Button("Рейд 5 минут", Array.Empty<GUILayoutOption>()))
				{
					SendRequest("start", 300);
				}
				if (GUILayout.Button("Эвакуация (админ)", Array.Empty<GUILayoutOption>()))
				{
					SendRequest("extract", 0);
				}
				GUILayout.EndHorizontal();
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				if (GUILayout.Button("Завершить", Array.Empty<GUILayoutOption>()))
				{
					SendRequest("finish", 0);
				}
				if (GUILayout.Button("Сбросить", Array.Empty<GUILayoutOption>()))
				{
					SendRequest("reset", 0);
				}
				GUILayout.EndHorizontal();
				GUI.enabled = true;
				DrawTravelMenu();
				input = GUILayout.TextField(input, 120, Array.Empty<GUILayoutOption>());
				if (GUILayout.Button("Выполнить команду", Array.Empty<GUILayoutOption>()))
				{
					ExecuteLine(input);
				}
				GUILayout.Label(reply, Array.Empty<GUILayoutOption>());
				if (GUILayout.Button("Обновить статус", Array.Empty<GUILayoutOption>()))
				{
					SendRequest("status", 0);
				}
				if (GUILayout.Button("Закрыть", Array.Empty<GUILayoutOption>()))
				{
					SetMenu(open: false);
				}
				GUI.DragWindow(new Rect(0f, 0f, 10000f, 25f));
			}

			private IEnumerable<PresentPlayer> PresentPlayers()
			{
				//IL_0065: Unknown result type (might be due to invalid IL or missing references)
				//IL_0074: Unknown result type (might be due to invalid IL or missing references)
				//IL_0079: Unknown result type (might be due to invalid IL or missing references)
				//IL_0087: Unknown result type (might be due to invalid IL or missing references)
				//IL_008f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0097: Unknown result type (might be due to invalid IL or missing references)
				//IL_0101: Unknown result type (might be due to invalid IL or missing references)
				//IL_0106: Unknown result type (might be due to invalid IL or missing references)
				//IL_010e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0116: Unknown result type (might be due to invalid IL or missing references)
				//IL_011e: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)network == (Object)null)
				{
					return Array.Empty<PresentPlayer>();
				}
				List<PresentPlayer> list = new List<PresentPlayer>();
				foreach (ZNetPeer item in from p in network.GetPeers()
					where !p.m_server && p.IsReady() && p.m_rpc.IsConnected()
					select p)
				{
					ZDOMan obj = ZDOMan.instance;
					ZDO val = ((obj != null) ? obj.GetZDO(item.m_characterID) : null);
					if (val != null)
					{
						Vector3 position = val.GetPosition();
						list.Add(new PresentPlayer(item.m_socket.GetHostName(), new Point(position.x, position.y, position.z), val.GetFloat("health", 1f) > 0f));
					}
				}
				if (!network.IsDedicated() && (Object)(object)Player.m_localPlayer != (Object)null)
				{
					Player localPlayer = Player.m_localPlayer;
					Vector3 position2 = ((Component)localPlayer).transform.position;
					list.Add(new PresentPlayer("local-host", new Point(position2.x, position2.y, position2.z), !((Character)localPlayer).IsDead()));
				}
				return list;
			}

			private void CheckPlayerDeaths()
			{
				if (!state.Layout.Enabled)
				{
					return;
				}
				foreach (PresentPlayer item in from p in PresentPlayers()
					where !p.Alive
					select p)
				{
					TravelRules.Death(state, item.Id);
				}
			}

			private string ExecuteTravelCommand(string action, int seconds, bool admin, string id)
			{
				PresentPlayer presentPlayer = PresentPlayers().FirstOrDefault((PresentPlayer presentPlayer2) => presentPlayer2.Id == id);
				if (action == "spawn")
				{
					TravelRules.Spawn(state, id);
					return "Точка появления синхронизирована.";
				}
				if (action == "death")
				{
					TravelRules.Death(state, id);
					return "Возврат после смерти поставлен в очередь.";
				}
				if (action.StartsWith("ack:", StringComparison.Ordinal))
				{
					Transfer transfer = state.Transfers.FirstOrDefault((Transfer t) => t.Id == id && t.Token == action.Substring(4));
					if (transfer == null)
					{
						return "Перенос уже подтверждён.";
					}
					Point p = ((transfer.Target == "landing") ? state.Layout.Landing : state.Layout.Hub);
					if (presentPlayer == null || !presentPlayer.Alive || presentPlayer.Position.Distance(p) > 20.0)
					{
						return "Ожидание прибытия в точку переноса.";
					}
					if (!TravelRules.Ack(state, id, transfer.Token))
					{
						return "Нет ожидающего переноса.";
					}
					return "Перенос подтверждён.";
				}
				if (action == "evac")
				{
					if (!TravelRules.CanEvacuate(state, id, presentPlayer))
					{
						return "Эвакуация доступна живому участнику рейда у сигнальной точки.";
					}
					return state.Execute("extract", 0, admin: true, Now, OnlineMembers(network));
				}
				if (action == "hub")
				{
					if (state.Phase != RaidPhase.Waiting && state.Phase != RaidPhase.Finished)
					{
						return "Возврат в хаб во время рейда доступен через эвакуацию.";
					}
					TravelRules.Queue(state, id, "hub");
					return "Возврат в хаб поставлен в очередь.";
				}
				if (action.StartsWith("set:", StringComparison.Ordinal))
				{
					if (!admin)
					{
						return "Только администратор может задавать точки.";
					}
					if (state.Phase != RaidPhase.Waiting || state.Transfers.Count > 0)
					{
						return "Задавай точки в ожидании, после завершения переносов.";
					}
					if (presentPlayer == null || !presentPlayer.Position.Valid || !presentPlayer.Alive)
					{
						return "Нет позиции живого игрока.";
					}
					if (IslandTerrain.Applies(network.GetWorldName()) && IslandTerrain.Height(presentPlayer.Position.X, presentPlayer.Position.Z, network.GetWorld().m_seed) < 31.0)
					{
						return "Точка должна находиться на суше.";
					}
					string text = action.Substring(4);
					switch (text)
					{
					case "hub":
						state.Layout.Hub = presentPlayer.Position.Copy();
						break;
					case "landing":
						state.Layout.Landing = presentPlayer.Position.Copy();
						break;
					case "exit":
						state.Layout.Exit = presentPlayer.Position.Copy();
						break;
					default:
						return "Неизвестная точка.";
					}
					state.Revision++;
					return "Точка " + text + " сохранена для этого мира.";
				}
				return state.Execute(action, seconds, admin, Now, OnlineMembers(network));
			}

			private static void WritePoint(ZPackage p, Point point)
			{
				p.Write(point.X);
				p.Write(point.Y);
				p.Write(point.Z);
			}

			private static Point ReadPoint(ZPackage p)
			{
				Point point = new Point(p.ReadDouble(), p.ReadDouble(), p.ReadDouble());
				if (!point.Valid)
				{
					throw new InvalidDataException("Invalid point.");
				}
				return point;
			}

			private static void WriteLayout(ZPackage p, RaidLayout layout)
			{
				p.Write(layout.Enabled);
				WritePoint(p, layout.Hub);
				WritePoint(p, layout.Landing);
				WritePoint(p, layout.Exit);
			}

			private static RaidLayout ReadLayout(ZPackage p)
			{
				return new RaidLayout
				{
					Enabled = p.ReadBool(),
					Hub = ReadPoint(p),
					Landing = ReadPoint(p),
					Exit = ReadPoint(p)
				};
			}

			private void ClearTravel()
			{
				if ((Object)(object)exitMarker != (Object)null)
				{
					Object.Destroy((Object)(object)exitMarker);
				}
				exitMarker = null;
				travelPlayer = null;
				needsSpawnNotification = false;
				teleportToken = (completedToken = null);
				nextTravelAttempt = 0.0;
			}

			internal static void NotifySpawn()
			{
				if ((Object)(object)instance != (Object)null)
				{
					instance.needsSpawnNotification = true;
					instance.nextTravelAttempt = Now + 1.0;
				}
			}

			internal static void NotifyDeath()
			{
				if ((Object)(object)instance != (Object)null)
				{
					instance.SendRequest("death", 0);
				}
			}

			private Vector3 TargetPosition(Point point)
			{
				//IL_004d: Unknown result type (might be due to invalid IL or missing references)
				float num = (float)point.X;
				float num2 = (float)point.Z;
				float num3 = ((point.Y == 0.0 && WorldGenerator.instance != null) ? (WorldGenerator.instance.GetHeight(num, num2) + 2f) : ((float)point.Y + 0.3f));
				return new Vector3(num, num3, num2);
			}

			private void UpdateTravelClient()
			{
				//IL_0123: Unknown result type (might be due to invalid IL or missing references)
				//IL_0128: Unknown result type (might be due to invalid IL or missing references)
				//IL_0134: Unknown result type (might be due to invalid IL or missing references)
				//IL_0135: Unknown result type (might be due to invalid IL or missing references)
				//IL_013f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0144: Unknown result type (might be due to invalid IL or missing references)
				//IL_00df: Unknown result type (might be due to invalid IL or missing references)
				//IL_0108: Unknown result type (might be due to invalid IL or missing references)
				//IL_0172: Unknown result type (might be due to invalid IL or missing references)
				//IL_018c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0266: Unknown result type (might be due to invalid IL or missing references)
				//IL_026b: Unknown result type (might be due to invalid IL or missing references)
				//IL_02b6: Unknown result type (might be due to invalid IL or missing references)
				//IL_02bb: Unknown result type (might be due to invalid IL or missing references)
				//IL_032c: Unknown result type (might be due to invalid IL or missing references)
				//IL_032d: Unknown result type (might be due to invalid IL or missing references)
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null || view?.Layout == null || !view.Layout.Enabled)
				{
					return;
				}
				if ((Object)(object)travelPlayer != (Object)(object)localPlayer)
				{
					travelPlayer = localPlayer;
					needsSpawnNotification = true;
					nextTravelAttempt = Now + 1.0;
					teleportToken = (completedToken = null);
				}
				if ((Object)(object)exitMarker == (Object)null)
				{
					exitMarker = GameObject.CreatePrimitive((PrimitiveType)2);
					((Object)exitMarker).name = "Roguenarok_ExtractionBeacon";
					Collider component = exitMarker.GetComponent<Collider>();
					if ((Object)(object)component != (Object)null)
					{
						component.enabled = false;
					}
					exitMarker.transform.localScale = new Vector3(1.5f, 5f, 1.5f);
					exitMarker.GetComponent<Renderer>().material.color = new Color(1f, 0.65f, 0.12f);
				}
				Vector3 val = TargetPosition(view.Layout.Exit);
				exitMarker.transform.position = val + Vector3.up * 1.5f;
				if (((Character)localPlayer).IsDead())
				{
					return;
				}
				if (!MenuOpen && view.Phase == RaidPhase.Active && new Point(((Component)localPlayer).transform.position.x, 0.0, ((Component)localPlayer).transform.position.z).Distance(view.Layout.Exit) <= view.Layout.ExitRadius && Input.GetKeyDown((KeyCode)288))
				{
					SendRequest("evac", 0);
				}
				if (Now < nextTravelAttempt)
				{
					return;
				}
				if (needsSpawnNotification)
				{
					needsSpawnNotification = false;
					nextTravelAttempt = Now + 0.75;
					SendRequest("spawn", 0);
					return;
				}
				Transfer transfer = view.Transfer;
				if (transfer == null)
				{
					teleportToken = null;
					return;
				}
				Vector3 val2 = TargetPosition((transfer.Target == "landing") ? view.Layout.Landing : view.Layout.Hub);
				if (((Character)localPlayer).IsTeleporting())
				{
					nextTravelAttempt = Now + 0.5;
				}
				else if (completedToken == transfer.Token || (teleportToken == transfer.Token && Vector3.Distance(((Component)localPlayer).transform.position, val2) < 12f))
				{
					completedToken = transfer.Token;
					nextTravelAttempt = Now + 1.0;
					SendRequest("ack:" + transfer.Token, 0);
				}
				else if (teleportToken != transfer.Token || Now - teleportStartedAt > 10.0)
				{
					if (((Character)localPlayer).TeleportTo(val2, Quaternion.identity, true))
					{
						teleportToken = transfer.Token;
						teleportStartedAt = Now;
						nextTravelAttempt = Now + 2.0;
						((BaseUnityPlugin)this).Logger.LogInfo((object)("Transfer started: " + transfer.Target + " token=" + transfer.Token));
					}
					else
					{
						nextTravelAttempt = Now + 1.0;
					}
				}
				else
				{
					nextTravelAttempt = Now + 0.5;
				}
			}

			private void DrawExitPrompt()
			{
				//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_004a: Unknown result type (might be due to invalid IL or missing references)
				//IL_005a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0095: Unknown result type (might be due to invalid IL or missing references)
				//IL_009a: Unknown result type (might be due to invalid IL or missing references)
				//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
				//IL_00aa: Expected O, but got Unknown
				//IL_013d: Unknown result type (might be due to invalid IL or missing references)
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null || view?.Layout == null || !view.Layout.Enabled || ((Character)localPlayer).IsDead())
				{
					return;
				}
				Vector3 position = ((Component)localPlayer).transform.position;
				double num = new Point(position.x, 0.0, position.z).Distance(view.Layout.Exit);
				if (num < 120.0)
				{
					GUIStyle val = new GUIStyle(GUI.skin.label)
					{
						alignment = (TextAnchor)4,
						fontSize = 18
					};
					string text = "Сигнальная точка · " + Math.Ceiling(num) + " м";
					if (num <= view.Layout.ExitRadius)
					{
						text += ((view.Phase == RaidPhase.Active) ? "\nF7 — начать эвакуацию" : ((view.Phase == RaidPhase.Evacuating) ? "\nОставайся здесь до конца эвакуации" : ""));
					}
					GUI.Label(new Rect((float)Screen.width / 2f - 230f, (float)(Screen.height - 150), 460f, 70f), text, val);
				}
			}

			private void DrawTravelMenu()
			{
				if (view?.Layout != null && view.Layout.Enabled)
				{
					GUILayout.Label("Этап острова 1 · хаб / высадка / сигнальная точка", Array.Empty<GUILayoutOption>());
					if (GUILayout.Button("В хаб (вне рейда)", Array.Empty<GUILayoutOption>()))
					{
						SendRequest("hub", 0);
					}
					GUI.enabled = view.Admin && view.Phase == RaidPhase.Waiting;
					GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
					if (GUILayout.Button("Здесь хаб", Array.Empty<GUILayoutOption>()))
					{
						SendRequest("set:hub", 0);
					}
					if (GUILayout.Button("Здесь высадка", Array.Empty<GUILayoutOption>()))
					{
						SendRequest("set:landing", 0);
					}
					if (GUILayout.Button("Здесь выход", Array.Empty<GUILayoutOption>()))
					{
						SendRequest("set:exit", 0);
					}
					GUILayout.EndHorizontal();
					GUI.enabled = true;
				}
			}

			internal static bool ProtectedByHub(Character c)
			{
				//IL_0050: Unknown result type (might be due to invalid IL or missing references)
				//IL_0055: Unknown result type (might be due to invalid IL or missing references)
				//IL_0056: Unknown result type (might be due to invalid IL or missing references)
				//IL_0066: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)instance == (Object)null || (Object)(object)c != (Object)(object)Player.m_localPlayer || instance.view?.Layout == null || !instance.view.Layout.Enabled)
				{
					return false;
				}
				Vector3 position = ((Component)c).transform.position;
				return new Point(position.x, 0.0, position.z).Distance(instance.view.Layout.Hub) <= instance.view.Layout.HubRadius;
			}
		}
		[HarmonyPatch(typeof(PlayerController), "TakeInput")]
		internal static class MenuInputPatch
		{
			private static bool Prefix(ref bool __result)
			{
				if (!Plugin.MenuOpen)
				{
					return true;
				}
				__result = false;
				return false;
			}
		}
		[HarmonyPatch(typeof(Player), "SetControls")]
		internal static class MenuControlPatch
		{
			private static void Prefix(Player __instance, object[] __args)
			{
				//IL_002d: Unknown result type (might be due to invalid IL or missing references)
				if (Plugin.MenuOpen && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer))
				{
					for (int i = 0; i < __args.Length; i++)
					{
						__args[i] = ((__args[i] is Vector3) ? ((object)Vector3.zero) : ((object)false));
					}
				}
			}
		}
		public enum RaidPhase
		{
			Waiting,
			Preparing,
			Active,
			Evacuating,
			Finished
		}
		public sealed class Member
		{
			public string Id;

			public string Name;

			public bool Connected;

			public MemberStatus Status = MemberStatus.Lobby;

			public Member(string id, string name, bool connected = true)
			{
				Id = id;
				Name = name;
				Connected = connected;
			}

			public Member Copy()
			{
				return new Member(Id, Name, Connected)
				{
					Status = Status
				};
			}
		}
		public sealed class RaidState
		{
			public string Id = "";

			public string Result = "";

			public RaidPhase Phase;

			public long Revision;

			public double Deadline;

			public double RaidDeadline;

			public RaidLayout Layout = new RaidLayout();

			public readonly List<Transfer> Transfers = new List<Transfer>();

			public readonly List<Member> Members = new List<Member>();

			public double Remaining(double now)
			{
				if (Phase != RaidPhase.Preparing && Phase != RaidPhase.Active && Phase != RaidPhase.Evacuating)
				{
					return 0.0;
				}
				return Math.Max(0.0, Deadline - now);
			}

			public string Execute(string action, int seconds, bool admin, double now, IEnumerable<Member> online)
			{
				Tick(now);
				if (action == "status" || action == "sync")
				{
					return "Состояние обновлено.";
				}
				if (!admin)
				{
					return "Отказ: команда доступна только администратору сервера.";
				}
				switch (action)
				{
				case "start":
				{
					if (Phase != RaidPhase.Waiting)
					{
						return "Сначала выполни rogue reset.";
					}
					if (seconds < 10 || seconds > 2700)
					{
						return "Длительность: от 10 до 2700 секунд.";
					}
					List<Member> list = (from m in online
						group m by m.Id into g
						select g.First().Copy()).ToList();
					if (list.Count == 0 || list.Count > 4)
					{
						return "Для тестового рейда нужно от 1 до 4 подключённых игроков.";
					}
					Members.Clear();
					Members.AddRange(list);
					Transfers.Clear();
					foreach (Member member in Members)
					{
						member.Status = MemberStatus.Preparing;
					}
					Id = Guid.NewGuid().ToString("N");
					Result = "";
					Phase = RaidPhase.Preparing;
					Deadline = now + 5.0;
					RaidDeadline = Deadline + (double)seconds;
					Revision++;
					return "Подготовка 5 секунд. Затем начнётся тестовый рейд.";
				}
				case "extract":
					if (Phase != RaidPhase.Active)
					{
						return "Эвакуацию можно начать только во время рейда.";
					}
					Phase = RaidPhase.Evacuating;
					Deadline = Math.Min(now + 120.0, RaidDeadline);
					Revision++;
					return "Тестовая эвакуация началась. Награды пока не выдаются.";
				case "finish":
					if (Phase != RaidPhase.Active && Phase != RaidPhase.Evacuating)
					{
						return "Завершать можно только рейд или эвакуацию.";
					}
					Finish("Завершён администратором");
					return "Тестовый рейд завершён.";
				case "reset":
					Phase = RaidPhase.Waiting;
					Id = "";
					Result = "";
					Deadline = (RaidDeadline = 0.0);
					foreach (Member member2 in Members)
					{
						TravelRules.Queue(this, member2.Id, "hub");
					}
					Members.Clear();
					Revision++;
					return "Рейд сброшен. Мир и инвентари не изменены.";
				default:
					return "Команды: rogue status | start [секунды] | extract | finish | reset";
				}
			}

			public bool Tick(double now)
			{
				bool result = false;
				if (Phase == RaidPhase.Preparing && now >= Deadline)
				{
					Phase = RaidPhase.Active;
					Deadline = RaidDeadline;
					Revision++;
					result = true;
				}
				if (Phase == RaidPhase.Active && now >= Deadline)
				{
					Finish("Время рейда истекло");
					result = true;
				}
				else if (Phase == RaidPhase.Evacuating && now >= Deadline)
				{
					Finish("Тестовая эвакуация завершена");
					result = true;
				}
				return result;
			}

			public bool RefreshMembers(IEnumerable<Member> online)
			{
				List<Member> list = (from m in online
					group m by m.Id into g
					select g.First().Copy() into m
					orderby m.Id
					select m).ToList();
				if (Phase == RaidPhase.Waiting)
				{
					if (Members.Count == list.Count && Members.Zip(list, (Member a, Member b) => a.Id == b.Id && a.Name == b.Name).All((bool x) => x))
					{
						return false;
					}
					Members.Clear();
					Members.AddRange(list);
					Revision++;
					return true;
				}
				bool flag = false;
				foreach (Member member in Members)
				{
					bool flag2 = list.Any((Member m) => m.Id == member.Id);
					if (flag2 != member.Connected)
					{
						member.Connected = flag2;
						flag = true;
					}
				}
				if (flag)
				{
					Revision++;
				}
				return flag;
			}

			public RaidState Copy()
			{
				RaidState raidState = new RaidState();
				raidState.Id = Id;
				raidState.Result = Result;
				raidState.Phase = Phase;
				raidState.Revision = Revision;
				raidState.Deadline = Deadline;
				raidState.RaidDeadline = RaidDeadline;
				raidState.Members.AddRange(Members.Select((Member m) => m.Copy()));
				raidState.Layout = Layout.Copy();
				raidState.Transfers.AddRange(Transfers.Select((Transfer t) => t.Copy()));
				return raidState;
			}

			private void Finish(string reason)
			{
				Phase = RaidPhase.Finished;
				Deadline = 0.0;
				Result = reason;
				Revision++;
			}
		}
		public sealed class SnapshotOrder
		{
			private string epoch;

			private long sequence = -1L;

			public void Reset()
			{
				epoch = null;
				sequence = -1L;
			}

			public bool Accept(string newEpoch, long newSequence)
			{
				if (epoch != null && epoch != newEpoch)
				{
					return false;
				}
				if (newSequence <= sequence)
				{
					return false;
				}
				epoch = newEpoch;
				sequence = newSequence;
				return true;
			}
		}
		public sealed class RaidStore
		{
			private const int Schema = 2;

			private const long MaxBytes = 65536L;

			public string FilePath { get; }

			public RaidStore(string filePath)
			{
				FilePath = filePath;
			}

			public void Save(RaidState state, double monotonicNow, DateTime utcNow)
			{
				Directory.CreateDirectory(Path.GetDirectoryName(FilePath));
				XElement xElement = new XElement("RoguenarokRaid", new XAttribute("schema", 2), new XElement("Id", state.Id), new XElement("Phase", state.Phase.ToString()), new XElement("Revision", state.Revision), new XElement("Result", state.Result), new XElement("SavedUtc", utcNow.ToUniversalTime().Ticks), new XElement("DeadlineUtc", DeadlineTicks(state.Deadline, monotonicNow, utcNow)), new XElement("RaidDeadlineUtc", DeadlineTicks(state.RaidDeadline, monotonicNow, utcNow)), new XElement("Members", state.Members.Select((Member m) => new XElement("Member", new XAttribute("id", m.Id), new XAttribute("name", m.Name), new XAttribute("status", m.Status.ToString())))), new XElement("Layout", new XAttribute("enabled", state.Layout.Enabled), PointElement("Hub", state.Layout.Hub), PointElement("Landing", state.Layout.Landing), PointElement("Exit", state.Layout.Exit)), new XElement("Transfers", state.Transfers.Select((Transfer t) => new XElement("Transfer", new XAttribute("id", t.Id), new XAttribute("token", t.Token), new XAttribute("target", t.Target)))));
				XDocument xDocument = new XDocument(xElement);
				string text = FilePath + ".tmp";
				using (FileStream fileStream = new FileStream(text, FileMode.Create, FileAccess.Write, FileShare.None))
				{
					xDocument.Save(fileStream);
					fileStream.Flush(flushToDisk: true);
				}
				if (File.Exists(FilePath))
				{
					File.Replace(text, FilePath, FilePath + ".bak");
				}
				else
				{
					File.Move(text, FilePath);
				}
			}

			private static XElement PointElement(string name, Point point)
			{
				return new XElement(name, new XAttribute("x", point.X), new XAttribute("y", point.Y), new XAttribute("z", point.Z));
			}

			private static long DeadlineTicks(double deadline, double now, DateTime utc)
			{
				if (deadline != 0.0)
				{
					return utc.ToUniversalTime().AddSeconds(deadline - now).Ticks;
				}
				return 0L;
			}

			public RaidState Load(double monotonicNow, DateTime utcNow, out string source)
			{
				source = "new";
				if (!File.Exists(FilePath) && !File.Exists(FilePath + ".bak"))
				{
					return new RaidState();
				}
				Exception ex = null;
				try
				{
					RaidState result = Read(FilePath, monotonicNow, utcNow);
					source = "primary";
					return result;
				}
				catch (Exception ex2) when (ex2 is IOException || ex2 is XmlException || ex2 is InvalidDataException || ex2 is FormatException || ex2 is ArgumentException || ex2 is OverflowException)
				{
					ex = ex2;
				}
				try
				{
					RaidState result2 = Read(FilePath + ".bak", monotonicNow, utcNow);
					if (File.Exists(FilePath))
					{
						File.Move(FilePath, FilePath + ".corrupt-" + Guid.NewGuid().ToString("N"));
					}
					source = "backup";
					return result2;
				}
				catch (Exception ex3) when (ex3 is IOException || ex3 is XmlException || ex3 is InvalidDataException || ex3 is FormatException || ex3 is ArgumentException || ex3 is OverflowException)
				{
					throw new InvalidDataException("No valid raid checkpoint. Primary: " + ex.GetType().Name + "; backup: " + ex3.GetType().Name, ex3);
				}
			}

			private static RaidState Read(string path, double monotonicNow, DateTime utcNow)
			{
				FileInfo fileInfo = new FileInfo(path);
				if (!fileInfo.Exists || fileInfo.Length > 65536 || fileInfo.Length == 0L)
				{
					throw new InvalidDataException("Invalid checkpoint size.");
				}
				XDocument xDocument;
				using (XmlReader reader = XmlReader.Create(path, new XmlReaderSettings
				{
					DtdProcessing = DtdProcessing.Prohibit,
					XmlResolver = null,
					MaxCharactersInDocument = 65536L
				}))
				{
					xDocument = XDocument.Load(reader);
				}
				XElement root = xDocument.Root;
				if (root == null || root.Name != "RoguenarokRaid" || ((int?)root.Attribute("schema") != 2 && (int?)root.Attribute("schema") != 1))
				{
					throw new InvalidDataException("Unsupported checkpoint schema.");
				}
				if (!Enum.TryParse<RaidPhase>(Text("Phase"), out var result) || !Enum.IsDefined(typeof(RaidPhase), result))
				{
					throw new InvalidDataException("Invalid phase.");
				}
				long num = long.Parse(Text("Revision"), CultureInfo.InvariantCulture);
				long ticks = long.Parse(Text("SavedUtc"), CultureInfo.InvariantCulture);
				DateTime savedUtc = new DateTime(ticks, DateTimeKind.Utc);
				RaidState raidState = new RaidState
				{
					Id = Text("Id"),
					Phase = result,
					Revision = num,
					Result = Text("Result")
				};
				if (num < 0 || num > 9223372036853775807L || raidState.Result.Length > 512 || raidState.Id.Length > 64)
				{
					throw new InvalidDataException("Invalid checkpoint fields.");
				}
				if (result != RaidPhase.Waiting && !Guid.TryParseExact(raidState.Id, "N", out var result2))
				{
					throw new InvalidDataException("Invalid raid ID.");
				}
				if (result == RaidPhase.Waiting && raidState.Id != "")
				{
					throw new InvalidDataException("Waiting raid must not have an ID.");
				}
				raidState.Deadline = ReadDeadline("DeadlineUtc");
				raidState.RaidDeadline = ReadDeadline("RaidDeadlineUtc");
				foreach (XElement item in (root.Element("Members") ?? throw new InvalidDataException("Missing members.")).Elements("Member"))
				{
					string id = (string?)item.Attribute("id");
					string text = (string?)item.Attribute("name");
					if (string.IsNullOrWhiteSpace(id) || id.Length > 128 || text == null || text.Length > 48 || raidState.Members.Count >= 64 || raidState.Members.Any((Member m) => m.Id == id))
					{
						throw new InvalidDataException("Invalid participant list.");
					}
					Member member = new Member(id, text, connected: false);
					if ((int?)root.Attribute("schema") == 2)
					{
						if (!Enum.TryParse<MemberStatus>((string?)item.Attribute("status"), out var result3) || !Enum.IsDefined(typeof(MemberStatus), result3))
						{
							throw new InvalidDataException("Invalid member status.");
						}
						member.Status = result3;
					}
					raidState.Members.Add(member);
				}
				if ((result == RaidPhase.Preparing || result == RaidPhase.Active || result == RaidPhase.Evacuating) && (raidState.Members.Count < 1 || raidState.Members.Count > 4 || Text("DeadlineUtc") == "0" || Text("RaidDeadlineUtc") == "0" || raidState.Deadline > raidState.RaidDeadline))
				{
					throw new InvalidDataException("Invalid active raid.");
				}
				XElement layout;
				if ((int?)root.Attribute("schema") == 2)
				{
					layout = root.Element("Layout") ?? throw new InvalidDataException("Missing layout.");
					raidState.Layout.Enabled = (bool?)layout.Attribute("enabled") == true;
					raidState.Layout.Hub = ReadPoint("Hub");
					raidState.Layout.Landing = ReadPoint("Landing");
					raidState.Layout.Exit = ReadPoint("Exit");
					foreach (XElement item2 in (root.Element("Transfers") ?? throw new InvalidDataException("Missing transfers.")).Elements("Transfer"))
					{
						string id2 = (string?)item2.Attribute("id");
						string text2 = (string?)item2.Attribute("token");
						string text3 = (string?)item2.Attribute("target");
						if (string.IsNullOrWhiteSpace(id2) || id2.Length > 128 || !Guid.TryParseExact(text2, "N", out result2) || (text3 != "hub" && text3 != "landing") || raidState.Transfers.Count >= 64 || raidState.Transfers.Any((Transfer t) => t.Id == id2))
						{
							throw new InvalidDataException("Invalid transfer.");
						}
						raidState.Transfers.Add(new Transfer(id2, text3)
						{
							Token = text2
						});
					}
				}
				raidState.Tick(monotonicNow);
				TravelRules.Restore(raidState);
				return raidState;
				double ReadDeadline(string key)
				{
					long num2 = long.Parse(Text(key), CultureInfo.InvariantCulture);
					if (num2 == 0L)
					{
						return 0.0;
					}
					DateTime dateTime = new DateTime(num2, DateTimeKind.Utc);
					if ((dateTime - savedUtc).TotalSeconds > 3000.0)
					{
						throw new InvalidDataException("Checkpoint deadline exceeds maximum raid duration.");
					}
					return monotonicNow + (dateTime - utcNow.ToUniversalTime()).TotalSeconds;
				}
				Point ReadPoint(string name)
				{
					XElement xElement = layout.Element(name) ?? throw new InvalidDataException("Missing point.");
					if (xElement.Attribute("x") == null || xElement.Attribute("y") == null || xElement.Attribute("z") == null)
					{
						throw new InvalidDataException("Missing point coordinates.");
					}
					Point point = new Point((double)xElement.Attribute("x"), (double)xElement.Attribute("y"), (double)xElement.Attribute("z"));
					if (!point.Valid)
					{
						throw new InvalidDataException("Invalid point coordinates.");
					}
					return point;
				}
				string Text(string key)
				{
					return root.Element(key)?.Value ?? throw new InvalidDataException("Missing field: " + key);
				}
			}
		}
		[HarmonyPatch(typeof(Player), "OnSpawned")]
		internal static class SpawnTravelPatch
		{
			private static void Postfix(Player __instance)
			{
				if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer)
				{
					Plugin.NotifySpawn();
				}
			}
		}
		[HarmonyPatch(typeof(Player), "OnDeath")]
		internal static class DeathTravelPatch
		{
			private static void Postfix(Player __instance)
			{
				if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer)
				{
					Plugin.NotifyDeath();
				}
			}
		}
		[HarmonyPatch(typeof(Character), "RPC_Damage")]
		internal static class HubDamagePatch
		{
			private static bool Prefix(Character __instance)
			{
				return !Plugin.ProtectedByHub(__instance);
			}
		}
		public static class TravelRules
		{
			public static bool Queue(RaidState state, string id, string target)
			{
				if (!state.Layout.Enabled)
				{
					return false;
				}
				Transfer transfer = state.Transfers.FirstOrDefault((Transfer t) => t.Id == id);
				if (transfer != null && transfer.Target == target)
				{
					return false;
				}
				if (transfer != null)
				{
					state.Transfers.Remove(transfer);
				}
				state.Transfers.Add(new Transfer(id, target));
				state.Revision++;
				return true;
			}

			public static bool Ack(RaidState state, string id, string token)
			{
				Transfer transfer = state.Transfers.FirstOrDefault((Transfer t) => t.Id == id && t.Token == token);
				if (transfer == null)
				{
					return false;
				}
				Member member = state.Members.FirstOrDefault((Member m) => m.Id == id);
				if (transfer.Target == "landing" && member != null && member.Status == MemberStatus.Deploying)
				{
					member.Status = MemberStatus.OnIsland;
				}
				state.Transfers.Remove(transfer);
				state.Revision++;
				return true;
			}

			public static bool Death(RaidState state, string id)
			{
				if (!state.Layout.Enabled)
				{
					return false;
				}
				Member member = state.Members.FirstOrDefault((Member x) => x.Id == id);
				if (member == null || member.Status == MemberStatus.Dead || member.Status == MemberStatus.Extracted || member.Status == MemberStatus.Failed)
				{
					return false;
				}
				member.Status = MemberStatus.Dead;
				state.Revision++;
				Queue(state, id, "hub");
				return true;
			}

			public static void Spawn(RaidState state, string id)
			{
				Member member = state.Members.FirstOrDefault((Member m) => m.Id == id);
				if (member == null || state.Phase == RaidPhase.Waiting || member.Status == MemberStatus.Dead || member.Status == MemberStatus.Extracted || member.Status == MemberStatus.Failed)
				{
					Queue(state, id, "hub");
				}
			}

			public static void Transition(RaidState state, RaidPhase previous, IEnumerable<PresentPlayer> online)
			{
				if (!state.Layout.Enabled)
				{
					return;
				}
				if (state.Phase == RaidPhase.Active && previous == RaidPhase.Preparing)
				{
					foreach (Member item in state.Members.Where((Member m) => m.Status == MemberStatus.Preparing))
					{
						item.Status = MemberStatus.Deploying;
						state.Revision++;
						Queue(state, item.Id, "landing");
					}
				}
				if (state.Phase == RaidPhase.Finished && previous != RaidPhase.Finished)
				{
					Finish(state, previous == RaidPhase.Evacuating, online);
				}
			}

			public static void Finish(RaidState state, bool evacuation, IEnumerable<PresentPlayer> online)
			{
				List<PresentPlayer> source = online.ToList();
				foreach (Member m in state.Members.Where((Member member) => member.Status != MemberStatus.Dead && member.Status != MemberStatus.Extracted && member.Status != MemberStatus.Failed))
				{
					PresentPlayer presentPlayer = source.FirstOrDefault((PresentPlayer p) => p.Id == m.Id);
					m.Status = ((evacuation && m.Status == MemberStatus.OnIsland && presentPlayer != null && presentPlayer.Alive && presentPlayer.Position.Distance(state.Layout.Exit) <= state.Layout.ExitRadius) ? MemberStatus.Extracted : MemberStatus.Failed);
					state.Revision++;
					Queue(state, m.Id, "hub");
				}
			}

			public static void Restore(RaidState state)
			{
				if (!state.Layout.Enabled)
				{
					return;
				}
				if (state.Phase == RaidPhase.Finished)
				{
					Finish(state, evacuation: false, Array.Empty<PresentPlayer>());
				}
				else
				{
					if (state.Phase != RaidPhase.Active && state.Phase != RaidPhase.Evacuating)
					{
						return;
					}
					foreach (Member item in state.Members.Where((Member m) => m.Status == MemberStatus.Preparing))
					{
						item.Status = MemberStatus.Deploying;
						state.Revision++;
						Queue(state, item.Id, "landing");
					}
				}
			}

			public static bool CanEvacuate(RaidState state, string id, PresentPlayer p)
			{
				if (state.Layout.Enabled && state.Phase == RaidPhase.Active && p != null && p.Alive && state.Members.Any((Member m) => m.Id == id && m.Status == MemberStatus.OnIsland))
				{
					return p.Position.Distance(state.Layout.Exit) <= state.Layout.ExitRadius;
				}
				return false;
			}
		}
	}