Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ShipCargo v2.3.5
plugins/ShipCargo.dll
Decompiled 10 months agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; 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("ShipCargo")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("ShipCargo")] [assembly: AssemblyTitle("ShipCargo")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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 ShipCargo { internal static class PluginInfo { public const string GUID = "Soul.ShipCargo"; public const string NAME = "ShipCargo"; public const string AUTHOR = "By Soul"; public const string VERSION = "2.3.5"; } [BepInPlugin("Soul.ShipCargo", "ShipCargo", "2.3.5")] public class ShipCargoPlugin : BaseUnityPlugin { internal static ShipCargoPlugin Instance; internal static ManualLogSource Log; private Harmony _harmony; public static ConfigEntry<bool> AllowClientsToStore; public static ConfigEntry<bool> AllowClientsToRetrieve; public static ConfigEntry<bool> AllowClientsToList; public static ConfigEntry<bool> AllowClientsToSort; public static ConfigEntry<float> ItemSpawnSpread; private void Awake() { //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; Info("[ShipCargo] Initializing..."); AllowClientsToStore = ((BaseUnityPlugin)this).Config.Bind<bool>("Permissions", "AllowClientsToStore", true, "If false, only host can store items."); AllowClientsToRetrieve = ((BaseUnityPlugin)this).Config.Bind<bool>("Permissions", "AllowClientsToRetrieve", true, "If false, only host can retrieve items."); AllowClientsToList = ((BaseUnityPlugin)this).Config.Bind<bool>("Permissions", "AllowClientsToList", true, "If false, clients cannot use cargo list or scan."); AllowClientsToSort = ((BaseUnityPlugin)this).Config.Bind<bool>("Permissions", "AllowClientsToSort", true, "If false, clients cannot change sorting mode."); ItemSpawnSpread = ((BaseUnityPlugin)this).Config.Bind<float>("Tweaks", "ItemSpawnSpread", 0.45f, "Radius for item spawn randomization in the ship."); ShipCargoManager.Init(); _harmony = new Harmony("Soul.ShipCargo"); _harmony.PatchAll(); try { Type type = Type.GetType("InteractiveTerminalAPI.TerminalAPI, InteractiveTerminalAPI"); if (type != null) { MethodInfo method = type.GetMethod("RegisterWord", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(string) }, null); if (method != null) { method.Invoke(null, new object[1] { "cargo" }); Info("[ShipCargo] Registered keyword 'cargo' with ITA."); } else { Warn("[ShipCargo] ITA found, but RegisterWord() was not detected."); } } } catch (Exception arg) { Error($"ITA integration failed: {arg}"); } Info("[ShipCargo] Loaded successfully."); } public static string Red(string s) { return "<color=red>" + s + "</color>"; } public static string Green(string s) { return "<color=green>" + s + "</color>"; } public static string Gold(string s) { return "<color=#FFD700>" + s + "</color>"; } public static string Yellow(string s) { return "<color=yellow>" + s + "</color>"; } public static string Blue(string s) { return "<color=#5dcaff>" + s + "</color>"; } public static string Cyan(string s) { return "<color=#00FFFF>" + s + "</color>"; } public static string Money(int v) { return $"<color=white>${v}</color>"; } public static string Index(int i) { return $"<color=green>{i}.</color>"; } public static string StripPunctuation(string s) { if (string.IsNullOrEmpty(s)) { return s; } StringBuilder stringBuilder = new StringBuilder(s.Length); foreach (char c in s) { if (!char.IsPunctuation(c)) { stringBuilder.Append(c); } } return stringBuilder.ToString(); } public static void Info(string msg) { Log.LogInfo((object)msg); } public static void Warn(string msg) { Log.LogWarning((object)msg); } public static void Error(string msg) { Log.LogError((object)msg); } public static bool IsHost() { if ((Object)(object)NetworkManager.Singleton != (Object)null && NetworkManager.Singleton.IsServer) { return NetworkManager.Singleton.IsListening; } return false; } public static bool HostAllows(ConfigEntry<bool> entry) { return entry.Value; } } [Serializable] public class StoredScrapItem { public string ItemName; public int ScrapValue; } public static class ShipCargoManager { private enum SortMode { Name, Value } private class ShipGroup { public string Name; public int Count; public int Value; } private class CargoGroup { public string Name; public int Count; public int Value; } internal static readonly object _lock = new object(); private static readonly List<StoredScrapItem> _storedSaved = new List<StoredScrapItem>(); internal static readonly List<StoredScrapItem> _storedCurrent = new List<StoredScrapItem>(); private static SortMode _sortMode = SortMode.Name; private static bool _menuInjected = false; private static readonly HashSet<string> ExcludedNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "ClipboardManual", "StickyNoteItem" }; private static readonly HashSet<string> ToolNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Shovel", "Flashlight", "Pro-flashlight", "ProFlashlight", "Walkie-talkie", "WalkieTalkie", "Zap gun", "ZapGun", "Jetpack", "Extension ladder", "ExtensionLadder", "Stun grenade", "StunGrenade", "Radar-booster", "RadarBooster", "Spray paint", "SprayPaint", "Boombox", "TZP-Inhalant", "TZPInhalant", "Lockpicker" }; public static DateTime _lastKnownSaveTime = DateTime.MinValue; private static List<StoredScrapItem> CombinedStored { get { lock (_lock) { List<StoredScrapItem> source = _storedSaved.Concat(_storedCurrent).ToList(); if (_sortMode == SortMode.Name) { return source.OrderBy((StoredScrapItem x) => x.ItemName).ToList(); } return source.OrderBy((StoredScrapItem x) => x.ScrapValue).ToList(); } } } private static string FilePath { get { string text = GameNetworkManager.Instance?.currentSaveFileName; if (string.IsNullOrWhiteSpace(text)) { return Path.Combine(Paths.ConfigPath, "ShipCargo", "NO_SAVE_LOADED.json"); } string text2 = Path.Combine(Paths.ConfigPath, "ShipCargo"); if (!Directory.Exists(text2)) { Directory.CreateDirectory(text2); } return Path.Combine(text2, text + ".inventory.json"); } } public static void Init() { } public static void TryPatchMainMenu() { if (_menuInjected) { return; } try { TerminalNode[] array = Resources.FindObjectsOfTypeAll<TerminalNode>(); foreach (TerminalNode val in array) { string displayText = val.displayText; if (!string.IsNullOrEmpty(displayText) && displayText.Contains(">MOONS") && displayText.Contains(">STORE") && displayText.Contains(">OTHER")) { if (displayText.Contains(">CARGO")) { _menuInjected = true; break; } int num = displayText.IndexOf(">OTHER", StringComparison.Ordinal); if (num >= 0) { string value = ">CARGO\nCargo management commands\n\n"; val.displayText = displayText.Insert(num, value); ShipCargoPlugin.Info("[ShipCargo] CARGO added to main terminal menu."); _menuInjected = true; break; } } } } catch (Exception arg) { ShipCargoPlugin.Error($"Failed to patch main menu: {arg}"); } } public static List<GrabbableObject> GetShipScrap() { List<GrabbableObject> list = new List<GrabbableObject>(); try { GameObject val = GameObject.Find("/Environment/HangarShip"); if ((Object)(object)val == (Object)null) { return list; } GrabbableObject[] componentsInChildren = val.GetComponentsInChildren<GrabbableObject>(true); foreach (GrabbableObject val2 in componentsInChildren) { if ((Object)(object)val2?.itemProperties == (Object)null) { continue; } string text = val2.itemProperties.itemName ?? ""; if (!ExcludedNames.Contains(text) && !ToolNames.Contains(text)) { bool flag = text.Equals("Key", StringComparison.OrdinalIgnoreCase) || text.Equals("Apparatus", StringComparison.OrdinalIgnoreCase); if ((val2.itemProperties.isScrap || flag) && !((Object)(object)val2.playerHeldBy != (Object)null) && !val2.isHeld && !val2.isPocketed) { list.Add(val2); } } } } catch (Exception arg) { ShipCargoPlugin.Error($"Scan error: {arg}"); } return list; } private static List<ShipGroup> GetShipGroups() { List<ShipGroup> list = (from x in GetShipScrap() group x by x.itemProperties.itemName into g select new ShipGroup { Name = g.Key, Count = g.Count(), Value = g.Sum((GrabbableObject o) => o.scrapValue) }).ToList(); SortShip(list); return list; } private static List<CargoGroup> GetCargoGroups() { List<StoredScrapItem> combinedStored; lock (_lock) { combinedStored = CombinedStored; } List<CargoGroup> list = (from x in combinedStored group x by x.ItemName into g select new CargoGroup { Name = g.Key, Count = g.Count(), Value = g.Sum((StoredScrapItem o) => o.ScrapValue) }).ToList(); SortCargo(list); return list; } private static void SortShip(List<ShipGroup> g) { if (_sortMode == SortMode.Name) { g.Sort((ShipGroup a, ShipGroup b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase)); return; } g.Sort(delegate(ShipGroup a, ShipGroup b) { int num = a.Value.CompareTo(b.Value); return (num != 0) ? num : string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase); }); } private static void SortCargo(List<CargoGroup> g) { if (_sortMode == SortMode.Name) { g.Sort((CargoGroup a, CargoGroup b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase)); return; } g.Sort(delegate(CargoGroup a, CargoGroup b) { int num = a.Value.CompareTo(b.Value); return (num != 0) ? num : string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase); }); } public static int ShipValue() { return GetShipScrap().Sum((GrabbableObject g) => g.scrapValue); } public static int CargoValue() { lock (_lock) { return CombinedStored.Sum((StoredScrapItem x) => x.ScrapValue); } } public static string CmdScan(ulong sender) { if (!ShipCargoPlugin.IsHost() && !ShipCargoPlugin.HostAllows(ShipCargoPlugin.AllowClientsToList)) { return ShipCargoPlugin.Red("Clients cannot scan ship (disabled by host).\n\n"); } List<ShipGroup> shipGroups = GetShipGroups(); if (shipGroups.Count == 0) { return ShipCargoPlugin.Green("No sellable items inside the ship.\n\n"); } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(ShipCargoPlugin.Cyan("=== SHIP SCAN ===\n")); stringBuilder.AppendLine(); int num = 1; foreach (ShipGroup item in shipGroups) { stringBuilder.AppendLine($"{ShipCargoPlugin.Index(num)} {item.Name} <color=green>(</color><color=blue>x{item.Count}</color><color=green>)</color> ({ShipCargoPlugin.Money(item.Value)})"); num++; } stringBuilder.AppendLine(); stringBuilder.AppendLine("\nTotal ship value: " + ShipCargoPlugin.Money(ShipValue())); return stringBuilder.ToString() + "\n"; } public static string CmdList(ulong sender) { if (!ShipCargoPlugin.IsHost() && !ShipCargoPlugin.HostAllows(ShipCargoPlugin.AllowClientsToList)) { return ShipCargoPlugin.Red("Clients cannot list cargo (disabled by host).\n\n"); } List<CargoGroup> cargoGroups = GetCargoGroups(); if (cargoGroups.Count == 0) { return ShipCargoPlugin.Green("Stored cargo is empty.\n\n"); } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(ShipCargoPlugin.Cyan("=== STORED CARGO ===\n")); stringBuilder.AppendLine(); int num = 1; foreach (CargoGroup item in cargoGroups) { stringBuilder.AppendLine($"{ShipCargoPlugin.Index(num)} {item.Name} <color=green>(</color><color=blue>x{item.Count}</color><color=green>)</color> ({ShipCargoPlugin.Money(item.Value)})"); num++; } stringBuilder.AppendLine(); stringBuilder.AppendLine("\nTotal stored value: " + ShipCargoPlugin.Money(CargoValue())); return stringBuilder.ToString() + "\n"; } public static string CmdSortName(ulong sender) { if (!ShipCargoPlugin.IsHost() && !ShipCargoPlugin.HostAllows(ShipCargoPlugin.AllowClientsToSort)) { return ShipCargoPlugin.Red("Clients cannot change sorting (disabled by host).\n\n"); } _sortMode = SortMode.Name; return ShipCargoPlugin.Green("Sorting mode set to alphabetical.\n\n"); } public static string CmdSortValue(ulong sender) { if (!ShipCargoPlugin.IsHost() && !ShipCargoPlugin.HostAllows(ShipCargoPlugin.AllowClientsToSort)) { return ShipCargoPlugin.Red("Clients cannot change sorting (disabled by host).\n\n"); } _sortMode = SortMode.Value; return ShipCargoPlugin.Green("Sorting mode set to value (low → high).\n\n"); } private static void StoreItems(IEnumerable<GrabbableObject> items, out int storedCount, out int storedValue) { storedCount = 0; storedValue = 0; lock (_lock) { foreach (GrabbableObject item in items) { if ((Object)(object)item == (Object)null || (Object)(object)item.itemProperties == (Object)null) { continue; } _storedCurrent.Add(new StoredScrapItem { ItemName = item.itemProperties.itemName, ScrapValue = item.scrapValue }); storedCount++; storedValue += item.scrapValue; try { NetworkObject component = ((Component)item).GetComponent<NetworkObject>(); if ((Object)(object)component != (Object)null && component.IsSpawned) { component.Despawn(true); } else { Object.Destroy((Object)(object)((Component)item).gameObject); } } catch { } } } } public static string CmdStoreAll(ulong sender) { if (!ShipCargoPlugin.HostAllows(ShipCargoPlugin.AllowClientsToStore) && sender != 0L) { return ShipCargoPlugin.Red("Clients storing is disabled by host.\n\n"); } List<GrabbableObject> shipScrap = GetShipScrap(); if (shipScrap.Count == 0) { return ShipCargoPlugin.Green("No sellable items found in ship.\n\n"); } StoreItems(shipScrap, out var storedCount, out var storedValue); ShipCargoRPC.SendCargoSync(); return string.Format("{0} {1} items ({2}).\n\n", ShipCargoPlugin.Green("Stored"), storedCount, ShipCargoPlugin.Money(storedValue)); } public static string CmdStoreAllByIndex(int index, ulong sender) { if (!ShipCargoPlugin.HostAllows(ShipCargoPlugin.AllowClientsToStore) && sender != 0L) { return ShipCargoPlugin.Red("Clients storing is disabled by host.\n\n"); } List<ShipGroup> shipGroups = GetShipGroups(); if (shipGroups.Count == 0) { return ShipCargoPlugin.Green("No sellable items found in ship.\n\n"); } if (index < 1 || index > shipGroups.Count) { return ShipCargoPlugin.Red($"No ship item at index {index}.\n\n"); } return CmdStoreAllByName(shipGroups[index - 1].Name, sender); } public static string CmdStoreAllByName(string name, ulong sender) { if (!ShipCargoPlugin.HostAllows(ShipCargoPlugin.AllowClientsToStore) && sender != 0L) { return ShipCargoPlugin.Red("Clients storing is disabled by host.\n\n"); } if (string.IsNullOrWhiteSpace(name)) { return ShipCargoPlugin.Red("You must provide a name.\n\n"); } string target = name.Trim(); List<GrabbableObject> list = (from s in GetShipScrap() where string.Equals(s.itemProperties.itemName, target, StringComparison.OrdinalIgnoreCase) select s).ToList(); if (list.Count == 0) { return ShipCargoPlugin.Green("No ship items named \"" + target + "\".\n\n"); } StoreItems(list, out var storedCount, out var storedValue); ShipCargoRPC.SendCargoSync(); return string.Format("{0} {1} {2} ({3}).\n\n", ShipCargoPlugin.Green("Stored"), storedCount, target, ShipCargoPlugin.Money(storedValue)); } public static string CmdStoreOneByIndex(int index, ulong sender) { if (!ShipCargoPlugin.HostAllows(ShipCargoPlugin.AllowClientsToStore) && sender != 0L) { return ShipCargoPlugin.Red("Clients storing is disabled by host.\n\n"); } List<ShipGroup> shipGroups = GetShipGroups(); if (shipGroups.Count == 0) { return ShipCargoPlugin.Green("No sellable items in ship.\n\n"); } if (index < 1 || index > shipGroups.Count) { return ShipCargoPlugin.Red($"No ship item at index {index}.\n\n"); } return CmdStoreOneByName(shipGroups[index - 1].Name, sender); } public static string CmdStoreOneByName(string name, ulong sender) { if (!ShipCargoPlugin.HostAllows(ShipCargoPlugin.AllowClientsToStore) && sender != 0L) { return ShipCargoPlugin.Red("Clients storing is disabled by host.\n\n"); } if (string.IsNullOrWhiteSpace(name)) { return ShipCargoPlugin.Red("You must provide a name.\n\n"); } string target = name.Trim(); List<GrabbableObject> list = (from s in GetShipScrap() where string.Equals(s.itemProperties.itemName, target, StringComparison.OrdinalIgnoreCase) select s).ToList(); if (list.Count == 0) { return ShipCargoPlugin.Green("No ship items named \"" + target + "\".\n\n"); } StoreItems(list.Take(1), out var _, out var storedValue); ShipCargoRPC.SendCargoSync(); return ShipCargoPlugin.Green("Stored") + " 1 " + target + " (" + ShipCargoPlugin.Money(storedValue) + ").\n\n"; } private static bool TryGetShipInterior(out Bounds bounds) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //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) bounds = default(Bounds); GameObject val = GameObject.Find("/Environment/HangarShip"); if (!Object.op_Implicit((Object)(object)val)) { return false; } BoxCollider val2 = ((IEnumerable<BoxCollider>)val.GetComponentsInChildren<BoxCollider>(true)).FirstOrDefault((Func<BoxCollider, bool>)((BoxCollider c) => ((Object)c).name.Contains("StrictInnerRoomBounds") || ((Object)c).name.Contains("InnerRoomBounds"))); if ((Object)(object)val2 != (Object)null) { bounds = ((Collider)val2).bounds; return true; } return false; } private static void SpawnStoredItems(List<StoredScrapItem> list, out int spawned, out int value) { //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: 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_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) spawned = 0; value = 0; GameObject val = GameObject.Find("/Environment/HangarShip"); if ((Object)(object)val == (Object)null) { return; } Bounds bounds; bool flag = TryGetShipInterior(out bounds); Vector3 val3 = default(Vector3); foreach (StoredScrapItem it in list) { try { Item val2 = ((IEnumerable<Item>)StartOfRound.Instance.allItemsList.itemsList).FirstOrDefault((Func<Item, bool>)((Item d) => string.Equals(d.itemName, it.ItemName, StringComparison.OrdinalIgnoreCase))); if (!((Object)(object)val2 == (Object)null)) { if (flag) { ((Vector3)(ref val3))..ctor(((Bounds)(ref bounds)).center.x + Random.Range(0f - ShipCargoPlugin.ItemSpawnSpread.Value, ShipCargoPlugin.ItemSpawnSpread.Value), ((Bounds)(ref bounds)).min.y + 1.1f, ((Bounds)(ref bounds)).center.z + Random.Range(0f - ShipCargoPlugin.ItemSpawnSpread.Value, ShipCargoPlugin.ItemSpawnSpread.Value)); } else { val3 = val.transform.position + Vector3.up; } GameObject obj = Object.Instantiate<GameObject>(val2.spawnPrefab, val3, Quaternion.identity); obj.transform.SetParent(val.transform, true); NetworkObject component = obj.GetComponent<NetworkObject>(); if ((Object)(object)component != (Object)null && !component.IsSpawned) { component.Spawn(true); } GrabbableObject component2 = obj.GetComponent<GrabbableObject>(); if ((Object)(object)component2 != (Object)null) { component2.SetScrapValue(it.ScrapValue); } spawned++; value += it.ScrapValue; } } catch { } } } public static string CmdRetrieveAll(ulong sender) { if (!ShipCargoPlugin.HostAllows(ShipCargoPlugin.AllowClientsToRetrieve) && sender != 0L) { return ShipCargoPlugin.Red("Clients retrieving is disabled by host.\n\n"); } if (ShipBusy()) { return ShipCargoPlugin.Red("Cannot retrieve items while the ship is moving.\n\n"); } List<StoredScrapItem> list; lock (_lock) { list = CombinedStored.ToList(); } if (list.Count == 0) { return ShipCargoPlugin.Green("Stored cargo is empty.\n\n"); } lock (_lock) { _storedSaved.Clear(); _storedCurrent.Clear(); SaveSavedToDisk(); } SpawnStoredItems(list, out var spawned, out var value); ShipCargoRPC.SendCargoSync(); return string.Format("{0} {1} items ({2}).\n\n", ShipCargoPlugin.Green("Retrieved"), spawned, ShipCargoPlugin.Money(value)); } public static string CmdRetrieveAllByIndex(int index, ulong sender) { if (!ShipCargoPlugin.HostAllows(ShipCargoPlugin.AllowClientsToRetrieve) && sender != 0L) { return ShipCargoPlugin.Red("Clients retrieving is disabled by host.\n\n"); } if (ShipBusy()) { return ShipCargoPlugin.Red("Cannot retrieve items while the ship is moving.\n\n"); } List<CargoGroup> cargoGroups = GetCargoGroups(); if (cargoGroups.Count == 0) { return ShipCargoPlugin.Green("Stored cargo is empty.\n\n"); } if (index < 1 || index > cargoGroups.Count) { return ShipCargoPlugin.Red($"No stored item at index {index}.\n\n"); } return CmdRetrieveAllByName(cargoGroups[index - 1].Name, sender); } public static string CmdRetrieveAllByName(string name, ulong sender) { if (!ShipCargoPlugin.HostAllows(ShipCargoPlugin.AllowClientsToRetrieve) && sender != 0L) { return ShipCargoPlugin.Red("Clients retrieving is disabled by host.\n\n"); } if (ShipBusy()) { return ShipCargoPlugin.Red("Cannot retrieve items while the ship is moving.\n\n"); } if (string.IsNullOrWhiteSpace(name)) { return ShipCargoPlugin.Red("You must provide a name.\n\n"); } string target = name.Trim(); List<StoredScrapItem> list; lock (_lock) { list = CombinedStored.Where((StoredScrapItem s) => s.ItemName.Equals(target, StringComparison.OrdinalIgnoreCase)).ToList(); if (list.Count == 0) { return ShipCargoPlugin.Green("No stored items named \"" + target + "\".\n\n"); } foreach (StoredScrapItem item in list) { if (!_storedCurrent.Remove(item)) { _storedSaved.Remove(item); } } SaveSavedToDisk(); } SpawnStoredItems(list, out var spawned, out var value); ShipCargoRPC.SendCargoSync(); return string.Format("{0} {1} {2} ({3}).\n\n", ShipCargoPlugin.Green("Retrieved"), spawned, target, ShipCargoPlugin.Money(value)); } public static string CmdRetrieveOneByIndex(int index, ulong sender) { if (!ShipCargoPlugin.HostAllows(ShipCargoPlugin.AllowClientsToRetrieve) && sender != 0L) { return ShipCargoPlugin.Red("Clients retrieving is disabled by host.\n\n"); } if (ShipBusy()) { return ShipCargoPlugin.Red("Cannot retrieve items while the ship is moving.\n\n"); } List<CargoGroup> cargoGroups = GetCargoGroups(); if (cargoGroups.Count == 0) { return ShipCargoPlugin.Green("Stored cargo is empty.\n\n"); } if (index < 1 || index > cargoGroups.Count) { return ShipCargoPlugin.Red($"No stored item at index {index}.\n\n"); } return CmdRetrieveOneByName(cargoGroups[index - 1].Name, sender); } public static string CmdRetrieveOneByName(string name, ulong sender) { if (!ShipCargoPlugin.HostAllows(ShipCargoPlugin.AllowClientsToRetrieve) && sender != 0L) { return ShipCargoPlugin.Red("Clients retrieving is disabled by host.\n\n"); } if (ShipBusy()) { return ShipCargoPlugin.Red("Cannot retrieve items while the ship is moving.\n\n"); } if (string.IsNullOrWhiteSpace(name)) { return ShipCargoPlugin.Red("You must provide a name.\n\n"); } string target = name.Trim(); StoredScrapItem storedScrapItem; lock (_lock) { storedScrapItem = CombinedStored.FirstOrDefault((StoredScrapItem s) => s.ItemName.Equals(target, StringComparison.OrdinalIgnoreCase)); if (storedScrapItem == null) { return ShipCargoPlugin.Green("No stored items named \"" + target + "\".\n\n"); } if (!_storedCurrent.Remove(storedScrapItem)) { _storedSaved.Remove(storedScrapItem); } SaveSavedToDisk(); } SpawnStoredItems(new List<StoredScrapItem> { storedScrapItem }, out var _, out var value); ShipCargoRPC.SendCargoSync(); return ShipCargoPlugin.Green("Retrieved") + " 1 " + target + " (" + ShipCargoPlugin.Money(value) + ").\n\n"; } public static string CmdCargoMenu() { int v = ShipValue(); int v2 = CargoValue(); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("<align=\"center\">" + ShipCargoPlugin.Gold("== ShipCargo ==") + "</align>\n"); stringBuilder.AppendLine("<align=\"center\">" + ShipCargoPlugin.Green("By ") + "<color=#000000>Soul</color></align>"); stringBuilder.AppendLine("<align=\"center\">" + ShipCargoPlugin.Green("Version ") + ShipCargoPlugin.Red("2.3.5") + "</align>"); stringBuilder.AppendLine(); stringBuilder.AppendLine("<color=green>Ship Value:</color> " + ShipCargoPlugin.Money(v)); stringBuilder.AppendLine("<color=green>Cargo Value:</color> " + ShipCargoPlugin.Money(v2)); stringBuilder.AppendLine(ShipCargoPlugin.Cyan("===== LIST COMMANDS =====")); stringBuilder.AppendLine(ShipCargoPlugin.Yellow("cargo scan") + " - Scan ship for storable items"); stringBuilder.AppendLine(ShipCargoPlugin.Yellow("cargo list") + " - List stored cargo"); stringBuilder.AppendLine(ShipCargoPlugin.Cyan("===== STORE COMMANDS =====")); stringBuilder.AppendLine(ShipCargoPlugin.Yellow("cargo s all") + " - Store ALL items"); stringBuilder.AppendLine(ShipCargoPlugin.Yellow("cargo s <index>") + " - Store ONE by index"); stringBuilder.AppendLine(ShipCargoPlugin.Yellow("cargo s <name>") + " - Store ONE by name"); stringBuilder.AppendLine(ShipCargoPlugin.Yellow("cargo s all <name/index>") + " - Store ALL by name or index"); stringBuilder.AppendLine(ShipCargoPlugin.Cyan("===== RETRIEVE COMMANDS =====")); stringBuilder.AppendLine(ShipCargoPlugin.Yellow("cargo r all") + " - Retrieve ALL items"); stringBuilder.AppendLine(ShipCargoPlugin.Yellow("cargo r <index>") + " - Retrieve ONE by index"); stringBuilder.AppendLine(ShipCargoPlugin.Yellow("cargo r <name>") + " - Retrieve ONE by name"); stringBuilder.AppendLine(ShipCargoPlugin.Yellow("cargo r all <name/index>") + " - Retrieve ALL by name or index"); stringBuilder.AppendLine(ShipCargoPlugin.Cyan("===== SORT COMMANDS =====")); stringBuilder.AppendLine(ShipCargoPlugin.Yellow("cargo sort name") + " - Sort alphabetically"); stringBuilder.AppendLine(ShipCargoPlugin.Yellow("cargo sort value") + " - Sort by total value"); return stringBuilder.ToString(); } public static string HandleCargoCommand(string input, ulong sender) { if (string.IsNullOrWhiteSpace(input)) { return ShipCargoPlugin.Red("Invalid command.\n"); } string[] array = input.Split(' ', StringSplitOptions.RemoveEmptyEntries); if (array.Length == 0) { return ShipCargoPlugin.Red("Invalid command.\n"); } if (!array[0].Equals("cargo", StringComparison.OrdinalIgnoreCase)) { return null; } if (array.Length == 1) { return CmdCargoMenu(); } switch (array[1].ToLowerInvariant()) { case "scan": return CmdScan(sender); case "list": return CmdList(sender); case "sort": if (array.Length < 3) { return ShipCargoPlugin.Yellow("Usage: cargo sort name | value\n"); } if (array[2].Equals("name", StringComparison.OrdinalIgnoreCase)) { return CmdSortName(sender); } if (array[2].Equals("value", StringComparison.OrdinalIgnoreCase)) { return CmdSortValue(sender); } return ShipCargoPlugin.Yellow("Usage: cargo sort name | value\n"); case "s": return HandleStoreCommand(array, sender); case "r": return HandleRetrieveCommand(array, sender); default: return ShipCargoPlugin.Red("Unknown cargo command.\n\n"); } } private static string HandleStoreCommand(string[] t, ulong sender) { if (t.Length == 2) { return ShipCargoPlugin.Red("Invalid store command.\n\n"); } bool flag = t[2].Equals("all", StringComparison.OrdinalIgnoreCase); if (flag && t.Length == 3) { return CmdStoreAll(sender); } if (flag && t.Length >= 4) { string text = string.Join(" ", t.Skip(3)); if (int.TryParse(text, out var result)) { return CmdStoreAllByIndex(result, sender); } return CmdStoreAllByName(text, sender); } if (int.TryParse(t[2], out var result2)) { return CmdStoreOneByIndex(result2, sender); } return CmdStoreOneByName(string.Join(" ", t.Skip(2)), sender); } private static string HandleRetrieveCommand(string[] t, ulong sender) { if (t.Length == 2) { return ShipCargoPlugin.Red("Invalid retrieve command."); } bool flag = t[2].Equals("all", StringComparison.OrdinalIgnoreCase); if (flag && t.Length == 3) { return CmdRetrieveAll(sender); } if (flag && t.Length >= 4) { string text = string.Join(" ", t.Skip(3)); if (int.TryParse(text, out var result)) { return CmdRetrieveAllByIndex(result, sender); } return CmdRetrieveAllByName(text, sender); } if (int.TryParse(t[2], out var result2)) { return CmdRetrieveOneByIndex(result2, sender); } return CmdRetrieveOneByName(string.Join(" ", t.Skip(2)), sender); } public static List<StoredScrapItem> GetStoredSnapshot() { lock (_lock) { return CombinedStored; } } public static void ClientApplyCargoState(List<StoredScrapItem> newList) { lock (_lock) { _storedSaved.Clear(); _storedSaved.AddRange(newList); _storedCurrent.Clear(); } } public static string HandleCargoCommandStatic(string cmd, ulong sender) { return HandleCargoCommand(cmd, sender); } private static bool IsInOrbit(StartOfRound sor) { if ((Object)(object)sor.currentLevel != (Object)null) { return sor.currentLevel.sceneName == "CompanyBuilding"; } return false; } private static bool ShipBusy() { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { return true; } bool flag = !instance.shipHasLanded && instance.inShipPhase && !instance.shipIsLeaving && !instance.travellingToNewLevel && !instance.shipLeftAutomatically; if (flag) { return false; } if (instance.shipIsLeaving) { return true; } if (instance.travellingToNewLevel) { return true; } if (instance.shipLeftAutomatically) { return true; } if (instance.firingPlayersCutsceneRunning || instance.suckingPlayersOutOfShip) { return true; } if ((Object)(object)instance.currentLevel != (Object)null && !flag) { return !instance.shipHasLanded; } return false; } public static void ReloadAfterSaveChange() { lock (_lock) { _storedSaved.Clear(); _storedCurrent.Clear(); } LoadFromDiskIntoSaved(); } public static void ClearMemory() { lock (_lock) { _storedSaved.Clear(); _storedCurrent.Clear(); } } internal static void LoadFromDiskIntoSaved() { try { if (!File.Exists(FilePath)) { return; } List<StoredScrapItem> list = JsonConvert.DeserializeObject<List<StoredScrapItem>>(File.ReadAllText(FilePath)); lock (_lock) { _storedSaved.Clear(); if (list != null) { _storedSaved.AddRange(list); } _storedCurrent.Clear(); } } catch { } } private static void SaveSavedToDisk() { lock (_lock) { File.WriteAllText(FilePath, JsonConvert.SerializeObject((object)_storedSaved, (Formatting)1)); } } public static void PromoteCurrentToSaved() { lock (_lock) { _storedSaved.AddRange(_storedCurrent); _storedCurrent.Clear(); SaveSavedToDisk(); } } private static string GetActiveSavePath() { string text = GameNetworkManager.Instance?.currentSaveFileName; if (string.IsNullOrWhiteSpace(text)) { return null; } return Path.Combine(Application.persistentDataPath, text); } public static DateTime GetActiveSaveTimestamp() { string activeSavePath = GetActiveSavePath(); if (string.IsNullOrEmpty(activeSavePath) || !File.Exists(activeSavePath)) { return DateTime.MinValue; } return File.GetLastWriteTime(activeSavePath); } } public class ShipCargoRPC : NetworkBehaviour { public static ShipCargoRPC Instance; public static void Init() { } [ServerRpc(RequireOwnership = false)] public void ForwardCargoCommandServerRpc(string command, ulong sender) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) if (ShipCargoPlugin.IsHost()) { string text = ShipCargoManager.HandleCargoCommand(command, sender); if (text != null) { SendReplyClientRpc(text, new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = new ulong[1] { sender } } }); } } } [ClientRpc] public void SendReplyClientRpc(string text, ClientRpcParams p = default(ClientRpcParams)) { Terminal val = Object.FindObjectOfType<Terminal>(); if (!((Object)(object)val == (Object)null)) { TerminalNode val2 = ScriptableObject.CreateInstance<TerminalNode>(); val2.displayText = text; val2.clearPreviousText = true; val.currentNode = val2; val.LoadNewNode(val2); } } [ClientRpc] public void SyncCargoClientRpc(byte[] jsonData) { try { List<StoredScrapItem> list = JsonConvert.DeserializeObject<List<StoredScrapItem>>(Encoding.UTF8.GetString(jsonData)); if (list != null) { ShipCargoManager.ClientApplyCargoState(list); } } catch (Exception arg) { ShipCargoPlugin.Error($"Cargo sync failed: {arg}"); } } public static void SendCargoSync() { if (ShipCargoPlugin.IsHost()) { string s = JsonConvert.SerializeObject((object)ShipCargoManager.GetStoredSnapshot(), (Formatting)1); byte[] bytes = Encoding.UTF8.GetBytes(s); Instance?.SyncCargoClientRpc(bytes); } } } [HarmonyPatch(typeof(Terminal), "Awake")] public static class TerminalCargoMenuInject { private static void Postfix(Terminal __instance) { try { ShipCargoManager.TryPatchMainMenu(); } catch (Exception arg) { ShipCargoPlugin.Error($"Menu inject error: {arg}"); } } } [HarmonyPatch(typeof(Terminal), "ParsePlayerSentence")] public static class TerminalParsePatch_Final { private static bool Prefix(Terminal __instance, ref TerminalNode __result) { int length = __instance.screenText.text.Length; int textAdded = __instance.textAdded; if (textAdded <= 0 || textAdded > length) { return true; } string s = __instance.screenText.text.Substring(length - textAdded); s = ShipCargoPlugin.StripPunctuation(s); s = s.Trim().TrimStart('>').Trim(); if (!s.StartsWith("cargo", StringComparison.OrdinalIgnoreCase)) { return true; } ShipCargoPlugin.Info("[ShipCargo] Intercepted cargo command: '" + s + "'"); PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; if ((Object)(object)val == (Object)null) { return true; } if (!ShipCargoPlugin.IsHost()) { ShipCargoRPC.Instance.ForwardCargoCommandServerRpc(s, val.playerClientId); TerminalNode val2 = ScriptableObject.CreateInstance<TerminalNode>(); val2.displayText = ShipCargoPlugin.Yellow("Request sent to host...\n"); val2.clearPreviousText = true; __instance.currentNode = val2; __instance.LoadNewNode(val2); __result = val2; return false; } string text = ShipCargoManager.HandleCargoCommand(s, val.playerClientId); if (!string.IsNullOrEmpty(text)) { TerminalNode val3 = ScriptableObject.CreateInstance<TerminalNode>(); val3.displayText = text; val3.clearPreviousText = true; __instance.currentNode = val3; __instance.LoadNewNode(val3); __result = val3; return false; } return true; } } [HarmonyPatch(typeof(GameNetworkManager), "Start")] public static class Patch_GNM_Start_SpawnRPC { private static void Postfix(GameNetworkManager __instance) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown if (!((Object)(object)ShipCargoRPC.Instance != (Object)null)) { GameObject val = new GameObject("ShipCargoRPC_Prefab"); val.AddComponent<NetworkObject>(); val.AddComponent<ShipCargoRPC>(); ((Component)__instance).GetComponent<NetworkManager>().PrefabHandler.AddNetworkPrefab(val); if (NetworkManager.Singleton.IsServer) { GameObject obj = Object.Instantiate<GameObject>(val); obj.GetComponent<NetworkObject>().Spawn(true); ShipCargoRPC.Instance = obj.GetComponent<ShipCargoRPC>(); ShipCargoPlugin.Info("[ShipCargo] RPC network prefab spawned."); } } } } [HarmonyPatch(typeof(ShipCargoRPC), "OnNetworkSpawn")] public static class Patch_RPC_OnSpawn { private static void Postfix(ShipCargoRPC __instance) { ShipCargoRPC.Instance = __instance; ShipCargoPlugin.Info("[ShipCargo] RPC instance linked on client."); } } [HarmonyPatch(typeof(StartOfRound), "Start")] public static class Patch_SOR_Start { private static void Postfix() { string text = GameNetworkManager.Instance?.currentSaveFileName; if (string.IsNullOrWhiteSpace(text)) { ShipCargoPlugin.Info("[ShipCargo] SOR_Start: No active save → Clearing memory."); ShipCargoManager.ClearMemory(); } else { ShipCargoPlugin.Info("[ShipCargo] SOR_Start: Active save = " + text); } } } [HarmonyPatch(typeof(StartOfRound), "LoadUnlockables")] public static class Patch_SOR_AfterLoad { private static void Postfix() { if ((Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)StartOfRound.Instance.unlockablesList == (Object)null || StartOfRound.Instance.unlockablesList.unlockables == null) { ShipCargoPlugin.Warn("[ShipCargo] AfterLoad: UnlockablesList not ready yet. Skipping."); return; } if (string.IsNullOrWhiteSpace(GameNetworkManager.Instance?.currentSaveFileName)) { ShipCargoPlugin.Info("[ShipCargo] AfterLoad: No active save. Clearing memory."); ShipCargoManager.ClearMemory(); if (ShipCargoPlugin.IsHost()) { ShipCargoRPC.SendCargoSync(); } return; } DateTime activeSaveTimestamp = ShipCargoManager.GetActiveSaveTimestamp(); if (activeSaveTimestamp == DateTime.MinValue) { ShipCargoPlugin.Warn("[ShipCargo] AfterLoad: No active save timestamp. Clearing cargo."); ShipCargoManager.ClearMemory(); if (ShipCargoPlugin.IsHost()) { ShipCargoRPC.SendCargoSync(); } } else if (activeSaveTimestamp == ShipCargoManager._lastKnownSaveTime) { ShipCargoPlugin.Warn("[ShipCargo] AfterLoad: Save unchanged → wiping UNSAVED cargo."); lock (ShipCargoManager._lock) { ShipCargoManager._storedCurrent.Clear(); ShipCargoManager.LoadFromDiskIntoSaved(); } if (ShipCargoPlugin.IsHost()) { ShipCargoRPC.SendCargoSync(); } } else { ShipCargoPlugin.Info("[ShipCargo] AfterLoad: Save updated → loading cargo."); ShipCargoManager._lastKnownSaveTime = activeSaveTimestamp; ShipCargoManager.ReloadAfterSaveChange(); if (ShipCargoPlugin.IsHost()) { ShipCargoRPC.SendCargoSync(); } } } } [HarmonyPatch(typeof(StartOfRound), "ShipLeave")] public static class Patch_SaveOnTakeoff { private static void Prefix() { ShipCargoManager.PromoteCurrentToSaved(); } } }