Decompiled source of SimpleSell v0.1.15

SimpleSell.dll

Decompiled 13 hours 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 Steamworks;
using Steamworks.Data;
using TMPro;
using Unity.Netcode;
using UnityEngine;

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

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace SimpleSell
{
	internal static class ChatUtil
	{
		internal static bool BroadcastReplies;

		internal static string InvokerName;

		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 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 "help":
				return "help";
			default:
				return null;
			}
		}

		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;
		}

		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();
				if (TournamentGate.Active && text != "help" && CanonicalVerb(text) != 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 text4 = ((array.Length >= 2) ? array[1].ToLowerInvariant() : "");
					string arg = ItemNameResolver.JoinName(array, 2).ToLowerInvariant();
					switch (text4)
					{
					case "":
						OrganizeService.OrganizeEquipment("");
						OrganizeService.OrganizeScrap("", quiet: true);
						return true;
					case "scrap":
						OrganizeService.OrganizeScrap(arg);
						return true;
					case "gear":
					case "equipment":
					case "e":
						OrganizeService.OrganizeEquipment(arg);
						return true;
					case "set":
						OrganizeService.SetAnchorAuto(array);
						return true;
					case "unset":
						OrganizeService.UnsetAuto(ItemNameResolver.JoinName(array, 2));
						return true;
					case "shelf":
						OrganizeService.SetShelfAnchor(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 "l":
				case "lobby":
				{
					string text2 = ((array.Length >= 2) ? array[1].ToLowerInvariant() : "");
					switch (text2)
					{
					case "hide":
					case "h":
						LobbyTagFeature.Apply(hide: true);
						break;
					case "show":
					case "s":
						LobbyTagFeature.Apply(hide: false);
						break;
					case "status":
						LobbyTagFeature.Status();
						break;
					case "name":
						LobbyTagFeature.Rename(string.Join(" ", array, 2, array.Length - 2).Trim());
						break;
					case "public":
					case "friends":
					case "private":
						LobbyTagFeature.SetVisibility(text2);
						break;
					case "late":
					case "open":
					{
						string text3 = ((array.Length >= 3) ? array[2].ToLowerInvariant() : "");
						if (text3 == "on" || text3 == "off")
						{
							ModConfig.AllowLateJoin.Value = text3 == "on";
							LateJoin.ResetPushState();
							LateJoin.Refresh();
							ChatUtil.Send(LateJoin.Status());
						}
						else
						{
							ChatUtil.Send(LateJoin.Status() + " Usage: /lobby late on|off");
						}
						break;
					}
					default:
						ChatUtil.Send("Usage: /lobby hide | show | status | name <new name> | public | friends | private | late on|off");
						break;
					}
					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: /scrap set <item name> [pile|stack]");
						return true;
					case "organize":
					case "o":
						ChatUtil.Send("Moved in Round 31: use /organize scrap [ship|cruiser|<item>] (or /o scrap).");
						return true;
					case "unset":
						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] | unset <item> | anchors — organizing: /organize scrap");
						return true;
					}
				case "players":
					if (array.Length >= 2 && array[1].Equals("ids", StringComparison.OrdinalIgnoreCase))
					{
						GuardCommands.PlayersIds();
					}
					else
					{
						GuardCommands.Players();
					}
					return true;
				case "help":
				{
					string text5 = ((array.Length >= 2) ? array[1].ToLowerInvariant() : "");
					if (text5 == "")
					{
						ChatUtil.Send("SimpleSell — /help sell");
						return true;
					}
					if (text5 != "sell" && text5 != "simplesell")
					{
						return false;
					}
					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], /e set, /scrap set, unset, anchors");
					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("Lobby: /lobby hide|show · /players [ids] /kick /guard /whitelist /ban /unban /bans");
					return true;
				}
				case "kick":
					GuardCommands.Kick(ItemNameResolver.JoinName(array, 1));
					return true;
				case "guard":
					GuardCommands.Guard((array.Length >= 2) ? array[1].ToLowerInvariant() : "");
					return true;
				case "whitelist":
					GuardCommands.Whitelist((array.Length >= 2) ? array[1].ToLowerInvariant() : "", ItemNameResolver.JoinName(array, 2));
					return true;
				case "ban":
					GuardCommands.Ban(ItemNameResolver.JoinName(array, 1));
					return true;
				case "unban":
					GuardCommands.Unban(ItemNameResolver.JoinName(array, 1));
					return true;
				case "bans":
					GuardCommands.Bans();
					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 enum GuardEnforcement
	{
		Warn,
		Kick
	}
	internal static class ModConfig
	{
		internal static ConfigEntry<string> CommandPrefix { get; private set; }

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

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

		internal static ConfigEntry<bool> FixCrewList { 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> LobbyHideDefaultMigrated { 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<string> ScrapItemAnchors { get; private set; }

		internal static ConfigEntry<GuardEnforcement> GuardMode { get; private set; }

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

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

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

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

		internal static ConfigEntry<float> DeskGuardRange { 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<bool> AnnounceBans { 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)
		{
			CommandPrefix = file.Bind<string>("General", "CommandPrefix", "/", "Prefix for chat commands (e.g. /sell).");
			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.");
			AnnounceBans = file.Bind<bool>("Lobby", "AnnounceBans", false, "If true, a successful /ban broadcasts 'X was banned by the host.' to the crew. Default off: ban confirmations stay host-only.");
			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.");
			FixCrewList = file.Bind<bool>("Lobby", "FixCrewList", true, "Remove leftover names from the pause menu crew list. The game adds a name as soon as someone enters the Steam lobby and never removes it if they're turned away or give up, so public lobbies fill the list with strangers.");
			AllowLateJoin = file.Bind<bool>("Lobby", "AllowLateJoin", false, "OFF by default and switched back OFF every time the game starts — it reopens the lobby mid-run, which shouldn't happen unless you ask for it. Turn on with /lobby late on when someone needs to join; it lasts until you quit. Lets someone join after the game has started — ONLY while in orbit (ship phase, not leaving, not travelling). Landed or mid-flight stays closed: the joiner would arrive into a generated level that vanilla never expected to sync to a newcomer. Vanilla's version check, kicked-player check and SimpleSell's bans all still apply. Toggle in-game: /lobby late on|off.");
			HideLobbyModTag = file.Bind<bool>("Lobby", "HideLobbyModTag", true, "If true, newly created lobbies present as vanilla (no mods tag in the browser, no mods warning on join). Can be toggled live with /lobby hide|show.");
			LobbyHideDefaultMigrated = file.Bind<bool>("Lobby", "HideDefaultMigrated", false, "Internal flag — do not edit. (Round 26 one-time switch to hidden-by-default.)");
			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.");
			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 with /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 with /equipment set cruiser.");
			EquipItemAnchors = file.Bind<string>("Equipment", "ItemAnchors", "", "Per-item gear spots as 'Item Name|ship|x,y,z|pile;...'. Managed in-game via /e set <item> [pile|stack], /e unset <item>, /e anchors.");
			ScrapItemAnchors = file.Bind<string>("Scrap", "ItemAnchors", "", "Per-item SCRAP spots, same format as Equipment ItemAnchors. Managed in-game via /scrap set <item> [pile|stack], /scrap unset <item>, /scrap anchors. Anchored scrap types are skipped by /merge and moved by /scrap organize.");
			GuardMode = file.Bind<GuardEnforcement>("Lobby", "GuardMode", GuardEnforcement.Warn, "What happens when a client sends modded network traffic. Warn = private alert to the host only; Kick = auto-kick on first modded packet (also kicks networked cosmetic mods - they are indistinguishable on the wire). The sell-desk beam block is always active regardless of mode. Live toggle: /guard warn | kick.");
			WhitelistedPlayers = file.Bind<string>("Lobby", "WhitelistedPlayers", "", "Trusted players as 'steamid|name;...' — they may run the WhitelistedCommands from vanilla chat (no mods needed on their end). Managed in-game via /whitelist add|remove.");
			BannedPlayers = file.Bind<string>("Lobby", "BannedPlayers", "", "Banned players as 'steamid|name;...' — rejected at connection approval, before spawning. Name changes don't help; the Steam ID is what's enforced. Managed in-game via /ban, /unban, /bans.");
			WhitelistedCommands = file.Bind<string>("Lobby", "WhitelistedCommands", "stock, time, gear, organize, merge", "Comma-separated commands whitelisted players may use (canonical names; aliases resolve automatically). Selling, lobby, guard, and recall stay host-only unless you add them here.");
			AltCommandPrefix = file.Bind<string>("Lobby", "AltCommandPrefix", "!", "Second prefix whitelisted players may use (e.g. !merge). Friends whose own chat-command mods swallow '/' messages locally can still reach the host this way. Empty disables.");
			DeskGuardRange = file.Bind<float>("Lobby", "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.");
			if (!LobbyHideDefaultMigrated.Value)
			{
				HideLobbyModTag.Value = true;
				LobbyHideDefaultMigrated.Value = true;
			}
		}
	}
	[BepInPlugin("local.simplesell", "SimpleSell", "0.1.15")]
	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;
			ModConfig.AllowLateJoin.Value = false;
			_harmony.PatchAll();
			Logger.LogInfo((object)"SimpleSell v0.1.15 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.15";
	}
	public static class WhitelistInfo
	{
		public static bool IsWhitelisted(ulong steamId)
		{
			return PersistedIdList.Whitelist.Contains(steamId);
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "SimpleSell";

		public const string PLUGIN_NAME = "SimpleSell";

		public const string PLUGIN_VERSION = "0.1.12";
	}
}
namespace SimpleSell.Patches
{
	[HarmonyPatch(typeof(GameNetworkManager), "ConnectionApproval")]
	internal static class BanApprovalPatch
	{
		[HarmonyPostfix]
		private static void Postfix(ConnectionApprovalRequest request, ConnectionApprovalResponse response)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (response != null && response.Approved && PersistedIdList.Bans.Count != 0)
				{
					string text = Encoding.ASCII.GetString(request.Payload);
					string[] array = text.Split(',');
					if (array.Length >= 2 && ulong.TryParse(array[1], out var result) && PersistedIdList.Bans.Contains(result))
					{
						response.Approved = false;
						response.Reason = "You are banned from this lobby.";
						string text2 = PersistedIdList.Bans.NameOf(result);
						Plugin.Logger.LogInfo((object)$"[Ban] rejected join from {text2} ({result}).");
						ChatUtil.Send("⚠ Blocked banned player " + text2 + " from joining.");
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[BanApprovalPatch] {arg}");
			}
		}
	}
	[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 = null;
				if (!string.IsNullOrEmpty(chatMessage))
				{
					if (chatMessage.StartsWith(ModConfig.CommandPrefix.Value))
					{
						text = ModConfig.CommandPrefix.Value;
					}
					else if (!string.IsNullOrEmpty(ModConfig.AltCommandPrefix.Value) && chatMessage.StartsWith(ModConfig.AltCommandPrefix.Value))
					{
						text = ModConfig.AltCommandPrefix.Value;
					}
				}
				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 (!PersistedIdList.Whitelist.Contains(val.playerSteamId))
				{
					return true;
				}
				string text2 = chatMessage.Substring(text.Length).Trim();
				if (CommandRouter.SimpleHQLoaded && CommandRouter.IsBankSale(text2.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries)))
				{
					return true;
				}
				string text3 = text2.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault() ?? "";
				string text4 = CommandRouter.CanonicalVerb(text3.ToLowerInvariant());
				if (text4 == null)
				{
					return true;
				}
				if (!IsCommandAllowed(text4))
				{
					ChatUtil.Send("'" + text4 + "' is host-only.", broadcast: true);
					return false;
				}
				ManualLogSource logger = Plugin.Logger;
				if (logger != null)
				{
					logger.LogInfo((object)("[Whitelist] " + val.playerUsername + " ran /" + text2));
				}
				ChatUtil.BroadcastReplies = true;
				ChatUtil.InvokerName = val.playerUsername;
				bool flag;
				try
				{
					flag = CommandRouter.Route(text2);
				}
				finally
				{
					ChatUtil.BroadcastReplies = false;
					ChatUtil.InvokerName = null;
				}
				return !flag;
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[WhitelistChatPatch] {arg}");
				return true;
			}
		}

		private static bool IsCommandAllowed(string canonical)
		{
			string[] array = ModConfig.WhitelistedCommands.Value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
			foreach (string text in array)
			{
				string text2 = CommandRouter.CanonicalVerb(text.Trim().ToLowerInvariant()) ?? text.Trim().ToLowerInvariant();
				if (text2 == canonical)
				{
					return true;
				}
			}
			return false;
		}
	}
	[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(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();
				LobbyGuard.Reset();
				PlayerJoinLog.ResetSession();
				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");
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "OnPlayerConnectedClientRpc")]
	internal static class PlayerConnectedPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			try
			{
				if (!((Object)(object)NetworkManager.Singleton == (Object)null) && NetworkManager.Singleton.IsHost)
				{
					PlayerJoinLog.LogLobbyPlayers();
					LobbyGuard.KickAnyBanned();
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[PlayerConnectedPatch] {arg}");
			}
		}
	}
	[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();
					LateJoin.Refresh();
				}
			}
			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(GameNetworkManager), "ConnectionApproval")]
	internal static class LateJoinApprovalPatch
	{
		[HarmonyPrefix]
		[HarmonyPriority(800)]
		private static void Prefix(GameNetworkManager __instance, out bool __state)
		{
			__state = false;
			try
			{
				if (LateJoin.WindowOpen() && __instance.gameHasStarted)
				{
					__state = true;
					__instance.gameHasStarted = false;
					Plugin.Logger.LogInfo((object)"[LateJoin] allowing a join attempt (in orbit).");
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[LateJoinApprovalPatch.Prefix] {arg}");
			}
		}

		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void Postfix(GameNetworkManager __instance, bool __state)
		{
			try
			{
				if (__state)
				{
					__instance.gameHasStarted = true;
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[LateJoinApprovalPatch.Postfix] {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(GameNetworkManager), "LeaveLobbyAtGameStart")]
	internal static class LateJoinKeepLobbyPatch
	{
		[HarmonyPrefix]
		private static bool Prefix()
		{
			try
			{
				if (!LateJoin.Enabled)
				{
					return true;
				}
				Plugin.Logger.LogInfo((object)"[LateJoin] keeping the Steam lobby alive past game start.");
				LateJoin.ResetPushState();
				LateJoin.Refresh();
				return false;
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[LateJoinKeepLobbyPatch] {arg}");
				return true;
			}
		}
	}
	[HarmonyPatch(typeof(GameNetworkManager), "SetLobbyJoinable")]
	internal static class LateJoinJoinablePatch
	{
		[HarmonyPrefix]
		private static void Prefix(ref bool joinable)
		{
			try
			{
				if (!joinable && LateJoin.WindowOpen())
				{
					joinable = true;
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[LateJoinJoinablePatch] {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "OnPlayerConnectedClientRpc")]
	internal static class LateJoinWeatherPatch
	{
		internal struct Snapshot
		{
			internal LevelWeatherType[] Levels;

			internal LevelWeatherType Current;
		}

		[HarmonyPrefix]
		[HarmonyPriority(800)]
		private static void Prefix(StartOfRound __instance, ulong clientId, out Snapshot __state)
		{
			//IL_0085: 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_00c2: Unknown result type (might be due to invalid IL or missing references)
			__state = default(Snapshot);
			try
			{
				if (!LateJoin.Enabled || (Object)(object)GameNetworkManager.Instance == (Object)null || !GameNetworkManager.Instance.gameHasStarted)
				{
					return;
				}
				NetworkManager singleton = NetworkManager.Singleton;
				if ((!((Object)(object)singleton != (Object)null) || singleton.LocalClientId != clientId) && __instance.levels != null)
				{
					LevelWeatherType[] array = (LevelWeatherType[])(object)new LevelWeatherType[__instance.levels.Length];
					for (int i = 0; i < array.Length; i++)
					{
						array[i] = (LevelWeatherType)(((Object)(object)__instance.levels[i] != (Object)null) ? ((int)__instance.levels[i].currentWeather) : (-1));
					}
					__state = new Snapshot
					{
						Levels = array,
						Current = (LevelWeatherType)((!((Object)(object)TimeOfDay.Instance != (Object)null)) ? (-1) : ((int)TimeOfDay.Instance.currentLevelWeather))
					};
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[LateJoinWeatherPatch.Prefix] {arg}");
			}
		}

		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void Postfix(StartOfRound __instance, Snapshot __state)
		{
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Invalid comparison between Unknown and I4
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: 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_00a1: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (__state.Levels == null || __instance.levels == null)
				{
					return;
				}
				bool flag = false;
				int num = Math.Min(__state.Levels.Length, __instance.levels.Length);
				for (int i = 0; i < num; i++)
				{
					if (!((Object)(object)__instance.levels[i] == (Object)null) && (int)__instance.levels[i].currentWeather != (int)__state.Levels[i])
					{
						__instance.levels[i].currentWeather = __state.Levels[i];
						flag = true;
					}
				}
				if ((Object)(object)TimeOfDay.Instance != (Object)null && TimeOfDay.Instance.currentLevelWeather != __state.Current)
				{
					TimeOfDay.Instance.currentLevelWeather = __state.Current;
					flag = true;
				}
				if (flag)
				{
					Plugin.Logger.LogInfo((object)"[LateJoin] restored weather after a mid-run join (vanilla re-rolls it on connect).");
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[LateJoinWeatherPatch.Postfix] {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "StartGame")]
	internal static class LateJoinLandingPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			LateJoin.Refresh();
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "SetShipReadyToLand")]
	internal static class LateJoinOrbitPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			LateJoin.Refresh();
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "ShipLeave")]
	internal static class LateJoinDepartPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			LateJoin.Refresh();
		}
	}
	[HarmonyPatch(typeof(CustomMessagingManager), "InvokeNamedMessage")]
	internal static class NamedMessageGuardPatch
	{
		[HarmonyPrefix]
		private static void Prefix(ulong sender, ulong hash)
		{
			try
			{
				if (!LobbyGuard.IsSuiteMessage(hash))
				{
					LobbyGuard.OnModdedTraffic(sender, "named message");
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[NamedMessageGuardPatch] {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(CustomMessagingManager), "InvokeUnnamedMessage")]
	internal static class UnnamedMessageGuardPatch
	{
		[HarmonyPrefix]
		private static void Prefix(ulong clientId)
		{
			try
			{
				LobbyGuard.OnModdedTraffic(clientId, "unnamed message");
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[UnnamedMessageGuardPatch] {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(DepositItemsDesk), "AddObjectToDeskServerRpc")]
	internal static class DeskBeamGuardPatch
	{
		[HarmonyPrefix]
		private static bool Prefix(DepositItemsDesk __instance, NetworkObjectReference grabbableObjectNetObject)
		{
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: 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 (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;
				}
				LobbyGuard.OnDeskBeamBlocked(component, num);
				return false;
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[DeskBeamGuardPatch] {arg}");
				return true;
			}
		}
	}
	[HarmonyPatch(typeof(GameNetworkManager), "SteamMatchmaking_OnLobbyCreated")]
	internal static class LobbyCreatedPatch
	{
		[HarmonyPostfix]
		private static void Postfix(Result result, Lobby lobby)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Invalid comparison between Unknown and I4
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if ((int)result == 1 && ModConfig.HideLobbyModTag.Value)
				{
					LobbyTagFeature.Apply(hide: true, lobby);
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[LobbyCreatedPatch] {arg}");
			}
		}
	}
	[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 CrewListFix
	{
		[HarmonyPatch(typeof(GameNetworkManager), "SteamMatchmaking_OnLobbyMemberJoined")]
		private static class LobbyMemberJoinedPatch
		{
			[HarmonyPostfix]
			private static void Postfix()
			{
				Reconcile();
			}
		}

		[HarmonyPatch(typeof(GameNetworkManager), "SteamMatchmaking_OnLobbyMemberLeave")]
		private static class LobbyMemberLeavePatch
		{
			[HarmonyPostfix]
			private static void Postfix()
			{
				Reconcile();
			}
		}

		[HarmonyPatch(typeof(GameNetworkManager), "Singleton_OnClientDisconnectCallback")]
		private static class ClientDisconnectPatch
		{
			[HarmonyPostfix]
			private static void Postfix()
			{
				Reconcile();
			}
		}

		internal static void Reconcile()
		{
			try
			{
				if (!ModConfig.FixCrewList.Value)
				{
					return;
				}
				StartOfRound instance = StartOfRound.Instance;
				if (instance?.allPlayerScripts == null || instance.ClientPlayerList == null)
				{
					return;
				}
				QuickMenuManager val = Object.FindObjectOfType<QuickMenuManager>();
				if (val?.playerListSlots == null)
				{
					return;
				}
				HashSet<int> hashSet = new HashSet<int>(instance.ClientPlayerList.Values);
				int num = Mathf.Min(val.playerListSlots.Length, instance.allPlayerScripts.Length);
				int num2 = 0;
				int num3 = 0;
				for (int i = 0; i < num; i++)
				{
					PlayerListSlot val2 = val.playerListSlots[i];
					if (val2 == null)
					{
						continue;
					}
					PlayerControllerB val3 = instance.allPlayerScripts[i];
					if (hashSet.Contains(i) && (Object)(object)val3 != (Object)null && val3.playerSteamId != 0L)
					{
						if (!val2.isConnected || val2.playerSteamId != val3.playerSteamId)
						{
							val.AddUserToPlayerList(val3.playerSteamId, val3.playerUsername, i);
							num2++;
						}
					}
					else if (val2.isConnected || ((Object)(object)val2.slotContainer != (Object)null && val2.slotContainer.activeSelf))
					{
						val.RemoveUserFromPlayerList(i);
						num3++;
					}
				}
				if (num2 + num3 > 0)
				{
					Plugin.Logger.LogInfo((object)$"[CrewList] tidied the pause menu list: {num2} restored, {num3} leftover name(s) removed.");
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[CrewListFix.Reconcile] {arg}");
			}
		}
	}
	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 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 LateJoin
	{
		private static bool _lastPushed;

		internal static bool Enabled
		{
			get
			{
				if (ModConfig.AllowLateJoin.Value)
				{
					return !TournamentGate.Active;
				}
				return false;
			}
		}

		internal static bool InOrbit()
		{
			try
			{
				StartOfRound instance = StartOfRound.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					return false;
				}
				return instance.inShipPhase && !instance.beganLoadingNewLevel && !instance.shipIsLeaving && !instance.travellingToNewLevel;
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[LateJoin.InOrbit] {arg}");
				return false;
			}
		}

		internal static bool WindowOpen()
		{
			if (Enabled)
			{
				return InOrbit();
			}
			return false;
		}

		internal static void Refresh()
		{
			try
			{
				GameNetworkManager instance = GameNetworkManager.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					return;
				}
				bool flag = WindowOpen();
				if (flag != _lastPushed)
				{
					_lastPushed = flag;
					instance.SetLobbyJoinable(flag);
					Plugin.Logger.LogInfo((object)("[LateJoin] lobby " + (flag ? "OPEN (in orbit)" : "closed") + "."));
					if (flag)
					{
						ChatUtil.Send("Lobby open — someone can join while we're in orbit.");
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[LateJoin.Refresh] {arg}");
			}
		}

		internal static void ResetPushState()
		{
			_lastPushed = false;
		}

		internal static string Status()
		{
			if (!Enabled)
			{
				return "Late join: OFF. /lobby late on to allow joins while in orbit.";
			}
			if (!InOrbit())
			{
				return "Late join: ON — but closed until we're back in orbit.";
			}
			return "Late join: ON — lobby is OPEN right now (in orbit).";
		}
	}
	internal static class LobbyGuard
	{
		private static readonly HashSet<ulong> _flagged = new HashSet<ulong>();

		private static float _lastBeamAlert;

		private static int _beamBlockedSinceAlert;

		private static HashSet<ulong> _suiteHashes;

		internal static IReadOnlyCollection<ulong> Flagged => _flagged;

		internal static bool IsFlagged(ulong clientId)
		{
			return _flagged.Contains(clientId);
		}

		internal static void KickAnyBanned()
		{
			try
			{
				NetworkManager singleton = NetworkManager.Singleton;
				StartOfRound instance = StartOfRound.Instance;
				if ((Object)(object)singleton == (Object)null || !singleton.IsHost || instance?.allPlayerScripts == null || PersistedIdList.Bans.Count == 0)
				{
					return;
				}
				PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController;
				PlayerControllerB[] allPlayerScripts = instance.allPlayerScripts;
				foreach (PlayerControllerB val2 in allPlayerScripts)
				{
					if (!((Object)(object)val2 == (Object)null) && val2.isPlayerControlled && !((Object)(object)val2 == (Object)(object)val) && val2.playerSteamId != 0L && PersistedIdList.Bans.Contains(val2.playerSteamId))
					{
						Plugin.Logger.LogInfo((object)$"[Ban] {val2.playerUsername} ({val2.playerSteamId}) is banned: kicking.");
						ChatUtil.Send("⚠ " + val2.playerUsername + " is banned: kicked.");
						Kick(val2.actualClientId);
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[LobbyGuard.KickAnyBanned] {arg}");
			}
		}

		internal static bool IsSuiteMessage(ulong hash)
		{
			try
			{
				if (_suiteHashes == null)
				{
					_suiteHashes = new HashSet<ulong>();
					string[] array = new string[8] { "SimpleSuite.Hello", "SimpleSuite.Verdict", "SimpleSuite.Spray", "SimpleBlackBox.Hello", "SimpleBlackBox.Verdict", "SimpleHQ.Hello", "SimpleWeather.Hello", "SimpleWeather.Verdict" };
					foreach (string text in array)
					{
						_suiteHashes.Add(XXHash.Hash32(text));
						_suiteHashes.Add(XXHash.Hash64(text));
					}
				}
				return _suiteHashes.Contains(hash);
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[LobbyGuard.IsSuiteMessage] {arg}");
				return false;
			}
		}

		internal static void Reset()
		{
			_flagged.Clear();
			_beamBlockedSinceAlert = 0;
		}

		internal static void OnModdedTraffic(ulong sender, string kind)
		{
			try
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if ((Object)(object)singleton == (Object)null || !singleton.IsHost || sender == singleton.LocalClientId)
				{
					return;
				}
				if (_flagged.Add(sender))
				{
					string text = ResolveName(sender);
					ChatUtil.Send("⚠ " + text + " is running networked mods (" + kind + ").");
					if (ModConfig.GuardMode.Value == GuardEnforcement.Kick)
					{
						if (Kick(sender))
						{
							ChatUtil.Send("Auto-kicked " + text + " (guard mode: kick).");
						}
					}
					else
					{
						ChatUtil.Send("Guard mode is warn — /kick " + text + " or /guard kick to enforce.");
					}
				}
				else if (ModConfig.GuardMode.Value == GuardEnforcement.Kick)
				{
					Kick(sender);
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[LobbyGuard.OnModdedTraffic] {arg}");
			}
		}

		internal static void OnDeskBeamBlocked(GrabbableObject obj, float dist)
		{
			_beamBlockedSinceAlert++;
			ManualLogSource logger = Plugin.Logger;
			if (logger != null)
			{
				logger.LogWarning((object)string.Format("[LobbyGuard] blocked desk beam: {0} from {1:F0}m away.", ((Object)(object)obj != (Object)null) ? ((Object)obj).name : "?", dist));
			}
			if (!(Time.realtimeSinceStartup - _lastBeamAlert < 5f))
			{
				_lastBeamAlert = Time.realtimeSinceStartup;
				ChatUtil.Send($"⚠ Blocked {_beamBlockedSinceAlert} item(s) beamed to the sell desk by a modded client. /players to review.");
				_beamBlockedSinceAlert = 0;
			}
		}

		internal static string ResolveName(ulong clientId)
		{
			PlayerControllerB val = FindByClientId(clientId);
			if (!((Object)(object)val != (Object)null) || string.IsNullOrEmpty(val.playerUsername))
			{
				return $"client {clientId}";
			}
			return val.playerUsername;
		}

		internal static PlayerControllerB FindByClientId(ulong clientId)
		{
			StartOfRound instance = StartOfRound.Instance;
			if (instance?.allPlayerScripts == null)
			{
				return null;
			}
			PlayerControllerB[] allPlayerScripts = instance.allPlayerScripts;
			foreach (PlayerControllerB val in allPlayerScripts)
			{
				if ((Object)(object)val != (Object)null && val.isPlayerControlled && val.actualClientId == clientId)
				{
					return val;
				}
			}
			return null;
		}

		internal static bool Kick(ulong clientId)
		{
			try
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if ((Object)(object)singleton == (Object)null || clientId == singleton.LocalClientId)
				{
					return false;
				}
				StartOfRound instance = StartOfRound.Instance;
				if (instance?.allPlayerScripts != null)
				{
					for (int i = 0; i < instance.allPlayerScripts.Length; i++)
					{
						PlayerControllerB val = instance.allPlayerScripts[i];
						if ((Object)(object)val != (Object)null && val.isPlayerControlled && val.actualClientId == clientId)
						{
							instance.KickPlayer(i);
							return true;
						}
					}
				}
				singleton.DisconnectClient(clientId, "Rejected by host.");
				return true;
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[LobbyGuard.Kick] {arg}");
				return false;
			}
		}
	}
	internal static class LobbyTagFeature
	{
		private static readonly string[] ModTagKeys = new string[1] { "dmods" };

		private static readonly Dictionary<string, string> _saved = new Dictionary<string, string>();

		private static string _visibility = "(unchanged)";

		internal static bool Hidden { get; private set; }

		internal static string VisibilityStatus => _visibility;

		internal static void Apply(bool hide, Lobby? lobbyOverride = null)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Lobby? val = lobbyOverride;
				Lobby? obj;
				if (!val.HasValue)
				{
					GameNetworkManager instance = GameNetworkManager.Instance;
					obj = ((instance != null) ? instance.currentLobby : ((Lobby?)null));
				}
				else
				{
					obj = val;
				}
				Lobby? val2 = obj;
				if (!val2.HasValue || !val2.HasValue)
				{
					ChatUtil.Send("No active lobby.");
					return;
				}
				Lobby value = val2.Value;
				if (hide)
				{
					string[] modTagKeys = ModTagKeys;
					foreach (string text in modTagKeys)
					{
						string data = ((Lobby)(ref value)).GetData(text);
						if (!string.IsNullOrEmpty(data))
						{
							_saved[text] = data;
						}
						((Lobby)(ref value)).SetData(text, "f");
					}
					Hidden = true;
					ChatUtil.Send("Lobby now presents as vanilla.");
					return;
				}
				foreach (KeyValuePair<string, string> item in _saved)
				{
					((Lobby)(ref value)).SetData(item.Key, item.Value);
				}
				_saved.Clear();
				Hidden = false;
				ChatUtil.Send("Lobby mod tag restored.");
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[LobbyTagFeature] {arg}");
			}
		}

		internal static void Status()
		{
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			ChatUtil.Send((Hidden ? "Lobby tag: HIDDEN (presenting as vanilla)." : "Lobby tag: visible (normal modded tag).") + " Visibility: " + _visibility + ".");
			try
			{
				GameNetworkManager instance = GameNetworkManager.Instance;
				Lobby? val = ((instance != null) ? instance.currentLobby : ((Lobby?)null));
				if (!val.HasValue || !val.HasValue)
				{
					ChatUtil.Send("⚠ No Steam lobby exists right now — nobody can join or be invited.");
					Plugin.Logger.LogWarning((object)"[LobbyStatus] currentLobby is null.");
					return;
				}
				Lobby value = val.Value;
				string data = ((Lobby)(ref value)).GetData("joinable");
				string data2 = ((Lobby)(ref value)).GetData("vers");
				int memberCount = ((Lobby)(ref value)).MemberCount;
				string text = ((data == "false") ? "joiners WILL be refused" : ((memberCount >= 4) ? "lobby is FULL (4)" : "a joiner should get in"));
				ChatUtil.Send($"Steam lobby: joinable=\"{data}\" · members {memberCount}/4 · vers {data2} → {text}.");
				Plugin.Logger.LogInfo((object)($"[LobbyStatus] id={((Lobby)(ref value)).Id} joinable=\"{data}\" members={memberCount} vers=\"{data2}\" " + $"lateJoin={ModConfig.AllowLateJoin.Value} window={LateJoin.InOrbit()}"));
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[LobbyTagFeature.Status] {arg}");
			}
		}

		internal static void SetVisibility(string mode)
		{
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				GameNetworkManager instance = GameNetworkManager.Instance;
				Lobby? val = ((instance != null) ? instance.currentLobby : ((Lobby?)null));
				if (!val.HasValue || !val.HasValue)
				{
					HostSettings val2 = GameNetworkManager.Instance?.lobbyHostSettings;
					if (val2 == null || (mode != "public" && mode != "friends" && mode != "private"))
					{
						ChatUtil.Send("No active lobby.");
						return;
					}
					val2.isLobbyPublic = mode == "public";
					_visibility = mode;
					ChatUtil.Send((mode == "public") ? "The Steam lobby is already closed (the game has started), so nobody new can join. Marked as public." : ("The Steam lobby is already closed (the game has started), so nobody new can join. Marked as " + ((mode == "friends") ? "friends only" : "private") + "."));
					return;
				}
				Lobby value = val.Value;
				switch (mode)
				{
				case "public":
					((Lobby)(ref value)).SetPublic();
					break;
				case "friends":
					((Lobby)(ref value)).SetFriendsOnly();
					break;
				case "private":
					((Lobby)(ref value)).SetPrivate();
					break;
				default:
					ChatUtil.Send("Usage: /lobby public | friends | private");
					return;
				}
				_visibility = mode;
				if (GameNetworkManager.Instance.lobbyHostSettings != null)
				{
					GameNetworkManager.Instance.lobbyHostSettings.isLobbyPublic = mode == "public";
				}
				string text = "";
				if (GameNetworkManager.Instance.gameHasStarted)
				{
					text = ((!ModConfig.AllowLateJoin.Value) ? " The run has started and late join is OFF — /lobby late on to allow joins." : (LateJoin.InOrbit() ? " Late join is on and we're in orbit — a friend can join now." : " Late join is on, but the window only opens in orbit."));
				}
				ChatUtil.Send("Lobby visibility: " + mode + "." + text);
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[LobbyTagFeature.SetVisibility] {arg}");
			}
		}

		internal static void Rename(string newName)
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (string.IsNullOrWhiteSpace(newName))
				{
					ChatUtil.Send("Usage: /lobby name <new name>");
					return;
				}
				GameNetworkManager instance = GameNetworkManager.Instance;
				Lobby? val = ((instance != null) ? instance.currentLobby : ((Lobby?)null));
				if (!val.HasValue || !val.HasValue)
				{
					ChatUtil.Send("No active lobby.");
					return;
				}
				Lobby value = val.Value;
				((Lobby)(ref value)).SetData("name", newName);
				GameNetworkManager.Instance.steamLobbyName = newName;
				ChatUtil.Send("Lobby renamed to \"" + newName + "\" — new browsers see it immediately.");
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[LobbyTagFeature.Rename] {arg}");
			}
		}
	}
	internal static class PlayerJoinLog
	{
		private static readonly string LogPath = Path.Combine(Paths.ConfigPath, "SimpleSell.playerlog.txt");

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

		internal static void ResetSession()
		{
			_loggedThisSession.Clear();
		}

		internal static void LogLobbyPlayers()
		{
			try
			{
				StartOfRound instance = StartOfRound.Instance;
				if (instance?.allPlayerScripts == null)
				{
					return;
				}
				PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController;
				PlayerControllerB[] allPlayerScripts = instance.allPlayerScripts;
				foreach (PlayerControllerB val2 in allPlayerScripts)
				{
					if (!((Object)(object)val2 == (Object)null) && val2.isPlayerControlled && !((Object)(object)val2 == (Object)(object)val) && val2.playerSteamId != 0L && _loggedThisSession.Add(val2.playerSteamId))
					{
						File.AppendAllText(LogPath, $"{DateTime.Now:yyyy-MM-dd HH:mm}|{val2.playerSteamId}|{val2.playerUsername}\r\n");
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[PlayerJoinLog] {arg}");
			}
		}

		internal static List<(ulong SteamId, string Name, string LastSeen)> FindByName(string query)
		{
			List<(ulong, string, string)> list = new List<(ulong, string, string)>();
			try
			{
				if (!File.Exists(LogPath) || string.IsNullOrWhiteSpace(query))
				{
					return list;
				}
				string value = Squash(query);
				HashSet<ulong> hashSet = new HashSet<ulong>();
				foreach (string item in File.ReadAllLines(LogPath).Reverse())
				{
					string[] array = item.Split('|');
					if (array.Length >= 3 && ulong.TryParse(array[1], out var result) && Squash(array[2]).Contains(value) && hashSet.Add(result))
					{
						list.Add((result, array[2], array[0]));
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[PlayerJoinLog.FindByName] {arg}");
			}
			return list;
		}

		private static string Squash(string s)
		{
			return new string((s ?? "").ToLowerInvariant().Where(char.IsLetterOrDigit).ToArray());
		}

		internal static List<(ulong SteamId, string Name, string LastSeen)> Recent(int count, HashSet<ulong> exclude)
		{
			List<(ulong, string, string)> list = new List<(ulong, string, string)>();
			try
			{
				if (!File.Exists(LogPath))
				{
					return list;
				}
				HashSet<ulong> hashSet = new HashSet<ulong>();
				foreach (string item in File.ReadAllLines(LogPath).Reverse())
				{
					string[] array = item.Split('|');
					if (array.Length == 3 && ulong.TryParse(array[1], out var result) && result != 0L && (exclude == null || !exclude.Contains(result)) && hashSet.Add(result))
					{
						list.Add((result, array[2], array[0]));
						if (list.Count >= count)
						{
							break;
						}
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"[PlayerJoinLog.Recent] {arg}");
			}
			return list;
		}
	}
	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()
		{
			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;
			try
			{
				List<ScrapItem> items = ScrapScanner.ScanAll(shipAndCruiserOnly: true, applyExclusions: false);
				num = ScrapScanner.SumValue(items, ScrapLocation.Ship);
				num2 = ScrapScanner.SumValue(items, ScrapLocation.Cruiser);
				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);
			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 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)[4]
		{
			("kitchen knife", "Kitchen knife"),
			("double-barrel", "Double-barrel"),
			("shotgun shell", "Shotgun shell"),
			("homemade flashbang", "Homemade flashbang")
		};

		internal static void TrySeed()
		{
			TrySeedGear();
			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 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 GearList
	{
		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.GearItems.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;
		}

		private static void Save()
		{
			ModConfig.GearItems.Value = string.Join(",", from s in Load()
				orderby s
				select s);
		}
	}
	internal static class ItemNameResolver
	{
		internal enum ResultKind
		{
			Ok,
			Ambiguous,
			Unknown,
			Empty
		}

		internal readonly struct ResolveResult
		{
			internal ResultKind Kind { get; }

			internal string Name { get; }

			internal IReadOnlyList<string> Candidates { get; }

			internal static ResolveResult Empty => new ResolveResult(ResultKind.Empty, null, null);

			internal ResolveResult(ResultKind kind, string name, IReadOnlyList<string> candidates)
			{
				Kind = kind;
				Name = name;
				Candidates = candidates;
			}

			internal static ResolveResult Ok(string name)
			{
				return new ResolveResult(ResultKind.Ok, name, null);
			}

			internal static ResolveResult Ambiguous(IReadOnlyList<string> candidates)
			{
				return new ResolveResult(ResultKind.Ambiguous, null, candidates);
			}

			internal static ResolveResult Unknown(string raw)
			{
				return new ResolveResult(ResultKind.Unknown, raw, null);
			}
		}

		private static readonly Dictionary<string, string> Aliases = new Dictionary<string, string>
		{
			{ "shotgunshell", "Ammo" },
			{ "shotgunshells", "Ammo" },
			{ "shell", "Ammo" },
			{ "shells", "Ammo" },
			{ "shotgunammo", "Ammo" }
		};

		internal static string JoinName(string[] parts, int startIndex)
		{
			if (parts == null || startIndex >= parts.Length)
			{
				return string.Empty;
			}
			string text = string.Join(" ", parts, startIndex, parts.Length - startIndex).Trim();
			if (text.Length >= 2 && text[0] == '"' && text[text.Length - 1] == '"')
			{
				text = text.Substring(1, text.Length - 2).Trim();
			}
			return text;
		}

		internal static string ApplyAlias(string query)
		{
			if (string.IsNullOrWhiteSpace(query))
			{
				return query;
			}
			string norm = Normalize(query.Trim());
			if (!Aliases.TryGetValue(norm, out var canonical))
			{
				return query;
			}
			List<string> allItemNames = GetAllItemNames();
			string singular = Singularize(norm);
			if (allItemNames.Any((string n) => Normalize(n).Contains(norm) || Normalize(n).Contains(singular)))
			{
				return query;
			}
			if (!allItemNames.Any((string n) => n.Equals(canonical, StringComparison.OrdinalIgnoreCase)))
			{
				return query;
			}
			return canonical;
		}

		internal static ResolveResult Resolve(string input)
		{
			string query = ApplyAlias(JoinName(new string[1] { input }, 0));
			if (string.IsNullOrWhiteSpace(query))
			{
				return ResolveResult.Empty;
			}
			List<string> allItemNames = GetAllItemNames();
			string text = allItemNames.FirstOrDefault((string n) => n.Equals(query, StringComparison.OrdinalIgnoreCase));
			if (text != null)
			{
				return ResolveResult.Ok(text);
			}
			string norm = Normalize(query);
			List<string> list = allItemNames.Where((string n) => Normalize(n).Contains(norm)).ToList();
			if (list.Count == 0)
			{
				string singular = Singularize(norm);
				if (singular != nor