Decompiled source of SimpleSell v0.1.44

SimpleSell.dll

Decompiled an hour ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using DunGen;
using DunGen.Graph;
using GameNetcodeStuff;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using SimpleSell.Commands;
using SimpleSell.Core;
using SimpleSell.Features;
using TMPro;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.UI;

[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 SimpleSell
{
	internal static class ChatUtil
	{
		internal static bool BroadcastReplies;

		internal static string InvokerName;

		internal static PlayerControllerB Invoker;

		internal static void SendPrivate(string message)
		{
			bool broadcastReplies = BroadcastReplies;
			BroadcastReplies = false;
			try
			{
				Send(message);
			}
			finally
			{
				BroadcastReplies = 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=#7ec8ff>SimpleSell</color>: <color=#e8e86a>" + 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 SendRaw(string message, bool broadcast = false)
		{
			try
			{
				HUDManager instance = HUDManager.Instance;
				if (!((Object)(object)instance == (Object)null))
				{
					if (broadcast)
					{
						instance.AddTextMessageServerRpc(message);
					}
					else
					{
						instance.AddChatMessage(message, "", -1, false);
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[ChatUtil.SendRaw] {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.LastIndexOfAny(new char[2] { '·', ',' }, 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 SimpleHQLoaded => Chainloader.PluginInfos.ContainsKey("local.simplehq");

		internal static bool SimpleBuyLoaded => Chainloader.PluginInfos.ContainsKey("local.simplebuy");

		internal static bool IsSenderTheHost(int playerId)
		{
			try
			{
				if ((Object)(object)NetworkManager.Singleton == (Object)null || !NetworkManager.Singleton.IsHost)
				{
					return false;
				}
				if (playerId < 0)
				{
					Plugin.Logger.LogWarning((object)$"[CommandRouter] playerId={playerId} on host — allowing (local chat path).");
					return true;
				}
				PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController;
				if ((Object)(object)val == (Object)null)
				{
					return false;
				}
				return playerId == (int)val.playerClientId || playerId == (int)val.actualClientId;
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[CommandRouter.IsSenderTheHost] {arg}");
				return false;
			}
		}

		internal static string CanonicalVerb(string cmd)
		{
			switch (cmd)
			{
			case "sell":
			case "s":
				return "sell";
			case "stock":
			case "st":
				return "stock";
			case "m":
			case "merge":
				return "merge";
			case "gear":
			case "g":
				return "gear";
			case "e":
			case "equipment":
				return "equipment";
			case "o":
			case "organize":
				return "organize";
			case "time":
			case "t":
				return "time";
			case "ot":
			case "overtime":
				return "overtime";
			case "b":
			case "briefing":
				return "briefing";
			case "l":
			case "lobby":
				return "lobby";
			case "scrap":
				return "scrap";
			case "players":
				return "players";
			case "kick":
				return "kick";
			case "guard":
				return "guard";
			case "whitelist":
				return "whitelist";
			case "ban":
				return "ban";
			case "unban":
				return "unban";
			case "bans":
				return "bans";
			case "return":
				return "return";
			case "buy":
				return "buy";
			case "fastbuy":
				return "fastbuy";
			case "credits":
				return "credits";
			case "cr":
				return "credits";
			case "help":
				return "help";
			default:
				return null;
			}
		}

		internal static bool IsLobbyVerb(string canonical)
		{
			switch (canonical)
			{
			case "guard":
			case "lobby":
			case "unban":
			case "bans":
			case "kick":
			case "players":
			case "whitelist":
			case "ban":
				return true;
			default:
				return false;
			}
		}

		internal static bool IsBankSale(string[] parts)
		{
			if (parts.Length >= 2 && (parts[0].Equals("sell", StringComparison.OrdinalIgnoreCase) || parts[0].Equals("s", StringComparison.OrdinalIgnoreCase)))
			{
				return parts[1].Equals("bank", StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}

		private static bool IsHost()
		{
			if (!((Object)(object)NetworkManager.Singleton == (Object)null))
			{
				return NetworkManager.Singleton.IsHost;
			}
			return true;
		}

		internal static bool Route(string raw)
		{
			try
			{
				if (string.IsNullOrWhiteSpace(raw))
				{
					return false;
				}
				string[] array = raw.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
				string text = array[0].ToLowerInvariant();
				string text2 = CanonicalVerb(text);
				if (text2 != null && IsLobbyVerb(text2))
				{
					if (LobbyBridge.Loaded)
					{
						return false;
					}
					ChatUtil.Send("/" + text2 + " moved to the SimpleLobby plugin (SimpleSell 0.1.40). Install SimpleLobby to use it.");
					return true;
				}
				if (TournamentGate.Active && text != "help" && text2 != null && !IsBankSale(array))
				{
					ChatUtil.Send("Tournament mode: mod commands are off until the run ends.");
					return true;
				}
				switch (text)
				{
				case "sell":
				case "s":
					if (array.Length >= 2 && array[1].Equals("u", StringComparison.OrdinalIgnoreCase))
					{
						HandleScrapUpdate((array.Length >= 3) ? array[2] : "");
						return true;
					}
					if (IsBankSale(array))
					{
						if (!SimpleHQLoaded)
						{
							ChatUtil.Send("Selling from the bank needs the SimpleHQ mod.");
						}
						return true;
					}
					return SellCommands.Handle(array);
				case "stock":
				case "st":
					UtilityCommands.Stock();
					return true;
				case "m":
				case "merge":
				{
					string text6 = ItemNameResolver.JoinName(array, 1);
					if (text6.Equals("auto", StringComparison.OrdinalIgnoreCase))
					{
						ModConfig.AutoMergeOnOrbit.Value = !ModConfig.AutoMergeOnOrbit.Value;
						ChatUtil.Send(ModConfig.AutoMergeOnOrbit.Value ? "Auto-merge ON — cruiser scrap sweeps to the ship when you reach orbit." : "Auto-merge OFF.");
						return true;
					}
					UtilityCommands.Merge(string.IsNullOrWhiteSpace(text6) ? null : text6);
					return true;
				}
				case "gear":
				case "g":
					switch ((array.Length >= 2) ? array[1].ToLowerInvariant() : "")
					{
					case "add":
						UtilityCommands.GearAdd(ItemNameResolver.JoinName(array, 2));
						break;
					case "remove":
						UtilityCommands.GearRemove(ItemNameResolver.JoinName(array, 2));
						break;
					case "list":
						UtilityCommands.GearListShow();
						break;
					default:
						UtilityCommands.Gear();
						break;
					}
					return true;
				case "e":
				case "equipment":
					return EquipmentCommands.Handle(array);
				case "o":
				case "organize":
				{
					string text7 = ((array.Length >= 2) ? array[1].ToLowerInvariant() : "");
					string arg = ItemNameResolver.JoinName(array, 2).ToLowerInvariant();
					switch (text7)
					{
					case "":
						OrganizeService.OrganizeEquipment("");
						OrganizeService.OrganizeScrap("", quiet: true);
						return true;
					case "scrap":
						OrganizeService.OrganizeScrap(arg);
						return true;
					case "e":
					case "gear":
					case "equipment":
						OrganizeService.OrganizeEquipment(arg);
						return true;
					case "set":
						OrganizeService.SetAnchorAuto(array);
						return true;
					case "unset":
					case "clear":
					case "delete":
					case "remove":
					case "rm":
						OrganizeService.UnsetAuto(ItemNameResolver.JoinName(array, 2));
						return true;
					case "?":
					case "help":
						OrganizeService.SendHelp();
						return true;
					case "shelf":
						OrganizeService.SetAnchorAuto(array);
						return true;
					case "spots":
					case "list":
					case "anchors":
						OrganizeService.ListAnchors(AnchorStore.Equipment, showDefaults: true, "/organize set");
						OrganizeService.ListAnchors(AnchorStore.Scrap, showDefaults: false, "/organize set");
						return true;
					default:
						OrganizeService.OrganizeOne(ItemNameResolver.JoinName(array, 1));
						return true;
					}
				}
				case "ot":
				case "overtime":
					UtilityCommands.Overtime((array.Length >= 2) ? array[1] : "");
					return true;
				case "time":
				case "t":
					if (array.Length >= 2 && (array[1].Equals("update", StringComparison.OrdinalIgnoreCase) || array[1].Equals("u", StringComparison.OrdinalIgnoreCase)))
					{
						TimeAnnounceFeature.Handle((array.Length >= 3) ? array[2].ToLowerInvariant() : "");
						return true;
					}
					ChatUtil.Send("Time: " + TimeUtil.FormatGameTime());
					return true;
				case "b":
				case "briefing":
					DayBriefingFeature.HandleToggle((array.Length >= 2) ? array[1].ToLowerInvariant() : "");
					return true;
				case "scrap":
					switch ((array.Length >= 2) ? array[1].ToLowerInvariant() : "")
					{
					case "update":
						HandleScrapUpdate((array.Length >= 3) ? array[2] : "");
						return true;
					case "set":
						OrganizeService.SetAnchor(array, AnchorStore.Scrap, allowDefaults: false, "Usage: put the item where you want it, then /scrap set <item name> [pile|stack]", "/scrap set");
						return true;
					case "organize":
					case "o":
						ChatUtil.Send("Moved in Round 31: use /organize scrap [ship|cruiser|<item>] (or /o scrap).");
						return true;
					case "delete":
					case "remove":
					case "clear":
					case "unset":
					case "rm":
						OrganizeService.Unset(ItemNameResolver.JoinName(array, 2), AnchorStore.Scrap);
						return true;
					case "spots":
					case "anchors":
					case "list":
						OrganizeService.ListAnchors(AnchorStore.Scrap, showDefaults: false, "/scrap set");
						return true;
					default:
						ChatUtil.Send("Usage: /scrap update <mode> | set <item> [pile|stack] | remove <item> (or delete) | anchors — organizing: /organize scrap");
						return true;
					}
				case "help":
				{
					string text3 = ((array.Length >= 2) ? array[1].ToLowerInvariant() : "");
					switch (text3)
					{
					case "":
						ChatUtil.Send("SimpleSell — /help sell");
						return true;
					case "organize":
					case "o":
						if (IsHost())
						{
							OrganizeService.SendHelp();
							return true;
						}
						break;
					}
					if (text3 != "sell" && text3 != "simplesell")
					{
						return false;
					}
					if (!IsHost())
					{
						string text4 = LobbyBridge.AltCommandPrefix();
						string text5 = string.Join(", ", from x in LobbyBridge.WhitelistedCommands().Split(',')
							select "/" + x.Trim() into x
							where x.Length > 1
							select x);
						ChatUtil.Send("SimpleSell, for you: " + ((text5.Length == 0) ? "nothing yet" : text5) + (string.IsNullOrEmpty(text4) ? "" : (" (or " + text4 + " if / doesn't work)")));
						return true;
					}
					ChatUtil.Send("Selling: /sell <amount|quota|all|item [n|all]> · /sell exclude|include|exclusions");
					ChatUtil.Send("Cruiser: /merge [item|auto] · Spots: /organize [scrap|equipment] [ship|cruiser|item], /organize set|remove|delete <item>, anchors");
					ChatUtil.Send("Shotguns by shells: /organize shotgun@0|1|2 · /organize set shotgun@2 (plain 'shotgun' = every gun)");
					ChatUtil.Send("Spots & shelves: put the item where you want it, then /organize set <item> · remove: /organize remove|delete <item> · more: /help organize");
					ChatUtil.Send("Info: /stock · /gear [add|remove <item>] · /time [update l|p|o] · /briefing l|p|o · /s u l|p|o|ship|cruiser|both|split");
					ChatUtil.Send(LobbyBridge.Loaded ? "Lobby, whitelist, bans, guard: SimpleLobby — /help lobby" : "Lobby, whitelist, bans, guard: moved to the SimpleLobby plugin (not installed)");
					ChatUtil.Send("Buy (Company): /buy <item> [qty] · /buy 2 stun 2 weed 4 belt (over 12 → batches; /buy cancel) · /buy list · /credits · reorder: /buy ORD-code · favourites: /buy save|delete <name>, /buy <name> · more: /buy help");
					ChatUtil.Send("FastBuy (saved, default off, host only): /ss fastbuy on|off or /fastbuy on|off — in orbit or at the Company, paid items appear on the ship floor instead of the dropship. Cruiser unchanged.");
					return true;
				}
				case "return":
					DeskReturnCommands.Return((array.Length >= 2) ? array[1] : null);
					return true;
				case "buy":
					if (SimpleBuyLoaded)
					{
						return false;
					}
					BuyCommands.Buy(array);
					return true;
				case "fastbuy":
					FastBuyCommands.Handle(array);
					return true;
				case "ss":
				{
					if (!FastBuyLogic.TryParseCommand(array, out var _))
					{
						return false;
					}
					if (TournamentGate.Active)
					{
						ChatUtil.Send("Tournament mode: mod commands are off until the run ends.");
						return true;
					}
					FastBuyCommands.Handle(array);
					return true;
				}
				case "cr":
				case "credits":
					if (SimpleBuyLoaded)
					{
						return false;
					}
					BuyCommands.Credits();
					return true;
				default:
					return false;
				}
			}
			catch (Exception arg2)
			{
				Plugin.Logger.LogError((object)$"[CommandRouter.Route] {arg2}");
				ChatUtil.Send("Command failed — see BepInEx log.");
				return true;
			}
		}

		private static void HandleScrapUpdate(string modeToken)
		{
			switch (modeToken.ToLowerInvariant())
			{
			case "l":
			case "live":
				ScrapUpdateFeature.Enable(broadcast: true);
				break;
			case "p":
			case "private":
				ScrapUpdateFeature.Enable(broadcast: false);
				break;
			case "o":
			case "off":
				ScrapUpdateFeature.Disable();
				break;
			case "ship":
				ScrapUpdateFeature.SetDisplayMode(UpdateDisplayMode.Ship);
				break;
			case "cruiser":
				ScrapUpdateFeature.SetDisplayMode(UpdateDisplayMode.Cruiser);
				break;
			case "both":
				ScrapUpdateFeature.SetDisplayMode(UpdateDisplayMode.Combined);
				break;
			case "split":
				ScrapUpdateFeature.SetDisplayMode(UpdateDisplayMode.Breakdown);
				break;
			default:
				ChatUtil.Send("Usage: /s u l (live) | p (private) | o (off) | ship | cruiser | both | split");
				break;
			}
		}
	}
	internal enum UpdateDisplayMode
	{
		Ship,
		Cruiser,
		Combined,
		Breakdown
	}
	internal static class ModConfig
	{
		internal static ConfigEntry<int> MaxQuantityPerCommand { get; private set; }

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

		internal static ConfigEntry<int> ConfirmThreshold { get; private set; }

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

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

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

		internal static ConfigEntry<int> MaxBatchedOrderItems { get; private set; }

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

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

		internal static ConfigEntry<float> CompanyDropshipLeaveDelay { get; private set; }

		internal static ConfigEntry<float> NextBatchArrivalSeconds { get; private set; }

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

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

		internal static ConfigEntry<UpdateDisplayMode> UpdateMode { get; private set; }

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

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

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

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

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

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

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

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

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

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

		internal static ConfigEntry<float> DeskGuardRange { get; private set; }

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

		internal static ConfigEntry<float> CruiserTransferRange { get; private set; }

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

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

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

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

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

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

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

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

		internal static void Init(ConfigFile file)
		{
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Expected O, but got Unknown
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Expected O, but got Unknown
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_017a: Expected O, but got Unknown
			MaxQuantityPerCommand = file.Bind<int>("Buying", "MaxQuantityPerCommand", 12, "Most of one item a single /buy line can order (per item in a multi-buy too). Vanilla dropship cap is 12 total.");
			ConfirmLargePurchases = file.Bind<bool>("Buying", "ConfirmLargePurchases", true, "Ask for /buy confirm before an expensive order.");
			ConfirmThreshold = file.Bind<int>("Buying", "ConfirmThreshold", 500, "An order costing at least this many credits asks for confirmation.");
			AllowMultiItemBuy = file.Bind<bool>("Buying", "AllowMultiItemBuy", true, "Allow one /buy line to order several items (e.g. /buy 2 stun 2 weed 4 belt). Off = legacy single-item syntax only (/buy <item> [qty]).");
			ShowOrderCode = file.Bind<bool>("Buying", "ShowOrderCode", true, "Show a reorder code (e.g. ORD-XXXX-XXXX) after a successful /buy. /buy <code> orders the same again in any lobby. Off only hides it in the receipt; codes and /buy save still work.");
			BatchLargeOrders = file.Bind<bool>("Buying", "BatchLargeOrders", true, "Orders over the vanilla 12-item dropship cap are sent in batches of up to 12; each next batch is ordered (and charged) once the previous dropship has been opened. Off = refuse orders over the cap (0.1.35 behaviour).");
			MaxBatchedOrderItems = file.Bind<int>("Buying", "MaxBatchedOrderItems", 60, new ConfigDescription("Most items one batched /buy can order in total.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(12, 255), Array.Empty<object>()));
			CompanyDropshipFastLeave = file.Bind<bool>("Buying", "CompanyDropshipFastLeave", true, "At the Company, the item dropship takes off right after its doors are opened and the items dropped, instead of idling for vanilla's 30 seconds. Host-side, synced to clients with the vanilla leave RPC. The cruiser drop is untouched.");
			CompanyDropshipLeaveDelay = file.Bind<float>("Buying", "CompanyDropshipLeaveDelay", 2f, new ConfigDescription("Seconds after the doors open before the dropship leaves (fast leave).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 30f), Array.Empty<object>()));
			FastBuy = file.Bind<bool>("Buying", "FastBuy", false, "OFF by default, and remembered. When on, ordinary paid item purchases made in orbit or at the Company are placed on the ship floor instead of waiting for the delivery dropship. The game still charges, once. Vehicles still use the dropship. Other moons are unchanged. Host only: /ss fastbuy on|off or /fastbuy on|off.");
			NextBatchArrivalSeconds = file.Bind<float>("Buying", "NextBatchArrivalSeconds", 15f, new ConfigDescription("With fast leave, roughly how many seconds after take-off the next batch lands (vanilla wait is 40s per order).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(8f, 40f), Array.Empty<object>()));
			CommandPrefix = file.Bind<string>("General", "CommandPrefix", "/", "Prefix for chat commands (e.g. /sell). (SimpleLobby has its own, copied from this one.)");
			DayBriefing = file.Bind<bool>("General", "DayBriefing", false, "Post a summary when the ship lands: interior type, scrap count + value, hives. Off every launch (session toggle). Turn on in-game: /briefing live | private.");
			DayBriefingBroadcast = file.Bind<bool>("General", "DayBriefingBroadcast", false, "If true the day briefing goes to the whole crew; if false (default), host-only. In-game: /briefing live | private.");
			ConfirmSellAll = file.Bind<bool>("Selling", "ConfirmSellAll", false, "If true, /sell all shows the count/value and requires /sell confirm within 15s. Default off: /sell all fires immediately.");
			MergeExcludeKeywords = file.Bind<string>("Merge", "ExcludeKeywords", "", "Comma-separated name keywords /merge never touches (normalized: lowercase, no spaces — e.g. 'goldbar, pickle'). Empty by default.");
			EquipmentNonGearKeywords = file.Bind<string>("Equipment", "NonGearKeywords", "clipboard, sticky note", "Comma-separated name keywords treated as NOT equipment (excluded from /gear and /organize equipment). Defaults cover the ship's clipboard and sticky note.");
			GearItems = file.Bind<string>("Gear", "Items", "", "Comma-separated item names declared as gear (may include scrap). Gear-listed items show in /gear, organize as equipment, and are excluded from selling, /merge, and /organize scrap. Managed in-game via /gear add|remove.");
			GearSeeded = file.Bind<bool>("Gear", "Seeded", false, "Internal flag — do not edit. (One-time seeding of the four weapons into the gear list.)");
			WeaponExclusionsMigrated = file.Bind<bool>("Gear", "WeaponExclusionsMigrated", false, "Internal flag — do not edit. (One-time removal of the Round 25 weapon sell-exclusions; the gear list is their single source of truth now.)");
			CruiserTransferRange = file.Bind<float>("General", "CruiserTransferRange", 30f, "Max distance (meters) between cruiser and ship for moving items between them (/e o cross-moves, /merge cruiser sweep). Beyond this the cruiser counts as 'away' and items stay put — no beaming across the map. Magnetted always counts as docked.");
			ExcludedItems = file.Bind<string>("Selling", "ExcludedItems", "", "Comma-separated item names permanently excluded from selling. Managed in-game via /sell exclude, /sell include, /sell exclusions.");
			UpdateMode = file.Bind<UpdateDisplayMode>("ScrapUpdate", "DisplayMode", UpdateDisplayMode.Combined, "What the hourly scrap update line shows. Ship = ship only, Cruiser = cruiser only, Combined = one total, Breakdown = ship + cruiser + total. Changeable in-game: /s u ship | cruiser | both | split");
			AutoMergeOnOrbit = file.Bind<bool>("Merge", "AutoMergeOnOrbit", false, "OFF by default (Round 65). When on, cruiser scrap is moved to piles by the ship door every time the ship reaches orbit, the same as typing /merge. Toggle in-game: /merge auto. Placement is network-synced (Round 26) — all players see items move.");
			RetiredSeedsDropped = file.Bind<bool>("Selling", "RetiredSeedsDropped", false, "Internal: one-time removal of protections SimpleSell no longer seeds (the Homemade flashbang). /sell exclude puts anything back for good.");
			DefaultExclusionsSeeded = file.Bind<bool>("Selling", "DefaultExclusionsSeeded", false, "Internal flag — do not edit.");
			EquipShipAnchor = file.Bind<string>("Equipment", "ShipAnchor", "", "Ship gear-stack spot as x,y,z in ship-local space. Set in-game: put any item down on the ship where gear without its own spot should go, then /equipment set ship.");
			EquipCruiserAnchor = file.Bind<string>("Equipment", "CruiserAnchor", "", "Cruiser gear-stack spot as x,y,z in the cruiser's item space. Set in-game: put any item down in the cruiser, then /equipment set cruiser.");
			EquipItemAnchors = file.Bind<string>("Equipment", "ItemAnchors", "", "Per-item gear spots as 'Item Name|ship|x,y,z|pile;...' ('|exact' = keep that height). Managed in-game: put the item where you want it, then /e set <item> [pile|stack] (or /organize set); /e remove <item>, /e anchors. A shotgun can also have one spot per shells loaded: 'Shotgun@2', 'Shotgun@1', 'Shotgun@0' (/organize set shotgun@2); a gun without its own @ spot uses the plain 'Shotgun' spot.");
			StackLeastFullOnTop = file.Bind<bool>("Organize", "StackLeastFullOnTop", true, "Stack-mode spots (/organize set <item> stack): the item with the LEAST left goes on top, so it's the one you grab first, and the fullest is at the bottom. 'Left' = battery charge (flashlights, walkie-talkies, jetpack, boombox, zap gun, radar booster...; dead battery = 0), shotgun shells loaded, spray paint / weed killer tank, TZP fuel. Items without any of these keep their order, under the ones that have them. Host's values. false = unsorted, as before 0.1.33. Pile mode is not affected.");
			GroupShotgunsByShells = file.Bind<bool>("Equipment", "GroupShotgunsByShells", true, "When shotguns with different shells loaded share one spot (the plain Shotgun spot, the Two-handed spot or the default gear spot), /organize lays them out as separate groups: 2 shells at the spot, then 1, then 0 beside it. false = one pile, as before 0.1.32. Applies to gear and scrap spots alike.");
			ScrapItemAnchors = file.Bind<string>("Scrap", "ItemAnchors", "", "Per-item SCRAP spots, same format as Equipment ItemAnchors (Shotgun@N spots included). Managed in-game via put the item where you want it, then /scrap set <item> [pile|stack] (or /organize set); /scrap remove <item>, /scrap anchors. Anchored scrap types are skipped by /merge and moved by /scrap organize.");
			AutoReturnBeamedItems = file.Bind<bool>("Selling", "AutoReturnBeamedItems", true, "Put items back on the ship automatically when a modded client beams them to the sell desk and the guard refuses the sale, so they aren't stranded on the counter.");
			DeskGuardRange = file.Bind<float>("Selling", "DeskGuardRange", 12f, "Max distance (meters) an item may be from the sell desk when a client places it on the counter. Vanilla physical placement is always within a few meters; modded clients beaming items from the ship get blocked. (With SimpleLobby's item firewall on, SimpleLobby checks desk deposits instead, with its own [Lobby] DeskGuardRange.)");
			DeskSettingsMoved = file.Bind<bool>("Selling", "DeskSettingsMoved", false, "Internal flag — do not edit. (SimpleSell 0.1.40 one-time copy of DeskGuardRange and AutoReturnBeamedItems from the old [Lobby] section.)");
			if (DeskSettingsMoved.Value)
			{
				return;
			}
			try
			{
				Dictionary<(string, string), string> dictionary = ReadCfg(file.ConfigFilePath);
				if (dictionary.TryGetValue(("Lobby", "DeskGuardRange"), out var value))
				{
					((ConfigEntryBase)DeskGuardRange).SetSerializedValue(value);
				}
				if (dictionary.TryGetValue(("Lobby", "AutoReturnBeamedItems"), out var value2))
				{
					((ConfigEntryBase)AutoReturnBeamedItems).SetSerializedValue(value2);
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)("[Config] couldn't copy the old [Lobby] desk settings: " + ex.Message));
			}
			DeskSettingsMoved.Value = true;
		}

		private static Dictionary<(string, string), string> ReadCfg(string path)
		{
			Dictionary<(string, string), string> dictionary = new Dictionary<(string, string), string>();
			if (string.IsNullOrEmpty(path) || !File.Exists(path))
			{
				return dictionary;
			}
			string item = "";
			string[] array = File.ReadAllLines(path);
			foreach (string text in array)
			{
				string text2 = text.Trim();
				if (text2.Length == 0 || text2.StartsWith("#") || text2.StartsWith(";"))
				{
					continue;
				}
				if (text2.StartsWith("[") && text2.EndsWith("]"))
				{
					item = text2.Substring(1, text2.Length - 2).Trim();
					continue;
				}
				int num = text2.IndexOf('=');
				if (num > 0)
				{
					dictionary[(item, text2.Substring(0, num).Trim())] = text2.Substring(num + 1).Trim();
				}
			}
			return dictionary;
		}
	}
	[BepInPlugin("local.simplesell", "SimpleSell", "0.1.44")]
	public class Plugin : BaseUnityPlugin
	{
		private readonly Harmony _harmony = new Harmony("local.simplesell");

		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);
			ModConfig.DayBriefing.Value = false;
			_harmony.PatchAll();
			Logger.LogInfo((object)"SimpleSell v0.1.44 loaded. Host-only chat commands armed.");
		}
	}
	public static class PluginInfo
	{
		public const string Guid = "local.simplesell";

		public const string Name = "SimpleSell";

		public const string Version = "0.1.44";
	}
	public static class SellApi
	{
		public static string[] GearItems()
		{
			try
			{
				return GearList.All.ToArray();
			}
			catch
			{
				return new string[0];
			}
		}

		public static void NoteDeskBlocked(GrabbableObject obj)
		{
			try
			{
				DeskReturnCommands.NoteBlocked(obj);
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[SellApi.NoteDeskBlocked] {arg}");
			}
		}

		public static void ForgetPlacement(GrabbableObject obj)
		{
			try
			{
				PlacementTracker.Forget(obj);
			}
			catch
			{
			}
		}
	}
	public static class WhitelistInfo
	{
		public static bool IsWhitelisted(ulong steamId)
		{
			return LobbyBridge.IsWhitelisted(steamId);
		}
	}
}
namespace SimpleSell.Patches
{
	[HarmonyPatch(typeof(HUDManager), "AddPlayerChatMessageServerRpc")]
	internal static class WhitelistChatPatch
	{
		[HarmonyPrefix]
		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 = LobbyBridge.CommandText(chatMessage);
				if (text == null)
				{
					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;
				}
				if (!LobbyBridge.IsTrustedClient(val.actualClientId))
				{
					return true;
				}
				if (CommandRouter.SimpleHQLoaded && CommandRouter.IsBankSale(text.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries)))
				{
					return true;
				}
				string text2 = text.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault() ?? "";
				string text3 = CommandRouter.CanonicalVerb(text2.ToLowerInvariant());
				if (text3 == null || CommandRouter.IsLobbyVerb(text3))
				{
					return true;
				}
				if (text3 != "help" && !LobbyBridge.IsCommandAllowed(text3))
				{
					ChatUtil.Send("'" + text3 + "' is host-only.", broadcast: true);
					return false;
				}
				ManualLogSource logger = Plugin.Logger;
				if (logger != null)
				{
					logger.LogInfo((object)("[Whitelist] " + val.playerUsername + " ran /" + text));
				}
				ChatUtil.BroadcastReplies = true;
				ChatUtil.InvokerName = val.playerUsername;
				ChatUtil.Invoker = val;
				bool flag;
				try
				{
					flag = CommandRouter.Route(text);
				}
				finally
				{
					ChatUtil.BroadcastReplies = false;
					ChatUtil.InvokerName = null;
					ChatUtil.Invoker = null;
				}
				return !flag;
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[WhitelistChatPatch] {arg}");
				return true;
			}
		}
	}
	[HarmonyPatch(typeof(HUDManager), "AddTextToChatOnServer")]
	internal static class ChatCommandPatch
	{
		[HarmonyPrefix]
		private static bool Prefix(string chatMessage, int playerId)
		{
			try
			{
				Plugin.Logger.LogDebug((object)$"[ChatPatch] playerId={playerId} msg={chatMessage}");
				if (string.IsNullOrEmpty(chatMessage))
				{
					return true;
				}
				if (!chatMessage.StartsWith(ModConfig.CommandPrefix.Value))
				{
					return true;
				}
				if (!CommandRouter.IsSenderTheHost(playerId))
				{
					return true;
				}
				string raw = chatMessage.Substring(ModConfig.CommandPrefix.Value.Length).Trim();
				bool flag = CommandRouter.Route(raw);
				return !flag;
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[ChatCommandPatch] {arg}");
				return true;
			}
		}
	}
	[HarmonyPatch(typeof(ItemDropship), "OpenShipDoorsOnServer")]
	internal static class DropshipFastLeavePatch
	{
		[HarmonyPostfix]
		private static void Postfix(ItemDropship __instance)
		{
			try
			{
				if (ModConfig.CompanyDropshipFastLeave.Value && !((Object)(object)NetworkManager.Singleton == (Object)null) && NetworkManager.Singleton.IsServer && !((Object)(object)__instance == (Object)null) && !__instance.deliveringVehicle && BuyService.AtCompany())
				{
					((MonoBehaviour)CoroutineRunner.Instance).StartCoroutine(LeaveSoon(__instance));
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[FastLeave] {arg}");
			}
		}

		private static IEnumerator LeaveSoon(ItemDropship ship)
		{
			float waited = 0f;
			while ((Object)(object)ship != (Object)null && !ship.shipDoorsOpened && waited < 5f)
			{
				waited += Time.deltaTime;
				yield return null;
			}
			yield return (object)new WaitForSeconds(ModConfig.CompanyDropshipLeaveDelay.Value);
			try
			{
				if (!((Object)(object)ship == (Object)null) && !((Object)(object)NetworkManager.Singleton == (Object)null) && NetworkManager.Singleton.IsServer && ship.shipLanded && ship.shipDoorsOpened && !ship.deliveringVehicle && BuyService.AtCompany())
				{
					ship.timesPlayedWithoutTurningOff = 0;
					ship.shipLanded = false;
					Terminal t = BuyService.FindTerminal();
					if (BuyCommands.BatchWantsQuickReturn(t))
					{
						ship.shipTimer = Mathf.Max(0f, 40f - ModConfig.NextBatchArrivalSeconds.Value);
					}
					ship.ShipLeaveClientRpc();
					Plugin.Logger.LogInfo((object)"[FastLeave] Company dropship sent off after delivery.");
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[FastLeave] {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(Terminal), "BuyItemsServerRpc")]
	internal static class FastBuyServerRpcPatch
	{
		[HarmonyPrefix]
		private static void Prefix(Terminal __instance, ref int __state)
		{
			__state = (__instance?.orderedItemsFromTerminal?.Count).GetValueOrDefault();
		}

		[HarmonyPostfix]
		private static void Postfix(Terminal __instance, int[] boughtItems, int __state)
		{
			FastBuyService.FromServerRpc(__instance, boughtItems, __state);
		}
	}
	[HarmonyPatch(typeof(Terminal), "LoadNewNodeIfAffordable")]
	internal static class FastBuyLoadPatch
	{
		private static int _qty;

		[HarmonyPrefix]
		private static void Prefix(Terminal __instance, ref int __state)
		{
			FastBuyScope.Depth++;
			__state = (__instance?.orderedItemsFromTerminal?.Count).GetValueOrDefault();
			_qty = __instance?.playerDefinedAmount ?? 0;
		}

		[HarmonyPostfix]
		private static void Postfix(Terminal __instance, TerminalNode node, int __state)
		{
			try
			{
				FastBuyService.FromLoadNode(__instance, node, __state, _qty);
			}
			finally
			{
				if (FastBuyScope.Depth > 0)
				{
					FastBuyScope.Depth--;
				}
			}
		}
	}
	[HarmonyPatch(typeof(TimeOfDay), "Update")]
	internal static class TimeOfDayUpdatePatch
	{
		private static int _lastHour = -1;

		[HarmonyPostfix]
		private static void Postfix(TimeOfDay __instance)
		{
			try
			{
				if (!((Object)(object)NetworkManager.Singleton == (Object)null) && NetworkManager.Singleton.IsHost && __instance.hour != _lastHour)
				{
					_lastHour = __instance.hour;
					if (!TournamentGate.Active)
					{
						ScrapUpdateFeature.OnHourChanged();
						TimeAnnounceFeature.OnHourChanged();
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[TimeOfDayUpdatePatch] {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "Start")]
	internal static class StartOfRoundStartPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			try
			{
				DefaultExclusionsSeeder.TrySeed();
				DeskGuard.Reset();
				TournamentGate.Register();
				((MonoBehaviour)CoroutineRunner.Instance).StartCoroutine(StartupLine());
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[StartOfRoundStartPatch] {arg}");
			}
		}

		private static IEnumerator StartupLine()
		{
			yield return (object)new WaitForSeconds(3f);
			SuiteAnnounce.MaybeAnnounce("local.simplesell");
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton != (Object)null && singleton.IsHost && !LobbyBridge.Loaded)
			{
				ChatUtil.Send("SimpleLobby isn't installed: no bans, whitelist, lobby guard or firewall in this lobby.");
			}
		}
	}
	[HarmonyPatch(typeof(RoundManager), "FinishGeneratingNewLevelClientRpc")]
	internal static class ShipLandedPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			try
			{
				if (!((Object)(object)NetworkManager.Singleton == (Object)null) && NetworkManager.Singleton.IsHost)
				{
					DayBriefingFeature.OnLandingStarted();
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[ShipLandedPatch] {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(GiantKiwiAI), "SpawnEggsClientRpc")]
	internal static class SapsuckerEggsPatch
	{
		[HarmonyPostfix]
		private static void Postfix(GiantKiwiAI __instance, int[] eggScrapValues)
		{
			try
			{
				if (!((Object)(object)NetworkManager.Singleton == (Object)null) && NetworkManager.Singleton.IsHost)
				{
					DayBriefingFeature.OnEggsSpawned(((Object)__instance).GetInstanceID(), eggScrapValues);
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[SapsuckerEggsPatch] {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "ShipHasLeft")]
	internal static class ShipHasLeftPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			try
			{
				if (!((Object)(object)NetworkManager.Singleton == (Object)null) && NetworkManager.Singleton.IsHost)
				{
					ScrapUpdateFeature.OnReachedOrbit();
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[ShipHasLeftPatch] {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(ItemDropship), "OpenShipDoorsOnServer")]
	internal static class DropshipOpenPatch
	{
		[HarmonyPrefix]
		private static void Prefix(ItemDropship __instance, out HashSet<int> __state)
		{
			__state = null;
			try
			{
				if (!((Object)(object)NetworkManager.Singleton == (Object)null) && NetworkManager.Singleton.IsHost && __instance.shipLanded && !__instance.shipDoorsOpened && __instance.itemsToDeliver.Count != 0)
				{
					__state = DropshipDeliveries.SnapshotIds();
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[DropshipOpenPatch.Prefix] {arg}");
			}
		}

		[HarmonyPostfix]
		private static void Postfix(HashSet<int> __state)
		{
			try
			{
				if (__state != null)
				{
					DropshipDeliveries.RecordNew(__state);
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[DropshipOpenPatch.Postfix] {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(DepositItemsDesk), "AddObjectToDeskServerRpc")]
	internal static class DeskBeamGuardPatch
	{
		[HarmonyPrefix]
		private static bool Prefix(DepositItemsDesk __instance, NetworkObjectReference grabbableObjectNetObject)
		{
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if ((Object)(object)singleton == (Object)null || !singleton.IsServer)
				{
					return true;
				}
				if (LobbyBridge.ItemFirewallActive())
				{
					return true;
				}
				if (SaleDisplayAggregator.BatchedSaleInProgress)
				{
					return true;
				}
				NetworkObject val = default(NetworkObject);
				if (!((NetworkObjectReference)(ref grabbableObjectNetObject)).TryGet(ref val, (NetworkManager)null) || (Object)(object)val == (Object)null)
				{
					return true;
				}
				GrabbableObject component = ((Component)val).GetComponent<GrabbableObject>();
				if ((Object)(object)component == (Object)null)
				{
					return true;
				}
				Vector3 val2;
				if (!((Object)(object)__instance.triggerCollider != (Object)null))
				{
					val2 = ((Component)__instance).transform.position;
				}
				else
				{
					Bounds bounds = ((Collider)__instance.triggerCollider).bounds;
					val2 = ((Bounds)(ref bounds)).center;
				}
				Vector3 val3 = val2;
				float num = Vector3.Distance(((Component)component).transform.position, val3);
				if (num <= ModConfig.DeskGuardRange.Value)
				{
					return true;
				}
				DeskGuard.OnDeskBeamBlocked(component, num, LobbyBridge.CurrentRemoteSender());
				return false;
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[DeskBeamGuardPatch] {arg}");
				return true;
			}
		}
	}
	[HarmonyPatch(typeof(PlayerControllerB), "PlaceGrabbableObject")]
	internal static class PlaceGrabbableObjectTrackPatch
	{
		[HarmonyPostfix]
		private static void Postfix(PlayerControllerB __instance, GrabbableObject placeObject)
		{
			try
			{
				if ((Object)(object)NetworkManager.Singleton != (Object)null && NetworkManager.Singleton.IsServer)
				{
					PlacementTracker.Recorded(__instance, placeObject);
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("[PlacementTracker.Place] " + ex.Message));
			}
		}
	}
	[HarmonyPatch(typeof(PlayerControllerB), "SetObjectAsNoLongerHeld")]
	internal static class SetObjectAsNoLongerHeldTrackPatch
	{
		[HarmonyPostfix]
		private static void Postfix(PlayerControllerB __instance, GrabbableObject dropObject)
		{
			try
			{
				if ((Object)(object)NetworkManager.Singleton != (Object)null && NetworkManager.Singleton.IsServer)
				{
					PlacementTracker.Recorded(__instance, dropObject);
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("[PlacementTracker.Drop] " + ex.Message));
			}
		}
	}
	[HarmonyPatch(typeof(DepositItemsDesk), "SellAndDisplayItemProfits")]
	internal static class SellAndDisplayItemProfitsPatch
	{
		[HarmonyPrefix]
		private static void Prefix(int profit, int newGroupCredits)
		{
			if (SaleDisplayAggregator.BatchedSaleInProgress && !((Object)(object)NetworkManager.Singleton == (Object)null) && NetworkManager.Singleton.IsServer)
			{
				SaleDisplayAggregator.NoteBatchSold();
				SaleDisplayAggregator.Accumulate(profit, newGroupCredits);
			}
		}
	}
	[HarmonyPatch(typeof(HUDManager), "DisplayCreditsEarning")]
	internal static class DisplayCreditsEarningPatch
	{
		[HarmonyPrefix]
		private static bool Prefix()
		{
			return !SaleDisplayAggregator.ShouldSuppressDisplay();
		}
	}
}
namespace SimpleSell.Features
{
	internal static class BuyService
	{
		internal readonly struct StoreEntry
		{
			internal readonly string Name;

			internal readonly TerminalNode Node;

			internal readonly int ItemIndex;

			internal readonly bool IsVehicle;

			internal readonly int BaseCost;

			internal readonly int SaleIndex;

			internal StoreEntry(string name, TerminalNode node, int itemIndex, int baseCost, int saleIndex, bool isVehicle = false)
			{
				Name = name;
				Node = node;
				ItemIndex = itemIndex;
				BaseCost = baseCost;
				SaleIndex = saleIndex;
				IsVehicle = isVehicle;
			}
		}

		internal const int DropshipCapacity = 12;

		internal static bool AtCompany()
		{
			return (Object)(object)Object.FindObjectOfType<DepositItemsDesk>() != (Object)null;
		}

		internal static int RemainingDropshipSlots(Terminal terminal)
		{
			if ((Object)(object)terminal == (Object)null)
			{
				return 0;
			}
			int num = 12 - terminal.numberOfItemsInDropship;
			if (num >= 0)
			{
				return num;
			}
			return 0;
		}

		internal static bool VehicleOrderAllowed(Terminal terminal, out string why)
		{
			why = null;
			if ((Object)(object)Object.FindObjectOfType<VehicleController>() != (Object)null)
			{
				why = "There's already a cruiser here.";
				return false;
			}
			if (terminal.vehicleInDropship)
			{
				why = "A cruiser is already waiting in the dropship.";
				return false;
			}
			ItemDropship val = Object.FindObjectOfType<ItemDropship>();
			if ((Object)(object)val != (Object)null && val.deliveringVehicle)
			{
				why = "A cruiser is already on its way.";
				return false;
			}
			if (terminal.numberOfItemsInDropship > 0)
			{
				why = $"The dropship still has {terminal.numberOfItemsInDropship} item(s) to deliver; order the cruiser after they arrive.";
				return false;
			}
			return true;
		}

		internal static Terminal FindTerminal()
		{
			try
			{
				return Object.FindObjectOfType<Terminal>();
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[BuyService.FindTerminal] {arg}");
				return null;
			}
		}

		internal static List<StoreEntry> GetStore(Terminal terminal)
		{
			List<StoreEntry> list = new List<StoreEntry>();
			try
			{
				if (terminal?.terminalNodes?.allKeywords == null || terminal.buyableItemsList == null)
				{
					return list;
				}
				TerminalKeyword val = ((IEnumerable<TerminalKeyword>)terminal.terminalNodes.allKeywords).FirstOrDefault((Func<TerminalKeyword, bool>)((TerminalKeyword k) => (Object)(object)k != (Object)null && k.isVerb && k.word == "buy"));
				if (val?.compatibleNouns == null)
				{
					return list;
				}
				CompatibleNoun[] compatibleNouns = val.compatibleNouns;
				foreach (CompatibleNoun val2 in compatibleNouns)
				{
					TerminalNode result = val2.result;
					if ((Object)(object)result == (Object)null || (Object)(object)val2.noun == (Object)null)
					{
						continue;
					}
					int idx = result.buyItemIndex;
					int vehicleIdx = result.buyVehicleIndex;
					bool flag = vehicleIdx >= 0 && terminal.buyableVehicles != null && vehicleIdx < terminal.buyableVehicles.Length;
					if (!flag && (idx < 0 || idx >= terminal.buyableItemsList.Length))
					{
						continue;
					}
					TerminalNode val3 = ResolvePurchaseNode(result);
					if ((Object)(object)val3 == (Object)null)
					{
						Plugin.Logger.LogWarning((object)("[BuyService] no confirm option for '" + val2.noun.word + "' — skipped."));
						continue;
					}
					if (flag)
					{
						BuyableVehicle val4 = terminal.buyableVehicles[vehicleIdx];
						string name = (string.IsNullOrEmpty(val4?.vehicleDisplayName) ? val2.noun.word : val4.vehicleDisplayName);
						if (!list.Exists((StoreEntry x) => x.IsVehicle && x.ItemIndex == vehicleIdx))
						{
							list.Add(new StoreEntry(name, val3, vehicleIdx, val3.itemCost, terminal.buyableItemsList.Length + vehicleIdx, isVehicle: true));
						}
						continue;
					}
					string text = terminal.buyableItemsList[idx]?.itemName ?? val2.noun.word;
					int num2 = list.FindIndex((StoreEntry x) => !x.IsVehicle && x.ItemIndex == idx);
					if (num2 >= 0)
					{
						Plugin.Logger.LogDebug((object)("[BuyService] '" + val2.noun.word + "' is another name for " + text + ", skipped."));
					}
					else
					{
						list.Add(new StoreEntry(text, val3, idx, terminal.buyableItemsList[idx]?.creditsWorth ?? 0, idx));
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[BuyService.GetStore] {arg}");
			}
			return list;
		}

		internal static TerminalNode ResolvePurchaseNode(TerminalNode node)
		{
			if ((Object)(object)node == (Object)null)
			{
				return null;
			}
			if (!node.isConfirmationNode)
			{
				return node;
			}
			if (node.terminalOptions == null)
			{
				return null;
			}
			TerminalNode val = null;
			CompatibleNoun[] terminalOptions = node.terminalOptions;
			foreach (CompatibleNoun val2 in terminalOptions)
			{
				TerminalNode val3 = val2?.result;
				if (!((Object)(object)val3 == (Object)null) && !val3.isConfirmationNode)
				{
					if (val3.buyItemIndex >= 0 && val3.buyItemIndex == node.buyItemIndex)
					{
						return val3;
					}
					if (val3.buyVehicleIndex >= 0 && val3.buyVehicleIndex == node.buyVehicleIndex)
					{
						return val3;
					}
					if ((Object)(object)val == (Object)null && (Object)(object)val2.noun != (Object)null && val2.noun.word == "confirm")
					{
						val = val3;
					}
				}
			}
			return val;
		}

		internal static int UnitPrice(Terminal terminal, in StoreEntry e)
		{
			try
			{
				int num = 100;
				if (terminal.itemSalesPercentages != null && e.SaleIndex >= 0 && e.SaleIndex < terminal.itemSalesPercentages.Length)
				{
					num = terminal.itemSalesPercentages[e.SaleIndex];
				}
				return (int)((float)e.BaseCost * ((float)num / 100f));
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[BuyService.UnitPrice] {arg}");
				return 0;
			}
		}

		internal static int SalePercent(Terminal terminal, in StoreEntry e)
		{
			try
			{
				if (terminal.itemSalesPercentages == null || e.SaleIndex < 0 || e.SaleIndex >= terminal.itemSalesPercentages.Length)
				{
					return 100;
				}
				return terminal.itemSalesPercentages[e.SaleIndex];
			}
			catch
			{
				return 100;
			}
		}

		internal static bool Resolve(List<StoreEntry> store, string query, out StoreEntry match, out string error)
		{
			match = default(StoreEntry);
			error = null;
			string q = (query ?? "").Trim().ToLowerInvariant();
			if (q.Length == 0)
			{
				error = "Nothing to buy — try /buy list.";
				return false;
			}
			if (TryPick(store.Where((StoreEntry e) => e.Name.ToLowerInvariant() == q).ToList(), query, ref match, ref error, out var decided))
			{
				return decided;
			}
			if (TryPick(store.Where((StoreEntry e) => e.Name.ToLowerInvariant().StartsWith(q)).ToList(), query, ref match, ref error, out decided))
			{
				return decided;
			}
			if (TryPick(store.Where((StoreEntry e) => e.Name.ToLowerInvariant().Contains(q)).ToList(), query, ref match, ref error, out decided))
			{
				return decided;
			}
			error = "No store item matches '" + query + "'. /buy list shows what's available.";
			return false;
		}

		private static bool TryPick(List<StoreEntry> candidates, string query, ref StoreEntry match, ref string error, out bool decided)
		{
			decided = false;
			if (candidates.Count == 0)
			{
				return false;
			}
			if (candidates.Select((StoreEntry e) => e.ItemIndex).Distinct().Count() == 1)
			{
				match = candidates[0];
				decided = true;
				return true;
			}
			string text = string.Join(", ", candidates.Select((StoreEntry e) => e.Name).Distinct());
			error = "'" + query + "' is ambiguous: " + text;
			Plugin.Logger.LogInfo((object)$"[Buy] '{query}' matched {candidates.Count} entries: {text}");
			return true;
		}

		internal static int Purchase(Terminal terminal, in StoreEntry e, int quantity, out int creditsAfter)
		{
			int num = (creditsAfter = terminal.groupCredits);
			try
			{
				int playerDefinedAmount = terminal.playerDefinedAmount;
				terminal.playerDefinedAmount = quantity;
				terminal.LoadNewNodeIfAffordable(e.Node);
				terminal.playerDefinedAmount = playerDefinedAmount;
				creditsAfter = terminal.groupCredits;
				return num - creditsAfter;
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[BuyService.Purchase] {arg}");
				creditsAfter = terminal.groupCredits;
				return num - creditsAfter;
			}
		}
	}
	internal static class DayBriefingFeature
	{
		private const float DoorWaitLimit = 60f;

		private static bool _watching;

		private static readonly HashSet<int> _eggNestsAnnounced = new HashSet<int>();

		internal static void OnLandingStarted()
		{
			_eggNestsAnnounced.Clear();
			if (ModConfig.DayBriefing.Value && !_watching && !TournamentGate.Active)
			{
				_watching = true;
				Plugin.Logger.LogInfo((object)"[DayBriefing] level ready, watching for the ship doors.");
				((MonoBehaviour)CoroutineRunner.Instance).StartCoroutine(BriefWhenDoorsOpen());
			}
		}

		private static IEnumerator BriefWhenDoorsOpen()
		{
			float waited = 0f;
			float enabledFor = 0f;
			string how = "time limit";
			while (waited < 60f)
			{
				StartOfRound instance = StartOfRound.Instance;
				Animator val = instance?.shipDoorsAnimator;
				if ((Object)(object)val != (Object)null && !val.GetBool("Closed"))
				{
					how = "doors open";
					break;
				}
				if ((Object)(object)instance != (Object)null && instance.shipDoorsEnabled)
				{
					enabledFor += Time.deltaTime;
					if (enabledFor >= 1.5f)
					{
						how = "doors enabled";
						break;
					}
				}
				waited += Time.deltaTime;
				yield return null;
			}
			_watching = false;
			try
			{
				Plugin.Logger.LogInfo((object)$"[DayBriefing] briefing ({how}, {waited:0.0}s).");
				Broadcast();
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[DayBriefing] {arg}");
			}
		}

		internal static void OnEggsSpawned(int birdId, int[] values)
		{
			if (ModConfig.DayBriefing.Value && values != null && values.Length != 0 && !TournamentGate.Active && _eggNestsAnnounced.Add(birdId))
			{
				int num = values.Sum();
				ChatUtil.Send(string.Format("Sapsucker nest: {0} egg{1} (${2})", values.Length, (values.Length == 1) ? "" : "s", num), ModConfig.DayBriefingBroadcast.Value);
			}
		}

		private static void Broadcast()
		{
			if (DeskHelper.HasSellDesk())
			{
				return;
			}
			List<string> list = new List<string>();
			string interiorName = GetInteriorName();
			if (interiorName != null)
			{
				list.Add(interiorName);
			}
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			int num5 = 0;
			int num6 = 0;
			int num7 = 0;
			VehicleController vehicle = Object.FindObjectOfType<VehicleController>();
			Bounds? vehicleBounds = ScrapScanner.ComputeVehicleBounds(vehicle);
			GrabbableObject[] array = Object.FindObjectsByType<GrabbableObject>((FindObjectsSortMode)0);
			GrabbableObject[] array2 = array;
			foreach (GrabbableObject val in array2)
			{
				if ((Object)(object)val == (Object)null || (Object)(object)val.itemProperties == (Object)null || !val.itemProperties.isScrap || val.isInShipRoom || val.isInElevator || val.scrapValue <= 0)
				{
					continue;
				}
				if (ScrapScanner.ClassifyLocation(val, vehicle, vehicleBounds) != ScrapLocation.Other)
				{
					num7++;
					continue;
				}
				string itemName = val.itemProperties.itemName;
				if (val is KiwiBabyItem)
				{
					num5++;
					num6 += val.scrapValue;
				}
				else if (!string.IsNullOrEmpty(itemName) && itemName.ToLowerInvariant().Contains("hive"))
				{
					num3++;
					num4 += val.scrapValue;
				}
				else
				{
					num++;
					num2 += val.scrapValue;
				}
			}
			if (num > 0)
			{
				list.Add($"{num} scrap (${num2})");
			}
			if (num3 > 0)
			{
				list.Add(string.Format("{0} hive{1} (${2})", num3, (num3 == 1) ? "" : "s", num4));
			}
			if (num5 > 0)
			{
				list.Add(string.Format("{0} egg{1} (${2})", num5, (num5 == 1) ? "" : "s", num6));
			}
			if ((Object)(object)RoundManager.Instance != (Object)null)
			{
				ManualLogSource logger = Plugin.Logger;
				if (logger != null)
				{
					logger.LogInfo((object)($"[DayBriefing] scan: count={num} value={num2} hives={num3} (${num4}) eggs={num5} (${num6}) " + $"excludedAboard={num7} " + $"(RoundManager.totalScrapValueInLevel={RoundManager.Instance.totalScrapValueInLevel:0})"));
				}
			}
			if (list.Count != 0)
			{
				ChatUtil.Send("Today: " + string.Join(" · ", list), ModConfig.DayBriefingBroadcast.Value);
			}
		}

		internal static void HandleToggle(string token)
		{
			switch (token)
			{
			case "l":
			case "live":
				ModConfig.DayBriefing.Value = true;
				ModConfig.DayBriefingBroadcast.Value = true;
				ChatUtil.Send("Day briefing: ON (live — whole crew sees it).");
				break;
			case "p":
			case "private":
				ModConfig.DayBriefing.Value = true;
				ModConfig.DayBriefingBroadcast.Value = false;
				ChatUtil.Send("Day briefing: ON (private — host only).");
				break;
			case "o":
			case "off":
				ModConfig.DayBriefing.Value = false;
				ChatUtil.Send("Day briefing: OFF.");
				break;
			case "":
			{
				string text = ((!ModConfig.DayBriefing.Value) ? "OFF" : (ModConfig.DayBriefingBroadcast.Value ? "ON (live)" : "ON (private)"));
				ChatUtil.Send("Day briefing: " + text + ". Usage: /briefing live | private | off");
				break;
			}
			default:
				ChatUtil.Send("Usage: /briefing live | private | off  (shorthand: /b l | p | o)");
				break;
			}
		}

		private static string GetInteriorName()
		{
			try
			{
				RoundManager instance = RoundManager.Instance;
				if ((Object)(object)instance == (Object)null || (Object)(object)instance.dungeonGenerator == (Object)null)
				{
					return null;
				}
				IndoorMapType[] dungeonFlowTypes = instance.dungeonFlowTypes;
				int currentDungeonType = instance.currentDungeonType;
				object obj;
				if (dungeonFlowTypes == null || currentDungeonType < 0 || currentDungeonType >= dungeonFlowTypes.Length || !((Object)(object)dungeonFlowTypes[currentDungeonType]?.dungeonFlow != (Object)null))
				{
					DungeonGenerator generator = instance.dungeonGenerator.Generator;
					if (generator == null)
					{
						obj = null;
					}
					else
					{
						DungeonFlow dungeonFlow = generator.DungeonFlow;
						obj = ((dungeonFlow != null) ? ((Object)dungeonFlow).name : null);
					}
				}
				else
				{
					obj = ((Object)dungeonFlowTypes[currentDungeonType].dungeonFlow).name;
				}
				string text = (string)obj;
				if (string.IsNullOrEmpty(text))
				{
					return null;
				}
				string text2 = text.ToLowerInvariant();
				if (text2.Contains("level1") || text2.Contains("factory"))
				{
					return "Factory";
				}
				if (text2.Contains("level2") || text2.Contains("manor") || text2.Contains("mansion"))
				{
					return "Mansion";
				}
				if (text2.Contains("level3") || text2.Contains("mineshaft") || text2.Contains("mine"))
				{
					return "Mineshaft";
				}
				return text;
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[DayBriefing.GetInteriorName] {arg}");
				return null;
			}
		}
	}
	internal static class DeskGuard
	{
		private static float _lastBeamAlert;

		private static int _beamBlockedSinceAlert;

		internal static void OnDeskBeamBlocked(GrabbableObject obj, float dist, ulong? sender = null)
		{
			DeskReturnCommands.NoteBlocked(obj);
			_beamBlockedSinceAlert++;
			string arg = (sender.HasValue ? $" by {LobbyBridge.PlayerName(sender.Value)} (client {sender.Value})" : "");
			ManualLogSource logger = Plugin.Logger;
			if (logger != null)
			{
				logger.LogWarning((object)string.Format("[DeskGuard] blocked desk beam{0}: {1} from {2:F0}m away.", arg, ((Object)(object)obj != (Object)null) ? ((Object)obj).name : "?", dist));
			}
			if (sender.HasValue)
			{
				LobbyBridge.ReportModdedTraffic(sender.Value, "desk beam");
			}
			if (!(Time.realtimeSinceStartup - _lastBeamAlert < 5f))
			{
				_lastBeamAlert = Time.realtimeSinceStartup;
				ChatUtil.Send($"⚠ Blocked {_beamBlockedSinceAlert} item(s) beamed to the sell desk by a modded client. " + (LobbyBridge.Active ? "/players to review. " : "") + (ModConfig.AutoReturnBeamedItems.Value ? "They go back on the ship by themselves." : "/return puts them back on the ship."));
				_beamBlockedSinceAlert = 0;
			}
		}

		internal static void Reset()
		{
			_beamBlockedSinceAlert = 0;
		}
	}
	internal static class DropshipDeliveries
	{
		private static readonly HashSet<GrabbableObject> Delivered = new HashSet<GrabbableObject>();

		internal static HashSet<int> SnapshotIds()
		{
			HashSet<int> hashSet = new HashSet<int>();
			GrabbableObject[] array = Object.FindObjectsByType<GrabbableObject>((FindObjectsSortMode)0);
			foreach (GrabbableObject val in array)
			{
				hashSet.Add(((Object)val).GetInstanceID());
			}
			return hashSet;
		}

		internal static void RecordNew(HashSet<int> before)
		{
			int num = 0;
			GrabbableObject[] array = Object.FindObjectsByType<GrabbableObject>((FindObjectsSortMode)0);
			foreach (GrabbableObject val in array)
			{
				if (!before.Contains(((Object)val).GetInstanceID()))
				{
					Delivered.Add(val);
					num++;
				}
			}
			Delivered.RemoveWhere((GrabbableObject o) => (Object)(object)o == (Object)null);
			if (num > 0)
			{
				Plugin.Logger.LogInfo((object)$"[Dropship] tracked {num} delivered item(s)");
			}
		}

		internal static bool IsWaitingDelivery(GrabbableObject o)
		{
			if ((Object)(object)o != (Object)null && Delivered.Contains(o) && !o.isHeld)
			{
				return !o.heldByPlayerOnServer;
			}
			return false;
		}

		internal static void Forget(GrabbableObject o)
		{
			Delivered.Remove(o);
		}
	}
	internal static class FastBuyService
	{
		private static bool _consumed;

		private static bool _handling;

		private static int _floorSlot;

		internal static void MarkConsumed()
		{
			_consumed = true;
		}

		internal static void ClearConsumedSignal()
		{
			_consumed = false;
		}

		internal static bool TakeConsumedSignal()
		{
			bool consumed = _consumed;
			_consumed = false;
			return consumed;
		}

		internal static bool IsAuthority()
		{
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton != (Object)null)
			{
				return singleton.IsServer;
			}
			return false;
		}

		internal static void FromServerRpc(Terminal terminal, int[] boughtItems, int queueCountBefore)
		{
			if (FastBuyScope.Depth <= 0 && IsAuthority() && !((Object)(object)terminal == (Object)null) && boughtItems != null && boughtItems.Length != 0)
			{
				int appended = QueueCount(terminal) - queueCountBefore;
				Handle(terminal, boughtItems, appended);
			}
		}

		internal static void FromLoadNode(Terminal terminal, TerminalNode node, int queueCountBefore, int quantityAtEntry)
		{
			if (!IsAuthority() || (Object)(object)terminal == (Object)null || (Object)(object)node == (Object)null || node.isConfirmationNode || node.buyItemIndex < 0)
			{
				return;
			}
			int num = QueueCount(terminal) - queueCountBefore;
			if (num <= 0 || terminal.orderedItemsFromTerminal == null)
			{
				return;
			}
			List<int> orderedItemsFromTerminal = terminal.orderedItemsFromTerminal;
			int num2 = orderedItemsFromTerminal.Count - num;
			for (int i = num2; i < orderedItemsFromTerminal.Count; i++)
			{
				if (orderedItemsFromTerminal[i] != node.buyItemIndex)
				{
					return;
				}
			}
			int num3 = quantityAtEntry;
			if (num3 <= 0 || num % num3 != 0)
			{
				num3 = num;
			}
			int[] array = new int[num3];
			for (int j = 0; j < num3; j++)
			{
				array[j] = node.buyItemIndex;
			}
			Handle(terminal, array, num);
		}

		private static void Handle(Terminal terminal, int[] bought, int appended)
		{
			if (_handling)
			{
				return;
			}
			_handling = true;
			try
			{
				if (!FastBuyLogic.ShouldRedirect(ModConfig.FastBuy != null && ModConfig.FastBuy.Value, TournamentGate.Active, CurrentPlace(), vehiclePurchase: false, bought.Length))
				{
					return;
				}
				List<int> orderedItemsFromTerminal = terminal.orderedItemsFromTerminal;
				if (orderedItemsFromTerminal == null)
				{
					return;
				}
				int[] array = FastBuyLogic.SelectToSpawn(orderedItemsFromTerminal, appended, bought);
				if (array.Length == 0)
				{
					return;
				}
				List<int> list = new List<int>(array.Length);
				int[] array2 = array;
				foreach (int num in array2)
				{
					if (TrySpawnOnShip(terminal, num))
					{
						list.Add(num);
					}
				}
				List<int> list2 = FastBuyLogic.ApplyRemoval(orderedItemsFromTerminal, appended, bought, list);
				bool flag = list2.Count != orderedItemsFromTerminal.Count;
				if (!flag)
				{
					for (int j = 0; j < list2.Count; j++)
					{
						if (list2[j] != orderedItemsFromTerminal[j])
						{
							flag = true;
							break;
						}
					}
				}
				if (flag)
				{
					orderedItemsFromTerminal.Clear();
					orderedItemsFromTerminal.AddRange(list2);
				}
				if (list.Count > 0)
				{
					int numberOfItemsInDropship = terminal.numberOfItemsInDropship;
					terminal.numberOfItemsInDropship = FastBuyLogic.AdjustInFlightCount(numberOfItemsInDropship, list.Count);
					SyncCount(terminal);
					MarkConsumed();
					Plugin.Logger.LogInfo((object)($"[FastBuy] replicated {list.Count} item(s) onto the ship " + $"(catalogue {Format(list)}). Dropship queue {orderedItemsFromTerminal.Count}, in-flight count {terminal.numberOfItemsInDropship}."));
				}
				else if (flag)
				{
					Plugin.Logger.LogInfo((object)"[FastBuy] removed a duplicate queue copy; nothing new was spawned.");
				}
				if (list.Count < array.Length)
				{
					Plugin.Logger.LogWarning((object)$"[FastBuy] {array.Length - list.Count} item(s) stayed on the delivery queue (spawn failed).");
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[FastBuy] {arg}");
			}
			finally
			{
				_handling = false;
			}
		}

		private static FastBuyLogic.Place CurrentPlace()
		{
			StartOfRound instance = StartOfRound.Instance;
			bool inShipPhase = (Object)(object)instance != (Object)null && instance.inShipPhase;
			return FastBuyLogic.ClassifyPlace(inShipPhase, BuyService.AtCompany());
		}

		private static int QueueCount(Terminal terminal)
		{
			return (terminal?.orderedItemsFromTerminal?.Count).GetValueOrDefault();
		}

		private static Transform FindShipNetworkParent(StartOfRound sor)
		{
			Transform elevatorTransform = sor.elevatorTransform;
			if ((Object)(object)elevatorTransform != (Object)null)
			{
				NetworkObject val = ((Component)elevatorTransform).GetComponent<NetworkObject>();
				if ((Object)(object)val == (Object)null)
				{
					val = ((Component)elevatorTransform).GetComponentInParent<NetworkObject>();
				}
				if (IsReplicatedAnchor(val))
				{
					return ((Component)val).transform;
				}
			}
			if (IsReplicatedAnchor(sor.shipAnimatorObject))
			{
				return ((Component)sor.shipAnimatorObject).transform;
			}
			return null;
		}

		private static bool IsReplicatedAnchor(NetworkObject net)
		{
			if ((Object)(object)net != (Object)null && net.IsSpawned)
			{
				return FastBuyLogic.CountsAsReplicated(net.NetworkObjectId, parentReplicated: true);
			}
			return false;
		}

		private static void SyncCount(Terminal terminal)
		{
			try
			{
				terminal.SyncGroupCreditsClientRpc(terminal.groupCredits, terminal.numberOfItemsInDropship);
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[FastBuy.Sync] {arg}");
			}
		}

		private static bool TrySpawnOnShip(Terminal terminal, int itemIndex)
		{
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_018e: Unknown result type (might be due to invalid IL or missing references)
			//IL_019c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = null;
			try
			{
				if (itemIndex < 0 || terminal.buyableItemsList == null || itemIndex >= terminal.buyableItemsList.Length)
				{
					return false;
				}
				Item val2 = terminal.buyableItemsList[itemIndex];
				if ((Object)(object)val2 == (Object)null || (Object)(object)val2.spawnPrefab == (Object)null)
				{
					return false;
				}
				StartOfRound instance = StartOfRound.Instance;
				if ((Object)(object)instance?.elevatorTransform == (Object)null || instance.playerSpawnPositions == null || instance.playerSpawnPositions.Length == 0)
				{
					return false;
				}
				int num = _floorSlot++;
				Vector3 val3 = default(Vector3);
				((Vector3)(ref val3))..ctor((float)(num % 5 - 2) * 0.45f, 0.2f, (float)(num / 5 % 5 - 2) * 0.45f);
				Vector3 val4 = ItemPlacer.SnapToFloor(instance.playerSpawnPositions[0].position + val3);
				val = Object.Instantiate<GameObject>(val2.spawnPrefab, val4, Quaternion.identity);
				GrabbableObject component = val.GetComponent<GrabbableObject>();
				NetworkObject component2 = val.GetComponent<NetworkObject>();
				if ((Object)(object)component == (Object)null || (Object)(object)component2 == (Object)null)
				{
					Object.Destroy((Object)(object)val);
					val = null;
					return false;
				}
				component.fallTime = 0f;
				component.isHeld = false;
				component.heldByPlayerOnServer = false;
				component.floorYRot = -1;
				Transform val5 = FindShipNetworkParent(instance);
				if ((Object)(object)val5 == (Object)null)
				{
					Plugin.Logger.LogWarning((object)$"[FastBuy] item {itemIndex} has no spawned ship NetworkObject; left on the dropship.");
					Object.Destroy((Object)(object)val);
					val = null;
					return false;
				}
				val.transform.SetParent(val5, true);
				component.targetFloorPosition = ((Component)component).transform.localPosition;
				component.startFallingPosition = ((Component)component).transform.localPosition;
				component2.Spawn(false);
				bool flag = (Object)(object)val.transform.parent != (Object)null;
				if (!FastBuyLogic.CountsAsReplicated(component2.IsSpawned ? component2.NetworkObjectId : 0, flag))
				{
					Plugin.Logger.LogWarning((object)$"[FastBuy] item {itemIndex} did not replicate (spawned {component2.IsSpawned}, id {component2.NetworkObjectId}, parent {flag}); left on the dropship.");
					if (component2.IsSpawned)
					{
						component2.Despawn(true);
					}
					else
					{
						Object.Destroy((Object)(object)val);
					}
					val = null;
					return false;
				}
				val = null;
				component.isInElevator = true;
				component.isInShipRoom = true;
				component.EnablePhysics(true);
				component.EnableItemMeshes(true);
				Plugin.Logger.LogInfo((object)$"[FastBuy] item {itemIndex} network id {component2.NetworkObjectId} parented to {((Object)val5).name}.");
				return true;
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[FastBuy.Spawn] item {itemIndex}: {arg}");
				if ((Object)(object)val != (Object)null)
				{
					Object.Destroy((Object)(object)val);
				}
				return false;
			}
		}

		private static string Format(List<int> ids)
		{
			if (ids == null || ids.Count == 0)
			{
				return "";
			}
			return string.Join(",", ids);
		}
	}
	internal static class FastBuyScope
	{
		internal static int Depth;
	}
	internal static class ScrapUpdateFeature
	{
		internal static bool Enabled { get; private set; }

		internal static bool Broadcast { get; private set; }

		internal static void Enable(bool broadcast)
		{
			Enabled = true;
			Broadcast = broadcast;
			ChatUtil.Send(broadcast ? "Scrap Update Enabled (Live)" : "Scrap Update Enabled (Private)");
			PostUpdateLine();
		}

		internal static void Disable()
		{
			Enabled = false;
			Broadcast = false;
			ChatUtil.Send("Scrap Update Disabled");
		}

		internal static void SetDisplayMode(UpdateDisplayMode mode)
		{
			ModConfig.UpdateMode.Value = mode;
			ChatUtil.Send($"Scrap update mode: {mode}");
			if (Enabled)
			{
				PostUpdateLine();
			}
		}

		internal static void OnHourChanged()
		{
			OrganizeService.RefreshTallies();
			if (Enabled)
			{
				PostUpdateLine();
			}
		}

		private static void PostUpdateLine()
		{
			string text = TimeUtil.FormatGameTime();
			VehicleController val = Object.FindObjectOfType<VehicleController>();
			bool flag = (Object)(object)val != (Object)null;
			bool flag2 = flag && val.magnetedToShip;
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			try
			{
				List<ScrapItem> items = ScrapScanner.ScanAll();
				List<ScrapItem> items2 = ScrapScanner.ScanAll(shipAndCruiserOnly: true, applyExclusions: false);
				num = ScrapScanner.SumValue(items, ScrapLocation.Ship);
				num2 = ScrapScanner.SumValue(items, ScrapLocation.Cruiser);
				num3 = ScrapScanner.SumValue(items2, ScrapLocation.Ship) + ScrapScanner.SumValue(items2, ScrapLocation.Cruiser) - num - num2;
				ManualLogSource logger = Plugin.Logger;
				if (logger != null)
				{
					logger.LogInfo((object)$"[ScrapUpdate] mode={ModConfig.UpdateMode.Value} ship={num} cruiser={num2} magneted={flag2}");
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[ScrapUpdate] value computation failed: {arg}");
				ChatUtil.SendRaw("<color=#5fe35f>" + text + " — ship scrap: $?</color>", Broadcast);
				return;
			}
			int total = num + num2;
			string text2 = FormatValueLine(num, num2, total, flag, flag2);
			if (num3 > 0)
			{
				text2 += $" (+${num3} protected)";
			}
			ChatUtil.SendRaw("<color=#5fe35f>" + text + " — " + text2 + "</color>", Broadcast);
		}

		private static string FormatValueLine(int ship, int cruiser, int total, bool hasVehicle, bool magnetted)
		{
			switch (ModConfig.UpdateMode.Value)
			{
			case UpdateDisplayMode.Ship:
				return $"ship: ${ship}";
			case UpdateDisplayMode.Cruiser:
				if (!hasVehicle)
				{
					return "cruiser: no cruiser";
				}
				if (magnetted)
				{
					return "cruiser: magnetted (counted in ship)";
				}
				return $"cruiser: ${cruiser}";
			case UpdateDisplayMode.Combined:
				return $"ship scrap: ${total}";
			default:
				if (magnetted)
				{
					return $"ship (incl. magnetted cruiser): ${total}";
				}
				if (!hasVehicle)
				{
					return $"ship: ${ship} · total: ${total}";
				}
				return $"ship: ${ship} · cruiser: ${cruiser} · total: ${total}";
			}
		}

		internal static void OnReachedOrbit()
		{
			if (ModConfig.AutoMergeOnOrbit.Value && !TournamentGate.Active)
			{
				UtilityCommands.Merge(null, quiet: true);
				OrganizeService.OrganizeScrap("", quiet: true);
			}
		}
	}
	internal static class TimeAnnounceFeature
	{
		internal static bool Enabled { get; private set; }

		internal static bool Broadcast { get; private set; }

		internal static void Handle(string token)
		{
			switch (token)
			{
			case "l":
			case "live":
				Enabled = true;
				Broadcast = true;
				ChatUtil.Send("Time Update Enabled (Live)");
				PostTimeLine();
				break;
			case "p":
			case "private":
				Enabled = true;
				Broadcast = false;
				ChatUtil.Send("Time Update Enabled (Private)");
				PostTimeLine();
				break;
			case "o":
			case "off":
				Enabled = false;
				Broadcast = false;
				ChatUtil.Send("Time Update Disabled");
				break;
			default:
				ChatUtil.Send("Usage: /time update l (live) | p (private) | o (off)");
				break;
			}
		}

		internal static void OnHourChanged()
		{
			if (Enabled)
			{
				PostTimeLine();
			}
		}

		private static void PostTimeLine()
		{
			ChatUtil.SendRaw("<color=#6fb7ff>" + TimeUtil.FormatGameTime() + "</color>", Broadcast);
		}
	}
}
namespace SimpleSell.Core
{
	internal sealed class ItemAnchor
	{
		internal string ItemName;

		internal ScrapLocation Loc;

		internal Vector3 LocalPos;

		internal bool Stack;

		internal bool Exact;
	}
	internal sealed class AnchorStore
	{
		internal static readonly AnchorStore Equipment = new AnchorStore(() => ModConfig.EquipItemAnchors);

		internal static readonly AnchorStore Scrap = new AnchorStore(() => ModConfig.ScrapItemAnchors);

		private readonly Func<ConfigEntry<string>> _entry;

		private Dictionary<string, ItemAnchor> _map;

		internal IReadOnlyCollection<ItemAnchor> All => Load().Values.ToList();

		private AnchorStore(Func<ConfigEntry<string>> entry)
		{
			_entry = entry;
		}

		private Dictionary<string, ItemAnchor> Load()
		{
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			if (_map != null)
			{
				return _map;
			}
			_map = new Dictionary<string, ItemAnchor>();
			try
			{
				string[] array = _entry().Value.Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries);
				foreach (string text in array)
				{
					string[] array2 = text.Split('|');
					if (array2.Length == 4 || array2.Length == 5)
					{
						string text2 = array2[0].Trim();
						if (text2.Length != 0 && TryParseVec3(array2[2], out var v))
						{
							_map[text2.ToLowerInvariant()] = new ItemAnchor
							{
								ItemName = text2,
								Loc = ((array2[1].Trim().ToLowerInvariant() == "cruiser") ? ScrapLocation.Cruiser : ScrapLocation.Ship),
								LocalPos = v,
								Stack = (array2[3].Trim().ToLowerInvariant() == "stack"),
								Exact = (array2.Length == 5 && array2[4].Trim().ToLowerInvariant() == "exact")
							};
						}
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[AnchorStore] {arg}");
			}
			return _map;
		}

		internal ItemAnchor Get(string itemName)
		{
			if (string.IsNullOrEmpty(itemName))
			{
				return null;
			}
			if (!Load().TryGetValue(itemName.ToLowerInvariant(), out var value))
			{
				return null;
			}
			return value;
		}

		internal void Set(ItemAnchor anchor)
		{
			Load()[anchor.ItemName.ToLowerInvariant()] = anchor;
			Save();
		}

		internal bool Remove(string itemName)
		{
			bool flag = Load().Remove(itemName.ToLowerInvariant());
			if (flag)
			{
				Save();
			}
			return flag;
		}

		private void Save()
		{
			_entry().Value = string.Join(";", from a in Load().Values
				orderby a.ItemName
				select a.ItemName + "|" + ((a.Loc == ScrapLocation.Cruiser) ? "cruiser" : "ship") + "|" + FormatVec3(a.LocalPos) + "|" + (a.Stack ? "stack" : "pile") + (a.Exact ? "|exact" : ""));
		}

		internal static string FormatVec3(Vector3 v)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			return string.Format(CultureInfo.InvariantCulture, "{0:0.##},{1:0.##},{2:0.##}", v.x, v.y, v.z);
		}

		internal static bool TryParseVec3(string s, out Vector3 v)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			v = Vector3.zero;
			if (string.IsNullOrWhiteSpace(s))
			{
				return false;
			}
			string[] array = s.Split(',');
			if (array.Length != 3)
			{
				return false;
			}
			if (float.TryParse(array[0].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && float.TryParse(array[1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result2) && float.TryParse(array[2].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result3))
			{
				v = new Vector3(result, result2, result3);
				return true;
			}
			return false;
		}
	}
	internal static class BankBridge
	{
		private static bool _looked;

		private static PropertyInfo _total;

		private static PropertyInfo _count;

		internal static bool TryGet(out int total, out int count)
		{
			total = 0;
			count = 0;
			try
			{
				if (!_looked)
				{
					_looked = true;
					if (Chainloader.PluginInfos.ContainsKey("local.simplehq"))
					{
						Type type = null;
						Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
						foreach (Assembly assembly in assemblies)
						{
							if (!(assembly.GetName().Name != "SimpleHQ"))
							{
								type = assembly.GetType("SimpleHQ.BankInfo");
								if (type != null)
								{
									break;
								}
							}
						}
						_total = type?.GetProperty("Total", BindingFlags.Static | BindingFlags.Public);
						_count = type?.GetProperty("Count", BindingFlags.Static | BindingFlags.Public);
						if (_total == null)
						{
							Plugin.Logger.LogWarning((object)"[BankBridge] SimpleHQ is loaded but has no BankInfo.Total; bank left out of /stock.");
						}
					}
				}
				if (_total == null)
				{
					return false;
				}
				total = (int)_total.GetValue(null);
				count = ((_count != null) ? ((int)_count.GetValue(null)) : 0);
				return true;
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[BankBridge] {arg}");
				_total = null;
				return false;
			}
		}
	}
	internal static class BatchPlanner
	{
		internal static List<(int index, int qty)> SplitBatch(IList<int> pendingQty, int slots)
		{
			List<(int, int)> list = new List<(int, int)>();
			for (int i = 0; i < pendingQty.Count; i++)
			{
				if (slots <= 0)
				{
					break;
				}
				int num = Math.Min(pendingQty[i], slots);
				if (num > 0)
				{
					list.Add((i, num));
					slots -= num;
				}
			}
			return list;
		}

		internal static List<List<(int index, int qty)>> Plan(IList<int> qty, int capacity)
		{
			List<int> list = new List<int>(qty);
			List<List<(int, int)>> list2 = new List<List<(int, int)>>();
			while (true)
			{
				List<(int, int)> list3 = SplitBatch(list, capacity);
				if (list3.Count == 0)
				{
					break;
				}
				foreach (var (index, num) in list3)
				{
					list[index] -= num;
				}
				list2.Add(list3);
			}
			return list2;
		}
	}
	internal sealed class CoroutineRunner : MonoBehaviour
	{
		private static CoroutineRunner _instance;

		internal static CoroutineRunner Instance
		{
			get
			{
				//IL_0012: Unknown result type (might be due to invalid IL or missing references)
				//IL_0018: Expected O, but got Unknown
				if ((Object)(object)_instance == (Object)null)
				{
					GameObject val = new GameObject("SimpleSell.CoroutineRunner");
					((Object)val).hideFlags = (HideFlags)61;
					Object.DontDestroyOnLoad((Object)(object)val);
					_instance = val.AddComponent<CoroutineRunner>();
				}
				return _instance;
			}
		}
	}
	internal static class DefaultExclusionsSeeder
	{
		private static readonly (string Query, string FallbackCanonical)[] DefaultWeapons = new(string, string)[3]
		{
			("kitchen knife", "Kitchen knife"),
			("shotgun", "Shotgun"),
			("ammo", "Ammo")
		};

		private static readonly string[] NoLongerSeeded = new string[1] { "Homemade flashbang" };

		internal static void TrySeed()
		{
			TrySeedGear();
			DropRetiredSeeds();
			if (ModConfig.DefaultExclusionsSeeded.Value)
			{
				return;
			}
			StartOfRound instance = StartOfRound.Instance;
			if (instance?.allItemsList?.itemsList == null || instance.allItemsList.itemsList.Count == 0)
			{
				return;
			}
			int num = 0;
			(string, string)[] defaultWeapons = DefaultWeapons;
			for (int i = 0; i < defaultWeapons.Length; i++)
			{
				(string, string) tuple = defaultWeapons[i];
				string item = tuple.Item1;
				string item2 = tuple.Item2;
				ItemNameResolver.ResolveResult resolveResult = ItemNameResolver.Resolve(item);
				string text = ((resolveResult.Kind == ItemNameResolver.ResultKind.Ok) ? resolveResult.Name : item2);
				if (ExclusionList.Add(text))
				{
					num++;
					ManualLogSource logger = Plugin.Logger;
					if (logger != null)
					{
						logger.LogInfo((object)("[DefaultExclusions] seeded \"" + text + "\" (query=\"" + item + "\")."));
					}
				}
				else
				{
					ManualLogSource logger2 = Plugin.Logger;
					if (logger2 != null)
					{
						logger2.LogInfo((object)("[DefaultExclusions] \"" + text + "\" already excluded."));
					}
				}
			}
			ModConfig.DefaultExclusionsSeeded.Value = true;
			ManualLogSource logger3 = Plugin.Logger;
			if (logger3 != null)
			{
				logger3.LogInfo((object)$"[DefaultExclusions] seed complete — {num} new exclusion(s), {ExclusionList.Count} total.");
			}
		}

		private static void DropRetiredSeeds()
		{
			if (ModConfig.RetiredSeedsDropped.Value)
			{
				return;
			}
			string[] noLongerSeeded = NoLongerSeeded;
			foreach (string text in noLongerSeeded)
			{
				if (ExclusionList.Remove(text))
				{
					ManualLogSource logger = Plugin.Logger;
					if (logger != null)
					{
						logger.LogInfo((object)("[DefaultExclusions] \"" + text + "\" is no longer protected by default."));
					}
				}
			}
			ModConfig.RetiredSeedsDropped.Value = true;
		}

		private static void TrySeedGear()
		{
			if (ModConfig.GearSeeded.Value)
			{
				return;
			}
			StartOfRound instance = StartOfRound.Instance;
			if (instance?.allItemsList?.itemsList == null || instance.allItemsList.itemsList.Count == 0)
			{
				return;
			}
			int num = 0;
			(string, string)[] defaultWeapons = DefaultWeapons;
			for (int i = 0; i < defaultWeapons.Length; i++)
			{
				(string, string) tuple = defaultWeapons[i];
				string item = tuple.Item1;
				string item2 = tuple.Item2;
				ItemNameResolver.ResolveResult resolveResult = ItemNameResolver.Resolve(item);
				string canonicalName = ((resolveResult.Kind == ItemNameResolver.ResultKind.Ok) ? resolveResult.Name : item2);
				if (GearList.Add(canonicalName))
				{
					num++;
				}
			}
			ModConfig.GearSeeded.Value = true;
			ManualLogSource logger = Plugin.Logger;
			if (logger != null)
			{
				logger.LogInfo((object)$"[GearSeed] seeded {num} weapon(s) into the gear list ({GearList.Count} total).");
			}
			if (ModConfig.WeaponExclusionsMigrated.Value)
			{
				return;
			}
			int num2 = 0;
			(string, string)[] defaultWeapons2 = DefaultWeapons;
			for (int j = 0; j < defaultWeapons2.Length; j++)
			{
				(string, string) tuple2 = defaultWeapons2[j];
				string item3 = tuple2.Item1;
				string item4 = tuple2.Item2;
				ItemNameResolver.ResolveResult resolveResult2 = ItemNameResolver.Resolve(item3);
				string canonicalName2 = ((resolveResult2.Kind == ItemNameResolver.ResultKind.Ok) ? resolveResult2.Name : item4);
				if (ExclusionList.Remove(canonicalName2))
				{
					num2++;
				}
			}
			ModConfig.WeaponExclusionsMigrated.Value = true;
			ManualLogSource logger2 = Plugin.Logger;
			if (logger2 != null)
			{
				logger2.LogInfo((object)$"[GearSeed] lifted {num2} weapon sell-exclusion(s) — gear list governs them now.");
			}
		}
	}
	internal static class DeskHelper
	{
		private struct SoldRecord
		{
			internal string Name;

			internal int Value;

			internal bool IsBody;

			internal SaleDisplayAggregator.SoldLine ToDisplayLine()
			{
				return new SaleDisplayAggregator.SoldLine
				{
					Name = Name,
					Value = Value
				};
			}
		}

		private const int DeskCounterCap = 12;

		internal static DepositItemsDesk FindDesk()
		{
			return Object.FindObjectOfType<DepositItemsDesk>();
		}

		internal static bool HasSellDesk()
		{
			return (Object)(object)FindDesk() != (Object)null;
		}

		internal static void DepositAndSell(List<ScrapItem> chosen)
		{
			DepositItemsDesk val = FindDesk();
			if ((Object)(object)val == (Object)null)
			{
				throw new InvalidOperationException("No sell desk found.");
			}
			int groupCredits = GetGroupCredits();
			bool broadcastReplies = ChatUtil.BroadcastReplies;
			((MonoBehaviour)CoroutineRunner.Instance).StartCoroutine(SellInBatches(val, chosen, groupCredits, broadcastReplies));
		}

		private static IEnumerator SellInBatches(DepositItemsDesk desk, List<ScrapItem> chosen, int creditsBefore, bool announce)
		{
			SaleDisplayAggregator.BatchedSaleInProgress = true;
			SaleDisplayAggregator.Reset();
			List<SoldRecord> sold = new List<SoldRecord>();
			int totalBatches = (chosen.Count + 12 - 1) / 12;
			if (totalBatches == 0)
			{
				totalBatches = 1;
			}
			int batchNum = 0;
			bool aborted = false;
			for (int i = 0; i < chosen.Count; i += 12)
			{
				batchNum++;
				int placed = 0;
				List<SoldRecord> batchRecords = new List<SoldRecord>();
				for (int j = i; j < chosen.Count && j - i < 12; j++)
				{
					GrabbableObject val = chosen[j].Object;
					if (!((Object)(object)val == (Object)null))
					{
						NetworkObject component = ((Component)val).GetComponent<NetworkObject>();
						if ((Object)(object)component == (Object)null)
						{
							Plugin.Logger.LogWarning((object)("[Sell] " + ((Object)val).name + " has no NetworkObject, skipping."));
							continue;
						}
						batchRecords.Add(new SoldRecord
						{
							Name = (((Object)(object)val.itemProperties != (Object)null) ? val.itemProperties.itemName : ((Object)val).name),
							Value = val.scrapValue,
							IsBody = ScrapScanner.IsBody(val)
						});
						desk.AddObjectToDeskServerRpc(NetworkObjectReference.op_Implicit(component));
						placed++;
					}
				}
				ManualLogSource logger = Plugin.Logger;
				if (logger != null)
				{
					logger.LogInfo((object)$"[Sell] batch {batchNum}: placed {placed} item(s) on counter.");
				}
				if (placed == 0)
				{
					continue;
				}
				yield return null;
				desk.SellItemsOnServer();
				float deadline = Time.realtimeSinceStartup + 10f;
				while (desk.itemsOnCounter.Count > 0 && Time.realtimeSinceStartup < deadline)
				{
					yield return (object)new WaitForSeconds(0.25f);
				}
				if (desk.itemsOnCounter.Count > 0)
				{
					Plugin.Logger.LogWarning((object)"[Sell] desk counter did not clear within 10s; aborting remaining batches.");
					if (sold.Count > 0)
					{
						FlushSaleDisplay(sold, creditsBefore);
						BroadcastSaleSummary(sold, creditsBefore, partial: true, announce);
					}
					ChatUtil.Send("Sale stalled — sold what fit; run the command again for the rest.", announce);
					aborted = true;
					break;
				}
				sold.AddRange(batchRecords);
				int num = batchRecords.Sum((SoldRecord r) => r.Value);
				ChatUtil.Send($"Sold batch {batchNum}/{totalBatches} — {placed} items, ${num}.", announce);
			}
			if (!aborted && sold.Count > 0)
			{
				FlushSaleDisplay(sold, creditsBefore);
				BroadcastSaleSummary(sold, creditsBefore, partial: false, announce);
			}
			float suppressDeadline = Time.realtimeSinceStartup + 15f;
			while (SaleDisplayAggregator.PendingDisplaySuppressions > 0 && Time.realtimeSinceStartup < suppressDeadline)
			{
				yield return (object)new WaitForSeconds(0.25f);
			}
			SaleDisplayAggregator.BatchedSaleInProgress = false;
		}

		private static void FlushSaleDisplay(List<SoldRecord> sold, int creditsBefore)
		{
			SaleDisplayAggregator.Flush(sold.Select((SoldRecord s) => s.ToDisplayLine()).ToList(), creditsBefore);
		}

		private static void BroadcastSaleSummary(List<SoldRecord> sold, int creditsBefore, bool partial, bool announce)
		{
			int num = sold.Sum((SoldRecord s) => s.Value);
			int count = sold.Count;
			string message = (partial ? $"SOLD (partial): {count} items for ${num} raw" : $"SOLD: {count} items for ${num} raw");
			ChatUtil.Send(message, announce);
			List<SoldRecord> list = sold.Where((SoldRecord s) => s.IsBody).ToList();
			if (list.Count > 0)
			{
				int num2 = list.Sum((SoldRecord s) => s.Value);
				ChatUtil.Send($"Bodies x{list.Count} (${num2})", announce);
			}
			var list2 = (from s in sold
				where !s.IsBody
				group s by s.Name into g
				select new
				{
					Name = g.Key,
					Count = g.Count(),
					Total = g.Sum((SoldRecord x) => x.Value)
				} into g
				orderby g.Total descending
				select g).ToList();
			if (list2.Count > 0)
			{
				List<string> values = list2.Select(g => $"{g.Name} x{g.Count} (${g.Total})").ToList();
				ChatUtil.SendChunked(string.Join(" · ", values), announce);
			}
			float num3 = (((Object)(object)StartOfRound.Instance != (Object)null) ? Mathf.Max(0f, StartOfRound.Instance.companyBuyingRate) : 1f);
			int num4 = Mathf.FloorToInt((float)num * num3);
			int groupCredits = GetGroupCredits();
			int num5 = groupCredits - creditsBefore;
			int num6 = (((Object)(object)TimeOfDay.Instance != (Object)null) ? TimeOfDay.Instance.quotaFulfilled : 0);
			int num7 = (((Object)(object)TimeOfDay.Instance != (Object)null) ? TimeOfDay.Instance.profitQuota : 0);
			string text = ((num5 > 0) ? $"+${num5}" : $"+${num4} (est)");
			if (num5 > 0 && num5 != num4)
			{
				ManualLogSource logger = Plugin.Logger;
				if (logger != null)
				{
					logger.LogInfo((object)$"[Sell] credits est={num4} actual={num5}");
				}
			}
			ChatUtil.Send($"Quota: {num6}/{num7} · Credits: {text} ({Mathf.RoundToInt(num3 * 100f)}% rate)", announce);
		}

		private static int GetGroupCredits()
		{
			Terminal val = Object.FindObjectOfType<Terminal>();
			if (!((Object)(object)val != (Object)null))
			{
				return 0;
			}
			return val.groupCredits;
		}
	}
	internal static class ExclusionList
	{
		private static HashSet<string> _set;

		internal static IReadOnlyCollection<string> All => Load();

		internal static int Count => Load().Count;

		private static HashSet<string> Load()
		{
			if (_set != null)
			{
				return _set;
			}
			_set = new HashSet<string>(from s in ModConfig.ExcludedItems.Value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries)
				select s.Trim().ToLowerInvariant());
			return _set;
		}

		internal static bool Contains(string itemName)
		{
			if (string.IsNullOrEmpty(itemName))
			{
				return false;
			}
			return Load().Contains(itemName.ToLowerInvariant());
		}

		internal static bool Add(string canonicalName)
		{
			bool flag = Load().Add(canonicalName.ToLowerInvariant());
			if (flag)
			{
				Save();
			}
			return flag;
		}

		internal static bool Remove(string canonicalName)
		{
			bool flag = Load().Remove(canonicalName.ToLowerInvariant());
			if (flag)
			{
				Save();
			}
			return flag;
		}

		internal static void Reload()
		{
			_set = null;
			Load();
		}

		private static void Save()
		{
			ModConfig.ExcludedItems.Value = string.Join(",", from s in Load()
				orderby s
				select s);
		}
	}
	internal static class FastBuyLogic
	{
		internal enum Place
		{
			Orbit,
			Company,
			Elsewhere
		}

		internal enum SwitchParse
		{
			Status,
			On,
			Off,
			Invalid
		}

		internal const int CreditDelta = 0;

		internal const bool ClientAnnounceRequiredForVisibility = false;

		internal const int VisibilityDelayFrames = 0;

		internal static bool CountsAsReplicated(ulong networkObjectId, bool parentReplicated)
		{
			return networkObjectId != 0 && parentReplicated;
		}

		internal static Place ClassifyPlace(bool inShipPhase, bool atCompany)
		{
			if (inShipPhase)
			{
				return Place.Orbit;
			}
			if (atCompany)
			{
				return Place.Company;
			}
			return Place.Elsewhere;
		}

		internal static bool ShouldRedirect(bool enabled, bool to