using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using RunicCrafting.Domain; using RunicCrafting.Integration; using TMPro; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Runic Crafting")] [assembly: AssemblyDescription("Vanilla-preserving crafting and building from authorized nearby materials.")] [assembly: AssemblyCompany("Chazman")] [assembly: AssemblyProduct("Runic Crafting")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: ComVisible(false)] [assembly: Guid("c54681ca-bb60-4d57-a83a-6d9996aa3f57")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [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.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace RunicCrafting { internal static class Configuration { internal static ConfigEntry Enabled { get; private set; } internal static ConfigEntry CraftFromContainers { get; private set; } internal static ConfigEntry BuildFromContainers { get; private set; } internal static ConfigEntry StationlessBuildFromContainers { get; private set; } internal static ConfigEntry StationlessBuildRangeMeters { get; private set; } internal static ConfigEntry StationlessPieceAllowList { get; private set; } internal static ConfigEntry StationlessPieceDenyList { get; private set; } internal static ConfigEntry RepairAll { get; private set; } internal static ConfigEntry RangeCapMeters { get; private set; } internal static ConfigEntry MaximumCandidateContainers { get; private set; } internal static ConfigEntry MaximumReturnedContainers { get; private set; } internal static ConfigEntry ExcludePersonalContainers { get; private set; } internal static ConfigEntry RequireWardAccess { get; private set; } internal static ConfigEntry DefaultStationUse { get; private set; } internal static ConfigEntry DefaultLocalMaterialUse { get; private set; } internal static ConfigEntry ShowStatusMessages { get; private set; } internal static ConfigEntry DetailedLogging { get; private set; } internal static StationlessPiecePolicy StationlessPolicy { get; private set; } internal static float SafeRangeCap => Math.Max(1f, Math.Min(50f, RangeCapMeters.Value)); internal static int SafeMaximumCandidates => Math.Max(1, Math.Min(256, MaximumCandidateContainers.Value)); internal static int SafeMaximumReturned => Math.Max(1, Math.Min(Math.Min(128, SafeMaximumCandidates), MaximumReturnedContainers.Value)); internal static float SafeStationlessBuildRange => Math.Max(1f, Math.Min(SafeRangeCap, StationlessBuildRangeMeters.Value)); internal static string StateSummary => "master=" + OnOff(Enabled.Value) + "; nearby crafting=" + OnOff(CraftFromContainers.Value) + "; nearby building=" + OnOff(BuildFromContainers.Value) + "; stationless building=" + OnOff(StationlessBuildFromContainers.Value) + " (player-local " + SafeStationlessBuildRange.ToString("0.#", CultureInfo.InvariantCulture) + "m); stationless allow=" + (StationlessPolicy.AllowsWildcard ? "*" : StationlessPolicy.AllowedRuleCount.ToString(CultureInfo.InvariantCulture)) + "; stationless deny=" + StationlessPolicy.DeniedRuleCount.ToString(CultureInfo.InvariantCulture) + "; Repair All=" + OnOff(RepairAll.Value) + "; station HUD=" + OnOff(ShowStatusMessages.Value) + "; range cap=" + SafeRangeCap.ToString("0.#", CultureInfo.InvariantCulture) + "m; local-material default=" + DefaultLocalMaterialUse.Value.ToString() + "; detailed logging=" + OnOff(DetailedLogging.Value) + "; input=automatic (normal craft/build/repair controls, no hotkey)"; internal static string HudSummary { get { if (Enabled.Value) { return "Runic Crafting: nearby craft " + OnOff(CraftFromContainers.Value) + ", nearby build " + OnOff(BuildFromContainers.Value) + ", stationless build " + OnOff(StationlessBuildFromContainers.Value) + ", Repair All " + OnOff(RepairAll.Value); } return "Runic Crafting: gameplay disabled in configuration"; } } internal static void Bind(ConfigFile config) { //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Expected O, but got Unknown //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Expected O, but got Unknown //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Expected O, but got Unknown //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Expected O, but got Unknown Enabled = config.Bind("General", "Enabled", true, "Master switch. False leaves crafting, building, and repair gameplay to vanilla while status UI may report that Runic Crafting is off."); CraftFromContainers = config.Bind("Materials", "CraftFromContainers", true, "Allow a player to satisfy ordinary recipe deficits from eligible nearby containers. Dedicated clients use the authenticated durable server path."); BuildFromContainers = config.Bind("Materials", "BuildFromContainers", true, "Allow build pieces to satisfy deficits from eligible nearby containers."); StationlessBuildFromContainers = config.Bind("Stationless Building", "Enabled", true, "Allow explicitly listed pieces that require no crafting station (for example fires, the basic cooking station, and the workbench itself) to use eligible containers around the player."); StationlessBuildRangeMeters = config.Bind("Stationless Building", "PlayerLocalRangeMeters", 20f, new ConfigDescription("Player-centered range for explicitly allowed stationless pieces. Materials.RangeCapMeters remains the global upper bound.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 50f), Array.Empty())); StationlessPieceAllowList = config.Bind("Stationless Building", "AllowedPrefabIds", "blackmarble_pile,blackwood_stack,bone_stack,bonfire,coal_pile,fire_pit,fire_pit_iron,grausten_pile,guard_stone,piece_artisanstation,piece_cookingstation,piece_groundtorch_mist,piece_logbench01,piece_pot1,piece_pot1_cracked,piece_pot1_red,piece_pot2,piece_pot2_cracked,piece_pot2_red,piece_pot3,piece_pot3_cracked,piece_pot3_red,piece_workbench,sapling_barley,sapling_carrot,sapling_flax,sapling_jotunpuffs,sapling_magecap,sapling_onion,sapling_seedcarrot,sapling_seedonion,sapling_seedturnip,sapling_turnip,sign,skull_pile,stone_pile,treasure_pile,treasure_stack,wood_core_stack,wood_fine_stack,wood_stack,wood_yggdrasil_stack", "Comma/semicolon-separated exact Piece prefab IDs allowed to use the stationless path. Add modded prefabs here. Empty allows none; an explicit * allows all stationless prefabs."); StationlessPieceDenyList = config.Bind("Stationless Building", "DeniedPrefabIds", string.Empty, "Comma/semicolon-separated exact Piece prefab IDs denied from the stationless path. Deny rules win over allow rules; * denies all."); RepairAll = config.Bind("Repair", "RepairAll", true, "One repair-button press repairs every item that vanilla currently considers repairable."); RangeCapMeters = config.Bind("Materials", "RangeCapMeters", 20f, new ConfigDescription("Global hard cap applied after an active station's range or the configured stationless player-local range.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 50f), Array.Empty())); MaximumCandidateContainers = config.Bind("Materials", "MaximumCandidateContainers", 64, new ConfigDescription("Maximum loaded containers considered by one event-driven query.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 256), Array.Empty())); MaximumReturnedContainers = config.Bind("Materials", "MaximumReturnedContainers", 32, new ConfigDescription("Maximum eligible containers returned to one material plan.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 128), Array.Empty())); ExcludePersonalContainers = config.Bind("Materials", "ExcludePersonalContainers", true, "Exclude Valheim personal/private container types from nearby material use."); RequireWardAccess = config.Bind("Materials", "RequireWardAccess", false, "Require ward access even for containers that do not normally check a guard stone. Native guard-stone checks are always honored."); DefaultStationUse = config.Bind("Workshop Access", "DefaultStationUse", WorkshopPolicyKind.Everyone, "Default independent policy for using a station."); DefaultLocalMaterialUse = config.Bind("Workshop Access", "DefaultLocalMaterialUse", WorkshopPolicyKind.Approved, "Default independent policy for consuming nearby materials through a station."); ShowStatusMessages = config.Bind("Diagnostics", "ShowStatusMessages", true, "Show a short controller-neutral HUD status when opening a station or changing a setting."); DetailedLogging = config.Bind("Diagnostics", "DetailedLogging", false, "Log attempts, every important early gate/no-op reason, bounded source plans, and exact denial codes. Repeated UI probes are rate-limited."); RefreshDerivedSettings(); } internal static void RefreshDerivedSettings() { StationlessPolicy = StationlessPiecePolicy.Parse(StationlessPieceAllowList?.Value, StationlessPieceDenyList?.Value); } private static string OnOff(bool value) { if (!value) { return "OFF"; } return "ON"; } } [BepInPlugin("chazman.RunicCrafting", "Runic Crafting", "1.0.0")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "chazman.RunicCrafting"; public const string Name = "Runic Crafting"; public const string Version = "1.0.0"; private Harmony _harmony; private bool _configurationSubscribed; private string _pendingHudNotice; internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Configuration.Bind(((BaseUnityPlugin)this).Config); ((BaseUnityPlugin)this).Config.SettingChanged += OnSettingChanged; _configurationSubscribed = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)("Runic Crafting configuration: " + Configuration.StateSummary + ".")); try { CraftingRuntime.Initialize(((BaseUnityPlugin)this).Logger); _harmony = new Harmony("chazman.RunicCrafting"); _harmony.PatchAll(typeof(Plugin).Assembly); WorkshopAccessCommands.Initialize(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Runic Crafting v1.0.0 ready. " + Configuration.StateSummary + ". Nearby crafting and building use locally owned Valheim inventories.")); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Runic Crafting startup failed; vanilla behavior remains available: " + ex)); Shutdown(); } } private void Update() { string text = Interlocked.Exchange(ref _pendingHudNotice, null); if (text != null && Configuration.ShowStatusMessages.Value) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { ((Character)localPlayer).Message((MessageType)1, text, 0, (Sprite)null); } } } private void OnSettingChanged(object sender, SettingChangedEventArgs arguments) { try { Configuration.RefreshDerivedSettings(); CraftingRuntime.OnConfigurationChanged(); CraftingDiagnostics.ResetRepeatSuppression(); string text = ((((arguments != null) ? arguments.ChangedSetting : null) == null) ? "unknown setting" : (arguments.ChangedSetting.Definition.Section + "." + arguments.ChangedSetting.Definition.Key)); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Runic Crafting configuration changed (" + text + "): " + Configuration.StateSummary + ". Changes are active now; no restart is required.")); CraftingDiagnostics.TraceAction("configuration-change", text, Configuration.StateSummary); Interlocked.Exchange(ref _pendingHudNotice, Configuration.HudSummary); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Crafting configuration refresh failed: " + ex.Message)); } } private void OnDestroy() { Shutdown(); } private void Shutdown() { if (_configurationSubscribed) { ((BaseUnityPlugin)this).Config.SettingChanged -= OnSettingChanged; _configurationSubscribed = false; } Interlocked.Exchange(ref _pendingHudNotice, null); try { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not remove every crafting patch: " + ex.Message)); } _harmony = null; CraftingRuntime.Shutdown(); CraftingDiagnostics.ResetRepeatSuppression(); Log = null; } } } namespace RunicCrafting.Integration { internal sealed class ContainerQueryRuntime { private readonly WorkshopAccessRuntime _workshopAccess; internal ContainerQueryRuntime(WorkshopAccessRuntime workshopAccess) { _workshopAccess = workshopAccess ?? throw new ArgumentNullException("workshopAccess"); } internal IReadOnlyList ResolveSources(Player player, CraftingStation station, Vector3 origin, float radius, IEnumerable requirements, string purposeId, bool stationlessAccessAuthorized, out string reasonCode) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_0283: Unknown result type (might be due to invalid IL or missing references) //IL_02e7: Unknown result type (might be due to invalid IL or missing references) //IL_02f3: Unknown result type (might be due to invalid IL or missing references) //IL_02f8: Unknown result type (might be due to invalid IL or missing references) //IL_02fd: Unknown result type (might be due to invalid IL or missing references) List list = new List(); reasonCode = "ok"; if (!ValheimReflection.CanMutateLocalPlayer(player)) { reasonCode = "local-player-owner-required"; CraftingDiagnostics.TraceGate(purposeId + ":container-query", reasonCode); return list.AsReadOnly(); } string[] resourceIds = requirements.Select((MaterialRequirement requirement) => requirement.ResourceId).Distinct(StringComparer.Ordinal).OrderBy((string result) => result, StringComparer.Ordinal) .ToArray(); list.Add(new ValheimMaterialSource(PrincipalValue(player), ((Humanoid)player).GetInventory(), MaterialSourceKind.PlayerInventory, 0f, resourceIds, () => ValheimReflection.CanMutateLocalPlayer(player) && ((Object)(object)station == (Object)null || _workshopAccess.Evaluate(station, player, WorkshopAction.StationUse).Allowed))); if ((Object)(object)station == (Object)null && !stationlessAccessAuthorized) { reasonCode = "station-required-for-nearby-materials"; CraftingDiagnostics.TraceGate(purposeId + ":container-query", reasonCode); return list.AsReadOnly(); } if ((Object)(object)station != (Object)null) { WorkshopAccessDecision workshopAccessDecision = _workshopAccess.Evaluate(station, player, WorkshopAction.StationUse); if (!workshopAccessDecision.Allowed) { reasonCode = "station-use:" + workshopAccessDecision.ReasonCode; CraftingDiagnostics.TraceGate(purposeId + ":container-query", reasonCode); return list.AsReadOnly(); } WorkshopAccessDecision workshopAccessDecision2 = _workshopAccess.Evaluate(station, player, WorkshopAction.LocalMaterialUse); if (!workshopAccessDecision2.Allowed) { reasonCode = "local-materials:" + workshopAccessDecision2.ReasonCode; CraftingDiagnostics.TraceGate(purposeId + ":container-query", reasonCode); return list.AsReadOnly(); } } radius = Math.Max(1f, Math.Min(Configuration.SafeRangeCap, radius)); IReadOnlyList readOnlyList = ContainerSpatialIndex.Query(origin, radius, Configuration.SafeMaximumCandidates); SortedDictionary sortedDictionary = new SortedDictionary(StringComparer.Ordinal); int num = 0; foreach (Container item in readOnlyList) { if (num >= Configuration.SafeMaximumReturned) { break; } if (!IsEligibleContainer(item, player, origin, radius, out var rejectionReason)) { sortedDictionary.TryGetValue(rejectionReason, out var value); sortedDictionary[rejectionReason] = value + 1; continue; } Container captured = item; string sourceId = ValheimReflection.ContainerEndpointId(captured); Inventory inventory = captured.GetInventory(); Vector3 val = ((Component)captured).transform.position - origin; list.Add(new ValheimMaterialSource(sourceId, inventory, MaterialSourceKind.NearbyContainer, ((Vector3)(ref val)).sqrMagnitude, resourceIds, delegate { //IL_00b5: Unknown result type (might be due to invalid IL or missing references) bool num2; if (!((Object)(object)station == (Object)null)) { if (_workshopAccess.Evaluate(station, player, WorkshopAction.StationUse).Allowed) { num2 = _workshopAccess.Evaluate(station, player, WorkshopAction.LocalMaterialUse).Allowed; goto IL_009c; } } else if (stationlessAccessAuthorized) { num2 = ValheimReflection.CanMutateLocalPlayer(player); goto IL_009c; } goto IL_00cd; IL_009c: string rejectionReason2; if (num2) { return IsEligibleContainer(captured, player, origin, radius, out rejectionReason2); } goto IL_00cd; IL_00cd: return false; })); num++; } reasonCode = "bounded-local-ownership"; CraftingDiagnostics.TraceGate(purposeId + ":container-query", reasonCode, "candidates=" + readOnlyList.Count + "; selected=" + num + FormatRejections(sortedDictionary)); return list.AsReadOnly(); } private static bool IsEligibleContainer(Container container, Player player, Vector3 origin, float radius, out string rejectionReason) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: 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_0046: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) rejectionReason = "eligible"; if ((Object)(object)container == (Object)null || !((Behaviour)container).isActiveAndEnabled) { rejectionReason = "inactive"; return false; } if (!ValheimReflection.CanMutateLocalPlayer(player)) { rejectionReason = "player-owner"; return false; } Vector3 val = ((Component)container).transform.position - origin; if (((Vector3)(ref val)).sqrMagnitude > radius * radius) { rejectionReason = "out-of-range"; return false; } if (container.IsInUse() || ((Object)(object)container.m_wagon != (Object)null && container.m_wagon.InUse())) { rejectionReason = "in-use"; return false; } if (!container.IsOwner()) { rejectionReason = "not-zdo-owner"; return false; } if (Configuration.ExcludePersonalContainers.Value && (int)container.m_privacy == 0) { rejectionReason = "personal-excluded"; return false; } if (!ValheimReflection.ContainerAllows(container, player.GetPlayerID())) { rejectionReason = "container-access-denied"; return false; } if ((container.m_checkGuardStone || Configuration.RequireWardAccess.Value) && !PrivateArea.CheckAccess(((Component)container).transform.position, 0f, false, false)) { rejectionReason = "ward-denied"; return false; } return container.GetInventory() != null; } private static string FormatRejections(IEnumerable> counts) { string[] array = counts.Select((KeyValuePair pair) => pair.Key + "=" + pair.Value).ToArray(); if (array.Length != 0) { return "; rejected[" + string.Join(",", array) + "]"; } return string.Empty; } internal static string PrincipalValue(Player player) { return "valheim.player:" + player.GetPlayerID().ToString(CultureInfo.InvariantCulture); } } internal static class ContainerSpatialIndex { private readonly struct Candidate { internal Container Container { get; } internal float DistanceSquared { get; } internal Candidate(Container container, float distanceSquared) { Container = container; DistanceSquared = distanceSquared; } } private readonly struct CellKey : IEquatable { internal int X { get; } internal int Z { get; } internal CellKey(int x, int z) { X = x; Z = z; } internal static CellKey From(Vector3 position) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) return new CellKey(Mathf.FloorToInt(position.x / 10f), Mathf.FloorToInt(position.z / 10f)); } public bool Equals(CellKey other) { if (X == other.X) { return Z == other.Z; } return false; } public override bool Equals(object obj) { if (obj is CellKey other) { return Equals(other); } return false; } public override int GetHashCode() { return (X * 397) ^ Z; } } private const float CellSize = 10f; private static readonly object Gate = new object(); private static readonly Dictionary> Cells = new Dictionary>(); private static readonly Dictionary Membership = new Dictionary(); internal static void Register(Container container) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)container == (Object)null) { return; } lock (Gate) { int instanceID = ((Object)container).GetInstanceID(); CellKey cellKey = CellKey.From(((Component)container).transform.position); CellKey value; bool num = Membership.TryGetValue(instanceID, out value); bool flag = num && value.Equals(cellKey); List value2; bool exactContainerPresent = flag && Cells.TryGetValue(value, out value2) && ContainsExact(value2, container); if (!SpatialMembershipPolicy.CanRetainExistingRegistration(num, flag, exactContainerPresent)) { UnregisterLocked(container, instanceID); if (!Cells.TryGetValue(cellKey, out var value3)) { value3 = new List(); Cells.Add(cellKey, value3); } value3.Add(container); Membership[instanceID] = cellKey; } } } internal static void Unregister(Container container) { if (container == null) { return; } lock (Gate) { UnregisterLocked(container, ((Object)container).GetInstanceID()); } } internal static IReadOnlyList Query(Vector3 origin, float radius, int maximumCandidates) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) radius = Math.Max(1f, Math.Min(50f, radius)); maximumCandidates = Math.Max(1, Math.Min(256, maximumCandidates)); List list = new List(maximumCandidates); float num = radius * radius; CellKey cellKey = CellKey.From(origin - new Vector3(radius, 0f, radius)); CellKey cellKey2 = CellKey.From(origin + new Vector3(radius, 0f, radius)); lock (Gate) { for (int i = cellKey.X; i <= cellKey2.X; i++) { for (int j = cellKey.Z; j <= cellKey2.Z; j++) { CellKey key = new CellKey(i, j); if (!Cells.TryGetValue(key, out var value)) { continue; } for (int num2 = value.Count - 1; num2 >= 0; num2--) { Container val = value[num2]; if ((Object)(object)val == (Object)null) { value.RemoveAt(num2); } else { Vector3 val2 = ((Component)val).transform.position - origin; float sqrMagnitude = ((Vector3)(ref val2)).sqrMagnitude; if (!(sqrMagnitude > num)) { list.Add(new Candidate(val, sqrMagnitude)); if (list.Count > maximumCandidates) { RemoveWorstCandidate(list); } } } } if (value.Count == 0) { Cells.Remove(key); } } } } list.Sort(CompareCandidate); List list2 = new List(list.Count); foreach (Candidate item in list) { list2.Add(item.Container); } return list2.AsReadOnly(); } private static void RemoveWorstCandidate(List candidates) { int index = 0; for (int i = 1; i < candidates.Count; i++) { if (CompareCandidate(candidates[index], candidates[i]) < 0) { index = i; } } candidates.RemoveAt(index); } private static int CompareCandidate(Candidate left, Candidate right) { int num = left.DistanceSquared.CompareTo(right.DistanceSquared); if (num == 0) { return StringComparer.Ordinal.Compare(ValheimReflection.ContainerEndpointId(left.Container), ValheimReflection.ContainerEndpointId(right.Container)); } return num; } internal static void Clear() { lock (Gate) { Cells.Clear(); Membership.Clear(); } } private static void UnregisterLocked(Container container, int instanceId) { if (!Membership.TryGetValue(instanceId, out var value)) { return; } if (Cells.TryGetValue(value, out var value2)) { value2.RemoveAll((Container val) => (Object)(object)val == (Object)null || val == container); if (value2.Count == 0) { Cells.Remove(value); } } Membership.Remove(instanceId); } private static bool ContainsExact(List entries, Container container) { if (entries == null) { return false; } for (int i = 0; i < entries.Count; i++) { if (entries[i] == container) { return true; } } return false; } } internal static class CraftingDiagnostics { private static readonly object Gate = new object(); private static readonly Dictionary LastRepeatedTraceUtc = new Dictionary(StringComparer.Ordinal); private static readonly TimeSpan RepeatWindow = TimeSpan.FromSeconds(2.0); internal static void TraceAction(string operation, string reason, string detail = null) { Trace(operation, reason, detail, suppressRepeated: false); } internal static void TraceGate(string operation, string reason, string detail = null) { Trace(operation, reason, detail, suppressRepeated: true); } internal static void ResetRepeatSuppression() { lock (Gate) { LastRepeatedTraceUtc.Clear(); } } private static void Trace(string operation, string reason, string detail, bool suppressRepeated) { if (Configuration.DetailedLogging == null || !Configuration.DetailedLogging.Value) { return; } operation = (string.IsNullOrWhiteSpace(operation) ? "operation" : operation.Trim()); reason = (string.IsNullOrWhiteSpace(reason) ? "unspecified" : reason.Trim()); if (suppressRepeated) { string key = operation + "\n" + reason; DateTime utcNow = DateTime.UtcNow; lock (Gate) { if (LastRepeatedTraceUtc.TryGetValue(key, out var value) && utcNow - value < RepeatWindow) { return; } if (LastRepeatedTraceUtc.Count >= 256) { LastRepeatedTraceUtc.Clear(); } LastRepeatedTraceUtc[key] = utcNow; } } string text = (string.IsNullOrWhiteSpace(detail) ? string.Empty : ("; " + detail.Trim())); ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("[Detailed] " + operation + " -> " + reason + text)); } } } internal static class CraftingRequirementUiContext { [ThreadStatic] private static Recipe _currentRecipe; [ThreadStatic] private static Piece _currentPiece; internal static Recipe CurrentRecipe => _currentRecipe; internal static Piece CurrentPiece => _currentPiece; internal static Recipe Push(InventoryGui inventoryGui) { Recipe currentRecipe = _currentRecipe; _currentRecipe = ValheimReflection.SelectedRecipe(inventoryGui); return currentRecipe; } internal static void Restore(Recipe previous) { _currentRecipe = previous; } internal static Piece PushPiece(Piece piece) { Piece currentPiece = _currentPiece; _currentPiece = piece; return currentPiece; } internal static void RestorePiece(Piece previous) { _currentPiece = previous; } } internal static class CraftingRuntime { private sealed class ActiveOperation { internal Player Player { get; } internal Requirement[] Requirements { get; } internal int Quality { get; } internal int ItemQuality { get; } internal int Multiplier { get; } internal Recipe Recipe { get; } internal Piece Piece { get; } internal MaterialConsumptionLease Lease { get; } internal bool VanillaConsumeReached { get; set; } internal bool OutputCreated { get; set; } internal GameObject PlacedObject { get; set; } internal ActiveOperation(Player player, Requirement[] requirements, int quality, int itemQuality, int multiplier, Recipe recipe, Piece piece, MaterialConsumptionLease lease) { Player = player; Requirements = requirements; Quality = quality; ItemQuality = itemQuality; Multiplier = multiplier; Recipe = recipe; Piece = piece; Lease = lease; } } private sealed class PendingCraft { internal Player Player { get; } internal Recipe Recipe { get; } internal CraftingStation Station { get; } internal Vector3 Origin { get; } internal float Range { get; } internal IReadOnlyList Requirements { get; } internal int Quality { get; } internal int Multiplier { get; } internal PendingCraft(Player player, Recipe recipe, CraftingStation station, Vector3 origin, float range, IReadOnlyList requirements, int quality, int multiplier) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) Player = player; Recipe = recipe; Station = station; Origin = origin; Range = range; Requirements = requirements; Quality = quality; Multiplier = multiplier; } } private readonly struct BreakdownKey : IEquatable { private long PlayerId { get; } private string StationId { get; } private string ResourceId { get; } private int Required { get; } internal BreakdownKey(long playerId, string stationId, string resourceId, int required) { PlayerId = playerId; StationId = stationId; ResourceId = resourceId; Required = required; } public bool Equals(BreakdownKey other) { if (PlayerId == other.PlayerId && Required == other.Required && string.Equals(StationId, other.StationId, StringComparison.Ordinal)) { return string.Equals(ResourceId, other.ResourceId, StringComparison.Ordinal); } return false; } public override bool Equals(object obj) { if (obj is BreakdownKey other) { return Equals(other); } return false; } public override int GetHashCode() { return (((((PlayerId.GetHashCode() * 397) ^ ((StationId != null) ? StringComparer.Ordinal.GetHashCode(StationId) : 0)) * 397) ^ ((ResourceId != null) ? StringComparer.Ordinal.GetHashCode(ResourceId) : 0)) * 397) ^ Required; } } private readonly struct BreakdownEntry { internal int Nearby { get; } internal float ExpiresAt { get; } internal BreakdownEntry(int nearby, float expiresAt) { Nearby = nearby; ExpiresAt = expiresAt; } } private static readonly ExactMaterialPlanner Planner = new ExactMaterialPlanner(); private static readonly ExactMaterialTransactionEngine Engine = new ExactMaterialTransactionEngine(Planner); private static ManualLogSource _log; private static WorkshopAccessRuntime _workshopAccess; private static ContainerQueryRuntime _containerQuery; private static readonly object AvailabilityCacheGate = new object(); private static readonly Dictionary AvailabilityCache = new Dictionary(); private static string _lastStationStatusKey; private static float _lastStationStatusAt = -100f; [ThreadStatic] private static ActiveOperation _activeOperation; [ThreadStatic] private static PendingCraft _pendingCraft; [ThreadStatic] private static bool _insidePlacementUpdate; internal static bool IsInitialized => _containerQuery != null; internal static void Initialize(ManualLogSource log) { _log = log ?? throw new ArgumentNullException("log"); _workshopAccess = new WorkshopAccessRuntime(); _containerQuery = new ContainerQueryRuntime(_workshopAccess); } internal static void Shutdown() { _pendingCraft = null; _insidePlacementUpdate = false; CleanupActive(success: false); ContainerSpatialIndex.Clear(); _containerQuery = null; _workshopAccess = null; _log = null; lock (AvailabilityCacheGate) { AvailabilityCache.Clear(); } _lastStationStatusKey = null; _lastStationStatusAt = -100f; } internal static void OnConfigurationChanged() { lock (AvailabilityCacheGate) { AvailabilityCache.Clear(); } _lastStationStatusKey = null; _lastStationStatusAt = -100f; } internal static bool CanUseStation(CraftingStation station, Player player, out string reason) { reason = "vanilla"; if (!Configuration.Enabled.Value) { CraftingDiagnostics.TraceGate("station-access", "bypass:mod-disabled"); return true; } if (!IsInitialized) { CraftingDiagnostics.TraceGate("station-access", "bypass:runtime-unavailable"); return true; } if (!ValheimReflection.CanMutateLocalPlayer(player)) { CraftingDiagnostics.TraceGate("station-access", "bypass:local-player-owner-required"); return true; } WorkshopAccessDecision workshopAccessDecision = _workshopAccess.Evaluate(station, player, WorkshopAction.StationUse); reason = workshopAccessDecision.ReasonCode; CraftingDiagnostics.TraceGate("station-access", workshopAccessDecision.Allowed ? "allowed" : ("denied:" + reason)); return workshopAccessDecision.Allowed; } internal static void ShowStationStatus(CraftingStation station, Player player) { if (Configuration.ShowStatusMessages.Value && !((Object)(object)player == (Object)null)) { NearbyCraftingFeatureState nearbyCraftingFeatureState = EvaluateNearbyCraftingState(player, null, station, evaluateRecipeRules: false); string text = (((Object)(object)station == (Object)null) ? "none" : ValheimReflection.StationEndpointId(station)) + "|" + nearbyCraftingFeatureState.ReasonCode; float realtimeSinceStartup = Time.realtimeSinceStartup; if (!string.Equals(_lastStationStatusKey, text, StringComparison.Ordinal) || !(realtimeSinceStartup - _lastStationStatusAt < 2f)) { _lastStationStatusKey = text; _lastStationStatusAt = realtimeSinceStartup; string text2 = (nearbyCraftingFeatureState.IsReady ? ("Runic Crafting: nearby materials ON (up to " + GetStationRange(station).ToString("0.#", CultureInfo.InvariantCulture) + "m); recipe rows show combined/required totals with C/N/T/M hover details") : ExplainNearbyState(nearbyCraftingFeatureState)); ((Character)player).Message((MessageType)1, text2, 0, (Sprite)null); CraftingDiagnostics.TraceAction("station-status", nearbyCraftingFeatureState.ReasonCode, text2); } } } internal static bool BeforeCraft(InventoryGui gui, Player player, Recipe recipe, ItemData upgradeItem, bool multiCrafting, int multiCraftAmount) { //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_026a: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_0243: Unknown result type (might be due to invalid IL or missing references) CraftingDiagnostics.TraceAction("craft-attempt", "received"); CraftingStation val = (((Object)(object)player != (Object)null) ? player.GetCurrentCraftingStation() : null); if (Configuration.Enabled.Value && IsInitialized && ValheimReflection.CanMutateLocalPlayer(player) && (Object)(object)val != (Object)null && !CanUseStation(val, player, out var reason)) { ((Character)player).Message((MessageType)2, "Station use denied: " + reason, 0, (Sprite)null); CraftingDiagnostics.TraceAction("craft-cancelled", "station-use-denied:" + reason); return false; } NearbyCraftingFeatureState nearbyCraftingFeatureState = EvaluateNearbyCraftingState(player, recipe, val, evaluateRecipeRules: true); if (!nearbyCraftingFeatureState.IsReady) { CraftingDiagnostics.TraceAction("nearby-craft-bypass", nearbyCraftingFeatureState.ReasonCode, "vanilla handles the action"); return true; } int num = ((upgradeItem == null) ? 1 : (upgradeItem.m_quality + 1)); if (num > recipe.m_item.m_itemData.m_shared.m_maxQuality || (upgradeItem != null && !((Humanoid)player).GetInventory().ContainsItem(upgradeItem))) { CraftingDiagnostics.TraceAction("nearby-craft-bypass", "invalid-upgrade-state", "vanilla handles the action"); return true; } int multiplier = ((!multiCrafting) ? 1 : Math.Max(1, multiCraftAmount)); if (!TryBuildRequirements(recipe.m_resources, num, multiplier, out var requirements)) { CraftingDiagnostics.TraceAction("nearby-craft-bypass", "no-valid-material-requirements", "vanilla handles the action"); return true; } if (InventorySatisfies(((Humanoid)player).GetInventory(), requirements)) { CraftingDiagnostics.TraceAction("nearby-craft-bypass", "carried-inventory-satisfies-cost", "vanilla consumes carried materials"); return true; } if ((Object)(object)val == (Object)null) { CraftingDiagnostics.TraceAction("nearby-craft-bypass", "crafting-station-required", "vanilla handles the action"); return true; } float stationRange = GetStationRange(val); Vector3 origin = (((Object)(object)val != (Object)null) ? ((Component)val).transform.position : ((Component)player).transform.position); if (upgradeItem == null) { if (_pendingCraft != null || _activeOperation != null) { ((Character)player).Message((MessageType)2, "Nearby crafting cancelled: another material operation is active", 0, (Sprite)null); CraftingDiagnostics.TraceAction("craft-cancelled", "another-material-operation-is-active"); return false; } if (!CheckAvailability(player, val, origin, stationRange, requirements, "runic.crafting.recipe-preview", stationlessAccessAuthorized: false, out var plan, out var reason2)) { CraftingDiagnostics.TraceAction("nearby-craft-bypass", reason2, "vanilla reports the material shortage"); return true; } if (!plan.Lines.Any((MaterialPlanLine line) => !line.SourceId.StartsWith("valheim.player:", StringComparison.Ordinal))) { CraftingDiagnostics.TraceAction("nearby-craft-bypass", "no-nearby-source-needed", "vanilla consumes carried materials"); return true; } _pendingCraft = new PendingCraft(player, recipe, val, origin, stationRange, requirements, num, multiplier); CraftingDiagnostics.TraceAction("nearby-craft", "pending-vanilla-capacity-check"); return true; } if (!TryBeginNearbyOperation(player, val, origin, stationRange, requirements, "runic.crafting.recipe", stationlessAccessAuthorized: false, recipe.m_resources, num, -1, multiplier, recipe, null, out var reason3)) { ((Character)player).Message((MessageType)2, "Nearby crafting cancelled: " + reason3, 0, (Sprite)null); CraftingDiagnostics.TraceAction("craft-cancelled", reason3); return false; } CraftingDiagnostics.TraceAction("nearby-craft", "exact-material-operation-started"); return true; } internal static bool AfterCraftCapacityCheck(Inventory inventory, GameObject prefab, bool vanillaAllowed) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) PendingCraft pendingCraft = _pendingCraft; if (pendingCraft == null || inventory == null || (Object)(object)prefab == (Object)null || inventory != ((Humanoid)pendingCraft.Player).GetInventory() || (Object)(object)prefab != (Object)(object)((Component)pendingCraft.Recipe.m_item).gameObject) { return vanillaAllowed; } _pendingCraft = null; if (!vanillaAllowed) { CraftingDiagnostics.TraceAction("craft-cancelled", "vanilla-output-capacity-check-failed"); return false; } if (TryBeginNearbyOperation(pendingCraft.Player, pendingCraft.Station, pendingCraft.Origin, pendingCraft.Range, pendingCraft.Requirements, "runic.crafting.recipe", stationlessAccessAuthorized: false, pendingCraft.Recipe.m_resources, pendingCraft.Quality, -1, pendingCraft.Multiplier, pendingCraft.Recipe, null, out var reason)) { CraftingDiagnostics.TraceAction("nearby-craft", "exact-material-operation-started"); return true; } ((Character)pendingCraft.Player).Message((MessageType)2, "Nearby crafting cancelled: " + reason, 0, (Sprite)null); CraftingDiagnostics.TraceAction("craft-cancelled", reason); return false; } internal static void CraftOutputAdded(Inventory inventory, ItemData result) { ActiveOperation activeOperation = _activeOperation; if (activeOperation != null && !((Object)(object)activeOperation.Recipe == (Object)null) && result != null && inventory == ((Humanoid)activeOperation.Player).GetInventory()) { activeOperation.OutputCreated = true; } } internal static void CancelPendingCraft() { _pendingCraft = null; CraftingDiagnostics.TraceAction("craft-cancelled", "pending-capacity-hook-failed"); } internal static void FinishCraft(Exception exception) { _pendingCraft = null; if (exception != null) { CraftingDiagnostics.TraceAction("craft-finished", "exception:" + exception.GetType().Name); } CleanupActive(_activeOperation != null && (_activeOperation.VanillaConsumeReached || _activeOperation.OutputCreated)); } internal static void BeginPlacementScope() { _insidePlacementUpdate = true; } internal static void FinishPlacementScope(Exception exception) { try { if (_activeOperation != null) { PlacementLeaseDisposition placementLeaseDisposition = PlacementLeasePolicy.Resolve(_activeOperation.OutputCreated || (Object)(object)_activeOperation.PlacedObject != (Object)null, _activeOperation.VanillaConsumeReached); CraftingDiagnostics.TraceAction("nearby-build-finalize", (placementLeaseDisposition == PlacementLeaseDisposition.Commit) ? "commit" : "rollback", (exception == null) ? "placement-update-completed" : ("placement-update-exception:" + exception.GetType().Name)); CleanupActive(placementLeaseDisposition == PlacementLeaseDisposition.Commit); } } finally { _insidePlacementUpdate = false; } } internal static bool BeforePlacePiece(Player player, Piece piece, out string reason) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) reason = "vanilla"; if (!_insidePlacementUpdate) { CraftingDiagnostics.TraceAction("nearby-build-bypass", "outside-vanilla-placement-scope"); return true; } if ((Object)(object)player == (Object)null || (Object)(object)piece == (Object)null) { CraftingDiagnostics.TraceAction("nearby-build-bypass", "invalid-player-or-piece"); return true; } CraftingStation val = (((Object)(object)piece.m_craftingStation == (Object)null) ? null : CraftingStation.HaveBuildStationInRange(piece.m_craftingStation.m_name, ((Component)player).transform.position)); if (Configuration.Enabled.Value && IsInitialized && ValheimReflection.CanMutateLocalPlayer(player) && (Object)(object)val != (Object)null && !CanUseStation(val, player, out reason)) { reason = "station-use:" + reason; CraftingDiagnostics.TraceAction("build-cancelled", reason); return false; } if (!CanUseNearbyBuilding(player, piece, out var reason2)) { CraftingDiagnostics.TraceAction("nearby-build-bypass", reason2, "vanilla handles the placement"); return true; } if (!TryBuildRequirements(piece.m_resources, 0, 1, out var requirements)) { CraftingDiagnostics.TraceAction("nearby-build-bypass", "no-valid-material-requirements", "vanilla handles the placement"); return true; } if (InventorySatisfies(((Humanoid)player).GetInventory(), requirements)) { CraftingDiagnostics.TraceAction("nearby-build-bypass", "carried-inventory-satisfies-cost", "vanilla consumes carried materials"); return true; } BuildMaterialQueryScope buildMaterialQueryScope = BuildMaterialQueryScopePolicy.Resolve((Object)(object)piece.m_craftingStation != (Object)null, (Object)(object)val != (Object)null); if (buildMaterialQueryScope == BuildMaterialQueryScope.RequiredStationUnavailable) { reason = "required-station-unavailable"; CraftingDiagnostics.TraceAction("build-cancelled", reason); return false; } bool flag = buildMaterialQueryScope == BuildMaterialQueryScope.PlayerLocalStationless; float range = (flag ? Configuration.SafeStationlessBuildRange : GetStationRange(val)); Vector3 origin = (flag ? ((Component)player).transform.position : ((Component)val).transform.position); if (!TryBeginNearbyOperation(player, val, origin, range, requirements, "runic.crafting.build", flag, piece.m_resources, 0, -1, 1, null, piece, out reason)) { CraftingDiagnostics.TraceAction("build-cancelled", reason); return false; } CraftingDiagnostics.TraceAction("nearby-build", "exact-material-operation-started"); return true; } internal static void PlacePieceReturned(bool succeeded) { if (_activeOperation != null) { if (succeeded) { _activeOperation.OutputCreated = true; CraftingDiagnostics.TraceAction("nearby-build", "vanilla-piece-created"); } else { CraftingDiagnostics.TraceAction("nearby-build", "vanilla-placement-returned-false; rolling-back"); CleanupActive(success: false); } } } internal static void PlacementObjectInstantiated(GameObject placedObject, Player player, Piece piece) { ActiveOperation activeOperation = _activeOperation; if (_insidePlacementUpdate && activeOperation != null && !((Object)(object)placedObject == (Object)null) && activeOperation.Player == player && activeOperation.Piece == piece) { activeOperation.PlacedObject = placedObject; } } internal static bool BeforeHaveRecipeRequirements(Player player, Recipe recipe, bool discover, int quality, int multiplier, ref bool result) { if (_activeOperation != null && (Object)(object)_activeOperation.Player == (Object)(object)player && _activeOperation.Recipe == recipe && !discover && _activeOperation.Quality == quality && _activeOperation.Multiplier == multiplier) { result = true; return false; } return true; } internal static void AddNearbyRecipeAvailability(Player player, Recipe recipe, bool discover, int quality, int multiplier, ref bool result) { //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) if (result) { CraftingDiagnostics.TraceGate("recipe-preview", "vanilla-requirements-satisfied"); return; } if (discover) { CraftingDiagnostics.TraceGate("recipe-preview", "discovery-check-remains-vanilla"); return; } CraftingStation val = (((Object)(object)player != (Object)null) ? player.GetCurrentCraftingStation() : null); NearbyCraftingFeatureState nearbyCraftingFeatureState = EvaluateNearbyCraftingState(player, recipe, val, evaluateRecipeRules: true); List requirements; if (!nearbyCraftingFeatureState.IsReady) { CraftingDiagnostics.TraceGate("recipe-preview", nearbyCraftingFeatureState.ReasonCode); } else if (TryBuildRequirements(recipe.m_resources, quality, multiplier, out requirements)) { result = CheckAvailability(player, val, ((Object)(object)val != (Object)null) ? ((Component)val).transform.position : ((Component)player).transform.position, GetStationRange(val), requirements, "runic.crafting.recipe-preview", stationlessAccessAuthorized: false, out var _, out var reason); CraftingDiagnostics.TraceGate("recipe-preview", result ? "nearby-materials-satisfy-cost" : reason); } } internal static void AddNearbyPieceAvailability(Player player, Piece piece, RequirementMode mode, ref bool result) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0078: 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_007d: 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) if (result || (int)mode != 0) { return; } CraftingStation station; NearbyCraftingFeatureState nearbyCraftingFeatureState = EvaluateNearbyBuildingState(player, piece, out station); List requirements; if (!nearbyCraftingFeatureState.IsReady) { CraftingDiagnostics.TraceGate("build-preview", nearbyCraftingFeatureState.ReasonCode); } else if (TryBuildRequirements(piece.m_resources, 0, 1, out requirements)) { BuildMaterialQueryScope buildMaterialQueryScope = BuildMaterialQueryScopePolicy.Resolve((Object)(object)piece.m_craftingStation != (Object)null, (Object)(object)station != (Object)null); if (buildMaterialQueryScope != BuildMaterialQueryScope.RequiredStationUnavailable) { bool flag = buildMaterialQueryScope == BuildMaterialQueryScope.PlayerLocalStationless; Vector3 origin = (flag ? ((Component)player).transform.position : ((Component)station).transform.position); float range = (flag ? Configuration.SafeStationlessBuildRange : GetStationRange(station)); result = CheckAvailability(player, station, origin, range, requirements, "runic.crafting.build-preview", flag, out var _, out var reason); CraftingDiagnostics.TraceGate("build-preview", result ? "nearby-materials-satisfy-cost" : reason); } } } internal static bool BeforeVanillaConsume(Player player, Requirement[] requirements, int quality, int itemQuality, int multiplier) { ActiveOperation activeOperation = _activeOperation; if (activeOperation == null || (Object)(object)activeOperation.Player != (Object)(object)player || activeOperation.Requirements != requirements || activeOperation.Quality != quality || activeOperation.ItemQuality != itemQuality || activeOperation.Multiplier != multiplier) { return true; } activeOperation.VanillaConsumeReached = true; return false; } internal static bool TryGetAvailabilityBreakdown(Player player, Recipe recipe, Requirement requirement, int quality, int multiplier, out int carried, out int nearby, out int required, out NearbyCraftingFeatureState state) { //IL_00c4: Unknown result type (might be due to invalid IL or missing references) carried = (nearby = (required = 0)); state = new NearbyCraftingFeatureState(isReady: false, "requirement-unavailable", "N:OFF(requirement)"); if (requirement == null || (Object)(object)requirement.m_resItem == (Object)null || (Object)(object)player == (Object)null) { return false; } required = checked(requirement.GetAmount(quality) * multiplier); if (required <= 0) { return false; } string text = ValheimReflection.ResourceId(requirement.m_resItem); if (text.Length == 0) { return false; } carried = ValheimReflection.CountRequirementItems(((Humanoid)player).GetInventory(), text); CraftingStation currentCraftingStation = player.GetCurrentCraftingStation(); state = EvaluateNearbyCraftingState(player, recipe, currentCraftingStation, evaluateRecipeRules: true); if (!state.IsReady) { CraftingDiagnostics.TraceGate("requirement-row", state.ReasonCode); return true; } return TryResolveNearbyBreakdown(player, currentCraftingStation, text, required, carried, "requirement-row", "runic.crafting.ui", ((Component)currentCraftingStation).transform.position, GetStationRange(currentCraftingStation), stationlessAccessAuthorized: false, ref nearby, ref state); } internal static bool TryGetBuildAvailabilityBreakdown(Player player, Piece piece, Requirement requirement, out int carried, out int nearby, out int required, out NearbyCraftingFeatureState state) { //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) carried = (nearby = (required = 0)); state = new NearbyCraftingFeatureState(isReady: false, "requirement-unavailable", "N:OFF(requirement)"); if (requirement == null || (Object)(object)requirement.m_resItem == (Object)null || (Object)(object)player == (Object)null || (Object)(object)piece == (Object)null) { return false; } required = requirement.GetAmount(0); if (required <= 0) { return false; } string text = ValheimReflection.ResourceId(requirement.m_resItem); if (text.Length == 0) { return false; } carried = ValheimReflection.CountRequirementItems(((Humanoid)player).GetInventory(), text); state = EvaluateNearbyBuildingState(player, piece, out var station); if (!state.IsReady) { CraftingDiagnostics.TraceGate("build-requirement-row", state.ReasonCode); return true; } BuildMaterialQueryScope buildMaterialQueryScope = BuildMaterialQueryScopePolicy.Resolve((Object)(object)piece.m_craftingStation != (Object)null, (Object)(object)station != (Object)null); if (buildMaterialQueryScope == BuildMaterialQueryScope.RequiredStationUnavailable) { state = BuildingFailureState("required-station-unavailable"); return true; } bool flag = buildMaterialQueryScope == BuildMaterialQueryScope.PlayerLocalStationless; Vector3 origin = (flag ? ((Component)player).transform.position : ((Component)station).transform.position); float range = (flag ? Configuration.SafeStationlessBuildRange : GetStationRange(station)); return TryResolveNearbyBreakdown(player, station, text, required, carried, "build-requirement-row", "runic.crafting.build-ui", origin, range, flag, ref nearby, ref state); } private static bool TryResolveNearbyBreakdown(Player player, CraftingStation station, string resource, int required, int carried, string diagnosticOperation, string queryPurpose, Vector3 origin, float range, bool stationlessAccessAuthorized, ref int nearby, ref NearbyCraftingFeatureState state) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) BreakdownKey key = new BreakdownKey(player.GetPlayerID(), stationlessAccessAuthorized ? StationlessAnchorId(origin) : ValheimReflection.StationEndpointId(station), resource, required); float realtimeSinceStartup = Time.realtimeSinceStartup; lock (AvailabilityCacheGate) { if (AvailabilityCache.TryGetValue(key, out var value) && value.ExpiresAt >= realtimeSinceStartup) { nearby = value.Nearby; CraftingDiagnostics.TraceGate(diagnosticOperation, "ready", "resource=" + resource + "; required=" + required + "; carried=" + carried + "; nearby=" + nearby + "; cached=true"); return true; } } MaterialRequirement[] requirements = new MaterialRequirement[1] { new MaterialRequirement(resource, required) }; string reasonCode; IReadOnlyList readOnlyList = _containerQuery.ResolveSources(player, station, origin, range, requirements, queryPurpose, stationlessAccessAuthorized, out reasonCode); if (IsBlockingQueryReason(reasonCode)) { state = QueryFailureState(reasonCode); CraftingDiagnostics.TraceGate(diagnosticOperation, state.ReasonCode); return true; } foreach (IMutableMaterialSource item in readOnlyList) { MaterialSourceSnapshot materialSourceSnapshot = item.Snapshot(); if (materialSourceSnapshot.Kind == MaterialSourceKind.NearbyContainer) { int num = materialSourceSnapshot.Available(resource); nearby = ((nearby > int.MaxValue - num) ? int.MaxValue : (nearby + num)); } } lock (AvailabilityCacheGate) { if (AvailabilityCache.Count >= 512) { AvailabilityCache.Clear(); } AvailabilityCache[key] = new BreakdownEntry(nearby, realtimeSinceStartup + 0.25f); } CraftingDiagnostics.TraceGate(diagnosticOperation, "ready", "resource=" + resource + "; required=" + required + "; carried=" + carried + "; nearby=" + nearby + "; source-query=" + reasonCode); return true; } private static bool TryBeginNearbyOperation(Player player, CraftingStation station, Vector3 origin, float range, IReadOnlyList requirements, string purpose, bool stationlessAccessAuthorized, Requirement[] vanillaRequirements, int quality, int itemQuality, int multiplier, Recipe recipe, Piece piece, out string reason) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) reason = "insufficient-materials"; if (_activeOperation != null) { reason = "another-material-operation-is-active"; CraftingDiagnostics.TraceAction(purpose, reason); return false; } string reasonCode; IReadOnlyList readOnlyList = _containerQuery.ResolveSources(player, station, origin, range, requirements, purpose, stationlessAccessAuthorized, out reasonCode); CraftingDiagnostics.TraceAction(purpose + ":source-query", reasonCode, "sources=" + readOnlyList.Count + "; range=" + range.ToString("0.#", CultureInfo.InvariantCulture) + "m"); MaterialSourceSnapshot[] sources = readOnlyList.Select((IMutableMaterialSource source) => source.Snapshot()).ToArray(); if (Configuration.DetailedLogging.Value) { CraftingDiagnostics.TraceAction(purpose + ":availability", "evaluated", FormatMaterialAvailability(requirements, sources)); } if (!Planner.TryPlan(requirements, sources, out var plan, out var denialReason)) { if (reasonCode.StartsWith("station-use:", StringComparison.Ordinal) || reasonCode.StartsWith("local-materials:", StringComparison.Ordinal)) { reason = reasonCode; } else { reason = denialReason; } CraftingDiagnostics.TraceAction(purpose, reason); return false; } if (!plan.Lines.Any((MaterialPlanLine line) => !line.SourceId.StartsWith("valheim.player:", StringComparison.Ordinal))) { reason = "no-nearby-source-needed"; CraftingDiagnostics.TraceAction(purpose, reason); return false; } if (!Engine.TryBegin(requirements, readOnlyList, out var lease, out var denialReason2)) { reason = denialReason2; CraftingDiagnostics.TraceAction(purpose, reason); return false; } _activeOperation = new ActiveOperation(player, vanillaRequirements, quality, itemQuality, multiplier, recipe, piece, lease); if (Configuration.DetailedLogging.Value) { ManualLogSource log = _log; if (log != null) { log.LogInfo((object)$"Reserved exact {purpose} plan with {lease.Plan.Lines.Count} source lines ({reasonCode})."); } } return true; } private static bool CheckAvailability(Player player, CraftingStation station, Vector3 origin, float range, IReadOnlyList requirements, string purpose, bool stationlessAccessAuthorized, out MaterialPlan plan, out string reason) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) plan = null; reason = "insufficient-materials"; if ((Object)(object)station == (Object)null && !stationlessAccessAuthorized) { reason = "crafting-station-required"; return false; } if ((Object)(object)station != (Object)null && !CanUseStation(station, player, out var reason2)) { reason = "station-use:" + reason2; return false; } string reasonCode; IReadOnlyList source = _containerQuery.ResolveSources(player, station, origin, range, requirements, purpose, stationlessAccessAuthorized, out reasonCode); if (IsBlockingQueryReason(reasonCode)) { reason = reasonCode; return false; } string denialReason; bool flag = Planner.TryPlan(requirements, source.Select((IMutableMaterialSource mutableMaterialSource) => mutableMaterialSource.Snapshot()), out plan, out denialReason); reason = (flag ? ("ok:" + reasonCode) : denialReason); return flag; } private static NearbyCraftingFeatureState EvaluateNearbyCraftingState(Player player, Recipe recipe, CraftingStation station, bool evaluateRecipeRules) { bool noCostMode = ((Object)(object)player != (Object)null && player.NoCostCheat()) || ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey((GlobalKeys)20)); NearbyCraftingFeatureState nearbyCraftingFeatureState = NearbyCraftingFeatureStateEvaluator.Evaluate(Configuration.Enabled.Value, Configuration.CraftFromContainers.Value, IsInitialized, ValheimReflection.CanMutateLocalPlayer(player), !evaluateRecipeRules || (Object)(object)recipe != (Object)null, evaluateRecipeRules && (Object)(object)recipe != (Object)null && recipe.m_requireOnlyOneIngredient, noCostMode, (Object)(object)station != (Object)null, stationUseAllowed: true, null, localMaterialsAllowed: true, null); if (!nearbyCraftingFeatureState.IsReady) { return nearbyCraftingFeatureState; } WorkshopAccessDecision workshopAccessDecision = _workshopAccess.Evaluate(station, player, WorkshopAction.StationUse); WorkshopAccessDecision workshopAccessDecision2 = (workshopAccessDecision.Allowed ? _workshopAccess.Evaluate(station, player, WorkshopAction.LocalMaterialUse) : new WorkshopAccessDecision(allowed: false, WorkshopAction.LocalMaterialUse, "station-use-denied")); return NearbyCraftingFeatureStateEvaluator.Evaluate(masterEnabled: true, craftFromContainersEnabled: true, runtimeAvailable: true, localPlayerOwner: true, recipeAvailable: true, specialOneIngredientRecipe: false, noCostMode: false, stationPresent: true, workshopAccessDecision.Allowed, workshopAccessDecision.ReasonCode, workshopAccessDecision2.Allowed, workshopAccessDecision2.ReasonCode); } private static NearbyCraftingFeatureState EvaluateNearbyBuildingState(Player player, Piece piece, out CraftingStation station) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) station = null; if (!CanUseNearbyBuilding(player, piece, out var reason)) { return BuildingFailureState(reason); } if (!ValheimReflection.DlcAllows(piece.m_dlc)) { return BuildingFailureState("dlc-entitlement-required"); } if ((Object)(object)piece.m_craftingStation == (Object)null) { return new NearbyCraftingFeatureState(isReady: true, "ready-stationless", "N:ON"); } station = CraftingStation.HaveBuildStationInRange(piece.m_craftingStation.m_name, ((Component)player).transform.position); if ((Object)(object)station == (Object)null) { return BuildingFailureState("required-station-unavailable"); } WorkshopAccessDecision workshopAccessDecision = _workshopAccess.Evaluate(station, player, WorkshopAction.StationUse); if (!workshopAccessDecision.Allowed) { return new NearbyCraftingFeatureState(isReady: false, "station-use-denied:" + workshopAccessDecision.ReasonCode, "N:OFF(access)"); } WorkshopAccessDecision workshopAccessDecision2 = _workshopAccess.Evaluate(station, player, WorkshopAction.LocalMaterialUse); if (!workshopAccessDecision2.Allowed) { return new NearbyCraftingFeatureState(isReady: false, "local-material-use-denied:" + workshopAccessDecision2.ReasonCode, "N:OFF(access)"); } return new NearbyCraftingFeatureState(isReady: true, "ready", "N:ON"); } private static NearbyCraftingFeatureState BuildingFailureState(string reason) { return new NearbyCraftingFeatureState(isReady: false, reason, reason switch { "mod-disabled" => "N:OFF(mod)", "build-from-containers-disabled" => "N:OFF(config)", "runtime-unavailable" => "N:OFF(startup)", "local-player-owner-required" => "N:OFF(owner)", "no-cost-mode" => "N:OFF(no-cost)", "required-station-unavailable" => "N:OFF(station)", "dlc-entitlement-required" => "N:OFF(dlc)", "stationless-building-disabled" => "N:OFF(config)", _ => "N:OFF(build)", }); } private static bool CanUseNearbyBuilding(Player player, Piece piece, out string reason) { //IL_00a4: Unknown result type (might be due to invalid IL or missing references) reason = "ready"; if (!Configuration.Enabled.Value) { reason = "mod-disabled"; } else if (!Configuration.BuildFromContainers.Value) { reason = "build-from-containers-disabled"; } else if (!IsInitialized) { reason = "runtime-unavailable"; } else if (!ValheimReflection.CanMutateLocalPlayer(player)) { reason = "local-player-owner-required"; } else if ((Object)(object)piece == (Object)null) { reason = "piece-unavailable"; } else if (!ValheimReflection.DlcAllows(piece.m_dlc)) { reason = "dlc-entitlement-required"; } else if (player.NoCostCheat() || ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey(piece.FreeBuildKey()))) { reason = "no-cost-mode"; } else if ((Object)(object)piece.m_craftingStation == (Object)null) { if (!Configuration.StationlessBuildFromContainers.Value) { reason = "stationless-building-disabled"; } else { StationlessPieceDecision stationlessPieceDecision = Configuration.StationlessPolicy.Evaluate(ValheimReflection.PiecePrefabId(piece)); if (!stationlessPieceDecision.Allowed) { reason = stationlessPieceDecision.ReasonCode; } } } return reason == "ready"; } private static bool IsBlockingQueryReason(string reason) { if (!string.Equals(reason, "local-player-owner-required", StringComparison.Ordinal) && !string.Equals(reason, "station-required-for-nearby-materials", StringComparison.Ordinal)) { if (reason != null) { if (!reason.StartsWith("station-use:", StringComparison.Ordinal)) { return reason.StartsWith("local-materials:", StringComparison.Ordinal); } return true; } return false; } return true; } private static NearbyCraftingFeatureState QueryFailureState(string reason) { string displayLabel = "N:OFF(access)"; return new NearbyCraftingFeatureState(isReady: false, reason, displayLabel); } private static string ExplainNearbyState(NearbyCraftingFeatureState state) { string reasonCode = state.ReasonCode; switch (reasonCode) { case "mod-disabled": return "Runic Crafting: gameplay disabled in configuration"; case "craft-from-containers-disabled": return "Runic Crafting: nearby materials OFF (CraftFromContainers=false)"; case "runtime-unavailable": return "Runic Crafting: nearby materials unavailable; check the BepInEx log"; case "local-player-owner-required": return "Runic Crafting: nearby materials require the local player owner"; case "special-one-ingredient-recipe": return "Runic Crafting: this special recipe uses vanilla carried materials"; case "no-cost-mode": return "Runic Crafting: no-cost mode is active; nearby containers are intentionally unused"; case "crafting-station-required": return "Runic Crafting: an active crafting station is required for nearby materials"; default: if (reasonCode.StartsWith("station-use-denied:", StringComparison.Ordinal)) { return "Runic Crafting: nearby materials OFF because station use is denied (" + reasonCode.Substring("station-use-denied:".Length) + ")"; } if (reasonCode.StartsWith("local-material-use-denied:", StringComparison.Ordinal)) { return "Runic Crafting: nearby materials OFF by this station's Local Material Use policy (" + reasonCode.Substring("local-material-use-denied:".Length) + ")"; } return "Runic Crafting: nearby materials unavailable (" + reasonCode + ")"; } } private static bool TryBuildRequirements(Requirement[] source, int quality, int multiplier, out List requirements) { requirements = new List(); if (source == null || multiplier <= 0) { return false; } try { foreach (Requirement val in source) { if ((Object)(object)val?.m_resItem == (Object)null) { continue; } int num = checked(val.GetAmount(quality) * multiplier); if (num > 0) { string text = ValheimReflection.ResourceId(val.m_resItem); if (text.Length == 0) { requirements.Clear(); return false; } requirements.Add(new MaterialRequirement(text, num)); } } return requirements.Count > 0; } catch (OverflowException) { requirements.Clear(); return false; } } private static string FormatMaterialAvailability(IEnumerable requirements, IEnumerable sources) { SortedDictionary sortedDictionary = new SortedDictionary(StringComparer.Ordinal); Dictionary dictionary = new Dictionary(StringComparer.Ordinal); Dictionary dictionary2 = new Dictionary(StringComparer.Ordinal); foreach (MaterialRequirement requirement in requirements) { sortedDictionary.TryGetValue(requirement.ResourceId, out var value); sortedDictionary[requirement.ResourceId] = SaturatingAdd(value, requirement.Quantity); } foreach (MaterialSourceSnapshot source in sources) { Dictionary dictionary3 = ((source.Kind == MaterialSourceKind.PlayerInventory) ? dictionary : dictionary2); foreach (string key in sortedDictionary.Keys) { int right = source.Available(key); dictionary3.TryGetValue(key, out var value2); dictionary3[key] = SaturatingAdd(value2, right); } } List list = new List(sortedDictionary.Count); foreach (KeyValuePair item in sortedDictionary) { dictionary.TryGetValue(item.Key, out var value3); dictionary2.TryGetValue(item.Key, out var value4); list.Add(item.Key + " required=" + item.Value + " carried=" + value3 + " nearby=" + value4 + " total=" + SaturatingAdd(value3, value4)); } return string.Join("; ", list); } private static int SaturatingAdd(int left, int right) { if (left <= int.MaxValue - right) { return left + right; } return int.MaxValue; } private static bool InventorySatisfies(Inventory inventory, IEnumerable requirements) { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); try { foreach (MaterialRequirement requirement in requirements) { dictionary.TryGetValue(requirement.ResourceId, out var value); dictionary[requirement.ResourceId] = checked(value + requirement.Quantity); } } catch (OverflowException) { return false; } foreach (KeyValuePair item in dictionary) { if (ValheimReflection.CountRequirementItems(inventory, item.Key) < item.Value) { return false; } } return true; } private static float GetStationRange(CraftingStation station) { if ((Object)(object)station == (Object)null) { return Configuration.SafeRangeCap; } float num = station.GetStationBuildRange(); if (num <= 0f) { num = station.m_rangeBuild; } return Math.Max(1f, Math.Min(Configuration.SafeRangeCap, num)); } private static string StationlessAnchorId(Vector3 origin) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0014: 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) int num = (int)Math.Floor(origin.x * 2f); int num2 = (int)Math.Floor(origin.y * 2f); int num3 = (int)Math.Floor(origin.z * 2f); return "player-local:" + num + ":" + num2 + ":" + num3; } private static void CleanupActive(bool success) { ActiveOperation activeOperation = _activeOperation; _activeOperation = null; if (activeOperation == null) { return; } try { if (success) { activeOperation.Lease.Commit(); CraftingDiagnostics.TraceAction("material-lease", "committed"); } else if (!activeOperation.Lease.Rollback()) { ManualLogSource log = _log; if (log != null) { log.LogError((object)"Runic Crafting rollback could not restore every removed stack. Stop and inspect inventories before continuing."); } } else { CraftingDiagnostics.TraceAction("material-lease", "rolled-back"); } } catch (Exception ex) { ManualLogSource log2 = _log; if (log2 != null) { log2.LogError((object)("Runic Crafting could not close its material lease safely: " + ex)); } } } } [HarmonyPatch(typeof(Container), "Awake")] internal static class ContainerAwakePatch { private static void Postfix(Container __instance) { try { ContainerSpatialIndex.Register(__instance); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Could not index a container: " + ex.Message)); } } } } [HarmonyPatch(typeof(Container), "OnDestroyed")] internal static class ContainerDestroyedPatch { private static void Postfix(Container __instance, bool __runOriginal) { if (!__runOriginal) { return; } try { ContainerSpatialIndex.Unregister(__instance); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Could not remove a container index entry: " + ex.Message)); } } } } [HarmonyPatch(typeof(Container), "CheckForChanges")] internal static class ContainerCheckForChangesPatch { private static void Postfix(Container __instance) { try { ContainerSpatialIndex.Register(__instance); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Could not refresh a container index entry: " + ex.Message)); } } } } [HarmonyPatch(typeof(CraftingStation), "Interact")] internal static class CraftingStationInteractPatch { private static bool Prefix(CraftingStation __instance, Humanoid user, ref bool __result) { try { Player val = (Player)(object)((user is Player) ? user : null); if (val == null || !ValheimReflection.CanMutateLocalPlayer(val)) { return true; } if (CraftingRuntime.CanUseStation(__instance, val, out var reason)) { return true; } ((Character)val).Message((MessageType)2, "Station use denied: " + reason, 0, (Sprite)null); __result = false; return false; } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogError((object)("Workshop Access failed closed for this station interaction: " + ex)); } Player val2 = (Player)(object)((user is Player) ? user : null); if (val2 != null && ValheimReflection.CanMutateLocalPlayer(val2) && Configuration.Enabled.Value && CraftingRuntime.IsInitialized) { ((Character)val2).Message((MessageType)2, "Station permissions are temporarily unavailable", 0, (Sprite)null); __result = false; return false; } return true; } } private static void Postfix(CraftingStation __instance, Humanoid user, bool repeat) { try { if (!repeat) { Player val = (Player)(object)((user is Player) ? user : null); if (val != null && val.GetCurrentCraftingStation() == __instance) { CraftingRuntime.ShowStationStatus(__instance, val); } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Could not show the crafting station status: " + ex.Message)); } } } } [HarmonyPatch(typeof(InventoryGui), "DoCrafting")] internal static class InventoryGuiDoCraftingPatch { [HarmonyPriority(0)] [HarmonyAfter(new string[] { "chazman.RunicInventory" })] private static bool Prefix(InventoryGui __instance, Player player, Recipe ___m_craftRecipe, ItemData ___m_craftUpgradeItem, bool ___m_multiCrafting, int ___m_multiCraftAmount) { try { return CraftingRuntime.BeforeCraft(__instance, player, ___m_craftRecipe, ___m_craftUpgradeItem, ___m_multiCrafting, ___m_multiCraftAmount); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogError((object)("Craft preparation failed closed before output creation: " + ex)); } CraftingRuntime.FinishCraft(ex); return false; } } private static Exception Finalizer(Exception __exception) { CraftingRuntime.FinishCraft(__exception); return __exception; } } [HarmonyPatch(typeof(Inventory), "CanAddItem", new Type[] { typeof(GameObject), typeof(int) })] internal static class InventoryCanAddCraftOutputPatch { private static void Postfix(Inventory __instance, GameObject prefab, ref bool __result) { try { __result = CraftingRuntime.AfterCraftCapacityCheck(__instance, prefab, __result); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogError((object)("Craft reservation failed closed at the vanilla capacity check: " + ex)); } CraftingRuntime.CancelPendingCraft(); __result = false; } } } [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(string), typeof(int), typeof(int), typeof(int), typeof(long), typeof(string), typeof(Vector2i), typeof(bool) })] internal static class InventoryAddCraftOutputPatch { private static void Postfix(Inventory __instance, ItemData __result) { try { CraftingRuntime.CraftOutputAdded(__instance, __result); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Could not record the completed craft output: " + ex.Message)); } } } } [HarmonyPatch(typeof(Player), "HaveRequirementItems", new Type[] { typeof(Recipe), typeof(bool), typeof(int), typeof(int) })] internal static class PlayerHaveRequirementItemsPatch { private static bool Prefix(Player __instance, Recipe piece, bool discover, int qualityLevel, int amount, ref bool __result) { return CraftingRuntime.BeforeHaveRecipeRequirements(__instance, piece, discover, qualityLevel, amount, ref __result); } private static void Postfix(Player __instance, Recipe piece, bool discover, int qualityLevel, int amount, ref bool __result) { try { CraftingRuntime.AddNearbyRecipeAvailability(__instance, piece, discover, qualityLevel, amount, ref __result); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Nearby recipe preview was disabled for this check: " + ex.Message)); } } } } [HarmonyPatch(typeof(Player), "HaveRequirements", new Type[] { typeof(Piece), typeof(RequirementMode) })] internal static class PlayerHavePieceRequirementsPatch { private static void Postfix(Player __instance, Piece piece, RequirementMode mode, ref bool __result) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) try { CraftingRuntime.AddNearbyPieceAvailability(__instance, piece, mode, ref __result); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Nearby build preview was disabled for this check: " + ex.Message)); } } } } [HarmonyPatch(typeof(Player), "ConsumeResources", new Type[] { typeof(Requirement[]), typeof(int), typeof(int), typeof(int) })] internal static class PlayerConsumeResourcesPatch { private static bool Prefix(Player __instance, Requirement[] requirements, int qualityLevel, int itemQuality, int multiplier) { return CraftingRuntime.BeforeVanillaConsume(__instance, requirements, qualityLevel, itemQuality, multiplier); } } [HarmonyPatch(typeof(Player), "UpdatePlacement")] internal static class PlayerUpdatePlacementPatch { private static void Prefix() { CraftingRuntime.BeginPlacementScope(); } private static Exception Finalizer(Exception __exception) { CraftingRuntime.FinishPlacementScope(__exception); return __exception; } } [HarmonyPatch(typeof(Player), "PlacePiece", new Type[] { typeof(Piece), typeof(Vector3), typeof(Quaternion), typeof(bool) })] internal static class PlayerPlacePieceInstantiationPatch { private static readonly MethodInfo PlacementCreated = typeof(CraftingRuntime).GetMethod("PlacementObjectInstantiated", BindingFlags.Static | BindingFlags.NonPublic) ?? throw new MissingMethodException(typeof(CraftingRuntime).FullName, "PlacementObjectInstantiated"); private static IEnumerable Transpiler(IEnumerable instructions) { //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Expected O, but got Unknown //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Expected O, but got Unknown //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Expected O, but got Unknown //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Expected O, but got Unknown List list = new List(instructions); int num = -1; for (int i = 0; i < list.Count - 1; i++) { if (IsExactGameObjectInstantiate(list[i].operand as MethodInfo) && !(list[i + 1].opcode != OpCodes.Stloc_0)) { if (num >= 0) { throw new InvalidOperationException("Player.PlacePiece contains more than one exact placement Instantiate call."); } num = i; } } if (num < 0) { throw new MissingMethodException("Player.PlacePiece no longer contains the audited GameObject Instantiate -> stloc.0 boundary."); } list.InsertRange(num + 1, (IEnumerable)(object)new CodeInstruction[4] { new CodeInstruction(OpCodes.Dup, (object)null), new CodeInstruction(OpCodes.Ldarg_0, (object)null), new CodeInstruction(OpCodes.Ldarg_1, (object)null), new CodeInstruction(OpCodes.Call, (object)PlacementCreated) }); return list; } private static bool IsExactGameObjectInstantiate(MethodInfo method) { if (method == null || !method.IsGenericMethod || method.DeclaringType != typeof(Object) || !string.Equals(method.Name, "Instantiate", StringComparison.Ordinal)) { return false; } Type[] genericArguments = method.GetGenericArguments(); ParameterInfo[] parameters = method.GetParameters(); if (genericArguments.Length == 1 && genericArguments[0] == typeof(GameObject) && method.ReturnType == typeof(GameObject)) { return parameters.Select((ParameterInfo parameter) => parameter.ParameterType).SequenceEqual(new Type[3] { typeof(GameObject), typeof(Vector3), typeof(Quaternion) }); } return false; } } [HarmonyPatch(typeof(Player), "TryPlacePiece")] internal static class PlayerTryPlacePiecePatch { private static bool Prefix(Player __instance, Piece piece, ref bool __result) { try { if (CraftingRuntime.BeforePlacePiece(__instance, piece, out var reason)) { return true; } ((Character)__instance).Message((MessageType)2, "Nearby building cancelled: " + reason, 0, (Sprite)null); __result = false; return false; } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogError((object)("Build reservation failed closed before placement: " + ex)); } __result = false; return false; } } private static void Postfix(bool __result) { CraftingRuntime.PlacePieceReturned(__result); } } [HarmonyPatch(typeof(InventoryGui), "RepairOneItem")] internal static class InventoryGuiRepairOneItemPatch { private static bool Prefix(InventoryGui __instance) { try { return !RepairAllRuntime.TryHandle(__instance); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogError((object)("Repair All failed closed on the authoritative process: " + ex)); } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && localPlayer == Player.m_localPlayer && ((Character)localPlayer).IsOwner() && Configuration.Enabled.Value && CraftingRuntime.IsInitialized) { ((Character)localPlayer).Message((MessageType)2, "Repair is temporarily unavailable", 0, (Sprite)null); return false; } return true; } } } [HarmonyPatch(typeof(InventoryGui), "SetupRequirement")] internal static class InventoryGuiSetupRequirementPatch { private static void Postfix(Transform elementRoot, Requirement req, Player player, bool craft, int quality, int craftMultiplier) { //IL_0087: Unknown result type (might be due to invalid IL or missing references) try { if (!(craft ? CraftingRuntime.TryGetAvailabilityBreakdown(player, CraftingRequirementUiContext.CurrentRecipe, req, quality, craftMultiplier, out var carried, out var nearby, out var required, out var state) : CraftingRuntime.TryGetBuildAvailabilityBreakdown(player, CraftingRequirementUiContext.CurrentPiece, req, out carried, out nearby, out required, out state))) { return; } Transform val = elementRoot.Find("res_amount"); TMP_Text val2 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent() : null); if (!((Object)(object)val2 == (Object)null)) { val2.text = CraftingRequirementDisplay.FormatCompactAmount(required, carried, nearby, state); if (CraftingRequirementDisplay.CombinedTotalSatisfies(required, carried, nearby, state)) { ((Graphic)val2).color = Color.white; } UITooltip component = ((Component)elementRoot).GetComponent(); if ((Object)(object)component != (Object)null) { string text = component.m_text ?? string.Empty; string text2 = CraftingRequirementDisplay.FormatTooltipBreakdown(required, carried, nearby, state); component.m_text = ((text.Length == 0) ? text2 : (text + "\n" + text2)); } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Could not add carried/nearby requirement counts: " + ex.Message)); } } } } [HarmonyPatch(typeof(Hud), "SetupPieceInfo", new Type[] { typeof(Piece) })] internal static class HudSetupPieceInfoPatch { private static void Prefix(Piece piece, out Piece __state) { __state = CraftingRequirementUiContext.PushPiece(piece); } private static void Postfix(Piece __state) { CraftingRequirementUiContext.RestorePiece(__state); } private static Exception Finalizer(Exception __exception, Piece __state) { CraftingRequirementUiContext.RestorePiece(__state); return __exception; } } [HarmonyPatch(typeof(InventoryGui), "SetupRequirementList", new Type[] { typeof(int), typeof(Player), typeof(bool), typeof(int) })] internal static class InventoryGuiSetupRequirementListPatch { private static void Prefix(InventoryGui __instance, out Recipe __state) { __state = CraftingRequirementUiContext.Push(__instance); } private static void Postfix(Recipe __state) { CraftingRequirementUiContext.Restore(__state); } private static Exception Finalizer(Exception __exception, Recipe __state) { CraftingRequirementUiContext.Restore(__state); return __exception; } } internal static class RepairAllRuntime { private static readonly MethodInfo CanRepairMethod = AccessTools.Method(typeof(InventoryGui), "CanRepair", (Type[])null, (Type[])null); internal static bool TryHandle(InventoryGui gui) { //IL_022c: Unknown result type (might be due to invalid IL or missing references) //IL_0231: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if (!Configuration.Enabled.Value) { CraftingDiagnostics.TraceAction("repair-all-bypass", "mod-disabled", "vanilla repairs one item"); return false; } if (!Configuration.RepairAll.Value) { CraftingDiagnostics.TraceAction("repair-all-bypass", "repair-all-disabled", "vanilla repairs one item"); return false; } if (!CraftingRuntime.IsInitialized) { CraftingDiagnostics.TraceAction("repair-all-bypass", "runtime-unavailable", "vanilla repairs one item"); return false; } if ((Object)(object)localPlayer == (Object)null || localPlayer != Player.m_localPlayer || !((Character)localPlayer).IsOwner()) { CraftingDiagnostics.TraceAction("repair-all-bypass", "local-player-owner-required", "vanilla repairs one item"); return false; } CraftingStation currentCraftingStation = localPlayer.GetCurrentCraftingStation(); if ((Object)(object)currentCraftingStation == (Object)null && !localPlayer.NoCostCheat()) { CraftingDiagnostics.TraceAction("repair-all-bypass", "crafting-station-required", "vanilla handles the repair press"); return false; } if ((Object)(object)currentCraftingStation != (Object)null) { if (!CraftingRuntime.CanUseStation(currentCraftingStation, localPlayer, out var reason)) { ((Character)localPlayer).Message((MessageType)2, "Station use denied: " + reason, 0, (Sprite)null); CraftingDiagnostics.TraceAction("repair-all-cancelled", "station-use-denied:" + reason); return true; } if (!currentCraftingStation.CheckUsable(localPlayer, false)) { CraftingDiagnostics.TraceAction("repair-all-bypass", "station-not-usable", "vanilla handles the repair press"); return false; } } if (CanRepairMethod == null) { CraftingDiagnostics.TraceAction("repair-all-bypass", "valheim-can-repair-method-unavailable", "vanilla repairs one item"); return false; } List list = new List(); Inventory inventory = ((Humanoid)localPlayer).GetInventory(); inventory.GetWornItems(list); int num = 0; foreach (ItemData item in list) { if ((bool)CanRepairMethod.Invoke(gui, new object[1] { item })) { float maxDurability = item.GetMaxDurability(); if (!(maxDurability <= 0f) && !(item.m_durability >= maxDurability)) { ((Character)localPlayer).RaiseSkill((SkillType)107, 1f - item.m_durability / maxDurability); item.m_durability = maxDurability; num++; } } } if (num == 0) { ((Character)localPlayer).Message((MessageType)2, "No more item to repair", 0, (Sprite)null); CraftingDiagnostics.TraceAction("repair-all", "nothing-repairable"); return true; } ValheimReflection.NotifyInventoryChanged(inventory); if ((Object)(object)currentCraftingStation != (Object)null) { currentCraftingStation.m_repairItemDoneEffects.Create(((Component)currentCraftingStation).transform.position, Quaternion.identity, (Transform)null, 1f, -1); } ((Character)localPlayer).Message((MessageType)2, $"Repaired {num} items", 0, (Sprite)null); CraftingDiagnostics.TraceAction("repair-all", "completed", "items=" + num); return true; } } internal sealed class ValheimMaterialSource : IMutableMaterialSource { private readonly struct Removal { internal ItemData Original { get; } internal int Quantity { get; } internal Removal(ItemData original, int quantity) { Original = original; Quantity = quantity; } } private sealed class InventoryRestoreToken : IMaterialRestoreToken { private readonly Inventory _inventory; private readonly ZPackage _snapshot; private bool _active = true; internal InventoryRestoreToken(Inventory inventory, ZPackage snapshot) { _inventory = inventory; _snapshot = snapshot; } public bool Restore() { if (!_active) { return true; } try { ValheimReflection.RestoreInventory(_inventory, _snapshot); _active = false; return true; } catch { return false; } } } private readonly Inventory _inventory; private readonly MaterialSourceKind _kind; private readonly float _distanceSquared; private readonly string[] _resourceIds; private readonly Func _eligibility; public string SourceId { get; } internal bool IsEligible => _eligibility(); internal ValheimMaterialSource(string sourceId, Inventory inventory, MaterialSourceKind kind, float distanceSquared, IEnumerable resourceIds, Func eligibility) { SourceId = sourceId ?? throw new ArgumentNullException("sourceId"); _inventory = inventory ?? throw new ArgumentNullException("inventory"); _kind = kind; _distanceSquared = distanceSquared; _resourceIds = new List(resourceIds ?? throw new ArgumentNullException("resourceIds")).ToArray(); _eligibility = eligibility ?? throw new ArgumentNullException("eligibility"); } public MaterialSourceSnapshot Snapshot() { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); if (IsEligible) { string[] resourceIds = _resourceIds; foreach (string text in resourceIds) { dictionary[text] = ValheimReflection.CountRequirementItems(_inventory, text); } } return new MaterialSourceSnapshot(SourceId, _kind, _distanceSquared, dictionary); } public bool TryTake(string resourceId, int quantity, out IMaterialRestoreToken restoreToken) { restoreToken = null; if (!IsEligible || quantity <= 0) { return false; } List allItemsInGridOrder = _inventory.GetAllItemsInGridOrder(); List list = new List(); int num = quantity; for (int i = 0; i < allItemsInGridOrder.Count; i++) { if (num <= 0) { break; } ItemData val = allItemsInGridOrder[i]; if (ValheimReflection.IsUsableRequirementItem(val, resourceId)) { int num2 = Math.Min(val.m_stack, num); list.Add(new Removal(val, num2)); num -= num2; } } if (num != 0) { return false; } if (!ValheimReflection.CanRoundTripInventory(_inventory)) { return false; } ZPackage val2 = ValheimReflection.SaveInventory(_inventory); try { foreach (Removal item in list) { if (!_inventory.RemoveItem(item.Original, item.Quantity)) { ValheimReflection.RestoreInventory(_inventory, val2); return false; } } } catch { ValheimReflection.RestoreInventory(_inventory, val2); throw; } restoreToken = new InventoryRestoreToken(_inventory, val2); return true; } } internal static class ValheimReflection { private static readonly FieldInfo ContainerViewField = AccessTools.Field(typeof(Container), "m_nview"); private static readonly FieldInfo StationViewField = AccessTools.Field(typeof(CraftingStation), "m_nview"); private static readonly FieldInfo WardAreasField = AccessTools.Field(typeof(PrivateArea), "m_allAreas"); private static readonly MethodInfo InventoryChangedMethod = AccessTools.Method(typeof(Inventory), "Changed", (Type[])null, (Type[])null); private static readonly MethodInfo ContainerCheckAccessMethod = AccessTools.Method(typeof(Container), "CheckAccess", (Type[])null, (Type[])null); private static readonly MethodInfo WardIsEnabledMethod = AccessTools.Method(typeof(PrivateArea), "IsEnabled", (Type[])null, (Type[])null); private static readonly MethodInfo WardIsInsideMethod = AccessTools.Method(typeof(PrivateArea), "IsInside", (Type[])null, (Type[])null); private static readonly FieldInfo SelectedRecipePairField = AccessTools.Field(typeof(InventoryGui), "m_selectedRecipe"); private static readonly FieldInfo SelectedRecipeValueField = SelectedRecipePairField?.FieldType.GetField("k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic); internal static bool CanMutateLocalPlayer(Player player) { if ((Object)(object)player != (Object)null && player == Player.m_localPlayer) { return ((Character)player).IsOwner(); } return false; } internal static Recipe SelectedRecipe(InventoryGui inventoryGui) { if ((Object)(object)inventoryGui == (Object)null || SelectedRecipePairField == null || SelectedRecipeValueField == null) { return null; } try { object value = SelectedRecipePairField.GetValue(inventoryGui); return (Recipe)((value == null) ? null : /*isinst with value type is only supported in some contexts*/); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Could not resolve Valheim's selected crafting recipe: " + ex.GetType().Name + ".")); } return null; } } internal static ZNetView GetView(Container container) { if (!((Object)(object)container == (Object)null)) { object? obj = ContainerViewField?.GetValue(container); return (ZNetView)((obj is ZNetView) ? obj : null); } return null; } internal static ZNetView GetView(CraftingStation station) { if (!((Object)(object)station == (Object)null)) { object? obj = StationViewField?.GetValue(station); return (ZNetView)((obj is ZNetView) ? obj : null); } return null; } internal unsafe static string ContainerEndpointId(Container container) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) ZNetView view = GetView(container); ZDO val = (((Object)(object)view != (Object)null && view.IsValid()) ? view.GetZDO() : null); if (val == null || !val.IsValid()) { return "valheim.instance:" + ((Object)container).GetInstanceID(); } ZDOID uid = val.m_uid; return "valheim.zdo:" + ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString(); } internal unsafe static string StationEndpointId(CraftingStation station) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) ZNetView view = GetView(station); ZDO val = (((Object)(object)view != (Object)null && view.IsValid()) ? view.GetZDO() : null); if (val == null || !val.IsValid()) { return "valheim.station:" + ((Object)station).GetInstanceID(); } ZDOID uid = val.m_uid; return "valheim.zdo:" + ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString(); } internal static long StationRevision(CraftingStation station) { ZNetView view = GetView(station); ZDO val = (((Object)(object)view != (Object)null && view.IsValid()) ? view.GetZDO() : null); if (val != null) { return val.DataRevision; } return 0L; } internal static ZDO StationZdo(CraftingStation station) { ZNetView view = GetView(station); if (!((Object)(object)view != (Object)null) || !view.IsValid()) { return null; } return view.GetZDO(); } internal static void NotifyInventoryChanged(Inventory inventory) { InventoryChangedMethod?.Invoke(inventory, Array.Empty()); } internal static ZPackage SaveInventory(Inventory inventory) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown if (inventory == null) { throw new ArgumentNullException("inventory"); } ZPackage val = new ZPackage(); inventory.Save(val); return val; } internal static void RestoreInventory(Inventory inventory, ZPackage package) { //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Expected O, but got Unknown //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected O, but got Unknown if (inventory == null) { throw new ArgumentNullException("inventory"); } if (package == null) { throw new ArgumentNullException("package"); } byte[] array = package.GetArray(); string text = Convert.ToBase64String(array); LoadExactInventoryShadow(inventory, array, text); byte[] array2 = SaveInventory(inventory).GetArray(); string text2 = Convert.ToBase64String(array2); LoadExactInventoryShadow(inventory, array2, text2); Action onChanged = inventory.m_onChanged; inventory.m_onChanged = null; try { inventory.Load(new ZPackage(array)); if (!string.Equals(SaveInventory(inventory).GetBase64(), text, StringComparison.Ordinal)) { throw new InvalidOperationException("The exact crafting inventory snapshot did not round-trip."); } } catch (Exception ex) { try { inventory.Load(new ZPackage(array2)); if (!string.Equals(SaveInventory(inventory).GetBase64(), text2, StringComparison.Ordinal)) { throw new InvalidOperationException("The original crafting inventory did not restore."); } } catch (Exception ex2) { throw new InvalidOperationException("Crafting snapshot restoration failed and its original inventory could not be recovered.", new AggregateException(ex, ex2)); } throw new InvalidOperationException("Crafting snapshot restoration was rejected; its original inventory was restored.", ex); } finally { inventory.m_onChanged = onChanged; } try { NotifyInventoryChanged(inventory); } catch (Exception ex3) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Inventory state was restored exactly, but a Changed subscriber threw: " + ex3.GetType().Name + ".")); } throw; } if (!string.Equals(SaveInventory(inventory).GetBase64(), text, StringComparison.Ordinal)) { throw new InvalidOperationException("The restored crafting inventory changed during publication."); } } internal static bool CanRoundTripInventory(Inventory inventory) { if (inventory == null) { return false; } try { byte[] array = SaveInventory(inventory).GetArray(); LoadExactInventoryShadow(inventory, array, Convert.ToBase64String(array)); return true; } catch { return false; } } internal static Inventory LoadExactInventoryShadow(Inventory shape, byte[] payload, string expected) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown //IL_0061: Expected O, but got Unknown if (shape == null || payload == null || payload.Length == 0 || string.IsNullOrEmpty(expected)) { throw new InvalidOperationException("An exact crafting inventory snapshot is unavailable."); } Inventory val = new Inventory(shape.GetName(), (Sprite)null, shape.GetWidth(), shape.GetHeight()); val.Load(new ZPackage(payload)); if (!string.Equals(SaveInventory(val).GetBase64(), expected, StringComparison.Ordinal)) { throw new InvalidOperationException("A crafting inventory snapshot is not an exact round trip under current item definitions."); } return val; } internal static bool DlcAllows(string dlcId) { if (string.IsNullOrEmpty(dlcId)) { return true; } DLCMan instance = DLCMan.instance; return DlcEligibility.IsAllowed(dlcId, (Object)(object)instance != (Object)null, (Object)(object)instance != (Object)null && instance.IsDLCInstalled(dlcId)); } internal static bool ContainerAllows(Container container, long playerId) { if ((Object)(object)container != (Object)null && ContainerCheckAccessMethod != null) { return (bool)ContainerCheckAccessMethod.Invoke(container, new object[1] { playerId }); } return false; } internal static string ResourceId(ItemDrop drop) { if ((Object)(object)drop == (Object)null) { return string.Empty; } return NormalizePrefabId(((Object)(object)drop.m_itemData?.m_dropPrefab != (Object)null) ? ((Object)drop.m_itemData.m_dropPrefab).name : (((Object)(object)((Component)drop).gameObject != (Object)null) ? ((Object)((Component)drop).gameObject).name : string.Empty), drop.m_itemData?.m_shared?.m_name); } internal static string ResourceId(ItemData item) { if (item == null) { return string.Empty; } return NormalizePrefabId(((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : string.Empty, item.m_shared?.m_name); } internal static string PiecePrefabId(Piece piece) { if (!((Object)(object)piece == (Object)null)) { return NormalizePrefabId(((Object)(object)((Component)piece).gameObject != (Object)null) ? ((Object)((Component)piece).gameObject).name : string.Empty, piece.m_name); } return string.Empty; } internal static bool IsUsableRequirementItem(ItemData item, string resourceId) { if (item != null && item.m_worldLevel >= Game.m_worldLevel) { return string.Equals(ResourceId(item), resourceId, StringComparison.Ordinal); } return false; } internal static int CountRequirementItems(Inventory inventory, string resourceId) { if (inventory == null || string.IsNullOrEmpty(resourceId)) { return 0; } int num = 0; foreach (ItemData allItem in inventory.GetAllItems()) { if (IsUsableRequirementItem(allItem, resourceId)) { num = ((num > int.MaxValue - Math.Max(0, allItem.m_stack)) ? int.MaxValue : (num + Math.Max(0, allItem.m_stack))); } } return num; } internal static bool WardIsEnabled(PrivateArea area) { if ((Object)(object)area != (Object)null && WardIsEnabledMethod != null) { return (bool)WardIsEnabledMethod.Invoke(area, Array.Empty()); } return false; } internal static bool WardContains(PrivateArea area, Vector3 position) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)area != (Object)null && WardIsInsideMethod != null) { return (bool)WardIsInsideMethod.Invoke(area, new object[2] { position, 0f }); } return false; } internal static WardResolution ResolveWard(Vector3 position) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) if (!(WardAreasField?.GetValue(null) is List list)) { return new WardResolution(hasWard: false, allowed: true, ambiguous: false); } int num = 0; for (int i = 0; i < list.Count; i++) { PrivateArea val = list[i]; if ((Object)(object)val != (Object)null && WardIsEnabled(val) && WardContains(val, position)) { num++; } } if (num == 0) { return new WardResolution(hasWard: false, allowed: true, ambiguous: false); } bool allowed = PrivateArea.CheckAccess(position, 0f, false, false); return new WardResolution(hasWard: true, allowed, num > 1); } private static string NormalizePrefabId(string prefab, string fallback) { string text = (string.IsNullOrWhiteSpace(prefab) ? fallback : prefab); text = (text ?? string.Empty).Trim(); if (text.EndsWith("(Clone)", StringComparison.Ordinal)) { text = text.Substring(0, text.Length - "(Clone)".Length).TrimEnd(); } return text; } } internal readonly struct WardResolution { internal bool HasWard { get; } internal bool Allowed { get; } internal bool Ambiguous { get; } internal WardResolution(bool hasWard, bool allowed, bool ambiguous) { HasWard = hasWard; Allowed = allowed; Ambiguous = ambiguous; } } internal static class WorkshopAccessCommands { [CompilerGenerated] private static class <>O { public static ConsoleEvent <0>__OnCommand; } private static ConsoleCommand _command; internal static void Initialize() { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown if (_command == null) { object obj = <>O.<0>__OnCommand; if (obj == null) { ConsoleEvent val = OnCommand; <>O.<0>__OnCommand = val; obj = (object)val; } _command = new ConsoleCommand("runiccrafting_access", "Show or configure the owned nearby station: show | station | materials | group | approve | unapprove ", (ConsoleEvent)obj, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } } private static void OnCommand(ConsoleEventArgs args) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if (!ValheimReflection.CanMutateLocalPlayer(localPlayer)) { args.Context.AddString("Runic Crafting: workshop configuration requires the local player owner."); return; } CraftingStation val = localPlayer.GetCurrentCraftingStation() ?? CraftingStation.GetCraftingStation(((Component)localPlayer).transform.position); if ((Object)(object)val == (Object)null) { args.Context.AddString("Runic Crafting: stand at or use the station you want to configure."); return; } Piece component = ((Component)val).GetComponent(); if ((Object)(object)component == (Object)null || component.GetCreator() != localPlayer.GetPlayerID()) { args.Context.AddString("Runic Crafting: only the station owner can change Workshop Access."); return; } ZNetView view = ValheimReflection.GetView(val); ZDO val2 = (((Object)(object)view != (Object)null && view.IsValid() && view.IsOwner()) ? view.GetZDO() : null); if (val2 == null) { args.Context.AddString("Runic Crafting: the authoritative station record is unavailable."); return; } string text = ((args.Args.Length > 1) ? args.Args[1].Trim().ToLowerInvariant() : "show"); switch (text) { case "show": Show(args.Context, val2); break; case "station": case "materials": { if (args.Args.Length < 3 || !WorkshopAccessRuntime.TryParsePolicy(args.Args[2], out var policy)) { args.Context.AddString("Policy must be everyone, approved, owner, nobody, ward, ward.exceptions, or group."); break; } string text3 = ((text == "station") ? "runic.crafting.station-use" : "runic.crafting.local-materials"); val2.Set(text3, WorkshopAccessRuntime.PolicyWireName(policy)); args.Context.AddString("Runic Crafting: " + text + " policy set to " + WorkshopAccessRuntime.PolicyWireName(policy) + "."); break; } case "approve": case "unapprove": { if (args.Args.Length < 3 || !long.TryParse(args.Args[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result == 0L) { args.Context.AddString("Runic Crafting: supply a non-zero stable Valheim player ID."); break; } SortedSet sortedSet = new SortedSet(); string[] array = val2.GetString("runic.crafting.approved", string.Empty).Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { if (long.TryParse(array[i].Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2)) { sortedSet.Add(result2); } } if (text == "approve") { sortedSet.Add(result); } else { sortedSet.Remove(result); } val2.Set("runic.crafting.approved", string.Join(",", sortedSet.Select((long value) => value.ToString(CultureInfo.InvariantCulture)))); args.Context.AddString(string.Format("Runic Crafting: player {0} {1}.", result, (text == "approve") ? "approved" : "removed from approvals")); break; } case "group": { string text2 = ((args.Args.Length > 2) ? args.Args[2] : string.Empty); if (!WorkshopAccessRuntime.IsCanonicalGroupId(text2)) { args.Context.AddString("Runic Crafting: supply the exact 32-character canonical group UUID shown by runic_group list."); break; } val2.Set("runic.crafting.group-id", text2); args.Context.AddString("Runic Crafting: workshop group set to " + text2 + "."); break; } default: args.Context.AddString("Usage: runiccrafting_access show | station | materials | group | approve | unapprove "); break; } } private static void Show(Terminal terminal, ZDO zdo) { string text = zdo.GetString("runic.crafting.station-use", WorkshopAccessRuntime.PolicyWireName(Configuration.DefaultStationUse.Value)); string text2 = zdo.GetString("runic.crafting.local-materials", WorkshopAccessRuntime.PolicyWireName(Configuration.DefaultLocalMaterialUse.Value)); string text3 = zdo.GetString("runic.crafting.approved", string.Empty); string text4 = WorkshopAccessRuntime.ReadGroupId(zdo); terminal.AddString("Runic Crafting Workshop Access: station=" + text + "; materials=" + text2 + "; group=" + ((text4.Length == 0) ? "none" : text4) + "; approved=" + ((text3.Length == 0) ? "none" : text3)); } } internal sealed class WorkshopAccessRuntime { internal const string StationUseKey = "runic.crafting.station-use"; internal const string LocalMaterialsKey = "runic.crafting.local-materials"; internal const string ApprovedKey = "runic.crafting.approved"; internal const string GroupIdKey = "runic.crafting.group-id"; private readonly WorkshopAccessEvaluator _evaluator = new WorkshopAccessEvaluator(); internal WorkshopAccessDecision Evaluate(CraftingStation station, Player player, WorkshopAction action) { //IL_00d8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)station == (Object)null || !ValheimReflection.CanMutateLocalPlayer(player)) { return new WorkshopAccessDecision(allowed: false, action, "local-player-owner-required"); } ZDO val = ValheimReflection.StationZdo(station); if (val == null || !val.IsValid()) { return new WorkshopAccessDecision(allowed: false, action, "station-record-unavailable"); } Piece component = ((Component)station).GetComponent(); string ownerId = (((Object)(object)component == (Object)null) ? 0 : component.GetCreator()).ToString(CultureInfo.InvariantCulture); string subjectId = player.GetPlayerID().ToString(CultureInfo.InvariantCulture); WorkshopPolicyKind workshopPolicyKind = ReadPolicy(val, "runic.crafting.station-use", Configuration.DefaultStationUse.Value); WorkshopPolicyKind workshopPolicyKind2 = ReadPolicy(val, "runic.crafting.local-materials", Configuration.DefaultLocalMaterialUse.Value); string groupId = ReadGroupId(val); bool groupProviderAvailable = false; bool member = false; if (((action == WorkshopAction.StationUse) ? workshopPolicyKind : workshopPolicyKind2) == WorkshopPolicyKind.Group) { groupProviderAvailable = TryResolveGroup(groupId, player.GetPlayerID(), out member); } WardResolution wardResolution = ValheimReflection.ResolveWard(((Component)station).transform.position); bool wardAllows = !wardResolution.HasWard || (wardResolution.Allowed && !wardResolution.Ambiguous); WorkshopAccessProfile profile = new WorkshopAccessProfile(ownerId, workshopPolicyKind, workshopPolicyKind2, ReadApproved(val)); return _evaluator.Evaluate(profile, action, subjectId, new WorkshopAccessContext(wardAllows, groupProviderAvailable, member)); } internal static string ReadGroupId(ZDO zdo) { string text = ((zdo != null) ? zdo.GetString("runic.crafting.group-id", string.Empty) : null) ?? string.Empty; if (!IsCanonicalGroupId(text)) { return string.Empty; } return text; } private static WorkshopPolicyKind ReadPolicy(ZDO zdo, string key, WorkshopPolicyKind fallback) { if (zdo == null) { return fallback; } if (!TryParsePolicy(zdo.GetString(key, string.Empty), out var policy)) { return fallback; } return policy; } internal static bool TryParsePolicy(string value, out WorkshopPolicyKind policy) { switch ((value ?? string.Empty).Trim().ToLowerInvariant()) { case "everyone": policy = WorkshopPolicyKind.Everyone; return true; case "approved": policy = WorkshopPolicyKind.Approved; return true; case "owner": policy = WorkshopPolicyKind.Owner; return true; case "nobody": policy = WorkshopPolicyKind.Nobody; return true; case "ward": policy = WorkshopPolicyKind.Ward; return true; case "ward.exceptions": policy = WorkshopPolicyKind.WardWithExceptions; return true; case "group": policy = WorkshopPolicyKind.Group; return true; default: policy = (WorkshopPolicyKind)0; return false; } } internal static string PolicyWireName(WorkshopPolicyKind policy) { return policy switch { WorkshopPolicyKind.Everyone => "everyone", WorkshopPolicyKind.Approved => "approved", WorkshopPolicyKind.Owner => "owner", WorkshopPolicyKind.Nobody => "nobody", WorkshopPolicyKind.Ward => "ward", WorkshopPolicyKind.WardWithExceptions => "ward.exceptions", WorkshopPolicyKind.Group => "group", _ => "nobody", }; } internal static bool IsCanonicalGroupId(string value) { if (value != null && value.Length == 32 && Guid.TryParseExact(value, "N", out var result) && result != Guid.Empty) { return string.Equals(value, result.ToString("N"), StringComparison.Ordinal); } return false; } private static IEnumerable ReadApproved(ZDO zdo) { if (zdo == null) { return Array.Empty(); } long result; return (from value in zdo.GetString("runic.crafting.approved", string.Empty).Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries) select value.Trim() into value where long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out result) && result != 0 select value).Distinct(StringComparer.Ordinal).ToArray(); } private static bool TryResolveGroup(string groupId, long playerId, out bool member) { member = false; if (!IsCanonicalGroupId(groupId) || playerId == 0L) { return false; } try { MethodInfo methodInfo = (from assembly in AppDomain.CurrentDomain.GetAssemblies() select assembly.GetType("RunicPortals.Api.GroupIntegrationApi", throwOnError: false)).FirstOrDefault((Type type) => type != null)?.GetMethod("TryIsMember", BindingFlags.Static | BindingFlags.Public, null, new Type[3] { typeof(string), typeof(long), typeof(bool).MakeByRefType() }, null); if (methodInfo == null) { return false; } object[] array = new object[3] { groupId, playerId, false }; object obj = methodInfo.Invoke(null, array); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } bool flag2 = (byte)((uint)num & (flag ? 1u : 0u)) != 0; bool flag3 = default(bool); int num2; if (flag2) { obj = array[2]; if (obj is bool) { flag3 = (bool)obj; num2 = 1; } else { num2 = 0; } } else { num2 = 0; } member = (byte)((uint)num2 & (flag3 ? 1u : 0u)) != 0; return flag2; } catch { member = false; return false; } } } } namespace RunicCrafting.Domain { internal enum BuildMaterialQueryScope { RequiredStationUnavailable, StationLocal, PlayerLocalStationless } internal static class BuildMaterialQueryScopePolicy { internal static BuildMaterialQueryScope Resolve(bool pieceRequiresCraftingStation, bool requiredStationResolved) { if (!pieceRequiresCraftingStation) { return BuildMaterialQueryScope.PlayerLocalStationless; } if (!requiredStationResolved) { return BuildMaterialQueryScope.RequiredStationUnavailable; } return BuildMaterialQueryScope.StationLocal; } } public static class DlcEligibility { public static bool IsAllowed(string dlcId, bool managerAvailable, bool installed) { if (string.IsNullOrEmpty(dlcId)) { return true; } return managerAvailable && installed; } } public sealed class ExactMaterialPlanner { public const int MaximumRequirements = 256; public const int MaximumSources = 129; public bool TryPlan(IEnumerable requirements, IEnumerable sources, out MaterialPlan plan, out string denialReason) { plan = null; denialReason = string.Empty; if (requirements == null || sources == null) { denialReason = "invalid-request"; return false; } List list; List list2; try { list = CanonicalizeRequirements(requirements); list2 = sources.Take(130).ToList(); } catch (Exception) { denialReason = "invalid-request"; return false; } if (list.Count == 0 || list2.Count > 129) { denialReason = ((list2.Count > 129) ? "source-limit" : "no-requirements"); return false; } if (list2.Any((MaterialSourceSnapshot source) => source == null) || list2.Select((MaterialSourceSnapshot source) => source.SourceId).Distinct(StringComparer.Ordinal).Count() != list2.Count) { denialReason = "invalid-sources"; return false; } list2.Sort(CompareSources); List list3 = new List(); foreach (MaterialRequirement item in list) { int num = item.Quantity; foreach (MaterialSourceSnapshot item2 in list2) { int num2 = item2.Available(item.ResourceId); if (num2 > 0) { int num3 = Math.Min(num2, num); list3.Add(new MaterialPlanLine(item2.SourceId, item.ResourceId, num3)); num -= num3; if (num == 0) { break; } } } if (num != 0) { denialReason = "missing:" + item.ResourceId; return false; } } plan = new MaterialPlan(list, list3); return true; } private static List CanonicalizeRequirements(IEnumerable requirements) { SortedDictionary sortedDictionary = new SortedDictionary(StringComparer.Ordinal); int num = 0; foreach (MaterialRequirement requirement in requirements) { if (++num > 256 || requirement == null) { throw new ArgumentException("Invalid requirement list.", "requirements"); } sortedDictionary.TryGetValue(requirement.ResourceId, out var value); sortedDictionary[requirement.ResourceId] = checked(value + requirement.Quantity); } return sortedDictionary.Select((KeyValuePair pair) => new MaterialRequirement(pair.Key, pair.Value)).ToList(); } private static int CompareSources(MaterialSourceSnapshot left, MaterialSourceSnapshot right) { int num = left.Kind.CompareTo(right.Kind); if (num != 0) { return num; } num = left.DistanceSquared.CompareTo(right.DistanceSquared); if (num != 0) { return num; } return StringComparer.Ordinal.Compare(left.SourceId, right.SourceId); } } public sealed class ExactMaterialTransactionEngine { private readonly object _gate = new object(); private readonly ExactMaterialPlanner _planner; public ExactMaterialTransactionEngine(ExactMaterialPlanner planner = null) { _planner = planner ?? new ExactMaterialPlanner(); } public bool TryBegin(IEnumerable requirements, IEnumerable sources, out MaterialConsumptionLease lease, out string denialReason) { lease = null; denialReason = string.Empty; List list = new List(); Monitor.Enter(_gate); try { List list2 = new List(); foreach (IMutableMaterialSource source in sources) { if (source == null || list2.Count >= 129) { denialReason = "source-limit"; Monitor.Exit(_gate); return false; } list2.Add(source); } List list3 = new List(list2.Count); Dictionary dictionary = new Dictionary(StringComparer.Ordinal); foreach (IMutableMaterialSource item in list2) { MaterialSourceSnapshot materialSourceSnapshot = item.Snapshot(); if (!string.Equals(materialSourceSnapshot.SourceId, item.SourceId, StringComparison.Ordinal) || dictionary.ContainsKey(item.SourceId)) { denialReason = "invalid-sources"; Monitor.Exit(_gate); return false; } list3.Add(materialSourceSnapshot); dictionary.Add(item.SourceId, item); } if (!_planner.TryPlan(requirements, list3, out var plan, out denialReason)) { Monitor.Exit(_gate); return false; } foreach (MaterialPlanLine line in plan.Lines) { if (!dictionary[line.SourceId].TryTake(line.ResourceId, line.Quantity, out var restoreToken) || restoreToken == null) { RestoreReverse(list); denialReason = "source-changed:" + line.SourceId; Monitor.Exit(_gate); return false; } list.Add(restoreToken); } lease = new MaterialConsumptionLease(_gate, plan, list3, list); return true; } catch { RestoreReverse(list); Monitor.Exit(_gate); throw; } } internal static bool RestoreReverse(IReadOnlyList tokens) { bool flag = true; for (int num = tokens.Count - 1; num >= 0; num--) { try { flag &= tokens[num].Restore(); } catch (Exception) { flag = false; } } return flag; } } public sealed class MaterialConsumptionLease : IDisposable { private readonly object _gate; private readonly IReadOnlyList _restoreTokens; private int _active = 1; public MaterialPlan Plan { get; } public IReadOnlyList SourceSnapshots { get; } public bool IsActive => Volatile.Read(in _active) == 1; internal MaterialConsumptionLease(object gate, MaterialPlan plan, IReadOnlyList sourceSnapshots, IReadOnlyList restoreTokens) { _gate = gate; Plan = plan; SourceSnapshots = sourceSnapshots; _restoreTokens = restoreTokens; } public void Commit() { if (Interlocked.Exchange(ref _active, 0) == 1) { Monitor.Exit(_gate); } } public bool Rollback() { if (Interlocked.Exchange(ref _active, 0) != 1) { return true; } bool result = ExactMaterialTransactionEngine.RestoreReverse(_restoreTokens); Monitor.Exit(_gate); return result; } public void Dispose() { Rollback(); } } public enum MaterialSourceKind { PlayerInventory, NearbyContainer } public sealed class MaterialRequirement { public string ResourceId { get; } public int Quantity { get; } public MaterialRequirement(string resourceId, int quantity) { if (string.IsNullOrWhiteSpace(resourceId)) { throw new ArgumentException("A resource identifier is required.", "resourceId"); } if (quantity <= 0) { throw new ArgumentOutOfRangeException("quantity"); } ResourceId = resourceId.Trim(); Quantity = quantity; } } public sealed class MaterialSourceSnapshot { private readonly ReadOnlyDictionary _quantities; public string SourceId { get; } public MaterialSourceKind Kind { get; } public float DistanceSquared { get; } public IReadOnlyDictionary Quantities => _quantities; public MaterialSourceSnapshot(string sourceId, MaterialSourceKind kind, float distanceSquared, IDictionary quantities) { if (string.IsNullOrWhiteSpace(sourceId)) { throw new ArgumentException("A source identifier is required.", "sourceId"); } if (float.IsNaN(distanceSquared) || float.IsInfinity(distanceSquared) || distanceSquared < 0f) { throw new ArgumentOutOfRangeException("distanceSquared"); } if (quantities == null) { throw new ArgumentNullException("quantities"); } SourceId = sourceId.Trim(); Kind = kind; DistanceSquared = distanceSquared; Dictionary dictionary = new Dictionary(StringComparer.Ordinal); foreach (KeyValuePair quantity in quantities) { if (string.IsNullOrWhiteSpace(quantity.Key) || quantity.Value < 0) { throw new ArgumentException("Source quantities must have valid IDs and non-negative amounts.", "quantities"); } dictionary[quantity.Key.Trim()] = quantity.Value; } _quantities = new ReadOnlyDictionary(dictionary); } public int Available(string resourceId) { if (resourceId == null || !_quantities.TryGetValue(resourceId, out var value)) { return 0; } return value; } } public sealed class MaterialPlanLine { public string SourceId { get; } public string ResourceId { get; } public int Quantity { get; } public MaterialPlanLine(string sourceId, string resourceId, int quantity) { if (string.IsNullOrWhiteSpace(sourceId)) { throw new ArgumentException("Source ID is required.", "sourceId"); } if (string.IsNullOrWhiteSpace(resourceId)) { throw new ArgumentException("Resource ID is required.", "resourceId"); } if (quantity <= 0) { throw new ArgumentOutOfRangeException("quantity"); } SourceId = sourceId; ResourceId = resourceId; Quantity = quantity; } } public sealed class MaterialPlan { private readonly ReadOnlyCollection _lines; private readonly ReadOnlyCollection _requirements; public IReadOnlyList Requirements => _requirements; public IReadOnlyList Lines => _lines; internal MaterialPlan(IEnumerable requirements, IEnumerable lines) { _requirements = new List(requirements).AsReadOnly(); _lines = new List(lines).AsReadOnly(); } } public interface IMaterialRestoreToken { bool Restore(); } public interface IMutableMaterialSource { string SourceId { get; } MaterialSourceSnapshot Snapshot(); bool TryTake(string resourceId, int quantity, out IMaterialRestoreToken restoreToken); } public sealed class NearbyCraftingFeatureState { public bool IsReady { get; } public string ReasonCode { get; } public string DisplayLabel { get; } public NearbyCraftingFeatureState(bool isReady, string reasonCode, string displayLabel) { IsReady = isReady; ReasonCode = (string.IsNullOrWhiteSpace(reasonCode) ? "unavailable" : reasonCode); DisplayLabel = (string.IsNullOrWhiteSpace(displayLabel) ? "N:OFF" : displayLabel); } } public static class NearbyCraftingFeatureStateEvaluator { public static NearbyCraftingFeatureState Evaluate(bool masterEnabled, bool craftFromContainersEnabled, bool runtimeAvailable, bool localPlayerOwner, bool recipeAvailable, bool specialOneIngredientRecipe, bool noCostMode, bool stationPresent, bool stationUseAllowed, string stationDenialReason, bool localMaterialsAllowed, string localMaterialsDenialReason) { if (!masterEnabled) { return Blocked("mod-disabled", "N:OFF(mod)"); } if (!craftFromContainersEnabled) { return Blocked("craft-from-containers-disabled", "N:OFF(config)"); } if (!runtimeAvailable) { return Blocked("runtime-unavailable", "N:OFF(startup)"); } if (!localPlayerOwner) { return Blocked("local-player-owner-required", "N:OFF(owner)"); } if (!recipeAvailable) { return Blocked("recipe-unavailable", "N:OFF(recipe)"); } if (specialOneIngredientRecipe) { return Blocked("special-one-ingredient-recipe", "N:OFF(recipe)"); } if (noCostMode) { return Blocked("no-cost-mode", "N:OFF(no-cost)"); } if (!stationPresent) { return Blocked("crafting-station-required", "N:OFF(station)"); } if (!stationUseAllowed) { return Blocked("station-use-denied:" + NormalizeReason(stationDenialReason), "N:OFF(access)"); } if (!localMaterialsAllowed) { return Blocked("local-material-use-denied:" + NormalizeReason(localMaterialsDenialReason), "N:OFF(access)"); } return new NearbyCraftingFeatureState(isReady: true, "ready", "N:ON"); } private static NearbyCraftingFeatureState Blocked(string reason, string label) { return new NearbyCraftingFeatureState(isReady: false, reason, label); } private static string NormalizeReason(string value) { if (!string.IsNullOrWhiteSpace(value)) { return value.Trim(); } return "denied"; } } public static class CraftingRequirementDisplay { public static string Format(int required, int carried, int nearby, NearbyCraftingFeatureState state) { if (required < 0) { throw new ArgumentOutOfRangeException("required"); } if (carried < 0) { throw new ArgumentOutOfRangeException("carried"); } if (nearby < 0) { throw new ArgumentOutOfRangeException("nearby"); } if (state == null) { throw new ArgumentNullException("state"); } if (!state.IsReady) { int num = Math.Max(0, required - carried); return required + "\nC:" + carried + " " + state.DisplayLabel + " M:" + num; } int num2 = ((carried > int.MaxValue - nearby) ? int.MaxValue : (carried + nearby)); int num3 = Math.Max(0, required - num2); return required + "\nC:" + carried + " N:" + nearby + " T:" + num2 + " M:" + num3; } public static string FormatCompactAmount(int required, int carried, int nearby, NearbyCraftingFeatureState state) { Validate(required, carried, nearby, state); return (state.IsReady ? AddSaturated(carried, nearby) : carried) + "/" + required; } public static string FormatTooltipBreakdown(int required, int carried, int nearby, NearbyCraftingFeatureState state) { Validate(required, carried, nearby, state); if (!state.IsReady) { return "Runic materials — Carried: " + carried + " | " + state.DisplayLabel + " | Required: " + required + " | Missing: " + Math.Max(0, required - carried); } int num = AddSaturated(carried, nearby); return "Runic materials — Carried: " + carried + " | Nearby: " + nearby + " | Total: " + num + " | Required: " + required + " | Missing: " + Math.Max(0, required - num); } public static bool CombinedTotalSatisfies(int required, int carried, int nearby, NearbyCraftingFeatureState state) { Validate(required, carried, nearby, state); if (state.IsReady) { return AddSaturated(carried, nearby) >= required; } return false; } private static int AddSaturated(int left, int right) { if (left <= int.MaxValue - right) { return left + right; } return int.MaxValue; } private static void Validate(int required, int carried, int nearby, NearbyCraftingFeatureState state) { if (required < 0) { throw new ArgumentOutOfRangeException("required"); } if (carried < 0) { throw new ArgumentOutOfRangeException("carried"); } if (nearby < 0) { throw new ArgumentOutOfRangeException("nearby"); } if (state == null) { throw new ArgumentNullException("state"); } } } public enum PlacementLeaseDisposition { Rollback, Commit } public static class PlacementLeasePolicy { public static PlacementLeaseDisposition Resolve(bool outputCreated, bool vanillaConsumeReached) { if (!(outputCreated || vanillaConsumeReached)) { return PlacementLeaseDisposition.Rollback; } return PlacementLeaseDisposition.Commit; } } internal static class SpatialMembershipPolicy { internal static bool CanRetainExistingRegistration(bool membershipExists, bool cellUnchanged, bool exactContainerPresent) { return membershipExists && cellUnchanged && exactContainerPresent; } } internal sealed class StationlessPiecePolicy { internal const string DefaultVanillaAllowList = "blackmarble_pile,blackwood_stack,bone_stack,bonfire,coal_pile,fire_pit,fire_pit_iron,grausten_pile,guard_stone,piece_artisanstation,piece_cookingstation,piece_groundtorch_mist,piece_logbench01,piece_pot1,piece_pot1_cracked,piece_pot1_red,piece_pot2,piece_pot2_cracked,piece_pot2_red,piece_pot3,piece_pot3_cracked,piece_pot3_red,piece_workbench,sapling_barley,sapling_carrot,sapling_flax,sapling_jotunpuffs,sapling_magecap,sapling_onion,sapling_seedcarrot,sapling_seedonion,sapling_seedturnip,sapling_turnip,sign,skull_pile,stone_pile,treasure_pile,treasure_stack,wood_core_stack,wood_fine_stack,wood_stack,wood_yggdrasil_stack"; private const int MaximumRuleTextLength = 16384; private const int MaximumRulesPerList = 256; private const int MaximumPrefabIdLength = 128; private static readonly char[] Separators = new char[4] { ',', ';', '\r', '\n' }; private readonly HashSet _allowed; private readonly HashSet _denied; private readonly bool _allowAll; internal int AllowedRuleCount => _allowed.Count; internal int DeniedRuleCount => _denied.Count; internal bool AllowsWildcard => _allowAll; private StationlessPiecePolicy(HashSet allowed, HashSet denied, bool allowAll) { _allowed = allowed; _denied = denied; _allowAll = allowAll; } internal static StationlessPiecePolicy Parse(string allowList, string denyList) { HashSet result; bool wildcard; bool num = TryParseRules(allowList, out result, out wildcard); HashSet result2; bool wildcard2; bool flag = TryParseRules(denyList, out result2, out wildcard2); if (!num) { result.Clear(); wildcard = false; } if (!flag) { wildcard2 = true; } if (wildcard2) { result2.Add("*"); } return new StationlessPiecePolicy(result, result2, wildcard); } internal static StationlessPiecePolicy VanillaDefaults() { return Parse("blackmarble_pile,blackwood_stack,bone_stack,bonfire,coal_pile,fire_pit,fire_pit_iron,grausten_pile,guard_stone,piece_artisanstation,piece_cookingstation,piece_groundtorch_mist,piece_logbench01,piece_pot1,piece_pot1_cracked,piece_pot1_red,piece_pot2,piece_pot2_cracked,piece_pot2_red,piece_pot3,piece_pot3_cracked,piece_pot3_red,piece_workbench,sapling_barley,sapling_carrot,sapling_flax,sapling_jotunpuffs,sapling_magecap,sapling_onion,sapling_seedcarrot,sapling_seedonion,sapling_seedturnip,sapling_turnip,sign,skull_pile,stone_pile,treasure_pile,treasure_stack,wood_core_stack,wood_fine_stack,wood_stack,wood_yggdrasil_stack", string.Empty); } internal StationlessPieceDecision Evaluate(string prefabId) { string text = NormalizePrefabId(prefabId); if (text.Length == 0 || text.Length > 128) { return new StationlessPieceDecision(allowed: false, "stationless-prefab-id-unavailable"); } if (_denied.Contains("*") || _denied.Contains(text)) { return new StationlessPieceDecision(allowed: false, "stationless-prefab-denied:" + text); } if (_allowAll || _allowed.Contains(text)) { return new StationlessPieceDecision(allowed: true, "stationless-prefab-allowed:" + text); } return new StationlessPieceDecision(allowed: false, "stationless-prefab-not-allowed:" + text); } private static bool TryParseRules(string text, out HashSet result, out bool wildcard) { wildcard = false; result = new HashSet(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrWhiteSpace(text)) { return true; } if (text.Length > 16384) { return false; } string[] array = text.Split(Separators, StringSplitOptions.RemoveEmptyEntries); if (array.Length > 256) { return false; } string[] array2 = array; for (int i = 0; i < array2.Length; i++) { string text2 = NormalizePrefabId(array2[i]); if (text2.Length == 0 || text2.Length > 128) { return false; } if (string.Equals(text2, "*", StringComparison.Ordinal)) { wildcard = true; } else { result.Add(text2); } } return true; } private static string NormalizePrefabId(string value) { value = (value ?? string.Empty).Trim(); if (value.EndsWith("(Clone)", StringComparison.OrdinalIgnoreCase)) { value = value.Substring(0, value.Length - "(Clone)".Length).TrimEnd(); } return value; } } internal readonly struct StationlessPieceDecision { internal bool Allowed { get; } internal string ReasonCode { get; } internal StationlessPieceDecision(bool allowed, string reasonCode) { Allowed = allowed; ReasonCode = reasonCode ?? string.Empty; } } public enum WorkshopPolicyKind { Everyone = 1, Approved, Owner, Nobody, Ward, WardWithExceptions, Group } public enum WorkshopAction { StationUse = 1, LocalMaterialUse } public sealed class WorkshopAccessProfile { private readonly HashSet _approved; public string OwnerId { get; } public WorkshopPolicyKind StationUse { get; } public WorkshopPolicyKind LocalMaterialUse { get; } public IReadOnlyCollection ApprovedIds => _approved; public WorkshopAccessProfile(string ownerId, WorkshopPolicyKind stationUse, WorkshopPolicyKind localMaterialUse, IEnumerable approvedIds = null) { OwnerId = (ownerId ?? string.Empty).Trim(); StationUse = stationUse; LocalMaterialUse = localMaterialUse; _approved = new HashSet(from value in approvedIds ?? Array.Empty() where !string.IsNullOrWhiteSpace(value) select value.Trim(), StringComparer.Ordinal); } public bool IsApproved(string subjectId) { if (subjectId != null) { return _approved.Contains(subjectId); } return false; } } public sealed class WorkshopAccessContext { public bool WardAllows { get; } public bool GroupProviderAvailable { get; } public bool GroupMember { get; } public WorkshopAccessContext(bool wardAllows, bool groupProviderAvailable, bool groupMember) { WardAllows = wardAllows; GroupProviderAvailable = groupProviderAvailable; GroupMember = groupMember; } } public sealed class WorkshopAccessDecision { public bool Allowed { get; } public WorkshopAction Action { get; } public string ReasonCode { get; } public WorkshopAccessDecision(bool allowed, WorkshopAction action, string reasonCode) { Allowed = allowed; Action = action; ReasonCode = (string.IsNullOrWhiteSpace(reasonCode) ? "denied" : reasonCode); } } public sealed class WorkshopAccessEvaluator { public WorkshopAccessDecision Evaluate(WorkshopAccessProfile profile, WorkshopAction action, string subjectId, WorkshopAccessContext context) { if (profile == null || string.IsNullOrWhiteSpace(subjectId) || context == null) { return new WorkshopAccessDecision(allowed: false, action, "invalid-context"); } WorkshopPolicyKind workshopPolicyKind = ((action == WorkshopAction.StationUse) ? profile.StationUse : profile.LocalMaterialUse); bool flag = string.Equals(profile.OwnerId, subjectId, StringComparison.Ordinal); bool flag2 = flag || profile.IsApproved(subjectId); switch (workshopPolicyKind) { case WorkshopPolicyKind.Everyone: return new WorkshopAccessDecision(allowed: true, action, "everyone"); case WorkshopPolicyKind.Approved: return new WorkshopAccessDecision(flag2, action, flag2 ? "approved" : "not-approved"); case WorkshopPolicyKind.Owner: return new WorkshopAccessDecision(flag, action, flag ? "owner" : "not-owner"); case WorkshopPolicyKind.Nobody: return new WorkshopAccessDecision(allowed: false, action, "nobody"); case WorkshopPolicyKind.Ward: return new WorkshopAccessDecision(context.WardAllows, action, context.WardAllows ? "ward" : "ward-denied"); case WorkshopPolicyKind.WardWithExceptions: return new WorkshopAccessDecision(flag2 || context.WardAllows, action, flag2 ? "approved-exception" : (context.WardAllows ? "ward" : "ward-denied")); case WorkshopPolicyKind.Group: { bool flag3 = context.GroupProviderAvailable && context.GroupMember; return new WorkshopAccessDecision(flag3, action, (!context.GroupProviderAvailable) ? "group-provider-missing" : (flag3 ? "group" : "not-group-member")); } default: return new WorkshopAccessDecision(allowed: false, action, "unknown-policy"); } } } }