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 RobseStorage v1.0.0
BepInEx/plugins/RobseStorage/RobseStorage.dll
Decompiled 4 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using LethalLib.Extras; using LethalLib.Modules; using LobbyCompatibility.Enums; using LobbyCompatibility.Features; using Microsoft.CodeAnalysis; 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: AssemblyCompany("RobseStorage")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+def874770235e257766bda7f39ff5a21e7461f5a")] [assembly: AssemblyProduct("RobseStorage")] [assembly: AssemblyTitle("RobseStorage")] [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 RobseStorage { public sealed class CupboardSlot { public int Number { get; } public string Colour { get; } public string UnlockableName => Colour + " Cupboard"; public string TerminalKeyword => UnlockableName.ToLowerInvariant().Replace(" ", "-"); public string NumberedDisplayName => "Cupboard " + Number; public CupboardSlot(int number, string colour) { if (number < 1) { throw new ArgumentOutOfRangeException("number", number, "Cupboard numbers start at 1."); } if (string.IsNullOrWhiteSpace(colour)) { throw new ArgumentException("Colour must not be empty.", "colour"); } Number = number; Colour = colour; } public string AssetName(bool doorless) { return "Cupboard" + Number + (doorless ? "Alt" : string.Empty) + "UnlockableItemDef"; } } public static class CupboardCatalog { public const string VanillaCupboardName = "Cupboard"; public const int MaxSlots = 5; public static IReadOnlyList<CupboardSlot> Slots { get; } = new CupboardSlot[5] { new CupboardSlot(1, "Orange"), new CupboardSlot(2, "Yellow"), new CupboardSlot(3, "Green"), new CupboardSlot(4, "Blue"), new CupboardSlot(5, "Purple") }; public static IReadOnlyList<CupboardSlot> Take(int count) { int count2 = Math.Clamp(count, 0, 5); return Slots.Take(count2).ToList(); } public static CupboardSlot? FindByNumber(int number) { return Slots.FirstOrDefault((CupboardSlot slot) => slot.Number == number); } public static CupboardSlot? FindByColour(string colour) { return Slots.FirstOrDefault((CupboardSlot slot) => string.Equals(slot.Colour, colour, StringComparison.OrdinalIgnoreCase)); } public static CupboardSlot? FindByUnlockableName(string unlockableName) { return Slots.FirstOrDefault((CupboardSlot slot) => string.Equals(slot.UnlockableName, unlockableName, StringComparison.Ordinal)); } } internal sealed class RegisteredCupboard { public CupboardSlot Slot { get; } public UnlockableItemDef Definition { get; } public GameObject Prefab { get; } public PlaceableShipObject PrefabPlaceable { get; } public UnlockableItem Unlockable => Definition.unlockable; public int ListIndex => ((!((Object)(object)StartOfRound.Instance != (Object)null)) ? null : StartOfRound.Instance.unlockablesList?.unlockables)?.IndexOf(Unlockable) ?? (-1); public RegisteredCupboard(CupboardSlot slot, UnlockableItemDef definition, GameObject prefab, PlaceableShipObject prefabPlaceable) { Slot = slot; Definition = definition; Prefab = prefab; PrefabPlaceable = prefabPlaceable; } } internal static class CupboardRegistry { private const string InfoNodeAssetName = "CupboardInfo"; private const int FallbackPrice = 300; private static readonly List<RegisteredCupboard> cupboards = new List<RegisteredCupboard>(); public static IReadOnlyList<RegisteredCupboard> Cupboards => cupboards; public static void RegisterCupboards(AssetBundle bundle, int configuredPrice, int configuredCount, bool doorless) { int num = ((configuredPrice >= 0) ? configuredPrice : 300); if (configuredPrice < 0) { RobseStoragePlugin.Logger.LogWarning((object)$"Configured cupboard price {configuredPrice} is negative, falling back to {300}."); } TerminalNode val = bundle.LoadAsset<TerminalNode>("CupboardInfo"); if ((Object)(object)val == (Object)null) { RobseStoragePlugin.Logger.LogWarning((object)"Asset 'CupboardInfo' is missing from the bundle; LethalLib will generate a placeholder info node."); } foreach (CupboardSlot item in CupboardCatalog.Take(configuredCount)) { string text = item.AssetName(doorless); UnlockableItemDef val2 = bundle.LoadAsset<UnlockableItemDef>(text); if ((Object)(object)val2 == (Object)null || val2.unlockable == null) { RobseStoragePlugin.Logger.LogError((object)$"Asset '{text}' is missing or has no UnlockableItem; cupboard {item.Number} is skipped."); continue; } GameObject prefabObject = val2.unlockable.prefabObject; if ((Object)(object)prefabObject == (Object)null) { RobseStoragePlugin.Logger.LogError((object)$"Asset '{text}' has no prefab; cupboard {item.Number} is skipped."); continue; } NetworkObject component = prefabObject.GetComponent<NetworkObject>(); PlaceableShipObject componentInChildren = prefabObject.GetComponentInChildren<PlaceableShipObject>(true); AutoParentToShip component2 = prefabObject.GetComponent<AutoParentToShip>(); if ((Object)(object)component == (Object)null || (Object)(object)componentInChildren == (Object)null || (Object)(object)component2 == (Object)null) { RobseStoragePlugin.Logger.LogError((object)$"Prefab '{((Object)prefabObject).name}' of asset '{text}' lacks NetworkObject, PlaceableShipObject or AutoParentToShip; cupboard {item.Number} is skipped."); continue; } ApplyUnlockableSettings(val2.unlockable, item); NetworkPrefabs.RegisterNetworkPrefab(prefabObject); Unlockables.RegisterUnlockable(val2, (StoreType)1, (TerminalNode)null, (TerminalNode)null, val, num); cupboards.Add(new RegisteredCupboard(item, val2, prefabObject, componentInChildren)); RobseStoragePlugin.Logger.LogInfo((object)$"Registered '{item.UnlockableName}' (asset '{text}', prefab '{((Object)prefabObject).name}', keyword '{item.TerminalKeyword}', price {num})."); } RobseStoragePlugin.Logger.LogInfo((object)$"{cupboards.Count} cupboard(s) registered with LethalLib."); } private static void ApplyUnlockableSettings(UnlockableItem unlockable, CupboardSlot slot) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) string unlockableName = unlockable.unlockableName; unlockable.unlockableName = slot.UnlockableName; unlockable.unlockableType = 1; unlockable.spawnPrefab = true; unlockable.IsPlaceable = true; unlockable.canBeStored = true; unlockable.alwaysInStock = true; unlockable.maxNumber = 1; unlockable.alreadyUnlocked = false; unlockable.unlockedInChallengeFile = false; unlockable.hasBeenUnlockedByPlayer = false; unlockable.inStorage = false; unlockable.hasBeenMoved = false; unlockable.placedPosition = Vector3.zero; unlockable.placedRotation = Vector3.zero; RobseStoragePlugin.Logger.LogDebug((object)$"Unlockable '{unlockableName}' renamed to '{unlockable.unlockableName}', type={unlockable.unlockableType}, spawnPrefab={unlockable.spawnPrefab}, IsPlaceable={unlockable.IsPlaceable}, canBeStored={unlockable.canBeStored}, alwaysInStock={unlockable.alwaysInStock}."); } public static RegisteredCupboard? FindByUnlockable(UnlockableItem? unlockable) { if (unlockable == null) { return null; } foreach (RegisteredCupboard cupboard in cupboards) { if (cupboard.Unlockable == unlockable) { return cupboard; } } return null; } public static RegisteredCupboard? FindByIndex(int index) { List<UnlockableItem> list = ((!((Object)(object)StartOfRound.Instance != (Object)null)) ? null : StartOfRound.Instance.unlockablesList?.unlockables); if (list == null || index < 0 || index >= list.Count) { return null; } return FindByUnlockable(list[index]); } public static UnlockableItem? FindVanillaCupboard() { List<UnlockableItem> list = ((!((Object)(object)StartOfRound.Instance != (Object)null)) ? null : StartOfRound.Instance.unlockablesList?.unlockables); if (list == null) { return null; } foreach (UnlockableItem item in list) { if (item.unlockableName == "Cupboard") { return item; } } return null; } public static IReadOnlyList<CupboardShopState> GetShopStates() { List<CupboardShopState> list = new List<CupboardShopState>(cupboards.Count); foreach (RegisteredCupboard cupboard in cupboards) { list.Add(new CupboardShopState(cupboard.Slot, cupboard.Unlockable.hasBeenUnlockedByPlayer, cupboard.Unlockable.inStorage)); } return list; } public static List<(RegisteredCupboard Cupboard, PlaceableShipObject Instance)> FindSpawnedCupboards() { List<(RegisteredCupboard, PlaceableShipObject)> list = new List<(RegisteredCupboard, PlaceableShipObject)>(); PlaceableShipObject[] array = Object.FindObjectsByType<PlaceableShipObject>((FindObjectsInactive)0, (FindObjectsSortMode)0); PlaceableShipObject[] array2 = array; foreach (PlaceableShipObject val in array2) { RegisteredCupboard registeredCupboard = FindByIndex(val.unlockableID); if (registeredCupboard != null) { list.Add((registeredCupboard, val)); } } return list; } } public class LobbyCompatibility { public static void RegisterCompatibility() { PluginHelper.RegisterPlugin("Robse.RobseStorage", Version.Parse("1.0.0"), (CompatibilityLevel)2, (VersionStrictness)0); } } [BepInPlugin("Robse.RobseStorage", "RobseStorage", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInIncompatibility("ScienceBird.MoreCupboards")] public class RobseStoragePlugin : BaseUnityPlugin { public const string AssetBundleFileName = "robsestorage.assets"; public const string MoreCupboardsGuid = "ScienceBird.MoreCupboards"; public const string LobbyCompatibilityGuid = "BMX.LobbyCompatibility"; public const string MattyFixesGuid = "mattymatty.MattyFixes"; public const string TerminalFormatterGuid = "TerminalFormatter"; public const string DawnLibGuid = "com.github.teamxiaolan.dawnlib"; public static PluginInfo pluginInfo; public static ConfigEntry<int> cupboardPrice; public static ConfigEntry<int> maximumCupboards; public static ConfigEntry<bool> noDoors; public static ConfigEntry<bool> separateCupboardEntries; public static ConfigEntry<bool> useColourNames; public static ConfigEntry<bool> autoParent; public static bool mattyPresent; public static bool mrovPresent; public static bool dawnLibPresent; public static RobseStoragePlugin Instance { get; private set; } internal static ManualLogSource Logger { get; private set; } internal static Harmony? Harmony { get; set; } public static AssetBundle CupboardAssets { get; private set; } public static CupboardNamingMode NamingMode => TerminalText.ResolveMode(separateCupboardEntries.Value, useColourNames.Value); private void Awake() { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown Logger = ((BaseUnityPlugin)this).Logger; Instance = this; pluginInfo = ((BaseUnityPlugin)this).Info; cupboardPrice = ((BaseUnityPlugin)this).Config.Bind<int>("General", "Cupboard Price", 250, "How much it costs to buy cupboards from the store."); maximumCupboards = ((BaseUnityPlugin)this).Config.Bind<int>("General", "Maximum Cupboards", 5, new ConfigDescription("Maximum extra cupboards you're allowed to buy for your ship. This is in addition to the vanilla cupboard, so the actual number of cupboards you can have on the ship will be one higher than this.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 5), Array.Empty<object>())); noDoors = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "No Doors", false, "Remove the doors from purchased cupboards and hide the doors of the vanilla cupboard."); separateCupboardEntries = ((BaseUnityPlugin)this).Config.Bind<bool>("Terminal", "Separate Cupboard Entries", false, "Rather than the shop listing a single cupboard, it and the storage menu will list every cupboard separately (Cupboard 1, Cupboard 2, ...) so you can request specific cupboards. Typing e.g. 'cupboard 2', '2cupboard' or 'yellow cupboard' always works, regardless of this setting."); useColourNames = ((BaseUnityPlugin)this).Config.Bind<bool>("Terminal", "Use Colour Names", false, "If using the above separate entries option, list the cupboards by colour (Orange Cupboard, Yellow Cupboard, ...) instead of by number."); autoParent = ((BaseUnityPlugin)this).Config.Bind<bool>("Technical", "Auto Parent", true, "After loading a save or joining a lobby, parent loose items standing inside purchased cupboards to that cupboard so they move with it. Items placed on the shelves are parented by the game itself."); string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty; string text = Path.Combine(path, "robsestorage.assets"); AssetBundle val = AssetBundle.LoadFromFile(text); if ((Object)(object)val == (Object)null) { Logger.LogError((object)("Asset bundle '" + text + "' could not be loaded. RobseStorage stays inactive.")); return; } CupboardAssets = val; CupboardRegistry.RegisterCupboards(val, cupboardPrice.Value, maximumCupboards.Value, noDoors.Value); if (CupboardRegistry.Cupboards.Count == 0) { Logger.LogError((object)"No cupboard could be registered. RobseStorage stays inactive."); return; } Patch(); Logger.LogInfo((object)string.Format("{0} v{1} has loaded! Naming mode: {2}, no doors: {3}, auto parent: {4}, DawnLib: {5}, MattyFixes: {6}, TerminalFormatter: {7}.", "Robse.RobseStorage", "1.0.0", NamingMode, noDoors.Value, autoParent.Value, dawnLibPresent, mattyPresent, mrovPresent)); } internal static void Patch() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown if (Harmony == null) { Harmony = new Harmony("Robse.RobseStorage"); } bool flag = Chainloader.PluginInfos.ContainsKey("BMX.LobbyCompatibility"); mattyPresent = Chainloader.PluginInfos.ContainsKey("mattymatty.MattyFixes"); mrovPresent = Chainloader.PluginInfos.ContainsKey("TerminalFormatter"); dawnLibPresent = Chainloader.PluginInfos.ContainsKey("com.github.teamxiaolan.dawnlib"); if (flag) { LobbyCompatibility.RegisterCompatibility(); } Logger.LogDebug((object)"Patching..."); Harmony.PatchAll(); Logger.LogDebug((object)"Finished patching!"); } internal static void Unpatch() { Logger.LogDebug((object)"Unpatching..."); Harmony? harmony = Harmony; if (harmony != null) { harmony.UnpatchSelf(); } Logger.LogDebug((object)"Finished unpatching!"); } } public enum CupboardNamingMode { Collapsed, Numbered, Coloured } public readonly struct CupboardShopState { public CupboardSlot Slot { get; } public bool IsUnlocked { get; } public bool IsInStorage { get; } public CupboardShopState(CupboardSlot slot, bool isUnlocked, bool isInStorage) { Slot = slot; IsUnlocked = isUnlocked; IsInStorage = isInStorage; } } public static class TerminalText { private const string VerbGroup = "^(?<verb>buy\\s+)?"; private static readonly string ColourAlternation = string.Join("|", CupboardCatalog.Slots.Select((CupboardSlot slot) => Regex.Escape(slot.Colour.ToLowerInvariant()))); private static readonly Regex CollapsedUpgradeLine = new Regex("^\\*\\s*" + Regex.Escape("Cupboard") + "\\s*//\\s*Price:\\s*\\$-?\\d+\\s*$", RegexOptions.Compiled); private static readonly Regex ColourCommand = new Regex("^(?<verb>buy\\s+)?(?<colour>" + ColourAlternation + ")\\s*cupb\\w*$", RegexOptions.Compiled); private static readonly Regex NumberAfterCommand = new Regex("^(?<verb>buy\\s+)?cupb\\w*\\s*#?\\s*(?<n>[1-9])$", RegexOptions.Compiled); private static readonly Regex NumberBeforeCommand = new Regex("^(?<verb>buy\\s+)?(?<n>[1-9])\\s*cupb\\w*$", RegexOptions.Compiled); private static readonly Regex GenericCommand = new Regex("^(?<verb>buy\\s+)?cupb\\w*$", RegexOptions.Compiled); private static readonly Regex WhitespaceRun = new Regex("\\s+", RegexOptions.Compiled); public static CupboardNamingMode ResolveMode(bool separateEntries, bool useColourNames) { if (!separateEntries) { return CupboardNamingMode.Collapsed; } if (!useColourNames) { return CupboardNamingMode.Numbered; } return CupboardNamingMode.Coloured; } public static string RewriteDisplayText(string text, CupboardNamingMode mode, IReadOnlyList<CupboardSlot> slots, bool trailingSpaceAfterCollapsedName) { if (string.IsNullOrEmpty(text) || mode == CupboardNamingMode.Coloured) { return text; } string text2 = text; foreach (CupboardSlot slot in slots) { string newValue = ((mode == CupboardNamingMode.Numbered) ? slot.NumberedDisplayName : ("Cupboard" + (trailingSpaceAfterCollapsedName ? " " : string.Empty))); text2 = text2.Replace(slot.UnlockableName, newValue); } if (mode != CupboardNamingMode.Collapsed) { return text2; } return CollapseDuplicateUpgradeLines(text2); } internal static string CollapseDuplicateUpgradeLines(string text) { string[] array = text.Split('\n'); List<string> list = new List<string>(array.Length); bool flag = false; string[] array2 = array; foreach (string text2 in array2) { if (CollapsedUpgradeLine.IsMatch(text2)) { if (flag) { continue; } flag = true; } list.Add(text2); } return string.Join("\n", list); } public static string? MapTypedCommand(string typed, CupboardNamingMode mode, bool vanillaCupboardInStorage, IReadOnlyList<CupboardShopState> cupboards) { if (string.IsNullOrWhiteSpace(typed) || cupboards.Count == 0) { return null; } string input = Normalize(typed); Match match = ColourCommand.Match(input); if (match.Success) { CupboardSlot slot = CupboardCatalog.FindByColour(match.Groups["colour"].Value); return Compose(match, slot, cupboards); } Match match2 = NumberAfterCommand.Match(input); if (!match2.Success) { match2 = NumberBeforeCommand.Match(input); } if (match2.Success) { CupboardSlot slot2 = CupboardCatalog.FindByNumber(int.Parse(match2.Groups["n"].Value)); return Compose(match2, slot2, cupboards); } if (mode != CupboardNamingMode.Collapsed) { return null; } Match match3 = GenericCommand.Match(input); if (!match3.Success || vanillaCupboardInStorage) { return null; } CupboardSlot slot3 = PickStoredCupboard(cupboards) ?? PickNextPurchasableCupboard(cupboards); return Compose(match3, slot3, cupboards); } private static CupboardSlot? PickStoredCupboard(IReadOnlyList<CupboardShopState> cupboards) { return (from state in cupboards where state.IsInStorage orderby state.Slot.Number select state.Slot).FirstOrDefault(); } private static CupboardSlot? PickNextPurchasableCupboard(IReadOnlyList<CupboardShopState> cupboards) { return (from state in cupboards where !state.IsUnlocked orderby state.Slot.Number select state.Slot).FirstOrDefault(); } private static string? Compose(Match match, CupboardSlot? slot, IReadOnlyList<CupboardShopState> cupboards) { if (slot == null || cupboards.All((CupboardShopState state) => state.Slot.Number != slot.Number)) { return null; } return match.Groups["verb"].Value + slot.TerminalKeyword; } private static string Normalize(string typed) { string input = typed.ToLowerInvariant().Replace('-', ' ').Replace('_', ' '); return WhitespaceRun.Replace(input, " ").Trim(); } } public static class MyPluginInfo { public const string PLUGIN_GUID = "Robse.RobseStorage"; public const string PLUGIN_NAME = "RobseStorage"; public const string PLUGIN_VERSION = "1.0.0"; } } namespace RobseStorage.Patches { [HarmonyPatch] internal static class CupboardItemParentingPatches { private const int SettleFrames = 30; private static bool applyScheduled; [HarmonyPatch(typeof(StartOfRound), "LoadShipGrabbableItems")] [HarmonyPostfix] private static void LoadShipGrabbableItems_Postfix() { Schedule("LoadShipGrabbableItems"); } [HarmonyPatch(typeof(StartOfRound), "SyncShipUnlockablesClientRpc")] [HarmonyPostfix] private static void SyncShipUnlockablesClientRpc_Postfix() { Schedule("SyncShipUnlockablesClientRpc"); } private static void Schedule(string reason) { if (RobseStoragePlugin.autoParent.Value && !applyScheduled && !((Object)(object)RobseStoragePlugin.Instance == (Object)null)) { applyScheduled = true; RobseStoragePlugin.Logger.LogDebug((object)("Auto parent scheduled after " + reason + ".")); ((MonoBehaviour)RobseStoragePlugin.Instance).StartCoroutine(ApplyAfterSettling(reason)); } } private static IEnumerator ApplyAfterSettling(string reason) { for (int frame = 0; frame < 30; frame++) { yield return null; } applyScheduled = false; try { ApplyCupboardParents(reason); } catch (Exception arg) { RobseStoragePlugin.Logger.LogError((object)$"Auto parent failed: {arg}"); } } internal static void ApplyCupboardParents(string reason) { //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)StartOfRound.Instance == (Object)null) { return; } List<(RegisteredCupboard, PlaceableShipObject)> list = CupboardRegistry.FindSpawnedCupboards(); if (list.Count == 0) { RobseStoragePlugin.Logger.LogDebug((object)("Auto parent (" + reason + "): no purchased cupboard in the scene.")); return; } GrabbableObject[] array = Object.FindObjectsByType<GrabbableObject>((FindObjectsInactive)0, (FindObjectsSortMode)0); int num = 0; foreach (var item3 in list) { RegisteredCupboard item = item3.Item1; PlaceableShipObject item2 = item3.Item2; Collider placeObjectCollider = item2.placeObjectCollider; Transform val = (((Object)(object)item2.parentObject != (Object)null) ? ((Component)item2.parentObject).transform : null); if ((Object)(object)placeObjectCollider == (Object)null || (Object)(object)val == (Object)null) { RobseStoragePlugin.Logger.LogWarning((object)("Auto parent (" + reason + "): '" + item.Slot.UnlockableName + "' has no placement collider or parent object; skipped.")); continue; } GrabbableObject[] array2 = array; foreach (GrabbableObject val2 in array2) { if (IsLooseItem(val2) && !((Object)(object)((Component)val2).transform.parent == (Object)(object)val)) { Bounds bounds = placeObjectCollider.bounds; if (((Bounds)(ref bounds)).Contains(((Component)val2).transform.position)) { ((Component)val2).transform.SetParent(val, true); val2.targetFloorPosition = ((Component)val2).transform.localPosition; num++; } } } } RobseStoragePlugin.Logger.LogInfo((object)$"Auto parent ({reason}): {num} item(s) parented to {list.Count} cupboard(s)."); } private static bool IsLooseItem(GrabbableObject grabbable) { if ((Object)(object)grabbable != (Object)null && !grabbable.isHeld && !grabbable.isHeldByEnemy && !grabbable.isPocketed) { return (Object)(object)grabbable.playerHeldBy == (Object)null; } return false; } } [HarmonyPatch] internal static class TerminalPatches { private const string StorePagePlaceholder = "[buyableItemsList]"; [HarmonyPatch(typeof(Terminal), "LoadNewNode")] [HarmonyPrefix] [HarmonyBefore(new string[] { "TerminalFormatter" })] private static void LoadNewNode_Prefix(TerminalNode node, out bool __state) { __state = (Object)(object)node != (Object)null && node.displayText != null && node.displayText.Contains("[buyableItemsList]"); } [HarmonyPatch(typeof(Terminal), "LoadNewNode")] [HarmonyPostfix] [HarmonyPriority(0)] [HarmonyAfter(new string[] { "TerminalFormatter" })] private static void LoadNewNode_Postfix(Terminal __instance, bool __state) { try { CupboardNamingMode namingMode = RobseStoragePlugin.NamingMode; if (namingMode != CupboardNamingMode.Coloured && __instance.currentText != null) { bool trailingSpaceAfterCollapsedName = RobseStoragePlugin.mrovPresent && __state && namingMode == CupboardNamingMode.Collapsed; string text = TerminalText.RewriteDisplayText(__instance.currentText, namingMode, CupboardCatalog.Take(RobseStoragePlugin.maximumCupboards.Value), trailingSpaceAfterCollapsedName); if (!string.Equals(text, __instance.currentText, StringComparison.Ordinal)) { __instance.currentText = text; __instance.screenText.text = text; } } } catch (Exception arg) { RobseStoragePlugin.Logger.LogError((object)$"Failed to rewrite terminal text: {arg}"); } } [HarmonyPatch(typeof(Terminal), "OnSubmit")] [HarmonyPrefix] private static void OnSubmit_Prefix(Terminal __instance) { try { string text = __instance.screenText.text; int textAdded = __instance.textAdded; if (!__instance.terminalInUse || textAdded <= 0 || textAdded > text.Length || ((Object)(object)__instance.currentNode != (Object)null && __instance.currentNode.overrideOptions)) { return; } string text2 = text.Substring(text.Length - textAdded); bool vanillaCupboardInStorage = CupboardRegistry.FindVanillaCupboard()?.inStorage ?? false; string text3 = TerminalText.MapTypedCommand(text2, RobseStoragePlugin.NamingMode, vanillaCupboardInStorage, CupboardRegistry.GetShopStates()); if (text3 != null) { string text4 = text.Substring(0, text.Length - textAdded); string text5 = text4 + text3; if (!string.Equals(text5, text, StringComparison.Ordinal)) { __instance.modifyingText = true; __instance.screenText.text = text5; __instance.modifyingText = false; __instance.textAdded = text3.Length; RobseStoragePlugin.Logger.LogInfo((object)("Terminal command '" + text2.Trim() + "' mapped to '" + text3 + "'.")); } } } catch (Exception arg) { RobseStoragePlugin.Logger.LogError((object)$"Failed to map terminal command: {arg}"); } } } [HarmonyPatch] internal static class UnlockableDiagnosticsPatches { private const string Tag = "[Diag]"; [HarmonyPatch(typeof(StartOfRound), "Start")] [HarmonyPostfix] private static void StartOfRound_Start_Postfix(StartOfRound __instance) { Guard(delegate { LogUnlockableTable(__instance, "StartOfRound.Start"); }); } [HarmonyPatch(typeof(StartOfRound), "LoadUnlockables")] [HarmonyPostfix] private static void LoadUnlockables_Postfix(StartOfRound __instance) { Guard(delegate { string text = (RobseStoragePlugin.dawnLibPresent ? "DawnLib save handler (vanilla LoadUnlockables skipped)" : "vanilla LoadUnlockables"); RobseStoragePlugin.Logger.LogInfo((object)("[Diag] Unlockables loaded via " + text + ".")); LogUnlockableTable(__instance, "LoadUnlockables"); }); } [HarmonyPatch(typeof(StartOfRound), "UnlockShipObject")] [HarmonyPostfix] private static void UnlockShipObject_Postfix(StartOfRound __instance, int unlockableID) { Guard(delegate { RegisteredCupboard registeredCupboard = CupboardRegistry.FindByIndex(unlockableID); if (registeredCupboard != null) { GameObject value; bool flag = __instance.SpawnedShipUnlockables.TryGetValue(unlockableID, out value) && (Object)(object)value != (Object)null; RobseStoragePlugin.Logger.LogInfo((object)string.Format("{0} Purchased '{1}' (index {2}); unlocked={3}, spawnedObjectTracked={4}.", "[Diag]", registeredCupboard.Slot.UnlockableName, unlockableID, registeredCupboard.Unlockable.hasBeenUnlockedByPlayer, flag)); } }); } [HarmonyPatch(typeof(StartOfRound), "SpawnUnlockable")] [HarmonyPostfix] private static void SpawnUnlockable_Postfix(StartOfRound __instance, int unlockableIndex, bool useSavedPositionData) { Guard(delegate { //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) RegisteredCupboard registeredCupboard = CupboardRegistry.FindByIndex(unlockableIndex); if (registeredCupboard != null) { string text = "no object tracked"; if (__instance.SpawnedShipUnlockables.TryGetValue(unlockableIndex, out var value) && (Object)(object)value != (Object)null) { NetworkObject component = value.GetComponent<NetworkObject>(); PlaceableShipObject componentInChildren = value.GetComponentInChildren<PlaceableShipObject>(); text = string.Format("object '{0}', networkId={1}, isSpawned={2}, instanceUnlockableID={3}, worldPos={4}", ((Object)value).name, ((Object)(object)component != (Object)null) ? component.NetworkObjectId.ToString() : "none", (Object)(object)component != (Object)null && component.IsSpawned, ((Object)(object)componentInChildren != (Object)null) ? componentInChildren.unlockableID.ToString() : "none", value.transform.position); } RobseStoragePlugin.Logger.LogInfo((object)string.Format("{0} Spawned '{1}' (index {2}, useSavedPositionData={3}, placedPos={4}, inStorage={5}): {6}.", "[Diag]", registeredCupboard.Slot.UnlockableName, unlockableIndex, useSavedPositionData, registeredCupboard.Unlockable.placedPosition, registeredCupboard.Unlockable.inStorage, text)); } }); } [HarmonyPatch(typeof(GameNetworkManager), "SaveGameValues")] [HarmonyPostfix] private static void SaveGameValues_Postfix() { Guard(delegate { //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) string text = (RobseStoragePlugin.dawnLibPresent ? "vanilla keys plus DawnLib key-based data" : "vanilla keys"); foreach (RegisteredCupboard cupboard in CupboardRegistry.Cupboards) { UnlockableItem unlockable = cupboard.Unlockable; bool flag = unlockable.hasBeenUnlockedByPlayer || unlockable.hasBeenMoved || unlockable.inStorage; RobseStoragePlugin.Logger.LogInfo((object)string.Format("{0} Save ({1}): '{2}' index={3} persisted={4} unlocked={5} moved={6} inStorage={7} pos={8} rot={9}.", "[Diag]", text, unlockable.unlockableName, cupboard.ListIndex, flag, unlockable.hasBeenUnlockedByPlayer, unlockable.hasBeenMoved, unlockable.inStorage, unlockable.placedPosition, unlockable.placedRotation)); } }); } [HarmonyPatch(typeof(StartOfRound), "SyncShipUnlockablesClientRpc")] [HarmonyPostfix] private static void SyncShipUnlockablesClientRpc_Postfix(StartOfRound __instance, bool[] unlockedObjects, Vector3[] placeableObjectPositions, int[] storedItems) { Guard(delegate { if (((NetworkBehaviour)__instance).IsServer || unlockedObjects == null || placeableObjectPositions == null || storedItems == null) { return; } int count = __instance.unlockablesList.unlockables.Count; if (unlockedObjects.Length != count) { RobseStoragePlugin.Logger.LogError((object)string.Format("{0} Client sync: host sent {1} unlockables but this client has {2}. The mod lists differ between host and client; cupboard indices are unreliable.", "[Diag]", unlockedObjects.Length, count)); } else { RobseStoragePlugin.Logger.LogInfo((object)string.Format("{0} Client sync: {1} unlockables, list lengths match.", "[Diag]", unlockedObjects.Length)); } PlaceableShipObject[] array = Object.FindObjectsByType<PlaceableShipObject>((FindObjectsInactive)0, (FindObjectsSortMode)0); foreach (RegisteredCupboard cupboard in CupboardRegistry.Cupboards) { int listIndex = cupboard.ListIndex; bool flag = listIndex >= 0 && listIndex < unlockedObjects.Length && unlockedObjects[listIndex]; bool flag2 = listIndex >= 0 && Array.IndexOf(storedItems, listIndex) >= 0; string text = ((listIndex >= 0 && listIndex < placeableObjectPositions.Length) ? ((object)Unsafe.As<Vector3, Vector3>(ref placeableObjectPositions[listIndex])/*cast due to .constrained prefix*/).ToString() : "n/a"); int num = 0; PlaceableShipObject[] array2 = array; foreach (PlaceableShipObject val in array2) { if (val.unlockableID == listIndex) { num++; } } RobseStoragePlugin.Logger.LogInfo((object)string.Format("{0} Client sync: '{1}' index={2} hostUnlocked={3} hostStored={4} hostPos={5} sceneInstances={6}.", "[Diag]", cupboard.Slot.UnlockableName, listIndex, flag, flag2, text, num)); } }); } internal static void LogUnlockableTable(StartOfRound startOfRound, string stage) { //IL_0120: Unknown result type (might be due to invalid IL or missing references) List<UnlockableItem> unlockables = startOfRound.unlockablesList.unlockables; int num = unlockables.FindIndex((UnlockableItem val) => val.unlockableName == "Cupboard"); RobseStoragePlugin.Logger.LogInfo((object)string.Format("{0} {1}: unlockablesList has {2} entries, vanilla '{3}' at index {4}, IsServer={5}.", "[Diag]", stage, unlockables.Count, "Cupboard", num, ((NetworkBehaviour)startOfRound).IsServer)); foreach (RegisteredCupboard cupboard in CupboardRegistry.Cupboards) { int listIndex = cupboard.ListIndex; int unlockableID = cupboard.PrefabPlaceable.unlockableID; UnlockableItem unlockable = cupboard.Unlockable; string text = string.Format("{0} {1}: '{2}' index={3} prefabUnlockableID={4} unlocked={5} inStorage={6} moved={7} pos={8}.", "[Diag]", stage, unlockable.unlockableName, listIndex, unlockableID, unlockable.hasBeenUnlockedByPlayer, unlockable.inStorage, unlockable.hasBeenMoved, unlockable.placedPosition); if (listIndex < 0) { RobseStoragePlugin.Logger.LogError((object)(text + " The cupboard is missing from unlockablesList.")); } else if (listIndex != unlockableID) { RobseStoragePlugin.Logger.LogError((object)(text + " Prefab unlockableID does not match the list index.")); } else { RobseStoragePlugin.Logger.LogInfo((object)text); } } } private static void Guard(Action action) { try { action(); } catch (Exception arg) { RobseStoragePlugin.Logger.LogWarning((object)string.Format("{0} Diagnostics failed: {1}", "[Diag]", arg)); } } } [HarmonyPatch] internal static class VanillaCupboardDoorsPatch { private const string ShipSceneName = "SampleSceneRelay"; private const string VanillaCupboardObjectName = "StorageCloset"; private static readonly string[] DoorObjectNames = new string[2] { "Cube.000", "Cube.002" }; [HarmonyPatch(typeof(StartOfRound), "SceneManager_OnLoadComplete1")] [HarmonyPostfix] private static void SceneManager_OnLoadComplete1_Postfix(string sceneName) { if (sceneName != "SampleSceneRelay" || !RobseStoragePlugin.noDoors.Value) { return; } try { HideVanillaCupboardDoors(); } catch (Exception arg) { RobseStoragePlugin.Logger.LogError((object)$"Failed to hide the vanilla cupboard doors: {arg}"); } } private static void HideVanillaCupboardDoors() { GameObject val = GameObject.Find("StorageCloset"); if ((Object)(object)val == (Object)null) { RobseStoragePlugin.Logger.LogWarning((object)"Vanilla cupboard 'StorageCloset' not found; doors stay visible."); return; } int num = 0; Animator[] componentsInChildren = val.GetComponentsInChildren<Animator>(true); foreach (Animator val2 in componentsInChildren) { if (Array.IndexOf(DoorObjectNames, ((Object)((Component)val2).gameObject).name) >= 0) { HideDoor(((Component)val2).gameObject); num++; } } if (num == 0) { RobseStoragePlugin.Logger.LogWarning((object)"No door objects found on the vanilla cupboard; the game may have renamed them."); } else { RobseStoragePlugin.Logger.LogInfo((object)$"Hid {num} vanilla cupboard door(s) without touching their network components."); } } private static void HideDoor(GameObject door) { Renderer[] componentsInChildren = door.GetComponentsInChildren<Renderer>(true); foreach (Renderer val in componentsInChildren) { val.enabled = false; } Collider[] componentsInChildren2 = door.GetComponentsInChildren<Collider>(true); foreach (Collider val2 in componentsInChildren2) { val2.enabled = false; } Animator[] componentsInChildren3 = door.GetComponentsInChildren<Animator>(true); foreach (Animator val3 in componentsInChildren3) { ((Behaviour)val3).enabled = false; } InteractTrigger[] componentsInChildren4 = door.GetComponentsInChildren<InteractTrigger>(true); foreach (InteractTrigger val4 in componentsInChildren4) { val4.interactable = false; val4.hoverTip = string.Empty; val4.disabledHoverTip = string.Empty; } } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }