Decompiled source of ShipStarterKit v1.0.0

ShipStarterKit.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ShipStarterKit")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("ShipStarterKit")]
[assembly: AssemblyTitle("ShipStarterKit")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.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.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[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 ShipStarterKit
{
	internal static class Es3SaveGate
	{
		public const string SuppliedKey = "ShipStarterKit_Supplied";

		private static Type? _es3Type;

		private static bool _es3Resolved;

		private static MethodInfo? _keyExists;

		private static MethodInfo? _deleteKey;

		private static MethodInfo? _saveBool;

		public static string? CurrentSaveName()
		{
			GameNetworkManager instance = GameNetworkManager.Instance;
			if ((Object)(object)instance == (Object)null || string.IsNullOrEmpty(instance.currentSaveFileName))
			{
				return null;
			}
			return instance.currentSaveFileName;
		}

		public static bool AlreadySupplied(string saveName)
		{
			if (TryEs3KeyExists("ShipStarterKit_Supplied", saveName, out var exists))
			{
				return exists;
			}
			return File.Exists(FallbackPath(saveName));
		}

		public static void MarkSupplied(string saveName)
		{
			if (TryEs3SaveBool("ShipStarterKit_Supplied", value: true, saveName))
			{
				Plugin.V("ES3 marked ShipStarterKit_Supplied=true on '" + saveName + "'.");
				return;
			}
			try
			{
				string text = FallbackPath(saveName);
				Directory.CreateDirectory(Path.GetDirectoryName(text));
				File.WriteAllText(text, "1");
				Plugin.Log.LogWarning((object)("ES3 unavailable; wrote fallback marker " + text));
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Failed to write supply marker for '{saveName}': {arg}");
			}
		}

		public static void ClearSupplied(string saveName)
		{
			if (TryEs3DeleteKey("ShipStarterKit_Supplied", saveName))
			{
				Plugin.V("ES3 cleared ShipStarterKit_Supplied on '" + saveName + "' (new game reset).");
			}
			try
			{
				string path = FallbackPath(saveName);
				if (File.Exists(path))
				{
					File.Delete(path);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Failed to clear fallback marker for '" + saveName + "': " + ex.Message));
			}
		}

		private static string FallbackPath(string saveName)
		{
			string text = string.Join("_", saveName.Split(Path.GetInvalidFileNameChars()));
			return Path.Combine(Paths.ConfigPath, "ShipStarterKit", "supplied_" + text + ".flag");
		}

		private static bool EnsureEs3()
		{
			if (_es3Resolved)
			{
				return _es3Type != null;
			}
			_es3Resolved = true;
			_es3Type = AccessTools.TypeByName("ES3");
			if (_es3Type == null)
			{
				Plugin.Log.LogWarning((object)"ES3 type not found; using file fallback for first-load gate.");
				return false;
			}
			_keyExists = AccessTools.Method(_es3Type, "KeyExists", new Type[2]
			{
				typeof(string),
				typeof(string)
			}, (Type[])null);
			_deleteKey = AccessTools.Method(_es3Type, "DeleteKey", new Type[2]
			{
				typeof(string),
				typeof(string)
			}, (Type[])null);
			MethodInfo[] methods = _es3Type.GetMethods(BindingFlags.Static | BindingFlags.Public);
			foreach (MethodInfo methodInfo in methods)
			{
				if (!(methodInfo.Name != "Save") && methodInfo.IsGenericMethodDefinition)
				{
					ParameterInfo[] parameters = methodInfo.GetParameters();
					if (parameters.Length == 3 && parameters[0].ParameterType == typeof(string) && parameters[2].ParameterType == typeof(string))
					{
						_saveBool = methodInfo.MakeGenericMethod(typeof(bool));
						break;
					}
				}
			}
			if (_keyExists == null || _saveBool == null)
			{
				Plugin.Log.LogWarning((object)"ES3 methods incomplete; using file fallback for first-load gate.");
				_es3Type = null;
				return false;
			}
			Plugin.V("ES3 reflection ready for ShipStarterKit save gate.");
			return true;
		}

		private static bool TryEs3KeyExists(string key, string file, out bool exists)
		{
			exists = false;
			if (!EnsureEs3() || _keyExists == null)
			{
				return false;
			}
			try
			{
				exists = (bool)_keyExists.Invoke(null, new object[2] { key, file });
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("ES3.KeyExists failed: " + ex.Message));
				return false;
			}
		}

		private static bool TryEs3SaveBool(string key, bool value, string file)
		{
			if (!EnsureEs3() || _saveBool == null)
			{
				return false;
			}
			try
			{
				_saveBool.Invoke(null, new object[3] { key, value, file });
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("ES3.Save<bool> failed: " + ex.Message));
				return false;
			}
		}

		private static bool TryEs3DeleteKey(string key, string file)
		{
			if (!EnsureEs3() || _deleteKey == null)
			{
				return false;
			}
			try
			{
				_deleteKey.Invoke(null, new object[2] { key, file });
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("ES3.DeleteKey failed: " + ex.Message));
				return false;
			}
		}
	}
	internal static class HostModGate
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static HandleNamedMessageDelegate <0>__OnMessage;

			public static Action<ulong> <1>__OnClientConnected;
		}

		public const string MessageName = "MrGlim.ShipStarterKit";

		private const byte OpHostHello = 0;

		private const byte OpClientSyncRequest = 1;

		private static bool _registered;

		private static bool _clientConnectedHooked;

		private static bool _requestedSync;

		private static bool _clientHostEnabled;

		public static bool HostHasMod
		{
			get
			{
				EnsureRegistered();
				NetworkManager singleton = NetworkManager.Singleton;
				if ((Object)(object)singleton == (Object)null)
				{
					return true;
				}
				if (singleton.IsServer || singleton.IsHost)
				{
					return LocalEnabled();
				}
				return _clientHostEnabled;
			}
		}

		public static bool FeaturesActive => HostHasMod;

		private static bool LocalEnabled()
		{
			if (Plugin.Enabled != null)
			{
				return Plugin.Enabled.Value;
			}
			return false;
		}

		public static void Reset()
		{
			_registered = false;
			_clientConnectedHooked = false;
			_requestedSync = false;
			_clientHostEnabled = false;
		}

		public static void EnsureRegistered()
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Expected O, but got Unknown
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton == (Object)null)
			{
				_registered = false;
				_clientConnectedHooked = false;
				return;
			}
			if (!_registered)
			{
				CustomMessagingManager customMessagingManager = singleton.CustomMessagingManager;
				object obj = <>O.<0>__OnMessage;
				if (obj == null)
				{
					HandleNamedMessageDelegate val = OnMessage;
					<>O.<0>__OnMessage = val;
					obj = (object)val;
				}
				customMessagingManager.RegisterNamedMessageHandler("MrGlim.ShipStarterKit", (HandleNamedMessageDelegate)obj);
				_registered = true;
				Plugin.Log.LogInfo((object)"MrGlim.ShipStarterKit net handler registered.");
			}
			if (singleton.IsServer && !_clientConnectedHooked)
			{
				singleton.OnClientConnectedCallback += OnClientConnected;
				_clientConnectedHooked = true;
			}
			if (!singleton.IsServer && singleton.IsConnectedClient && !_requestedSync)
			{
				_requestedSync = true;
				RequestSync();
			}
		}

		private static void OnClientConnected(ulong clientId)
		{
			NetworkManager singleton = NetworkManager.Singleton;
			if (!((Object)(object)singleton == (Object)null) && singleton.IsServer && clientId != singleton.LocalClientId)
			{
				SendHello(clientId);
			}
		}

		private static void RequestSync()
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			NetworkManager singleton = NetworkManager.Singleton;
			if (!((Object)(object)singleton == (Object)null) && !singleton.IsServer)
			{
				FastBufferWriter val = default(FastBufferWriter);
				((FastBufferWriter)(ref val))..ctor(16, (Allocator)2, -1);
				byte b = 1;
				((FastBufferWriter)(ref val)).WriteValueSafe<byte>(ref b, default(ForPrimitives));
				singleton.CustomMessagingManager.SendNamedMessage("MrGlim.ShipStarterKit", 0uL, val, (NetworkDelivery)2);
				((FastBufferWriter)(ref val)).Dispose();
			}
		}

		private static void SendHello(ulong clientId)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			NetworkManager singleton = NetworkManager.Singleton;
			if (!((Object)(object)singleton == (Object)null) && singleton.IsServer)
			{
				FastBufferWriter val = default(FastBufferWriter);
				((FastBufferWriter)(ref val))..ctor(16, (Allocator)2, -1);
				byte b = 0;
				((FastBufferWriter)(ref val)).WriteValueSafe<byte>(ref b, default(ForPrimitives));
				bool flag = LocalEnabled();
				((FastBufferWriter)(ref val)).WriteValueSafe<bool>(ref flag, default(ForPrimitives));
				singleton.CustomMessagingManager.SendNamedMessage("MrGlim.ShipStarterKit", clientId, val, (NetworkDelivery)2);
				((FastBufferWriter)(ref val)).Dispose();
			}
		}

		private static void OnMessage(ulong sender, FastBufferReader reader)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			byte b = default(byte);
			((FastBufferReader)(ref reader)).ReadValueSafe<byte>(ref b, default(ForPrimitives));
			NetworkManager singleton = NetworkManager.Singleton;
			switch (b)
			{
			case 1:
				if ((Object)(object)singleton != (Object)null && singleton.IsServer)
				{
					SendHello(sender);
				}
				break;
			case 0:
			{
				bool clientHostEnabled = default(bool);
				((FastBufferReader)(ref reader)).ReadValueSafe<bool>(ref clientHostEnabled, default(ForPrimitives));
				if ((Object)(object)singleton != (Object)null && !singleton.IsServer)
				{
					_clientHostEnabled = clientHostEnabled;
					Plugin.Log.LogInfo((object)("Host hello: enabled=" + clientHostEnabled));
				}
				break;
			}
			}
		}
	}
	[HarmonyPatch(typeof(GameNetworkManager), "Disconnect")]
	internal static class HostModGateDisconnectPatch
	{
		public static void Prefix()
		{
			HostModGate.Reset();
			Plugin.Log.LogInfo((object)"MrGlim.ShipStarterKit gate reset on disconnect.");
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "Start")]
	internal static class HostModGateStartPatch
	{
		public static void Postfix()
		{
			HostModGate.EnsureRegistered();
		}
	}
	internal static class ItemCountConfig
	{
		private const string Section = "ItemCounts";

		private static readonly (string Key, string[] Matchers, int Default)[] KnownDefaults = new(string, string[], int)[7]
		{
			("BeltBag", new string[2] { "belt bag", "beltbag" }, 10),
			("ProFlashlight", new string[3] { "pro-flashlight", "pro flashlight", "proflashlight" }, 10),
			("Jetpack", new string[1] { "jetpack" }, 10),
			("Lockpicker", new string[2] { "lockpicker", "lockpick" }, 10),
			("SprayPaint", new string[2] { "spray paint", "spraypaint" }, 10),
			("WeedKiller", new string[2] { "weed killer", "weedkiller" }, 10),
			("Shovel", new string[1] { "shovel" }, 10)
		};

		private static readonly Dictionary<string, ConfigEntry<int>> Entries = new Dictionary<string, ConfigEntry<int>>(StringComparer.OrdinalIgnoreCase);

		private static readonly HashSet<string> BoundDynamicKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static ConfigFile? _config;

		private static bool _defaultsBound;

		public static void BindDefaults(ConfigFile config)
		{
			_config = config;
			if (!_defaultsBound)
			{
				(string, string[], int)[] knownDefaults = KnownDefaults;
				for (int i = 0; i < knownDefaults.Length; i++)
				{
					var (text, _, num) = knownDefaults[i];
					Entries[text] = config.Bind<int>("ItemCounts", text, num, "How many '" + text + "' to spawn on first save load (0 = skip). Matched flexibly against store itemName.");
				}
				_defaultsBound = true;
			}
		}

		public static void EnsureBuyableEntries(Item[]? buyable)
		{
			if (_config == null || buyable == null)
			{
				return;
			}
			foreach (Item val in buyable)
			{
				if (!((Object)(object)val == (Object)null) && !string.IsNullOrWhiteSpace(val.itemName) && !val.isScrap && !TryGetKnownKey(val.itemName, out string _))
				{
					string text = SanitizeKey(val.itemName);
					if (!string.IsNullOrEmpty(text) && !Entries.ContainsKey(text) && !BoundDynamicKeys.Contains(text))
					{
						Entries[text] = _config.Bind<int>("ItemCounts", text, 0, "How many '" + val.itemName + "' to spawn on first save load (0 = skip). Auto-discovered from Terminal.buyableItemsList.");
						BoundDynamicKeys.Add(text);
						Plugin.V("Bound dynamic config ItemCounts." + text + " for store item '" + val.itemName + "' (default 0).");
					}
				}
			}
		}

		public static int GetCountForItem(Item item)
		{
			if ((Object)(object)item == (Object)null || string.IsNullOrWhiteSpace(item.itemName))
			{
				return 0;
			}
			if (TryGetKnownKey(item.itemName, out string key) && Entries.TryGetValue(key, out ConfigEntry<int> value))
			{
				return Math.Max(0, value.Value);
			}
			string key2 = SanitizeKey(item.itemName);
			if (Entries.TryGetValue(key2, out ConfigEntry<int> value2))
			{
				return Math.Max(0, value2.Value);
			}
			return 0;
		}

		public static bool TryGetKnownKey(string itemName, out string key)
		{
			string text = itemName.Trim();
			(string, string[], int)[] knownDefaults = KnownDefaults;
			for (int i = 0; i < knownDefaults.Length; i++)
			{
				(string, string[], int) tuple = knownDefaults[i];
				string item = tuple.Item1;
				string[] item2 = tuple.Item2;
				foreach (string value in item2)
				{
					if (text.Equals(value, StringComparison.OrdinalIgnoreCase) || text.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0)
					{
						key = item;
						return true;
					}
				}
			}
			key = "";
			return false;
		}

		private static string SanitizeKey(string itemName)
		{
			string text = Regex.Replace(itemName.Trim(), "[^A-Za-z0-9]+", "");
			if (text.Length == 0)
			{
				return "";
			}
			if (char.IsDigit(text[0]))
			{
				text = "Item" + text;
			}
			return text;
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "Start")]
	internal static class StartOfRoundStartSupplyPatch
	{
		public static void Postfix(StartOfRound __instance)
		{
			HostModGate.EnsureRegistered();
			NetworkManager singleton = NetworkManager.Singleton;
			if (!((Object)(object)singleton == (Object)null) && (singleton.IsServer || singleton.IsHost))
			{
				StarterKitSpawner.TrySupplyOnShipStart();
			}
		}
	}
	[HarmonyPatch(typeof(GameNetworkManager), "ResetSavedGameValues")]
	internal static class ResetSavedGameValuesClearGatePatch
	{
		public static void Postfix(GameNetworkManager __instance)
		{
			if (!((Object)(object)__instance == (Object)null) && !string.IsNullOrEmpty(__instance.currentSaveFileName))
			{
				Es3SaveGate.ClearSupplied(__instance.currentSaveFileName);
				StarterKitSpawner.ResetSession();
				Plugin.V("Cleared ShipStarterKit supply flag after ResetSavedGameValues on '" + __instance.currentSaveFileName + "'.");
			}
		}
	}
	[HarmonyPatch(typeof(GameNetworkManager), "Disconnect")]
	internal static class DisconnectResetSessionPatch
	{
		public static void Prefix()
		{
			StarterKitSpawner.ResetSession();
		}
	}
	[BepInPlugin("com.benhough.lethal.ShipStarterKit", "ShipStarterKit", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string ModGuid = "com.benhough.lethal.ShipStarterKit";

		public const string ModName = "ShipStarterKit";

		public const string ModVersion = "1.0.0";

		private readonly Harmony _harmony = new Harmony("com.benhough.lethal.ShipStarterKit");

		internal static Plugin Instance { get; private set; }

		internal static ManualLogSource Log { get; private set; }

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

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

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

		private void Awake()
		{
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Master toggle. Host with this mod enabled supplies starter store equipment on first load of each save.");
			VerboseLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "VerboseLogging", false, "Log item matching, pile positions, and save-gate decisions.");
			SpawnOncePerSave = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "SpawnOncePerSave", true, "If true (recommended), spawn only once per save file via an ES3 flag on that save. Reloading the same save or returning from moons will not re-spawn. Set false only for testing.");
			ItemCountConfig.BindDefaults(((BaseUnityPlugin)this).Config);
			try
			{
				_harmony.PatchAll(typeof(Plugin).Assembly);
				Log.LogInfo((object)"ShipStarterKit v1.0.0 loaded.");
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"Harmony patch failed: {arg}");
			}
		}

		internal static void V(string msg)
		{
			if (VerboseLogging != null && VerboseLogging.Value)
			{
				Log.LogInfo((object)msg);
			}
		}
	}
	internal static class PluginInfo
	{
		public const string PLUGIN_GUID = "com.benhough.lethal.ShipStarterKit";

		public const string PLUGIN_NAME = "ShipStarterKit";

		public const string PLUGIN_VERSION = "1.0.0";
	}
	internal static class StarterKitSpawner
	{
		private static bool _sessionAttempted;

		public static void ResetSession()
		{
			_sessionAttempted = false;
		}

		public static void TrySupplyOnShipStart()
		{
			//IL_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_0188: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ba: Unknown result type (might be due to invalid IL or missing references)
			if (_sessionAttempted)
			{
				return;
			}
			_sessionAttempted = true;
			if (Plugin.Enabled == null || !Plugin.Enabled.Value)
			{
				Plugin.V("Skip supply: Enabled=false.");
				return;
			}
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton == (Object)null || (!singleton.IsServer && !singleton.IsHost))
			{
				Plugin.V("Skip supply: not host/server.");
				return;
			}
			if (!HostModGate.FeaturesActive)
			{
				Plugin.V("Skip supply: HostModGate inactive.");
				return;
			}
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				Plugin.Log.LogWarning((object)"Skip supply: StartOfRound.Instance null.");
				return;
			}
			string text = Es3SaveGate.CurrentSaveName();
			if (string.IsNullOrEmpty(text))
			{
				Plugin.Log.LogWarning((object)"Skip supply: currentSaveFileName unavailable.");
				return;
			}
			bool flag = Plugin.SpawnOncePerSave == null || Plugin.SpawnOncePerSave.Value;
			if (flag && Es3SaveGate.AlreadySupplied(text))
			{
				Plugin.Log.LogInfo((object)("ShipStarterKit: save '" + text + "' already supplied — skipping."));
				return;
			}
			Terminal val = Object.FindObjectOfType<Terminal>();
			if ((Object)(object)val == (Object)null || val.buyableItemsList == null || val.buyableItemsList.Length == 0)
			{
				Plugin.Log.LogWarning((object)"Skip supply: Terminal.buyableItemsList unavailable.");
				return;
			}
			ItemCountConfig.EnsureBuyableEntries(val.buyableItemsList);
			List<(Item, int)> list = BuildSpawnPlan(val.buyableItemsList);
			if (list.Count == 0)
			{
				Plugin.Log.LogInfo((object)"ShipStarterKit: all ItemCounts are 0 — nothing to spawn.");
				if (flag)
				{
					Es3SaveGate.MarkSupplied(text);
				}
				return;
			}
			Vector3[] array = BuildPileCenters(instance, list.Count);
			int num = 0;
			for (int i = 0; i < list.Count; i++)
			{
				(Item, int) tuple = list[i];
				Item item = tuple.Item1;
				int item2 = tuple.Item2;
				Vector3 val2 = array[i];
				Plugin.Log.LogInfo((object)$"Spawning pile '{item.itemName}' x{item2} at {val2}");
				num += SpawnPile(instance, item, item2, val2);
			}
			if (flag)
			{
				Es3SaveGate.MarkSupplied(text);
			}
			Plugin.Log.LogInfo((object)$"ShipStarterKit: spawned {num} store items across {list.Count} piles on '{text}'.");
		}

		private static List<(Item item, int count)> BuildSpawnPlan(Item[] buyable)
		{
			List<(Item, int)> list = new List<(Item, int)>();
			HashSet<Item> hashSet = new HashSet<Item>();
			foreach (Item val in buyable)
			{
				if ((Object)(object)val == (Object)null || (Object)(object)val.spawnPrefab == (Object)null || val.isScrap || hashSet.Contains(val))
				{
					continue;
				}
				int countForItem = ItemCountConfig.GetCountForItem(val);
				if (countForItem > 0)
				{
					if (ItemCountConfig.TryGetKnownKey(val.itemName, out string key))
					{
						Plugin.V($"Matched store item '{val.itemName}' → config '{key}' count={countForItem}");
					}
					else
					{
						Plugin.V($"Matched store item '{val.itemName}' → dynamic count={countForItem}");
					}
					list.Add((val, countForItem));
					hashSet.Add(val);
				}
			}
			return list;
		}

		private static Vector3[] BuildPileCenters(StartOfRound sor, int pileCount)
		{
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//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)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: 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_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			Vector3[] array = (Vector3[])(object)new Vector3[pileCount];
			Vector3 val = ((sor.playerSpawnPositions != null && sor.playerSpawnPositions.Length != 0 && (Object)(object)sor.playerSpawnPositions[0] != (Object)null) ? sor.playerSpawnPositions[0].position : ((!((Object)(object)sor.elevatorTransform != (Object)null)) ? Vector3.zero : sor.elevatorTransform.position));
			val += new Vector3(0f, 0.15f, 1.2f);
			if (pileCount == 1)
			{
				array[0] = val;
				return array;
			}
			int num = Mathf.Clamp(Mathf.CeilToInt(Mathf.Sqrt((float)pileCount)), 2, 4);
			int num2 = Mathf.CeilToInt((float)pileCount / (float)num);
			float num3 = 1.15f;
			float num4 = 1.05f;
			float num5 = -0.5f * (float)(num - 1) * num3;
			float num6 = -0.35f * (float)(num2 - 1) * num4;
			for (int i = 0; i < pileCount; i++)
			{
				int num7 = i % num;
				int num8 = i / num;
				array[i] = val + new Vector3(num5 + (float)num7 * num3, 0f, num6 + (float)num8 * num4);
			}
			return array;
		}

		private static int SpawnPile(StartOfRound sor, Item item, int count, Vector3 center)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: 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)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			Transform val = (((Object)(object)sor.elevatorTransform != (Object)null) ? sor.elevatorTransform : sor.propsContainer);
			int num = 0;
			Random random = new Random((item.itemName?.GetHashCode() ?? 0) ^ (count * 397) ^ ((int)(center.x * 100f) * 31) ^ (int)(center.z * 100f));
			for (int i = 0; i < count; i++)
			{
				try
				{
					float num2 = (float)(random.NextDouble() * 0.55 - 0.275);
					float num3 = (float)(random.NextDouble() * 0.55 - 0.275);
					float num4 = 0.05f + (float)(random.NextDouble() * 0.08);
					Vector3 val2 = center + new Vector3(num2, num4, num3);
					if ((Object)(object)sor.shipBounds != (Object)null)
					{
						Bounds bounds = sor.shipBounds.bounds;
						if (!((Bounds)(ref bounds)).Contains(val2))
						{
							val2 = center;
							val2.y = center.y + 0.1f;
							Plugin.V($"Pile item '{item.itemName}' #{i} outside shipBounds; snapped to pile center.");
						}
					}
					GameObject val3 = Object.Instantiate<GameObject>(item.spawnPrefab, val2, Quaternion.identity, val);
					GrabbableObject component = val3.GetComponent<GrabbableObject>();
					if ((Object)(object)component == (Object)null)
					{
						Plugin.Log.LogWarning((object)("Prefab for '" + item.itemName + "' has no GrabbableObject — destroying."));
						Object.Destroy((Object)(object)val3);
						continue;
					}
					component.fallTime = 0f;
					component.scrapPersistedThroughRounds = true;
					component.isInElevator = true;
					component.isInShipRoom = true;
					if ((Object)(object)component.radarIcon != (Object)null)
					{
						Object.Destroy((Object)(object)((Component)component.radarIcon).gameObject);
					}
					NetworkObject networkObject = ((NetworkBehaviour)component).NetworkObject;
					if ((Object)(object)networkObject == (Object)null)
					{
						Plugin.Log.LogWarning((object)("Prefab for '" + item.itemName + "' has no NetworkObject — destroying."));
						Object.Destroy((Object)(object)val3);
					}
					else
					{
						networkObject.Spawn(false);
						num++;
					}
				}
				catch (Exception arg)
				{
					Plugin.Log.LogError((object)$"Failed spawning '{item.itemName}' #{i}: {arg}");
				}
			}
			return num;
		}
	}
}