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 LocalCraftFromStoragePrime v1.0.0
plugins/LocalCraftFromStoragePrime.dll
Decompiled 2 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [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("LuminairPrime")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Craft from nearby chests and put materials away with one click.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+02cd96c4dedfeca267efeeb46e6b6909a78ad570")] [assembly: AssemblyProduct("LocalCraftFromStoragePrime")] [assembly: AssemblyTitle("LocalCraftFromStoragePrime")] [assembly: AssemblyVersion("1.0.0.0")] [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 LocalCraftFromStoragePrime { internal sealed class PluginConfiguration { private sealed class RadiusRange : AcceptableValueRange<float> { private readonly string _setting; internal RadiusRange(string setting) : base(1f, 128f) { _setting = setting; } public override object Clamp(object value) { float num = (float)value; if (float.IsNaN(num) || float.IsInfinity(num)) { Plugin.Log.LogWarning((object)(_setting + " must be a finite number from 1 to 128. Using 50 metres.")); } return RuntimeSettings.NormalizeRadius(num); } } private readonly ConfigFile _config; private ConfigEntry<bool> _enabled; private ConfigEntry<bool> _craftingFromStorage; private ConfigEntry<bool> _buildingFromStorage; private ConfigEntry<float> _craftingDistance; private ConfigEntry<float> _buildingDistance; private ConfigEntry<bool> _allowShips; private ConfigEntry<bool> _allowCarts; private ConfigEntry<string> _reserves; private IReadOnlyDictionary<string, int> _parsedReserves = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); private ConfigEntry<bool> _depositEnabled; private ConfigEntry<float> _depositRange; private ConfigEntry<string> _depositExcludedItems; private ConfigEntry<bool> _depositFeedback; private ConfigEntry<string> _depositIncludedItems; private ConfigEntry<string> _depositButtonLabel; internal RuntimeSettings Current { get; private set; } internal ConfigEntry<bool> DebugLogging { get; private set; } internal ConfigEntry<bool> ExportDepositCatalogue { get; private set; } internal ConfigEntry<bool> StoragePerfDiagnostics { get; private set; } internal PluginConfiguration(ConfigFile config) { _config = config; Current = RuntimeSettings.Default; } internal bool IsReservesEntry(SettingChangedEventArgs args) { return (object)args.ChangedSetting == _reserves; } internal void Bind() { //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Expected O, but got Unknown //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Expected O, but got Unknown //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Expected O, but got Unknown _enabled = _config.Bind<bool>("01 - General", "Enabled", true, "Control all features at once. Changes take effect immediately without a restart."); _allowShips = _config.Bind<bool>("01 - General", "IncludeShipContainers", false, "Use containers on ships for all features."); _allowCarts = _config.Bind<bool>("01 - General", "IncludeCartContainers", false, "Use containers on carts for all features."); _reserves = _config.Bind<string>("01 - General", "ReserveItems", "", "Add *:1 if you want crafting from storage to always leave 1 item behind so you can easily refill the storage again. *:1,Wood:5 leaves one of each item except five wood; Wood:0 exempts wood. See docs/deposit-items.md for keys."); _craftingFromStorage = _config.Bind<bool>("02 - Craft from Storage", "Enabled", true, "Use nearby containers for crafting and upgrades."); _craftingDistance = _config.Bind<float>("02 - Craft from Storage", "Distance", 50f, new ConfigDescription("Search distance in m from the current crafting station, or the player when no station is active. Stay below 64 m; above that containers may be unavailable or buggy.", (AcceptableValueBase)(object)new RadiusRange("Craft from Storage Distance"), Array.Empty<object>())); _buildingFromStorage = _config.Bind<bool>("03 - Build from Storage", "Enabled", true, "Use nearby containers for building with the Hammer and other build tools."); _buildingDistance = _config.Bind<float>("03 - Build from Storage", "Distance", 50f, new ConfigDescription("Search distance in m from the player using the build tool. Stay below 64 m; above that containers may be unavailable or buggy.", (AcceptableValueBase)(object)new RadiusRange("Build from Storage Distance"), Array.Empty<object>())); _depositEnabled = _config.Bind<bool>("04 - Store Nearby", "Enabled", true, "Show the Store Nearby button in the player inventory."); _depositButtonLabel = _config.Bind<string>("04 - Store Nearby", "ButtonLabel", "Store Nearby", "'Store Nearby' button text can be replaced up to 32 text elements, or left blank for a square button without text."); _depositRange = _config.Bind<float>("04 - Store Nearby", "Distance", 50f, new ConfigDescription("Search distance in m from the player for Store Nearby. Stay below 64 m; above that containers may be unavailable or buggy.", (AcceptableValueBase)(object)new RadiusRange("Store Nearby Distance"), Array.Empty<object>())); _depositExcludedItems = _config.Bind<string>("04 - Store Nearby", "ExcludedItems", "", "Item keys to keep out of Store Nearby. Exclusions override inclusions. See docs/deposit-items.md for keys."); _depositIncludedItems = _config.Bind<string>("04 - Store Nearby", "IncludedItems", "raspberries, blueberries, cloudberries, vineberry, mushroomcommon, mushroomyellow, mushroomblue, jotunpuffs, magecap, smokepuff, pukeberries, trophies", "Additional item keys or trophy type aliases eligible for Store Nearby. See docs/deposit-items.md for keys and aliases."); _depositFeedback = _config.Bind<bool>("04 - Store Nearby", "FailureMessages", true, "Explain an unsuccessful Store Nearby click."); DebugLogging = _config.Bind<bool>("05 - Diagnostics", "DebugLogging", false, "Log container discovery, exclusions, counts, withdrawals, and deposits."); ExportDepositCatalogue = _config.Bind<bool>("05 - Diagnostics", "ExportDepositItemCatalogue", false, "Export registered item details for diagnostics after entering a world. Writes BepInEx/config/LuminairPrime.valheim.localcraftfromstorageprime.deposit-items.md."); StoragePerfDiagnostics = _config.Bind<bool>("05 - Diagnostics", "StoragePerformanceDiagnostics", false, "Record container search and frame-interval timings for performance diagnosis."); } internal void ReadSettings(bool reparseReserves = true) { if (reparseReserves) { List<string> list = new List<string>(); _parsedReserves = StorageRules.ParseReserves(_reserves.Value, list.Add); foreach (string item in list) { Plugin.Log.LogWarning((object)("Ignored invalid reserve entry '" + item + "'. Use Item, Item:Count, or *:Count. Item counts may be zero; wildcard counts must be above zero.")); } } Current = new RuntimeSettings(_enabled.Value, _craftingFromStorage.Value, _buildingFromStorage.Value, _craftingDistance.Value, _buildingDistance.Value, _allowShips.Value, _allowCarts.Value, _parsedReserves, _depositEnabled.Value, _depositRange.Value, DepositRules.ParseExcludedNames(_depositExcludedItems.Value), _depositFeedback.Value, DepositRules.ParseExcludedNames(_depositIncludedItems.Value), _depositButtonLabel.Value); } } internal static class RuntimeSettingsLimits { internal const float MinDistance = 1f; internal const float MaxDistance = 128f; internal const float DefaultDistance = 50f; internal const float SafeTestedMax = 64f; } internal sealed class RuntimeSettings { internal static RuntimeSettings Default { get; } = new RuntimeSettings(enabled: true, craftingFromStorage: true, buildingFromStorage: true, 50f, 50f, allowShips: false, allowCarts: false, StorageRules.DefaultReserves, depositEnabled: true, 50f, DepositRules.ParseExcludedNames(""), depositFeedback: true, DepositRules.ParseExcludedNames("raspberries, blueberries, cloudberries, vineberry, mushroomcommon, mushroomyellow, mushroomblue, jotunpuffs, magecap, smokepuff, pukeberries, trophies")); internal bool Enabled { get; } internal bool CraftingFromStorage { get; } internal bool BuildingFromStorage { get; } internal float CraftingDistance { get; } internal float BuildingDistance { get; } internal bool AllowShips { get; } internal bool AllowCarts { get; } internal IReadOnlyDictionary<string, int> Reserves { get; } internal bool DepositEnabled { get; } internal float DepositRange { get; } internal IReadOnlyCollection<string> DepositExcludedNames { get; } internal bool DepositFeedback { get; } internal IReadOnlyCollection<string> DepositIncludedNames { get; } internal string DepositButtonLabel { get; } internal RuntimeSettings(bool enabled, bool craftingFromStorage, bool buildingFromStorage, float craftingDistance, float buildingDistance, bool allowShips, bool allowCarts, IReadOnlyDictionary<string, int> reserves, bool depositEnabled, float depositRange, IReadOnlyCollection<string> depositExcludedNames, bool depositFeedback = true, IReadOnlyCollection<string>? depositIncludedNames = null, string? depositButtonLabel = "Store Nearby") { Enabled = enabled; CraftingFromStorage = craftingFromStorage; BuildingFromStorage = buildingFromStorage; CraftingDistance = NormalizeRadius(craftingDistance); BuildingDistance = NormalizeRadius(buildingDistance); AllowShips = allowShips; AllowCarts = allowCarts; Reserves = reserves; DepositEnabled = depositEnabled; DepositRange = NormalizeRadius(depositRange); DepositExcludedNames = depositExcludedNames; DepositFeedback = depositFeedback; DepositIncludedNames = (IReadOnlyCollection<string>)(((object)depositIncludedNames) ?? ((object)Array.Empty<string>())); DepositButtonLabel = DepositLabel.Normalize(depositButtonLabel); } internal int ReserveFor(string itemName) { if (Reserves.TryGetValue(StorageRules.NormalizeItemName(itemName), out var value)) { return value; } if (!Reserves.TryGetValue("*", out var value2)) { return 0; } return value2; } internal static float NormalizeRadius(float value) { if (!float.IsNaN(value) && !float.IsInfinity(value)) { return Math.Max(1f, Math.Min(128f, value)); } return 50f; } } internal enum EnumerateCaller { Presence, Scan, Capture } internal sealed class PerformanceCounters { internal const double WindowSeconds = 5.0; internal const double SpikeThresholdMs = 50.0; private readonly long[] _enumCounts = new long[3]; private readonly double[] _enumMsTotal = new double[3]; private readonly double[] _enumMsMax = new double[3]; private readonly long[] _enumReturnedTotal = new long[3]; private readonly int[] _enumReturnedMax = new int[3]; private long _presencePolls; private double _presenceMsTotal; private double _presenceMsMax; private long _presenceTrue; private long _presenceFalse; private long _presenceFailed; private long _discoveryHits; private double _discoveryHitMsTotal; private double _discoveryHitMsMax; private long _discoveryScans; private double _discoveryScanMsTotal; private double _discoveryScanMsMax; private long _discoveryEligibleTotal; private int _discoveryEligibleMax; private long _craftLoads; private double _craftLoadMsTotal; private double _craftLoadMsMax; private long _craftInventoriesTotal; private int _craftInventoriesMax; private long _hammerHits; private long _hammerCaptures; private double _hammerCaptureMsTotal; private double _hammerCaptureMsMax; private long _frames; private double _frameMsTotal; private double _frameMsMax; private bool _maxFrameHadEnum; private long _spikes; private long _spikesWithEnum; private long _enumFrames; private readonly long[] _buildUiCalls = new long[2]; private readonly double[] _buildUiTotal = new double[2]; private readonly double[] _buildUiMax = new double[2]; internal long PresencePolls => _presencePolls; internal long DiscoveryHits => _discoveryHits; internal long DiscoveryScans => _discoveryScans; internal long CraftLoads => _craftLoads; internal long HammerHits => _hammerHits; internal long HammerCaptures => _hammerCaptures; internal long Frames => _frames; internal long Spikes => _spikes; internal bool HasActivity { get { if (_enumCounts[0] + _enumCounts[1] + _enumCounts[2] <= 0 && _presencePolls <= 0 && _discoveryHits <= 0 && _discoveryScans <= 0 && _craftLoads <= 0 && _hammerHits <= 0) { return _hammerCaptures > 0; } return true; } } internal void OnBuildUi(bool rows, double elapsedMs) { int num = ((!rows) ? 1 : 0); _buildUiCalls[num]++; _buildUiTotal[num] += Math.Max(0.0, elapsedMs); _buildUiMax[num] = Math.Max(_buildUiMax[num], elapsedMs); } internal long EnumCount(EnumerateCaller caller) { return _enumCounts[(int)caller]; } internal double EnumMsTotal(EnumerateCaller caller) { return _enumMsTotal[(int)caller]; } internal double EnumMsMax(EnumerateCaller caller) { return _enumMsMax[(int)caller]; } internal long EnumReturnedTotal(EnumerateCaller caller) { return _enumReturnedTotal[(int)caller]; } internal int EnumReturnedMax(EnumerateCaller caller) { return _enumReturnedMax[(int)caller]; } internal void OnEnumerate(EnumerateCaller caller, double elapsedMs, int returned) { if (elapsedMs < 0.0) { elapsedMs = 0.0; } if (returned < 0) { returned = 0; } _enumCounts[(int)caller]++; _enumMsTotal[(int)caller] += elapsedMs; if (elapsedMs > _enumMsMax[(int)caller]) { _enumMsMax[(int)caller] = elapsedMs; } _enumReturnedTotal[(int)caller] += returned; if (returned > _enumReturnedMax[(int)caller]) { _enumReturnedMax[(int)caller] = returned; } } internal void OnPresence(double elapsedMsInclusive, bool? result) { if (elapsedMsInclusive < 0.0) { elapsedMsInclusive = 0.0; } _presencePolls++; _presenceMsTotal += elapsedMsInclusive; if (elapsedMsInclusive > _presenceMsMax) { _presenceMsMax = elapsedMsInclusive; } if (result == true) { _presenceTrue++; } else if (result == false) { _presenceFalse++; } else { _presenceFailed++; } } internal void OnDiscoveryHit(double elapsedMsInclusive) { if (elapsedMsInclusive < 0.0) { elapsedMsInclusive = 0.0; } _discoveryHits++; _discoveryHitMsTotal += elapsedMsInclusive; if (elapsedMsInclusive > _discoveryHitMsMax) { _discoveryHitMsMax = elapsedMsInclusive; } } internal void OnDiscoveryScan(double elapsedMsInclusive, int eligible) { if (elapsedMsInclusive < 0.0) { elapsedMsInclusive = 0.0; } if (eligible < 0) { eligible = 0; } _discoveryScans++; _discoveryScanMsTotal += elapsedMsInclusive; if (elapsedMsInclusive > _discoveryScanMsMax) { _discoveryScanMsMax = elapsedMsInclusive; } _discoveryEligibleTotal += eligible; if (eligible > _discoveryEligibleMax) { _discoveryEligibleMax = eligible; } } internal void OnCraftLoad(double elapsedMsInclusive, int inventories) { if (elapsedMsInclusive < 0.0) { elapsedMsInclusive = 0.0; } if (inventories < 0) { inventories = 0; } _craftLoads++; _craftLoadMsTotal += elapsedMsInclusive; if (elapsedMsInclusive > _craftLoadMsMax) { _craftLoadMsMax = elapsedMsInclusive; } _craftInventoriesTotal += inventories; if (inventories > _craftInventoriesMax) { _craftInventoriesMax = inventories; } } internal void OnHammerHit() { _hammerHits++; } internal void OnHammerCapture(double elapsedMsInclusive) { if (elapsedMsInclusive < 0.0) { elapsedMsInclusive = 0.0; } _hammerCaptures++; _hammerCaptureMsTotal += elapsedMsInclusive; if (elapsedMsInclusive > _hammerCaptureMsMax) { _hammerCaptureMsMax = elapsedMsInclusive; } } internal void TickFrame(double frameMs, bool hadEnum) { if (frameMs < 0.0) { frameMs = 0.0; } _frames++; _frameMsTotal += frameMs; if (frameMs > _frameMsMax) { _frameMsMax = frameMs; _maxFrameHadEnum = hadEnum; } if (hadEnum) { _enumFrames++; } if (frameMs > 50.0) { _spikes++; if (hadEnum) { _spikesWithEnum++; } } } internal void Reset() { Array.Clear(_buildUiCalls, 0, 2); Array.Clear(_buildUiTotal, 0, 2); Array.Clear(_buildUiMax, 0, 2); Array.Clear(_enumCounts, 0, _enumCounts.Length); Array.Clear(_enumMsTotal, 0, _enumMsTotal.Length); Array.Clear(_enumMsMax, 0, _enumMsMax.Length); Array.Clear(_enumReturnedTotal, 0, _enumReturnedTotal.Length); Array.Clear(_enumReturnedMax, 0, _enumReturnedMax.Length); _presencePolls = 0L; _presenceMsTotal = 0.0; _presenceMsMax = 0.0; _presenceTrue = 0L; _presenceFalse = 0L; _presenceFailed = 0L; _discoveryHits = 0L; _discoveryHitMsTotal = 0.0; _discoveryHitMsMax = 0.0; _discoveryScans = 0L; _discoveryScanMsTotal = 0.0; _discoveryScanMsMax = 0.0; _discoveryEligibleTotal = 0L; _discoveryEligibleMax = 0; _craftLoads = 0L; _craftLoadMsTotal = 0.0; _craftLoadMsMax = 0.0; _craftInventoriesTotal = 0L; _craftInventoriesMax = 0; _hammerHits = 0L; _hammerCaptures = 0L; _hammerCaptureMsTotal = 0.0; _hammerCaptureMsMax = 0.0; _frames = 0L; _frameMsTotal = 0.0; _frameMsMax = 0.0; _maxFrameHadEnum = false; _spikes = 0L; _spikesWithEnum = 0L; _enumFrames = 0L; } internal bool TryCloseWindow(double windowSeconds, int gc0Delta, int gc1Delta, int gc2Delta, out string summary) { summary = string.Empty; if (windowSeconds < 5.0 || (_frames == 0L && !HasActivity)) { if (windowSeconds >= 5.0) { Reset(); } return false; } double num = ((_frames > 0) ? (_frameMsTotal / (double)_frames) : 0.0); summary = string.Format(CultureInfo.InvariantCulture, "[PerfDiag] backend=registry {0:0.0}s window: frames={1} avgFrame={2:0.0}ms maxFrame={3:0.0}ms(maxEnum={4}) spikes>50ms={5}(enum={6}) enumFrames={7} | enum presence={8} total={9:0.00}ms max={10:0.00}ms returned={11}(max{12}) | enum scan={13} total={14:0.00}ms max={15:0.00}ms returned={16}(max{17}) | enum capture={18} total={19:0.00}ms max={20:0.00}ms returned={21}(max{22}) | presence(incl.)={23} total={24:0.00}ms max={25:0.00}ms true={26} false={27} failed={28} | discovery hits={29} total={30:0.00}ms max={31:0.00}ms scans(incl.)={32} total={33:0.00}ms max={34:0.00}ms eligible={35}(max{36}) | craftLoad(incl.)={37} total={38:0.00}ms max={39:0.00}ms inv={40}(max{41}) | hammer hits={42} captures(incl.)={43} total={44:0.00}ms max={45:0.00}ms | gc0=+{46} gc1=+{47} gc2=+{48} (CPU timings are execution time; frame intervals include render/GC gaps outside timers. Do not sum inclusive timings.)", windowSeconds, _frames, num, _frameMsMax, _maxFrameHadEnum ? "yes" : "no", _spikes, _spikesWithEnum, _enumFrames, _enumCounts[0], _enumMsTotal[0], _enumMsMax[0], _enumReturnedTotal[0], _enumReturnedMax[0], _enumCounts[1], _enumMsTotal[1], _enumMsMax[1], _enumReturnedTotal[1], _enumReturnedMax[1], _enumCounts[2], _enumMsTotal[2], _enumMsMax[2], _enumReturnedTotal[2], _enumReturnedMax[2], _presencePolls, _presenceMsTotal, _presenceMsMax, _presenceTrue, _presenceFalse, _presenceFailed, _discoveryHits, _discoveryHitMsTotal, _discoveryHitMsMax, _discoveryScans, _discoveryScanMsTotal, _discoveryScanMsMax, _discoveryEligibleTotal, _discoveryEligibleMax, _craftLoads, _craftLoadMsTotal, _craftLoadMsMax, _craftInventoriesTotal, _craftInventoriesMax, _hammerHits, _hammerCaptures, _hammerCaptureMsTotal, _hammerCaptureMsMax, gc0Delta, gc1Delta, gc2Delta); summary += string.Format(CultureInfo.InvariantCulture, " | buildRows(incl.)={0} total={1:0.00}ms max={2:0.00}ms buildIcons(excl.capture)={3} total={4:0.00}ms max={5:0.00}ms (mod UI only; deferred canvas work excluded)", _buildUiCalls[0], _buildUiTotal[0], _buildUiMax[0], _buildUiCalls[1], _buildUiTotal[1], _buildUiMax[1]); Reset(); return true; } } internal static class PerformanceDiagnostics { internal static readonly PerformanceCounters Counters = new PerformanceCounters(); private static bool _hasBaseline; private static float _lastFrameTime; private static float _windowStart; private static bool _hadEnumSinceTick; private static int _gc0Start; private static int _gc1Start; private static int _gc2Start; internal static bool Enabled => Plugin.StoragePerfDiagnostics?.Value ?? false; internal static long HammerCaptures => Counters.HammerCaptures; internal static long Timestamp() { try { if (!Enabled) { return 0L; } return Stopwatch.GetTimestamp(); } catch { return 0L; } } internal static double ElapsedMs(long start) { try { return (double)(Stopwatch.GetTimestamp() - start) * 1000.0 / (double)Stopwatch.Frequency; } catch { return 0.0; } } internal static void OnBuildUi(bool rows, long start) { if (Enabled && start != 0L) { Counters.OnBuildUi(rows, ElapsedMs(start)); } } internal static void OnEnumerate(EnumerateCaller caller, long start, int returned) { if (Enabled) { Counters.OnEnumerate(caller, ElapsedMs(start), returned); _hadEnumSinceTick = true; } } internal static void OnPresence(long start, bool? result) { if (Enabled) { Counters.OnPresence(ElapsedMs(start), result); } } internal static void OnDiscoveryHit(long start) { if (Enabled) { Counters.OnDiscoveryHit(ElapsedMs(start)); } } internal static void OnDiscoveryScan(long start, int eligible) { if (Enabled) { Counters.OnDiscoveryScan(ElapsedMs(start), eligible); } } internal static void OnCraftLoad(long start, int inventories) { if (Enabled) { Counters.OnCraftLoad(ElapsedMs(start), inventories); } } internal static void OnHammerCapture(long start) { if (Enabled) { Counters.OnHammerCapture(ElapsedMs(start)); } } internal static void OnHammerHit() { if (Enabled) { Counters.OnHammerHit(); } } internal static void TickFrame() { float realtimeSinceStartup; try { realtimeSinceStartup = Time.realtimeSinceStartup; } catch { return; } if (!Enabled) { _hasBaseline = false; _lastFrameTime = realtimeSinceStartup; _windowStart = realtimeSinceStartup; _hadEnumSinceTick = false; return; } if (!_hasBaseline) { _hasBaseline = true; _lastFrameTime = realtimeSinceStartup; _windowStart = realtimeSinceStartup; _hadEnumSinceTick = false; _gc0Start = GC.CollectionCount(0); _gc1Start = GC.CollectionCount(1); _gc2Start = GC.CollectionCount(2); Counters.Reset(); return; } double num = (double)(realtimeSinceStartup - _lastFrameTime) * 1000.0; _lastFrameTime = realtimeSinceStartup; bool hadEnumSinceTick = _hadEnumSinceTick; _hadEnumSinceTick = false; if (num < 0.0) { num = 0.0; } Counters.TickFrame(num, hadEnumSinceTick); if (num > 50.0) { Plugin.Log.LogInfo((object)string.Format("[PerfDiag] slow frame {0:0.0}ms enum={1}.", num, hadEnumSinceTick ? "yes" : "no")); } double num2 = realtimeSinceStartup - _windowStart; if (num2 >= 5.0) { int num3 = GC.CollectionCount(0); int num4 = GC.CollectionCount(1); int num5 = GC.CollectionCount(2); if (Counters.TryCloseWindow(num2, num3 - _gc0Start, num4 - _gc1Start, num5 - _gc2Start, out string summary)) { Plugin.Log.LogInfo((object)summary); } _windowStart = realtimeSinceStartup; _gc0Start = num3; _gc1Start = num4; _gc2Start = num5; } } } internal sealed class BuildMaterialTotals { private readonly Dictionary<string, int> _items = new Dictionary<string, int>(StringComparer.Ordinal); internal int Count(string name) { if (!_items.TryGetValue(name, out var value)) { return 0; } return value; } internal void Add(string name, int amount) { _items[name] = StorageRules.SaturatingAdd(Count(name), amount); } internal void AddStorage(BuildMaterialTotals chest, Func<string, int> reserveFor) { foreach (KeyValuePair<string, int> item in chest._items) { Add(item.Key, StorageRules.AvailableAfterReserve(item.Value, reserveFor(item.Key))); } } } internal static class BuildMenuIcons { private static readonly Color Unavailable = new Color(1f, 1f, 0f, 0.75f); private static HammerMaterialSnapshot? _lastSnapshot; private static bool _wasEnabled; internal static bool Enabled { get { if (Plugin.Settings.Enabled) { return Plugin.Settings.BuildingFromStorage; } return false; } } internal static void Refresh(BuildUi ui, bool force = false) { //IL_03f5: Unknown result type (might be due to invalid IL or missing references) //IL_03a5: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_02dc: Unknown result type (might be due to invalid IL or missing references) //IL_02d5: Unknown result type (might be due to invalid IL or missing references) //IL_02e1: Unknown result type (might be due to invalid IL or missing references) //IL_030e: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) if (!force && !((Component)ui).gameObject.activeInHierarchy) { return; } bool enabled = Enabled; if (!force && !enabled && !_wasEnabled) { return; } bool flag = _wasEnabled && !enabled; _wasEnabled = enabled; if (!enabled) { _lastSnapshot = null; } if (!enabled) { if (!flag || !(PatchContracts.BuildPieceButtonsField?.GetValue(ui) is List<BuildUiPieceButton> list)) { return; } object? obj = PatchContracts.BuildSpecialPieceButtonField?.GetValue(ui); BuildUiPieceButton val = (BuildUiPieceButton)((obj is BuildUiPieceButton) ? obj : null); foreach (BuildUiPieceButton item2 in list) { item2.UpdateRequirements(); } if ((Object)(object)val != (Object)null) { val.UpdateRequirements(); } return; } List<BuildUiPieceButton> list2 = null; List<BuildUiPieceButton> list3 = null; BuildUiPieceButton val2 = null; long start = 0L; try { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { _lastSnapshot = null; return; } HammerMaterialSnapshot orRefresh = HammerMaterialReader.GetOrRefresh(localPlayer, Plugin.Settings); if ((!force && orRefresh == _lastSnapshot) || !(PatchContracts.BuildPieceButtonsField?.GetValue(ui) is List<BuildUiPieceButton> list4)) { return; } list3 = list4; object? obj2 = PatchContracts.BuildSpecialPieceButtonField?.GetValue(ui); val2 = (BuildUiPieceButton)((obj2 is BuildUiPieceButton) ? obj2 : null); start = PerformanceDiagnostics.Timestamp(); _lastSnapshot = orRefresh; list2 = new List<BuildUiPieceButton>(list4); if ((Object)(object)val2 != (Object)null) { list2.Add(val2); } bool flag2 = Plugin.DebugLogging?.Value ?? false; long start2 = (flag2 ? Stopwatch.GetTimestamp() : 0); Vector3 position = ((Component)localPlayer).transform.position; Dictionary<string, CraftingStation> dictionary = new Dictionary<string, CraftingStation>(); List<(Image, Piece, CraftingStation, bool)> list5 = new List<(Image, Piece, CraftingStation, bool)>(); foreach (BuildUiPieceButton item3 in list2) { Piece piece = item3.Piece; if ((Object)(object)piece == (Object)null) { continue; } object? obj3 = PatchContracts.BuildPieceIconField?.GetValue(item3); Image val3 = (Image)((obj3 is Image) ? obj3 : null); if (val3 == null) { continue; } CraftingStation value = null; if ((Object)(object)piece.m_craftingStation != (Object)null) { string name = piece.m_craftingStation.m_name; if (!dictionary.TryGetValue(name, out value)) { value = CraftingStation.HaveBuildStationInRange(name, position); dictionary.Add(name, value); } } bool item = (Object)(object)piece.m_craftingStation == (Object)null || (Object)(object)value != (Object)null; list5.Add((val3, piece, value, item)); } BuildMaterialTotals carried = ReadInventory(((Humanoid)localPlayer).GetInventory()); Color[] array = (Color[])(object)new Color[list5.Count]; for (int i = 0; i < list5.Count; i++) { (Image, Piece, CraftingStation, bool) tuple = list5[i]; array[i] = (CanBuild(tuple.Item2, tuple.Item3, carried, orRefresh, tuple.Item4) ? Color.white : Unavailable); } for (int j = 0; j < list5.Count; j++) { ((Graphic)list5[j].Item1).color = array[j]; } if (flag2) { Plugin.Debug($"Hammer batch: {list5.Count} icons, snapshot available={orRefresh.Available}, {PerformanceDiagnostics.ElapsedMs(start2):0.00} ms."); } } catch (Exception ex) { if (list2 != null || list3 != null) { foreach (BuildUiPieceButton item4 in list2 ?? list3) { object? obj4 = PatchContracts.BuildPieceIconField?.GetValue(item4); Image val4 = (Image)((obj4 is Image) ? obj4 : null); if (val4 != null) { ((Graphic)val4).color = Color.white; } } } if (list2 == null && (Object)(object)val2 != (Object)null) { object? obj5 = PatchContracts.BuildPieceIconField?.GetValue(val2); Image val5 = (Image)((obj5 is Image) ? obj5 : null); if (val5 != null) { ((Graphic)val5).color = Color.white; } } Plugin.Debug("Could not refresh Hammer batch: " + ex.GetType().Name + ": " + ex.Message); } finally { PerformanceDiagnostics.OnBuildUi(rows: false, start); } } private static BuildMaterialTotals ReadInventory(Inventory inventory) { BuildMaterialTotals buildMaterialTotals = new BuildMaterialTotals(); foreach (ItemData allItem in inventory.GetAllItems()) { if (allItem.m_worldLevel >= Game.m_worldLevel) { buildMaterialTotals.Add(allItem.m_shared.m_name, allItem.m_stack); } } return buildMaterialTotals; } private static bool CanBuild(Piece piece, CraftingStation? station, BuildMaterialTotals carried, HammerMaterialSnapshot storage, bool hasOrigin) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)piece.m_craftingStation != (Object)null && (Object)(object)station == (Object)null && !ZoneSystem.instance.GetGlobalKey((GlobalKeys)27)) { return false; } if (piece.m_dlc.Length > 0 && !DLCMan.instance.IsDLCInstalled(piece.m_dlc)) { return false; } if (ZoneSystem.instance.GetGlobalKey(piece.FreeBuildKey())) { return true; } if (!storage.Available) { return true; } Requirement[] resources = piece.m_resources; foreach (Requirement val in resources) { if (!((Object)(object)val.m_resItem == (Object)null) && val.m_amount > 0) { string name = val.m_resItem.m_itemData.m_shared.m_name; int right = (hasOrigin ? storage.Count(name) : 0); if (StorageRules.SaturatingAdd(carried.Count(name), right) < val.m_amount) { return false; } } } return true; } } internal static class BuildRequirementColors { internal static void Refresh(Hud hud, MaterialLinkContext? context) { //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) if (context == null || context.Mode != LinkMode.BuildDisplay || context.Piece?.m_resources == null || context.PlayerInventory == null || hud?.m_requirementItems == null) { return; } int num = Math.Min(context.Piece.m_resources.Length, hud.m_requirementItems.Length); HammerMaterialSnapshot displaySnapshot = context.DisplaySnapshot; if (!displaySnapshot.Available) { return; } for (int i = 0; i < num; i++) { Requirement val = context.Piece.m_resources[i]; if (!MaterialRequirements.TryGetRequirementName(val, out string itemName)) { continue; } int amount = val.GetAmount(0); int left; using (MaterialLinkScope.Enter(null)) { left = context.PlayerInventory.CountItems(itemName, -1, true); } if (StorageRules.SaturatingAdd(left, displaySnapshot.Count(itemName)) >= amount) { GameObject obj = hud.m_requirementItems[i]; object obj2; if (obj == null) { obj2 = null; } else { Transform obj3 = obj.transform.Find("res_amount"); obj2 = ((obj3 != null) ? ((Component)obj3).GetComponent<TMP_Text>() : null); } TMP_Text val2 = (TMP_Text)obj2; object obj4; if (obj == null) { obj4 = null; } else { Transform obj5 = obj.transform.Find("res_icon"); obj4 = ((obj5 != null) ? ((Component)obj5).GetComponent<Image>() : null); } Image val3 = (Image)obj4; if ((Object)(object)val2 != (Object)null) { ((Graphic)val2).color = Color.white; } if ((Object)(object)val3 != (Object)null) { ((Behaviour)val3).enabled = true; ((Graphic)val3).color = Color.white; } } } } } internal static class HammerMaterialReader { private static readonly HammerSnapshotCache _cache = new HammerSnapshotCache(); private static Player? _player; internal static HammerMaterialSnapshot GetOrRefresh(Player player, RuntimeSettings settings) { float now = Time.unscaledTime; if ((Object)(object)player == (Object)null) { return HammerMaterialSnapshot.Unavailable(now); } if (settings == null || !settings.Enabled || !settings.BuildingFromStorage) { return HammerMaterialSnapshot.Unavailable(now); } if (player != _player) { _cache.Clear(); _player = player; } long sessionEpoch = ContainerAccessService.SessionEpoch; int playerId = (int)player.GetPlayerID(); int worldLevel = Game.m_worldLevel; bool diagOn = PerformanceDiagnostics.Enabled; long num = (diagOn ? PerformanceDiagnostics.HammerCaptures : 0); HammerMaterialSnapshot? result = _cache.GetOrRefresh(now, sessionEpoch, playerId, settings, worldLevel, delegate { long start = (diagOn ? PerformanceDiagnostics.Timestamp() : 0); try { HammerMaterialSnapshot result2 = Capture(player, settings, now); if (diagOn) { PerformanceDiagnostics.OnHammerCapture(start); } return result2; } catch (Exception ex) { if (diagOn) { PerformanceDiagnostics.OnHammerCapture(start); } Plugin.Debug("Hammer snapshot capture failed: " + ex.GetType().Name + ": " + ex.Message); return HammerMaterialSnapshot.Unavailable(now); } }) ?? HammerMaterialSnapshot.Unavailable(now); if (diagOn && PerformanceDiagnostics.HammerCaptures == num) { PerformanceDiagnostics.OnHammerHit(); } return result; } internal static void Invalidate() { _cache.Invalidate(); } internal static void Clear() { _cache.Clear(); _player = null; } private static HammerMaterialSnapshot Capture(Player player, RuntimeSettings settings, float sampleTime) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: 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) //IL_0061: Unknown result type (might be due to invalid IL or missing references) Vector3 position = ((Component)player).transform.position; float buildingDistance = settings.BuildingDistance; float num = buildingDistance * buildingDistance; BuildMaterialTotals buildMaterialTotals = new BuildMaterialTotals(); foreach (Container item in PerformanceDiagnostics.Enabled ? ContainerDiscovery.EnumerateTimed(EnumerateCaller.Capture) : ContainerDiscovery.EnumerateLoaded()) { if ((Object)(object)item == (Object)null) { continue; } Vector3 val = ((Component)item).transform.position - position; if (((Vector3)(ref val)).sqrMagnitude > num || !ContainerEligibility.Check(player, item, settings, out ZNetView _, null, advisory: true)) { continue; } Inventory val2 = ContainerApi.LoadInventory(item); if (val2 == null) { continue; } BuildMaterialTotals buildMaterialTotals2 = new BuildMaterialTotals(); foreach (ItemData allItem in val2.GetAllItems()) { if (allItem?.m_shared != null && allItem.m_worldLevel >= Game.m_worldLevel) { buildMaterialTotals2.Add(allItem.m_shared.m_name, allItem.m_stack); } } buildMaterialTotals.AddStorage(buildMaterialTotals2, settings.ReserveFor); } return HammerMaterialSnapshot.FromTotals(buildMaterialTotals, sampleTime); } } internal sealed class HammerMaterialSnapshot { private readonly BuildMaterialTotals _totals; internal bool Available { get; } internal float SampleTime { get; } private HammerMaterialSnapshot(BuildMaterialTotals totals, bool available, float sampleTime) { _totals = totals; Available = available; SampleTime = sampleTime; } internal static HammerMaterialSnapshot FromTotals(BuildMaterialTotals totals, float sampleTime) { return new HammerMaterialSnapshot(totals, available: true, sampleTime); } internal static HammerMaterialSnapshot Unavailable(float sampleTime) { return new HammerMaterialSnapshot(new BuildMaterialTotals(), available: false, sampleTime); } internal int Count(string name) { if (!Available) { return 0; } return _totals.Count(name); } } internal sealed class HammerSnapshotCache { internal const float TimeToLive = 0.25f; private HammerMaterialSnapshot? _current; private float _expiry; private long _epoch; private int _player; private object? _settings; private int _world; private bool _invalidated = true; internal HammerMaterialSnapshot? GetOrRefresh(float now, long sessionEpoch, int playerId, object? settingsToken, int worldLevel, Func<HammerMaterialSnapshot?> capture) { if (!_invalidated && _current != null && _epoch == sessionEpoch && _player == playerId && _settings == settingsToken && _world == worldLevel && now < _expiry) { return _current; } HammerMaterialSnapshot hammerMaterialSnapshot = capture(); if (hammerMaterialSnapshot == null) { if (_epoch != sessionEpoch || _player != playerId || _settings != settingsToken || _world != worldLevel) { return null; } return _current; } _current = hammerMaterialSnapshot; _expiry = now + 0.25f; _epoch = sessionEpoch; _player = playerId; _settings = settingsToken; _world = worldLevel; _invalidated = false; return hammerMaterialSnapshot; } internal void Invalidate() { _invalidated = true; _current = null; } internal void Clear() { Invalidate(); } } internal static class BuildActionRules { internal const float PlacementDistanceTolerance = 0.1f; internal const float PlacementAngleTolerance = 1f; internal const float StationDisplacementTolerance = 0.1f; internal static bool SamePieceName(string? ghostPieceName, string? expectedPieceName) { if (!string.IsNullOrEmpty(expectedPieceName)) { return ghostPieceName == expectedPieceName; } return false; } internal static bool SamePlacement(bool sameGhost, bool samePrefab, bool samePiece, float distanceSquared, float angle) { if ((sameGhost || (samePrefab && samePiece)) && distanceSquared >= 0f && distanceSquared <= 0.010000001f && angle >= 0f) { return angle <= 1f; } return false; } internal static bool RejectUnmatchedStorageCheck(bool actionMatchesPlayer, bool linkedCheck, bool outerMatchesPiece, bool carriedCovers, bool freeBuild) { if (actionMatchesPlayer && linkedCheck && !outerMatchesPiece && !carriedCovers) { return !freeBuild; } return false; } internal static bool IsWithinRange(float distanceSquared, float radius) { return distanceSquared <= radius * radius; } internal static bool SourceEligible(float originDistanceSquared, float? livePlayerDistanceSquared, float radius, bool usesLivePlayerRange) { if (IsWithinRange(originDistanceSquared, radius)) { if (usesLivePlayerRange) { if (livePlayerDistanceSquared.HasValue) { return IsWithinRange(livePlayerDistanceSquared.Value, radius); } return false; } return true; } return false; } internal static bool IsPlayerDisplaced(float distanceSquared, float radius) { return distanceSquared > radius * radius; } internal static bool IsStationDisplaced(float distanceSquared) { return distanceSquared > 0.010000001f; } } internal sealed class BuildPaymentOperation : PaymentOperation { private readonly Piece _piece; private readonly ItemData _tool; private readonly Vector3 _position; private readonly Quaternion _rotation; private readonly GameObject? _ghost; private readonly string? _ghostName; private readonly string? _pieceName; internal override StorageOperationKind Kind => StorageOperationKind.Build; internal override bool UsesLivePlayerRange => true; internal BuildPaymentOperation(MaterialLinkContext context) : base(context, 0, 1) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: 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_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) _piece = context.Piece; _tool = (ItemData)PaymentGameAccess.Invoke(Player, "GetRightItem"); _ghost = PaymentGameAccess.ReadField<GameObject>(Player, "m_placementGhost"); _ghostName = (((Object)(object)_ghost != (Object)null) ? ((Object)_ghost).name : null); _pieceName = _piece.m_name; _position = (((Object)(object)_ghost != (Object)null) ? _ghost.transform.position : Vector3.zero); _rotation = (((Object)(object)_ghost != (Object)null) ? _ghost.transform.rotation : Quaternion.identity); } internal bool SamePlacement(Player player, string phase = "pending", bool logAccepted = false) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0052: 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) //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_0251: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Unknown result type (might be due to invalid IL or missing references) GameObject val = PaymentGameAccess.ReadField<GameObject>(player, "m_placementGhost"); float num; Vector3 val2; if (!((Object)(object)val != (Object)null)) { num = float.PositiveInfinity; } else { val2 = val.transform.position - _position; num = ((Vector3)(ref val2)).sqrMagnitude; } float num2 = num; float num3 = (((Object)(object)val != (Object)null) ? Quaternion.Angle(val.transform.rotation, _rotation) : float.PositiveInfinity); bool flag = (Object)(object)val != (Object)null && (Object)(object)val == (Object)(object)_ghost; bool flag2 = (Object)(object)val != (Object)null && _ghostName != null && ((Object)val).name == _ghostName; bool flag3 = SamePieceComponent(val); bool flag4 = (Object)(object)player == (Object)(object)Player && BuildActionRules.SamePlacement(flag, flag2, flag3, num2, num3); if (!flag4 || logAccepted) { ConfigEntry<bool>? debugLogging = Plugin.DebugLogging; if (debugLogging != null && debugLogging.Value) { string[] obj = new string[18] { $"Storage {Id}: placement {phase} piece={((Object)_piece).name} accepted={flag4} sameGhost={flag} samePrefab={flag2} samePiece={flag3} ", "ghost=", ((Object)(object)val != (Object)null) ? ((Object)val).name : "missing", " expected=", _ghostName ?? "missing", " ghostPiece=", ((Object)(object)val != (Object)null) ? GhostPieceName(val) : "missing", " expectedPiece=", _pieceName ?? "missing", " ", $"distance={Mathf.Sqrt(num2):F4}m angle={num3:F3}deg ", $"limit={0.1f:F2}m/{1f:F1}deg ", "from=", null, null, null, null, null }; val2 = _position; obj[13] = ((Vector3)(ref val2)).ToString("F3"); obj[14] = " to="; object obj2; if (!((Object)(object)val != (Object)null)) { obj2 = "missing"; } else { val2 = val.transform.position; obj2 = ((Vector3)(ref val2)).ToString("F3"); } obj[15] = (string)obj2; obj[16] = " "; obj[17] = string.Format("status={0}.", PaymentGameAccess.ReadField<object>(player, "m_placementStatus")); Plugin.Debug(string.Concat(obj)); } } return flag4; } private bool SamePieceComponent(GameObject? ghost) { if ((Object)(object)ghost == (Object)null || (Object)(object)_piece == (Object)null) { return false; } try { Piece component = ghost.GetComponent<Piece>(); if (component == null || (Object)(object)component == (Object)null) { return false; } if (component == _piece || (Object)(object)component == (Object)(object)_piece) { return true; } return BuildActionRules.SamePieceName(component.m_name, _pieceName); } catch { return false; } } private static string GhostPieceName(GameObject ghost) { try { Piece component = ghost.GetComponent<Piece>(); return (component != null && (Object)(object)component != (Object)null) ? component.m_name : "missing"; } catch { return "unavailable"; } } internal bool CheckPlacement(Player player, Piece piece) { if (SamePlacement(player, "recalculated", logAccepted: true) && (Object)(object)piece == (Object)(object)_piece) { return true; } Rejected = "cancelled"; return false; } internal override string? InvalidReason() { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Invalid comparison between Unknown and O object obj = base.InvalidReason(); if (obj == null) { if (((Character)Player).InPlaceMode() && !((Object)(object)MaterialLinkContextFactory.SelectedPiece(Player) != (Object)(object)_piece) && (object)(ItemData)PaymentGameAccess.Invoke(Player, "GetRightItem") == _tool && SamePlacement(Player) && !Hud.IsPieceSelectionVisible() && !InventoryGui.IsVisible() && !Menu.IsVisible() && ((Character)Player).HaveStamina(_tool.m_shared.m_attack.m_attackStamina) && (!_tool.m_shared.m_useDurability || !(_tool.m_durability <= 0f))) { return null; } obj = "cancelled"; } return (string?)obj; } protected override void Execute() { using (MaterialLinkScope.Enter(Context)) { using PlacementActionScope placementActionScope = PlacementActionScope.Enter(Player); placementActionScope.CheckedPiece = _piece; if (!Player.HaveRequirements(_piece, (RequirementMode)0) || !Debit()) { if (Rejected == null) { Rejected = "action changed"; } return; } try { if (!Player.TryPlacePiece(_piece)) { if (Rejected == null) { Rejected = "placement rejected"; } Plugin.Debug(string.Format("Storage {0}: build refused piece={1} reason={2} status={3}.", Id, ((Object)_piece).name, Rejected, PaymentGameAccess.ReadField<object>(Player, "m_placementStatus"))); } else { Consumed = true; CompleteBuild(); } } catch { if (Consumed) { Plugin.Log.LogError((object)$"Storage {Id}: placement output started before an exception; payment retained, world effects were not rolled back."); } throw; } } } private void CompleteBuild() { //IL_0066: 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) //IL_0078: Invalid comparison between Unknown and I4 //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0173: 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_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Invalid comparison between Unknown and I4 Hud.instance.m_buildUi.AddRecentPiece(Player.GetSelectedPiece()); PaymentGameAccess.WriteField(Player, "m_lastToolUseTime", Time.time); ((Character)Player).UseStamina((float)PaymentGameAccess.Invoke(Player, "GetBuildStamina")); PieceTable buildTool = Player.GetBuildTool(); if ((int)buildTool.m_skill != 0) { if ((int)buildTool.m_skill == 107) { Game.instance.IncrementPlayerStat((PlayerStatType)168, 1f, false); } int num = PaymentGameAccess.ReadField<int>(Player, "m_buildRemoveDebt"); if (num > 0) { PaymentGameAccess.WriteField(Player, "m_buildRemoveDebt", num - 1); } else { ((Character)Player).RaiseSkill(buildTool.m_skill, 1f); if ((int)buildTool.m_skill == 107) { Game.instance.IncrementPlayerStat((PlayerStatType)169, 1f, false); } } } if (_tool.m_shared.m_useDurability) { ItemData tool = _tool; tool.m_durability -= (float)PaymentGameAccess.Invoke(Player, "GetPlaceDurability", _tool) * Game.m_durabilityRate; } _tool.m_shared.m_buildEffect.Create(((Component)Player).transform.position, Quaternion.identity, (Transform)null, 1f, -1, ((Character)Player).GetZDOID()); } } internal sealed class PlacementActionScope : IDisposable { [ThreadStatic] private static PlacementActionScope? _current; private readonly PlacementActionScope? _previous; private bool _disposed; internal Player? Player { get; } internal Piece? CheckedPiece { get; set; } internal static PlacementActionScope? Current => _current; private PlacementActionScope(Player? player) { _previous = _current; Player = player; _current = this; } internal static PlacementActionScope Enter(Player? player) { return new PlacementActionScope(player); } public void Dispose() { if (!_disposed) { _disposed = true; _current = _previous; } } } internal sealed class PlacementState : IDisposable { internal PlacementActionScope? Action { get; } internal MaterialLinkScope? Link { get; } internal PlacementState(PlacementActionScope? action, MaterialLinkScope? link) { Action = action; Link = link; } public void Dispose() { Link?.Dispose(); Action?.Dispose(); } } internal sealed class CraftPaymentOperation : PaymentOperation { private readonly InventoryGui _gui; private readonly Recipe _recipe; private readonly ItemData? _upgrade; private readonly int _quality; private readonly int _quantity; private readonly int _variant; private readonly CraftingStation? _station; internal override StorageOperationKind Kind => StorageOperationKind.Craft; internal CraftPaymentOperation(MaterialLinkContext context, InventoryGui gui) : base(context, (PaymentGameAccess.ReadField<ItemData>(gui, "m_craftUpgradeItem")?.m_quality ?? 0) + 1, (!PaymentGameAccess.ReadField<bool>(gui, "m_multiCrafting")) ? 1 : PaymentGameAccess.ReadField<int>(gui, "m_multiCraftAmount")) { _gui = gui; _recipe = context.Recipe; _station = context.Station; _upgrade = PaymentGameAccess.ReadField<ItemData>(gui, "m_craftUpgradeItem"); _quality = _upgrade?.m_quality ?? 0; _quantity = ((!PaymentGameAccess.ReadField<bool>(gui, "m_multiCrafting")) ? 1 : PaymentGameAccess.ReadField<int>(gui, "m_multiCraftAmount")); _variant = PaymentGameAccess.ReadField<int>(gui, "m_craftVariant"); } internal override string? InvalidReason() { object obj = base.InvalidReason(); if (obj == null) { if (!((Object)(object)_gui == (Object)null) && InventoryGui.IsVisible() && !((Object)(object)Player.GetCurrentCraftingStation() != (Object)(object)_station) && !((Object)(object)PaymentGameAccess.ReadField<Recipe>(_gui, "m_craftRecipe") != (Object)(object)_recipe) && !((Object)(object)MaterialLinkContextFactory.ForSelectedRecipe(Player, LinkMode.Consumption)?.Recipe != (Object)(object)_recipe) && PaymentGameAccess.ReadField<ItemData>(_gui, "m_craftUpgradeItem") == _upgrade && (_upgrade == null || (((Humanoid)Player).GetInventory().ContainsItem(_upgrade) && _upgrade.m_quality == _quality)) && ((!PaymentGameAccess.ReadField<bool>(_gui, "m_multiCrafting")) ? 1 : PaymentGameAccess.ReadField<int>(_gui, "m_multiCraftAmount")) == _quantity && PaymentGameAccess.ReadField<int>(_gui, "m_craftVariant") == _variant) { return null; } obj = "cancelled"; } return (string?)obj; } protected override void Execute() { PlayerInventorySnapshot playerInventorySnapshot = new PlayerInventorySnapshot(Player); try { using (MaterialLinkScope.Enter(Context)) { PaymentGameAccess.Invoke(_gui, "DoCrafting", Player); } if (!Consumed && Debited) { PlayerRestored(); playerInventorySnapshot.Restore(); } if (!Consumed && Rejected == null) { Rejected = "craft rejected"; } } catch { Consumed = false; PlayerRestored(); playerInventorySnapshot.Restore(); throw; } } } internal sealed class PlayerInventorySnapshot { private readonly Player _player; private readonly List<(ItemData Item, int Stack, bool Equipped)> _items; internal PlayerInventorySnapshot(Player player) { _player = player; _items = (from i in ((Humanoid)player).GetInventory().GetAllItems() select (i: i, m_stack: i.m_stack, ((Humanoid)player).IsItemEquiped(i))).ToList(); } internal void Restore() { List<ItemData> allItems = ((Humanoid)_player).GetInventory().GetAllItems(); List<ItemData> list = allItems.Where((ItemData i) => !_items.Any<(ItemData, int, bool)>(((ItemData Item, int Stack, bool Equipped) x) => x.Item == i)).ToList(); allItems.Clear(); foreach (var item3 in _items) { item3.Item.m_stack = item3.Stack; allItems.Add(item3.Item); } foreach (ItemData item in list) { Attempt(delegate { ((Humanoid)_player).UnequipItem(item, true); }, "unequip"); } Attempt(delegate { PaymentGameAccess.Invoke(((Humanoid)_player).GetInventory(), "Changed", false, false); }, "change notification"); foreach (var item2 in _items) { if (item2.Equipped && !((Humanoid)_player).IsItemEquiped(item2.Item)) { Attempt(delegate { ((Humanoid)_player).EquipItem(item2.Item, false); }, "re-equip"); } } } private static void Attempt(Action action, string step) { try { action(); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Inventory restore " + step + " failed: " + ex.GetType().Name + ": " + ex.Message)); } } } internal static class DepositAction { internal static bool IsRunning { get { StorageOperation? current = ContainerAccessService.Current; if (current == null) { return false; } return current.Kind == StorageOperationKind.Deposit; } } internal static void Run() { Player localPlayer = Player.m_localPlayer; InventoryGui instance = InventoryGui.instance; if (!ContainerAccessService.Busy && Plugin.Settings.Enabled && Plugin.Settings.DepositEnabled && !((Object)(object)localPlayer == (Object)null) && !((Object)(object)instance == (Object)null) && InventoryGui.IsVisible() && !instance.IsContainerOpen() && !((Character)localPlayer).IsDead() && !((Character)localPlayer).IsTeleporting()) { ContainerAccessService.Start(new DepositOperation(localPlayer, instance)); DepositButton.RefreshOperationState(); } } } internal static class DepositButton { [CompilerGenerated] private static class <>O { public static UnityAction <0>__Run; } private static readonly Color DisabledFrameColor = new Color(0.566f, 0.566f, 0.566f, 0.502f); private static Button? _button; private static TMP_Text? _label; private static bool _manualLabelTint; private static Color _labelColor = Color.white; private static RectTransform? _rect; private static RectTransform? _panel; private static RectTransform? _stackRect; private static readonly Vector3[] _corners = (Vector3[])(object)new Vector3[4]; private static Vector2 _baseSize; private static float _padding; private static float _desiredWidth; private static float _lastPanelWidth; private static bool _baselineMeasured; private static bool _labelDirty; private static bool _layoutPending; private static float _nextRangeCheck; private static bool _nearby; private const float FallbackRightOffset = -1.3f; private const float MaxOverhang = 5f; private const float MaxInset = -50f; private const float VerticalOffset = -10f; internal static void RefreshOperationState() { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_button == (Object)null)) { bool flag = DepositRules.CanActivate(_nearby, ContainerAccessService.Busy); ((Selectable)_button).interactable = flag; if (_manualLabelTint && (Object)(object)_label != (Object)null) { ((Graphic)_label).color = (flag ? _labelColor : Color.grey); } } } private static void TryAlignRightOffset(InventoryGui? gui) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: 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) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00db: 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_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_rect == (Object)null || (Object)(object)_panel == (Object)null || (Object)(object)gui == (Object)null) { return; } Rect rect = _panel.rect; if (!DepositLabel.IsPositiveFinite(((Rect)(ref rect)).width)) { return; } if ((Object)(object)_stackRect == (Object)null && (Object)(object)gui.m_stackAllButton != (Object)null) { _stackRect = ((Component)gui.m_stackAllButton).GetComponent<RectTransform>(); } if ((Object)(object)_stackRect == (Object)null) { return; } rect = _stackRect.rect; if (!DepositLabel.IsPositiveFinite(((Rect)(ref rect)).width)) { return; } rect = _stackRect.rect; if (DepositLabel.IsPositiveFinite(((Rect)(ref rect)).height)) { _stackRect.GetWorldCorners(_corners); float x = ((Transform)_panel).InverseTransformPoint(_corners[2]).x; rect = _panel.rect; float num = x - ((Rect)(ref rect)).xMax; if (num <= 5f && num >= -50f && (!(Mathf.Abs(_rect.anchoredPosition.x - num) < 0.01f) || _rect.anchoredPosition.y != -10f)) { _rect.anchoredPosition = new Vector2(num, -10f); Plugin.Debug($"Store Nearby: aligned right offset to Place Stacks ({num:0.##} UI units)."); } } } internal static void Create(InventoryGui gui) { //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: 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_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Expected O, but got Unknown //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Unknown result type (might be due to invalid IL or missing references) //IL_0222: Expected O, but got Unknown Destroy(); Button val = (((Object)(object)gui.m_takeAllButton != (Object)null) ? gui.m_takeAllButton : gui.m_stackAllButton); if ((Object)(object)val == (Object)null || (Object)(object)gui.m_player == (Object)null) { Plugin.Log.LogWarning((object)"Could not create the Deposit button because the inventory controls were unavailable."); return; } GameObject val2 = Object.Instantiate<GameObject>(((Component)val).gameObject, (Transform)(object)gui.m_player, false); ((Object)val2).name = "LCFSP_StoreNearbyButton"; _button = val2.GetComponent<Button>(); if ((Object)(object)_button == (Object)null) { Object.Destroy((Object)(object)val2); Plugin.Log.LogWarning((object)"Could not create the Deposit button."); return; } RectTransform? obj = (_rect = val2.GetComponent<RectTransform>()); _panel = gui.m_player; Rect rect = obj.rect; _baseSize = ((Rect)(ref rect)).size; obj.anchorMin = new Vector2(1f, 0f); obj.anchorMax = new Vector2(1f, 0f); obj.pivot = new Vector2(1f, 1f); obj.anchoredPosition = new Vector2(-1.3f, -10f); ((Transform)obj).localScale = Vector3.one; TryAlignRightOffset(gui); Localize[] componentsInChildren = val2.GetComponentsInChildren<Localize>(true); for (int i = 0; i < componentsInChildren.Length; i++) { ((Behaviour)componentsInChildren[i]).enabled = false; } _label = val2.GetComponentInChildren<TMP_Text>(true); if ((Object)(object)_label != (Object)null) { float fontSize = DepositLabel.FixedFontSize(_label.enableAutoSizing, _label.fontSize, _label.fontSizeMax); _label.richText = false; _label.parseCtrlCharacters = false; _label.textWrappingMode = (TextWrappingModes)3; _label.enableAutoSizing = false; _label.fontSize = fontSize; _label.overflowMode = (TextOverflowModes)1; } else { Plugin.Log.LogWarning((object)"Store Nearby button has no TMP label; custom text and sizing are unavailable."); } ApplyDisabledStyle(val2, _button, _label); RefreshLabel(); _button.onClick = new ButtonClickedEvent(); ButtonClickedEvent onClick = _button.onClick; object obj2 = <>O.<0>__Run; if (obj2 == null) { UnityAction val3 = DepositAction.Run; <>O.<0>__Run = val3; obj2 = (object)val3; } ((UnityEvent)onClick).AddListener((UnityAction)obj2); RefreshVisibility(gui); } private static void ApplyDisabledStyle(GameObject clone, Button button, TMP_Text? label) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) ButtonTextColor component = clone.GetComponent<ButtonTextColor>(); if ((Object)(object)component != (Object)null) { component.m_disabledColor = Color.grey; } else if ((Object)(object)label != (Object)null) { _manualLabelTint = true; _labelColor = ((Graphic)label).color; } if ((Object)(object)((Selectable)button).targetGraphic == (Object)null) { ((Selectable)button).targetGraphic = (Graphic)(object)(clone.GetComponent<Image>() ?? clone.GetComponentInChildren<Image>(true)); } if ((Object)(object)((Selectable)button).targetGraphic != (Object)null) { if ((int)((Selectable)button).transition == 0) { ((Selectable)button).transition = (Transition)1; } ColorBlock colors = ((Selectable)button).colors; ((ColorBlock)(ref colors)).disabledColor = DisabledFrameColor; ((Selectable)button).colors = colors; } } internal static void RefreshLabel() { if (!((Object)(object)_label == (Object)null)) { string depositButtonLabel = Plugin.Settings.DepositButtonLabel; if (!_baselineMeasured || !(_label.text == depositButtonLabel)) { _label.text = depositButtonLabel; _labelDirty = true; _layoutPending = true; RefreshLayout(); } } } private static void RefreshLayout(InventoryGui? gui = null) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_rect == (Object)null || (Object)(object)_panel == (Object)null || (Object)(object)_label == (Object)null) { return; } TryAlignRightOffset(gui ?? InventoryGui.instance); Rect rect = _panel.rect; float width = ((Rect)(ref rect)).width; if (!_layoutPending && width == _lastPanelWidth) { return; } if (!DepositLabel.IsPositiveFinite(_baseSize.x) || !DepositLabel.IsPositiveFinite(_baseSize.y)) { rect = _rect.rect; _baseSize = ((Rect)(ref rect)).size; } if (!DepositLabel.IsPositiveFinite(_baseSize.x) || !DepositLabel.IsPositiveFinite(_baseSize.y)) { return; } if (!DepositLabel.IsPositiveFinite(width)) { SetSize(_baseSize.x); _layoutPending = true; return; } if (_labelDirty || !_baselineMeasured) { if ((Object)(object)_label.font == (Object)null) { return; } string text = _label.text; float num = (_baselineMeasured ? 0f : _label.GetPreferredValues("Store Nearby").x); bool flag = !string.IsNullOrEmpty(text); float x = _label.GetPreferredValues(text).x; if (!_baselineMeasured) { if (!DepositLabel.IsPositiveFinite(num)) { return; } _padding = Mathf.Max(0f, _baseSize.x - num); _baselineMeasured = true; } _desiredWidth = (flag ? (x + _padding) : 0f); _labelDirty = false; } float width2; bool num2 = DepositLabel.TryFitWidth(DepositLabel.GetMinimumWidth(_baseSize.y), width, _desiredWidth, out width2); if (num2) { SetSize(width2); } _layoutPending = !num2; _lastPanelWidth = width; } private static void SetSize(float width) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0034: 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) if (!((Object)(object)_rect == (Object)null)) { Rect rect = _rect.rect; if (((Rect)(ref rect)).width != width) { _rect.SetSizeWithCurrentAnchors((Axis)0, width); } rect = _rect.rect; if (((Rect)(ref rect)).height != _baseSize.y) { _rect.SetSizeWithCurrentAnchors((Axis)1, _baseSize.y); } } } internal static void RefreshVisibility(InventoryGui? gui = null) { _nextRangeCheck = 0f; if ((Object)(object)_button != (Object)null) { if (gui == null) { gui = InventoryGui.instance; } ((Component)_button).gameObject.SetActive((Object)(object)gui != (Object)null && DepositRules.ShowButton(Plugin.Settings.Enabled && Plugin.Settings.DepositEnabled, gui.IsContainerOpen())); RefreshAvailability(gui); } } internal static void RefreshAvailability(InventoryGui? gui = null) { //IL_007a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_button == (Object)null || !((Component)_button).gameObject.activeInHierarchy || !InventoryGui.IsVisible()) { _nextRangeCheck = 0f; return; } RefreshLayout(gui); if (Time.unscaledTime < _nextRangeCheck) { return; } _nextRangeCheck = Time.unscaledTime + 1f; Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { _nearby = false; ((Selectable)_button).interactable = false; return; } bool flag = ContainerDiscovery.AnyWithinRange(((Component)localPlayer).transform.position, Plugin.Settings.DepositRange) ?? _nearby; if (flag != _nearby) { Plugin.Debug(string.Format("Store Nearby: container {0} within {1:0.#}m.", flag ? "found" : "not found", Plugin.Settings.DepositRange)); } _nearby = flag; RefreshOperationState(); } internal static void Destroy() { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: 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_004f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_button != (Object)null) { Object.Destroy((Object)(object)((Component)_button).gameObject); _button = null; } _label = null; _manualLabelTint = false; _labelColor = Color.white; _rect = null; _panel = null; _stackRect = null; _baseSize = Vector2.zero; _padding = 0f; _desiredWidth = 0f; _lastPanelWidth = 0f; _baselineMeasured = false; _labelDirty = false; _layoutPending = false; _nearby = false; _nextRangeCheck = 0f; } } internal static class DepositItemCatalogue { private static ObjectDB? _database; private static RuntimeSettings? _settings; private static float _nextCheck; internal static void Clear() { _database = null; _settings = null; _nextCheck = 0f; } internal static void Tick(RuntimeSettings settings, bool export) { if (Time.unscaledTime < _nextCheck) { return; } _nextCheck = Time.unscaledTime + 1f; if ((Object)(object)Player.m_localPlayer == (Object)null) { _database = null; return; } ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null || instance.m_items.Count == 0 || ((Object)(object)_database == (Object)(object)instance && _settings == settings)) { return; } _database = instance; _settings = settings; try { HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); List<(string, string, string, string, bool)> list = new List<(string, string, string, string, bool)>(); foreach (GameObject item in instance.m_items) { ItemDrop val = (((Object)(object)item != (Object)null) ? item.GetComponent<ItemDrop>() : null); if (!((Object)(object)val == (Object)null) && val.m_itemData?.m_shared != null) { SharedData shared = val.m_itemData.m_shared; string text = StorageRules.NormalizeItemName(shared.m_name); hashSet.Add(text); dictionary.TryGetValue(text, out var value); dictionary[text] = value + 1; list.Add((text, Localization.instance.Localize(shared.m_name), ((Object)item).name, ((object)Unsafe.As<ItemType, ItemType>(ref shared.m_itemType)/*cast due to .constrained prefix*/).ToString(), shared.m_questItem)); } } WarnUnknown("04 - Store Nearby / IncludedItems", settings.DepositIncludedNames, hashSet, allowTypeAliases: true); WarnUnknown("04 - Store Nearby / ExcludedItems", settings.DepositExcludedNames, hashSet, allowTypeAliases: false); WarnUnknownReserves("01 - General / ReserveItems", settings.Reserves, hashSet); if (!export) { return; } list.Sort(((string Key, string Name, string Prefab, string Type, bool Quest) a, (string Key, string Name, string Prefab, string Type, bool Quest) b) => StringComparer.Ordinal.Compare(a.Prefab, b.Prefab)); IReadOnlyCollection<string> excludedNames = DepositRules.ParseExcludedNames(""); IReadOnlyCollection<string> includedNames = DepositRules.ParseExcludedNames("raspberries, blueberries, cloudberries, vineberry, mushroomcommon, mushroomyellow, mushroomblue, jotunpuffs, magecap, smokepuff, pukeberries, trophies"); StringBuilder stringBuilder = new StringBuilder("# Registered deposit item catalogue\n\n"); stringBuilder.AppendLine(string.Format("Game: {0}; mod: {1}; generated UTC: {2:O}.", Version.GetVersionString(false), "1.0.0", DateTime.UtcNow)); stringBuilder.AppendLine($"Source: live ObjectDB.m_items ({instance.m_items.Count} entries; {list.Count} item rows; {hashSet.Count} distinct keys)."); stringBuilder.AppendLine("Display language: " + Cell(Localization.instance.GetSelectedLanguage()) + ". This snapshot includes installed mod items."); stringBuilder.AppendLine("Baseline means a new configuration, outside the hotbar and unequipped. Current exclusions and protections still apply."); stringBuilder.AppendLine("Internal/test/unobtainable prefabs are retained; runtime registration does not prove obtainability."); stringBuilder.AppendLine("Duplicate keys intentionally affect every matching prefab. Blank keys cannot be configured.\n"); stringBuilder.AppendLine("Installed plugins:"); foreach (PluginInfo value2 in Chainloader.PluginInfos.Values) { stringBuilder.AppendLine($"- {Cell(value2.Metadata.GUID)} {value2.Metadata.Version}"); } stringBuilder.AppendLine("\n| Config key | Display name | Prefab | Type | Default deposit | Notes |"); stringBuilder.AppendLine("|---|---|---|---|---|---|"); foreach (var item2 in list) { bool flag = DepositRules.CanDeposit(item2.Item4, equipped: false, item2.Item5, inHotbar: false, item2.Item1, excludedNames, includedNames); string text2 = ((item2.Item1.Length == 0) ? "Blank key" : ((dictionary[item2.Item1] > 1) ? "Shared key" : "")); if (item2.Item5) { text2 += " Quest protected"; } stringBuilder.AppendLine("| " + Cell(item2.Item1) + " | " + Cell(item2.Item2) + " | " + Cell(item2.Item3) + " | " + Cell(item2.Item4) + " | " + (flag ? "Yes" : "No") + " | " + text2.Trim() + " |"); } string text3 = Path.Combine(Paths.ConfigPath, "LuminairPrime.valheim.localcraftfromstorageprime.deposit-items.md"); File.WriteAllText(text3, stringBuilder.ToString(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); Plugin.Log.LogInfo((object)$"Exported {list.Count} deposit item rows to {text3}."); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Item catalogue validation/export failed: " + ex.GetType().Name + ": " + ex.Message)); } } private static void WarnUnknown(string setting, IReadOnlyCollection<string> names, HashSet<string> known, bool allowTypeAliases) { List<string> list = new List<string>(); foreach (string name in names) { if (!known.Contains(name) && (!allowTypeAliases || !DepositRules.TypeAliases.ContainsKey(name))) { list.Add(name); } } if (list.Count > 0) { Plugin.Log.LogWarning((object)(setting + ": no registered item key matches " + string.Join(", ", list) + ". Entries are retained; export the item catalogue to check names.")); } } private static void WarnUnknownReserves(string setting, IReadOnlyDictionary<string, int> reserves, HashSet<string> known) { List<string> list = new List<string>(); foreach (string key in reserves.Keys) { if (key != "*" && !known.Contains(key)) { list.Add(key); } } if (list.Count > 0) { Plugin.Log.LogWarning((object)(setting + ": no registered item key matches " + string.Join(", ", list) + ". Entries are retained; export the item catalogue to check names.")); } } private static string Cell(string value) { return value.Replace("&", "&").Replace("<", "<").Replace(">", ">") .Replace("|", "|") .Replace("\r", " ") .Replace("\n", " "); } } internal static class DepositLabel { internal const string Default = "Store Nearby"; internal const int MaxTextElements = 32; internal static float FixedFontSize(bool autoSizing, float fontSize, float fontSizeMax) { if (!autoSizing || !IsPositiveFinite(fontSizeMax)) { return fontSize; } return fontSizeMax; } internal static string Normalize(string? value) { if (value == null) { return "Store Nearby"; } string text = value.Replace('\r', ' ').Replace('\n', ' ').Replace('\t', ' ') .Trim(); if (text.Length == 0) { return string.Empty; } TextElementEnumerator textElementEnumerator = StringInfo.GetTextElementEnumerator(text); int num = 0; while (textElementEnumerator.MoveNext()) { if (num++ == 32) { text = text.Substring(0, textElementEnumerator.ElementIndex).TrimEnd(); break; } } return text; } internal static bool IsPositiveFinite(float value) { if (value > 0f && !float.IsInfinity(value)) { return !float.IsNaN(value); } return false; } internal static float GetMinimumWidth(float baseHeight) { if (!IsPositiveFinite(baseHeight)) { return 0f; } return baseHeight; } internal static bool TryFitWidth(float minWidth, float panelWidth, float desiredWidth, out float width) { width = 0f; if (!IsPositiveFinite(minWidth)) { return false; } width = minWidth; if (!IsPositiveFinite(panelWidth)) { return false; } float val = panelWidth * 0.6f; float val2 = Math.Min(minWidth, val); if (float.IsNaN(desiredWidth) || float.IsInfinity(desiredWidth)) { desiredWidth = minWidth; } width = Math.Max(val2, Math.Min(desiredWidth, val)); return true; } } internal sealed class DepositOperation : StorageOperation { private readonly InventoryGui _gui; private readonly Inventory _source; private readonly List<ItemData> _items = new List<ItemData>(); private readonly DepositOutcome _outcome = new DepositOutcome(); private IReadOnlyList<NearbyContainer>? _containers; private int _index; internal override StorageOperationKind Kind => StorageOperationKind.Deposit; internal DepositOperation(Player player, InventoryGui gui) : base(player) { _gui = gui; _source = ((Humanoid)player).GetInventory(); } internal override string? InvalidReason() { object obj = base.InvalidReason(); if (obj == null) { if (!((Object)(object)_gui == (Object)null) && InventoryGui.IsVisible() && !_gui.IsContainerOpen()) { return null; } obj = "cancelled"; } return (string?)obj; } internal override bool CanUse(Container chest) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)chest != (Object)null) { Vector3 val = ((Component)chest).transform.position - ((Component)Player).transform.position; ZNetView view; if (((Vector3)(ref val)).sqrMagnitude <= Settings.DepositRange * Settings.DepositRange) { return ContainerEligibility.Check(Player, chest, Settings, out view, null, advisory: true); } } return false; } internal override void Step() { //IL_00b3: Unknown result type (might be due to invalid IL or missing references) if (PatchContracts.DragItemField.GetValue(_gui) != null) { _outcome.HeldItem = true; ContainerAccessService.Finish("held item"); return; } if (_containers == null) { foreach (ItemData allItem in _source.GetAllItems()) { if (Eligible(allItem)) { _items.Add(allItem); } } if (_items.Count == 0) { _outcome.NoItems = true; ContainerAccessService.Finish("no items"); return; } _containers = ContainerDiscovery.FindNearby(Player, ((Component)Player).transform.position, Settings.DepositRange, Settings, null, delegate(string reason) { _outcome.Unavailable++; Plugin.Debug($"Storage {Id}: skipped {reason}."); }); } while (_index < _containers.Count && DepositTransfer.HasRemaining(_source, _items)) { Container container = _containers[_index].Container; if (!CanUse(container)) { _outcome.Unavailable++; _index++; continue; } Inventory val = ContainerApi.LoadInventory(container); bool flag = false; foreach (ItemData item in _items) { if (_source.ContainsItem(item) && Eligible(item) && val != null && val.ContainsItemByName(item.m_shared.m_name)) { flag = true; } } if (!flag) { _outcome.Checked++; _index++; continue; } ContainerHold containerHold = ContainerAccessService.Acquire(this, container); if (containerHold == null) { return; } _outcome.Checked++; int moved = 0; bool changed = false; try { foreach (ItemData item2 in _items) { if (_source.ContainsItem(item2) && Eligible(item2) && containerHold.Inventory.ContainsItemByName(item2.m_shared.m_name)) { _outcome.Matched = true; if (!CanUse(container) || !containerHold.Valid()) { throw new StorageAbort("held chest changed"); } DepositTransfer.MoveIntoAvailableSpace(_source, containerHold.Inventory, item2, ref moved, ref changed); containerHold.Saved(); } } } finally { _outcome.Moved = StorageRules.SaturatingAdd(_outcome.Moved, moved); foreach (ItemData item3 in _items) { DepositTransfer.RemoveEmptySourceReference(_source, item3); } if (changed && (Object)(object)containerHold.View != (Object)null && containerHold.View.IsValid() && containerHold.View.IsOwner()) { ContainerApi.Save(container); } containerHold.Release(); Holds.Remove(containerHold); } _index++; } ContainerAccessService.Finish("completed"); } private bool Eligible(ItemData item) { if (item?.m_shared != null) { return DepositRules.CanDeposit(((object)Unsafe.As<ItemType, ItemType>(ref item.m_shared.m_itemType)/*cast due to .constrained prefix*/).ToString(), item.m_equipped || ((Humanoid)Player).IsItemEquiped(item), item.m_shared.m_questItem, item.m_gridPos.y == 0, item.m_shared.m_name, Settings.DepositExcludedNames, Settings.DepositIncludedNames); } return false; } internal override void Finish(string reason) { base.Finish(reason); _outcome.Remaining = DepositTransfer.HasRemaining(_source, _items); DepositOutcome outcome = _outcome; bool cancelled; switch (reason) { case "disconnect": case "world exit": case "plugin destroyed": case "settings changed": case "cancelled": case "manual open": case "manual open pending": case "player unavailable": case "session changed": cancelled = true; break; default: cancelled = false; break; } outcome.Cancelled = cancelled; _outcome.TimedOut = reason.Contains("timeout"); if (reason != "completed" && !_outcome.Cancelled && !_outcome.NoItems && !_outcome.HeldItem) { _outcome.Unavailable++; _outcome.RelevantAccessFailure = true; _outcome.Failed = reason == "exception"; } Plugin.Debug($"Deposit {Id}: movedUnits={_outcome.Moved}, checkedChests={_outcome.Checked}, unavailableChests={_outcome.Unavailable}, matched={_outcome.Matched}, remaining={_outcome.Remaining}, reason={reason}."); string text = _outcome.Message(); if (Settings.DepositFeedback && text != null && (Object)(object)Player == (Object)(object)Player.m_localPlayer && InventoryGui.IsVisible()) { ((Character)Player).Message((MessageType)2, text, 0, (Sprite)null, false); } } } internal sealed class DepositOutcome { internal bool Cancelled; internal bool TimedOut; internal int Moved; internal int Checked; internal int Unavailable; internal bool Matched; internal bool Remaining; internal bool RelevantAccessFailure; internal bool NoItems; internal bool HeldItem; internal bool Failed; internal string? Message() { if (Cancelled) { return null; } if (TimedOut) { if (Moved <= 0) { return "Storage did not respond. Try again."; } return "Some items were stored, but storage stopped responding."; } if (Failed) { return "Couldn't finish storing items. Check the log."; } if (HeldItem) { return "Finish moving the held item first."; } if (NoItems) { return "Nothing eligible to store."; } if (Moved > 0) { if (!Remaining || !RelevantAccessFailure) { return null; } return "Some storage was unavailable."; } if (Unavailable > 0 || Checked == 0) { return "No eligible storage."; } if (!Matched) { return "No available container holds these items."; } return "No room in matching containers."; } } internal static class DepositRules { internal const string DefaultExclusions = ""; internal const string DefaultInclusions = "raspberries, blueberries, cloudberries, vineberry, mushroomcommon, mushroomyellow, mushroomblue, jotunpuffs, magecap, smokepuff, pukeberries, trophies"; internal static readonly IReadOnlyDictionary<string, string> TypeAliases = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { { "trophies", "Trophy" }, { "trophy", "Trophy" } }; internal static bool ShowButton(bool enabled, bool containerOpen) { if (enabled) { return !containerOpen; } return false; } internal static bool CanActivate(bool present, bool running) { if (present) { return !running; } return false; } internal static IReadOnlyCollection<string> ParseExcludedNames(string? text) { HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrWhiteSpace(text)) { return hashSet; } string[] array = text.Split(','); for (int i = 0; i < array.Length; i++) { string text2 = StorageRules.NormalizeItemName(array[i]); if (text2.Length > 0) { hashSet.Add(text2); } } return hashSet; } internal static bool IsIncludedByType(string entry, string itemType) { if (TypeAliases.TryGetValue(entry, out string value)) { return string.Equals(value, itemType, StringComparison.OrdinalIgnoreCase); } return false; } internal static bool CanDeposit(string itemType, bool equipped, bool questItem, bool inHotbar, string itemName, IReadOnlyCollection<string> excludedNames, IReadOnlyCollection<string>? includedNames = null) { if (equipped || questItem || inHotbar) { return false; } string text = StorageRules.NormalizeItemName(itemName); foreach (string excludedName in excludedNames) { if (string.Equals(text, excludedName, StringComparison.OrdinalIgnoreCase)) { return false; } } if (text.Length == 0) { return false; } if (includedNames != null) { foreach (string includedName in includedNames) { if (string.Equals(text, includedName, StringComparison.OrdinalIgnoreCase)) { return true; } if (IsIncludedByType(includedName, itemType)) { return true; } } } return string.Equals(itemType, "Material", StringComparison.Ordinal); } internal static int CountMoved(int beforeStack, bool sourceContainsItem, int afterStack) { if (!sourceContainsItem) { return Math.Max(0, beforeStack); } return Math.Max(0, beforeStack - afterStack); } internal static int MoveAmount(int sourceStack, int destinationSpace) { return Math.Min(Math.Max(0, sourceStack), Math.Max(0, destinationSpace)); } } internal static class DepositTransfer { internal static void MoveIntoAvailableSpace(Inventory source, Inventory target, ItemData item, ref int moved, ref bool changed) { while (source.ContainsItem(item) && item.m_stack > 0) { ItemData val = FindCompatibleStack(target, item); int x; int y; int destinationSpace; if (val != null) { x = val.m_gridPos.x; y = val.m_gridPos.y; destinationSpace = val.m_shared.m_maxStackSize - val.m_stack; } else { if (!TryFindEmptySlot(target, out x, out y)) { break; } destinationSpace = item.m_shared.m_maxStackSize; } int num = DepositRules.MoveAmount(item.m_stack, destinationSpace); if (num != 0) { int stack = item.m_stack; bool flag; try { flag = target.MoveItemToThis(source, item, num, x, y); } catch { RecordMove(source, item, stack, ref moved, ref changed); RemoveEmptySourceReference(source, item); throw; } int num2 = RecordMove(source, item, stack, ref moved, ref changed); if (!flag || num2 == 0) { break; } continue; } break; } } private static ItemData? FindCompatibleStack(Inventory target, ItemData sourceItem) { foreach (ItemData allItem in target.GetAllItems()) { if (allItem != null && allItem.m_shared != null && allItem.IsSameType(sourceItem) && allItem.m_stack < allItem.m_shared.m_maxStackSize) { return allItem; } } return null; } private static bool TryFindEmptySlot(Inventory inventory, out int x, out int y) { for (int i = 0; i < inventory.GetHeight(); i++) { for (int j = 0; j < inventory.GetWidth(); j++) { if (inventory.GetItemAt(j, i) == null) { x = j; y = i; return true; } } } x = -1; y = -1; return false; } private static int RecordMove(Inventory source, ItemData item, int before, ref int totalMoved, ref bool changed) { int num = DepositRules.CountMoved(before, source.ContainsItem(item), item.m_stack); if (num > 0) { totalMoved = StorageRules.SaturatingAdd(totalMoved, num); changed = true; } return num; } internal static void RemoveEmptySourceReference(Inventory source, ItemData item) { if (source.ContainsItem(item) && item.m_stack <= 0) { try { source.RemoveItem(item); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not remove an empty inventory stack: " + ex.GetType().Name + ": " + ex.Message)); } } } internal static bool HasRemaining(Inventory source, IReadOnlyList<ItemData> items) { foreach (ItemData item in items) { if (source.ContainsItem(item) && item.m_stack > 0) { return true; } } return false; } } internal static class LinkedMaterialCounts { internal static bool ContextAllowsItem(MaterialLinkContext context, string itemName) { if (context.Recipe?.m_resources != null) { Requirement[] resources = context.Recipe.m_resources; foreach (Requirement val in resources) { if (MaterialRequirements.TryGetRequirementName(val, out string itemName2) && !(itemName2 != itemName) && val.m_upgraderResource == (context.Station?.m_upgrader ?? false)) { return true; } } return false; } if (context.Piece?.m_resources != null) { Requirement[] resources = context.Piece.m_resources; for (int i = 0; i < resources.Length; i++) { if (MaterialRequirements.TryGetRequirementName(resources[i], out string itemName3) && itemName3 == itemName) { return true; } } return false; } return false; } internal static int CountAvailable(MaterialLinkContext context, string itemName, int quality, bool matchWorldLevel, int stopAt = int.MaxValue) { if (!context.IsActive || (Object)(object)context.Player == (Object)null || string.IsNullOrEmpty(itemName)) { return 0; } if (context.Mode == LinkMode.BuildDisplay && quality == -1 && matchWorldLevel) { HammerMaterialSnapshot displaySnapshot = context.DisplaySnapshot; if (displaySnapshot.Available) { return Math.Min(displaySnapshot.Count(itemName), stopAt); } } return CountAvailableIn(context.CountingInventories, itemName, quality, matchWorldLevel, stopAt); } internal static IReadOnlyList<Inventory> LoadCountingInventories(MaterialLinkContext context) { long start = PerformanceDiagnostics.Timestamp(); List<Inventory> list = new List<Inventory>(); foreach (NearbyContainer item in FindNearbyContainers(context)) { Inventory val = ContainerApi.LoadInventory(item.Container); if (val != null) { list.Add(val); } } PerformanceDiagnostics.OnCraftLoad(start, list.Count); return list; } internal static int CountAvailableIn(IReadOnlyList<Inventory> inventories, string itemName, int quality, bool matchWorldLevel, int stopAt) { int num = 0; int reserve = Plugin.Settings.ReserveFor(itemName); foreach (Inventory inventory in inventories) { int itemCount = inventory.CountItems(itemName, quality, matchWorldLevel); num = StorageRules.SaturatingAdd(num, StorageRules.AvailableAfterReserve(itemCount, reserve)); if (num >= stopAt) { return stopAt; } } return num; } private static IReadOnlyList<NearbyContainer> FindNearbyContainers(MaterialLinkContext context) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) RuntimeSettings settings = Plugin.Settings; float radius = context.FeatureDistance(settings); if (context.Mode != LinkMode.Placement && (context.Mode != LinkMode.BuildRequirement || !context.RequiresFreshBuildSources)) { return ContainerDiscovery.FindNearby(context.Player, context.Origin, radius, settings, context.Station); } return ContainerDiscovery.FindForAction(context.Player, context.Origin, radius, settings, context.Station); } } internal enum LinkMode { Disabled, RequirementCheck, RequirementDisplay, BuildRequirement, BuildDisplay, Consumption, Placement } internal sealed class MaterialLinkContext { private IReadOnlyList<Inventory>? _countingInventories; private HammerMaterialSnapshot? _displaySnapshot; internal LinkMode Mode { get; } internal bool RequiresFreshBuildSources { get; } internal Player? Player { get; } internal Inventory? PlayerInventory { get; } internal CraftingStation? Station { get; } internal Recipe? Recipe { get; } internal Piece? Piece { get; } internal Vector3 Origin { get; } internal Vector3? StationOrigin { get; } internal bool IsActive { get; } internal bool AllowsCounting { get { bool flag = IsActive; if (flag) { LinkMode mode = Mode; bool flag2 = (uint)(mode - 1) <= 3u; flag = flag2; } return flag; } } internal bool AllowsPayment { get { bool flag = IsActive; if (flag) { LinkMode mode = Mode; bool flag2 = (uint)(mode - 5) <= 1u; flag = flag2; } return flag; } } internal IReadOnlyList<Inventory> CountingInventories => _countingInventories ?? (_countingInventories = LinkedMaterialCounts.LoadCountingInventories(this)); internal HammerMaterialSnapshot DisplaySnapshot => _displaySnapshot ?? (_displaySnapshot = (((Object)(object)Player != (Object)null) ? HammerMaterialReader.GetOrRefresh(Player, Plugin.Settings) : HammerMaterialSnapshot.Unavailable(Time.unscaledTime))); internal MaterialLinkContext(LinkMode mode, Player? player, CraftingStation? station, Recipe? recipe, Piece? piece, Vector3? originOverride = null, bool requiresFreshBuildSources = false) { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) Mode = mode; RequiresFreshBuildSources = requiresFreshBuildSources; Player = player; PlayerInventory = ((player != null) ? ((Humanoid)player).GetInventory() : null); Station = station; Recipe = recipe; Piece = piece; StationOrigin = (((Object