Decompiled source of SimpleHQ v0.4.4

SimpleHQ.dll

Decompiled 12 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using GameNetcodeStuff;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using SimpleHQ.Core;
using SimpleHQ.Features;
using TMPro;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("AmazingAssets.TerrainToMesh")]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp-firstpass")]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: IgnoresAccessChecksTo("ClientNetworkTransform")]
[assembly: IgnoresAccessChecksTo("com.olegknyazev.softmask")]
[assembly: IgnoresAccessChecksTo("DissonanceVoip")]
[assembly: IgnoresAccessChecksTo("DunGen")]
[assembly: IgnoresAccessChecksTo("DunGen.Integration.ASPP")]
[assembly: IgnoresAccessChecksTo("DunGen.Integration.UnityNav")]
[assembly: IgnoresAccessChecksTo("EasyTextEffects")]
[assembly: IgnoresAccessChecksTo("Facepunch Transport for Netcode for GameObjects")]
[assembly: IgnoresAccessChecksTo("Facepunch.Steamworks.Win64")]
[assembly: IgnoresAccessChecksTo("Unity.AI.Navigation")]
[assembly: IgnoresAccessChecksTo("Unity.Animation.Rigging")]
[assembly: IgnoresAccessChecksTo("Unity.Animation.Rigging.DocCodeExamples")]
[assembly: IgnoresAccessChecksTo("Unity.Burst")]
[assembly: IgnoresAccessChecksTo("Unity.Burst.Unsafe")]
[assembly: IgnoresAccessChecksTo("Unity.Collections")]
[assembly: IgnoresAccessChecksTo("Unity.Collections.LowLevel.ILSupport")]
[assembly: IgnoresAccessChecksTo("Unity.InputSystem")]
[assembly: IgnoresAccessChecksTo("Unity.InputSystem.ForUI")]
[assembly: IgnoresAccessChecksTo("Unity.Jobs")]
[assembly: IgnoresAccessChecksTo("Unity.Mathematics")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.Common")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.MetricTypes")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStats")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Component")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Configuration")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Implementation")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsReporting")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetworkProfiler.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetworkSolutionInterface")]
[assembly: IgnoresAccessChecksTo("Unity.Netcode.Components")]
[assembly: IgnoresAccessChecksTo("Unity.Netcode.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.Networking.Transport")]
[assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Csg")]
[assembly: IgnoresAccessChecksTo("Unity.ProBuilder")]
[assembly: IgnoresAccessChecksTo("Unity.ProBuilder.KdTree")]
[assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Poly2Tri")]
[assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Stl")]
[assembly: IgnoresAccessChecksTo("Unity.Profiling.Core")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.Core.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.Core.ShaderLibrary")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.HighDefinition.Config.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.HighDefinition.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Authentication")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Analytics")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Components")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Configuration")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Device")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Environments")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Environments.Internal")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Internal")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Networking")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Registration")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Scheduler")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Telemetry")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Core.Threading")]
[assembly: IgnoresAccessChecksTo("Unity.Services.QoS")]
[assembly: IgnoresAccessChecksTo("Unity.Services.Relay")]
[assembly: IgnoresAccessChecksTo("Unity.TextMeshPro")]
[assembly: IgnoresAccessChecksTo("Unity.Timeline")]
[assembly: IgnoresAccessChecksTo("Unity.VisualEffectGraph.Runtime")]
[assembly: IgnoresAccessChecksTo("Unity.XR.CoreUtils")]
[assembly: IgnoresAccessChecksTo("Unity.XR.Management")]
[assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR")]
[assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.ConformanceAutomation")]
[assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.MetaQuestSupport")]
[assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.MockRuntime")]
[assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.OculusQuestSupport")]
[assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.RuntimeDebugger")]
[assembly: IgnoresAccessChecksTo("UnityEngine.ARModule")]
[assembly: IgnoresAccessChecksTo("UnityEngine.NVIDIAModule")]
[assembly: IgnoresAccessChecksTo("UnityEngine.SpatialTracking")]
[assembly: IgnoresAccessChecksTo("UnityEngine.UI")]
[assembly: IgnoresAccessChecksTo("UnityEngine.XR.LegacyInputHelpers")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
[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 SimpleHQ
{
	public static class BankInfo
	{
		public static int Total => BankStore.Total;

		public static int Count => BankStore.Items.Count;
	}
	internal static class ChatUtil
	{
		internal static bool BroadcastReplies;

		internal static void Send(string message, bool broadcast = false)
		{
			try
			{
				if (BroadcastReplies)
				{
					broadcast = true;
				}
				HUDManager instance = HUDManager.Instance;
				if (!((Object)(object)instance == (Object)null))
				{
					string text = "<color=#ff6a33>HQ</color>: <color=#e8e8e8>" + message + "</color>";
					if (broadcast)
					{
						instance.AddTextMessageServerRpc(text);
					}
					else
					{
						instance.AddChatMessage(text, "", -1, false);
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[ChatUtil.Send] {arg}");
			}
		}

		internal static void SendChunked(string message, bool broadcast = false, int maxLen = 120)
		{
			if (string.IsNullOrEmpty(message))
			{
				return;
			}
			while (message.Length > maxLen)
			{
				int num = message.LastIndexOf(',', maxLen);
				if (num < maxLen / 2)
				{
					num = message.LastIndexOf(' ', maxLen);
				}
				if (num < maxLen / 2)
				{
					num = maxLen;
				}
				Send(message.Substring(0, num).TrimEnd(), broadcast);
				message = message.Substring(num).TrimStart(' ', ',');
			}
			if (message.Length > 0)
			{
				Send(message, broadcast);
			}
		}
	}
	internal static class CommandRouter
	{
		internal static bool IsOurs(string raw)
		{
			string[] array = Split(raw);
			if (array.Length == 0)
			{
				return false;
			}
			switch (array[0].ToLowerInvariant())
			{
			case "bank":
			case "tournament":
			case "agree":
				return true;
			case "sell":
			case "s":
				if (array.Length >= 2)
				{
					return array[1].ToLowerInvariant() == "bank";
				}
				break;
			}
			return false;
		}

		internal static void Route(string raw, string who, PlayerControllerB requester = null)
		{
			string[] array = Split(raw);
			string text = array[0].ToLowerInvariant();
			if (text == "tournament" || text == "agree")
			{
				RouteTournament(array, text, who, requester);
				return;
			}
			if (TournamentInfo.Active)
			{
				ChatUtil.Send("Tournament mode: the bank is off until the run ends.");
				return;
			}
			if ((!(text == "bank") || array.Length != 1) && (Object)(object)requester != (Object)null && !WhitelistBridge.IsWhitelisted(requester.playerSteamId))
			{
				ChatUtil.Send("Only the host and whitelisted players can use bank commands" + (string.IsNullOrEmpty(who) ? "" : (" (" + who + ")")) + ". /bank shows the total.");
				return;
			}
			if (text != "bank")
			{
				BankService.SellFromBank(Join(array, 2), who);
				return;
			}
			switch ((array.Length >= 2) ? array[1].ToLowerInvariant() : "")
			{
			case "":
				BankService.Status();
				break;
			case "d":
			case "deposit":
				BankService.Deposit(Join(array, 2), who);
				break;
			case "auto":
				BankService.SetAuto(Join(array, 2), who);
				break;
			case "info":
				BankService.Info();
				break;
			case "w":
			case "withdraw":
				BankService.Withdraw(Join(array, 2), who, requester);
				break;
			case "l":
			case "list":
				BankService.List();
				break;
			case "sell":
				ChatUtil.Send("Use /sell bank <amount> | quota | all at the Company.");
				break;
			default:
				ChatUtil.Send("/bank, /bank deposit <amount|all|item [count]>, /bank withdraw <item> [count|all], /bank auto on|off, /bank list, /bank info, /sell bank <amount|quota|all>");
				break;
			}
		}

		private static void RouteTournament(string[] parts, string verb, string who, PlayerControllerB requester)
		{
			if (verb == "agree")
			{
				TournamentService.Agree(who, requester);
				return;
			}
			string text = ((parts.Length >= 2) ? parts[1].ToLowerInvariant() : "");
			bool flag = (Object)(object)requester == (Object)null;
			switch (text)
			{
			case "status":
			case "":
				TournamentService.Status();
				break;
			case "start":
			case "on":
				if (!flag)
				{
					ChatUtil.Send("Only the host can start tournament mode.");
				}
				else
				{
					TournamentService.Start();
				}
				break;
			case "load":
				if (!flag)
				{
					ChatUtil.Send("Only the host can load a tournament kit.");
				}
				else
				{
					TournamentService.Load((parts.Length >= 3) ? parts[2] : "");
				}
				break;
			case "stop":
			case "end":
				if (!flag)
				{
					ChatUtil.Send("Only the host can end tournament mode.");
				}
				else
				{
					TournamentService.End();
				}
				break;
			default:
				ChatUtil.Send("/tournament (status), /tournament start (host), /agree, /tournament load <code> (host), /tournament end (host)");
				break;
			}
		}

		internal static bool IsSenderTheHost(int playerId)
		{
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton == (Object)null || !singleton.IsHost)
			{
				return false;
			}
			if (playerId < 0)
			{
				return true;
			}
			PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController;
			if ((Object)(object)val != (Object)null)
			{
				if (playerId != (int)val.playerClientId)
				{
					return playerId == (int)val.actualClientId;
				}
				return true;
			}
			return false;
		}

		private static string[] Split(string raw)
		{
			return (raw ?? "").Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
		}

		private static string Join(string[] parts, int from)
		{
			if (parts.Length <= from)
			{
				return "";
			}
			return string.Join(" ", parts, from, parts.Length - from);
		}
	}
	internal static class ModConfig
	{
		internal static ConfigEntry<string> CommandPrefix { get; private set; }

		internal static ConfigEntry<string> AltCommandPrefix { get; private set; }

		internal static ConfigEntry<string> KeepOnShip { get; private set; }

		internal static ConfigEntry<bool> IncludeCruiser { get; private set; }

		internal static ConfigEntry<bool> ShowBankOnTerminal { get; private set; }

		internal static ConfigEntry<bool> PrivateLobbiesOnly { get; private set; }

		internal static ConfigEntry<bool> AutoDeposit { get; private set; }

		internal static void Init(ConfigFile file)
		{
			CommandPrefix = file.Bind<string>("General", "CommandPrefix", "/", "Prefix for chat commands (e.g. /bank).");
			AltCommandPrefix = file.Bind<string>("General", "AltCommandPrefix", "!", "Second prefix that always works, e.g. !bank. Use it if /bank says 'unknown command': another chat mod is taking every '/' message first. Leave empty to disable.");
			KeepOnShip = file.Bind<string>("Bank", "KeepOnShip", "Shotgun, Kitchen knife", "Scrap that /bank deposit never takes, comma separated item names. Weapons you still want to use.");
			IncludeCruiser = file.Bind<bool>("Bank", "IncludeCruiser", true, "Also deposit scrap sitting in the Company Cruiser, like /sell does.");
			AutoDeposit = file.Bind<bool>("Bank", "AutoDeposit", false, "Deposit all scrap on the ship into the bank at the end of each day, just before the game saves in orbit. Same items as /bank deposit all. Toggle in-game: /bank auto on|off.");
			ShowBankOnTerminal = file.Bind<bool>("Bank", "ShowBankOnTerminal", true, "Show the bank's value beside the credits at the top right of the terminal.");
			PrivateLobbiesOnly = file.Bind<bool>("Bank", "PrivateLobbiesOnly", true, "The bank only works while the lobby is not public. Banked scrap is kept, just locked, while public.");
		}

		internal static bool IsKeptOnShip(string itemName)
		{
			if (string.IsNullOrEmpty(itemName))
			{
				return false;
			}
			IEnumerable<string> source = from s in (KeepOnShip.Value ?? "").Split(',')
				select s.Trim() into s
				where s.Length > 0
				select s;
			return source.Any((string n) => string.Equals(n, itemName, StringComparison.OrdinalIgnoreCase));
		}
	}
	[BepInPlugin("local.simplehq", "SimpleHQ", "0.4.4")]
	public class Plugin : BaseUnityPlugin
	{
		private readonly Harmony _harmony = new Harmony("local.simplehq");

		public static Plugin Instance { get; private set; }

		internal static ManualLogSource Logger { get; private set; }

		private void Awake()
		{
			Instance = this;
			Logger = ((BaseUnityPlugin)this).Logger;
			ModConfig.Init(((BaseUnityPlugin)this).Config);
			_harmony.PatchAll();
			Logger.LogInfo((object)"SimpleHQ v0.4.4 loaded.");
		}
	}
	public static class PluginInfo
	{
		public const string Guid = "local.simplehq";

		public const string Name = "SimpleHQ";

		public const string Version = "0.4.4";
	}
	public static class TournamentInfo
	{
		public static bool Active { get; internal set; }

		public static string KitName { get; internal set; }
	}
}
namespace SimpleHQ.Patches
{
	[HarmonyPatch(typeof(StartOfRound), "Start")]
	internal static class SessionStartPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			try
			{
				BankNet.Reset();
				BankNet.Register();
				TournamentService.Reset();
				TournamentNet.Register();
				ItemPlaceNet.Register();
				NetworkManager singleton = NetworkManager.Singleton;
				if ((Object)(object)singleton != (Object)null && singleton.IsHost)
				{
					BankStore.LoadForCurrentSave();
					BankNet.Publish();
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[SessionStartPatch] {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "OnPlayerConnectedClientRpc")]
	internal static class PlayerJoinedPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			try
			{
				BankNet.Publish();
				TournamentService.OnPlayersChanged();
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[PlayerJoinedPatch] {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(GameNetworkManager), "SaveGameValues")]
	internal static class SaveCommitPatch
	{
		[HarmonyPostfix]
		private static void Postfix(GameNetworkManager __instance)
		{
			try
			{
				StartOfRound instance = StartOfRound.Instance;
				if (__instance.isHostingGame && !((Object)(object)instance == (Object)null) && !instance.isChallengeFile && instance.inShipPhase)
				{
					BankStore.Commit();
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[SaveCommitPatch] {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "SetShipReadyToLand")]
	internal static class AutoDepositPatch
	{
		[HarmonyPrefix]
		private static void Prefix()
		{
			BankService.AutoDepositEndOfDay();
		}

		[HarmonyPostfix]
		private static void Postfix()
		{
			TournamentRun.OnShipInOrbit();
		}
	}
	[HarmonyPatch(typeof(RoundManager), "DespawnPropsAtEndOfRound")]
	internal static class WipePatch
	{
		[HarmonyPrefix]
		private static void Prefix(bool despawnAllItems)
		{
			TournamentRun.NoteWipe();
			BankService.OnPropsDespawning(despawnAllItems);
		}
	}
	[HarmonyPatch(typeof(GameNetworkManager), "ResetSavedGameValues")]
	internal static class FiredPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			TournamentRun.OnFired();
			BankService.OnFired();
		}
	}
	internal static class ChatPrefix
	{
		internal static string Strip(string chatMessage)
		{
			if (string.IsNullOrEmpty(chatMessage))
			{
				return null;
			}
			string value = ModConfig.CommandPrefix.Value;
			string value2 = ModConfig.AltCommandPrefix.Value;
			if (!string.IsNullOrEmpty(value) && chatMessage.StartsWith(value))
			{
				return chatMessage.Substring(value.Length).Trim();
			}
			if (!string.IsNullOrEmpty(value2) && chatMessage.StartsWith(value2))
			{
				return chatMessage.Substring(value2.Length).Trim();
			}
			return null;
		}
	}
	[HarmonyPatch(typeof(HUDManager), "AddTextToChatOnServer")]
	internal static class HostChatPatch
	{
		[HarmonyPrefix]
		[HarmonyPriority(800)]
		private static bool Prefix(string chatMessage, int playerId)
		{
			try
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if ((Object)(object)singleton == (Object)null || !singleton.IsHost)
				{
					return true;
				}
				string text = ChatPrefix.Strip(chatMessage);
				if (text == null || !CommandRouter.IsOurs(text))
				{
					return true;
				}
				if (!CommandRouter.IsSenderTheHost(playerId))
				{
					return true;
				}
				CommandRouter.Route(text, null);
				return false;
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[HostChatPatch] {arg}");
				return true;
			}
		}
	}
	[HarmonyPatch(typeof(HUDManager), "AddPlayerChatMessageServerRpc")]
	internal static class RemoteChatPatch
	{
		[HarmonyPrefix]
		[HarmonyPriority(800)]
		private static bool Prefix(string chatMessage, int playerId)
		{
			try
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if ((Object)(object)singleton == (Object)null || !singleton.IsHost)
				{
					return true;
				}
				string text = ChatPrefix.Strip(chatMessage);
				if (text == null || !CommandRouter.IsOurs(text))
				{
					return true;
				}
				if (CommandRouter.IsSenderTheHost(playerId))
				{
					return true;
				}
				StartOfRound instance = StartOfRound.Instance;
				if (instance?.allPlayerScripts == null || playerId < 0 || playerId >= instance.allPlayerScripts.Length)
				{
					return true;
				}
				PlayerControllerB val = instance.allPlayerScripts[playerId];
				if ((Object)(object)val == (Object)null || !val.isPlayerControlled)
				{
					return true;
				}
				ChatUtil.BroadcastReplies = true;
				try
				{
					CommandRouter.Route(text, val.playerUsername, val);
				}
				finally
				{
					ChatUtil.BroadcastReplies = false;
				}
				return false;
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[RemoteChatPatch] {arg}");
				return true;
			}
		}
	}
	[HarmonyPatch(typeof(PlayerControllerB))]
	internal static class GrabBeforeStartPatch
	{
		private static bool Hide()
		{
			try
			{
				if (!TournamentNet.ActiveHere)
				{
					return false;
				}
				GameNetworkManager instance = GameNetworkManager.Instance;
				if ((Object)(object)instance == (Object)null || instance.gameHasStarted)
				{
					return false;
				}
				instance.gameHasStarted = true;
				return true;
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[GrabBeforeStartPatch.Hide] {arg}");
				return false;
			}
		}

		private static void Restore(bool hidden)
		{
			if (hidden && !((Object)(object)GameNetworkManager.Instance == (Object)null))
			{
				GameNetworkManager.Instance.gameHasStarted = false;
			}
		}

		[HarmonyPatch("BeginGrabObject")]
		[HarmonyPrefix]
		[HarmonyPriority(800)]
		private static void GrabPrefix(out bool __state)
		{
			__state = Hide();
		}

		[HarmonyPatch("BeginGrabObject")]
		[HarmonyFinalizer]
		private static void GrabFinalizer(bool __state)
		{
			Restore(__state);
		}

		[HarmonyPatch("SetHoverTipAndCurrentInteractTrigger")]
		[HarmonyPrefix]
		[HarmonyPriority(800)]
		private static void TipPrefix(out bool __state)
		{
			__state = Hide();
		}

		[HarmonyPatch("SetHoverTipAndCurrentInteractTrigger")]
		[HarmonyFinalizer]
		private static void TipFinalizer(bool __state)
		{
			Restore(__state);
		}
	}
	[HarmonyPatch(typeof(VehicleController), "Start")]
	internal static class MagnetStatePatch
	{
		[HarmonyPostfix]
		private static void Postfix(VehicleController __instance)
		{
			try
			{
				StartOfRound instance = StartOfRound.Instance;
				if (!((Object)(object)instance == (Object)null) && TournamentNet.ActiveHere && instance.inShipPhase && __instance.magnetedToShip)
				{
					instance.isObjectAttachedToMagnet = true;
					instance.attachedVehicle = __instance;
					instance.magnetOn = true;
					if ((Object)(object)instance.magnetLever != (Object)null)
					{
						instance.magnetLever.initialBoolState = true;
						instance.magnetLever.setInitialState = true;
						instance.magnetLever.SetInitialState();
					}
					Plugin.Logger.LogInfo((object)"[Tournament] kit cruiser on the magnet; magnet switched on.");
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[MagnetStatePatch] {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(Terminal), "Update")]
	internal static class TerminalCreditsPatch
	{
		[HarmonyPostfix]
		private static void Postfix(Terminal __instance)
		{
			try
			{
				if (ModConfig.ShowBankOnTerminal.Value && __instance.terminalInUse && !((Object)(object)__instance.topRightText == (Object)null) && BankNet.Received && BankNet.Count != 0)
				{
					if (((TMP_Text)__instance.topRightText).enableWordWrapping)
					{
						((TMP_Text)__instance.topRightText).enableWordWrapping = false;
					}
					((TMP_Text)__instance.topRightText).text = $"${__instance.groupCredits} <size=55%><color=#ff6a33>${BankNet.Total}</color></size>";
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[TerminalCreditsPatch] {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(Terminal), "ParsePlayerSentence")]
	internal static class TerminalBankPatch
	{
		private static TerminalNode _node;

		private static bool _awaitingCode;

		private static string _codeSoFar = "";

		private static int _savedCharacterLimit = -1;

		private static bool BuilderActive
		{
			get
			{
				if (KitBuilder.Current != KitBuilder.Step.Off)
				{
					return KitBuilder.Current != KitBuilder.Step.Done;
				}
				return false;
			}
		}

		[HarmonyPrefix]
		private static bool Prefix(Terminal __instance, ref TerminalNode __result)
		{
			try
			{
				string text = __instance.screenText.text;
				int num = Mathf.Clamp(__instance.textAdded, 0, text.Length);
				string text2 = text.Substring(text.Length - num).Trim();
				string text3 = __instance.RemovePunctuation(text2).Trim().ToLowerInvariant();
				switch (text3)
				{
				case "tournament builder":
				case "tournament build":
				case "builder":
					if (TournamentNet.ActiveHere)
					{
						return ShowPage(__instance, "\n\n\nTOURNAMENT BUILDER\n\nNot during a run: build codes between tournaments.\n\n", ref __result);
					}
					SetTypingRoom(__instance, roomy: true);
					return ShowPage(__instance, "\n\n\n" + KitBuilder.Start() + "\n\n", ref __result);
				default:
				{
					if (BuilderActive)
					{
						if (text3 == "cancel")
						{
							KitBuilder.Cancel();
							SetTypingRoom(__instance, roomy: false);
							return ShowPage(__instance, "\n\n\nTOURNAMENT BUILDER\n\nCancelled.\n\n", ref __result);
						}
						string text4 = KitBuilder.Answer(text2.Trim());
						if (!BuilderActive)
						{
							SetTypingRoom(__instance, roomy: false);
						}
						return ShowPage(__instance, "\n\n\n" + text4 + "\n\n", ref __result);
					}
					if (_awaitingCode)
					{
						string text5;
						if (text3 == "cancel")
						{
							_awaitingCode = false;
							_codeSoFar = "";
							SetTypingRoom(__instance, roomy: false);
							text5 = "\n\n\nTOURNAMENT\n\nCancelled.\n\n";
						}
						else if (text3.Length == 0)
						{
							text5 = "\n\n\nTOURNAMENT\n\nType the code (or the rest of it), or cancel.\n\n";
						}
						else
						{
							_codeSoFar += text2.Trim();
							if (TournamentService.CanDecode(_codeSoFar))
							{
								_awaitingCode = false;
								SetTypingRoom(__instance, roomy: false);
								string codeSoFar = _codeSoFar;
								_codeSoFar = "";
								Kit activeKit = TournamentService.ActiveKit;
								TournamentService.Load(codeSoFar);
								bool flag = TournamentService.ActiveKit != null && TournamentService.ActiveKit != activeKit;
								text5 = "\n\n\nTOURNAMENT\n\n" + (flag ? ("Kit loaded: " + TournamentService.ActiveKit.name + "\nRouting to " + TournamentService.ActiveKit.days[0].moon + ". Pull the lever when ready.\n\n") : "That kit can't be used here. Chat says why.\nType tournament to try again.\n\n");
							}
							else if (_codeSoFar.Length > 300)
							{
								_awaitingCode = false;
								SetTypingRoom(__instance, roomy: false);
								_codeSoFar = "";
								text5 = "\n\n\nTOURNAMENT\n\nThat code doesn't check out.\nType tournament to start again.\n\n";
							}
							else
							{
								text5 = $"\n\n\nTOURNAMENT\n\nCode so far: {Strip(_codeSoFar).Length} characters.\n" + "Type the rest and press Enter (cancel to stop).\n\n";
							}
						}
						return ShowPage(__instance, text5, ref __result);
					}
					bool flag2 = text3 == "bank" || text3.StartsWith("bank ");
					bool flag3 = text3 == "sell" || text3.StartsWith("sell ");
					bool flag4 = text3 == "tournament" || text3.StartsWith("tournament ") || text3 == "agree";
					if (!flag2 && !flag3 && !flag4)
					{
						return true;
					}
					if (!flag4 && TournamentNet.ActiveHere)
					{
						return true;
					}
					string displayText;
					if (text3 == "tournament" && (Object)(object)NetworkManager.Singleton != (Object)null && NetworkManager.Singleton.IsHost)
					{
						if (TournamentInfo.Active)
						{
							_awaitingCode = true;
							_codeSoFar = "";
							SetTypingRoom(__instance, roomy: true);
							displayText = "\n\n\nTOURNAMENT\n\n" + TournamentService.TerminalStatus() + "\n\nType the organisers' kit code and press Enter.\nDashes and case don't matter.\n(Type cancel to back out.)\n\n";
						}
						else if (TournamentService.Proposed)
						{
							displayText = "\n\n\nTOURNAMENT\n\n" + TournamentService.TerminalStatus() + "\n\n";
						}
						else
						{
							TournamentService.Start();
							displayText = "\n\n\nTOURNAMENT\n\n" + TournamentService.TerminalStatus() + "\n\nEveryone types /agree in chat. Then the ship\nwaits at the Company: type tournament again\nhere to enter the code.\n\n(No code yet? Type tournament builder\nto make one.)\n\n";
						}
					}
					else if (flag4)
					{
						string[] array = text2.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
						string command = ((array.Length >= 3 && array[1].ToLowerInvariant() == "load") ? ("tournament load " + array[2]) : text3);
						displayText = (SendToHost(command) ? "\n\n\nTOURNAMENT\n\nSent. The reply shows in chat.\n\n" : "\n\n\nTOURNAMENT\n\nCouldn't reach the host.\n\n");
					}
					else if (flag2)
					{
						displayText = ((text3 == "bank") ? BuildText() : RunCommand(text3));
					}
					else
					{
						string text6 = ((text3.Length > 4) ? text3.Substring(5).Trim() : "");
						if (text6 == "bank" || text6.StartsWith("bank "))
						{
							displayText = RunCommand("bank sell " + text6.Substring(4).Trim());
						}
						else
						{
							if (!BankNet.HostHasSimpleSell)
							{
								return true;
							}
							displayText = RunSell(text6);
						}
					}
					if ((Object)(object)_node == (Object)null)
					{
						_node = ScriptableObject.CreateInstance<TerminalNode>();
						_node.clearPreviousText = true;
					}
					_node.maxCharactersToType = ((_awaitingCode || BuilderActive) ? 250 : 40);
					_node.displayText = displayText;
					__result = _node;
					return false;
				}
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[TerminalBankPatch] {arg}");
				return true;
			}
		}

		private static string Strip(string s)
		{
			return new string(s.Where(char.IsLetterOrDigit).ToArray());
		}

		private static void SetTypingRoom(Terminal terminal, bool roomy)
		{
			try
			{
				if ((Object)(object)terminal?.screenText == (Object)null)
				{
					return;
				}
				if (roomy)
				{
					if (_savedCharacterLimit < 0)
					{
						_savedCharacterLimit = terminal.screenText.characterLimit;
					}
					terminal.screenText.characterLimit = 0;
				}
				else if (_savedCharacterLimit >= 0)
				{
					terminal.screenText.characterLimit = _savedCharacterLimit;
					_savedCharacterLimit = -1;
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("[TerminalBankPatch] typing room: " + ex.Message));
			}
		}

		private static bool ShowPage(Terminal terminal, string text, ref TerminalNode result)
		{
			if ((Object)(object)_node == (Object)null)
			{
				_node = ScriptableObject.CreateInstance<TerminalNode>();
				_node.clearPreviousText = true;
			}
			_node.maxCharactersToType = ((_awaitingCode || BuilderActive) ? 250 : 40);
			_node.displayText = text;
			result = _node;
			return false;
		}

		private static string RunCommand(string typed)
		{
			string text = typed.Substring("bank ".Length).Trim();
			string command;
			string text3;
			if (text.StartsWith("deposit ") || text.StartsWith("d "))
			{
				string text2 = text.Substring(text.IndexOf(' ') + 1).Trim();
				command = "bank deposit " + text2;
				text3 = "Depositing " + text2 + ".";
			}
			else if (text.StartsWith("withdraw ") || text.StartsWith("w "))
			{
				string text4 = text.Substring(text.IndexOf(' ') + 1).Trim();
				command = "bank withdraw " + text4;
				text3 = "Withdrawing " + text4 + ".";
			}
			else if (text == "auto" || text.StartsWith("auto "))
			{
				command = "bank " + text;
				text3 = ((text == "auto") ? "Checking auto deposit." : ("Turning auto deposit " + text.Substring(5).Trim() + "."));
			}
			else
			{
				switch (text)
				{
				case "info":
					command = "bank info";
					text3 = "Telling everyone how the bank works.";
					break;
				case "list":
				case "l":
					command = "bank list";
					text3 = "Listing the bank in chat.";
					break;
				default:
					if (text.StartsWith("sell "))
					{
						string text5 = text.Substring("sell ".Length).Trim();
						command = "sell bank " + text5;
						text3 = "Selling " + text5 + " from the bank.";
						break;
					}
					return "\n\n\nSCRAP BANK\n\nbank                          show the bank\nbank deposit <amount|all>     store ship scrap\nbank deposit <item> [count]\nbank withdraw <item> [count|all]\nbank auto on|off              deposit at end of day\nbank list                     list it in chat\nbank info                     explain the bank to everyone\nbank sell <amount|quota|all>  at the Company\n\n";
				}
			}
			if (!SendToHost(command))
			{
				return "\n\n\nSCRAP BANK\n\nCouldn't reach the host.\n\n";
			}
			return "\n\n\nSCRAP BANK\n\n" + text3 + "\nThe result shows in chat. Type bank to see the bank.\n\n";
		}

		private static string RunSell(string rest)
		{
			NetworkManager singleton = NetworkManager.Singleton;
			PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController;
			if ((Object)(object)singleton == (Object)null || (Object)(object)val == (Object)null || (Object)(object)HUDManager.Instance == (Object)null)
			{
				return "\n\n\nSELL\n\nCouldn't reach the host.\n\n";
			}
			if (singleton.IsHost)
			{
				HUDManager.Instance.AddTextToChatOnServer(("/sell " + rest).Trim(), (int)val.playerClientId);
				if (rest.Length != 0)
				{
					return "\n\n\nSELL\n\nSelling " + rest + ".\nThe result shows in chat.\n\n";
				}
				return "\n\n\nSELL\n\nsell <amount>, sell quota, sell all, sell <item> [count]\nsell bank <amount|quota|all>\n\n";
			}
			HUDManager.Instance.AddTextToChatOnServer(("!sell " + rest).Trim(), (int)val.playerClientId);
			return "\n\n\nSELL\n\nSent \"sell " + rest + "\" to the host.\nIt only runs if the host lets you sell\n(SimpleSell whitelist). The result shows in chat.\n\n";
		}

		private static bool SendToHost(string command)
		{
			NetworkManager singleton = NetworkManager.Singleton;
			PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController;
			if ((Object)(object)singleton == (Object)null || (Object)(object)val == (Object)null)
			{
				return false;
			}
			if (singleton.IsHost)
			{
				CommandRouter.Route(command, null);
				return true;
			}
			string text = ((!string.IsNullOrEmpty(ModConfig.AltCommandPrefix.Value)) ? ModConfig.AltCommandPrefix.Value : ModConfig.CommandPrefix.Value);
			HUDManager instance = HUDManager.Instance;
			if (instance != null)
			{
				instance.AddTextToChatOnServer(text + command, (int)val.playerClientId);
			}
			return true;
		}

		private static string BuildText()
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("\n\n\nSCRAP BANK\n\n");
			if (!BankNet.Received)
			{
				stringBuilder.Append("No bank from the host. The host needs SimpleHQ installed.\n\n");
				return stringBuilder.ToString();
			}
			if (BankNet.Locked)
			{
				stringBuilder.Append("LOCKED: the lobby is public. Host as Friends only,\nor /lobby friends with SimpleSell.\n\n");
			}
			stringBuilder.Append($"Total value: ${BankNet.Total}\n");
			stringBuilder.Append($"Items: {BankNet.Count}\n\n");
			foreach (BankGroup group in BankNet.Groups)
			{
				stringBuilder.Append($"* {group.Name} x{group.Count}   ${group.Value}\n");
			}
			stringBuilder.Append("\nDeposit from the ship: bank deposit <amount|all>\n");
			stringBuilder.Append("Sell at the Company: bank sell <amount|quota|all>\n");
			stringBuilder.Append("(also /bank deposit and /sell bank in chat)\n\n");
			return stringBuilder.ToString();
		}
	}
	[HarmonyPatch(typeof(GameNetworkManager), "ConnectionApproval")]
	internal static class TournamentApprovalPatch
	{
		[HarmonyPostfix]
		private static void Postfix(ConnectionApprovalRequest request, ConnectionApprovalResponse response)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (response != null && response.Approved && TournamentInfo.Active)
				{
					ulong clientNetworkId = request.ClientNetworkId;
					NetworkManager singleton = NetworkManager.Singleton;
					if (clientNetworkId != ((singleton != null) ? new ulong?(singleton.LocalClientId) : ((ulong?)null)))
					{
						response.Approved = false;
						response.Reason = "A tournament run is in progress.";
						Plugin.Logger.LogInfo((object)"[Tournament] refused a join: run in progress.");
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[TournamentApprovalPatch] {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(GameNetworkManager), "Singleton_OnClientDisconnectCallback")]
	internal static class TournamentDisconnectPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			try
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if ((Object)(object)singleton != (Object)null && singleton.IsHost)
				{
					TournamentService.OnPlayersChanged();
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[TournamentDisconnectPatch] {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(Terminal), "ParsePlayerSentence")]
	internal static class TournamentRouteLockPatch
	{
		private static TerminalNode _node;

		[HarmonyPostfix]
		private static void Postfix(ref TerminalNode __result)
		{
			try
			{
				int lockedLevelId = TournamentApply.LockedLevelId;
				if ((Object)(object)__result == (Object)null || lockedLevelId < 0 || !TournamentNet.ActiveHere)
				{
					return;
				}
				int num = ((__result.buyRerouteToMoon >= 0) ? __result.buyRerouteToMoon : ((__result.buyRerouteToMoon == -2) ? __result.displayPlanetInfo : (-1)));
				if (num >= 0)
				{
					if ((Object)(object)_node == (Object)null)
					{
						_node = ScriptableObject.CreateInstance<TerminalNode>();
						_node.clearPreviousText = true;
						_node.maxCharactersToType = 25;
					}
					SelectableLevel[] array = StartOfRound.Instance?.levels;
					string text = ((array != null && lockedLevelId < array.Length) ? TournamentApply.ShortName(array[lockedLevelId].PlanetName) : "the kit's moon");
					_node.displayText = ((num == lockedLevelId) ? ("\n\n\nTOURNAMENT\n\nThe ship is already routed to " + text + ",\nfree. No need to route.\n\n") : ("\n\n\nTOURNAMENT\n\nRouting is locked to " + text + " for this run.\n\n"));
					__result = _node;
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[TournamentRouteLockPatch] {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(StartMatchLever), "LeverAnimation")]
	internal static class TournamentLeverLockPatch
	{
		internal const string Tip = "[ Waiting for the tournament code ]";

		internal static bool Locked(StartMatchLever lever)
		{
			if (TournamentApply.LeverLocked && TournamentNet.ActiveHere && (Object)(object)lever?.playersManager != (Object)null)
			{
				return lever.playersManager.inShipPhase;
			}
			return false;
		}

		[HarmonyPrefix]
		private static bool Prefix(StartMatchLever __instance)
		{
			try
			{
				if (!Locked(__instance))
				{
					return true;
				}
				HUDManager instance = HUDManager.Instance;
				if (instance != null)
				{
					instance.DisplayTip("Tournament", "The ship waits at the Company until the host enters the tournament code.", false, false, "LC_Tip1");
				}
				return false;
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[TournamentLeverLockPatch] {arg}");
				return true;
			}
		}
	}
	[HarmonyPatch(typeof(StartMatchLever), "Update")]
	internal static class TournamentLeverTipPatch
	{
		[HarmonyPostfix]
		private static void Postfix(StartMatchLever __instance)
		{
			if ((Object)(object)__instance.triggerScript != (Object)null && TournamentLeverLockPatch.Locked(__instance))
			{
				__instance.triggerScript.hoverTip = "[ Waiting for the tournament code ]";
			}
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "StartGame")]
	internal static class TournamentStartGameGuardPatch
	{
		[HarmonyPrefix]
		private static bool Prefix(StartOfRound __instance)
		{
			try
			{
				if (!TournamentInfo.Active || !TournamentApply.LeverLocked || !__instance.inShipPhase)
				{
					return true;
				}
				StartMatchLever obj = Object.FindObjectOfType<StartMatchLever>();
				if (obj != null)
				{
					obj.CancelStartGameClientRpc();
				}
				Plugin.Logger.LogInfo((object)"[Tournament] landing refused: waiting for the code.");
				return false;
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[TournamentStartGameGuardPatch] {arg}");
				return true;
			}
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "PassTimeToNextDay")]
	internal static class TournamentDayEndPatch
	{
		[HarmonyPrefix]
		private static void Prefix()
		{
			TournamentRun.OnDayEnding();
		}

		[HarmonyPostfix]
		private static void Postfix()
		{
			TournamentRun.OnDayPassed();
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "StartGame")]
	internal static class TournamentTimerPatch
	{
		[HarmonyPostfix]
		private static void Postfix(StartOfRound __instance)
		{
			if (__instance.beganLoadingNewLevel)
			{
				TournamentRun.OnLanding();
			}
		}
	}
	[HarmonyPatch(typeof(PlayerControllerB), "KillPlayerClientRpc")]
	internal static class TournamentDeathPatch
	{
		[HarmonyPostfix]
		private static void Postfix(int playerId)
		{
			try
			{
				StartOfRound instance = StartOfRound.Instance;
				string who = ((!((Object)(object)instance != (Object)null) || playerId < 0 || playerId >= instance.allPlayerScripts.Length) ? null : instance.allPlayerScripts[playerId]?.playerUsername);
				TournamentRun.OnPlayerDied(who);
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[TournamentDeathPatch] {arg}");
			}
		}
	}
}
namespace SimpleHQ.Features
{
	internal static class BankService
	{
		internal static void Deposit(string arg, string who)
		{
			try
			{
				if (!Ready(out var why))
				{
					ChatUtil.Send(why);
					return;
				}
				if (!BankGate.CanMoveScrap(out why))
				{
					ChatUtil.Send(why);
					return;
				}
				if (string.IsNullOrWhiteSpace(arg))
				{
					ChatUtil.Send("Usage: /bank deposit <amount> | all | <item> [count|all]");
					return;
				}
				List<GrabbableObject> list = FindDepositable();
				if (list.Count == 0)
				{
					ChatUtil.Send("No scrap on the ship to deposit.");
					return;
				}
				string text = arg.Trim().ToLowerInvariant();
				List<GrabbableObject> list2;
				int result;
				if (text == "all")
				{
					list2 = list;
				}
				else if (int.TryParse(text.TrimStart('$'), out result))
				{
					if (result <= 0)
					{
						ChatUtil.Send("Amount must be above 0.");
						return;
					}
					int num = list.Sum((GrabbableObject o) => o.scrapValue);
					if (num < result)
					{
						ChatUtil.Send($"Only ${num} of scrap on the ship, not ${result}.");
						return;
					}
					list2 = ((num == result) ? list : BankSelect.ClosestAtOrAbove(list, (GrabbableObject o) => o.scrapValue, result));
				}
				else
				{
					list2 = MatchByName(list, arg, out var problem);
					if (list2 == null)
					{
						ChatUtil.Send(problem);
						return;
					}
				}
				List<BankEntry> list3 = Bank(list2);
				if (list3.Count == 0)
				{
					ChatUtil.Send("Nothing could be deposited.");
					return;
				}
				int num2 = list3.Sum((BankEntry e) => e.Value);
				string text2 = (string.IsNullOrEmpty(who) ? "Deposited" : (who + " deposited"));
				ChatUtil.Send($"{text2} {list3.Count} item(s), ${num2}. Bank: {BankStore.Items.Count} item(s), ${BankStore.Total}.", broadcast: true);
				Plugin.Logger.LogInfo((object)string.Format("[Bank] {0} deposited {1} item(s) ${2} ({3})", who ?? "host", list3.Count, num2, arg));
			}
			catch (Exception arg2)
			{
				Plugin.Logger.LogError((object)$"[BankService.Deposit] {arg2}");
			}
		}

		private static List<BankEntry> Bank(List<GrabbableObject> chosen)
		{
			List<BankEntry> list = new List<BankEntry>();
			foreach (GrabbableObject item in chosen)
			{
				NetworkObject component = ((Component)item).GetComponent<NetworkObject>();
				if (!((Object)(object)component == (Object)null) && component.IsSpawned)
				{
					list.Add(new BankEntry
					{
						Name = item.itemProperties.itemName,
						Value = item.scrapValue,
						ItemId = ItemSpawner.IdOf(item.itemProperties),
						Data = ItemSpawner.SaveDataOf(item)
					});
					component.Despawn(true);
				}
			}
			if (list.Count > 0)
			{
				BankStore.Add(list);
				BankNet.Publish();
			}
			return list;
		}

		internal static void SetAuto(string arg, string who)
		{
			string text = (arg ?? "").Trim().ToLowerInvariant();
			if (text == "on" || text == "off")
			{
				ModConfig.AutoDeposit.Value = text == "on";
				string text2 = (string.IsNullOrEmpty(who) ? "" : (" (" + who + ")"));
				ChatUtil.Send(ModConfig.AutoDeposit.Value ? ("Auto deposit ON" + text2 + ": scrap on the ship goes into the bank at the end of each day.") : ("Auto deposit OFF" + text2 + "."), broadcast: true);
			}
			else
			{
				ChatUtil.Send("Auto deposit is " + (ModConfig.AutoDeposit.Value ? "ON" : "OFF") + ". /bank auto on|off");
			}
		}

		internal static void AutoDepositEndOfDay()
		{
			try
			{
				if (!ModConfig.AutoDeposit.Value || TournamentInfo.Active)
				{
					return;
				}
				NetworkManager singleton = NetworkManager.Singleton;
				if ((Object)(object)singleton == (Object)null || !singleton.IsHost)
				{
					return;
				}
				if (BankGate.IsLocked(out var reason))
				{
					Plugin.Logger.LogInfo((object)("[Bank] auto deposit skipped: " + reason));
					return;
				}
				List<GrabbableObject> list = FindDepositable();
				if (list.Count == 0)
				{
					return;
				}
				List<BankEntry> list2 = Bank(list);
				if (list2.Count != 0)
				{
					int num = list2.Sum((BankEntry e) => e.Value);
					ChatUtil.Send($"End of day: deposited {list2.Count} item(s), ${num}. Bank: {BankStore.Items.Count} item(s), ${BankStore.Total}.", broadcast: true);
					Plugin.Logger.LogInfo((object)$"[Bank] auto deposit: {list2.Count} item(s) ${num}");
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[BankService.AutoDepositEndOfDay] {arg}");
			}
		}

		private static List<GrabbableObject> FindDepositable()
		{
			VehicleController vehicle = Object.FindObjectOfType<VehicleController>();
			HashSet<GrabbableObject> inBags = new HashSet<GrabbableObject>();
			BeltBagItem[] array = Object.FindObjectsByType<BeltBagItem>((FindObjectsSortMode)0);
			foreach (BeltBagItem val in array)
			{
				if (val.objectsInBag == null)
				{
					continue;
				}
				foreach (GrabbableObject item in val.objectsInBag)
				{
					if ((Object)(object)item != (Object)null)
					{
						inBags.Add(item);
					}
				}
			}
			return (from o in Object.FindObjectsByType<GrabbableObject>((FindObjectsSortMode)0)
				where (Object)(object)o != (Object)null && (Object)(object)o.itemProperties != (Object)null && o.itemProperties.isScrap
				where o.scrapValue > 0
				where !o.isHeld && !o.heldByPlayerOnServer && !o.isPocketed && !o.deactivated
				where !(o is RagdollGrabbableObject) && !(o is BeltBagItem) && !inBags.Contains(o)
				where !ModConfig.IsKeptOnShip(o.itemProperties.itemName)
				where o.isInShipRoom || o.isInElevator || (ModConfig.IncludeCruiser.Value && (Object)(object)vehicle != (Object)null && ((Component)o).transform.IsChildOf(((Component)vehicle).transform))
				select o).ToList();
		}

		private static List<GrabbableObject> MatchByName(List<GrabbableObject> eligible, string arg, out string problem)
		{
			problem = null;
			string[] array = arg.Trim().Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
			int count = int.MaxValue;
			string text = arg.Trim();
			if (array.Length >= 2)
			{
				string text2 = array[^1].ToLowerInvariant();
				int result;
				if (text2 == "all")
				{
					text = string.Join(" ", array, 0, array.Length - 1);
				}
				else if (int.TryParse(text2, out result) && result > 0)
				{
					count = result;
					text = string.Join(" ", array, 0, array.Length - 1);
				}
			}
			string norm = Normalize(text);
			List<GrabbableObject> list = (from o in eligible
				where Normalize(o.itemProperties.itemName).Contains(norm)
				orderby o.scrapValue
				select o).Take(count).ToList();
			if (list.Count == 0)
			{
				problem = (ModConfig.IsKeptOnShip(text) ? (text + " is on the KeepOnShip list and is never deposited.") : ("No scrap on the ship matching \"" + text + "\"."));
				return null;
			}
			return list;
		}

		private static string Normalize(string s)
		{
			return (s ?? "").Replace(" ", "").ToLowerInvariant();
		}

		internal static void Withdraw(string arg, string who, PlayerControllerB requester)
		{
			try
			{
				if (!Ready(out var why))
				{
					ChatUtil.Send(why);
					return;
				}
				if (!BankGate.CanMoveScrap(out why))
				{
					ChatUtil.Send(why.Replace("only takes deposits", "only hands scrap back"));
					return;
				}
				if (string.IsNullOrWhiteSpace(arg))
				{
					ChatUtil.Send("Usage: /bank withdraw <item> [count|all]");
					return;
				}
				if (BankStore.Items.Count == 0)
				{
					ChatUtil.Send("The bank is empty.");
					return;
				}
				string[] array = arg.Trim().Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
				int count = 1;
				string text = arg.Trim();
				if (array.Length >= 2)
				{
					string text2 = array[^1].ToLowerInvariant();
					int result;
					if (text2 == "all")
					{
						count = int.MaxValue;
						text = string.Join(" ", array, 0, array.Length - 1);
					}
					else if (int.TryParse(text2, out result) && result > 0)
					{
						count = result;
						text = string.Join(" ", array, 0, array.Length - 1);
					}
				}
				string norm = Normalize(text);
				List<BankEntry> list = BankStore.Items.Where((BankEntry e) => Normalize(e.Name) == norm).ToList();
				if (list.Count == 0)
				{
					list = BankStore.Items.Where((BankEntry e) => Normalize(e.Name).Contains(norm)).ToList();
				}
				if (list.Count == 0)
				{
					ChatUtil.Send("Nothing in the bank matching \"" + text + "\". /bank list shows what's there.");
					return;
				}
				List<string> list2 = list.Select((BankEntry e) => e.Name).Distinct().ToList();
				if (list2.Count > 1)
				{
					ChatUtil.Send("\"" + text + "\" matches " + string.Join(", ", list2) + ". Be more specific.");
					return;
				}
				List<BankEntry> entries = list.OrderByDescending((BankEntry e) => e.Value).Take(count).ToList();
				PlayerControllerB near = requester ?? GameNetworkManager.Instance?.localPlayerController;
				List<BankEntry> list3 = ItemSpawner.SpawnOnShip(entries, near);
				if (list3.Count == 0)
				{
					ChatUtil.Send("Couldn't spawn " + list2[0] + ". It stays in the bank.");
					return;
				}
				BankStore.Remove(list3);
				BankNet.Publish();
				int num = list3.Sum((BankEntry e) => e.Value);
				string text3 = (string.IsNullOrEmpty(who) ? "Withdrew" : (who + " withdrew"));
				ChatUtil.Send($"{text3} {list3.Count} {list2[0]} (${num}). Bank: {BankStore.Items.Count} item(s), ${BankStore.Total}.", broadcast: true);
				Plugin.Logger.LogInfo((object)string.Format("[Bank] {0} withdrew {1} item(s) ${2} ({3})", who ?? "host", list3.Count, num, arg));
			}
			catch (Exception arg2)
			{
				Plugin.Logger.LogError((object)$"[BankService.Withdraw] {arg2}");
			}
		}

		internal static void SellFromBank(string arg, string who)
		{
			try
			{
				if (!Ready(out var why))
				{
					ChatUtil.Send(why);
					return;
				}
				DepositItemsDesk val = Object.FindObjectOfType<DepositItemsDesk>();
				if ((Object)(object)val == (Object)null)
				{
					ChatUtil.Send("No sell desk here. Land at the Company first.");
					return;
				}
				if (val.inSellingItemsAnimation || val.attacking)
				{
					ChatUtil.Send("The Company is busy. Wait for the current sale to finish.");
					return;
				}
				if (val.itemsOnCounter.Count > 0 || ((Component)val.deskObjectsContainer).GetComponentsInChildren<GrabbableObject>().Length != 0)
				{
					ChatUtil.Send("Items are on the counter. Sell or take them back first.");
					return;
				}
				if (BankStore.Items.Count == 0)
				{
					ChatUtil.Send("The bank is empty.");
					return;
				}
				float num = Mathf.Max(0.01f, StartOfRound.Instance.companyBuyingRate);
				string text = (arg ?? "").Trim().ToLowerInvariant();
				List<BankEntry> list;
				int num2;
				int total;
				switch (text)
				{
				case "all":
					list = BankStore.Items.ToList();
					break;
				case "quota":
				case "q":
				{
					int num3 = Mathf.Max(0, TimeOfDay.Instance.profitQuota - TimeOfDay.Instance.quotaFulfilled);
					if (num3 <= 0)
					{
						ChatUtil.Send("Quota already met.");
						return;
					}
					num2 = Mathf.CeilToInt((float)num3 / num);
					goto IL_0172;
				}
				default:
					{
						if (int.TryParse(text.TrimStart('$'), out var result) && result > 0)
						{
							num2 = Mathf.CeilToInt((float)result / num);
							goto IL_0172;
						}
						ChatUtil.Send("Usage: /sell bank <amount> | quota | all");
						return;
					}
					IL_0172:
					total = BankStore.Total;
					if (total < num2)
					{
						ChatUtil.Send($"The bank only has ${total} of scrap (${(int)((float)total * num)} at today's rate). /sell bank all sells it all.");
						return;
					}
					list = ((total == num2) ? BankStore.Items.ToList() : BankSelect.ClosestAtOrAbove(BankStore.Items, (BankEntry e) => e.Value, num2));
					break;
				}
				if (list.Count == 0)
				{
					ChatUtil.Send("Nothing to sell.");
					return;
				}
				int num4 = list.Sum((BankEntry e) => e.Value);
				float companyBuyingRate = StartOfRound.Instance.companyBuyingRate;
				int num5 = (int)((float)num4 * companyBuyingRate);
				Terminal val2 = Object.FindObjectOfType<Terminal>();
				BankStore.Remove(list);
				val.inSellingItemsAnimation = true;
				val.itemsOnCounterAmount = list.Count;
				val2.groupCredits += num5;
				val.SellItemsClientRpc(num5, val2.groupCredits, list.Count, companyBuyingRate);
				val.SellAndDisplayItemProfits(num5, val2.groupCredits);
				BankNet.Publish();
				string text2 = (string.IsNullOrEmpty(who) ? "Sold" : (who + " sold"));
				ChatUtil.Send($"{text2} {list.Count} banked item(s) (${num4}) for ${num5}. " + $"Bank: {BankStore.Items.Count} item(s), ${BankStore.Total}.", broadcast: true);
				Plugin.Logger.LogInfo((object)string.Format("[Bank] {0} sold {1} item(s) raw ${2} profit ${3} ({4})", who ?? "host", list.Count, num4, num5, arg));
			}
			catch (Exception arg2)
			{
				Plugin.Logger.LogError((object)$"[BankService.SellFromBank] {arg2}");
			}
		}

		internal static void Status()
		{
			if (BankGate.IsLocked(out var reason))
			{
				ChatUtil.Send(reason);
			}
			if (BankStore.Items.Count == 0)
			{
				ChatUtil.Send("The bank is empty. /bank deposit <amount|all> stores scrap.");
			}
			else
			{
				ChatUtil.Send(string.Format("{0} item(s), ${1}. Auto deposit {2}. /bank list, or type bank in the terminal.", BankStore.Items.Count, BankStore.Total, ModConfig.AutoDeposit.Value ? "ON" : "OFF"));
			}
		}

		internal static void Info()
		{
			ChatUtil.Send("This lobby's host runs SimpleHQ: scrap can be stored in a bank to cut ship lag. Anyone can type /bank to see the total.", broadcast: true);
			ChatUtil.Send("The host and whitelisted players can store scrap with /bank deposit <amount|all> (in orbit or at the Company), take it back with /bank withdraw <item>, and sell it with /sell bank <amount>. With SimpleHQ installed, type bank in the terminal.", broadcast: true);
		}

		internal static void List()
		{
			if (BankStore.Items.Count == 0)
			{
				ChatUtil.Send("The bank is empty.");
				return;
			}
			string message = string.Join(", ", from e in BankStore.Items
				group e by e.Name into g
				orderby g.Sum((BankEntry e) => e.Value) descending
				select $"{g.Key} x{g.Count()} ${g.Sum((BankEntry e) => e.Value)}");
			ChatUtil.SendChunked(message);
		}

		private static bool Ready(out string why)
		{
			why = null;
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton == (Object)null || !singleton.IsHost)
			{
				why = "Only the host can do that.";
				return false;
			}
			return !BankGate.IsLocked(out why);
		}

		internal static void OnPropsDespawning(bool despawnAllItems)
		{
			try
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if (!((Object)(object)singleton == (Object)null) && singleton.IsHost && (despawnAllItems || ((Object)(object)StartOfRound.Instance != (Object)null && StartOfRound.Instance.allPlayersDead)) && !TournamentInfo.Active && BankStore.Items.Count != 0)
				{
					int total = BankStore.Total;
					int count = BankStore.Items.Count;
					BankStore.Clear();
					BankNet.Publish();
					ChatUtil.Send($"The crew was wiped out. The bank lost {count} item(s), ${total}.", broadcast: true);
					Plugin.Logger.LogInfo((object)$"[Bank] wiped out: lost {count} item(s) ${total}");
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[BankService.OnPropsDespawning] {arg}");
			}
		}

		internal static void OnFired()
		{
			try
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if (!((Object)(object)singleton == (Object)null) && singleton.IsHost)
				{
					int count = BankStore.Items.Count;
					BankStore.Clear();
					BankStore.Commit();
					BankNet.Publish();
					if (count > 0)
					{
						Plugin.Logger.LogInfo((object)$"[Bank] fired: bank of {count} item(s) cleared");
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[BankService.OnFired] {arg}");
			}
		}
	}
	internal static class TournamentApply
	{
		internal const int NoWeather = int.MinValue;

		private static readonly List<NetworkObject> _kitItems = new List<NetworkObject>();

		private static (int level, bool overrideOn, LevelWeatherType overrideType, LevelWeatherType current)? _savedWeather;

		private static int _applyId;

		internal static int LevelId { get; private set; } = -1;

		internal static int Weather { get; private set; } = int.MinValue;

		internal static int LockedLevelId { get; private set; } = -1;

		internal static bool LeverLocked { get; private set; }

		internal static void Reset()
		{
			RestoreWeather();
			LevelId = -1;
			Weather = int.MinValue;
			LockedLevelId = -1;
			LeverLocked = false;
			_kitItems.Clear();
			_applyId++;
		}

		private static SelectableLevel CompanyLevel()
		{
			return (StartOfRound.Instance?.levels)?.FirstOrDefault((Func<SelectableLevel, bool>)((SelectableLevel l) => (Object)(object)l != (Object)null && (l.PlanetName.Contains("Gordion") || l.PlanetName.Contains("Company"))));
		}

		internal static string Problem(Kit kit)
		{
			if (kit.QuotaCount < 0)
			{
				return "this kit's format \"" + kit.format + "\" isn't 1o or a number of quotas like 3q.";
			}
			if (kit.days == null || kit.days.Length != kit.DaysNeeded)
			{
				string formatName = kit.FormatName;
				object arg = kit.DaysNeeded;
				KitDay[] days = kit.days;
				return $"a {formatName} kit needs {arg} day(s), this one has {((days != null) ? days.Length : 0)}.";
			}
			for (int i = 0; i < kit.days.Length; i++)
			{
				if ((Object)(object)FindLevel(kit.days[i].moon) == (Object)null)
				{
					return $"day {i + 1}'s moon \"{kit.days[i].moon}\" isn't in this game.";
				}
				if (!TryParseWeather(kit.days[i].weather, out var _))
				{
					return $"day {i + 1}'s weather \"{kit.days[i].weather}\" isn't a weather.";
				}
			}
			if (kit.quotas != null && kit.quotas.Length != 0 && kit.quotas.Length != kit.QuotaCount)
			{
				return $"this kit lists {kit.quotas.Length} quota(s) for a {kit.FormatName} run.";
			}
			KitItem[] array = kit.items ?? new KitItem[0];
			foreach (KitItem kitItem in array)
			{
				if ((Object)(object)FindItem(kitItem.item) == (Object)null)
				{
					return "this kit's item \"" + kitItem.item + "\" isn't in this game.";
				}
			}
			return null;
		}

		internal static void StartRun(Kit kit)
		{
			StartOfRound instance = StartOfRound.Instance;
			TimeOfDay instance2 = TimeOfDay.Instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null)
			{
				Plugin.Logger.LogError((object)"[TournamentApply] game not ready; kit not applied.");
				return;
			}
			TournamentCleanup.ClearShip();
			SpawnKitItems(kit);
			if (kit.cruiser)
			{
				((MonoBehaviour)CoroutineRunner.Instance).StartCoroutine(OrderCruiser(_applyId + 1));
			}
			if (!kit.OneOff)
			{
				instance2.profitQuota = FirstQuota(kit);
				instance2.quotaFulfilled = 0;
				instance2.timesFulfilledQuota = 0;
			}
			SetDeadlineThreeDays();
			ApplyDay(kit.days[0], kit.credits);
		}

		internal static int FirstQuota(Kit kit)
		{
			if (kit.quotas == null || kit.quotas.Length == 0)
			{
				if (!((Object)(object)TimeOfDay.Instance != (Object)null))
				{
					return 130;
				}
				return TimeOfDay.Instance.quotaVariables.startingQuota;
			}
			return kit.quotas[0];
		}

		internal static void ApplyDay(KitDay day, int credits = -1)
		{
			StartOfRound instance = StartOfRound.Instance;
			SelectableLevel val = FindLevel(day.moon);
			if ((Object)(object)instance == (Object)null || (Object)(object)val == (Object)null)
			{
				Plugin.Logger.LogError((object)("[TournamentApply] day's moon '" + day.moon + "' missing; not applied."));
				return;
			}
			int id = ++_applyId;
			LevelId = val.levelID;
			Weather = (TryParseWeather(day.weather, out var weather) ? weather : int.MinValue);
			instance.overrideRandomSeed = true;
			instance.overrideSeedNumber = day.seed;
			RestoreWeather();
			LeverLocked = false;
			ApplyLocal(LevelId, Weather);
			((MonoBehaviour)CoroutineRunner.Instance).StartCoroutine(RouteThenCredits(id, LevelId, credits));
			Plugin.Logger.LogInfo((object)$"[TournamentApply] day: level={LevelId} ({val.PlanetName}) weather={Weather} seed={day.seed}.");
		}

		internal static void GoToCompany(bool lockLever)
		{
			StartOfRound instance = StartOfRound.Instance;
			SelectableLevel val = CompanyLevel();
			if ((Object)(object)instance == (Object)null || (Object)(object)val == (Object)null)
			{
				Plugin.Logger.LogError((object)"[TournamentApply] can't find the Company.");
				return;
			}
			int id = ++_applyId;
			RestoreWeather();
			instance.overrideRandomSeed = false;
			LevelId = val.levelID;
			Weather = int.MinValue;
			LeverLocked = lockLever;
			ApplyLocal(LevelId, Weather);
			((MonoBehaviour)CoroutineRunner.Instance).StartCoroutine(RouteThenCredits(id, LevelId, -1));
			Plugin.Logger.LogInfo((object)("[TournamentApply] to " + val.PlanetName + ", lever " + (lockLever ? "locked" : "free") + "."));
		}

		internal static void Stage()
		{
			try
			{
				Terminal val = Object.FindObjectOfType<Terminal>();
				if ((Object)(object)val != (Object)null)
				{
					int num = val.orderedItemsFromTerminal?.Count ?? 0;
					val.orderedItemsFromTerminal?.Clear();
					bool flag = val.orderedVehicleFromTerminal != -1 || val.vehicleInDropship;
					val.orderedVehicleFromTerminal = -1;
					val.vehicleInDropship = false;
					val.numberOfItemsInDropship = 0;
					val.groupCredits = 0;
					val.SyncGroupCreditsClientRpc(0, 0);
					if (num > 0 || flag)
					{
						ChatUtil.Send(string.Format("Tournament mode cancelled {0} ordered item(s){1}.", num, flag ? " and a vehicle" : ""), broadcast: true);
					}
					Plugin.Logger.LogInfo((object)$"[TournamentApply] staging: credits 0, cancelled {num} order(s), vehicle={flag}.");
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[TournamentApply.Stage] orders: {arg}");
			}
			GoToCompany(lockLever: true);
		}

		internal static void SetDeadlineThreeDays()
		{
			TimeOfDay instance = TimeOfDay.Instance;
			if (!((Object)(object)instance == (Object)null))
			{
				int num = (int)(instance.totalTime * 3f) + 1;
				instance.timeUntilDeadline = num;
				instance.UpdateProfitQuotaCurrentTime();
				instance.SyncTimeClientRpc(instance.globalTime, num);
			}
		}

		internal static void NextQuota(Kit kit, int banked, int quotaIndex)
		{
			TimeOfDay instance = TimeOfDay.Instance;
			if (!((Object)(object)instance == (Object)null))
			{
				if (kit.quotas != null && quotaIndex < kit.quotas.Length)
				{
					int num = Mathf.Max(0, (banked - instance.profitQuota) / 5);
					instance.quotaFulfilled = 0;
					instance.timesFulfilledQuota++;
					instance.SyncNewProfitQuotaClientRpc(kit.quotas[quotaIndex], num, instance.timesFulfilledQuota);
				}
				else
				{
					instance.SetNewProfitQuota();
				}
				SetDeadlineThreeDays();
			}
		}

		internal static void AfterVanillaQuota(Kit kit, int quotaIndex)
		{
			TimeOfDay instance = TimeOfDay.Instance;
			if (!((Object)(object)instance == (Object)null))
			{
				if (kit.quotas != null && quotaIndex < kit.quotas.Length && instance.profitQuota != kit.quotas[quotaIndex])
				{
					instance.SyncNewProfitQuotaClientRpc(kit.quotas[quotaIndex], 0, instance.timesFulfilledQuota);
				}
				SetDeadlineThreeDays();
			}
		}

		internal static void Revert()
		{
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance != (Object)null)
			{
				instance.overrideRandomSeed = false;
			}
			RestoreWeather();
			LevelId = -1;
			Weather = int.MinValue;
			LockedLevelId = -1;
			LeverLocked = false;
			_kitItems.Clear();
			_applyId++;
		}

		private static IEnumerator RouteThenCredits(int id, int levelId, int credits)
		{
			StartOfRound sor = StartOfRound.Instance;
			float until = Time.realtimeSinceStartup + 60f;
			while ((Object)(object)sor != (Object)null && (sor.travellingToNewLevel || !sor.inShipPhase) && Time.realtimeSinceStartup < until)
			{
				yield return (object)new WaitForSecondsRealtime(0.25f);
			}
			if (id != _applyId || (Object)(object)sor == (Object)null)
			{
				yield break;
			}
			if (sor.currentLevelID != levelId)
			{
				Terminal val = Object.FindObjectOfType<Terminal>();
				if ((Object)(object)val != (Object)null)
				{
					sor.ChangeLevelServerRpc(levelId, val.groupCredits);
				}
				yield return (object)new WaitForSecondsRealtime(0.5f);
				until = Time.realtimeSinceStartup + 60f;
				while (sor.travellingToNewLevel && Time.realtimeSinceStartup < until)
				{
					yield return (object)new WaitForSecondsRealtime(0.25f);
				}
			}
			if (id != _applyId || credits < 0)
			{
				yield break;
			}
			try
			{
				Terminal val2 = Object.FindObjectOfType<Terminal>();
				if (!((Object)(object)val2 == (Object)null))
				{
					val2.groupCredits = credits;
					val2.SyncGroupCreditsClientRpc(credits, -1);
					Plugin.Logger.LogInfo((object)$"[TournamentApply] credits set to {credits}.");
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[TournamentApply.Credits] {arg}");
			}
		}

		private static IEnumerator OrderCruiser(int id)
		{
			StartOfRound sor = StartOfRound.Instance;
			float until = Time.realtimeSinceStartup + 60f;
			while ((Object)(object)sor != (Object)null && (sor.travellingToNewLevel || !sor.inShipPhase) && Time.realtimeSinceStartup < until)
			{
				yield return (object)new WaitForSecondsRealtime(0.25f);
			}
			yield return (object)new WaitForSecondsRealtime(0.5f);
			if (id != _applyId || (Object)(object)sor == (Object)null)
			{
				yield break;
			}
			try
			{
				if (!sor.inShipPhase)
				{
					Plugin.Logger.LogWarning((object)"[TournamentApply] not in orbit; the kit's cruiser can't be put on the magnet.");
					yield break;
				}
				if ((Object)(object)Object.FindObjectOfType<VehicleController>() != (Object)null)
				{
					Plugin.Logger.LogInfo((object)"[TournamentApply] a cruiser is already here; the kit's isn't spawned.");
					yield break;
				}
				Terminal val = Object.FindObjectOfType<Terminal>();
				BuyableVehicle val2 = ((val?.buyableVehicles != null && val.buyableVehicles.Length != 0) ? val.buyableVehicles[0] : null);
				if ((Object)(object)val2?.vehiclePrefab == (Object)null || (Object)(object)RoundManager.Instance == (Object)null)
				{
					Plugin.Logger.LogWarning((object)"[TournamentApply] no vehicle prefab in the store; the kit's cruiser can't be spawned.");
					ChatUtil.Send("This kit wants a cruiser, but there's no vehicle in the store to spawn.");
					yield break;
				}
				Object.Instantiate<GameObject>(val2.vehiclePrefab, RoundManager.Instance.VehiclesContainer).GetComponent<NetworkObject>().Spawn(false);
				if ((Object)(object)val2.secondaryPrefab != (Object)null)
				{
					Object.Instantiate<GameObject>(val2.secondaryPrefab, RoundManager.Instance.VehiclesContainer).GetComponent<NetworkObject>().Spawn(false);
				}
				Plugin.Logger.LogInfo((object)"[TournamentApply] kit cruiser spawned in orbit; it magnets itself to the ship.");
				ChatUtil.Send("The kit includes a Company Cruiser: it's on the magnet, ready to drop with you.");
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[TournamentApply.OrderCruiser] {arg}");
			}
		}

		private static void SpawnKitItems(Kit kit)
		{
			List<BankEntry> list = new List<BankEntry>();
			KitItem[] array = kit.items ?? new KitItem[0];
			foreach (KitItem kitItem in array)
			{
				Item val = FindItem(kitItem.item);
				if (!((Object)(object)val == (Object)null))
				{
					for (int j = 0; j < kitItem.count; j++)
					{
						list.Add(new BankEntry
						{
							Name = val.itemName,
							ItemId = ItemSpawner.IdOf(val),
							Value = 0,
							Data = -1
						});
					}
				}
			}
			if (list.Count != 0)
			{
				_kitItems.Clear();
				ItemSpawner.SpawnOnShip(list, null, _kitItems);
				TournamentCleanup.RememberKitOnly(_kitItems);
			}
		}

		internal static void OnRemoteState(bool active, int levelId, int weather, bool leverLocked)
		{
			try
			{
				if (!active || levelId < 0)
				{
					RestoreWeather();
					LockedLevelId = -1;
					Weather = int.MinValue;
					LeverLocked = false;
					return;
				}
				LeverLocked = leverLocked;
				if (LockedLevelId != levelId || Weather != weather)
				{
					RestoreWeather();
					Weather = weather;
					ApplyLocal(levelId, weather);
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[TournamentApply.OnRemoteState] {arg}");
			}
		}

		private static void ApplyLocal(int levelId, int weather)
		{
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			LockedLevelId = levelId;
			StartOfRound instance = StartOfRound.Instance;
			if (instance?.levels != null && levelId >= 0 && levelId < instance.levels.Length && weather != int.MinValue)
			{
				SelectableLevel val = instance.levels[levelId];
				if (!_savedWeather.HasValue || _savedWeather.Value.level != levelId)
				{
					RestoreWeather();
					_savedWeather = (levelId, val.overrideWeather, val.overrideWeatherType, val.currentWeather);
				}
				val.overrideWeather = true;
				val.overrideWeatherType = (LevelWeatherType)weather;
				val.currentWeather = (LevelWeatherType)weather;
				RefreshScreens();
				Plugin.Logger.LogInfo((object)$"[TournamentApply] {val.PlanetName} weather fixed to {(object)(LevelWeatherType)weather}.");
			}
		}

		private static void RestoreWeather()
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: 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_007e: Unknown result type (might be due to invalid IL or missing references)
			if (_savedWeather.HasValue)
			{
				(int, bool, LevelWeatherType, LevelWeatherType) value = _savedWeather.Value;
				_savedWeather = null;
				StartOfRound instance = StartOfRound.Instance;
				if (instance?.levels != null && value.Item1 >= 0 && value.Item1 < instance.levels.Length)
				{
					SelectableLevel val = instance.levels[value.Item1];
					val.overrideWeather = value.Item2;
					val.overrideWeatherType = value.Item3;
					val.currentWeather = value.Item4;
					RefreshScreens();
				}
			}
		}

		private static void RefreshScreens()
		{
			try
			{
				StartOfRound instance = StartOfRound.Instance;
				if (instance != null)
				{
					instance.SetMapScreenInfoToCurrentLevel();
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("[TournamentApply] monitor refresh: " + ex.Message));
			}
		}

		internal static SelectableLevel FindLevel(string name)
		{
			SelectableLevel[] array = StartOfRound.Instance?.levels;
			if (array == null || string.IsNullOrWhiteSpace(name))
			{
				return null;
			}
			string q = Squash(name);
			return ((IEnumerable<SelectableLevel>)array).FirstOrDefault((Func<SelectableLevel, bool>)((SelectableLevel l) => (Object)(object)l != (Object)null && Squash(ShortName(l.PlanetName)) == q)) ?? ((IEnumerable<SelectableLevel>)array).FirstOrDefault((Func<SelectableLevel, bool>)((SelectableLevel l) => (Object)(object)l != (Object)null && Squash(l.PlanetName).Contains(q)));
		}

		internal static string ShortName(string planet)
		{
			int num = planet.IndexOf(' ');
			if (num <= 0 || !char.IsDigit(planet[0]))
			{
				return planet;
			}
			return planet.Substring(num + 1);
		}

		internal static string MoonNameByKey(int key)
		{
			SelectableLevel[] array = StartOfRound.Instance?.levels;
			if (array == null)
			{
				return null;
			}
			SelectableLevel[] array2 = array;
			foreach (SelectableLevel val in array2)
			{
				if (!((Object)(object)val == (Object)null))
				{
					string text = ShortName(val.PlanetName);
					if (KitCode.MoonKey(text) == key)
					{
						return text;
					}
				}
			}
			return null;
		}

		internal static string ItemNameByKey(int key)
		{
			Terminal val = Object.FindObjectOfType<Terminal>();
			if ((Object)(object)val != (Object)null && val.buyableItemsList != null)
			{
				Item[] buyableItemsList = val.buyableItemsList;
				foreach (Item val2 in buyableItemsList)
				{
					if ((Object)(object)val2 != (Object)null && KitCode.ItemKey(val2.itemName) == key)
					{
						return val2.itemName;
					}
				}
			}
			List<Item> list = StartOfRound.Instance?.allItemsList?.itemsList;
			if (list == null)
			{
				return null;
			}
			foreach (Item item in list)
			{
				if ((Object)(object)item != (Object)null && KitCode.ItemKey(item.itemName) == key)
				{
					return item.itemName;
				}
			}
			return null;
		}

		internal static Item FindItem(string name)
		{
			List<Item> list = StartOfRound.Instance?.allItemsList?.itemsList;
			if (list == null || string.IsNullOrWhiteSpace(name))
			{
				return null;
			}
			string q = Squash(name);
			return ((IEnumerable<Item>)list).FirstOrDefault((Func<Item, bool>)((Item i) => (Object)(object)i != (Object)null && Squash(i.itemName) == q));
		}

		internal static bool TryParseWeather(string s, out int weather)
		{
			//IL_0089: 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_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Expected I4, but got Unknown
			weather = int.MinValue;
			string text = Squash(s ?? "");
			switch (text)
			{
			case "":
			case "random":
			case "any":
				return true;
			case "none":
			case "clear":
			case "mild":
				weather = -1;
				return true;
			default:
				foreach (LevelWeatherType value in Enum.GetValues(typeof(LevelWeatherType)))
				{
					if (Squash(((object)value/*cast due to .constrained prefix*/).ToString()) == text)
					{
						weather = (int)value;
						return true;
					}
				}
				return false;
			}
		}

		internal static string WeatherName(int weather)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			return weather switch
			{
				-1 => "clear", 
				int.MinValue => "moon's own", 
				_ => ((object)(LevelWeatherType)weather/*cast due to .constrained prefix*/).ToString(), 
			};
		}

		private static string Squash(string s)
		{
			return new string((s ?? "").ToLowerInvariant().Where(char.IsLetterOrDigit).ToArray());
		}
	}
	internal static class TournamentBanner
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static HandleNamedMessageDelegate <>9__1_0;

			public static Func<TMP_Text, float> <>9__4_1;

			internal void <Register>b__1_0(ulong sender, FastBufferReader reader)
			{
				try
				{
					string title = default(string);
					((FastBufferReader)(ref reader)).ReadValueSafe(ref title, false);
					string big = default(string);
					((FastBufferReader)(ref reader)).ReadValueSafe(ref big, false);
					string line = default(string);
					((FastBufferReader)(ref reader)).ReadValueSafe(ref line, false);
					ShowLocal(title, big, line);
				}
				catch (Exception arg)
				{
					Plugin.Logger.LogError((object)$"[TournamentBanner] {arg}");
				}
			}

			internal float <ShowLocal>b__4_1(TMP_Text t)
			{
				return t.fontSize;
			}
		}

		internal const string Msg = "SimpleHQ.Banner";

		private static int _showId;

		internal static void Register()
		{
			//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_0047: Expected O, but got Unknown
			NetworkManager singleton = NetworkManager.Singleton;
			if (((singleton != null) ? singleton.CustomMessagingManager : null) == null || singleton.IsHost)
			{
				return;
			}
			CustomMessagingManager customMessagingManager = singleton.CustomMessagingManager;
			object obj = <>c.<>9__1_0;
			if (obj == null)
			{
				HandleNamedMessageDelegate val = delegate(ulong sender, FastBufferReader reader)
				{
					try
					{
						string title = default(string);
						((FastBufferReader)(ref reader)).ReadValueSafe(ref title, false);
						string big = default(string);
						((FastBufferReader)(ref reader)).ReadValueSafe(ref big, false);
						string line = default(string);
						((FastBufferReader)(ref reader)).ReadValueSafe(ref line, false);
						ShowLocal(title, big, line);
					}
					catch (Exception arg)
					{
						Plugin.Logger.LogError((object)$"[TournamentBanner] {arg}");
					}
				};
				<>c.<>9__1_0 = val;
				obj = (object)val;
			}
			customMessagingManager.RegisterNamedMessageHandler("SimpleHQ.Banner", (HandleNamedMessageDelegate)obj);
		}

		internal unsafe static void Show(string title, string big, string line)
		{
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			ShowLocal(title, big, line);
			try
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if ((Object)(object)singleton == (Object)null || !singleton.IsHost || singleton.ConnectedClientsIds.Count <= 1)
				{
					return;
				}
				FastBufferWriter val = default(FastBufferWriter);
				((FastBufferWriter)(ref val))..ctor(256, (Allocator)2, 2048);
				try
				{
					((FastBufferWriter)(ref val)).WriteValueSafe(title ?? "", false);
					((FastBufferWriter)(ref val)).WriteValueSafe(big ?? "", false);
					((FastBufferWriter)(ref val)).WriteValueSafe(line ?? "", false);
					singleton.CustomMessagingManager.SendNamedMessageToAll("SimpleHQ.Banner", val, (NetworkDelivery)2);
				}
				finally
				{
					((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose();
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[TournamentBanner.Show] {arg}");
			}
		}

		private static void ShowLocal(string title, string big, string line)
		{
			try
			{
				HUDManager hud = HUDManager.Instance;
				if ((Object)(object)hud == (Object)null)
				{
					return;
				}
				Animator reachedProfitQuotaAnimator = hud.reachedProfitQuotaAnimator;
				List<TMP_Text> list = (((Object)(object)reachedProfitQuotaAnimator == (Object)null) ? new List<TMP_Text>() : (from t in ((Component)reachedProfitQuotaAnimator).GetComponentsInChildren<TMP_Text>(true)
					where (Object)(object)t != (Object)null && (Object)(object)t != (Object)(object)hud.newProfitQuotaText && (Object)(object)t != (Object)(object)hud.reachedProfitQuotaBonusText
					select t).ToList());
				if ((Object)(object)reachedProfitQuotaAnimator == (Object)null || list.Count == 0 || (Object)(object)hud.newProfitQuotaText == (Object)null)
				{
					hud.DisplayGlobalNotification(title);
					return;
				}
				TMP_Text val = list.OrderByDescending((TMP_Text t) => t.fontSize).First();
				string[] obj = new string[3]
				{
					val.text,
					((TMP_Text)hud.newProfitQuotaText).text,
					null
				};
				TextMeshProUGUI reachedProfitQuotaBonusText = hud.reachedProfitQuotaBonusText;
				obj[2] = ((reachedProfitQuotaBonusText != null) ? ((TMP_Text)reachedProfitQuotaBonusText).text : null);
				string[] saved = obj;
				val.text = title;
				((TMP_Text)hud.newProfitQuotaText).text = big;
				if ((Object)(object)hud.reachedProfitQuotaBonusText != (Object)null)
				{
					((TMP_Text)hud.reachedProfitQuotaBonusText).text = line;
				}
				reachedProfitQuotaAnimator.SetBool("display", true);
				if ((Object)(object)hud.UIAudio != (Object)null && (Object)(object)hud.reachedQuotaSFX != (Object)null)
				{
					hud.UIAudio.PlayOneShot(hud.reachedQuotaSFX);
				}
				((MonoBehaviour)CoroutineRunner.Instance).StartCoroutine(HideLater(++_showId, reachedProfitQuotaAnimator, val, saved));
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[TournamentBanner.ShowLocal] {arg}");
			}
		}

		private static IEnumerator HideLater(int id, Animator card, TMP_Text header, string[] saved)
		{
			yield return (object)new WaitForSeconds(6f);
			if (id != _showId)
			{
				yield break;
			}
			HUDManager hud = HUDManager.Instance;
			if ((Object)(object)card != (Object)null)
			{
				card.SetBool("display", false);
			}
			yield return (object)new WaitForSeconds(1.5f);
			if (id == _showId && !((Object)(object)hud == (Object)null))
			{
				if ((Object)(object)header != (Object)null)
				{
					header.text = saved[0];
				}
				if ((Object)(object)hud.newProfitQuotaText != (Object)null)
				{
					((TMP_Text)hud.newProfitQuotaText).text = saved[1];
				}
				if ((Object)(object)hud.reachedProfitQuotaBonusText != (Object)null && saved[2] != null)
				{
					((TMP_Text)hud.reachedProfitQuotaBonusText).text = saved[2];
				}
			}
		}
	}
	internal static class TournamentCleanup
	{
		private static readonly HashSet<ulong> _noScore = new HashSet<ulong>();

		private static readonly HashSet<ulong> _kitOnly = new HashSet<ulong>();

		internal static void Reset()
		{
			_noScore.Clear();
		}

		internal static void RememberKitItems(IEnumerable<NetworkObject> objects)
		{
			NoScore(objects);
		}

		internal static void NoScore(IEnumerable<NetworkObject> objects)
		{
			foreach (NetworkObject @object in objects)
			{
				if ((Object)(object)@object != (Object)null)
				{
					_noScore.Add(@object.NetworkObjectId);
				}
			}
		}

		internal static void CollectScrap(List<GrabbableObject> aboard, out List<BankEntry> scoring, out List<BankEntry> counted)
		{
			scoring = new List<BankEntry>();
			counted = new List<BankEntry>();
			foreach (GrabbableObject item2 in aboard)
			{
				if (!item2.itemProperties.isScrap || item2.scrapValue <= 0)
				{
					continue;
				}
				BankEntry item = new BankEntry
				{
					Name = item2.itemProperties.itemName,
					Value = item2.scrapValue,
					ItemId = ItemSpawner.IdOf(item2.itemProperties),
					Data = ItemSpawner.SaveDataOf(item2)
				};
				if (_noScore.Contains(((NetworkBehaviour)item2).NetworkObjectId))
				{
					if (!IsKitItem(item2))
					{
						counted.Add(item);
					}
				}
				else
				{
					scoring.Add(item);
				}
			}
		}

		private static bool IsKitItem(GrabbableObject o)
		{
			return _kitOnly.Contains(((NetworkBehaviour)o).NetworkObjectId);
		}

		internal static void RememberKitOnly(IEnumerable<NetworkObject> objects)
		{
			foreach (NetworkObject @object in objects)
			{
				if ((Object)(object)@object != (Object)null)
				{
					_kitOnly.Add(@object.NetworkObjectId);
					_noScore.Add(@object.NetworkObjectId);
				}
			}
		}

		internal static int ClearShip()
		{
			try
			{
				int num = Remove(Aboard());
				_noScore.Clear();
				_kitOnly.Clear();
				if (num > 0)
				{
					Plugin.Logger.LogInfo((object)$"[Tournament] cleared {num} item(s) off the ship for the kit.");
				}
				return num;
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[TournamentCleanup.ClearShip] {arg}");
				return 0;
			}
		}

		internal static List<GrabbableObject> Aboard()
		{
			List<VehicleController> vehicles = (from v in Object.FindObjectsByType<VehicleController>((FindObjectsSortMode)0)
				where (Object)(object)v != (Object)null && v.magnetedToShip
				select v).ToList();
			List<GrabbableObject> source = (from o in Object.FindObjectsByType<GrabbableObject>((FindObjectsSortMode)0)
				where (Object)(object)o != (Object)null && (Object)(object)o.itemProperties != (Object)null && (Object)(object)((NetworkBehaviour)o).NetworkObject != (Object)null && ((NetworkBehaviour)o).NetworkObject.IsSpawned
				where !(o is RagdollGrabbableObject) && !IsShipFixture(o)
				select o).ToList();
			HashSet<GrabbableObject> hashSet = new HashSet<GrabbableObject>(source.Where(IsAboard));
			foreach (BeltBagItem item in source.OfType<BeltBagItem>())
			{
				if (!hashSet.Contains((GrabbableObject)(object)item) || item.objectsInBag == null)
				{
					continue;
				}
				foreach (GrabbableObject item2 in item.objectsInBag)
				{
					if ((Object)(object)item2 != (Object)null && (Object)(object)((NetworkBehaviour)item2).NetworkObject != (Object)null && ((NetworkBehaviour)item2).NetworkObject.IsSpawned)
					{
						hashSet.Add(item2);
					}
				}
			}
			return hashSet.ToList();
			bool IsAboard(GrabbableObject o)
			{
				if (!o.isInShipRoom && !o.isInElevator && !o.isHeld && !o.isPocketed && !o.heldByPlayerOnServer)
				{
					return vehicles.Any((VehicleController v) => ((Component)o).transform.IsChildOf(((Component)v).transform));
				}
				return true;
			}
		}

		private static bool IsShipFixture(GrabbableObject o)
		{
			string text = (o.itemProperties.itemName ?? "").ToLowerInvariant();
			if (!text.Contains("clipboard"))
			{
				return text.Contains("sticky");
			}
			return true;
		}

		internal static int Remove(List<GrabbableObject> items)
		{
			HashSet<PlayerControllerB> hashSet = new HashSet<PlayerControllerB>();
			foreach (GrabbableObject item in items)
			{
				if (item.isHeld && (Object)(object)item.playerHeldBy != (Object)null)
				{
					hashSet.Add(item.playerHeldBy);
				}
			}
			foreach (PlayerControllerB item2 in hashSet)
			{
				try
				{
					item2.DropAllHeldItemsAndSyncNonexact();
				}
				catch (Exception ex)
				{
					Plugin.Logger.LogWarning((object)("[Tournament] couldn't make " + item2.playerUsername + " drop: " + ex.Message));
				}
			}
			int num = 0;
			foreach (GrabbableObject item3 in items)
			{
				try
				{
					if (!((Object)(object)item3 == (Object)null) && !((Object)(object)((NetworkBehaviour)item3).NetworkObject == (Object)null) && ((NetworkBehaviour)item3).NetworkObject.IsSpawned)
					{
						_noScore.Remove(((NetworkBehaviour)item3).NetworkObjectId);
						_kitOnly.Remove(((NetworkBehaviour)item3).NetworkObjectId);
						((NetworkBehaviour)item3).NetworkObject.Despawn(true);
						num++;
					}
				}
				catch (Exception ex2)
				{
					Plugin.Logger.LogWarning((object)("[Tournament] couldn't remove " + item3?.itemProperties?.itemName + ": " + ex2.Message));
				}
			}
			return num;
		}
	}
	internal static class TournamentRun
	{
		private static Kit _kit;

		private static string _overReason;

		private static bool _resultsPosted;

		private static bool _nextIsCompanyDay;

		private static bool _companyDayEnded;

		private static bool _pendingNextQuota;

		private static float _lastDayEnd = -100f;

		private static float _lastOrbit = -100f;

		private static bool _wipedThisDay;

		private static readonly List<BankEntry> _runBank = new List<BankEntry>();

		private static readonly List<(string name, int total, int days, string time, string reason)> _series = new List<(string, int, int, string, string)>();

		private static float _startedAt = -1f;

		private static float _endedAt = -1f;

		internal static int RunTotal { get; private set; }

		internal static int DaysDone { get; private set; }

		internal static int QuotaIndex { get; private set; }

		internal static int QuotasMet { get; private set; }

		internal static bool Over { get; private set; }

		internal static bool OnCompanyDay { get; private set; }

		internal static int QuotaBanked { get; private set; }

		internal static bool Running
		{
			get
			{
				if (_kit != null)
				{
					return TournamentInfo.Active;
				}
				return false;
			}
		}

		internal static int SeriesTotal => _series.Sum(((string name, int total, int days, string time, string reason) l) => l.total) + RunTotal;

		internal static int SeriesLegs => _series.Count + ((_kit != null) ? 1 : 0);

		internal static int ProfitQuotaShown
		{
			get
			{
				if (!Running || _kit.OneOff || !((Object)(object)TimeOfDay.Instance != (Object)null))
				{
					return -1;
				}
				return TimeOfDay.Instance.profitQuota;
			}
		}

		internal static int QuotaFulfilledShown
		{
			get
			{
				if (!Running || _kit.OneOff || _kit.hardcore || !((Object)(object)TimeOfDay.Instance != (Object)null))
				{
					return -1;
				}
				return TimeOfDay.Instance.quotaFulfilled;
			}
		}

		internal static void ResetAll()
		{
			_series.Clear();
			Reset();
		}

		internal static void Reset()
		{
			_kit = null;
			RunTotal = 0;
			DaysDone = 0;
			QuotaIndex = 0;
			QuotasMet = 0;
			QuotaBanked = 0;
			Over = false;
			OnCompanyDay = false;
			_overReason = null;
			_resultsPosted = false;
			_nextIsCompanyDay = false;
			_companyDayEnded = false;
			_pendingNextQuota = false;
			_wipedThisDay = false;
			_runBank.Clear();
			_startedAt = -1f;
			_endedAt = -1f;
			TournamentCleanup.Reset();
		}

		internal static void Begin(Kit kit)
		{
			CloseLeg();
			Reset();
			_kit = kit;
			TournamentApply.StartRun(kit);
		}

		private static void CloseLeg()
		{
			if (_kit != null && (DaysDone != 0 || RunTotal != 0))
			{
				_series.Add((_kit.name, RunTotal, DaysDone, Elapsed(), _overReason ?? "stopped early"));
				Plugin.Logger.LogInfo((object)$"[Tournament] leg {_series.Count} kept: '{_kit.name}' ${RunTotal} over {DaysDone} day(s).");
			}
		}

		internal static void PostSeries()
		{
			if (!IsHost())
			{
				return;
			}
			PostResults();
			CloseLeg();
			if (_series.Count == 0)
			{
				return;
			}
			if (_series.Count == 1)
			{
				ChatUtil.Send($"Final: \"{_series[0].name}\" ${_series[0].total} over {_series[0].days} day(s), " + "time " + _series[0].time + ".", broadcast: true);
			}
			else
			{
				ChatUtil.Send($"SERIES OVER: {_series.Count} run(s), ${_series.Sum(((string name, int total, int days, string time, string reason) l) => l.total)} in total.", broadcast: true);
				for (int num = 0; num < _series.Count; num++)
				{
					ChatUtil.Send($"  {num + 1}. \"{_series[num].name}\" ${_series[num].total} " + $"({_series[num].days} day(s), {_series[num].time}) - {_series[num].reason}", broadcast: true);
				}
			}
			Plugin.Logger.LogInfo((object)$"[Tournament] series: {_series.Count} leg(s), ${_series.Sum(((string name, int total, int days, string time, string reason) l) => l.total)} total.");
		}

		internal static void OnPlayerDied(string who)
		{
			if (IsHost() && Running && !Over && _kit != null && _kit.deathsEndRun)
			{
				End((string.IsNullOrEmpty(who) ? "a player" : who) + " died (this kit ends on any death)");
				ChatUtil.Send((string.IsNullOrEmpty(who) ? "A player" : who) + " died: the run is over. Results when the ship is back in orbit.", broadcast: true);
				TournamentNet.Publish();
			}
		}

		internal static void NoteWipe()
		{
			if (IsHost() && Running && (Object)(object)StartOfRound.Instance != (Object)null && StartOfRound.Instance.allPlayersDead)
			{
				_wipedThisDay = true;
				Plugin.Logger.LogInfo((object)"[Tournament] crew wiped out this day.");
			}
		}

		internal static void OnLanding()
		{
			if (IsHost() && Running && !Over && _startedAt < 0f)
			{
				_startedAt = Time.realtimeSinceStartup;
				Plugin.Logger.LogInfo((object)"[Tournament] run timer started.");
			}
		}

		internal static string Elapsed()
		{
			if (_startedAt < 0f)
			{
				return "not started";
			}
			float num = ((_endedAt >= 0f) ? _endedAt : Time.realtimeSinceStartup);
			TimeSpan timeSpan = TimeSpan.FromSeconds(num - _startedAt);
			if (!(timeSpan.TotalHours >= 1.0))
			{
				return $"{timeSpan.Minutes}:{timeSpan.Seconds:00}";
			}
			return $"{(int)timeSpan.TotalHours}:{timeSpan.Minutes:00}:{timeSpan.Seconds:00}";
		}

		internal static string StatusLine()
		{
			if (_kit == null)
			{
				return "";
			}
			if (Over)
			{
				return $"Run over: {_overReason}. Scrap brought back: ${RunTotal}. Time {Elapsed()}." + ((_series.Count > 0) ? $" Series: ${SeriesTotal} over {_series.Count + 1} run(s)." : "") + " The host can load the next code, or end it.";
			}
			string text = (OnCompanyDay ? "Company day" : $"day {DaysDone + 1} of {_kit.DaysNeeded}");
			string text2 = (_kit.OneOff ? "" : $", quota {QuotaIndex + 1} of {_kit.QuotaCount}");
			string text3 = ((_series.Count > 0) ? $" Series: run {_series.Count + 1}, ${SeriesTotal} so far." : "");
			return $"{_kit.FormatName}: {text}{text2}. Run total ${RunTotal}. Time {Elapsed()}.{text3}";
		}

		internal static void OnDayEnding()
		{
			try
			{
				if (!IsHost() || !Running || Over || Time.realtimeSinceStartup - _lastDayEnd < 10f)
				{
					return;
				}
				_lastDayEnd = Time.realtimeSinceStartup;
				bool wipedThisDay = _wipedThisDay;
				_wipedThisDay = false;
				if (OnCompanyDay)
				{
					List<GrabbableObject> list = TournamentCleanup.Aboard();
					TournamentCleanup.CollectScrap(list, out var scoring, out var counted);
					List<BankEntry> list2 = counted.Concat(scoring).ToList();
					TournamentCleanup.Remove(list.Where((GrabbableObject o) => o.itemProperties.isScrap && o.scrapValue > 0).ToList());
					AddToBank(list2);
					OnCompanyDay = false;
					_companyDayEnded = true;
					Plugin.Logger.LogInfo((object)$"[Tournament] Company day over: {list2.Count} unsold scrap back in the bank.");
					return;
				}
				int num = 0;
				int num2 = 0;
				if (wipedThisDay)
				{
					if (_kit.hardcore)
					{
						End("the crew was wiped out (hardcore)");
					}
					ChatUtil.Send($"Day {DaysDone + 1}: the crew was wiped out. That day's scrap is lost" + (_kit.hardcore ? "." : $"; the run total stays ${RunTotal}."), broadcast: true);
				}
				else
				{
					List<GrabbableObject> list3 = TournamentCleanup.Aboard();
					TournamentCleanup.CollectScrap(list3, out var scoring2, out var counted2);
					num = scoring2.Sum((BankEntry e) => e.Value);
					num2 = scoring2.Count;
					RunTotal += num;
					QuotaBanked += num;
					TournamentCleanup.Remove(_kit.OneOff ? list3 : list3.Where((GrabbableObject o) => o.itemProperties.isScrap && o.scrapValue > 0).ToList());
					AddToBank(scoring2.Concat(counted2).ToList());
					ChatUtil.Send($"Day {DaysDone + 1} over: {num2} scrap brought back, ${num}. Run total: ${RunTotal}.", broadcast: true);
				}
				DaysDone++;
				Plugin.Logger.LogInfo((object)$"[Tournament] day {DaysDone} ended: banked {num2} scrap ${num}, wiped={wipedThisDay}, total ${RunTotal}.");
				if (Over)
				{
					return;
				}
				if (_kit.OneOff)
				{
					End("one-off complete");
				}
				else if (DaysDone % 3 != 0)
				{
					ShowQuotaProgress();
				}
				else if (_kit.hardcore)
				{
					TimeOfDay instance = TimeOfDay.Instance;
					int num3 = (((Object)(object)instance != (Object)null) ? instance.profitQuota : 0);
					if (QuotaBanked >= num3)
					{
						QuotasMet++;
						ChatUtil.Send($"Quota {QuotaIndex + 1} met: ${QuotaBanked} of ${num3}.", broadcast: true);
						if (QuotaIndex + 1 >= _kit.QuotaCount)
						{
							End($"all {_kit.QuotaCount} quota(s) met");
						}
						else
						{
							_pendingNextQuota = true;
						}
					}
					else
					{
						ShowQuotaProgress();
						End($"quota {QuotaIndex + 1} missed: ${QuotaBanked} of ${num3} (hardcore)");
					}
				}
				else
				{
					_nextIsCompanyDay = true;
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[TournamentRun.OnDayEnding] {arg}");
			}
		}

		internal static void OnDayPassed()
		{
			try
			{
				if (IsHost() && Running && _pendingNextQuota)
				{
					_pendingNextQuota = false;
					int quotaBanked = QuotaBanked;
					QuotaIndex++;
					QuotaBanked = 0;
					TournamentApply.NextQuota(_kit, quotaBanked, QuotaIndex);
					TournamentNet.Publish();
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[TournamentRun.OnDayPassed] {arg}");
			}
		}

		internal static void OnShipInOrbit()
		{
			try
			{
				if (!IsHost() || !Running || Time.realtimeSinceStartup - _lastOrbit < 10f)
				{
					return;
				}
				_lastOrbit = Time.realtimeSinceStartup;
				if (_companyDayEnded)
				{
					_companyDayEnded = false;
					QuotasMet++;
					QuotaIndex++;
					QuotaBanked = 0;
					ChatUtil.Send($"Quota {QuotaIndex} met.", broadcast: true);
					if (QuotaIndex >= _kit.QuotaCount)
					{
						End($"all {_kit.QuotaCount} quota(s) met");
					}
					else
					{
						TournamentApply.AfterVanillaQuota(_kit, QuotaIndex);
					}
				}
				if (Over)
				{
					PostResults();
					TournamentApply.GoToCompany(lockLever: true);
				}
				else if (_nextIsCompanyDay)
				{
					_nextIsCompanyDay = false;
					OnCompanyDay = true;
					TournamentApply.GoToCompany(lockLever: false);
					int num = WithdrawRunBank();
					TimeOfDay instance = TimeOfDay.Instance;
					ChatUtil.Send($"Company day: sell to meet quota {QuotaIndex + 1} (${instance?.profitQuota ?? 0}). " + $"Your banked scrap is back on the ship ({num} item(s)).", broadcast: true);
				}
				else if (DaysDone < _kit.days.Length)
				{
					KitDay kitDay = _kit.days[DaysDone];
					TournamentApply.ApplyDay(kitDay);
					TournamentApply.TryParseWeather(kitDay.weather, out var weather);
					ChatUtil.Send($"Day {DaysDone + 1} of {_kit.DaysNeeded}: routing to {kitDay.moon} " + "(" + TournamentApply.WeatherName(weather) + " weather).", broadcast: true);
				}
				TournamentNet.Publish();
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[TournamentRun.OnShipInOrbit] {arg}");
			}
		}

		internal static void OnFired()
		{
			if (IsHost() && Running && !Over)
			{
				End($"quota {QuotaIndex + 1} missed, the crew was fired");
				PostResults();
				TournamentApply.GoToCompany(lockLever: true);
				TournamentNet.Publish();
			}
		}

		private static void End(string reason)
		{
			Over = true;
			_overReason = reason;
			if (_startedAt >= 0f)
			{
				_endedAt = Time.realtimeSinceStartup;
			}
			Plugin.Logger.LogInfo((object)$"[Tournament] run over: {reason}. Total ${RunTotal}, days {DaysDone}, quotas met {QuotasMet}.");
		}

		private static void PostResults()
		{
			if (!_resultsPosted && _kit != null)
			{
				_resultsPosted = true;
				string text = (_kit.OneOff ? "" : $" Quotas met: {QuotasMet} of {_kit.QuotaCount}.");
				ChatUtil.Send("TOURNAMENT OVER: \"" + _kit.name + "\" (" + _kit.FormatName + "). " + Cap(_overReason) + ".", broadcast: true);
				string text2 = ((_series.Count > 0) ? $" Series so far: ${SeriesTotal} over {_series.Count + 1} run(s)." : "");
				ChatUtil.Send($"Scrap brought back: ${RunTotal} over {DaysDone} day(s).{text} Time: {Elapsed()}.{text2} " + "The host can load the next code, or end it with /tournament end.", broadcast: true);
			}
		}

		private static string Cap(string s)
		{
			if (!string.IsNullOrEmpty(s))
			{
				return char.ToUpperInvariant(s[0]) + s.Substring(1);
			}
			return s;
		}

		private static void ShowQuotaProgress()
		{
			if (_kit.hardcore && !_kit.OneOff && !((Object)(object)TimeOfDay.Instance == (Object)null))
			{
				ChatUtil.Send($"Quota {QuotaIndex + 1}: ${QuotaBanked} of ${TimeOfDay.Instance.profitQuota} banked.", broadcast: true);
			}
		}

		private static void AddToBank(List<BankEntry> entries)
		{
			if (entries.Count != 0)
			{
				BankStore.Add(entries);
				_runBank.AddRange(entries);
				BankNet.Publish();
			}
		}

		private static int WithdrawRunBank()
		{
			List<BankEntry> list = _runBank.Where((BankEntry e) => BankStore.Items.Contains(e)).ToList();
			if (list.Count == 0)
			{
				return 0;
			}
			List<NetworkObject> list2 = new List<NetworkObject>();
			List<BankEntry> list3 = ItemSpawner.SpawnOnShip(list, null, list2);
			BankStore.Remove(list3);
			foreach (BankEntry item in list3)
			{
				_runBank.Remove(item);
			}
			TournamentCleanup.NoScore(list2);
			BankNet.Publish();
			return list3.Count;
		}

		private static bool IsHost()
		{
			if ((Object)(object)NetworkManager.Singleton != (Object)null)
			{
				return NetworkManager.Singleton.IsHost;
			}
			return false;
		}
	}
	internal static class TournamentService
	{
		private const float AgreeWindowSeconds = 120f;

		private static bool _proposed;

		private static Kit _active;

		private static readonly HashSet<ulong> _agreed = new HashSet<ulong>();

		private static int _proposalId;

		internal static Kit ActiveKit => _active;

		internal static bool Proposed => _proposed;

		internal static void Reset()
		{
			_proposed = false;
			_active = null;
			_agreed.Clear();
			_proposalId++;
			TournamentInfo.Active = false;
			TournamentInfo.KitName = null;
			TournamentApply.Reset();
			TournamentRun.ResetAll();
		}

		internal static void Status()
		{
			if (TournamentInfo.Active)
			{
				ChatUtil.Send((_active != null) ? ("Tournament mode ON: \"" + _active.name + "\". " + TournamentRun.StatusLine()) : "Tournament mode ON, waiting at the Company for the code. Lobby locked, mod commands off.");
			}
			else if (_proposed)
			{
				ChatUtil.Send("Tournament mode proposed. " + AgreedLine() + " Type /agree to start.");
			}
			else
			{
				ChatUtil.Send("Tournament mode is off. The host starts it with /tournament start (or tournament at the terminal), then enters the organisers' kit code.");
			}
		}

		internal static string TerminalStatus()
		{
			if (TournamentInfo.Active && _active != null)
			{
				return "ACTIVE: " + _active.name + "\n" + TournamentRun.StatusLine() + "\nLobby locked, mod commands off.\nAnother code in orbit restarts with that kit.";
			}
			if (TournamentInfo.Active)
			{
				return "ON: waiting at the Co