using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using RunicProduction.Contracts; using RunicProduction.Core; using RunicProduction.Integration; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Runic Production")] [assembly: AssemblyDescription("Standalone native-owner production with persistent links and bounded replenishment")] [assembly: AssemblyCompany("ChazmanMods")] [assembly: AssemblyProduct("Runic Production")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: InternalsVisibleTo("RunicProduction.Tests")] [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 RunicProduction { internal static class ProductionConfig { internal static ConfigEntry Enabled { get; private set; } internal static ConfigEntry MaximumLinkRange { get; private set; } internal static ConfigEntry MovedTargetTolerance { get; private set; } internal static ConfigEntry FuelReserve { get; private set; } internal static ConfigEntry PullBatchSize { get; private set; } internal static ConfigEntry CookingStationPrefabAllowList { get; private set; } internal static ConfigEntry CookingStationPrefabDenyList { get; private set; } internal static ConfigEntry RecipeStationPrefabAllowList { get; private set; } internal static ConfigEntry RecipeStationPrefabDenyList { get; private set; } internal static ConfigEntry RecipeNearbyIngredientsEnabled { get; private set; } internal static ConfigEntry RecipeNearbyMaximumSourceChests { get; private set; } internal static ConfigEntry FermenterPrefabAllowList { get; private set; } internal static ConfigEntry FermenterPrefabDenyList { get; private set; } internal static ConfigEntry DefaultIngredientReserve { get; private set; } internal static ConfigEntry IngredientReserveRules { get; private set; } internal static ConfigEntry MaximumLinksPerRole { get; private set; } internal static ConfigEntry DefaultReplenishmentReserve { get; private set; } internal static ConfigEntry ReplenishmentReserveRules { get; private set; } internal static ConfigEntry StockSchedulerIntervalSeconds { get; private set; } internal static ConfigEntry StockSchedulerMaximumOperations { get; private set; } internal static ConfigEntry ShowStatusOverlay { get; private set; } internal static ConfigEntry ShowControlHints { get; private set; } internal static ConfigEntry VerboseLogging { get; private set; } internal static void Bind(ConfigFile config) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Expected O, but got Unknown //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Expected O, but got Unknown //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Expected O, but got Unknown //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Expected O, but got Unknown //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Expected O, but got Unknown //IL_0259: Unknown result type (might be due to invalid IL or missing references) //IL_0263: Expected O, but got Unknown //IL_028a: Unknown result type (might be due to invalid IL or missing references) //IL_0294: Expected O, but got Unknown //IL_02e1: Unknown result type (might be due to invalid IL or missing references) //IL_02eb: Expected O, but got Unknown //IL_0312: Unknown result type (might be due to invalid IL or missing references) //IL_031c: Expected O, but got Unknown Enabled = config.Bind("General", "Enabled", true, "Enable explicit Runic Input, Output, and Replenishment links. Disabling immediately stops automation without leaving operation state behind."); MaximumLinkRange = config.Bind("Links", "MaximumLinkRange", 12f, new ConfigDescription("Maximum station-to-container distance in meters.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 30f), Array.Empty())); MovedTargetTolerance = config.Bind("Links", "MovedTargetTolerance", 0.75f, new ConfigDescription("Distance a linked target may move from its recorded ZDO-root position before automation pauses. The relation remains available for an explicit refresh.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 3f), Array.Empty())); FuelReserve = config.Bind("Fuel", "ProtectedReserve", 10, new ConfigDescription("Minimum fuel items retained in every linked fuel container.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 1000), Array.Empty())); PullBatchSize = config.Bind("Transfers", "PullBatchSize", 1, new ConfigDescription("Maximum items pulled per station update. Capacity and authorization are checked per item.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 10), Array.Empty())); CookingStationPrefabAllowList = config.Bind("Cooking Stations", "AllowedPrefabIds", "piece_cookingstation,piece_cookingstation_iron,piece_oven", "Comma-separated exact prefab IDs allowed to use CookingStation automation. A listed prefab must still pass strict runtime CookingStation compatibility checks."); CookingStationPrefabDenyList = config.Bind("Cooking Stations", "DeniedPrefabIds", string.Empty, "Comma-separated exact prefab IDs denied CookingStation automation. Deny entries override the allow list."); RecipeStationPrefabAllowList = config.Bind("Recipe Stations", "AllowedPrefabIds", "piece_cauldron,piece_MeadCauldron,piece_preptable", "Comma-separated exact prefab IDs allowed to perform signed replenishment recipes. Every station and recipe must still pass strict runtime compatibility and progression checks."); RecipeStationPrefabDenyList = config.Bind("Recipe Stations", "DeniedPrefabIds", string.Empty, "Comma-separated exact recipe-station prefab IDs denied replenishment. Deny entries override the allow list."); RecipeNearbyIngredientsEnabled = config.Bind("Recipe Nearby Ingredients", "Enabled", true, "Use bounded, station-centered discovery of eligible nearby ingredient chests for replenishment recipes, cooking, and fermenting. False preserves exact designated-Input-only behavior. The search radius is Links.MaximumLinkRange. Only loaded, static, accessible chests owned by the same local process qualify; Output, Fuel-compatibility, and Replenishment destinations are excluded unless explicitly linked as Input. Discovery alone never authorizes or mutates a chest."); RecipeNearbyMaximumSourceChests = config.Bind("Recipe Nearby Ingredients", "MaximumSourceChests", 32, new ConfigDescription("Maximum eligible nearby ingredient source chests accepted by one replenishment operation. The loaded-container spatial query is deterministic; exceeding this limit or the hard 64-candidate bound pauses production rather than using a partial source set.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 64), Array.Empty())); FermenterPrefabAllowList = config.Bind("Fermenters", "AllowedPrefabIds", "fermenter", "Comma-separated exact Fermenter prefab IDs allowed to participate in replenishment production."); FermenterPrefabDenyList = config.Bind("Fermenters", "DeniedPrefabIds", string.Empty, "Comma-separated exact Fermenter prefab IDs denied replenishment. Deny entries override the allow list."); DefaultIngredientReserve = config.Bind("Ingredient Reserves", "DefaultProtectedReserve", 0, new ConfigDescription("Default count retained for every exact ingredient prefab in a linked Input chest and independently in every eligible nearby donor chest.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 1000000), Array.Empty())); IngredientReserveRules = config.Bind("Ingredient Reserves", "ProtectedPrefabAmounts", string.Empty, "Semicolon-separated, case-sensitive exact prefab reserves, for example DeerMeat=10;RawMeat=20. Nearby production preserves the configured amount separately in every donor; direct recipes may combine one exact batch across several donors. Invalid or duplicate rules fail stock input consumption closed."); MaximumLinksPerRole = config.Bind("Links", "MaximumChestsPerRole", 8, new ConfigDescription("Single soft limit for Input, Output, and Replenishment chests per station and role. Existing links are retained if this is lowered. The hard safety limit is 16.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 16), Array.Empty())); DefaultReplenishmentReserve = config.Bind("Replenishment Stock", "DefaultReserve", 10, new ConfigDescription("Default target count maintained for each exemplar item in a linked Replenishment chest. Production runs only while the exact item count is below this reserve.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 1000000), Array.Empty())); ReplenishmentReserveRules = config.Bind("Replenishment Stock", "PrefabReserves", string.Empty, "Semicolon-separated exact prefab reserve overrides, for example QueensJam=20;MeadHealthMinor=10. Invalid or duplicate rules pause replenishment safely."); StockSchedulerIntervalSeconds = config.Bind("Stock Scheduler", "IntervalSeconds", 2f, new ConfigDescription("Native-owner fire/lamp and replenishment service quantum. Unloaded time never creates catch-up recipe crafts.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 10f), Array.Empty())); StockSchedulerMaximumOperations = config.Bind("Stock Scheduler", "MaximumOperationsPerQuantum", 64, new ConfigDescription("Global cap shared by fire/lamp, direct-recipe, timed-cooking, and Fermenter operations in one scheduler quantum. Each loaded station receives at most one operation per quantum.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 256), Array.Empty())); ShowStatusOverlay = config.Bind("UI", "ShowStatusOverlay", true, "Append the most recent compact automation result to station hover text."); ShowControlHints = config.Bind("UI", "ShowControlHints", true, "Show the two-step Alt+mouse link workflow, Shift+Alt+mouse unlink workflow, current link counts, and empty roles."); VerboseLogging = config.Bind("Diagnostics", "VerboseLogging", false, "Log link and transfer transitions. Per-frame and container-scan logging is never emitted."); } } internal static class ProductionDiagnostics { private const int MaximumRememberedStations = 2048; private static readonly Dictionary LastStopByStation = new Dictionary(StringComparer.Ordinal); private static ManualLogSource _log; internal static bool RuntimeAvailable { get; private set; } internal static string DisabledReason { get; private set; } internal static void Initialize(ManualLogSource log) { _log = log; RuntimeAvailable = false; DisabledReason = "startup has not completed"; LastStopByStation.Clear(); } internal static void MarkAvailable() { RuntimeAvailable = true; DisabledReason = string.Empty; } internal static void Disable(string reason, Exception exception = null) { RuntimeAvailable = false; DisabledReason = (string.IsNullOrWhiteSpace(reason) ? "unknown adapter failure" : reason); ManualLogSource log = _log; if (log != null) { log.LogError((object)("Runic Production disabled its automation; vanilla production remains active. " + DisabledReason + ((exception == null) ? string.Empty : (" " + exception.GetType().Name + ": " + exception.Message)))); } } internal static void StopChanged(string stationId, ProductionStopCode stopCode, string detail) { if (string.IsNullOrEmpty(stationId) || (LastStopByStation.TryGetValue(stationId, out var value) && value == stopCode)) { return; } if (LastStopByStation.Count >= 2048) { LastStopByStation.Clear(); } LastStopByStation[stationId] = stopCode; ConfigEntry verboseLogging = ProductionConfig.VerboseLogging; if (verboseLogging != null && verboseLogging.Value) { ManualLogSource log = _log; if (log != null) { log.LogInfo((object)$"production.stop station={stationId} code={stopCode} detail={detail}"); } } } internal static bool TryGetStop(string stationId, out ProductionStopCode stopCode) { stopCode = ProductionStopCode.Ready; if (!string.IsNullOrEmpty(stationId)) { return LastStopByStation.TryGetValue(stationId, out stopCode); } return false; } internal static void Configuration(string source) { if (ProductionConfig.Enabled == null) { return; } ManualLogSource log = _log; if (log != null) { log.LogInfo((object)(string.Format("production.config source={0} enabled={1} ", source ?? "unknown", ProductionConfig.Enabled.Value) + $"range={ProductionConfig.MaximumLinkRange.Value:0.##}m " + $"movedTolerance={ProductionConfig.MovedTargetTolerance.Value:0.##}m " + $"fuelReserve={ProductionConfig.FuelReserve.Value} pullBatch={ProductionConfig.PullBatchSize.Value} " + "cookingAllow='" + SafeConfigText(ProductionConfig.CookingStationPrefabAllowList.Value) + "' cookingDeny='" + SafeConfigText(ProductionConfig.CookingStationPrefabDenyList.Value) + "' recipeAllow='" + SafeConfigText(ProductionConfig.RecipeStationPrefabAllowList.Value) + "' recipeDeny='" + SafeConfigText(ProductionConfig.RecipeStationPrefabDenyList.Value) + "' " + $"recipeNearbyIngredients={ProductionConfig.RecipeNearbyIngredientsEnabled.Value} " + $"recipeNearbyMaxSources={ProductionConfig.RecipeNearbyMaximumSourceChests.Value} " + "fermenterAllow='" + SafeConfigText(ProductionConfig.FermenterPrefabAllowList.Value) + "' fermenterDeny='" + SafeConfigText(ProductionConfig.FermenterPrefabDenyList.Value) + "' " + $"defaultIngredientReserve={ProductionConfig.DefaultIngredientReserve.Value} " + "ingredientReserves='" + SafeConfigText(ProductionConfig.IngredientReserveRules.Value) + "' " + $"maximumChestsPerRole={ProductionConfig.MaximumLinksPerRole.Value} " + $"stockInterval={ProductionConfig.StockSchedulerIntervalSeconds.Value:0.##}s " + $"stockMaxOps={ProductionConfig.StockSchedulerMaximumOperations.Value} " + $"statusOverlay={ProductionConfig.ShowStatusOverlay.Value} " + $"controlHints={ProductionConfig.ShowControlHints.Value} verbose={ProductionConfig.VerboseLogging.Value}")); } if (ProductionConfig.Enabled.Value && ProductionConfig.FuelReserve.Value >= 100) { ManualLogSource log2 = _log; if (log2 != null) { log2.LogWarning((object)($"Runic Production fuel reserve is {ProductionConfig.FuelReserve.Value}; " + "a linked container at or below that amount will intentionally supply no fuel.")); } } } internal static void Trace(string message) { ConfigEntry verboseLogging = ProductionConfig.VerboseLogging; if (verboseLogging != null && verboseLogging.Value) { ManualLogSource log = _log; if (log != null) { log.LogInfo((object)("production.input " + (message ?? "unknown"))); } } } internal static void Info(string message) { ManualLogSource log = _log; if (log != null) { log.LogInfo((object)message); } } internal static void Warning(string message) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)message); } } private static string SafeConfigText(string value) { return new string((value ?? string.Empty).Replace('\r', ' ').Replace('\n', ' ').Replace('\t', ' ') .Take(256) .ToArray()); } } [BepInPlugin("chazman.RunicProduction", "Runic Production", "1.0.0")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "chazman.RunicProduction"; public const string Name = "Runic Production"; public const string Version = "1.0.0"; public const string ModuleId = "runic.production"; private Harmony _harmony; private void Awake() { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown ProductionDiagnostics.Initialize(((BaseUnityPlugin)this).Logger); ProductionConfig.Bind(((BaseUnityPlugin)this).Config); ((BaseUnityPlugin)this).Config.SettingChanged += OnSettingChanged; ProductionDiagnostics.Configuration("startup"); try { ValheimAccess.VerifySignatures(); ProductionRuntime.Initialize(); _harmony = new Harmony("chazman.RunicProduction"); _harmony.PatchAll(typeof(Plugin).Assembly); ProductionRuntime.SeedLoadedStations(); NearbyIngredientContainerIndex.SeedLoadedContainers(); ProductionDiagnostics.MarkAvailable(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Runic Production v1.0.0 ready: standalone native-owner production, persistent explicit links, and bounded exemplar replenishment."); } catch (Exception exception) { Cleanup(); ProductionDiagnostics.Disable("startup validation failed", exception); } } private void Update() { if (ProductionDiagnostics.RuntimeAvailable) { ProductionRuntime.Update(); } } private void OnDestroy() { Cleanup(); } private void OnSettingChanged(object sender, SettingChangedEventArgs eventArgs) { try { ProductionRuntime.RefreshConfiguration(); object obj; if (eventArgs == null) { obj = null; } else { ConfigEntryBase changedSetting = eventArgs.ChangedSetting; obj = ((changedSetting != null) ? ((object)changedSetting.Definition).ToString() : null); } if (obj == null) { obj = "setting-change"; } ProductionDiagnostics.Configuration((string)obj); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Runic Production rejected an updated setting: " + ex.Message)); } } private void Cleanup() { ((BaseUnityPlugin)this).Config.SettingChanged -= OnSettingChanged; NearbyIngredientContainerIndex.Clear(); ProductionRuntime.Shutdown(); try { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Runic Production could not remove every Harmony patch: " + ex.Message)); } _harmony = null; } } } namespace RunicProduction.Integration { internal static class CookingStationCompatibility { internal static bool TryDescribeRegistered(GameObject registeredPrefab, CookingStationPrefabPolicy policy, bool requireAllowed, out CookingStation station, out string prefabId, out string failure) { station = null; prefabId = ValheimAccess.PrefabName(registeredPrefab); failure = string.Empty; CookingStation[] array = (((Object)(object)registeredPrefab == (Object)null) ? Array.Empty() : registeredPrefab.GetComponents()); ZNetView[] array2 = (((Object)(object)registeredPrefab == (Object)null) ? Array.Empty() : registeredPrefab.GetComponents()); if ((Object)(object)registeredPrefab == (Object)null || prefabId.Length == 0 || array.Length != 1 || array2.Length != 1 || ((Component)array[0]).gameObject != registeredPrefab || ((Component)array2[0]).gameObject != registeredPrefab || (Object)(object)registeredPrefab.GetComponent() == (Object)null) { return Fail("CookingStation, ZNetView, and WearNTear require one registered root.", out failure); } station = array[0]; if (registeredPrefab.GetComponentsInChildren(true).Length != 0 || registeredPrefab.GetComponentsInChildren(true).Length != 0 || registeredPrefab.GetComponentsInChildren(true).Length != 0 || registeredPrefab.GetComponentsInChildren(true).Length != 0) { return Fail("Hybrid registered CookingStation prefabs are unsupported.", out failure); } if (requireAllowed && (policy == null || !policy.Allows(prefabId))) { return Fail("This exact CookingStation prefab ID is not enabled.", out failure); } if (station.m_slots == null || station.m_slots.Length < 1 || station.m_slots.Length > 64) { return Fail("CookingStation slot capacity is outside 1-64.", out failure); } HashSet hashSet = new HashSet(); Transform[] slots = station.m_slots; foreach (Transform val in slots) { if ((Object)(object)val == (Object)null || !hashSet.Add(((Object)val).GetInstanceID()) || (val != ((Component)station).transform && !val.IsChildOf(((Component)station).transform)) || (Object)(object)((Component)val).GetComponent() == (Object)null || (Object)(object)((Component)val).GetComponent() == (Object)null) { return Fail("CookingStation slot transforms are incomplete or ambiguous.", out failure); } } if (station.m_donePS == null || station.m_burntPS == null || (station.m_donePS.Length != 0 && (station.m_donePS.Length < station.m_slots.Length || station.m_donePS.Any((ParticleSystem value) => (Object)(object)value == (Object)null))) || (station.m_burntPS.Length != 0 && (station.m_burntPS.Length < station.m_slots.Length || station.m_burntPS.Any((ParticleSystem value) => (Object)(object)value == (Object)null)))) { return Fail("CookingStation visual arrays do not cover every slot.", out failure); } if (station.m_conversion == null || station.m_conversion.Count < 1 || station.m_conversion.Count > 256 || (Object)(object)station.m_overCookedItem == (Object)null) { return Fail("CookingStation conversion metadata is unavailable.", out failure); } HashSet hashSet2 = new HashSet(StringComparer.Ordinal); HashSet hashSet3 = new HashSet(StringComparer.Ordinal); foreach (ItemConversion item in station.m_conversion) { string text = (((Object)(object)item?.m_from == (Object)null) ? string.Empty : ValheimAccess.PrefabName(((Component)item.m_from).gameObject)); string text2 = (((Object)(object)item?.m_to == (Object)null) ? string.Empty : ValheimAccess.PrefabName(((Component)item.m_to).gameObject)); if (text.Length != 0 && text2.Length != 0 && IsFinitePositive(item.m_cookTime) && hashSet2.Add(text)) { GameObject obj = ValheimAccess.RegisteredItemPrefab(text); if (!((Object)(object)((obj != null) ? obj.GetComponent() : null) == (Object)null)) { GameObject obj2 = ValheimAccess.RegisteredItemPrefab(text2); if (!((Object)(object)((obj2 != null) ? obj2.GetComponent() : null) == (Object)null) && !((Object)(object)ValheimAccess.RegisteredItemPrefab(text).transform.Find("attach") == (Object)null) && !((Object)(object)ValheimAccess.RegisteredItemPrefab(text2).transform.Find("attach") == (Object)null)) { hashSet3.Add(text2); continue; } } } return Fail("CookingStation conversion metadata is invalid or ambiguous.", out failure); } string text3 = ValheimAccess.PrefabName(((Component)station.m_overCookedItem).gameObject); if (text3.Length != 0 && !hashSet2.Overlaps(hashSet3) && !hashSet2.Contains(text3) && !hashSet3.Contains(text3)) { GameObject obj3 = ValheimAccess.RegisteredItemPrefab(text3); if (!((Object)(object)((obj3 != null) ? obj3.GetComponent() : null) == (Object)null) && !((Object)(object)ValheimAccess.RegisteredItemPrefab(text3).transform.Find("attach") == (Object)null) && station.m_requireFire != station.m_useFuel) { if (station.m_useFuel && ((Object)(object)station.m_fuelItem == (Object)null || station.m_maxFuel <= 0 || station.m_maxFuel > 100000 || !IsFinitePositive(station.m_secPerFuel))) { return Fail("CookingStation fuel metadata is invalid.", out failure); } failure = "ok"; return true; } } return Fail("CookingStation output/heat metadata is ambiguous.", out failure); } internal static bool TryValidate(CookingStation station, CookingStationPrefabPolicy policy, bool requireAllowed, out string prefabId, out string failure) { prefabId = string.Empty; failure = string.Empty; if ((Object)(object)station == (Object)null || !ValheimAccess.TryGetCookingStationPrefab(station, out prefabId, out var registeredPrefab)) { return Fail("The exact CookingStation prefab identity is unavailable.", out failure); } if ((Object)(object)((Component)station).gameObject.GetComponent() != (Object)(object)station || ((Component)station).gameObject.GetComponents().Length != 1 || (Object)(object)((Component)station).gameObject.GetComponent() != (Object)(object)ValheimAccess.View(station) || (Object)(object)((Component)station).gameObject.GetComponent() == (Object)null || registeredPrefab.GetComponents().Length != 1 || (Object)(object)registeredPrefab.GetComponent() == (Object)null || (Object)(object)registeredPrefab.GetComponent() == (Object)null) { return Fail("CookingStation, ZNetView, and WearNTear must share one unambiguous registered prefab root.", out failure); } if (((Component)station).GetComponentsInChildren(true).Length != 0 || registeredPrefab.GetComponentsInChildren(true).Length != 0 || ((Component)station).GetComponentsInChildren(true).Length != 0 || registeredPrefab.GetComponentsInChildren(true).Length != 0 || ((Component)station).GetComponentsInChildren(true).Length != 0 || registeredPrefab.GetComponentsInChildren(true).Length != 0) { return Fail("Hybrid Smelter, Fermenter, CraftingStation, or CookingStation prefabs are ambiguous and unsupported.", out failure); } if (((Component)station).GetComponentsInChildren(true).Length != 0 || registeredPrefab.GetComponentsInChildren(true).Length != 0) { return Fail("Hybrid Container/CookingStation prefabs make explicit link gestures ambiguous.", out failure); } if (requireAllowed && (policy == null || !policy.Allows(prefabId))) { return Fail("This exact CookingStation prefab ID is not enabled by the allow list.", out failure); } ZNetView val = ValheimAccess.View(station); if ((Object)(object)val == (Object)null || !val.IsValid() || ValheimAccess.Zdo(station) == null) { return Fail("CookingStation network state is unavailable.", out failure); } if (station.m_slots == null || station.m_slots.Length == 0 || station.m_slots.Length > 64) { return Fail("CookingStation slot capacity is outside the supported 1-64 range.", out failure); } HashSet hashSet = new HashSet(); Transform[] slots = station.m_slots; foreach (Transform val2 in slots) { if ((Object)(object)val2 == (Object)null || !hashSet.Add(((Object)val2).GetInstanceID()) || (val2 != ((Component)station).transform && !val2.IsChildOf(((Component)station).transform)) || (Object)(object)((Component)val2).GetComponent() == (Object)null || (Object)(object)((Component)val2).GetComponent() == (Object)null) { return Fail("CookingStation slot transforms are missing or ambiguous.", out failure); } } if (station.m_donePS == null || station.m_burntPS == null || (station.m_donePS.Length != 0 && (station.m_donePS.Length < station.m_slots.Length || station.m_donePS.Any((ParticleSystem value) => (Object)(object)value == (Object)null))) || (station.m_burntPS.Length != 0 && (station.m_burntPS.Length < station.m_slots.Length || station.m_burntPS.Any((ParticleSystem value) => (Object)(object)value == (Object)null)))) { return Fail("CookingStation done/burnt visual arrays do not safely cover every slot.", out failure); } if (station.m_conversion == null || station.m_conversion.Count == 0 || station.m_conversion.Count > 256) { return Fail("CookingStation conversion count is outside the supported 1-256 range.", out failure); } HashSet hashSet2 = new HashSet(StringComparer.Ordinal); HashSet hashSet3 = new HashSet(StringComparer.Ordinal); foreach (ItemConversion item in station.m_conversion) { string text = (((Object)(object)item?.m_from == (Object)null) ? string.Empty : ValheimAccess.PrefabName(((Component)item.m_from).gameObject)); string text2 = (((Object)(object)item?.m_to == (Object)null) ? string.Empty : ValheimAccess.PrefabName(((Component)item.m_to).gameObject)); if (!string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(text2) && IsFinitePositive(item.m_cookTime) && hashSet2.Add(text)) { GameObject obj = ValheimAccess.RegisteredItemPrefab(text); if (!((Object)(object)((obj != null) ? obj.GetComponent() : null) == (Object)null)) { GameObject obj2 = ValheimAccess.RegisteredItemPrefab(text2); if (!((Object)(object)((obj2 != null) ? obj2.GetComponent() : null) == (Object)null)) { if ((Object)(object)ValheimAccess.RegisteredItemPrefab(text).transform.Find("attach") == (Object)null || (Object)(object)ValheimAccess.RegisteredItemPrefab(text2).transform.Find("attach") == (Object)null) { return Fail("CookingStation item prefabs require vanilla attach visuals.", out failure); } hashSet3.Add(text2); continue; } } } return Fail("CookingStation conversions contain a missing, invalid, or ambiguous input mapping.", out failure); } if (hashSet2.Overlaps(hashSet3)) { return Fail("CookingStation input/output prefab names collide and make vanilla conversion order ambiguous.", out failure); } string text3 = (((Object)(object)station.m_overCookedItem == (Object)null) ? string.Empty : ValheimAccess.PrefabName(((Component)station.m_overCookedItem).gameObject)); if (!string.IsNullOrEmpty(text3)) { GameObject obj3 = ValheimAccess.RegisteredItemPrefab(text3); if (!((Object)(object)((obj3 != null) ? obj3.GetComponent() : null) == (Object)null) && !((Object)(object)ValheimAccess.RegisteredItemPrefab(text3).transform.Find("attach") == (Object)null)) { if (hashSet2.Contains(text3) || hashSet3.Contains(text3)) { return Fail("CookingStation overcooked prefab collides with its conversion table.", out failure); } if (station.m_requireFire == station.m_useFuel) { return Fail("CookingStation must use exactly one vanilla heat model: external fire XOR internal fuel.", out failure); } if (station.m_requireFire && (station.m_fireCheckPoints == null || station.m_fireCheckPoints.Length == 0 || station.m_fireCheckPoints.Length > 64 || !IsFinitePositive(station.m_fireCheckRadius))) { return Fail("Fire-gated CookingStation fields are incomplete or out of bounds.", out failure); } if (station.m_requireFire) { slots = station.m_fireCheckPoints; foreach (Transform val3 in slots) { if ((Object)(object)val3 == (Object)null || (val3 != ((Component)station).transform && !val3.IsChildOf(((Component)station).transform))) { return Fail("CookingStation fire points must remain inside the registered station root.", out failure); } } } if (station.m_useFuel) { if (!((Object)(object)station.m_fuelItem == (Object)null) && station.m_maxFuel > 0 && station.m_maxFuel <= 100000 && station.m_secPerFuel > 0 && !((Object)(object)station.m_addFuelSwitch == (Object)null)) { GameObject obj4 = ValheimAccess.RegisteredItemPrefab(ValheimAccess.PrefabName(((Component)station.m_fuelItem).gameObject)); if (!((Object)(object)((obj4 != null) ? obj4.GetComponent() : null) == (Object)null)) { goto IL_0650; } } return Fail("Fuelled CookingStation fields are incomplete or out of bounds.", out failure); } goto IL_0650; } } return Fail("CookingStation has no valid vanilla overcooked item.", out failure); IL_0650: if (!ControlInside(station, station.m_addFoodSwitch) || !ControlInside(station, station.m_addFuelSwitch)) { return Fail("CookingStation controls must remain inside the registered station root.", out failure); } return true; } internal static bool TryValidateState(CookingStation station, out string failure) { failure = string.Empty; if ((Object)(object)station == (Object)null || station.m_slots == null) { return Fail("CookingStation state is unavailable.", out failure); } HashSet hashSet = new HashSet(station.m_conversion.Select((ItemConversion conversion) => ValheimAccess.PrefabName(((Component)conversion.m_from).gameObject)), StringComparer.Ordinal); HashSet hashSet2 = new HashSet(station.m_conversion.Select((ItemConversion conversion) => ValheimAccess.PrefabName(((Component)conversion.m_to).gameObject)), StringComparer.Ordinal); string b = ValheimAccess.PrefabName(((Component)station.m_overCookedItem).gameObject); for (int num = 0; num < station.m_slots.Length; num++) { ValheimAccess.GetCookingSlot(station, num, out var item, out var elapsed, out var status); if (float.IsNaN(elapsed) || float.IsInfinity(elapsed) || elapsed < 0f || !Enum.IsDefined(typeof(CookingSlotStatus), status)) { return Fail("CookingStation contains non-finite or invalid slot state.", out failure); } if (string.IsNullOrEmpty(item)) { if (elapsed != 0f || status != CookingSlotStatus.NotDone) { return Fail("CookingStation contains a non-canonical empty slot.", out failure); } } else if ((status != CookingSlotStatus.NotDone || !hashSet.Contains(item)) && (status != CookingSlotStatus.Done || !hashSet2.Contains(item)) && (status != CookingSlotStatus.Burnt || !string.Equals(item, b, StringComparison.Ordinal))) { return Fail("CookingStation slot item/status does not match its exact vanilla conversion table.", out failure); } } if (station.m_useFuel) { float num2 = ValheimAccess.CookingFuel(station); if (float.IsNaN(num2) || float.IsInfinity(num2) || num2 < 0f) { return Fail("CookingStation fuel state is non-finite or negative.", out failure); } } return true; } private static bool ControlInside(CookingStation station, Switch control) { if (!((Object)(object)control == (Object)null) && ((Component)control).transform != ((Component)station).transform) { return ((Component)control).transform.IsChildOf(((Component)station).transform); } return true; } private static bool IsFinitePositive(float value) { if (value > 0f && !float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } private static bool Fail(string value, out string failure) { failure = value; return false; } } internal sealed class StockSnapshotPublicationException : InvalidOperationException { internal bool RollbackProven { get; } internal StockSnapshotPublicationException(string message, bool rollbackProven, Exception inner) : base(message, inner) { RollbackProven = rollbackProven; } } internal enum StockSourcePreparationFailureKind { None, InvalidInput, InvalidRequirements, SnapshotUnavailable, ReserveProtected, IngredientUnavailable } internal sealed class StockOutputDefinition { internal string PrefabId { get; } internal int Amount { get; } internal int Quality { get; } internal int Variant { get; } internal long CrafterId { get; } internal string CrafterName { get; } internal StockOutputDefinition(string prefabId, int amount, int quality, int variant, long crafterId, string crafterName) { if (!StockDomainValidation.IsExactPrefabId(prefabId)) { throw new ArgumentException("An exact output prefab ID is required.", "prefabId"); } if (amount <= 0 || amount > 1000000) { throw new ArgumentOutOfRangeException("amount"); } if (quality < 1 || quality > 1000) { throw new ArgumentOutOfRangeException("quality"); } if (variant < 0 || variant > 1000000) { throw new ArgumentOutOfRangeException("variant"); } PrefabId = prefabId; Amount = amount; Quality = quality; Variant = variant; CrafterId = crafterId; if (crafterId == 0L) { if (!string.IsNullOrEmpty(crafterName)) { throw new ArgumentException("Vanilla-default output metadata requires both crafter ID zero and an empty name.", "crafterName"); } CrafterName = string.Empty; } else { CrafterName = StockDomainValidation.RequireStableText(crafterName, "crafterName", 128); } } } internal sealed class StockInventoryState { internal const int MaximumPayloadBytes = 262144; private readonly byte[] _payload; internal string FingerprintValue { get; } internal int PayloadLength => _payload.Length; internal StockInventoryState(byte[] payload, string fingerprint) { if (payload == null || payload.Length == 0 || payload.Length > 262144) { throw new ArgumentOutOfRangeException("payload"); } if (!string.Equals(Fingerprint(payload), fingerprint, StringComparison.Ordinal)) { throw new ArgumentException("The inventory payload does not match its fingerprint.", "fingerprint"); } _payload = (byte[])payload.Clone(); FingerprintValue = fingerprint; } internal byte[] CopyPayload() { return (byte[])_payload.Clone(); } internal bool Matches(Inventory inventory) { if (inventory != null) { return string.Equals(FingerprintValue, ExactStockInventoryMutation.Fingerprint(inventory), StringComparison.Ordinal); } return false; } internal static StockInventoryState Capture(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); byte[] array = val.GetArray(); return new StockInventoryState(array, Fingerprint(array)); } internal static string Fingerprint(byte[] payload) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) if (payload == null || payload.Length == 0 || payload.Length > 262144) { return string.Empty; } try { return Convert.ToBase64String(new ZPackage(payload).GenerateHash()); } catch { return string.Empty; } } } internal sealed class StockInventoryTransition { internal StockInventoryState Before { get; } internal StockInventoryState After { get; } internal StockInventoryTransition(StockInventoryState before, StockInventoryState after) { Before = before ?? throw new ArgumentNullException("before"); After = after ?? throw new ArgumentNullException("after"); if (string.Equals(before.FingerprintValue, after.FingerprintValue, StringComparison.Ordinal)) { throw new ArgumentException("A positive stock transition must change inventory state."); } } internal bool MatchesBefore(Inventory inventory) { return Before.Matches(inventory); } internal bool MatchesAfter(Inventory inventory) { return After.Matches(inventory); } internal void ApplyBefore(Inventory inventory) { ExactStockInventoryMutation.ApplySnapshot(inventory, Before); } internal void ApplyAfter(Inventory inventory) { ExactStockInventoryMutation.ApplySnapshot(inventory, After); } } internal sealed class ExactStockMutationPlan { internal StockInventoryTransition Source { get; } internal StockInventoryTransition Destination { get; } internal IReadOnlyList Requirements { get; } internal StockOutputDefinition Output { get; } internal ExactStockMutationPlan(StockInventoryTransition source, StockInventoryTransition destination, IEnumerable requirements, StockOutputDefinition output) { Source = source ?? throw new ArgumentNullException("source"); Destination = destination ?? throw new ArgumentNullException("destination"); List list = new List(); if (requirements != null) { list.AddRange(requirements); } if (list.Count == 0 || list.Any((ReplenishmentRequirement requirement) => requirement == null)) { throw new ArgumentException("A complete exact stock plan requires positive requirements.", "requirements"); } Requirements = new ReadOnlyCollection(list); Output = output ?? throw new ArgumentNullException("output"); } } internal sealed class ExactStockStagingPlan { internal int SourceIndex { get; } internal string SourceId { get; } internal string PrefabId { get; } internal int Amount { get; } internal StockInventoryTransition Source { get; } internal StockInventoryTransition Destination { get; } internal NearbyIngredientStagingPlan AggregatePlan { get; } internal ExactStockStagingPlan(int sourceIndex, string sourceId, string prefabId, int amount, StockInventoryTransition source, StockInventoryTransition destination, NearbyIngredientStagingPlan aggregatePlan) { if (sourceIndex < 0) { throw new ArgumentOutOfRangeException("sourceIndex"); } SourceId = StockDomainValidation.RequireStableText(sourceId, "sourceId", 200); if (!StockDomainValidation.IsExactPrefabId(prefabId)) { throw new ArgumentException("An exact staged prefab is required.", "prefabId"); } if (amount <= 0 || amount > 1000000) { throw new ArgumentOutOfRangeException("amount"); } SourceIndex = sourceIndex; PrefabId = prefabId; Amount = amount; Source = source ?? throw new ArgumentNullException("source"); Destination = destination ?? throw new ArgumentNullException("destination"); AggregatePlan = aggregatePlan ?? throw new ArgumentNullException("aggregatePlan"); } } internal sealed class ExactStockCompositeSourceEntry { internal int SourceIndex { get; } internal StockInventoryTransition Transition { get; } internal ExactStockCompositeSourceEntry(int sourceIndex, StockInventoryTransition transition) { if (sourceIndex < 0) { throw new ArgumentOutOfRangeException("sourceIndex"); } SourceIndex = sourceIndex; Transition = transition ?? throw new ArgumentNullException("transition"); } } internal sealed class ExactStockCompositeSourcePlan { internal int SourceCount { get; } internal IReadOnlyList Entries { get; } internal ExactStockCompositeSourcePlan(int sourceCount, IEnumerable entries) { if (sourceCount < 1 || sourceCount > 64) { throw new ArgumentOutOfRangeException("sourceCount"); } List list = entries?.ToList() ?? throw new ArgumentNullException("entries"); if (list.Count == 0 || list.Count > sourceCount || list.Any((ExactStockCompositeSourceEntry value) => value == null || value.SourceIndex >= sourceCount) || list.Select((ExactStockCompositeSourceEntry value) => value.SourceIndex).Distinct().Count() != list.Count) { throw new ArgumentException("Composite source transitions must be unique and bounded.", "entries"); } SourceCount = sourceCount; Entries = new ReadOnlyCollection(list); } } internal static class ExactStockInventoryMutation { private static readonly MethodInfo InventoryAddAtMethod = AccessTools.Method(typeof(Inventory), "AddItem", new Type[4] { typeof(ItemData), typeof(int), typeof(int), typeof(int) }, (Type[])null) ?? throw new MissingMethodException(typeof(Inventory).FullName, "AddItem(ItemData,int,int,int)"); internal static bool TryPrepare(Inventory source, Inventory destination, IEnumerable requirements, IngredientReservePolicy reserves, StockOutputDefinition output, out ExactStockMutationPlan plan, out string failure) { plan = null; failure = string.Empty; if (!TryPrepareSource(source, requirements, reserves, out var transition, out var normalizedRequirements, out failure)) { return false; } if (!TryPrepareDestination(destination, output, out var transition2, out failure)) { return false; } plan = new ExactStockMutationPlan(transition, transition2, normalizedRequirements, output); return true; } internal static bool TryPrepareSource(Inventory source, IEnumerable requirements, IngredientReservePolicy reserves, out StockInventoryTransition transition, out IReadOnlyList normalizedRequirements, out string failure) { StockSourcePreparationFailureKind failureKind; return TryPrepareSourceDetailed(source, requirements, reserves, out transition, out normalizedRequirements, out failureKind, out failure); } internal static bool TryPrepareSourceDetailed(Inventory source, IEnumerable requirements, IngredientReservePolicy reserves, out StockInventoryTransition transition, out IReadOnlyList normalizedRequirements, out StockSourcePreparationFailureKind failureKind, out string failure) { transition = null; normalizedRequirements = Array.Empty(); failureKind = StockSourcePreparationFailureKind.None; failure = string.Empty; if (source == null || reserves == null || !reserves.IsValid) { failureKind = StockSourcePreparationFailureKind.InvalidInput; failure = "stock.source-or-reserve-policy-unavailable"; return false; } if (!TryNormalizeRequirements(requirements, out var normalized, out failure)) { failureKind = StockSourcePreparationFailureKind.InvalidRequirements; return false; } StockInventoryState stockInventoryState = StockInventoryState.Capture(source); Inventory val; try { val = Clone(source, stockInventoryState); } catch { failureKind = StockSourcePreparationFailureKind.SnapshotUnavailable; failure = "stock.source-snapshot-not-roundtrippable"; return false; } foreach (ReplenishmentRequirement item in normalized) { int num = CountExact(val, item.PrefabId); if (!reserves.AllowsConsumption(item.PrefabId, num, item.Amount)) { failureKind = StockSourcePreparationFailureKind.ReserveProtected; failure = "stock.ingredient-reserve-protected:" + item.PrefabId; return false; } if (!RemoveExact(val, item.PrefabId, item.Amount)) { failureKind = StockSourcePreparationFailureKind.IngredientUnavailable; failure = "stock.ingredient-unavailable:" + item.PrefabId; return false; } } StockInventoryState stockInventoryState2 = StockInventoryState.Capture(val); try { Clone(val, stockInventoryState2); } catch { failureKind = StockSourcePreparationFailureKind.SnapshotUnavailable; failure = "stock.source-result-not-roundtrippable"; return false; } transition = new StockInventoryTransition(stockInventoryState, stockInventoryState2); normalizedRequirements = new ReadOnlyCollection(normalized); return true; } internal static bool TryPrepareDestination(Inventory destination, StockOutputDefinition output, out StockInventoryTransition transition, out string failure) { transition = null; failure = string.Empty; if (destination == null || output == null) { failure = "stock.destination-or-output-unavailable"; return false; } ObjectDB instance = ObjectDB.instance; GameObject val = ((instance != null) ? instance.GetItemPrefab(output.PrefabId) : null); ItemDrop val2 = ((val != null) ? val.GetComponent() : null); if ((Object)(object)val2 == (Object)null || !string.Equals(((Object)val).name, output.PrefabId, StringComparison.Ordinal)) { failure = "stock.output-prefab-unavailable"; return false; } StockInventoryState stockInventoryState = StockInventoryState.Capture(destination); Inventory val3; try { val3 = Clone(destination, stockInventoryState); } catch { failure = "stock.destination-snapshot-not-roundtrippable"; return false; } int num = CountExact(val3, output.PrefabId, matchWorldLevel: false); if (!TryAddExactOutput(val3, val, val2, output)) { failure = "stock.complete-output-batch-does-not-fit"; return false; } if (CountExact(val3, output.PrefabId, matchWorldLevel: false) - num != output.Amount) { failure = "stock.output-metadata-or-count-mismatch"; return false; } StockInventoryState stockInventoryState2 = StockInventoryState.Capture(val3); try { Clone(val3, stockInventoryState2); } catch { failure = "stock.destination-result-not-roundtrippable"; return false; } transition = new StockInventoryTransition(stockInventoryState, stockInventoryState2); return true; } internal static bool TryPrepareCompositeSources(IReadOnlyList orderedSources, IEnumerable requirements, IngredientReservePolicy reserves, out ExactStockCompositeSourcePlan plan, out string failure) { plan = null; failure = string.Empty; if (orderedSources == null || orderedSources.Count == 0 || orderedSources.Count > 64 || reserves == null || !reserves.IsValid || orderedSources.Any((Inventory value) => value == null) || orderedSources.Distinct().Count() != orderedSources.Count) { failure = "stock.composite-sources-invalid"; return false; } if (!TryNormalizeRequirements(requirements, out var normalized, out failure)) { return false; } try { StockInventoryState[] array = new StockInventoryState[orderedSources.Count]; Inventory[] array2 = (Inventory[])(object)new Inventory[orderedSources.Count]; bool[] array3 = new bool[orderedSources.Count]; for (int num = 0; num < orderedSources.Count; num++) { array[num] = StockInventoryState.Capture(orderedSources[num]); array2[num] = Clone(orderedSources[num], array[num]); } foreach (ReplenishmentRequirement item in normalized) { int num2 = item.Amount; for (int num3 = 0; num3 < array2.Length; num3++) { if (num2 <= 0) { break; } int num4 = CountExact(array2[num3], item.PrefabId); if (!reserves.TryGetProtectedReserve(item.PrefabId, out var protectedReserve)) { failure = "stock.composite-reserve-unavailable:" + item.PrefabId; return false; } int val = Math.Max(0, num4 - protectedReserve); int num5 = Math.Min(num2, val); if (num5 != 0) { if (!RemoveExact(array2[num3], item.PrefabId, num5)) { failure = "stock.composite-remove-failed:" + item.PrefabId; return false; } array3[num3] = true; num2 -= num5; } } if (num2 != 0) { failure = "stock.composite-ingredient-unavailable:" + item.PrefabId; return false; } } List list = new List(); for (int num6 = 0; num6 < array2.Length; num6++) { if (array3[num6]) { StockInventoryState stockInventoryState = StockInventoryState.Capture(array2[num6]); Clone(array2[num6], stockInventoryState); list.Add(new ExactStockCompositeSourceEntry(num6, new StockInventoryTransition(array[num6], stockInventoryState))); } } plan = new ExactStockCompositeSourcePlan(orderedSources.Count, list); return true; } catch (Exception ex) { failure = "stock.composite-snapshot-failure:" + ex.GetType().Name; plan = null; return false; } } internal static bool TryPrepareStaging(Inventory input, IReadOnlyList orderedSources, IReadOnlyList orderedSourceIds, IEnumerable requirements, IngredientReservePolicy reserves, int pullBatchSize, out ExactStockStagingPlan plan, out string failure) { plan = null; failure = string.Empty; if (input == null || orderedSources == null || orderedSourceIds == null || orderedSources.Count == 0 || orderedSources.Count != orderedSourceIds.Count || orderedSources.Count > 64 || reserves == null || !reserves.IsValid || pullBatchSize <= 0 || pullBatchSize > 10) { failure = "nearby-stage.inventory-input-invalid"; return false; } List list = new List(orderedSources.Count); List list2 = new List(orderedSources.Count); List list3 = new List(orderedSources.Count); HashSet hashSet = new HashSet(); HashSet hashSet2 = new HashSet(StringComparer.Ordinal); List list4 = requirements?.Take(17).ToList(); if (list4 == null || list4.Count == 0 || list4.Count > 16 || list4.Any((ReplenishmentRequirement value) => value == null)) { failure = "nearby-stage.requirements-invalid"; return false; } try { StockInventoryState stockInventoryState = StockInventoryState.Capture(input); Inventory val = Clone(input, stockInventoryState); IReadOnlyDictionary inputExactCounts = ExactCounts(input, list4); for (int num = 0; num < orderedSources.Count; num++) { Inventory val2 = orderedSources[num]; string text = orderedSourceIds[num]; if (val2 == null || val2 == input || !hashSet.Add(val2) || !hashSet2.Add(text ?? string.Empty)) { failure = "nearby-stage.source-alias-or-duplicate"; return false; } StockInventoryState stockInventoryState2 = StockInventoryState.Capture(val2); list.Add(stockInventoryState2); list2.Add(Clone(val2, stockInventoryState2)); list3.Add(new NearbyIngredientSourceStock(text, ExactCounts(val2, list4))); } if (!NearbyIngredientStagingPlanner.TryPlan(list4, inputExactCounts, list3, reserves, pullBatchSize, out var plan2, out failure)) { return false; } foreach (NearbyIngredientStagingContribution contribution in plan2.Contributions) { if (contribution.SourceIndex < 0 || contribution.SourceIndex >= list2.Count || !TryMoveExactPreservingMetadata(list2[contribution.SourceIndex], val, contribution.PrefabId, contribution.Amount)) { failure = "nearby-stage.complete-batch-does-not-fit-input"; return false; } } Clone(val, StockInventoryState.Capture(val)); foreach (Inventory item in list2) { Clone(item, StockInventoryState.Capture(item)); } NearbyIngredientStagingContribution nextContribution = plan2.NextContribution; Inventory val3 = Clone(orderedSources[nextContribution.SourceIndex], list[nextContribution.SourceIndex]); Inventory val4 = Clone(input, stockInventoryState); int num2 = CountExact(val3, nextContribution.PrefabId); int num3 = CountExact(val4, nextContribution.PrefabId); if (!TryMoveExactPreservingMetadata(val3, val4, nextContribution.PrefabId, plan2.NextTransferAmount) || num2 - CountExact(val3, nextContribution.PrefabId) != plan2.NextTransferAmount || CountExact(val4, nextContribution.PrefabId) - num3 != plan2.NextTransferAmount) { failure = "nearby-stage.exact-transfer-could-not-be-modeled"; return false; } StockInventoryState stockInventoryState3 = StockInventoryState.Capture(val3); StockInventoryState stockInventoryState4 = StockInventoryState.Capture(val4); Clone(val3, stockInventoryState3); Clone(val4, stockInventoryState4); plan = new ExactStockStagingPlan(nextContribution.SourceIndex, nextContribution.SourceId, nextContribution.PrefabId, plan2.NextTransferAmount, new StockInventoryTransition(list[nextContribution.SourceIndex], stockInventoryState3), new StockInventoryTransition(stockInventoryState, stockInventoryState4), plan2); return true; } catch (Exception ex) { failure = "nearby-stage.snapshot-or-metadata-failure:" + ex.GetType().Name; plan = null; return false; } } internal static void ApplySnapshot(Inventory inventory, StockInventoryState state) { //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Expected O, but got Unknown //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown if (inventory == null) { throw new ArgumentNullException("inventory"); } if (state == null) { throw new ArgumentNullException("state"); } Clone(inventory, state); StockInventoryState stockInventoryState = StockInventoryState.Capture(inventory); Clone(inventory, stockInventoryState); Action onChanged = inventory.m_onChanged; Exception ex2; try { inventory.m_onChanged = null; inventory.Load(new ZPackage(state.CopyPayload())); if (!state.Matches(inventory)) { throw new InvalidOperationException("The exact inventory snapshot did not apply."); } inventory.m_onChanged = onChanged; onChanged?.Invoke(); if (!state.Matches(inventory)) { throw new InvalidOperationException("The exact inventory snapshot changed during publication."); } return; } catch (Exception ex) { ex2 = ex; } finally { inventory.m_onChanged = onChanged; } Exception ex3 = null; bool flag = false; try { inventory.m_onChanged = null; inventory.Load(new ZPackage(stockInventoryState.CopyPayload())); if (!stockInventoryState.Matches(inventory)) { throw new InvalidOperationException("The original inventory snapshot did not restore."); } inventory.m_onChanged = onChanged; onChanged?.Invoke(); if (!stockInventoryState.Matches(inventory)) { throw new InvalidOperationException("The original inventory changed during rollback publication."); } flag = true; } catch (Exception ex4) { ex3 = ex4; } finally { inventory.m_onChanged = onChanged; } throw new StockSnapshotPublicationException(flag ? "The exact inventory publication failed; the original live state was restored." : "The exact inventory publication failed and rollback is indeterminate.", flag, (ex3 == null) ? ex2 : new AggregateException(ex2, ex3)); } internal static string Fingerprint(Inventory inventory) { return StockInventoryState.Capture(inventory).FingerprintValue; } internal static int CountExact(Inventory inventory, string prefabId, bool matchWorldLevel = true) { if (inventory == null || !StockDomainValidation.IsExactPrefabId(prefabId)) { return 0; } long num = 0L; foreach (ItemData allItem in inventory.GetAllItems()) { if (string.Equals(ValheimAccess.PrefabName(allItem), prefabId, StringComparison.Ordinal) && (!matchWorldLevel || allItem.m_worldLevel >= Game.m_worldLevel)) { num += Math.Max(0, allItem.m_stack); if (num >= int.MaxValue) { return int.MaxValue; } } } return (int)num; } private static IReadOnlyDictionary ExactCounts(Inventory inventory, IEnumerable requirements) { SortedDictionary sortedDictionary = new SortedDictionary(StringComparer.Ordinal); foreach (ReplenishmentRequirement requirement in requirements) { if (!sortedDictionary.ContainsKey(requirement.PrefabId)) { sortedDictionary.Add(requirement.PrefabId, CountExact(inventory, requirement.PrefabId)); } } return sortedDictionary; } private static bool TryNormalizeRequirements(IEnumerable requirements, out List normalized, out string failure) { normalized = new List(); failure = string.Empty; SortedDictionary sortedDictionary = new SortedDictionary(StringComparer.Ordinal); if (requirements != null) { int num = 0; foreach (ReplenishmentRequirement requirement in requirements) { if (requirement == null || ++num > 16) { failure = "stock.requirements-invalid-or-unbounded"; return false; } sortedDictionary.TryGetValue(requirement.PrefabId, out var value); long num2 = value + requirement.Amount; if (num2 <= 0 || num2 > 1000000) { failure = "stock.requirement-amount-out-of-range"; return false; } sortedDictionary[requirement.PrefabId] = num2; } } if (sortedDictionary.Count == 0) { failure = "stock.requirements-empty"; return false; } foreach (KeyValuePair item in sortedDictionary) { normalized.Add(new ReplenishmentRequirement(item.Key, (int)item.Value)); } return true; } private static bool RemoveExact(Inventory inventory, string prefabId, int amount) { List list = (from item in inventory.GetAllItems() where string.Equals(ValheimAccess.PrefabName(item), prefabId, StringComparison.Ordinal) && item.m_worldLevel >= Game.m_worldLevel orderby item.m_gridPos.y, item.m_gridPos.x, item.m_quality select item).ToList(); int num = amount; foreach (ItemData item in list) { int num2 = Math.Min(Math.Max(0, item.m_stack), num); if (num2 > 0) { if (!inventory.RemoveItem(item, num2)) { return false; } num -= num2; if (num == 0) { return true; } } } return false; } private static bool TryMoveExactPreservingMetadata(Inventory source, Inventory destination, string prefabId, int amount) { if (source == null || destination == null || source == destination || !StockDomainValidation.IsExactPrefabId(prefabId) || amount <= 0) { return false; } List list = (from item in source.GetAllItems() where string.Equals(ValheimAccess.PrefabName(item), prefabId, StringComparison.Ordinal) && item.m_worldLevel >= Game.m_worldLevel && item.m_stack > 0 orderby item.m_gridPos.y, item.m_gridPos.x, item.m_quality select item).ToList(); int num = amount; foreach (ItemData item in list) { int num2 = Math.Min(item.m_stack, num); if (num2 > 0) { ItemData val = item.Clone(); val.m_stack = num2; if (!TryAddExactPreservingMetadata(destination, val, num2) || !source.RemoveItem(item, num2)) { return false; } num -= num2; if (num == 0) { return true; } } } return false; } private static bool TryAddExactPreservingMetadata(Inventory inventory, ItemData template, int amount) { //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) if (inventory == null || template?.m_shared == null || (Object)(object)template.m_dropPrefab == (Object)null || amount <= 0) { return false; } int maximumStack = template.m_shared.m_maxStackSize; if (maximumStack <= 0) { return false; } List list = (from candidate in inventory.GetAllItems() where ExactPersistentMetadataEquals(template, candidate) && candidate.m_stack >= 0 && candidate.m_stack < maximumStack orderby candidate.m_gridPos.y, candidate.m_gridPos.x select candidate).ToList(); List list2 = new List(); for (int num = 0; num < inventory.GetHeight(); num++) { for (int num2 = 0; num2 < inventory.GetWidth(); num2++) { if (inventory.GetItemAt(num2, num) == null) { list2.Add(new Vector2i(num2, num)); } } } if (list.Sum((ItemData target) => (long)maximumStack - (long)target.m_stack) + (long)list2.Count * (long)maximumStack < amount) { return false; } int num3 = amount; foreach (ItemData item in list) { if (num3 == 0) { break; } int num4 = Math.Min(num3, maximumStack - item.m_stack); if (!AddExactAt(inventory, template, num4, item.m_gridPos)) { return false; } num3 -= num4; } foreach (Vector2i item2 in list2) { if (num3 == 0) { break; } int num5 = Math.Min(num3, maximumStack); if (!AddExactAt(inventory, template, num5, item2)) { return false; } num3 -= num5; } return num3 == 0; } private static bool TryAddExactOutput(Inventory inventory, GameObject outputPrefab, ItemDrop drop, StockOutputDefinition output) { //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_024d: Unknown result type (might be due to invalid IL or missing references) //IL_028d: Unknown result type (might be due to invalid IL or missing references) //IL_0292: Unknown result type (might be due to invalid IL or missing references) //IL_02b2: Unknown result type (might be due to invalid IL or missing references) if (inventory == null || (Object)(object)outputPrefab == (Object)null || drop?.m_itemData?.m_shared == null) { return false; } ItemData template = drop.m_itemData.Clone(); template.m_dropPrefab = outputPrefab; template.m_stack = 1; template.m_quality = output.Quality; template.m_variant = output.Variant; template.m_crafterID = output.CrafterId; template.m_crafterName = output.CrafterName; template.m_worldLevel = (byte)Game.m_worldLevel; template.m_pickedUp = false; template.m_equipped = false; template.m_durability = template.GetMaxDurability(); int maximumStack = template.m_shared.m_maxStackSize; if (maximumStack <= 0) { return false; } List list = (from candidate in inventory.GetAllItems() where ExactPersistentMetadataEquals(template, candidate) && candidate.m_stack >= 0 && candidate.m_stack < maximumStack orderby candidate.m_gridPos.y, candidate.m_gridPos.x select candidate).ToList(); List list2 = new List(); for (int num = 0; num < inventory.GetHeight(); num++) { for (int num2 = 0; num2 < inventory.GetWidth(); num2++) { if (inventory.GetItemAt(num2, num) == null) { list2.Add(new Vector2i(num2, num)); } } } long num3 = 0L; foreach (ItemData item in list) { num3 += maximumStack - item.m_stack; } num3 += (long)list2.Count * (long)maximumStack; if (num3 < output.Amount) { return false; } int num4 = output.Amount; foreach (ItemData item2 in list) { if (num4 == 0) { break; } int num5 = Math.Min(num4, maximumStack - item2.m_stack); if (!AddExactAt(inventory, template, num5, item2.m_gridPos)) { return false; } num4 -= num5; } foreach (Vector2i item3 in list2) { if (num4 == 0) { break; } int num6 = Math.Min(num4, maximumStack); if (!AddExactAt(inventory, template, num6, item3)) { return false; } num4 -= num6; } return num4 == 0; } private static bool AddExactAt(Inventory inventory, ItemData template, int amount, Vector2i slot) { //IL_002f: 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) if (amount <= 0) { return false; } ItemData val = template.Clone(); val.m_stack = amount; if ((bool)InventoryAddAtMethod.Invoke(inventory, new object[4] { val, amount, slot.x, slot.y })) { return val.m_stack == 0; } return false; } private static bool ExactPersistentMetadataEquals(ItemData left, ItemData right) { if (left == null || right == null || !string.Equals(ValheimAccess.PrefabName(left), ValheimAccess.PrefabName(right), StringComparison.Ordinal) || left.m_quality != right.m_quality || left.m_variant != right.m_variant || left.m_worldLevel != right.m_worldLevel || left.m_crafterID != right.m_crafterID || !string.Equals(left.m_crafterName ?? string.Empty, right.m_crafterName ?? string.Empty, StringComparison.Ordinal) || left.m_pickedUp != right.m_pickedUp || left.m_equipped != right.m_equipped || !left.m_durability.Equals(right.m_durability)) { return false; } return DictionaryEquals(left.m_customData, right.m_customData); } private static bool DictionaryEquals(IDictionary left, IDictionary right) { int num = left?.Count ?? 0; if (num != (right?.Count ?? 0)) { return false; } if (num == 0) { return true; } foreach (KeyValuePair item in left) { if (!right.TryGetValue(item.Key, out var value) || !string.Equals(item.Value, value, StringComparison.Ordinal)) { return false; } } return true; } private static Inventory Clone(Inventory source, StockInventoryState state) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown Inventory val = new Inventory(source.GetName(), (Sprite)null, source.GetWidth(), source.GetHeight()); val.Load(new ZPackage(state.CopyPayload())); if (!state.Matches(val)) { throw new InvalidOperationException("The serialized inventory snapshot is not an exact round trip under the current item definitions."); } return val; } } internal sealed class FermenterConversionDescriptor { internal string InputPrefabId { get; } internal string OutputPrefabId { get; } internal int OutputAmount { get; } internal FermenterConversionDescriptor(string inputPrefabId, string outputPrefabId, int outputAmount) { InputPrefabId = inputPrefabId; OutputPrefabId = outputPrefabId; OutputAmount = outputAmount; } } internal sealed class FermenterDescriptor { private readonly ReadOnlyCollection _conversions; private readonly Dictionary _byInput; private readonly Dictionary _byOutput; internal string PrefabId { get; } internal IReadOnlyList Conversions => _conversions; internal FermenterDescriptor(string prefabId, IEnumerable conversions) { PrefabId = prefabId; List list = conversions.OrderBy((FermenterConversionDescriptor value) => value.InputPrefabId, StringComparer.Ordinal).ToList(); _conversions = list.AsReadOnly(); _byInput = list.ToDictionary((FermenterConversionDescriptor value) => value.InputPrefabId, StringComparer.Ordinal); _byOutput = list.ToDictionary((FermenterConversionDescriptor value) => value.OutputPrefabId, StringComparer.Ordinal); } internal bool TryFromInput(string prefabId, out FermenterConversionDescriptor conversion) { return _byInput.TryGetValue(prefabId ?? string.Empty, out conversion); } internal bool TryFromOutput(string prefabId, out FermenterConversionDescriptor conversion) { return _byOutput.TryGetValue(prefabId ?? string.Empty, out conversion); } } internal static class FermenterCompatibility { private const float VanillaFermentationSeconds = 2400f; private const float DurationTolerance = 0.01f; private const int MaximumConversions = 256; internal static bool TryValidate(Fermenter station, FermenterPrefabPolicy policy, bool requireAllowed, out FermenterDescriptor descriptor, out string failure) { descriptor = null; failure = string.Empty; if ((Object)(object)station == (Object)null || !ValheimAccess.TryGetFermenterPrefab(station, out var prefabId, out var registeredPrefab)) { return Fail("The exact registered Fermenter prefab identity is unavailable.", out failure); } if (requireAllowed && (policy == null || !policy.Allows(prefabId))) { return Fail("This exact Fermenter prefab ID is not enabled by the local allow list.", out failure); } if (!RootIsExact(((Component)station).gameObject, station, ValheimAccess.View(station)) || !RegisteredRootIsExact(registeredPrefab)) { return Fail("Fermenter, ZNetView, and WearNTear must be unique components on one registered prefab root.", out failure); } if ((Object)(object)((Component)station).gameObject.GetComponent() != (Object)null || (Object)(object)((Component)station).gameObject.GetComponent() != (Object)null || ((Component)station).GetComponentsInChildren(true).Length != 0 || registeredPrefab.GetComponentsInChildren(true).Length != 0 || ((Component)station).GetComponentsInChildren(true).Length != 0 || registeredPrefab.GetComponentsInChildren(true).Length != 0) { return Fail("Hybrid production/container Fermenter roots are ambiguous and unsupported.", out failure); } ZNetView val = ValheimAccess.View(station); if ((Object)(object)val == (Object)null || !val.IsValid() || ValheimAccess.Zdo(station) == null) { return Fail("Fermenter network state is unavailable.", out failure); } if (!IsFinite(station.m_fermentationDuration) || Math.Abs(station.m_fermentationDuration - 2400f) > 0.01f) { return Fail("Fermenter duration must retain vanilla's exact 2400-second model.", out failure); } if (!IsFinite(station.m_tapDelay) || station.m_tapDelay < 0f || station.m_tapDelay > 30f) { return Fail("Fermenter tap delay is non-finite or outside the supported vanilla range.", out failure); } if (!ControlInside(station, station.m_addSwitch) || !ControlInside(station, station.m_tapSwitch) || !TransformInside(station, station.m_outputPoint) || !TransformInside(station, station.m_roofCheckPoint) || (Object)(object)station.m_addSwitch == (Object)null || (Object)(object)station.m_tapSwitch == (Object)null || (Object)(object)station.m_outputPoint == (Object)null || (Object)(object)station.m_roofCheckPoint == (Object)null) { return Fail("Fermenter controls, output, and cover points must remain inside its exact root.", out failure); } if (!TryReadConversions(station.m_conversion, out var descriptors, out failure)) { return false; } Fermenter component = registeredPrefab.GetComponent(); if ((Object)(object)component == (Object)null || !IsFinite(component.m_fermentationDuration) || Math.Abs(component.m_fermentationDuration - 2400f) > 0.01f || !IsFinite(component.m_tapDelay) || component.m_tapDelay < 0f || component.m_tapDelay > 30f || (Object)(object)component.m_addSwitch == (Object)null || (Object)(object)component.m_tapSwitch == (Object)null || (Object)(object)component.m_outputPoint == (Object)null || (Object)(object)component.m_roofCheckPoint == (Object)null || !ControlInside(component, component.m_addSwitch) || !ControlInside(component, component.m_tapSwitch) || !TransformInside(component, component.m_outputPoint) || !TransformInside(component, component.m_roofCheckPoint) || !TryReadConversions(component.m_conversion, out var descriptors2, out failure) || !SameConversions(descriptors, descriptors2)) { return Fail("The live Fermenter no longer matches its exact registered vanilla-compatible definition.", out failure); } descriptor = new FermenterDescriptor(prefabId, descriptors); return true; } internal static bool TryValidateState(Fermenter station, FermenterDescriptor descriptor, out FermenterStationState state, out string failure) { state = null; failure = string.Empty; if ((Object)(object)station == (Object)null || descriptor == null || !FermenterStationState.TryCapture(station, out state)) { return Fail("Fermenter content/start state is non-canonical.", out failure); } if (!state.IsEmpty && !descriptor.TryFromInput(state.InputPrefabId, out var _)) { return Fail("Fermenter content does not match its exact conversion table.", out failure); } if (ValheimAccess.FermenterDelayedTapActive(station)) { return Fail("A vanilla DelayedTap is active; links and refresh must wait for it to complete.", out failure); } return true; } internal static bool TryDescribeRegistered(Fermenter registered, string prefabId, out FermenterDescriptor descriptor, out string failure) { descriptor = null; failure = string.Empty; if ((Object)(object)registered == (Object)null || !StockDomainValidation.IsExactPrefabId(prefabId) || !RegisteredRootIsExact(((Component)registered).gameObject) || (Object)(object)((Component)registered).gameObject.GetComponent() != (Object)null || (Object)(object)((Component)registered).gameObject.GetComponent() != (Object)null || ((Component)registered).GetComponentsInChildren(true).Length != 0 || ((Component)registered).GetComponentsInChildren(true).Length != 0) { return Fail("The registered Fermenter producer root is structurally ambiguous.", out failure); } if (!IsFinite(registered.m_fermentationDuration) || Math.Abs(registered.m_fermentationDuration - 2400f) > 0.01f || !IsFinite(registered.m_tapDelay) || registered.m_tapDelay < 0f || registered.m_tapDelay > 30f || (Object)(object)registered.m_addSwitch == (Object)null || (Object)(object)registered.m_tapSwitch == (Object)null || (Object)(object)registered.m_outputPoint == (Object)null || (Object)(object)registered.m_roofCheckPoint == (Object)null || !ControlInside(registered, registered.m_addSwitch) || !ControlInside(registered, registered.m_tapSwitch) || !TransformInside(registered, registered.m_outputPoint) || !TransformInside(registered, registered.m_roofCheckPoint) || !TryReadConversions(registered.m_conversion, out var descriptors, out failure)) { return false; } descriptor = new FermenterDescriptor(prefabId, descriptors); failure = string.Empty; return true; } private static bool TryReadConversions(List source, out List descriptors, out string failure) { descriptors = new List(); failure = string.Empty; if (source == null || source.Count == 0 || source.Count > 256) { return Fail("Fermenter conversion count is outside the supported 1-256 range.", out failure); } HashSet hashSet = new HashSet(StringComparer.Ordinal); HashSet hashSet2 = new HashSet(StringComparer.Ordinal); foreach (ItemConversion item in source) { string text = (((Object)(object)item?.m_from == (Object)null) ? string.Empty : ValheimAccess.PrefabName(((Component)item.m_from).gameObject)); string text2 = (((Object)(object)item?.m_to == (Object)null) ? string.Empty : ValheimAccess.PrefabName(((Component)item.m_to).gameObject)); int num = item?.m_producedItems ?? 0; if (StockDomainValidation.IsExactPrefabId(text) && StockDomainValidation.IsExactPrefabId(text2) && hashSet.Add(text) && hashSet2.Add(text2) && (num == 3 || num == 6)) { GameObject obj = ValheimAccess.RegisteredItemPrefab(text); if (!((Object)(object)((obj != null) ? obj.GetComponent() : null) == (Object)null)) { GameObject obj2 = ValheimAccess.RegisteredItemPrefab(text2); if (!((Object)(object)((obj2 != null) ? obj2.GetComponent() : null) == (Object)null)) { descriptors.Add(new FermenterConversionDescriptor(text, text2, num)); continue; } } } return Fail("Fermenter conversions require unique exact inputs/outputs and complete vanilla 3/6-item batches.", out failure); } if (hashSet.Overlaps(hashSet2)) { return Fail("Fermenter input/output prefab IDs collide and are ambiguous.", out failure); } return true; } private static bool RootIsExact(GameObject root, Fermenter station, ZNetView expectedView) { if ((Object)(object)root != (Object)null && root.GetComponentsInChildren(true).Length == 1 && root.GetComponent() == station && root.GetComponentsInChildren(true).Length == 1 && root.GetComponent() == expectedView && root.GetComponentsInChildren(true).Length == 1) { return (Object)(object)root.GetComponent() != (Object)null; } return false; } private static bool RegisteredRootIsExact(GameObject root) { if ((Object)(object)root != (Object)null && root.GetComponentsInChildren(true).Length == 1 && (Object)(object)root.GetComponent() != (Object)null && root.GetComponentsInChildren(true).Length == 1 && (Object)(object)root.GetComponent() != (Object)null && root.GetComponentsInChildren(true).Length == 1) { return (Object)(object)root.GetComponent() != (Object)null; } return false; } private static bool ControlInside(Fermenter station, Switch control) { if ((Object)(object)control != (Object)null) { return TransformInside(station, ((Component)control).transform); } return false; } private static bool TransformInside(Fermenter station, Transform value) { if ((Object)(object)value != (Object)null) { if (value != ((Component)station).transform) { return value.IsChildOf(((Component)station).transform); } return true; } return false; } private static bool IsFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } private static bool SameConversions(IEnumerable left, IEnumerable right) { FermenterConversionDescriptor[] array = left.OrderBy((FermenterConversionDescriptor value) => value.InputPrefabId, StringComparer.Ordinal).ToArray(); FermenterConversionDescriptor[] array2 = right.OrderBy((FermenterConversionDescriptor value) => value.InputPrefabId, StringComparer.Ordinal).ToArray(); if (array.Length != array2.Length) { return false; } for (int num = 0; num < array.Length; num++) { if (!string.Equals(array[num].InputPrefabId, array2[num].InputPrefabId, StringComparison.Ordinal) || !string.Equals(array[num].OutputPrefabId, array2[num].OutputPrefabId, StringComparison.Ordinal) || array[num].OutputAmount != array2[num].OutputAmount) { return false; } } return true; } private static bool Fail(string value, out string failure) { failure = value; return false; } } internal static class FermenterPlanBuilder { internal static bool TryBuild(FermenterDescriptor descriptor, Inventory replenishmentInventory, StoredProductionLink link, string stationId, long actorId, string actorName, out ReplenishmentPlan plan, out string failure) { return TryBuild(descriptor, replenishmentInventory, link, stationId, actorId, actorName, null, out plan, out failure); } internal static bool TryBuild(FermenterDescriptor descriptor, Inventory replenishmentInventory, StoredProductionLink link, string stationId, long actorId, string actorName, ReplenishmentPlan previous, out ReplenishmentPlan plan, out string failure) { plan = null; failure = string.Empty; if (descriptor == null || replenishmentInventory == null || link == null || string.IsNullOrEmpty(stationId) || actorId == 0L || string.IsNullOrWhiteSpace(actorName)) { return Fail("Fermenter plan inputs are unavailable.", out failure); } bool flag = previous != null && previous.AdapterKind == ReplenishmentProducerKind.Fermenter && string.Equals(previous.StationPrefabId, descriptor.PrefabId, StringComparison.Ordinal) && ReplenishmentPlanStore.MatchesLink(previous, link, stationId); if (previous != null && !flag) { return Fail("The prior Fermenter plan is not bound to this exact station and link.", out failure); } long num = (flag ? previous.AuthorizedPlayerId : actorId); string text = (flag ? previous.AuthorizedPlayerName : actorName); if (num == 0L || num != link.OwnerId || string.IsNullOrWhiteSpace(text)) { return Fail("The Fermenter plan principal does not match the exact link owner.", out failure); } SortedSet sortedSet = new SortedSet(StringComparer.Ordinal); foreach (ItemData allItem in replenishmentInventory.GetAllItems()) { string text2 = ValheimAccess.PrefabName(allItem); if (allItem != null && allItem.m_stack > 0 && descriptor.TryFromOutput(text2, out var _)) { sortedSet.Add(text2); if (sortedSet.Count > 32) { return Fail("The replenishment chest contains too many Fermenter output exemplars.", out failure); } } } if (flag && previous.Revision == int.MaxValue) { return Fail("The Fermenter plan revision is exhausted; remove and re-add this destination.", out failure); } List list = new List(sortedSet.Count); foreach (string item in sortedSet) { if (!descriptor.TryFromOutput(item, out var conversion2)) { return Fail("A Fermenter output exemplar became ambiguous.", out failure); } list.Add(new ReplenishmentTargetAuthorization(conversion2.OutputPrefabId, ReplenishmentProducerKind.Fermenter, FermenterProducerSignature.ProducerId(descriptor, conversion2), FermenterProducerSignature.Create(descriptor, conversion2), conversion2.OutputAmount, string.Empty, 0, num, text, new ReplenishmentRequirement[1] { new ReplenishmentRequirement(conversion2.InputPrefabId, 1) })); } string priorTarget = ((flag && previous.Targets.Count > 0) ? previous.Targets[previous.Cursor].OutputPrefabId : string.Empty); int cursor = 0; if (priorTarget.Length != 0) { int num2 = list.FindIndex((ReplenishmentTargetAuthorization value) => string.Equals(value.OutputPrefabId, priorTarget, StringComparison.Ordinal)); if (num2 >= 0) { cursor = num2; } } plan = new ReplenishmentPlan(flag ? previous.PlanId : Guid.NewGuid().ToString("N"), (!flag) ? 1 : (previous.Revision + 1), cursor, descriptor.PrefabId, ReplenishmentProducerKind.Fermenter, ReplenishmentPlanStore.Bind(link, stationId), num, text, list); return true; } internal static bool TryValidate(ReplenishmentPlan plan, FermenterDescriptor descriptor, StoredProductionLink link, string stationId, Inventory replenishmentInventory, out string failure) { failure = string.Empty; if (plan == null || descriptor == null || link == null || replenishmentInventory == null || plan.AdapterKind != ReplenishmentProducerKind.Fermenter || !string.Equals(plan.StationPrefabId, descriptor.PrefabId, StringComparison.Ordinal) || !ReplenishmentPlanStore.MatchesLink(plan, link, stationId) || plan.AuthorizedPlayerId != link.OwnerId) { return Fail("The persisted Fermenter plan is not bound to this exact station/link.", out failure); } foreach (ReplenishmentTargetAuthorization target in plan.Targets) { if (target.ProducerKind != ReplenishmentProducerKind.Fermenter || target.AuthorizedPlayerId != link.OwnerId || target.Requirements.Count != 1 || target.Requirements[0].Amount != 1 || !descriptor.TryFromOutput(target.OutputPrefabId, out var conversion) || !string.Equals(target.Requirements[0].PrefabId, conversion.InputPrefabId, StringComparison.Ordinal) || target.OutputAmount != conversion.OutputAmount || !string.Equals(target.ProducerId, FermenterProducerSignature.ProducerId(descriptor, conversion), StringComparison.Ordinal) || !target.ProducerSignatureMatches(FermenterProducerSignature.Create(descriptor, conversion))) { return Fail("A Fermenter target no longer matches the exact producer signature.", out failure); } } return true; } private static bool Fail(string value, out string failure) { failure = value; return false; } } internal static class FermenterProducerSignature { private const int SchemaVersion = 1; internal static string ProducerId(FermenterDescriptor station, FermenterConversionDescriptor conversion) { if (station == null) { throw new ArgumentNullException("station"); } if (conversion == null) { throw new ArgumentNullException("conversion"); } return "fermenter:" + station.PrefabId + ":" + conversion.InputPrefabId; } internal static byte[] Create(FermenterDescriptor station, FermenterConversionDescriptor conversion) { if (station == null) { throw new ArgumentNullException("station"); } if (conversion == null) { throw new ArgumentNullException("conversion"); } return StockProducerSignature.Create(delegate(StockSignatureWriter writer) { writer.WriteString("RunicProduction.Fermenter"); writer.WriteInt32(1); writer.WriteString(station.PrefabId); writer.WriteSingle(2400f); writer.WriteString(conversion.InputPrefabId); writer.WriteString(conversion.OutputPrefabId); writer.WriteInt32(conversion.OutputAmount); }); } } internal sealed class FermenterStationState { private const int SchemaVersion = 1; private const int MaximumEncodedCharacters = 512; internal string InputPrefabId { get; } internal long StartTicks { get; } internal bool IsEmpty => InputPrefabId.Length == 0; internal static FermenterStationState Empty { get; } = new FermenterStationState(string.Empty, 0L); private FermenterStationState(string inputPrefabId, long startTicks) { InputPrefabId = inputPrefabId; StartTicks = startTicks; } internal static bool TryCreate(string inputPrefabId, long startTicks, out FermenterStationState state) { state = null; string text = inputPrefabId ?? string.Empty; if (text.Length == 0) { if (startTicks != 0L) { return false; } state = Empty; return true; } if (!StockDomainValidation.IsExactPrefabId(text) || startTicks <= 0) { return false; } try { if (new DateTime(startTicks).Kind == DateTimeKind.Local) { return false; } } catch (ArgumentOutOfRangeException) { return false; } state = new FermenterStationState(text, startTicks); return true; } internal static bool TryCapture(Fermenter station, out FermenterStationState state) { state = null; if ((Object)(object)station != (Object)null) { return TryCreate(ValheimAccess.FermenterContent(station), ValheimAccess.FermenterStartTicks(station), out state); } return false; } internal static bool TryCapture(ZDO zdo, out FermenterStationState state) { state = null; if (zdo != null && zdo.IsValid()) { return TryCreate(zdo.GetString(ZDOVars.s_content, string.Empty), zdo.GetLong(ZDOVars.s_startTime, 0L), out state); } return false; } internal string Serialize() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) ZPackage val = new ZPackage(); val.Write(1); val.Write(InputPrefabId); val.Write(StartTicks); return Convert.ToBase64String(val.GetArray()); } internal static bool TryParse(string encoded, out FermenterStationState state) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown state = null; if (string.IsNullOrEmpty(encoded) || encoded.Length > 512) { return false; } try { byte[] array = Convert.FromBase64String(encoded); if (array.Length == 0 || array.Length > 256) { return false; } ZPackage val = new ZPackage(array); if (val.ReadInt() != 1) { return false; } string inputPrefabId = val.ReadString(); long startTicks = val.ReadLong(); if (val.GetPos() != val.Size()) { return false; } return TryCreate(inputPrefabId, startTicks, out state) && string.Equals(state.Serialize(), encoded, StringComparison.Ordinal); } catch { return false; } } internal bool Matches(Fermenter station) { if ((Object)(object)station != (Object)null && string.Equals(InputPrefabId, ValheimAccess.FermenterContent(station), StringComparison.Ordinal)) { return StartTicks == ValheimAccess.FermenterStartTicks(station); } return false; } internal void Apply(Fermenter station) { if ((Object)(object)station == (Object)null) { throw new ArgumentNullException("station"); } ValheimAccess.SetFermenterState(station, InputPrefabId, StartTicks); } internal void ApplyToZdo(ZDO zdo) { if (zdo == null || !zdo.IsValid()) { throw new ArgumentNullException("zdo"); } zdo.Set(ZDOVars.s_content, InputPrefabId); zdo.Set(ZDOVars.s_startTime, StartTicks); } } [HarmonyPatch(typeof(Player), "Update")] internal static class ProductionPlayerUpdateInputPatch { [HarmonyPriority(800)] [HarmonyBefore(new string[] { "chazman.RunicAgriculture", "chazman.RunicBuildCamera" })] private static void Prefix(Player __instance) { try { ProductionRuntime.TickLocalPlayer(__instance); } catch (Exception exception) { ProductionRuntime.FailHook("Player.Update link input", exception); } } } [HarmonyPatch(typeof(ZInput), "GetButton", new Type[] { typeof(string) })] internal static class ProductionConsumedButtonPatch { [HarmonyPriority(800)] [HarmonyAfter(new string[] { "chazman.RunicStorage" })] [HarmonyBefore(new string[] { "chazman.RunicAgriculture", "chazman.RunicInventory" })] private static bool Prefix(string name, ref bool __result) { if (!ProductionLinkInput.ShouldSuppress(name, Time.frameCount)) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(ZInput), "GetButtonDown", new Type[] { typeof(string) })] internal static class ProductionConsumedButtonDownPatch { [HarmonyPriority(800)] [HarmonyAfter(new string[] { "chazman.RunicStorage" })] [HarmonyBefore(new string[] { "chazman.RunicAgriculture", "chazman.RunicInventory" })] private static bool Prefix(string name, ref bool __result) { if (!ProductionLinkInput.ShouldSuppress(name, Time.frameCount)) { return true; } __result = false; return false; } } [HarmonyPatch] internal static class ProductionSetControlsInputPatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(Player), "SetControls", new Type[12] { typeof(Vector3), typeof(bool), typeof(bool), typeof(bool), typeof(bool), typeof(bool), typeof(bool), typeof(bool), typeof(bool), typeof(bool), typeof(bool), typeof(bool) }, (Type[])null); } [HarmonyPriority(800)] [HarmonyBefore(new string[] { "chazman.RunicBuildCamera", "chazman.RunicInventory" })] private static void Prefix(Player __instance, ref bool attack, ref bool attackHold, ref bool secondaryAttack, ref bool secondaryAttackHold, ref bool block, ref bool blockHold) { try { ProductionRuntime.TickLocalPlayer(__instance); } catch (Exception exception) { ProductionRuntime.FailHook("Player.SetControls link input", exception); } ProductionLinkInput.SuppressSetControls(__instance, Time.frameCount, ref attack, ref attackHold, ref secondaryAttack, ref secondaryAttackHold, ref block, ref blockHold); } } [HarmonyPatch(typeof(Smelter), "Awake")] internal static class ProductionSmelterAwakePatch { private static void Postfix(Smelter __instance) { try { ProductionRuntime.Register(__instance); } catch (Exception exception) { ProductionRuntime.FailHook("Smelter.Awake", exception); } } } [HarmonyPatch(typeof(CookingStation), "Awake")] internal static class ProductionCookingAwakePatch { private static void Postfix(CookingStation __instance) { try { ProductionRuntime.Register(__instance); } catch (Exception exception) { ProductionRuntime.FailHook("CookingStation.Awake", exception); } } } [HarmonyPatch(typeof(CraftingStation), "Start")] internal static class ProductionRecipeStartPatch { private static void Postfix(CraftingStation __instance) { try { ProductionRuntime.Register(__instance); } catch (Exception exception) { ProductionRuntime.FailHook("CraftingStation.Start", exception); } } } [HarmonyPatch(typeof(Fermenter), "Awake")] internal static class ProductionFermenterAwakePatch { private static void Postfix(Fermenter __instance) { try { ProductionRuntime.Register(__instance); } catch (Exception exception) { ProductionRuntime.FailHook("Fermenter.Awake", exception); } } } [HarmonyPatch(typeof(Fireplace), "Awake")] internal static class ProductionFireplaceAwakePatch { private static void Postfix(Fireplace __instance) { try { ProductionRuntime.Register(__instance); } catch (Exception exception) { ProductionRuntime.FailHook("Fireplace.Awake", exception); } } } [HarmonyPatch(typeof(Smelter), "Spawn")] internal static class ProductionSmelterSpawnPatch { private static bool Prefix(Smelter __instance, string ore, int stack) { try { return !ProductionRuntime.TryDepositProducedOutput(__instance, ore, stack); } catch (Exception exception) { ProductionRuntime.FailStation((Component)(object)__instance, "Smelter.Spawn", exception); return true; } } } [HarmonyPatch(typeof(Smelter), "OnHoverAddOre")] internal static class ProductionSmelterInputHoverPatch { private static void Postfix(Smelter __instance, ref string __result) { ProductionRuntime.AppendHover((Component)(object)__instance, ProductionLinkRole.Input, ref __result); } } [HarmonyPatch(typeof(Smelter), "OnHoverAddFuel")] internal static class ProductionSmelterFuelHoverPatch { private static void Postfix(Smelter __instance, ref string __result) { ProductionRuntime.AppendHover((Component)(object)__instance, ProductionLinkRole.Fuel, ref __result); } } [HarmonyPatch(typeof(Smelter), "OnHoverEmptyOre")] internal static class ProductionSmelterOutputHoverPatch { private static void Postfix(Smelter __instance, ref string __result) { ProductionRuntime.AppendHover((Component)(object)__instance, ProductionLinkRole.Output, ref __result); } } [HarmonyPatch(typeof(CookingStation), "GetHoverText")] internal static class ProductionCookingHoverPatch { private static void Postfix(CookingStation __instance, ref string __result) { ProductionRuntime.AppendHover((Component)(object)__instance, ProductionLinkRole.Input, ref __result); } } [HarmonyPatch(typeof(CraftingStation), "GetHoverText")] internal static class ProductionRecipeHoverPatch { private static void Postfix(CraftingStation __instance, ref string __result) { ProductionRuntime.AppendHover((Component)(object)__instance, ProductionLinkRole.Input, ref __result); } } [HarmonyPatch(typeof(Fermenter), "GetHoverText")] internal static class ProductionFermenterHoverPatch { private static void Postfix(Fermenter __instance, ref string __result) { ProductionRuntime.AppendHover((Component)(object)__instance, ProductionLinkRole.Input, ref __result); } } [HarmonyPatch(typeof(Fireplace), "GetHoverText")] internal static class ProductionFireplaceHoverPatch { private static void Postfix(Fireplace __instance, ref string __result) { ProductionRuntime.AppendHover((Component)(object)__instance, ProductionLinkRole.Fuel, ref __result); } } [HarmonyPatch(typeof(Player), "Interact", new Type[] { typeof(GameObject), typeof(bool), typeof(bool) })] [HarmonyPriority(800)] internal static class ProductionPlayerInteractPatch { private static bool Prefix(Player __instance, GameObject go, bool hold, bool alt) { try { if (!ProductionRuntime.TryHandleInteraction(__instance, go, hold, alt, out var _)) { return true; } return false; } catch (Exception exception) { ProductionRuntime.FailHook("Player.Interact", exception); return true; } } } internal enum MultiReplenishmentCatalogCommitCode { Committed, NoChange, Absent, Invalid, Conflict, Rejected } internal sealed class MultiReplenishmentCatalogPublication { private readonly string[] _slotRecords; internal MultiReplenishmentCatalog Catalog { get; } internal string FinalIndexRecord { get; } internal int AggregateEncodedCharacters { get; } internal int AggregateDecodedBytes { get; } internal IReadOnlyList SlotRecords => new ReadOnlyCollection((string[])_slotRecords.Clone()); internal object Snapshot { get; } internal MultiReplenishmentCatalogPublication(MultiReplenishmentCatalog catalog, string finalIndexRecord, string[] slotRecords, int aggregateEncodedCharacters, int aggregateDecodedBytes, object snapshot) { Catalog = catalog ?? throw new ArgumentNullException("catalog"); FinalIndexRecord = finalIndexRecord ?? throw new ArgumentNullException("finalIndexRecord"); if (slotRecords == null || slotRecords.Length != 16) { throw new ArgumentException("Exactly sixteen fixed slot values are required.", "slotRecords"); } _slotRecords = (string[])slotRecords.Clone(); AggregateEncodedCharacters = aggregateEncodedCharacters; AggregateDecodedBytes = aggregateDecodedBytes; Snapshot = snapshot ?? throw new ArgumentNullException("snapshot"); } internal string SlotRecord(int slot) { return _slotRecords[slot] ?? string.Empty; } } internal static class MultiReplenishmentCatalogStore { private enum IndexMode { Final = 1, Transition } private sealed class SlotEnvelope { internal string CatalogId { get; } internal string StationId { get; } internal ReplenishmentDestinationRecord Record { get; } internal SlotEnvelope(string catalogId, string stationId, ReplenishmentDestinationRecord record) { CatalogId = catalogId; StationId = stationId; Record = record; } } private sealed class IndexSnapshot { private readonly ReadOnlyCollection _descriptors; internal string CatalogId { get; } internal string StationId { get; } internal int CatalogRevision { get; } internal long NextOrdinal { get; } internal long CursorOrdinal { get; } internal IReadOnlyList Descriptors => _descriptors; internal IndexSnapshot(string catalogId, string stationId, int catalogRevision, long nextOrdinal, long cursorOrdinal, IEnumerable descriptors) { CatalogId = catalogId; StationId = stationId; CatalogRevision = catalogRevision; NextOrdinal = nextOrdinal; CursorOrdinal = cursorOrdinal; _descriptors = new List(descriptors).AsReadOnly(); } internal SlotDescriptor Find(int slot) { foreach (SlotDescriptor descriptor in _descriptors) { if (descriptor.Slot == slot) { return descriptor; } } return null; } } private sealed class SlotDescriptor { private readonly byte[] _digest; internal int Slot { get; } internal long Ordinal { get; } internal int RecordRevision { get; } internal string LinkId { get; } internal ZDOID Target { get; } internal string TargetToken { get; } internal int TargetPrefabHash { get; } internal int LinkRevision { get; } internal byte[] Digest => CopyDigest(); internal SlotDescriptor(int slot, long ordinal, int recordRevision, string linkId, ZDOID target, string targetToken, int targetPrefabHash, int linkRevision, byte[] digest) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) if (slot < 0 || slot >= 16 || ordinal < 1 || recordRevision < 1 || ((ZDOID)(ref target)).IsNone() || linkRevision < 1 || string.IsNullOrWhiteSpace(linkId) || linkId.Length > 200 || !StockDomainValidation.IsLegacyOrWorldObjectIdentity(targetToken, targetPrefabHash) || digest == null || digest.Length != 32) { throw new InvalidOperationException("Invalid catalog slot descriptor."); } Slot = slot; Ordinal = ordinal; RecordRevision = recordRevision; LinkId = linkId; Target = target; TargetToken = targetToken ?? string.Empty; TargetPrefabHash = targetPrefabHash; LinkRevision = linkRevision; _digest = (byte[])digest.Clone(); } internal byte[] CopyDigest() { return (byte[])_digest.Clone(); } internal static bool Same(SlotDescriptor left, SlotDescriptor right) { //IL_003c: 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) if (left == right) { return true; } if (left == null || right == null) { return false; } if (left.Slot == right.Slot && left.Ordinal == right.Ordinal && left.RecordRevision == right.RecordRevision && left.Target == right.Target && left.TargetPrefabHash == right.TargetPrefabHash && left.LinkRevision == right.LinkRevision && string.Equals(left.TargetToken, right.TargetToken, StringComparison.Ordinal) && string.Equals(left.LinkId, right.LinkId, StringComparison.Ordinal)) { return DigestMatches(left._digest, right._digest); } return false; } } internal const int MaximumAggregateEncodedCharacters = 65536; internal const int MaximumAggregateDecodedBytes = 65536; private const int LegacySchemaVersion = 1; private const int StableIdentitySchemaVersion = 2; private const int SchemaVersion = 3; private const int DigestBytes = 32; private const int MaximumStableTextCharacters = 256; private static readonly string IndexKey = "runic.production.stock.destinations.index"; private static readonly string[] SlotKeys = CreateSlotKeys(); internal static string IndexStorageKey => IndexKey; internal static IReadOnlyList FixedSlotStorageKeys => new ReadOnlyCollection((string[])SlotKeys.Clone()); internal static StoredRecordState Read(ZDO stationZdo, out MultiReplenishmentCatalog catalog) { catalog = null; if (stationZdo == null) { return StoredRecordState.Absent; } bool wasTransition; return Parse(stationZdo.GetString(IndexKey, string.Empty), ReadSlots(stationZdo), out catalog, out wasTransition); } internal static bool TryCaptureCanonicalState(ZDO stationZdo, out MultiReplenishmentCatalog catalog, out string fingerprint, out string semanticRevision, out string failure) { catalog = null; fingerprint = string.Empty; semanticRevision = string.Empty; failure = "production-replenishment-catalog-unavailable"; if (stationZdo == null || !stationZdo.IsValid()) { return false; } switch (Read(stationZdo, out catalog)) { case StoredRecordState.Invalid: failure = "production-replenishment-catalog-corrupt"; return false; case StoredRecordState.Absent: fingerprint = CanonicalAbsentFingerprint(); semanticRevision = "production.replenishment.absent.v1"; failure = "ok"; return true; default: try { fingerprint = CanonicalFingerprint(catalog); semanticRevision = "production.replenishment.v1." + catalog.Revision.ToString(CultureInfo.InvariantCulture); failure = "ok"; return true; } catch { catalog = null; fingerprint = string.Empty; semanticRevision = string.Empty; failure = "production-replenishment-catalog-canonicalization-failed"; return false; } } } internal static string CanonicalFingerprint(MultiReplenishmentCatalog catalog) { if (catalog == null) { return CanonicalAbsentFingerprint(); } MultiReplenishmentCatalogPublication multiReplenishmentCatalogPublication = Prepare(catalog); using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true), leaveOpen: true); binaryWriter.Write("production.replenishment.catalog.v1"); binaryWriter.Write(multiReplenishmentCatalogPublication.FinalIndexRecord); for (int i = 0; i < 16; i++) { binaryWriter.Write(multiReplenishmentCatalogPublication.SlotRecord(i)); } binaryWriter.Flush(); using SHA256 sHA = SHA256.Create(); return LowerHex(sHA.ComputeHash(memoryStream.ToArray())); } internal static byte[] EncodeCanonicalDestinationDelta(MultiReplenishmentCatalog catalog, ReplenishmentDestinationRecord destination) { if (catalog == null) { throw new ArgumentNullException("catalog"); } return EncodeCanonicalDestinationDelta(catalog.CatalogId, catalog.StationId, destination); } internal static byte[] EncodeCanonicalDestinationDelta(string catalogId, string stationId, ReplenishmentDestinationRecord destination) { if (destination == null) { throw new ArgumentNullException("destination"); } byte[] array = SerializeSlotBytes(catalogId, stationId, destination); SlotEnvelope slotEnvelope = ParseSlotBytes(array); if (!string.Equals(slotEnvelope.CatalogId, catalogId, StringComparison.Ordinal) || !string.Equals(slotEnvelope.StationId, stationId, StringComparison.Ordinal) || !string.Equals(Encode(SerializeSlotBytes(catalogId, stationId, slotEnvelope.Record)), Encode(array), StringComparison.Ordinal)) { throw new InvalidOperationException("The replenishment destination delta did not round-trip canonically."); } return array; } internal static bool TryDecodeCanonicalDestinationDelta(byte[] bytes, out string catalogId, out string stationId, out ReplenishmentDestinationRecord destination) { catalogId = string.Empty; stationId = string.Empty; destination = null; try { SlotEnvelope slotEnvelope = ParseSlotBytes(bytes); byte[] right = SerializeSlotBytes(slotEnvelope.CatalogId, slotEnvelope.StationId, slotEnvelope.Record); if (!ByteEqual(bytes, right)) { return false; } catalogId = slotEnvelope.CatalogId; stationId = slotEnvelope.StationId; destination = slotEnvelope.Record; return true; } catch { catalogId = string.Empty; stationId = string.Empty; destination = null; return false; } } private static string CanonicalAbsentFingerprint() { using SHA256 sHA = SHA256.Create(); return LowerHex(sHA.ComputeHash(Encoding.UTF8.GetBytes("production.replenishment.catalog.absent.v1"))); } private static string LowerHex(byte[] bytes) { StringBuilder stringBuilder = new StringBuilder(bytes.Length * 2); foreach (byte b in bytes) { stringBuilder.Append(b.ToString("x2", CultureInfo.InvariantCulture)); } return stringBuilder.ToString(); } private static bool ByteEqual(byte[] left, byte[] right) { if (left == null || right == null || left.Length != right.Length) { return false; } int num = 0; for (int i = 0; i < left.Length; i++) { num |= left[i] ^ right[i]; } return num == 0; } internal static StoredRecordState Normalize(ZDO stationZdo, out MultiReplenishmentCatalog catalog) { catalog = null; if (stationZdo == null) { return StoredRecordState.Absent; } string[] encodedSlots = ReadSlots(stationZdo); bool wasTransition; StoredRecordState storedRecordState = Parse(stationZdo.GetString(IndexKey, string.Empty), encodedSlots, out catalog, out wasTransition); if (storedRecordState != StoredRecordState.Valid || !wasTransition) { return storedRecordState; } MultiReplenishmentCatalogPublication multiReplenishmentCatalogPublication = Prepare(catalog); stationZdo.Set(IndexKey, multiReplenishmentCatalogPublication.FinalIndexRecord); ClearUnreferencedSlots(stationZdo, multiReplenishmentCatalogPublication); return StoredRecordState.Valid; } internal static MultiReplenishmentCatalogCommitCode TryInitialize(ZDO stationZdo, MultiReplenishmentCatalog initial) { if (stationZdo == null) { throw new ArgumentNullException("stationZdo"); } if (initial == null) { throw new ArgumentNullException("initial"); } if (!string.IsNullOrEmpty(stationZdo.GetString(IndexKey, string.Empty))) { if (Read(stationZdo, out var catalog) != StoredRecordState.Valid || !CatalogsEqual(catalog, initial)) { return MultiReplenishmentCatalogCommitCode.Conflict; } return MultiReplenishmentCatalogCommitCode.NoChange; } MultiReplenishmentCatalogPublication multiReplenishmentCatalogPublication; try { multiReplenishmentCatalogPublication = Prepare(initial); } catch { return MultiReplenishmentCatalogCommitCode.Rejected; } for (int i = 0; i < SlotKeys.Length; i++) { if (!string.IsNullOrEmpty(multiReplenishmentCatalogPublication.SlotRecord(i))) { stationZdo.Set(SlotKeys[i], multiReplenishmentCatalogPublication.SlotRecord(i)); } } stationZdo.Set(IndexKey, multiReplenishmentCatalogPublication.FinalIndexRecord); ClearUnreferencedSlots(stationZdo, multiReplenishmentCatalogPublication); if (Read(stationZdo, out var catalog2) != StoredRecordState.Valid || !CatalogsEqual(catalog2, initial)) { return MultiReplenishmentCatalogCommitCode.Invalid; } return MultiReplenishmentCatalogCommitCode.Committed; } internal static MultiReplenishmentCatalogCommitCode TryCommit(ZDO stationZdo, MultiReplenishmentCatalog expected, MultiReplenishmentCatalog updated) { if (stationZdo == null) { throw new ArgumentNullException("stationZdo"); } if (expected == null) { throw new ArgumentNullException("expected"); } if (updated == null) { throw new ArgumentNullException("updated"); } MultiReplenishmentCatalog catalog; switch (Normalize(stationZdo, out catalog)) { case StoredRecordState.Absent: return MultiReplenishmentCatalogCommitCode.Absent; default: return MultiReplenishmentCatalogCommitCode.Invalid; case StoredRecordState.Valid: { if (!CatalogsEqual(catalog, expected)) { return MultiReplenishmentCatalogCommitCode.Conflict; } if (CatalogsEqual(expected, updated)) { return MultiReplenishmentCatalogCommitCode.NoChange; } MultiReplenishmentCatalogPublication multiReplenishmentCatalogPublication; MultiReplenishmentCatalogPublication multiReplenishmentCatalogPublication2; string text; int changedSlot; try { multiReplenishmentCatalogPublication = Prepare(expected); multiReplenishmentCatalogPublication2 = Prepare(updated); text = CreateTransitionIndex(multiReplenishmentCatalogPublication, multiReplenishmentCatalogPublication2, out changedSlot); } catch { return MultiReplenishmentCatalogCommitCode.Rejected; } ClearUnreferencedSlots(stationZdo, multiReplenishmentCatalogPublication); stationZdo.Set(IndexKey, text); if (changedSlot >= 0) { string text2 = multiReplenishmentCatalogPublication2.SlotRecord(changedSlot); if (!string.IsNullOrEmpty(text2)) { stationZdo.Set(SlotKeys[changedSlot], text2); } } stationZdo.Set(IndexKey, multiReplenishmentCatalogPublication2.FinalIndexRecord); ClearUnreferencedSlots(stationZdo, multiReplenishmentCatalogPublication2); if (Read(stationZdo, out var catalog2) != StoredRecordState.Valid || !CatalogsEqual(catalog2, updated)) { return MultiReplenishmentCatalogCommitCode.Invalid; } return MultiReplenishmentCatalogCommitCode.Committed; } } } internal static MultiReplenishmentCatalogPublication Prepare(MultiReplenishmentCatalog catalog) { //IL_0085: Unknown result type (might be due to invalid IL or missing references) if (catalog == null) { throw new ArgumentNullException("catalog"); } List list = new List(catalog.Destinations.Count); string[] array = new string[16]; foreach (ReplenishmentDestinationRecord destination in catalog.Destinations) { byte[] bytes = SerializeSlotBytes(catalog, destination); string text = Encode(bytes); array[destination.Slot] = text; StoredProductionLink link = destination.Link; list.Add(new SlotDescriptor(destination.Slot, destination.Ordinal, destination.RecordRevision, destination.LinkId, link.Target, link.TargetToken, link.TargetPrefabHash, link.Revision, ComputeDigest(bytes))); } list.Sort((SlotDescriptor left, SlotDescriptor right) => left.Slot.CompareTo(right.Slot)); IndexSnapshot indexSnapshot = new IndexSnapshot(catalog.CatalogId, catalog.StationId, catalog.Revision, catalog.NextOrdinal, catalog.DestinationCursorOrdinal, list); string text2 = Encode(SerializeIndexBytes(IndexMode.Final, indexSnapshot, null)); Measure(text2, array, out var encodedCharacters, out var decodedBytes); return new MultiReplenishmentCatalogPublication(catalog, text2, array, encodedCharacters, decodedBytes, indexSnapshot); } internal static string CreateTransitionIndex(MultiReplenishmentCatalogPublication oldPublication, MultiReplenishmentCatalogPublication newPublication, out int changedSlot) { if (oldPublication == null) { throw new ArgumentNullException("oldPublication"); } if (newPublication == null) { throw new ArgumentNullException("newPublication"); } IndexSnapshot indexSnapshot = (IndexSnapshot)oldPublication.Snapshot; IndexSnapshot indexSnapshot2 = (IndexSnapshot)newPublication.Snapshot; ValidateTransition(indexSnapshot, indexSnapshot2, out changedSlot); string text = Encode(SerializeIndexBytes(IndexMode.Transition, indexSnapshot, indexSnapshot2)); string[] array = new string[16]; for (int i = 0; i < array.Length; i++) { string text2 = oldPublication.SlotRecord(i); string text3 = newPublication.SlotRecord(i); array[i] = (((text2?.Length ?? 0) >= (text3?.Length ?? 0)) ? text2 : text3); } Measure(text, array, out var _, out var _); return text; } internal static StoredRecordState Parse(string encodedIndex, IReadOnlyList encodedSlots, out MultiReplenishmentCatalog catalog, out bool wasTransition) { //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Expected O, but got Unknown catalog = null; wasTransition = false; if (string.IsNullOrEmpty(encodedIndex)) { return StoredRecordState.Absent; } if (encodedSlots == null || encodedSlots.Count != 16) { return StoredRecordState.Invalid; } try { string[] array = CopySlots(encodedSlots); long num = encodedIndex.Length; string[] array2 = array; int i; for (i = 0; i < array2.Length; i++) { num += array2[i]?.Length ?? 0; } if (num > 65536) { return StoredRecordState.Invalid; } byte[] array3 = Decode(encodedIndex); if (array3.Length == 0 || array3.Length > 65536) { return StoredRecordState.Invalid; } ZPackage val = new ZPackage(array3); int schema = val.ReadInt(); if (!IsSupportedSchema(schema)) { return StoredRecordState.Invalid; } IndexMode indexMode = (IndexMode)val.ReadInt(); if (!Enum.IsDefined(typeof(IndexMode), indexMode)) { return StoredRecordState.Invalid; } IndexSnapshot indexSnapshot = ReadSnapshot(val, schema); IndexSnapshot indexSnapshot2 = ((indexMode == IndexMode.Transition) ? ReadSnapshot(val, schema) : null); if (val.GetPos() != val.Size()) { return StoredRecordState.Invalid; } if (indexMode == IndexMode.Final) { if (!TryLoad(indexSnapshot, array, out catalog, out var decodedSlotBytes)) { return StoredRecordState.Invalid; } if (array3.Length + decodedSlotBytes > 65536) { catalog = null; return StoredRecordState.Invalid; } return StoredRecordState.Valid; } wasTransition = true; ValidateTransition(indexSnapshot, indexSnapshot2, out i); MultiReplenishmentCatalog catalog2; int decodedSlotBytes2; bool flag = TryLoad(indexSnapshot, array, out catalog2, out decodedSlotBytes2); if (TryLoad(indexSnapshot2, array, out var catalog3, out var decodedSlotBytes3) && array3.Length + decodedSlotBytes3 <= 65536) { catalog = catalog3; return StoredRecordState.Valid; } if (flag && array3.Length + decodedSlotBytes2 <= 65536) { catalog = catalog2; return StoredRecordState.Valid; } catalog = null; return StoredRecordState.Invalid; } catch { catalog = null; wasTransition = false; return StoredRecordState.Invalid; } } internal static bool CatalogsEqual(MultiReplenishmentCatalog left, MultiReplenishmentCatalog right) { if (left == right) { return true; } if (left == null || right == null) { return false; } try { return string.Equals(Prepare(left).FinalIndexRecord, Prepare(right).FinalIndexRecord, StringComparison.Ordinal); } catch { return false; } } private static bool TryLoad(IndexSnapshot snapshot, string[] slots, out MultiReplenishmentCatalog catalog, out int decodedSlotBytes) { //IL_0117: 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) catalog = null; decodedSlotBytes = 0; checked { try { List list = new List(snapshot.Descriptors.Count); foreach (SlotDescriptor descriptor in snapshot.Descriptors) { string text = slots[descriptor.Slot]; if (string.IsNullOrEmpty(text)) { return false; } byte[] array = Decode(text); decodedSlotBytes += array.Length; if (!DigestMatches(descriptor.Digest, ComputeDigest(array))) { return false; } SlotEnvelope slotEnvelope = ParseSlotBytes(array); StoredProductionLink link = slotEnvelope.Record.Link; if (!string.Equals(slotEnvelope.CatalogId, snapshot.CatalogId, StringComparison.Ordinal) || !string.Equals(slotEnvelope.StationId, snapshot.StationId, StringComparison.Ordinal) || slotEnvelope.Record.Slot != descriptor.Slot || slotEnvelope.Record.Ordinal != descriptor.Ordinal || slotEnvelope.Record.RecordRevision != descriptor.RecordRevision || !string.Equals(slotEnvelope.Record.LinkId, descriptor.LinkId, StringComparison.Ordinal) || link.Target != descriptor.Target || link.TargetPrefabHash != descriptor.TargetPrefabHash || !string.Equals(link.TargetToken, descriptor.TargetToken, StringComparison.Ordinal) || link.Revision != descriptor.LinkRevision) { return false; } list.Add(slotEnvelope.Record); } catalog = new MultiReplenishmentCatalog(snapshot.CatalogId, snapshot.StationId, snapshot.CatalogRevision, snapshot.NextOrdinal, snapshot.CursorOrdinal, list); return true; } catch { catalog = null; decodedSlotBytes = 0; return false; } } } private static byte[] SerializeSlotBytes(MultiReplenishmentCatalog catalog, ReplenishmentDestinationRecord destination) { return SerializeSlotBytes(catalog?.CatalogId ?? throw new ArgumentNullException("catalog"), catalog.StationId, destination); } private static byte[] SerializeSlotBytes(string catalogId, string stationId, ReplenishmentDestinationRecord destination) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Expected O, but got Unknown if (string.IsNullOrEmpty(catalogId) || string.IsNullOrEmpty(stationId) || destination == null) { throw new ArgumentException("A complete catalog destination identity is required."); } ZPackage val = new ZPackage(); val.Write(3); val.Write(catalogId); val.Write(stationId); val.Write(destination.Slot); val.Write(destination.Ordinal); val.Write(destination.RecordRevision); val.Write((int)destination.State); val.Write((int)destination.FaultCode); WriteStoredLink(val, destination.Link, includeStableIdentity: true); WritePlan(val, destination.Plan, includeStableIdentity: true); byte[] array = val.GetArray(); if (array.Length == 0 || array.Length > 65536) { throw new InvalidOperationException("A replenishment destination exceeds its decoded storage bound."); } return array; } private static SlotEnvelope ParseSlotBytes(byte[] bytes) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown if (bytes == null || bytes.Length == 0 || bytes.Length > 65536) { throw new InvalidOperationException("The destination slot is outside its size bound."); } ZPackage val = new ZPackage(bytes); int num = val.ReadInt(); if (!IsSupportedSchema(num)) { throw new InvalidOperationException("Unsupported destination slot schema."); } string catalogId = ReadStableText(val, 200); string stationId = ReadStableText(val, 200); int slot = val.ReadInt(); long ordinal = val.ReadLong(); int recordRevision = val.ReadInt(); ReplenishmentDestinationState state = (ReplenishmentDestinationState)val.ReadInt(); ProductionStopCode faultCode = (ProductionStopCode)val.ReadInt(); StoredProductionLink storedProductionLink = ReadStoredLink(val, num >= 2); ReplenishmentPlan plan = ReadPlan(val, num, storedProductionLink.OwnerId); if (val.GetPos() != val.Size()) { throw new InvalidOperationException("The destination slot contains trailing data."); } return new SlotEnvelope(catalogId, stationId, new ReplenishmentDestinationRecord(slot, ordinal, recordRevision, state, faultCode, storedProductionLink, plan)); } private static byte[] SerializeIndexBytes(IndexMode mode, IndexSnapshot first, IndexSnapshot second) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(3); val.Write((int)mode); WriteSnapshot(val, first); if (mode == IndexMode.Transition) { WriteSnapshot(val, second ?? throw new ArgumentNullException("second")); } byte[] array = val.GetArray(); if (array.Length == 0 || array.Length > 65536) { throw new InvalidOperationException("The catalog index exceeds its decoded size bound."); } return array; } private static void WriteSnapshot(ZPackage package, IndexSnapshot snapshot) { //IL_00a2: Unknown result type (might be due to invalid IL or missing references) if (snapshot == null) { throw new ArgumentNullException("snapshot"); } package.Write(snapshot.CatalogId); package.Write(snapshot.StationId); package.Write(snapshot.CatalogRevision); package.Write(snapshot.NextOrdinal); package.Write(snapshot.CursorOrdinal); package.Write(snapshot.Descriptors.Count); foreach (SlotDescriptor descriptor in snapshot.Descriptors) { package.Write(descriptor.Slot); package.Write(descriptor.Ordinal); package.Write(descriptor.RecordRevision); package.Write(descriptor.LinkId); package.Write(descriptor.Target); package.Write(descriptor.TargetToken); package.Write(descriptor.TargetPrefabHash); package.Write(descriptor.LinkRevision); package.Write(descriptor.CopyDigest()); } } private static IndexSnapshot ReadSnapshot(ZPackage package, int schema) { //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) string catalogId = ReadStableText(package, 200); string stationId = ReadStableText(package, 200); int num = package.ReadInt(); long num2 = package.ReadLong(); long cursorOrdinal = package.ReadLong(); int num3 = package.ReadInt(); if (num < 1 || num2 < 1 || num3 < 0 || num3 > 16) { throw new InvalidOperationException("Invalid catalog index bounds."); } List list = new List(num3); HashSet hashSet = new HashSet(); HashSet hashSet2 = new HashSet(); HashSet hashSet3 = new HashSet(StringComparer.Ordinal); HashSet hashSet4 = new HashSet(); HashSet hashSet5 = new HashSet(StringComparer.Ordinal); for (int i = 0; i < num3; i++) { int num4 = package.ReadInt(); long num5 = package.ReadLong(); int recordRevision = package.ReadInt(); string text = ReadStableText(package, 200); ZDOID val = package.ReadZDOID(); string text2 = ((schema >= 2) ? package.ReadString() : string.Empty); int targetPrefabHash = ((schema >= 2) ? package.ReadInt() : 0); int linkRevision = package.ReadInt(); byte[] digest = package.ReadByteArray(); SlotDescriptor item = new SlotDescriptor(num4, num5, recordRevision, text, val, text2, targetPrefabHash, linkRevision, digest); if (!hashSet.Add(num4) || !hashSet2.Add(num5) || !hashSet3.Add(text) || !hashSet4.Add(val)) { throw new InvalidOperationException("The catalog index contains duplicates."); } if (!string.IsNullOrEmpty(text2) && !hashSet5.Add(text2)) { throw new InvalidOperationException("The catalog index contains duplicate persistent targets."); } list.Add(item); } list.Sort((SlotDescriptor left, SlotDescriptor right) => left.Slot.CompareTo(right.Slot)); return new IndexSnapshot(catalogId, stationId, num, num2, cursorOrdinal, list); } private static void ValidateTransition(IndexSnapshot oldSnapshot, IndexSnapshot newSnapshot, out int changedSlot) { if (oldSnapshot == null || newSnapshot == null) { throw new ArgumentNullException("newSnapshot"); } if (!string.Equals(oldSnapshot.CatalogId, newSnapshot.CatalogId, StringComparison.Ordinal) || !string.Equals(oldSnapshot.StationId, newSnapshot.StationId, StringComparison.Ordinal) || oldSnapshot.CatalogRevision == int.MaxValue || newSnapshot.CatalogRevision != oldSnapshot.CatalogRevision + 1) { throw new InvalidOperationException("Catalog transitions must advance one exact station revision."); } changedSlot = -1; for (int i = 0; i < 16; i++) { SlotDescriptor left = oldSnapshot.Find(i); SlotDescriptor right = newSnapshot.Find(i); if (!SlotDescriptor.Same(left, right)) { if (changedSlot >= 0) { throw new InvalidOperationException("A crash-safe catalog mutation may replace at most one fixed slot."); } changedSlot = i; } } } private static void WriteStoredLink(ZPackage package, StoredProductionLink link, bool includeStableIdentity) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) package.Write(link.LinkId); package.Write((int)link.Role); package.Write(link.Target); package.Write(link.ExpectedPosition); package.Write(link.OwnerId); package.Write(link.StationOwnerId); package.Write(link.TargetOwnerId); package.Write(link.Revision); if (includeStableIdentity) { package.Write(link.TargetToken ?? string.Empty); package.Write(link.TargetPrefabHash); } } private static StoredProductionLink ReadStoredLink(ZPackage package, bool includesStableIdentity) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //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) StoredProductionLink storedProductionLink = new StoredProductionLink { LinkId = ReadStableText(package, 200), Role = (ProductionLinkRole)package.ReadInt(), Target = package.ReadZDOID(), ExpectedPosition = package.ReadVector3(), OwnerId = package.ReadLong(), StationOwnerId = package.ReadLong(), TargetOwnerId = package.ReadLong(), Revision = package.ReadInt() }; if (includesStableIdentity) { storedProductionLink.TargetToken = package.ReadString(); storedProductionLink.TargetPrefabHash = package.ReadInt(); } else { storedProductionLink.TargetToken = string.Empty; storedProductionLink.TargetPrefabHash = 0; } return storedProductionLink; } private static void WritePlan(ZPackage package, ReplenishmentPlan plan, bool includeStableIdentity) { package.Write(plan.PlanId); package.Write(plan.Revision); package.Write(plan.Cursor); package.Write(plan.StationPrefabId); package.Write((int)plan.AdapterKind); package.Write(plan.AuthorizedPlayerId); package.Write(plan.AuthorizedPlayerName); package.Write(plan.Link.LinkId); package.Write(plan.Link.StationId); package.Write(plan.Link.TargetId); if (includeStableIdentity) { package.Write(plan.Link.TargetToken); package.Write(plan.Link.TargetPrefabHash); } package.Write(plan.Link.Revision); package.Write(plan.Targets.Count); foreach (ReplenishmentTargetAuthorization target in plan.Targets) { package.Write(target.OutputPrefabId); package.Write((int)target.ProducerKind); package.Write(target.ProducerId); package.Write(target.CopyProducerSignature()); package.Write(target.OutputAmount); package.Write(target.RequiredStationName); package.Write(target.RequiredStationLevel); package.Write(target.AuthorizedPlayerId); package.Write(target.AuthorizedPlayerName); package.Write(target.Requirements.Count); foreach (ReplenishmentRequirement requirement in target.Requirements) { package.Write(requirement.PrefabId); package.Write(requirement.Amount); } } } private static ReplenishmentPlan ReadPlan(ZPackage package, int schema, long expectedOwnerId) { string planId = ReadStableText(package, 200); int revision = package.ReadInt(); int cursor = package.ReadInt(); string stationPrefabId = package.ReadString(); ReplenishmentProducerKind adapterKind = (ReplenishmentProducerKind)package.ReadInt(); long playerId = ((schema >= 3) ? package.ReadLong() : 0); string playerName = ((schema >= 3) ? package.ReadString() : string.Empty); string linkId = ReadStableText(package, 200); string stationId = ReadStableText(package, 200); string targetId = ReadStableText(package, 200); string targetToken = ((schema >= 2) ? package.ReadString() : string.Empty); int targetPrefabHash = ((schema >= 2) ? package.ReadInt() : 0); ReplenishmentLinkBinding link = new ReplenishmentLinkBinding(linkId, stationId, targetId, targetToken, targetPrefabHash, package.ReadInt()); int num = package.ReadInt(); if (num < 0 || num > 32) { throw new InvalidOperationException("Invalid replenishment target count."); } List list = new List(num); for (int i = 0; i < num; i++) { string outputPrefabId = package.ReadString(); ReplenishmentProducerKind producerKind = (ReplenishmentProducerKind)package.ReadInt(); string producerId = package.ReadString(); byte[] producerSignature = package.ReadByteArray(); int outputAmount = package.ReadInt(); string requiredStationName = package.ReadString(); int requiredStationLevel = package.ReadInt(); long authorizedPlayerId = package.ReadLong(); string authorizedPlayerName = package.ReadString(); int num2 = package.ReadInt(); if (num2 <= 0 || num2 > 16) { throw new InvalidOperationException("Invalid replenishment requirement count."); } List list2 = new List(num2); for (int j = 0; j < num2; j++) { list2.Add(new ReplenishmentRequirement(package.ReadString(), package.ReadInt())); } list.Add(new ReplenishmentTargetAuthorization(outputPrefabId, producerKind, producerId, producerSignature, outputAmount, requiredStationName, requiredStationLevel, authorizedPlayerId, authorizedPlayerName, list2)); } if (schema < 3 && !TryDeriveLegacyPrincipal(list, out playerId, out playerName)) { throw new InvalidOperationException("A legacy empty or mixed-principal plan cannot be authorized."); } if (playerId == 0L || playerId != expectedOwnerId) { throw new InvalidOperationException("The replenishment plan principal does not match its stored link owner."); } return new ReplenishmentPlan(planId, revision, cursor, stationPrefabId, adapterKind, link, playerId, playerName, list); } private static bool TryDeriveLegacyPrincipal(IReadOnlyList targets, out long playerId, out string playerName) { playerId = 0L; playerName = string.Empty; if (targets == null || targets.Count == 0) { return false; } ReplenishmentTargetAuthorization replenishmentTargetAuthorization = targets[0]; if (replenishmentTargetAuthorization == null || replenishmentTargetAuthorization.AuthorizedPlayerId == 0L || string.IsNullOrWhiteSpace(replenishmentTargetAuthorization.AuthorizedPlayerName)) { return false; } for (int i = 1; i < targets.Count; i++) { ReplenishmentTargetAuthorization replenishmentTargetAuthorization2 = targets[i]; if (replenishmentTargetAuthorization2 == null || replenishmentTargetAuthorization2.AuthorizedPlayerId != replenishmentTargetAuthorization.AuthorizedPlayerId || !string.Equals(replenishmentTargetAuthorization2.AuthorizedPlayerName, replenishmentTargetAuthorization.AuthorizedPlayerName, StringComparison.Ordinal)) { return false; } } playerId = replenishmentTargetAuthorization.AuthorizedPlayerId; playerName = replenishmentTargetAuthorization.AuthorizedPlayerName; return true; } private static bool IsSupportedSchema(int schema) { if (schema != 1 && schema != 2) { return schema == 3; } return true; } private static string[] ReadSlots(ZDO zdo) { string[] array = new string[SlotKeys.Length]; for (int i = 0; i < array.Length; i++) { array[i] = zdo.GetString(SlotKeys[i], string.Empty); } return array; } private static string[] CopySlots(IReadOnlyList source) { string[] array = new string[16]; for (int i = 0; i < array.Length; i++) { array[i] = source[i] ?? string.Empty; } return array; } private static void ClearUnreferencedSlots(ZDO zdo, MultiReplenishmentCatalogPublication publication) { for (int i = 0; i < SlotKeys.Length; i++) { if (string.IsNullOrEmpty(publication.SlotRecord(i))) { zdo.Set(SlotKeys[i], string.Empty); } } } private static void Measure(string encodedIndex, IReadOnlyList slots, out int encodedCharacters, out int decodedBytes) { long num = encodedIndex?.Length ?? 0; long num2 = ((!string.IsNullOrEmpty(encodedIndex)) ? Decode(encodedIndex).Length : 0); foreach (string slot in slots) { num += slot?.Length ?? 0; if (!string.IsNullOrEmpty(slot)) { num2 += Decode(slot).Length; } } if (num <= 0 || num > 65536 || num2 <= 0 || num2 > 65536) { throw new InvalidOperationException("The replenishment catalog exceeds its aggregate 64 KiB storage bound."); } encodedCharacters = (int)num; decodedBytes = (int)num2; } private static string Encode(byte[] bytes) { if (bytes == null || bytes.Length == 0 || bytes.Length > 65536) { throw new InvalidOperationException("The record exceeds its decoded storage bound."); } string text = Convert.ToBase64String(bytes); if (text.Length > 65536) { throw new InvalidOperationException("The record exceeds its encoded storage bound."); } return text; } private static byte[] Decode(string encoded) { if (string.IsNullOrEmpty(encoded) || encoded.Length > 65536) { throw new InvalidOperationException("The record exceeds its encoded storage bound."); } byte[] array = Convert.FromBase64String(encoded); if (array.Length == 0 || array.Length > 65536) { throw new InvalidOperationException("The record exceeds its decoded storage bound."); } return array; } private static byte[] ComputeDigest(byte[] bytes) { using SHA256 sHA = SHA256.Create(); return sHA.ComputeHash(bytes); } private static bool DigestMatches(byte[] expected, byte[] actual) { if (expected == null || actual == null || expected.Length != actual.Length) { return false; } int num = 0; for (int i = 0; i < expected.Length; i++) { num |= expected[i] ^ actual[i]; } return num == 0; } private static string ReadStableText(ZPackage package, int maximum) { string text = package.ReadString(); if (string.IsNullOrWhiteSpace(text) || text.Length > maximum) { throw new InvalidOperationException(); } string text2 = text; for (int i = 0; i < text2.Length; i++) { if (char.IsControl(text2[i])) { throw new InvalidOperationException(); } } return text; } private static string[] CreateSlotKeys() { string[] array = new string[16]; for (int i = 0; i < array.Length; i++) { array[i] = "runic.production.stock.destinations.slot." + i.ToString("00"); } return array; } } internal static class MultiReplenishmentRuntimeSupport { internal static int SoftMaximumDestinations => Math.Max(1, Math.Min(16, ProductionConfig.MaximumLinksPerRole?.Value ?? 8)); internal static StoredRecordState ReadOrMigrate(ZDO stationZdo, string stationId, bool initializeEmpty, out MultiReplenishmentCatalog catalog, out string failure) { catalog = null; failure = string.Empty; if (stationZdo == null || string.IsNullOrWhiteSpace(stationId)) { return Fail("The exact station identity is unavailable.", out failure); } StoredRecordState storedRecordState = MultiReplenishmentCatalogStore.Normalize(stationZdo, out catalog); StoredProductionLink storedProductionLink = ProductionLinkStore.Load(stationZdo, ProductionLinkRole.Replenishment); ReplenishmentPlan plan; StoredRecordState storedRecordState2 = ReplenishmentPlanStore.Read(stationZdo, out plan); switch (storedRecordState) { case StoredRecordState.Invalid: return Fail("The replenishment destination catalog is corrupt or incomplete.", out failure); case StoredRecordState.Valid: if (ProductionEndpointIdentity.IsCanonicalToken(catalog.StationId) && !string.Equals(catalog.StationId, stationId, StringComparison.Ordinal)) { return Fail("The replenishment catalog belongs to another station identity.", out failure); } if (!LegacyDebrisMatchesCatalog(catalog, storedProductionLink, storedRecordState2, plan)) { return Fail("Legacy and catalog replenishment data disagree.", out failure); } if (storedProductionLink != null || storedRecordState2 != StoredRecordState.Absent) { ClearLegacySingleton(stationZdo); } return StoredRecordState.Valid; default: if (storedProductionLink != null || storedRecordState2 != StoredRecordState.Absent) { string text = plan?.Link?.StationId ?? string.Empty; bool flag = ProductionEndpointIdentity.IsCanonicalToken(text); string stationId2 = (flag ? stationId : text); if (storedProductionLink == null || storedRecordState2 != StoredRecordState.Valid || (flag && !string.Equals(text, stationId, StringComparison.Ordinal)) || !ReplenishmentPlanStore.MatchesLink(plan, storedProductionLink, stationId2)) { return Fail("The legacy replenishment link and plan are inconsistent.", out failure); } try { MultiReplenishmentCatalog multiReplenishmentCatalog = MultiReplenishmentCatalogPolicy.FromLegacySingleton(Guid.NewGuid().ToString("N"), stationId2, storedProductionLink, plan); MultiReplenishmentCatalogCommitCode multiReplenishmentCatalogCommitCode = MultiReplenishmentCatalogStore.TryInitialize(stationZdo, multiReplenishmentCatalog); if ((multiReplenishmentCatalogCommitCode != MultiReplenishmentCatalogCommitCode.Committed && multiReplenishmentCatalogCommitCode != MultiReplenishmentCatalogCommitCode.NoChange) || MultiReplenishmentCatalogStore.Read(stationZdo, out catalog) != StoredRecordState.Valid || !MultiReplenishmentCatalogStore.CatalogsEqual(catalog, multiReplenishmentCatalog)) { return Fail("The legacy replenishment catalog did not round-trip.", out failure); } ClearLegacySingleton(stationZdo); return StoredRecordState.Valid; } catch (Exception ex) { return Fail("Legacy replenishment migration failed: " + ex.GetType().Name + ".", out failure); } } if (!initializeEmpty) { return StoredRecordState.Absent; } try { MultiReplenishmentCatalog initial = MultiReplenishmentCatalogPolicy.CreateEmpty(Guid.NewGuid().ToString("N"), stationId); MultiReplenishmentCatalogCommitCode multiReplenishmentCatalogCommitCode2 = MultiReplenishmentCatalogStore.TryInitialize(stationZdo, initial); if ((multiReplenishmentCatalogCommitCode2 != MultiReplenishmentCatalogCommitCode.Committed && multiReplenishmentCatalogCommitCode2 != MultiReplenishmentCatalogCommitCode.NoChange) || MultiReplenishmentCatalogStore.Read(stationZdo, out catalog) != StoredRecordState.Valid) { return Fail("An empty replenishment catalog could not be initialized.", out failure); } return StoredRecordState.Valid; } catch (Exception ex2) { return Fail("Replenishment catalog initialization failed: " + ex2.GetType().Name + ".", out failure); } } } internal static bool LegacyDebrisMatchesCatalog(MultiReplenishmentCatalog catalog, StoredProductionLink legacyLink, StoredRecordState legacyPlanState, ReplenishmentPlan legacyPlan) { if (catalog == null || legacyPlanState == StoredRecordState.Invalid) { return false; } bool flag = legacyPlanState == StoredRecordState.Valid; if (flag != (legacyPlan != null)) { return false; } if (legacyLink == null && !flag) { return true; } int num = 0; foreach (ReplenishmentDestinationRecord destination in catalog.Destinations) { if ((legacyLink == null || ReplenishmentDestinationRecord.SameExactLink(destination.Link, legacyLink)) && (!flag || PlansEqual(destination.Plan, legacyPlan))) { num++; } } return num == 1; } private static bool PlansEqual(ReplenishmentPlan left, ReplenishmentPlan right) { if (left == null || right == null) { return false; } try { return string.Equals(ReplenishmentPlanStore.Serialize(left), ReplenishmentPlanStore.Serialize(right), StringComparison.Ordinal); } catch { return false; } } private static void ClearLegacySingleton(ZDO stationZdo) { ProductionLinkStore.Clear(stationZdo, ProductionLinkRole.Replenishment); ReplenishmentPlanStore.Clear(stationZdo); } private static StoredRecordState Fail(string detail, out string failure) { failure = detail; return StoredRecordState.Invalid; } } internal readonly struct NearbyIngredientContainerCandidate { internal Container Container { get; } internal ZDOID EndpointId { get; } internal float DistanceSquared { get; } internal NearbyIngredientContainerCandidate(Container container, ZDOID endpointId, float distanceSquared) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) Container = container ?? throw new ArgumentNullException("container"); if (((ZDOID)(ref endpointId)).IsNone()) { throw new ArgumentException("An exact container endpoint is required.", "endpointId"); } if (float.IsNaN(distanceSquared) || float.IsInfinity(distanceSquared) || distanceSquared < 0f) { throw new ArgumentOutOfRangeException("distanceSquared"); } EndpointId = endpointId; DistanceSquared = distanceSquared; } } internal sealed class NearbyIngredientContainerQueryResult { private readonly ReadOnlyCollection _candidates; internal IReadOnlyList Candidates => _candidates; internal int CandidateCount { get; } internal bool Truncated { get; } internal NearbyIngredientContainerQueryResult(IEnumerable candidates, int candidateCount, bool truncated) { List list = ((candidates == null) ? new List() : new List(candidates)); if (candidateCount < list.Count) { throw new ArgumentOutOfRangeException("candidateCount"); } CandidateCount = candidateCount; Truncated = truncated; _candidates = new ReadOnlyCollection(list); } } internal static class NearbyIngredientContainerIndex { 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 readonly struct IndexedContainer { internal int InstanceId { get; } internal Container Container { get; } internal IndexedContainer(int instanceId, Container container) { InstanceId = instanceId; Container = container; } } internal const float CellSizeMeters = 10f; internal const float HardMaximumRadiusMeters = 30f; internal const int HardMaximumSourceChests = 64; 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_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)container == (Object)null) { return; } int instanceID; Vector3 position; try { instanceID = ((Object)container).GetInstanceID(); position = ((Component)container).transform.position; } catch { return; } lock (Gate) { if (!IsFinite(position)) { UnregisterLocked(container, instanceID); return; } CellKey cellKey = CellKey.From(position); if (!Membership.TryGetValue(instanceID, out var value) || !value.Equals(cellKey) || !Cells.TryGetValue(value, out var value2) || !ContainsExact(value2, instanceID, container)) { UnregisterLocked(container, instanceID); if (!Cells.TryGetValue(cellKey, out var value3)) { value3 = new List(); Cells.Add(cellKey, value3); } value3.Add(new IndexedContainer(instanceID, container)); Membership[instanceID] = cellKey; } } } internal static void Unregister(Container container) { if (container == null) { return; } int instanceID; try { instanceID = ((Object)container).GetInstanceID(); } catch { return; } lock (Gate) { UnregisterLocked(container, instanceID); } } internal static void SeedLoadedContainers() { Clear(); Container[] array = Object.FindObjectsByType((FindObjectsSortMode)0); if (array != null) { Container[] array2 = array; for (int i = 0; i < array2.Length; i++) { Register(array2[i]); } } } internal static void Clear() { lock (Gate) { Cells.Clear(); Membership.Clear(); } } internal static NearbyIngredientContainerQueryResult Query(Vector3 origin, float radiusMeters, int maximumSourceChests) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) if (!IsFinite(origin) || float.IsNaN(radiusMeters) || float.IsInfinity(radiusMeters) || radiusMeters <= 0f || maximumSourceChests <= 0) { return new NearbyIngredientContainerQueryResult(Array.Empty(), 0, truncated: false); } float num = Math.Min(30f, radiusMeters); int num2 = Math.Min(64, maximumSourceChests); float radiusSquared = num * num; CellKey cellKey = CellKey.From(origin - new Vector3(num, 0f, num)); CellKey cellKey2 = CellKey.From(origin + new Vector3(num, 0f, num)); Dictionary dictionary = new Dictionary(); HashSet hashSet = new HashSet(); 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 num3 = value.Count - 1; num3 >= 0; num3--) { IndexedContainer indexedContainer = value[num3]; Container container = indexedContainer.Container; NearbyIngredientContainerCandidate candidate; if ((Object)(object)container == (Object)null) { value.RemoveAt(num3); Membership.Remove(indexedContainer.InstanceId); } else if (TryCreateCandidate(container, origin, radiusSquared, out candidate) && !hashSet.Contains(candidate.EndpointId)) { if (dictionary.ContainsKey(candidate.EndpointId)) { dictionary.Remove(candidate.EndpointId); hashSet.Add(candidate.EndpointId); } else { dictionary.Add(candidate.EndpointId, candidate); } } } if (value.Count == 0) { Cells.Remove(key); } } } } List list = new List(dictionary.Values); list.Sort(CompareCandidates); int count = list.Count; bool flag = count > num2; if (flag) { list.RemoveRange(num2, count - num2); } return new NearbyIngredientContainerQueryResult(list, count, flag); } internal static bool IsStaticNonWagon(Container container) { if ((Object)(object)container == (Object)null || (Object)(object)container.m_wagon != (Object)null) { return false; } try { Rigidbody componentInParent = ((Component)container).GetComponentInParent(); return (Object)(object)componentInParent == (Object)null || componentInParent.isKinematic; } catch { return false; } } private static bool TryCreateCandidate(Container container, Vector3 origin, float radiusSquared, out NearbyIngredientContainerCandidate candidate) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) candidate = default(NearbyIngredientContainerCandidate); if ((Object)(object)container == (Object)null || !((Behaviour)container).isActiveAndEnabled || !IsStaticNonWagon(container)) { return false; } ZNetView val = ValheimAccess.View(container); ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); if (val2 == null || !val2.IsValid() || ((ZDOID)(ref val2.m_uid)).IsNone()) { return false; } Vector3 position = ((Component)container).transform.position; if (!IsFinite(position)) { return false; } Vector3 val3 = position - origin; float sqrMagnitude = ((Vector3)(ref val3)).sqrMagnitude; if (float.IsNaN(sqrMagnitude) || float.IsInfinity(sqrMagnitude) || sqrMagnitude > radiusSquared) { return false; } candidate = new NearbyIngredientContainerCandidate(container, val2.m_uid, sqrMagnitude); return true; } private static int CompareCandidates(NearbyIngredientContainerCandidate left, NearbyIngredientContainerCandidate right) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) return CompareKeys(left.DistanceSquared, left.EndpointId, right.DistanceSquared, right.EndpointId); } internal static int CompareKeys(float leftDistanceSquared, ZDOID leftEndpointId, float rightDistanceSquared, ZDOID rightEndpointId) { int num = leftDistanceSquared.CompareTo(rightDistanceSquared); if (num != 0) { return num; } int num2 = ((ZDOID)(ref leftEndpointId)).UserID.CompareTo(((ZDOID)(ref rightEndpointId)).UserID); if (num2 == 0) { return ((ZDOID)(ref leftEndpointId)).ID.CompareTo(((ZDOID)(ref rightEndpointId)).ID); } return num2; } 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((IndexedContainer indexedContainer) => indexedContainer.InstanceId == instanceId || indexedContainer.Container == container); if (value2.Count == 0) { Cells.Remove(value); } } Membership.Remove(instanceId); } private static bool ContainsExact(List entries, int instanceId, Container container) { for (int i = 0; i < entries.Count; i++) { if (entries[i].InstanceId == instanceId && entries[i].Container == container) { return true; } } return false; } private static bool IsFinite(Vector3 value) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: 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_0034: 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) if (!float.IsNaN(value.x) && !float.IsInfinity(value.x) && !float.IsNaN(value.y) && !float.IsInfinity(value.y) && !float.IsNaN(value.z)) { return !float.IsInfinity(value.z); } return false; } } [HarmonyPatch(typeof(Container), "Awake")] internal static class NearbyIngredientContainerAwakePatch { private static void Postfix(Container __instance) { NearbyIngredientContainerIndex.Register(__instance); } } [HarmonyPatch(typeof(Container), "OnDestroyed")] internal static class NearbyIngredientContainerDestroyedPatch { private static void Postfix(Container __instance, bool __runOriginal) { if (__runOriginal) { NearbyIngredientContainerIndex.Unregister(__instance); } } } [HarmonyPatch(typeof(Container), "CheckForChanges")] internal static class NearbyIngredientContainerRefreshPatch { private static void Postfix(Container __instance) { NearbyIngredientContainerIndex.Register(__instance); } } internal enum ProductionIdentityStatus { Ready, Missing, Invalid, Unavailable, Ambiguous } internal static class ProductionEndpointIdentity { internal const string TokenStorageKey = "runic.transactions.world-object.token"; internal const string PresenceStorageKey = "runic.transactions.world-object.token-present"; internal const float LegacyPositionToleranceMeters = 0.05f; private const float MaximumResolutionRadiusMeters = 3f; internal static bool IsCanonicalToken(string value) { if (value != null && value.Length == 32 && Guid.TryParseExact(value, "N", out var result) && result != Guid.Empty) { return string.Equals(result.ToString("N"), value, StringComparison.Ordinal); } return false; } internal static bool TryCaptureCurrentTarget(ZDOID target, out string targetToken, out int targetPrefabHash, out Vector3 rootPosition, out string failure) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) targetToken = string.Empty; targetPrefabHash = 0; rootPosition = Vector3.zero; failure = string.Empty; if (((ZDOID)(ref target)).IsNone()) { return Fail("The selected endpoint has no current ZDOID.", out failure); } ZNetScene instance = ZNetScene.instance; if (!ValheimAccess.TryResolveExactContainer((instance != null) ? instance.FindInstance(target) : null, target, out var container)) { return Fail("The selected endpoint does not resolve to one exact loaded Container.", out failure); } ZNetView val = ValheimAccess.View(container); ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); if (val2 == null || val2.m_uid != target || !val.IsOwner()) { return Fail("The selected Container is not owned by this local process.", out failure); } int prefab = val2.GetPrefab(); Vector3 position = val2.GetPosition(); if (prefab == 0 || !ValheimAccess.IsFinite(position)) { return Fail("The selected Container has invalid prefab or position evidence.", out failure); } if (!TryGetOrEnsureToken(val, out var tokenValue)) { return Fail("A stable token could not be read or created on the exact native owner.", out failure); } targetToken = tokenValue; targetPrefabHash = prefab; rootPosition = position; return true; } internal static ProductionIdentityStatus ResolveStable(StoredProductionLink link, out Container container, out ZDO targetZdo, out string failure) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) container = null; targetZdo = null; failure = string.Empty; if (link == null || !IsCanonicalToken(link.TargetToken)) { failure = "The link has no canonical stable endpoint token."; if (!string.IsNullOrEmpty(link?.TargetToken)) { return ProductionIdentityStatus.Invalid; } return ProductionIdentityStatus.Missing; } if (link.TargetPrefabHash == 0 || !ValheimAccess.IsFinite(link.ExpectedPosition)) { failure = "The stable link has incomplete prefab or position evidence."; return ProductionIdentityStatus.Invalid; } if (!((ZDOID)(ref link.Target)).IsNone()) { ZNetScene instance = ZNetScene.instance; if (TryMatchContainer((instance != null) ? instance.FindInstance(link.Target) : null, link.Target, link.TargetToken, link.TargetPrefabHash, out container, out targetZdo)) { return ProductionIdentityStatus.Ready; } } NearbyIngredientContainerQueryResult nearbyIngredientContainerQueryResult = NearbyIngredientContainerIndex.Query(link.ExpectedPosition, 3f, 64); if (nearbyIngredientContainerQueryResult.Truncated) { failure = "Loaded endpoint discovery exceeded its bounded candidate set."; return ProductionIdentityStatus.Ambiguous; } foreach (NearbyIngredientContainerCandidate candidate in nearbyIngredientContainerQueryResult.Candidates) { Container container2 = candidate.Container; ZNetView val = ValheimAccess.View(container2); ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); if (val2 != null && val2.GetPrefab() == link.TargetPrefabHash && TokenMatches(val2, link.TargetToken)) { if ((Object)(object)container != (Object)null && container != container2) { container = null; targetZdo = null; failure = "More than one loaded Container carries the endpoint token."; return ProductionIdentityStatus.Ambiguous; } container = container2; targetZdo = val2; } } if ((Object)(object)container == (Object)null) { failure = "The stable endpoint is not currently loaded."; return ProductionIdentityStatus.Unavailable; } return ProductionIdentityStatus.Ready; } internal static bool TryResolveUniqueLegacyTarget(StoredProductionLink link, out Container container, out ZDO targetZdo, out string failure) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) container = null; targetZdo = null; failure = string.Empty; if (link == null || !string.IsNullOrEmpty(link.TargetToken) || !ValheimAccess.IsFinite(link.ExpectedPosition) || link.TargetOwnerId == 0L) { return Fail("Legacy link evidence is incomplete or already tokenized.", out failure); } NearbyIngredientContainerQueryResult nearbyIngredientContainerQueryResult = NearbyIngredientContainerIndex.Query(link.ExpectedPosition, 2f, 64); if (nearbyIngredientContainerQueryResult.Truncated) { return Fail("Legacy endpoint discovery exceeded its bounded candidate set.", out failure); } float num = 0.0025000002f; foreach (NearbyIngredientContainerCandidate candidate in nearbyIngredientContainerQueryResult.Candidates) { Container container2 = candidate.Container; ZDO val = ValheimAccess.Zdo(container2); if ((Object)(object)container2 == (Object)null || val == null || !NearbyIngredientContainerIndex.IsStaticNonWagon(container2) || (link.TargetPrefabHash != 0 && val.GetPrefab() != link.TargetPrefabHash) || ValheimAccess.Creator((Component)(object)container2) != link.TargetOwnerId) { continue; } Vector3 val2 = val.GetPosition() - link.ExpectedPosition; if (!(((Vector3)(ref val2)).sqrMagnitude > num)) { if ((Object)(object)container != (Object)null) { container = null; targetZdo = null; return Fail("More than one Container occupies the legacy root position.", out failure); } container = container2; targetZdo = val; } } if (!((Object)(object)container != (Object)null)) { return Fail("No unique Container occupies the legacy root position.", out failure); } return true; } internal static bool TryGetOrEnsureToken(ZNetView view, out string tokenValue) { tokenValue = string.Empty; ZDO val = (((Object)(object)view != (Object)null && view.IsValid()) ? view.GetZDO() : null); if (val == null || !val.IsValid()) { return false; } string text = val.GetString("runic.transactions.world-object.token", string.Empty); int num = val.GetInt("runic.transactions.world-object.token-present", 0); if (num == 1 && IsCanonicalToken(text)) { tokenValue = text; return true; } if (!view.IsOwner()) { return false; } if (num != 0 || (text.Length != 0 && !IsCanonicalToken(text))) { return false; } if (text.Length == 0) { text = Guid.NewGuid().ToString("N"); val.Set("runic.transactions.world-object.token", text); if (!string.Equals(val.GetString("runic.transactions.world-object.token", string.Empty), text, StringComparison.Ordinal)) { return false; } } val.Set("runic.transactions.world-object.token-present", 1); if (val.GetInt("runic.transactions.world-object.token-present", 0) != 1 || !string.Equals(val.GetString("runic.transactions.world-object.token", string.Empty), text, StringComparison.Ordinal)) { return false; } tokenValue = text; return true; } internal static bool TokenMatches(ZDO zdo, string token) { if (zdo != null && zdo.IsValid() && IsCanonicalToken(token) && zdo.GetInt("runic.transactions.world-object.token-present", 0) == 1) { return string.Equals(zdo.GetString("runic.transactions.world-object.token", string.Empty), token, StringComparison.Ordinal); } return false; } private static bool TryMatchContainer(GameObject root, ZDOID expected, string token, int prefabHash, out Container container, out ZDO zdo) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) zdo = null; if (!ValheimAccess.TryResolveExactContainer(root, expected, out container)) { return false; } zdo = ValheimAccess.Zdo(container); if (zdo != null && zdo.GetPrefab() == prefabHash && TokenMatches(zdo, token)) { return true; } container = null; zdo = null; return false; } private static bool Fail(string value, out string failure) { failure = value; return false; } } internal enum ProductionLinkHighlightRole { Input = 1, Output, Replenishment } internal sealed class ProductionLinkHighlight : MonoBehaviour { private const int Segments = 56; private const int RingCount = 3; private GameObject _visualRoot; private LineRenderer[] _rings; private Material _material; private Light _light; private Vector3 _center; private float _radius; private float _height; private float _expiresAt; private ProductionLinkHighlightRole _role; private bool _failedClosed; internal void Activate(ProductionLinkHighlightRole role, float lifetimeSeconds) { if (_failedClosed) { return; } try { _role = role; _expiresAt = Math.Max(_expiresAt, Time.unscaledTime + Math.Max(0.1f, lifetimeSeconds)); if ((Object.op_Implicit((Object)(object)_visualRoot) || CreateVisuals()) && (!ApplyColor() || !UpdateVisuals())) { FailClosed(); } } catch { FailClosed(); } } private bool CreateVisuals() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Expected O, but got Unknown //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Expected O, but got Unknown //IL_0050: 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_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) try { Bounds bounds = default(Bounds); ((Bounds)(ref bounds))..ctor(((Component)this).transform.position, Vector3.one); Renderer[] componentsInChildren = ((Component)this).GetComponentsInChildren(); bool flag = false; foreach (Renderer val in componentsInChildren) { if (Object.op_Implicit((Object)(object)val) && !(val is LineRenderer)) { if (!flag) { bounds = val.bounds; flag = true; } else { ((Bounds)(ref bounds)).Encapsulate(val.bounds); } } } _center = ((Bounds)(ref bounds)).center; _radius = Math.Max(0.8f, Math.Max(((Bounds)(ref bounds)).extents.x, ((Bounds)(ref bounds)).extents.z) * 1.4f); _height = Math.Max(0.65f, ((Bounds)(ref bounds)).size.y); _visualRoot = new GameObject("RunicProductionLinkRings"); _visualRoot.transform.SetParent(((Component)this).transform, true); _visualRoot.transform.position = _center; Shader val2 = Shader.Find("Sprites/Default") ?? Shader.Find("UI/Default"); if ((Object)(object)val2 != (Object)null) { _material = new Material(val2); } _rings = (LineRenderer[])(object)new LineRenderer[3]; for (int j = 0; j < _rings.Length; j++) { GameObject val3 = new GameObject("RunicProductionLinkRing" + j); val3.transform.SetParent(_visualRoot.transform, false); LineRenderer val4 = val3.AddComponent(); if (!Object.op_Implicit((Object)(object)val4)) { return FailClosed(); } val4.useWorldSpace = true; val4.loop = true; val4.positionCount = 56; val4.widthMultiplier = ((j == 1) ? 0.105f : 0.075f); val4.numCornerVertices = 2; val4.numCapVertices = 2; if ((Object)(object)_material != (Object)null) { ((Renderer)val4).sharedMaterial = _material; } _rings[j] = val4; } _light = _visualRoot.AddComponent(); if ((Object)(object)_light != (Object)null) { _light.type = (LightType)2; _light.range = _radius * 3.25f; _light.shadows = (LightShadows)0; } if (!VisualsReady()) { return FailClosed(); } return true; } catch { return FailClosed(); } } private void Update() { if (_failedClosed) { return; } try { if (Time.unscaledTime >= _expiresAt) { Object.Destroy((Object)(object)this); } else if (!UpdateVisuals()) { FailClosed(); } } catch { FailClosed(); } } private bool ApplyColor() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) if (!VisualsReady()) { return false; } Color val = ColorFor(_role); for (int i = 0; i < _rings.Length; i++) { LineRenderer val2 = _rings[i]; if (!Object.op_Implicit((Object)(object)val2)) { return false; } float a = ((i == 1) ? 1f : 0.78f); Color startColor = Color.Lerp(val, Color.white, (i == 1) ? 0.15f : 0.04f); startColor.a = a; val2.startColor = startColor; val2.endColor = val; } if ((Object)(object)_light != (Object)null) { _light.color = val; } return true; } private bool UpdateVisuals() { //IL_0114: Unknown result type (might be due to invalid IL or missing references) if (!VisualsReady()) { return false; } float unscaledTime = Time.unscaledTime; for (int i = 0; i < _rings.Length; i++) { LineRenderer val = _rings[i]; if (!Object.op_Implicit((Object)(object)val)) { return false; } float num = ((i % 2 == 0) ? 1f : (-1f)); float num2 = unscaledTime * (1.6f + (float)i * 0.35f) * num; float num3 = _center.y - _height * 0.42f + (float)i * _height * 0.42f; for (int j = 0; j < 56; j++) { float num4 = (float)j * MathF.PI * 2f / 56f + num2; float num5 = Mathf.Sin(num4 * 4f + unscaledTime * 4.5f) * 0.075f; val.SetPosition(j, new Vector3(_center.x + Mathf.Cos(num4) * (_radius + num5), num3 + Mathf.Sin(num4 * 2f + unscaledTime * 3.2f) * 0.07f, _center.z + Mathf.Sin(num4) * (_radius + num5))); } } if ((Object)(object)_light != (Object)null) { _light.intensity = 2.1f + Mathf.Sin(unscaledTime * 5.5f) * 0.5f; } return true; } private bool VisualsReady() { bool missingRing = false; if (_rings != null) { for (int i = 0; i < _rings.Length; i++) { if (!Object.op_Implicit((Object)(object)_rings[i])) { missingRing = true; break; } } } bool rootReady = Object.op_Implicit((Object)(object)_visualRoot); LineRenderer[] rings = _rings; return VisualGraphReady(rootReady, (rings != null) ? rings.Length : 0, missingRing); } internal static bool VisualGraphReady(bool rootReady, int ringCount, bool missingRing) { if (rootReady && ringCount == 3) { return !missingRing; } return false; } private bool FailClosed() { if (_failedClosed) { return false; } _failedClosed = true; ((Behaviour)this).enabled = false; DestroyVisualObjects(); Object.Destroy((Object)(object)this); return false; } private void DestroyVisualObjects() { GameObject visualRoot = _visualRoot; Material material = _material; _visualRoot = null; _rings = null; _light = null; _material = null; try { if (Object.op_Implicit((Object)(object)visualRoot)) { Object.Destroy((Object)(object)visualRoot); } } catch { } try { if (Object.op_Implicit((Object)(object)material)) { Object.Destroy((Object)(object)material); } } catch { } } private void OnDestroy() { _failedClosed = true; DestroyVisualObjects(); } internal static ProductionLinkHighlightRole VisualRole(ProductionLinkRole role) { return role switch { ProductionLinkRole.Replenishment => ProductionLinkHighlightRole.Replenishment, ProductionLinkRole.Output => ProductionLinkHighlightRole.Output, _ => ProductionLinkHighlightRole.Input, }; } internal static Color ColorFor(ProductionLinkHighlightRole role) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) return (Color)(role switch { ProductionLinkHighlightRole.Output => new Color(1f, 0.82f, 0.02f, 1f), ProductionLinkHighlightRole.Replenishment => new Color(0.02f, 1f, 0.82f, 1f), _ => new Color(0.12f, 1f, 0.18f, 1f), }); } internal static void Show(Container container, ProductionLinkRole role, float lifetimeSeconds) { if (Object.op_Implicit((Object)(object)container) && ((Behaviour)container).isActiveAndEnabled) { (((Component)container).GetComponent() ?? ((Component)container).gameObject.AddComponent()).Activate(VisualRole(role), lifetimeSeconds); } } internal static void Hide(Container container) { if (Object.op_Implicit((Object)(object)container)) { ProductionLinkHighlight component = ((Component)container).GetComponent(); if (Object.op_Implicit((Object)(object)component)) { Object.Destroy((Object)(object)component); } } } internal static void ClearAll() { ProductionLinkHighlight[] array = Object.FindObjectsByType((FindObjectsSortMode)0); for (int i = 0; i < array.Length; i++) { if (Object.op_Implicit((Object)(object)array[i])) { Object.Destroy((Object)(object)array[i]); } } } } internal static class ProductionLinkInput { private static int _sampleFrame = -1; private static int _processedGestureFrame = -1; private static bool _captureActive; private static ProductionLinkMouseButton _consumedButton; private static bool AltHeld { get { if (!ValheimAccess.KeyboardKeyHeld((KeyCode)308)) { return ValheimAccess.KeyboardKeyHeld((KeyCode)307); } return true; } } private static bool ShiftHeld { get { if (!ValheimAccess.KeyboardKeyHeld((KeyCode)304)) { return ValheimAccess.KeyboardKeyHeld((KeyCode)303); } return true; } } private static bool ControlHeld { get { if (!ValheimAccess.KeyboardKeyHeld((KeyCode)306)) { return ValheimAccess.KeyboardKeyHeld((KeyCode)305); } return true; } } internal static void BeginSample(int frame) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) if (_sampleFrame != frame) { _sampleFrame = frame; if (_captureActive && !KeyHeld(MouseKey(_consumedButton))) { _captureActive = false; } } } internal static bool TryReadExactGesture(int frame, out ProductionLinkMouseButton button, out bool remove) { button = ProductionLinkMouseButton.Left; remove = false; if (_processedGestureFrame == frame || !AltHeld || ControlHeld) { return false; } bool flag = KeyDown((KeyCode)323); bool flag2 = KeyDown((KeyCode)324); bool flag3 = KeyDown((KeyCode)325); if ((flag ? 1 : 0) + (flag2 ? 1 : 0) + (flag3 ? 1 : 0) != 1) { return false; } button = ((!flag) ? (flag2 ? ProductionLinkMouseButton.Right : ProductionLinkMouseButton.Middle) : ProductionLinkMouseButton.Left); remove = ShiftHeld; return true; } internal static void Consume(int frame, ProductionLinkMouseButton button) { _sampleFrame = frame; _processedGestureFrame = frame; _captureActive = true; _consumedButton = button; } internal static bool ShouldSuppress(string action, int frame) { if (!_captureActive || string.IsNullOrEmpty(action)) { return false; } switch (_consumedButton) { case ProductionLinkMouseButton.Left: return string.Equals(action, "Attack", StringComparison.Ordinal); case ProductionLinkMouseButton.Right: if (!string.Equals(action, "Block", StringComparison.Ordinal)) { return string.Equals(action, "BuildMenu", StringComparison.Ordinal); } return true; case ProductionLinkMouseButton.Middle: if (!string.Equals(action, "SecondaryAttack", StringComparison.Ordinal)) { return string.Equals(action, "Remove", StringComparison.Ordinal); } return true; default: return false; } } internal static bool SuppressSetControls(Player player, int frame, ref bool attack, ref bool attackHold, ref bool secondaryAttack, ref bool secondaryAttackHold, ref bool block, ref bool blockHold) { if (!_captureActive || (Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer) { return false; } switch (_consumedButton) { case ProductionLinkMouseButton.Left: attack = false; attackHold = false; break; case ProductionLinkMouseButton.Right: block = false; blockHold = false; break; case ProductionLinkMouseButton.Middle: secondaryAttack = false; secondaryAttackHold = false; break; } return true; } internal static void Reset() { _sampleFrame = -1; _processedGestureFrame = -1; _captureActive = false; _consumedButton = ProductionLinkMouseButton.Left; } private static bool KeyDown(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) try { return ZInput.GetKeyDown(key, false); } catch { return false; } } private static bool KeyHeld(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) try { return ZInput.GetKey(key, false); } catch { return false; } } private static KeyCode MouseKey(ProductionLinkMouseButton button) { return (KeyCode)(button switch { ProductionLinkMouseButton.Right => 324, ProductionLinkMouseButton.Middle => 325, _ => 323, }); } } internal sealed class StoredProductionLink { internal string LinkId; internal ProductionLinkRole Role; internal string TargetToken; internal int TargetPrefabHash; internal ZDOID Target; internal Vector3 ExpectedPosition; internal long OwnerId; internal long StationOwnerId; internal long TargetOwnerId; internal int Revision; } internal static class ProductionLinkStore { internal const int LegacySchemaVersion = 1; internal const int SchemaVersion = 2; private const int AtomicRecordSchemaVersion = 1; private const int MaximumAtomicRecordCharacters = 2048; private const int MaximumAtomicRecordBytes = 1536; internal const string AtomicTombstone = "!"; private const string InvalidAtomicLinkId = "invalid-atomic-record"; private static readonly string SchemaKey = Key("schema"); private static readonly string StationLinkIdKey = Key("station.link-id"); private static readonly string StationOwnerKey = Key("station.owner"); internal static StoredProductionLink Load(ZDO stationZdo, ProductionLinkRole role) { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) if (stationZdo == null) { return null; } string text = stationZdo.GetString(RoleRecordKey(role), string.Empty); if (string.Equals(text, "!", StringComparison.Ordinal)) { return null; } if (!string.IsNullOrEmpty(text)) { if (!TryParseAtomicRecord(text, role, out var link)) { return InvalidAtomicRecord(role); } return link; } int num = stationZdo.GetInt(SchemaKey, 0); if (num != 1 && num != 2) { return null; } string text2 = RolePrefix(role); ZDOID zDOID = stationZdo.GetZDOID(text2 + ".target"); string text3 = ((num >= 2) ? stationZdo.GetString(text2 + ".target-token", string.Empty) : string.Empty); string text4 = stationZdo.GetString(text2 + ".id", string.Empty); if ((((ZDOID)(ref zDOID)).IsNone() && string.IsNullOrEmpty(text3)) || string.IsNullOrEmpty(text4)) { return null; } return new StoredProductionLink { LinkId = text4, Role = role, TargetToken = text3, TargetPrefabHash = ((num >= 2) ? stationZdo.GetInt(text2 + ".target-prefab", 0) : 0), Target = zDOID, ExpectedPosition = stationZdo.GetVec3(text2 + ".position", Vector3.zero), OwnerId = stationZdo.GetLong(text2 + ".owner", 0L), StationOwnerId = stationZdo.GetLong(text2 + ".station-owner", 0L), TargetOwnerId = stationZdo.GetLong(text2 + ".target-owner", 0L), Revision = Math.Max(1, stationZdo.GetInt(text2 + ".revision", 1)) }; } internal static bool TryCaptureCanonicalPublication(ZDO stationZdo, ProductionLinkRole role, out string publication, out StoredProductionLink link, out string failure) { publication = string.Empty; link = null; failure = "production-link-record-unavailable"; if (stationZdo == null || !stationZdo.IsValid() || !Enum.IsDefined(typeof(ProductionLinkRole), role)) { return false; } string text = stationZdo.GetString(RoleRecordKey(role), string.Empty); if (string.Equals(text, "!", StringComparison.Ordinal)) { publication = "!"; failure = "ok"; return true; } if (!string.IsNullOrEmpty(text)) { if (!TryParseAtomicRecord(text, role, out link)) { failure = "production-link-record-corrupt"; return false; } publication = text; failure = "ok"; return true; } StoredProductionLink storedProductionLink = Load(stationZdo, role); if (storedProductionLink == null) { publication = "!"; failure = "ok"; return true; } try { publication = SerializeAtomicRecord(storedProductionLink); link = storedProductionLink; failure = "ok"; return true; } catch { publication = string.Empty; link = null; failure = "production-link-legacy-identity-incomplete"; return false; } } internal static bool TryPublishCanonicalPublication(ZDO stationZdo, ProductionLinkRole role, string expectedPublication, string desiredPublication, out string failure) { failure = "production-link-publication-invalid"; if (stationZdo == null || !stationZdo.IsValid() || !IsCanonicalPublication(role, expectedPublication) || !IsCanonicalPublication(role, desiredPublication)) { return false; } if (!TryCaptureCanonicalPublication(stationZdo, role, out var publication, out var link, out failure) || !string.Equals(publication, expectedPublication, StringComparison.Ordinal)) { failure = "production-link-publication-before-changed"; return false; } try { PublishAtomicRecord(stationZdo, role, desiredPublication); if (!TryCaptureCanonicalPublication(stationZdo, role, out var publication2, out link, out failure) || !string.Equals(publication2, desiredPublication, StringComparison.Ordinal)) { failure = "production-link-publication-readback"; return false; } ClearLegacyFields(stationZdo, role); if (!TryCaptureCanonicalPublication(stationZdo, role, out publication2, out link, out failure) || !string.Equals(publication2, desiredPublication, StringComparison.Ordinal)) { failure = "production-link-publication-cleanup-changed"; return false; } failure = "ok"; return true; } catch { failure = "production-link-publication-fault"; return false; } } internal static string CreateCanonicalPublication(StoredProductionLink link) { if (link != null) { return SerializeAtomicRecord(link); } return "!"; } internal static bool IsCanonicalPublication(ProductionLinkRole role, string publication) { if (string.Equals(publication, "!", StringComparison.Ordinal)) { return true; } StoredProductionLink link; return TryParseAtomicRecord(publication, role, out link); } internal static StoredProductionLink Save(ZDO stationZdo, ProductionLinkRole role, ZDOID target, Vector3 targetPosition, long actorId, long stationOwnerId, long targetOwnerId) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) if (stationZdo == null) { throw new ArgumentNullException("stationZdo"); } if (((ZDOID)(ref target)).IsNone()) { throw new ArgumentException("A target ZDO is required.", "target"); } StoredProductionLink previous = Load(stationZdo, role); StoredProductionLink storedProductionLink = CreateDetached(role, target, targetPosition, actorId, stationOwnerId, targetOwnerId, previous); string encoded = SerializeAtomicRecord(storedProductionLink); stationZdo.Set(SchemaKey, 2); if (string.IsNullOrEmpty(stationZdo.GetString(StationLinkIdKey, string.Empty))) { stationZdo.Set(StationLinkIdKey, Guid.NewGuid().ToString("N")); } stationZdo.Set(StationOwnerKey, actorId); PublishAtomicRecord(stationZdo, role, encoded); if (!SameCompleteRecord(Load(stationZdo, role), storedProductionLink)) { throw new InvalidOperationException("The atomic production link did not round-trip exactly."); } ClearLegacyFields(stationZdo, role); StoredProductionLink storedProductionLink2 = Load(stationZdo, role); if (!SameCompleteRecord(storedProductionLink2, storedProductionLink)) { throw new InvalidOperationException("The atomic production link changed during cleanup."); } return storedProductionLink2; } internal static StoredProductionLink CreateDetached(ProductionLinkRole role, ZDOID target, Vector3 targetPosition, long actorId, long stationOwnerId, long targetOwnerId, StoredProductionLink previous = null) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) if (((ZDOID)(ref target)).IsNone()) { throw new ArgumentException("A target ZDO is required.", "target"); } if (role != ProductionLinkRole.Input && role != ProductionLinkRole.Fuel && role != ProductionLinkRole.Output && role != ProductionLinkRole.Replenishment) { throw new ArgumentOutOfRangeException("role"); } if (!ProductionEndpointIdentity.TryCaptureCurrentTarget(target, out var targetToken, out var targetPrefabHash, out var rootPosition, out var failure)) { throw new InvalidOperationException("The selected container has no persistent world identity: " + failure); } int revision = ((previous == null) ? 1 : checked(previous.Revision + 1)); string text = previous?.LinkId; if (string.Equals(text, "invalid-atomic-record", StringComparison.Ordinal)) { text = string.Empty; } if (string.IsNullOrEmpty(text)) { text = Guid.NewGuid().ToString("N"); } return new StoredProductionLink { LinkId = text, Role = role, TargetToken = targetToken, TargetPrefabHash = targetPrefabHash, Target = target, ExpectedPosition = rootPosition, OwnerId = actorId, StationOwnerId = stationOwnerId, TargetOwnerId = targetOwnerId, Revision = revision }; } internal static bool TryRefreshResolvedTargetCache(ZDO stationZdo, StoredProductionLink expected, ZDO targetZdo, out StoredProductionLink refreshed) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) refreshed = null; if (stationZdo == null || expected == null || targetZdo == null || ((ZDOID)(ref targetZdo.m_uid)).IsNone() || !ProductionEndpointIdentity.IsCanonicalToken(expected.TargetToken)) { return false; } StoredProductionLink storedProductionLink = Load(stationZdo, expected.Role); if (!SameStableIdentity(storedProductionLink, expected) || storedProductionLink.TargetPrefabHash == 0 || storedProductionLink.TargetPrefabHash != targetZdo.GetPrefab()) { return false; } if (storedProductionLink.Target != targetZdo.m_uid) { StoredProductionLink storedProductionLink2 = Copy(storedProductionLink); storedProductionLink2.Target = targetZdo.m_uid; PublishAtomicRecord(stationZdo, expected.Role, SerializeAtomicRecord(storedProductionLink2)); ClearLegacyFields(stationZdo, expected.Role); } refreshed = Load(stationZdo, expected.Role); if (SameStableIdentity(refreshed, expected)) { return refreshed.Target == targetZdo.m_uid; } return false; } internal static void Clear(ZDO stationZdo, ProductionLinkRole role) { if (stationZdo != null) { PublishAtomicRecord(stationZdo, role, "!"); ClearLegacyFields(stationZdo, role); } } internal static bool TryUpgradeResolvedTarget(ZDO stationZdo, StoredProductionLink expected, ZDO targetZdo, string targetToken, int targetPrefabHash, out StoredProductionLink upgraded) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) upgraded = null; if (stationZdo == null || expected == null || targetZdo == null || ((ZDOID)(ref targetZdo.m_uid)).IsNone() || targetPrefabHash == 0 || !ProductionEndpointIdentity.IsCanonicalToken(targetToken)) { return false; } StoredProductionLink storedProductionLink = Load(stationZdo, expected.Role); if (!SameStoredIdentity(storedProductionLink, expected) || !string.IsNullOrEmpty(storedProductionLink.TargetToken)) { return false; } StoredProductionLink storedProductionLink2 = Copy(storedProductionLink); storedProductionLink2.TargetToken = targetToken; storedProductionLink2.TargetPrefabHash = targetPrefabHash; storedProductionLink2.Target = targetZdo.m_uid; string encoded = SerializeAtomicRecord(storedProductionLink2); stationZdo.Set(SchemaKey, 2); PublishAtomicRecord(stationZdo, expected.Role, encoded); upgraded = Load(stationZdo, expected.Role); if (!SameCompleteRecord(upgraded, storedProductionLink2)) { return false; } ClearLegacyFields(stationZdo, expected.Role); upgraded = Load(stationZdo, expected.Role); return SameCompleteRecord(upgraded, storedProductionLink2); } internal static string StationLinkId(ZDO zdo) { if (zdo == null) { return string.Empty; } string text = zdo.GetString(StationLinkIdKey, string.Empty); if (!string.IsNullOrEmpty(text)) { return text; } text = Guid.NewGuid().ToString("N"); zdo.Set(StationLinkIdKey, text); zdo.Set(SchemaKey, 2); return text; } private static string RolePrefix(ProductionLinkRole role) { return role switch { ProductionLinkRole.Input => Key("input"), ProductionLinkRole.Fuel => Key("fuel"), ProductionLinkRole.Output => Key("output"), ProductionLinkRole.Replenishment => Key("replenishment"), _ => throw new ArgumentOutOfRangeException("role"), }; } internal static string RoleRecordKey(ProductionLinkRole role) { return RolePrefix(role) + ".record"; } private static void PublishAtomicRecord(ZDO stationZdo, ProductionLinkRole role, string encoded) { stationZdo.Set(RoleRecordKey(role), encoded); } private static void ClearLegacyFields(ZDO stationZdo, ProductionLinkRole role) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) string text = RolePrefix(role); stationZdo.Set(text + ".id", string.Empty); stationZdo.Set(text + ".target-token", string.Empty); stationZdo.RemoveInt(text + ".target-prefab"); stationZdo.Set(text + ".target", ZDOID.None); stationZdo.RemoveVec3(text + ".position"); stationZdo.Set(text + ".owner", 0L); stationZdo.Set(text + ".station-owner", 0L); stationZdo.Set(text + ".target-owner", 0L); stationZdo.RemoveInt(text + ".revision"); } internal static string SerializeAtomicRecord(StoredProductionLink link) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002b: 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_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) if (!IsValidAtomicRecord(link)) { throw new InvalidOperationException("A complete stable production link is required for atomic publication."); } ZPackage val = new ZPackage(); val.Write(1); val.Write((int)link.Role); val.Write(link.LinkId); val.Write(link.TargetToken); val.Write(link.TargetPrefabHash); val.Write(link.Target); val.Write(link.ExpectedPosition); val.Write(link.OwnerId); val.Write(link.StationOwnerId); val.Write(link.TargetOwnerId); val.Write(link.Revision); byte[] array = val.GetArray(); if (array.Length == 0 || array.Length > 1536) { throw new InvalidOperationException("The production link record exceeds its bound."); } ZPackage val2 = new ZPackage(); val2.Write(array); val2.Write(ComputeDigest(array)); string text = Convert.ToBase64String(val2.GetArray()); if (text.Length > 2048) { throw new InvalidOperationException("The production link record exceeds its encoded bound."); } return text; } internal static bool TryParseAtomicRecord(string encoded, ProductionLinkRole expectedRole, out StoredProductionLink link) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Expected O, but got Unknown //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_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) link = null; if (string.IsNullOrEmpty(encoded) || string.Equals(encoded, "!", StringComparison.Ordinal) || encoded.Length > 2048) { return false; } try { byte[] array = Convert.FromBase64String(encoded); if (array.Length == 0 || array.Length > 1536) { return false; } ZPackage val = new ZPackage(array); byte[] array2 = val.ReadByteArray(); byte[] left = val.ReadByteArray(); if (val.GetPos() != val.Size() || array2 == null || array2.Length == 0 || array2.Length > 1536 || !DigestEquals(left, ComputeDigest(array2))) { return false; } ZPackage val2 = new ZPackage(array2); if (val2.ReadInt() != 1) { return false; } StoredProductionLink storedProductionLink = new StoredProductionLink { Role = (ProductionLinkRole)val2.ReadInt(), LinkId = val2.ReadString(), TargetToken = val2.ReadString(), TargetPrefabHash = val2.ReadInt(), Target = val2.ReadZDOID(), ExpectedPosition = val2.ReadVector3(), OwnerId = val2.ReadLong(), StationOwnerId = val2.ReadLong(), TargetOwnerId = val2.ReadLong(), Revision = val2.ReadInt() }; if (val2.GetPos() != val2.Size() || storedProductionLink.Role != expectedRole || !IsValidAtomicRecord(storedProductionLink) || !string.Equals(SerializeAtomicRecord(storedProductionLink), encoded, StringComparison.Ordinal)) { return false; } link = storedProductionLink; return true; } catch { link = null; return false; } } private static bool IsValidAtomicRecord(StoredProductionLink link) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) if (link != null && (link.Role == ProductionLinkRole.Input || link.Role == ProductionLinkRole.Fuel || link.Role == ProductionLinkRole.Output || link.Role == ProductionLinkRole.Replenishment) && !string.IsNullOrEmpty(link.LinkId) && link.LinkId.Length <= 128 && ProductionEndpointIdentity.IsCanonicalToken(link.TargetToken) && link.TargetPrefabHash != 0 && !((ZDOID)(ref link.Target)).IsNone() && ValheimAccess.IsFinite(link.ExpectedPosition)) { return link.Revision >= 1; } return false; } private static StoredProductionLink InvalidAtomicRecord(ProductionLinkRole role) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) return new StoredProductionLink { LinkId = "invalid-atomic-record", Role = role, TargetToken = "invalid", TargetPrefabHash = 0, Target = ZDOID.None, ExpectedPosition = Vector3.zero, Revision = 1 }; } private static StoredProductionLink Copy(StoredProductionLink link) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) if (link != null) { return new StoredProductionLink { LinkId = link.LinkId, Role = link.Role, TargetToken = link.TargetToken, TargetPrefabHash = link.TargetPrefabHash, Target = link.Target, ExpectedPosition = link.ExpectedPosition, OwnerId = link.OwnerId, StationOwnerId = link.StationOwnerId, TargetOwnerId = link.TargetOwnerId, Revision = link.Revision }; } return null; } private static byte[] ComputeDigest(byte[] bytes) { using SHA256 sHA = SHA256.Create(); return sHA.ComputeHash(bytes); } private static bool DigestEquals(byte[] left, byte[] right) { if (left == null || right == null || left.Length != right.Length) { return false; } int num = 0; for (int i = 0; i < left.Length; i++) { num |= left[i] ^ right[i]; } return num == 0; } private static bool SameStoredIdentity(StoredProductionLink left, StoredProductionLink right) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) if (left != null && right != null && left.Role == right.Role && left.Revision == right.Revision && left.Target == right.Target) { return string.Equals(left.LinkId, right.LinkId, StringComparison.Ordinal); } return false; } private static bool SameStableIdentity(StoredProductionLink left, StoredProductionLink right) { if (left != null && right != null && left.Role == right.Role && left.Revision == right.Revision && left.TargetPrefabHash == right.TargetPrefabHash && ProductionEndpointIdentity.IsCanonicalToken(left.TargetToken) && string.Equals(left.TargetToken, right.TargetToken, StringComparison.Ordinal)) { return string.Equals(left.LinkId, right.LinkId, StringComparison.Ordinal); } return false; } private static bool SameCompleteRecord(StoredProductionLink left, StoredProductionLink right) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) if (SameStableIdentity(left, right) && left.Target == right.Target && left.ExpectedPosition == right.ExpectedPosition && left.OwnerId == right.OwnerId && left.StationOwnerId == right.StationOwnerId) { return left.TargetOwnerId == right.TargetOwnerId; } return false; } private static string Key(string localName) { return "runic.production." + localName; } } internal static class ProductionRoleLinkCatalogStore { private const int SchemaVersion = 1; private const int MaximumEncodedCharacters = 49152; private const int MaximumPayloadBytes = 32768; internal static StoredRecordState Read(ZDO stationZdo, ProductionLinkRole role, out ProductionRoleLinkCatalog catalog) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Expected O, but got Unknown catalog = null; if (stationZdo == null || !Supported(role)) { return StoredRecordState.Invalid; } string text = stationZdo.GetString(Key(role), string.Empty); if (string.IsNullOrEmpty(text)) { return StoredRecordState.Absent; } if (text.Length > 49152) { return StoredRecordState.Invalid; } try { byte[] array = Convert.FromBase64String(text); if (array.Length == 0 || array.Length > 32768) { return StoredRecordState.Invalid; } ZPackage val = new ZPackage(array); byte[] array2 = val.ReadByteArray(); byte[] left = val.ReadByteArray(); if (val.GetPos() != val.Size() || array2 == null || array2.Length == 0 || array2.Length > 32768 || !DigestEquals(left, Digest(array2))) { return StoredRecordState.Invalid; } ZPackage val2 = new ZPackage(array2); if (val2.ReadInt() != 1 || val2.ReadInt() != (int)role) { return StoredRecordState.Invalid; } int num = val2.ReadInt(); int num2 = val2.ReadInt(); if (num < 1 || num2 < 0 || num2 > 16) { return StoredRecordState.Invalid; } List list = new List(num2); for (int i = 0; i < num2; i++) { if (!ProductionLinkStore.TryParseAtomicRecord(val2.ReadString(), role, out var link)) { return StoredRecordState.Invalid; } list.Add(link); } if (val2.GetPos() != val2.Size()) { return StoredRecordState.Invalid; } catalog = new ProductionRoleLinkCatalog(role, num, list); return string.Equals(Serialize(catalog), text, StringComparison.Ordinal) ? StoredRecordState.Valid : StoredRecordState.Invalid; } catch { catalog = null; return StoredRecordState.Invalid; } } internal static StoredRecordState ReadWithLegacy(ZDO stationZdo, ProductionLinkRole role, out ProductionRoleLinkCatalog catalog) { StoredRecordState storedRecordState = Read(stationZdo, role, out catalog); if (storedRecordState != StoredRecordState.Absent) { return storedRecordState; } StoredProductionLink storedProductionLink = ProductionLinkStore.Load(stationZdo, role); try { catalog = ((storedProductionLink == null) ? ProductionRoleLinkCatalogPolicy.Empty(role) : new ProductionRoleLinkCatalog(role, 1, new StoredProductionLink[1] { storedProductionLink })); return StoredRecordState.Valid; } catch { catalog = null; return StoredRecordState.Invalid; } } internal static bool Publish(ZDO stationZdo, ProductionRoleLinkCatalog expected, ProductionRoleLinkCatalog catalog) { if (stationZdo == null || expected == null || catalog == null || expected.Role != catalog.Role) { return false; } if (ReadWithLegacy(stationZdo, catalog.Role, out var catalog2) != StoredRecordState.Valid || !string.Equals(Serialize(catalog2), Serialize(expected), StringComparison.Ordinal)) { return false; } string text = Serialize(expected); string text2 = Serialize(catalog); stationZdo.Set(Key(catalog.Role), text2); if (Read(stationZdo, catalog.Role, out var catalog3) != StoredRecordState.Valid || !string.Equals(Serialize(catalog3), text2, StringComparison.Ordinal)) { stationZdo.Set(Key(catalog.Role), text); if (Read(stationZdo, catalog.Role, out catalog3) != StoredRecordState.Valid || !string.Equals(Serialize(catalog3), text, StringComparison.Ordinal)) { ProductionDiagnostics.Warning("Role-link catalog rollback could not be proven exactly; the operation was not reported as successful."); } return false; } ProductionLinkStore.Clear(stationZdo, catalog.Role); if (Read(stationZdo, catalog.Role, out catalog3) == StoredRecordState.Valid && string.Equals(Serialize(catalog3), text2, StringComparison.Ordinal)) { return true; } stationZdo.Set(Key(catalog.Role), text); if (Read(stationZdo, catalog.Role, out catalog3) != StoredRecordState.Valid || !string.Equals(Serialize(catalog3), text, StringComparison.Ordinal)) { ProductionDiagnostics.Warning("Role-link catalog rollback could not be proven exactly; the operation was not reported as successful."); } return false; } internal static string Serialize(ProductionRoleLinkCatalog catalog) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) if (catalog == null) { throw new ArgumentNullException("catalog"); } ZPackage val = new ZPackage(); val.Write(1); val.Write((int)catalog.Role); val.Write(catalog.Revision); val.Write(catalog.Links.Count); foreach (StoredProductionLink link in catalog.Links) { val.Write(ProductionLinkStore.CreateCanonicalPublication(link)); } byte[] array = val.GetArray(); if (array.Length == 0 || array.Length > 32768) { throw new InvalidOperationException("The role-link publication exceeds its bound."); } ZPackage val2 = new ZPackage(); val2.Write(array); val2.Write(Digest(array)); string text = Convert.ToBase64String(val2.GetArray()); if (text.Length > 49152) { throw new InvalidOperationException("The role-link publication exceeds its encoded bound."); } return text; } internal static string StorageKey(ProductionLinkRole role) { return Key(role); } private static bool Supported(ProductionLinkRole role) { if (role != ProductionLinkRole.Input && role != ProductionLinkRole.Fuel && role != ProductionLinkRole.Output) { return role == ProductionLinkRole.Replenishment; } return true; } private static string Key(ProductionLinkRole role) { return "runic.production." + role.ToString().ToLowerInvariant() + ".links.record"; } private static byte[] Digest(byte[] bytes) { using SHA256 sHA = SHA256.Create(); return sHA.ComputeHash(bytes); } private static bool DigestEquals(byte[] left, byte[] right) { if (left == null || right == null || left.Length != right.Length) { return false; } int num = 0; for (int i = 0; i < left.Length; i++) { num |= left[i] ^ right[i]; } return num == 0; } } internal enum ProductionStationKind { Smelter = 1, Cooking, Recipe, Fermenter, Fireplace } internal sealed class ProductionStationEntry { internal ProductionStationKind Kind { get; } internal Component Component { get; } internal ProductionStationEntry(ProductionStationKind kind, Component component) { Kind = kind; Component = component ?? throw new ArgumentNullException("component"); } } internal sealed class ProductionLinkSelection { internal long PlayerId; internal ProductionStationEntry Station; internal ProductionLinkRole Role; internal ProductionLinkMouseButton Button; internal bool Remove; internal float ExpiresAt; } internal sealed class ProductionEndpoint { internal StoredProductionLink Link; internal Container Container; internal ZDO Zdo; internal Inventory Inventory; } internal sealed class ProductionDestination { internal ProductionEndpoint Endpoint; internal MultiReplenishmentCatalog Catalog; internal ReplenishmentDestinationRecord Record; internal ReplenishmentTargetAuthorization Target; internal int TargetIndex; internal bool IsReplenishment => Catalog != null; } internal enum ProductionEndpointMutationState { Before = 1, After, Indeterminate } internal sealed class ProductionMutationIndeterminateException : InvalidOperationException { internal ProductionMutationIndeterminateException(string message) : base(message) { } } internal static class ProductionRuntime { private const int MaximumLoadedStations = 4096; private const int MaximumSelections = 16; private const float SelectionLifetimeSeconds = 30f; private const float ContextHighlightSeconds = 0.35f; private const float SuccessfulLinkHighlightSeconds = 12f; private static readonly Dictionary Stations = new Dictionary(); private static readonly Dictionary Selections = new Dictionary(); private static readonly HashSet SafetyPausedStations = new HashSet(); private static CookingStationPrefabPolicy _cookingPolicy; private static StockStationPrefabPolicy _recipePolicy; private static StockStationPrefabPolicy _cookingInputPolicy; private static StockStationPrefabPolicy _fermenterInputPolicy; private static FermenterPrefabPolicy _fermenterPolicy; private static IngredientReservePolicy _ingredientReserves; private static IngredientReservePolicy _fuelReserves; private static ReplenishmentTargetReservePolicy _replenishmentReserves; private static bool _initialized; private static bool _highlightFailureReported; private static int _highlightRefreshFrame; private static int _highlightRefreshStationId; private static float _nextQuantum; private static int _stationCursor; private static bool ShiftHeld { get { if (!ValheimAccess.KeyboardKeyHeld((KeyCode)304) && !ValheimAccess.KeyboardKeyHeld((KeyCode)303)) { return ValheimAccess.RawControllerButtonHeld("JoyNextSnap"); } return true; } } private static float LinkRange => Mathf.Clamp(ProductionConfig.MaximumLinkRange?.Value ?? 12f, 2f, 30f); internal static void Initialize() { Stations.Clear(); Selections.Clear(); SafetyPausedStations.Clear(); ProductionLinkInput.Reset(); ProductionLinkHighlight.ClearAll(); _highlightFailureReported = false; _highlightRefreshFrame = -1; _highlightRefreshStationId = 0; _stationCursor = 0; _nextQuantum = 0f; RefreshConfiguration(); _initialized = true; } internal static void Shutdown() { _initialized = false; Stations.Clear(); Selections.Clear(); SafetyPausedStations.Clear(); ProductionLinkInput.Reset(); ProductionLinkHighlight.ClearAll(); _highlightFailureReported = false; _highlightRefreshFrame = -1; _highlightRefreshStationId = 0; _stationCursor = 0; _nextQuantum = 0f; } internal static void RefreshConfiguration() { if (!CookingStationPrefabPolicy.TryParse(ProductionConfig.CookingStationPrefabAllowList?.Value ?? string.Empty, ProductionConfig.CookingStationPrefabDenyList?.Value ?? string.Empty, out var policy, out var failure)) { throw new InvalidOperationException(failure); } if (!StockStationPrefabPolicy.TryParse(ProductionConfig.RecipeStationPrefabAllowList?.Value ?? string.Empty, ProductionConfig.RecipeStationPrefabDenyList?.Value ?? string.Empty, out var policy2, out var failure2)) { throw new InvalidOperationException(failure2); } if (!StockStationPrefabPolicy.TryParse(ProductionConfig.CookingStationPrefabAllowList?.Value ?? string.Empty, ProductionConfig.CookingStationPrefabDenyList?.Value ?? string.Empty, out var policy3, out var failure3)) { throw new InvalidOperationException(failure3); } if (!StockStationPrefabPolicy.TryParse(ProductionConfig.FermenterPrefabAllowList?.Value ?? string.Empty, ProductionConfig.FermenterPrefabDenyList?.Value ?? string.Empty, out var policy4, out var failure4)) { throw new InvalidOperationException(failure4); } if (!FermenterPrefabPolicy.TryParse(ProductionConfig.FermenterPrefabAllowList?.Value ?? string.Empty, ProductionConfig.FermenterPrefabDenyList?.Value ?? string.Empty, out var policy5, out var failure5)) { throw new InvalidOperationException(failure5); } if (!IngredientReservePolicy.TryParse(ProductionConfig.DefaultIngredientReserve?.Value ?? 0, ProductionConfig.IngredientReserveRules?.Value ?? string.Empty, out var policy6, out var failure6)) { throw new InvalidOperationException(failure6); } if (!IngredientReservePolicy.TryParse(ProductionConfig.FuelReserve?.Value ?? 0, string.Empty, out var policy7, out var failure7)) { throw new InvalidOperationException(failure7); } if (!ReplenishmentTargetReservePolicy.TryParse(ProductionConfig.DefaultReplenishmentReserve?.Value ?? 10, ProductionConfig.ReplenishmentReserveRules?.Value ?? string.Empty, out var policy8, out var failure8)) { throw new InvalidOperationException(failure8); } _cookingPolicy = policy; _recipePolicy = policy2; _cookingInputPolicy = policy3; _fermenterInputPolicy = policy4; _fermenterPolicy = policy5; _ingredientReserves = policy6; _fuelReserves = policy7; _replenishmentReserves = policy8; _nextQuantum = 0f; } internal static void Register(Smelter station) { Register(ProductionStationKind.Smelter, (Component)(object)station); } internal static void Register(CookingStation station) { Register(ProductionStationKind.Cooking, (Component)(object)station); } internal static void Register(CraftingStation station) { Register(ProductionStationKind.Recipe, (Component)(object)station); } internal static void Register(Fermenter station) { Register(ProductionStationKind.Fermenter, (Component)(object)station); } internal static void Register(Fireplace station) { Register(ProductionStationKind.Fireplace, (Component)(object)station); } internal static void SeedLoadedStations() { Smelter[] array = Object.FindObjectsByType((FindObjectsSortMode)0); for (int i = 0; i < array.Length; i++) { Register(array[i]); } CookingStation[] array2 = Object.FindObjectsByType((FindObjectsSortMode)0); for (int i = 0; i < array2.Length; i++) { Register(array2[i]); } CraftingStation[] array3 = Object.FindObjectsByType((FindObjectsSortMode)0); for (int i = 0; i < array3.Length; i++) { Register(array3[i]); } Fermenter[] array4 = Object.FindObjectsByType((FindObjectsSortMode)0); for (int i = 0; i < array4.Length; i++) { Register(array4[i]); } Fireplace[] array5 = Object.FindObjectsByType((FindObjectsSortMode)0); for (int i = 0; i < array5.Length; i++) { Register(array5[i]); } } private static void Register(ProductionStationKind kind, Component component) { if (_initialized && !((Object)(object)component == (Object)null)) { int instanceID = ((Object)component).GetInstanceID(); if (!Stations.ContainsKey(instanceID) && Stations.Count >= 4096) { ProductionDiagnostics.Warning("Production station registration reached its 4096-object bound."); } else { Stations[instanceID] = new ProductionStationEntry(kind, component); } } } internal static void Update() { if (!_initialized) { return; } PruneSelections(); ConfigEntry enabled = ProductionConfig.Enabled; if (enabled == null || !enabled.Value) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup < _nextQuantum) { return; } float num = Mathf.Clamp(ProductionConfig.StockSchedulerIntervalSeconds?.Value ?? 2f, 0.5f, 10f); _nextQuantum = realtimeSinceStartup + num; int num2 = Mathf.Clamp(ProductionConfig.StockSchedulerMaximumOperations?.Value ?? 64, 1, 256); int[] array = Stations.Keys.ToArray(); if (array.Length == 0) { return; } Array.Sort(array); int num3 = Math.Abs(_stationCursor) % array.Length; int num4 = 0; for (int i = 0; i < array.Length; i++) { if (num4 >= num2) { break; } int key = array[(num3 + i) % array.Length]; if (!Stations.TryGetValue(key, out var value)) { continue; } if ((Object)(object)value.Component == (Object)null) { Stations.Remove(key); continue; } try { if (RunOne(value)) { num4++; } } catch (Exception exception) { FailStation(value.Component, "scheduler", exception); } } _stationCursor = (num3 + 1) % Math.Max(1, array.Length); } internal static void TickLocalPlayer(Player player) { ProductionLinkInput.BeginSample(Time.frameCount); if (!_initialized) { return; } ConfigEntry enabled = ProductionConfig.Enabled; if (enabled == null || !enabled.Value || (Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer || !((Character)player).IsOwner()) { return; } GameObject hoverObject = ((Humanoid)player).GetHoverObject(); bool num = ValheimAccess.PlayerTakesInput(player); ProductionLinkMouseButton button = ProductionLinkMouseButton.Left; bool remove = false; bool flag = num && ProductionLinkInput.TryReadExactGesture(Time.frameCount, out button, out remove); Container val = (((Object)(object)hoverObject == (Object)null) ? null : hoverObject.GetComponentInParent()); long playerID = player.GetPlayerID(); if (flag && (Object)(object)val != (Object)null && playerID != 0L && Selections.TryGetValue(playerID, out var value)) { if (value.Button != button || value.Remove != remove) { return; } ProductionLinkInput.Consume(Time.frameCount, button); Selections.Remove(playerID); if (CommitSelection(player, value, val, value.Remove, out var detail)) { if (value.Remove) { ProductionLinkHighlight.Hide(val); } else { ProductionLinkHighlight.Show(val, value.Role, 12f); } } ValheimAccess.Message(player, detail); return; } ProductionStationEntry station = null; if (!((Object)(object)hoverObject != (Object)null) || !TryClassifyStation(hoverObject, out station)) { return; } int instanceID = ((Object)station.Component).GetInstanceID(); if (_highlightRefreshFrame != Time.frameCount || _highlightRefreshStationId != instanceID) { _highlightRefreshFrame = Time.frameCount; _highlightRefreshStationId = instanceID; try { RefreshLinkedHighlights(station, 0.35f); } catch (Exception ex) { if (!_highlightFailureReported) { _highlightFailureReported = true; ProductionDiagnostics.Warning("Linked-chest ring rendering failed safely: " + ex.GetType().Name + "."); } } } if (flag) { ProductionLinkInput.Consume(Time.frameCount, button); TryBeginLinkSelection(player, station, hoverObject, button, remove, out var detail2); ValheimAccess.Message(player, detail2); } } private static bool RunOne(ProductionStationEntry entry) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Expected O, but got Unknown //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Expected O, but got Unknown //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Expected O, but got Unknown //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Expected O, but got Unknown //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Expected O, but got Unknown if ((Object)(object)entry?.Component == (Object)null || !ValheimAccess.IsNativeOwner(entry.Component)) { return false; } if (SafetyPausedStations.Contains(((Object)entry.Component).GetInstanceID())) { return false; } TryActivatePendingReplenishment(entry); return entry.Kind switch { ProductionStationKind.Smelter => RunSmelter((Smelter)entry.Component), ProductionStationKind.Cooking => RunCooking((CookingStation)entry.Component), ProductionStationKind.Recipe => RunRecipe((CraftingStation)entry.Component), ProductionStationKind.Fermenter => RunFermenter((Fermenter)entry.Component), ProductionStationKind.Fireplace => RunFireplace((Fireplace)entry.Component), _ => false, }; } private static void TryActivatePendingReplenishment(ProductionStationEntry station) { if ((Object)(object)station?.Component == (Object)null || !StationSupportsRole(station.Kind, ProductionLinkRole.Replenishment)) { return; } ZDO stationZdo = ValheimAccess.Zdo(station.Component); Player localPlayer = Player.m_localPlayer; string stationId = ValheimAccess.StableId(station.Component); if (MultiReplenishmentRuntimeSupport.ReadOrMigrate(stationZdo, stationId, initializeEmpty: false, out var catalog, out var failure) == StoredRecordState.Invalid || !TryReadOrMigrateReplenishmentRoleLinks(station.Component, catalog, localPlayer, out var links)) { return; } foreach (StoredProductionLink link in links.Links) { ReplenishmentDestinationRecord replenishmentDestinationRecord = catalog?.Destinations.FirstOrDefault((ReplenishmentDestinationRecord value) => SameTarget(value.Link, link.TargetToken, link.TargetPrefabHash)); int num; long num2; if (replenishmentDestinationRecord?.Plan != null) { num = ((replenishmentDestinationRecord.Plan.AuthorizedPlayerId == link.OwnerId) ? 1 : 0); if (num != 0) { num2 = replenishmentDestinationRecord.Plan.AuthorizedPlayerId; goto IL_00f5; } } else { num = 0; } num2 = ((localPlayer != null) ? localPlayer.GetPlayerID() : 0); goto IL_00f5; IL_00f5: long num3 = num2; string text = ((num != 0) ? replenishmentDestinationRecord.Plan.AuthorizedPlayerName : (((localPlayer != null) ? localPlayer.GetPlayerName() : null) ?? string.Empty)); if (num3 != 0L && num3 == link.OwnerId && !string.IsNullOrWhiteSpace(text) && TryResolveEndpoint(station.Component, link, singleton: true, out var endpoint, out failure)) { StoredProductionLink storedProductionLink = link; ReplenishmentPlan previous = ((replenishmentDestinationRecord != null && replenishmentDestinationRecord.Link.Revision == storedProductionLink.Revision) ? replenishmentDestinationRecord.Plan : null); if (TryBuildPlan(station, stationId, storedProductionLink, endpoint.Inventory, num3, text, previous, out var plan, out failure) && (replenishmentDestinationRecord == null || replenishmentDestinationRecord.Link.Revision != storedProductionLink.Revision || !SameTargetSet(replenishmentDestinationRecord.Plan, plan))) { TryActivateReplenishmentDestination(station, stationZdo, endpoint.Container, endpoint.Inventory, storedProductionLink, num3, text, out failure); break; } } } } private static bool TryReadOrMigrateReplenishmentRoleLinks(Component station, MultiReplenishmentCatalog plans, Player actor, out ProductionRoleLinkCatalog links) { links = null; ZDO stationZdo = ValheimAccess.Zdo(station); switch (ProductionRoleLinkCatalogStore.Read(stationZdo, ProductionLinkRole.Replenishment, out links)) { case StoredRecordState.Valid: return links != null; case StoredRecordState.Invalid: return false; default: { long num = ((actor != null) ? actor.GetPlayerID() : 0); List list = new List(); StoredProductionLink storedProductionLink = ProductionLinkStore.Load(stationZdo, ProductionLinkRole.Replenishment); if (storedProductionLink != null) { list.Add(storedProductionLink); } if (plans != null) { foreach (ReplenishmentDestinationRecord record in plans.Destinations) { if (record?.Link != null && !list.Any((StoredProductionLink value) => SameTarget(value, record.Link.TargetToken, record.Link.TargetPrefabHash))) { list.Add(record.Link); } } } links = ProductionRoleLinkCatalogPolicy.Empty(ProductionLinkRole.Replenishment); ProductionRoleLinkCatalog expected = links; int softMaximum = 16; foreach (StoredProductionLink candidate in list) { long num2 = (plans?.Destinations.FirstOrDefault((ReplenishmentDestinationRecord value) => candidate != null && SameTarget(value.Link, candidate.TargetToken, candidate.TargetPrefabHash)))?.Plan?.AuthorizedPlayerId ?? num; if (candidate != null && candidate.OwnerId == num2 && TryResolveEndpoint(station, candidate, singleton: true, out var endpoint, out var failure) && ProductionRoleLinkCatalogPolicy.TryAddOrRefresh(links, endpoint.Link, softMaximum, out var updated, out var _, out failure)) { links = updated; } } if (links.Links.Count == 0) { return true; } return ProductionRoleLinkCatalogStore.Publish(stationZdo, expected, links); } } } private static bool SameTargetSet(ReplenishmentPlan left, ReplenishmentPlan right) { if (left == null || right == null || left.Targets.Count != right.Targets.Count || left.AdapterKind != right.AdapterKind || left.AuthorizedPlayerId != right.AuthorizedPlayerId || !string.Equals(left.AuthorizedPlayerName, right.AuthorizedPlayerName, StringComparison.Ordinal) || !string.Equals(left.StationPrefabId, right.StationPrefabId, StringComparison.Ordinal)) { return false; } for (int i = 0; i < left.Targets.Count; i++) { ReplenishmentTargetAuthorization replenishmentTargetAuthorization = left.Targets[i]; ReplenishmentTargetAuthorization replenishmentTargetAuthorization2 = right.Targets[i]; if (!string.Equals(replenishmentTargetAuthorization.OutputPrefabId, replenishmentTargetAuthorization2.OutputPrefabId, StringComparison.Ordinal) || replenishmentTargetAuthorization.ProducerKind != replenishmentTargetAuthorization2.ProducerKind || !string.Equals(replenishmentTargetAuthorization.ProducerId, replenishmentTargetAuthorization2.ProducerId, StringComparison.Ordinal) || !replenishmentTargetAuthorization.ProducerSignatureMatches(replenishmentTargetAuthorization2.CopyProducerSignature()) || replenishmentTargetAuthorization.OutputAmount != replenishmentTargetAuthorization2.OutputAmount || !string.Equals(replenishmentTargetAuthorization.RequiredStationName, replenishmentTargetAuthorization2.RequiredStationName, StringComparison.Ordinal) || replenishmentTargetAuthorization.RequiredStationLevel != replenishmentTargetAuthorization2.RequiredStationLevel || replenishmentTargetAuthorization.AuthorizedPlayerId != replenishmentTargetAuthorization2.AuthorizedPlayerId || !string.Equals(replenishmentTargetAuthorization.AuthorizedPlayerName, replenishmentTargetAuthorization2.AuthorizedPlayerName, StringComparison.Ordinal) || replenishmentTargetAuthorization.Requirements.Count != replenishmentTargetAuthorization2.Requirements.Count) { return false; } for (int j = 0; j < replenishmentTargetAuthorization.Requirements.Count; j++) { if (!string.Equals(replenishmentTargetAuthorization.Requirements[j].PrefabId, replenishmentTargetAuthorization2.Requirements[j].PrefabId, StringComparison.Ordinal) || replenishmentTargetAuthorization.Requirements[j].Amount != replenishmentTargetAuthorization2.Requirements[j].Amount) { return false; } } } return true; } private static bool RunSmelter(Smelter station) { if ((Object)(object)station == (Object)null || station.m_maxOre <= 0) { return false; } if (ValheimAccess.QueueSize(station) < station.m_maxOre && TryPullSmelterInput(station)) { return true; } if ((Object)(object)station.m_fuelItem != (Object)null && station.m_maxFuel > 0 && ValheimAccess.Fuel(station) + 0.001f < (float)station.m_maxFuel) { return TryFuelSmelter(station); } return false; } private static bool TryPullSmelterInput(Smelter station) { foreach (ProductionEndpoint item in ResolveRoleEndpoints((Component)(object)station, ProductionLinkRole.Input)) { foreach (string prefab in ExactPrefabs(item.Inventory)) { if (ValheimAccess.Conversion(station, prefab) != null && ExactStockInventoryMutation.TryPrepareSource(item.Inventory, new ReplenishmentRequirement[1] { new ReplenishmentRequirement(prefab, 1) }, _ingredientReserves, out var transition, out var _, out var _)) { int before = ValheimAccess.QueueSize(station); string tail = ValheimAccess.QueueTail(station, before); if (ApplySourceToStation((Component)(object)station, item, transition, delegate { ValheimAccess.QueueOre(station, prefab); }, delegate { ValheimAccess.RestoreQueueTail(station, before, tail); }, () => ValheimAccess.QueueSize(station) == before && string.Equals(ValheimAccess.QueueTail(station, before), tail, StringComparison.Ordinal), () => ValheimAccess.QueueSize(station) == before + 1)) { Stop((Component)(object)station, ProductionStopCode.Ready, "pulled " + prefab); return true; } } } } return false; } private static bool TryFuelSmelter(Smelter station) { string text = ValheimAccess.PrefabName(((Component)station.m_fuelItem).gameObject); if (!StockDomainValidation.IsExactPrefabId(text)) { return false; } foreach (ProductionEndpoint item in ResolveFuelEndpoints((Component)(object)station)) { if (ExactStockInventoryMutation.TryPrepareSource(item.Inventory, new ReplenishmentRequirement[1] { new ReplenishmentRequirement(text, 1) }, _fuelReserves, out var transition, out var _, out var _)) { float before = ValheimAccess.Fuel(station); if (ApplySourceToStation((Component)(object)station, item, transition, delegate { ValheimAccess.SetFuel(station, before + 1f); }, delegate { ValheimAccess.SetFuel(station, before); }, () => Math.Abs(ValheimAccess.Fuel(station) - before) < 0.001f, () => Math.Abs(ValheimAccess.Fuel(station) - (before + 1f)) < 0.001f)) { Stop((Component)(object)station, ProductionStopCode.Ready, "supplied fuel"); return true; } } } return false; } private static bool RunFireplace(Fireplace station) { if ((Object)(object)station == (Object)null || !station.m_canRefill || station.m_infiniteFuel || (Object)(object)station.m_fuelItem == (Object)null || station.m_maxFuel <= 0f) { return false; } float before = ValheimAccess.FireplaceFuel(station); if (Mathf.Ceil(before) >= station.m_maxFuel) { return false; } string text = ValheimAccess.PrefabName(((Component)station.m_fuelItem).gameObject); if (!StockDomainValidation.IsExactPrefabId(text)) { return false; } foreach (ProductionEndpoint item in ResolveFuelEndpoints((Component)(object)station)) { if (ExactStockInventoryMutation.TryPrepareSource(item.Inventory, new ReplenishmentRequirement[1] { new ReplenishmentRequirement(text, 1) }, _fuelReserves, out var transition, out var _, out var _)) { float after = Math.Min(station.m_maxFuel, before + 1f); if (ApplySourceToStation((Component)(object)station, item, transition, delegate { ValheimAccess.SetFireplaceFuel(station, after); }, delegate { ValheimAccess.SetFireplaceFuel(station, before); }, () => Math.Abs(ValheimAccess.FireplaceFuel(station) - before) < 0.001f, () => Math.Abs(ValheimAccess.FireplaceFuel(station) - after) < 0.001f)) { Stop((Component)(object)station, ProductionStopCode.Ready, "supplied " + text); return true; } } } return false; } private static bool RunCooking(CookingStation station) { if (!CookingStationCompatibility.TryValidate(station, _cookingPolicy, requireAllowed: true, out var prefabId, out var failure)) { Stop((Component)(object)station, ProductionStopCode.StationUnusable, failure); return false; } if (!CookingStationCompatibility.TryValidateState(station, out var failure2)) { Stop((Component)(object)station, ProductionStopCode.StationUnusable, failure2); return false; } if (!TimedCookingStockModel.TryDescribe(station, prefabId, out var descriptor, out var failure3)) { Stop((Component)(object)station, ProductionStopCode.StationUnusable, failure3); return false; } if (TryRouteCookingOutput(station, descriptor)) { return true; } if (station.m_useFuel && TryFuelCooking(station, descriptor)) { return true; } return TryFillCookingSlot(station, descriptor); } private static bool TryRouteCookingOutput(CookingStation station, TimedCookingStationDescriptor descriptor) { for (int slot = 0; slot < descriptor.SlotCount; slot++) { ValheimAccess.GetCookingSlot(station, slot, out var item, out var elapsed, out var status); if (!string.IsNullOrEmpty(item) && (status == CookingSlotStatus.Done || status == CookingSlotStatus.Burnt) && TryResolveOutputDestination(new ProductionStationEntry(ProductionStationKind.Cooking, (Component)(object)station), item, 1, ReplenishmentProducerKind.TimedCooking, out var destination)) { StockOutputDefinition output = new StockOutputDefinition(item, 1, 1, 0, 0L, string.Empty); if (ExactStockInventoryMutation.TryPrepareDestination(destination.Endpoint.Inventory, output, out var transition, out var _) && ApplyDestinationFromStation((Component)(object)station, destination.Endpoint, transition, delegate { ValheimAccess.SetCookingSlot(station, slot, string.Empty, 0f, CookingSlotStatus.NotDone); }, delegate { ValheimAccess.SetCookingSlot(station, slot, item, elapsed, status); }, () => CookingSlotMatches(station, slot, item, elapsed, status), () => CookingSlotIsEmpty(station, slot))) { AdvanceDestination(ValheimAccess.Zdo(station), destination); Stop((Component)(object)station, ProductionStopCode.Ready, "stored " + item); return true; } } } return false; } private static bool TryFuelCooking(CookingStation station, TimedCookingStationDescriptor descriptor) { if (!descriptor.UsesFuel || string.IsNullOrEmpty(descriptor.FuelPrefabId) || ValheimAccess.CookingFuel(station) + 0.001f >= (float)descriptor.MaximumFuel) { return false; } foreach (ProductionEndpoint item in ResolveFuelEndpoints((Component)(object)station)) { if (ExactStockInventoryMutation.TryPrepareSource(item.Inventory, new ReplenishmentRequirement[1] { new ReplenishmentRequirement(descriptor.FuelPrefabId, 1) }, _fuelReserves, out var transition, out var _, out var _)) { float before = ValheimAccess.CookingFuel(station); if (ApplySourceToStation((Component)(object)station, item, transition, delegate { ValheimAccess.SetCookingFuel(station, before + 1f); }, delegate { ValheimAccess.SetCookingFuel(station, before); }, () => Math.Abs(ValheimAccess.CookingFuel(station) - before) < 0.001f, () => Math.Abs(ValheimAccess.CookingFuel(station) - (before + 1f)) < 0.001f)) { return true; } } } return false; } private static bool TryFillCookingSlot(CookingStation station, TimedCookingStationDescriptor descriptor) { int num = -1; for (int i = 0; i < descriptor.SlotCount; i++) { ValheimAccess.GetCookingSlot(station, i, out var item, out var _, out var _); if (string.IsNullOrEmpty(item)) { num = i; break; } } if (num < 0) { return false; } ProductionStationEntry station2 = new ProductionStationEntry(ProductionStationKind.Cooking, (Component)(object)station); string text = string.Empty; ProductionDestination destination = null; bool flag = TrySelectPlannedDestination(station2, ReplenishmentProducerKind.TimedCooking, out destination); if (flag) { if (destination.Target.Requirements.Count != 1) { return false; } text = destination.Target.Requirements[0].PrefabId; } else if (HasReplenishmentRecord(ValheimAccess.Zdo(station), (Component)(object)station)) { return false; } IEnumerable enumerable; if (flag) { enumerable = ResolveRecipeInputs((Component)(object)station, destination.Endpoint, destination.Target.AuthorizedPlayerId); } else { StoredProductionLink storedProductionLink = RoleLinks((Component)(object)station, ProductionLinkRole.Input).FirstOrDefault((StoredProductionLink value) => value != null && value.OwnerId != 0); enumerable = ((storedProductionLink == null) ? ResolveRoleEndpoints((Component)(object)station, ProductionLinkRole.Input) : ResolveRecipeInputs((Component)(object)station, null, storedProductionLink.OwnerId)); } foreach (ProductionEndpoint item2 in enumerable) { IEnumerable enumerable3; if (!string.IsNullOrEmpty(text)) { IEnumerable enumerable2 = new string[1] { text }; enumerable3 = enumerable2; } else { enumerable3 = ExactPrefabs(item2.Inventory); } foreach (string prefab in enumerable3) { if (descriptor.TryFromInput(prefab, out var _) && ExactStockInventoryMutation.TryPrepareSource(item2.Inventory, new ReplenishmentRequirement[1] { new ReplenishmentRequirement(prefab, 1) }, _ingredientReserves, out var transition, out var _, out var _)) { int slot = num; if (ApplySourceToStation((Component)(object)station, item2, transition, delegate { ValheimAccess.SetCookingSlot(station, slot, prefab, 0f, CookingSlotStatus.NotDone); }, delegate { ValheimAccess.SetCookingSlot(station, slot, string.Empty, 0f, CookingSlotStatus.NotDone); }, () => CookingSlotIsEmpty(station, slot), () => CookingSlotMatches(station, slot, prefab, CookingSlotStatus.NotDone))) { return true; } } } } return false; } private static bool RunRecipe(CraftingStation station) { if (!RecipeStationCompatibility.TryValidate(station, _recipePolicy, requireAllowed: true, out var prefabId, out var _, out var _, out var failure)) { return false; } string text = ValheimAccess.StableId((Component)(object)station); if (string.IsNullOrEmpty(text)) { return false; } ZDO stationZdo = ValheimAccess.Zdo(station); if (MultiReplenishmentRuntimeSupport.ReadOrMigrate(stationZdo, text, initializeEmpty: false, out var catalog, out failure) != StoredRecordState.Valid || catalog.Destinations.Count == 0) { return false; } foreach (ReplenishmentDestinationRecord item in catalog.ActiveRoundRobinOrder()) { ReplenishmentPlan plan = item.Plan; if (plan.AdapterKind != ReplenishmentProducerKind.DirectRecipe || plan.Targets.Count == 0 || !IsActiveReplenishmentLink((Component)(object)station, item.Link)) { continue; } for (int i = 0; i < plan.Targets.Count; i++) { int num = (plan.Cursor + i) % plan.Targets.Count; ReplenishmentTargetAuthorization replenishmentTargetAuthorization = plan.Targets[num]; if (!RecipeProducerResolver.TryResolveAuthorized(station, prefabId, replenishmentTargetAuthorization, _cookingInputPolicy, _fermenterInputPolicy, out var _, out failure) || !TryResolveEndpoint((Component)(object)station, item.Link, singleton: false, out var endpoint, out failure)) { continue; } int num2 = ExactStockInventoryMutation.CountExact(endpoint.Inventory, replenishmentTargetAuthorization.OutputPrefabId, matchWorldLevel: false); if (num2 <= 0 || !ReplenishmentNeedsStock(replenishmentTargetAuthorization.OutputPrefabId, num2, 0)) { continue; } StockOutputDefinition output = new StockOutputDefinition(replenishmentTargetAuthorization.OutputPrefabId, replenishmentTargetAuthorization.OutputAmount, 1, 0, replenishmentTargetAuthorization.AuthorizedPlayerId, replenishmentTargetAuthorization.AuthorizedPlayerName); if (ExactStockInventoryMutation.TryPrepareDestination(endpoint.Inventory, output, out var transition, out failure)) { List list = ResolveRecipeInputs((Component)(object)station, endpoint, replenishmentTargetAuthorization.AuthorizedPlayerId).Take(64).ToList(); if (list.Count != 0 && ExactStockInventoryMutation.TryPrepareCompositeSources(list.Select((ProductionEndpoint value) => value.Inventory).ToList(), replenishmentTargetAuthorization.Requirements, _ingredientReserves, out var plan2, out failure) && ApplyCompositeSourcesToDestination((Component)(object)station, list, plan2, endpoint, transition)) { AdvanceDestination(stationZdo, new ProductionDestination { Endpoint = endpoint, Catalog = catalog, Record = item, Target = replenishmentTargetAuthorization, TargetIndex = num }); Stop((Component)(object)station, ProductionStopCode.Ready, "crafted " + replenishmentTargetAuthorization.OutputPrefabId); return true; } } } } return false; } private static IEnumerable ResolveRecipeInputs(Component station, ProductionEndpoint destination, long actorId) { HashSet seen = new HashSet(); HashSet protectedDestinations = ProtectedDestinationIds(station); foreach (ProductionEndpoint item in ResolveRoleEndpoints(station, ProductionLinkRole.Input)) { if (item?.Zdo != null && item.Container != destination?.Container && seen.Add(item.Zdo.m_uid)) { protectedDestinations.Remove(item.Zdo.m_uid); yield return item; } } if (!(ProductionConfig.RecipeNearbyIngredientsEnabled?.Value ?? false)) { yield break; } int maximumSourceChests = Mathf.Clamp(ProductionConfig.RecipeNearbyMaximumSourceChests?.Value ?? 32, 1, 64); NearbyIngredientContainerQueryResult nearbyIngredientContainerQueryResult = NearbyIngredientContainerIndex.Query(station.transform.position, LinkRange, maximumSourceChests); if (nearbyIngredientContainerQueryResult.Truncated) { yield break; } foreach (NearbyIngredientContainerCandidate candidate in nearbyIngredientContainerQueryResult.Candidates) { Container container = candidate.Container; if (!((Object)(object)container == (Object)null) && container != destination?.Container && NearbyIngredientContainerIndex.IsStaticNonWagon(container) && ValheimAccess.IsNativeOwner((Component)(object)container)) { ZDO val = ValheimAccess.Zdo(container); if (val != null && !protectedDestinations.Contains(val.m_uid) && seen.Add(val.m_uid) && ValheimAccess.ContainerAllows(container, actorId) && ValheimAccess.WardAllows(((Component)container).transform.position, actorId) && ValheimAccess.TrySynchronizeLocallyOwnedContainer(container, out var inventory)) { yield return new ProductionEndpoint { Container = container, Zdo = val, Inventory = inventory }; } } } } private static HashSet ProtectedDestinationIds(Component station) { HashSet hashSet = new HashSet(); if ((Object)(object)station == (Object)null) { return hashSet; } ProductionLinkRole[] array = new ProductionLinkRole[3] { ProductionLinkRole.Fuel, ProductionLinkRole.Output, ProductionLinkRole.Replenishment }; foreach (ProductionLinkRole role in array) { foreach (StoredProductionLink item in RoleLinks(station, role)) { AddProtectedTarget(hashSet, item); } } return hashSet; } private static void AddProtectedTarget(ISet protectedIds, StoredProductionLink link) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (protectedIds != null && link != null) { if (ProductionEndpointIdentity.ResolveStable(link, out var _, out var targetZdo, out var _) == ProductionIdentityStatus.Ready && targetZdo != null) { protectedIds.Add(targetZdo.m_uid); } else if (!((ZDOID)(ref link.Target)).IsNone()) { protectedIds.Add(link.Target); } } } private static bool RunFermenter(Fermenter station) { if (!FermenterCompatibility.TryValidate(station, _fermenterPolicy, requireAllowed: true, out var descriptor, out var failure) || !FermenterCompatibility.TryValidateState(station, descriptor, out var state, out failure)) { return false; } if (state.IsEmpty) { return TryFillFermenter(station, descriptor); } if (ValheimAccess.FermenterStatus(station) != FermenterSlotStatus.Ready || !descriptor.TryFromInput(state.InputPrefabId, out var conversion)) { return false; } if (!TryResolveOutputDestination(new ProductionStationEntry(ProductionStationKind.Fermenter, (Component)(object)station), conversion.OutputPrefabId, conversion.OutputAmount, ReplenishmentProducerKind.Fermenter, out var destination)) { return false; } StockOutputDefinition output = new StockOutputDefinition(conversion.OutputPrefabId, conversion.OutputAmount, 1, 0, 0L, string.Empty); if (!ExactStockInventoryMutation.TryPrepareDestination(destination.Endpoint.Inventory, output, out var transition, out failure)) { return false; } if (!ApplyDestinationFromStation((Component)(object)station, destination.Endpoint, transition, delegate { ValheimAccess.SetFermenterState(station, string.Empty, 0L); }, delegate { state.Apply(station); }, () => FermenterStateMatches(station, state.InputPrefabId, state.StartTicks), () => string.IsNullOrEmpty(ValheimAccess.FermenterContent(station)) && ValheimAccess.FermenterStartTicks(station) == 0)) { return false; } AdvanceDestination(ValheimAccess.Zdo(station), destination); Stop((Component)(object)station, ProductionStopCode.Ready, "stored " + conversion.OutputPrefabId); return true; } private static bool TryFillFermenter(Fermenter station, FermenterDescriptor descriptor) { ValheimAccess.RefreshFermenterCover(station); if (!ValheimAccess.FermenterCovered(station)) { return false; } ProductionStationEntry station2 = new ProductionStationEntry(ProductionStationKind.Fermenter, (Component)(object)station); string text = string.Empty; ProductionDestination destination = null; bool flag = TrySelectPlannedDestination(station2, ReplenishmentProducerKind.Fermenter, out destination); if (flag) { if (destination.Target.Requirements.Count != 1) { return false; } text = destination.Target.Requirements[0].PrefabId; } else if (HasReplenishmentRecord(ValheimAccess.Zdo(station), (Component)(object)station)) { return false; } foreach (ProductionEndpoint item in flag ? ResolveRecipeInputs((Component)(object)station, destination.Endpoint, destination.Target.AuthorizedPlayerId) : ResolveRoleEndpoints((Component)(object)station, ProductionLinkRole.Input)) { IEnumerable enumerable2; if (!string.IsNullOrEmpty(text)) { IEnumerable enumerable = new string[1] { text }; enumerable2 = enumerable; } else { enumerable2 = ExactPrefabs(item.Inventory); } foreach (string prefab in enumerable2) { if (descriptor.TryFromInput(prefab, out var _) && ExactStockInventoryMutation.TryPrepareSource(item.Inventory, new ReplenishmentRequirement[1] { new ReplenishmentRequirement(prefab, 1) }, _ingredientReserves, out var transition, out var _, out var _)) { long ticks = ValheimAccess.NetworkTimeTicks(); if (ApplySourceToStation((Component)(object)station, item, transition, delegate { ValheimAccess.SetFermenterState(station, prefab, ticks); }, delegate { ValheimAccess.SetFermenterState(station, string.Empty, 0L); }, () => FermenterStateMatches(station, string.Empty, 0L), () => string.Equals(ValheimAccess.FermenterContent(station), prefab, StringComparison.Ordinal) && ValheimAccess.FermenterStartTicks(station) == ticks)) { return true; } } } } return false; } internal static bool TryDepositProducedOutput(Smelter station, string inputPrefab, int amount) { if (_initialized && !((Object)(object)station == (Object)null) && amount > 0) { ConfigEntry enabled = ProductionConfig.Enabled; if (enabled != null && enabled.Value && ValheimAccess.IsNativeOwner((Component)(object)station)) { if (SafetyPausedStations.Contains(((Object)station).GetInstanceID())) { return false; } ItemConversion val = ValheimAccess.Conversion(station, inputPrefab); string text = (((Object)(object)val?.m_to == (Object)null) ? string.Empty : ValheimAccess.PrefabName(((Component)val.m_to).gameObject)); if (!StockDomainValidation.IsExactPrefabId(text)) { return false; } StockOutputDefinition output = new StockOutputDefinition(text, amount, 1, 0, 0L, string.Empty); foreach (ProductionEndpoint item in ResolveRoleEndpoints((Component)(object)station, ProductionLinkRole.Output)) { if (!ExactStockInventoryMutation.TryPrepareDestination(item.Inventory, output, out var transition, out var _) || !EndpointStillOwned(item) || !transition.MatchesBefore(item.Inventory)) { continue; } try { transition.ApplyAfter(item.Inventory); switch (ResolveEndpointMutation(item, transition)) { case ProductionEndpointMutationState.After: return true; case ProductionEndpointMutationState.Before: continue; } SafetyPausedStations.Add(((Object)station).GetInstanceID()); ProductionDiagnostics.Warning("Smelter output suppressed vanilla fallback after an indeterminate destination publication; reload to resolve persisted state."); return true; } catch (Exception ex) { switch (ResolveEndpointMutation(item, transition)) { case ProductionEndpointMutationState.After: return true; case ProductionEndpointMutationState.Before: ProductionDiagnostics.Warning("Smelter output tried the next linked chest after a proven rollback: " + ex.GetType().Name + "."); break; default: SafetyPausedStations.Add(((Object)station).GetInstanceID()); ProductionDiagnostics.Warning("Smelter output suppressed vanilla fallback after an indeterminate destination failure: " + ex.GetType().Name + "."); return true; } } } return false; } } return false; } internal static bool TryHandleInteraction(Player player, GameObject target, bool hold, bool alternateUse, out string detail) { detail = string.Empty; return false; } private static bool TryBeginLinkSelection(Player player, ProductionStationEntry station, GameObject stationTarget, ProductionLinkMouseButton button, bool remove, out string detail) { detail = string.Empty; if ((Object)(object)player == (Object)null || (Object)(object)station?.Component == (Object)null) { return false; } long playerID = player.GetPlayerID(); if (playerID == 0L) { return false; } ProductionLinkRole requested = ProductionLinkGesturePolicy.RequestedRole(button); ProductionLinkRole productionLinkRole = RefineInputRoleForTarget(station, stationTarget, requested); if (!ValheimAccess.IsNativeOwner(station.Component)) { detail = "This process is not the station's current native owner."; return true; } if (!StationSupportsRole(station.Kind, productionLinkRole) || !ValidateStation(station, out detail)) { if (string.IsNullOrEmpty(detail)) { detail = productionLinkRole.ToString() + " is not supported by this station."; } return true; } if (Selections.TryGetValue(playerID, out var value) && value.Station.Component == station.Component && value.Role == productionLinkRole && value.Remove == remove) { Selections.Remove(playerID); detail = "Production link selection cancelled."; } else { if (!Selections.ContainsKey(playerID) && Selections.Count >= 16) { detail = "Too many link selections are active; try again shortly."; return true; } Selections[playerID] = new ProductionLinkSelection { PlayerId = playerID, Station = station, Role = productionLinkRole, Button = button, Remove = remove, ExpiresAt = Time.realtimeSinceStartup + 30f }; string text = DisplayRole(productionLinkRole); string text2 = GestureName(button, remove); detail = (remove ? ("Selected " + text + " unlink. Use " + text2 + " on a linked chest within 30 seconds.") : ("Selected " + text + ". Use " + text2 + " on a chest within 30 seconds.")); } return true; } private static bool CommitSelection(Player player, ProductionLinkSelection selection, Container container, bool remove, out string detail) { //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) detail = string.Empty; if (selection == null || (Object)(object)selection.Station?.Component == (Object)null || selection.ExpiresAt < Time.realtimeSinceStartup || selection.PlayerId != player.GetPlayerID()) { return Fail("The production link selection expired.", out detail); } Component component = selection.Station.Component; if (!ValheimAccess.IsNativeOwner(component) || !ValheimAccess.IsNativeOwner((Component)(object)container)) { return Fail("The station and chest must both be owned by this local process.", out detail); } ZDO val = ValheimAccess.Zdo(component); ZDO val2 = ValheimAccess.Zdo(container); if (val == null || val2 == null || val == val2 || !NearbyIngredientContainerIndex.IsStaticNonWagon(container)) { return Fail("The selected endpoint is not a static exact chest.", out detail); } float interactionRange = Mathf.Clamp(player.m_maxInteractDistance, 1f, 10f); if (!ValheimAccess.ContainerWithinReach(container, ((Component)player).transform.position, interactionRange)) { return Fail("The chest is outside interaction range.", out detail); } Vector3 position = val.GetPosition(); Vector3 position2 = val2.GetPosition(); if (ValheimAccess.IsFinite(position) && ValheimAccess.IsFinite(position2)) { Vector3 val3 = position - position2; if (!(((Vector3)(ref val3)).sqrMagnitude > LinkRange * LinkRange)) { long playerID = player.GetPlayerID(); if (!ValheimAccess.ContainerAllows(container, playerID) || !ValheimAccess.WardAllows(position, playerID) || !ValheimAccess.WardAllows(position2, playerID)) { return Fail("Vanilla chest or ward access denied this link.", out detail); } if (!ValheimAccess.ContainerWritable(container) || !ValheimAccess.TrySynchronizeLocallyOwnedContainer(container, out var inventory)) { return Fail("The chest is busy or not synchronized.", out detail); } if (!ProductionEndpointIdentity.TryCaptureCurrentTarget(val2.m_uid, out var targetToken, out var targetPrefabHash, out var rootPosition, out var failure)) { return Fail(failure, out detail); } if (remove) { return RemoveExactLink(selection.Station, selection.Role, val, targetToken, targetPrefabHash, out detail); } if (ProductionRoleLinkCatalogStore.ReadWithLegacy(val, selection.Role, out var catalog) != StoredRecordState.Valid) { return Fail("The selected role's link catalog is unavailable.", out detail); } StoredProductionLink previous = catalog.FindTarget(targetToken, targetPrefabHash); StoredProductionLink candidate = ProductionLinkStore.CreateDetached(selection.Role, val2.m_uid, rootPosition, playerID, ValheimAccess.Creator(component), ValheimAccess.Creator((Component)(object)container), previous); int softMaximum = Mathf.Clamp(ProductionConfig.MaximumLinksPerRole?.Value ?? 8, 1, 16); if (!ProductionRoleLinkCatalogPolicy.TryAddOrRefresh(catalog, candidate, softMaximum, out var updated, out var published, out detail)) { return Fail(string.IsNullOrEmpty(detail) ? "The role-link catalog rejected the link." : detail, out detail); } MultiReplenishmentCatalog catalog2 = null; if (selection.Role == ProductionLinkRole.Replenishment) { string stationId = ValheimAccess.StableId(selection.Station.Component); if (MultiReplenishmentRuntimeSupport.ReadOrMigrate(val, stationId, initializeEmpty: true, out catalog2, out detail) != StoredRecordState.Valid || !TryActivateReplenishmentDestination(selection.Station, val, container, inventory, published, player, out detail)) { return Fail(string.IsNullOrEmpty(detail) ? "The Replenishment plan did not publish." : detail, out detail); } } if (!ProductionRoleLinkCatalogStore.Publish(val, catalog, updated)) { if (catalog2 != null) { string stationId2 = ValheimAccess.StableId(selection.Station.Component); if (MultiReplenishmentRuntimeSupport.ReadOrMigrate(val, stationId2, initializeEmpty: false, out var catalog3, out var _) == StoredRecordState.Valid) { MultiReplenishmentCatalogCommitCode multiReplenishmentCatalogCommitCode = MultiReplenishmentCatalogStore.TryCommit(val, catalog3, catalog2); if (multiReplenishmentCatalogCommitCode != MultiReplenishmentCatalogCommitCode.Committed && multiReplenishmentCatalogCommitCode != MultiReplenishmentCatalogCommitCode.NoChange) { ProductionDiagnostics.Warning("An inert Replenishment plan could not be rolled back after the role-link publication failed: " + multiReplenishmentCatalogCommitCode.ToString() + "."); } } } return Fail("The role-link catalog did not publish.", out detail); } detail = "Linked as " + DisplayRole(selection.Role).ToLowerInvariant() + " for " + StationName(selection.Station.Component); return true; } } return Fail("The chest is outside the configured link range.", out detail); } private static bool AddOrRefreshDestination(ProductionStationEntry station, ZDO stationZdo, Container container, Inventory inventory, ZDO targetZdo, string targetToken, int targetPrefabHash, Vector3 targetPosition, StoredProductionLink suppliedLink, long actorId, string actorName, out string detail) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) detail = string.Empty; string text = ValheimAccess.StableId(station.Component); if (string.IsNullOrEmpty(text) || MultiReplenishmentRuntimeSupport.ReadOrMigrate(stationZdo, text, initializeEmpty: true, out var catalog, out detail) != StoredRecordState.Valid) { return false; } StoredProductionLink storedProductionLink = suppliedLink ?? ProductionLinkStore.CreateDetached(ProductionLinkRole.Replenishment, targetZdo.m_uid, targetPosition, actorId, ValheimAccess.Creator(station.Component), ValheimAccess.Creator((Component)(object)container)); ReplenishmentDestinationRecord destination; bool flag = catalog.TryGetByTargetIdentity(storedProductionLink, out destination); StoredProductionLink storedProductionLink2 = storedProductionLink; ReplenishmentPlan previous = ((flag && destination.Link.Revision == storedProductionLink2.Revision) ? destination.Plan : null); if (!TryBuildPlan(station, text, storedProductionLink2, inventory, actorId, actorName, previous, out var plan, out detail)) { return false; } if (!(flag ? MultiReplenishmentCatalogPolicy.TryRefresh(catalog, destination.LinkId, storedProductionLink2, plan, out var updated, out var refreshed, out var code) : MultiReplenishmentCatalogPolicy.TryAdd(catalog, storedProductionLink2, plan, 16, out updated, out refreshed, out code))) { return Fail("The destination catalog rejected the change: " + code.ToString() + ".", out detail); } MultiReplenishmentCatalogCommitCode multiReplenishmentCatalogCommitCode = MultiReplenishmentCatalogStore.TryCommit(stationZdo, catalog, updated); if (multiReplenishmentCatalogCommitCode != MultiReplenishmentCatalogCommitCode.Committed && multiReplenishmentCatalogCommitCode != MultiReplenishmentCatalogCommitCode.NoChange) { return Fail("The destination catalog did not publish: " + multiReplenishmentCatalogCommitCode.ToString() + ".", out detail); } detail = (flag ? "Replenishment targets refreshed from the physical exemplars." : "Replenishment destination added from the physical exemplars."); return true; } private static bool TryActivateReplenishmentDestination(ProductionStationEntry station, ZDO stationZdo, Container container, Inventory inventory, StoredProductionLink link, Player actor, out string detail) { detail = string.Empty; if ((Object)(object)station?.Component == (Object)null || stationZdo == null || (Object)(object)container == (Object)null || inventory == null || link == null || (Object)(object)actor == (Object)null) { return false; } return TryActivateReplenishmentDestination(station, stationZdo, container, inventory, link, actor.GetPlayerID(), actor.GetPlayerName(), out detail); } private static bool TryActivateReplenishmentDestination(ProductionStationEntry station, ZDO stationZdo, Container container, Inventory inventory, StoredProductionLink link, long actorId, string actorName, out string detail) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) detail = string.Empty; if ((Object)(object)station?.Component == (Object)null || stationZdo == null || (Object)(object)container == (Object)null || inventory == null || link == null || actorId == 0L || string.IsNullOrWhiteSpace(actorName)) { return false; } ZDO val = ValheimAccess.Zdo(container); if (val == null) { return false; } return AddOrRefreshDestination(station, stationZdo, container, inventory, val, link.TargetToken, link.TargetPrefabHash, link.ExpectedPosition, link, actorId, actorName, out detail); } private static bool TryBuildPlan(ProductionStationEntry station, string stationId, StoredProductionLink link, Inventory inventory, long actorId, string actorName, ReplenishmentPlan previous, out ReplenishmentPlan plan, out string failure) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Expected O, but got Unknown //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Expected O, but got Unknown plan = null; failure = string.Empty; switch (station.Kind) { case ProductionStationKind.Cooking: { CookingStation station3 = (CookingStation)station.Component; if (!CookingStationCompatibility.TryValidate(station3, _cookingPolicy, requireAllowed: true, out var prefabId2, out failure) || !TimedCookingStockModel.TryDescribe(station3, prefabId2, out var descriptor2, out failure)) { return false; } return TimedCookingPlanBuilder.TryBuild(descriptor2, inventory, link, stationId, actorId, actorName, previous, out plan, out failure); } case ProductionStationKind.Recipe: { CraftingStation station2 = (CraftingStation)station.Component; if (!RecipeStationCompatibility.TryValidate(station2, _recipePolicy, requireAllowed: true, out var prefabId, out var _, out var _, out failure)) { return false; } return RecipePlanBuilder.TryBuild(station2, stationId, prefabId, link, inventory, actorId, actorName, previous, _cookingInputPolicy, _fermenterInputPolicy, out plan, out failure); } case ProductionStationKind.Fermenter: { if (!FermenterCompatibility.TryValidate((Fermenter)station.Component, _fermenterPolicy, requireAllowed: true, out var descriptor, out failure)) { return false; } return FermenterPlanBuilder.TryBuild(descriptor, inventory, link, stationId, actorId, actorName, previous, out plan, out failure); } default: return Fail("This station does not support Replenishment destinations.", out failure); } } private static bool RemoveExactLink(ProductionStationEntry station, ProductionLinkRole role, ZDO stationZdo, string token, int prefabHash, out string detail) { detail = string.Empty; ProductionRoleLinkCatalog catalog; StoredRecordState storedRecordState = ProductionRoleLinkCatalogStore.Read(stationZdo, role, out catalog); if (storedRecordState == StoredRecordState.Invalid) { return Fail("The selected role's link catalog is unavailable.", out detail); } bool flag = storedRecordState == StoredRecordState.Absent; if (flag && ProductionRoleLinkCatalogStore.ReadWithLegacy(stationZdo, role, out catalog) != StoredRecordState.Valid) { return Fail("The selected role's legacy link is unavailable.", out detail); } ProductionRoleLinkCatalog updated = null; bool flag2 = catalog.FindTarget(token, prefabHash) != null; if (flag2 && !ProductionRoleLinkCatalogPolicy.TryRemove(catalog, token, prefabHash, out updated, out detail)) { return false; } if (role != ProductionLinkRole.Replenishment) { if (!flag2) { return Fail("That chest is not linked for the selected role.", out detail); } if (!ProductionRoleLinkCatalogStore.Publish(stationZdo, catalog, updated)) { return Fail("The link removal did not publish.", out detail); } detail = DisplayRole(role) + " Link removed for " + StationName(station.Component); return true; } string stationId = ValheimAccess.StableId(station.Component); MultiReplenishmentCatalog catalog2; ReplenishmentDestinationRecord replenishmentDestinationRecord = ((MultiReplenishmentRuntimeSupport.ReadOrMigrate(stationZdo, stationId, initializeEmpty: false, out catalog2, out detail) == StoredRecordState.Valid) ? catalog2.Destinations.FirstOrDefault((ReplenishmentDestinationRecord value) => SameTarget(value.Link, token, prefabHash)) : null); if (!flag2 && replenishmentDestinationRecord == null) { return Fail("That chest is not a Replenishment destination.", out detail); } if (flag2 || (flag && replenishmentDestinationRecord != null)) { ProductionRoleLinkCatalog catalog3 = (flag2 ? updated : catalog); if (!ProductionRoleLinkCatalogStore.Publish(stationZdo, catalog, catalog3)) { return Fail("The Replenishment link removal did not publish.", out detail); } } if (replenishmentDestinationRecord != null) { if (MultiReplenishmentCatalogPolicy.TryRemove(catalog2, replenishmentDestinationRecord.LinkId, out var updated2, out var _, out var code)) { MultiReplenishmentCatalogCommitCode multiReplenishmentCatalogCommitCode = MultiReplenishmentCatalogStore.TryCommit(stationZdo, catalog2, updated2); if (multiReplenishmentCatalogCommitCode != MultiReplenishmentCatalogCommitCode.Committed && multiReplenishmentCatalogCommitCode != MultiReplenishmentCatalogCommitCode.NoChange) { ProductionDiagnostics.Warning("An inert replenishment plan body could not be cleaned: " + multiReplenishmentCatalogCommitCode.ToString() + "."); } } else { ProductionDiagnostics.Warning("An inert replenishment plan body could not be removed: " + code.ToString() + "."); } } detail = "Replenishment Link removed for " + StationName(station.Component); return true; } private static bool TryResolveSingleton(Component station, ProductionLinkRole role, out ProductionEndpoint endpoint, out string failure) { foreach (StoredProductionLink item in RoleLinks(station, role)) { if (TryResolveEndpoint(station, item, singleton: true, out endpoint, out failure)) { return true; } } endpoint = null; failure = DisplayRole(role) + " link is missing or unavailable."; return false; } private static IReadOnlyList RoleLinks(Component station, ProductionLinkRole role) { if (ProductionRoleLinkCatalogStore.ReadWithLegacy(ValheimAccess.Zdo(station), role, out var catalog) != StoredRecordState.Valid || catalog == null) { return Array.Empty(); } return catalog.Links; } private static IEnumerable ResolveRoleEndpoints(Component station, ProductionLinkRole role) { foreach (StoredProductionLink item in RoleLinks(station, role)) { if (TryResolveEndpoint(station, item, singleton: true, out var endpoint, out var _)) { yield return endpoint; } } } private static bool IsActiveReplenishmentLink(Component station, StoredProductionLink link) { if ((Object)(object)station == (Object)null || link == null) { return false; } if (ProductionRoleLinkCatalogStore.ReadWithLegacy(ValheimAccess.Zdo(station), ProductionLinkRole.Replenishment, out var catalog) != StoredRecordState.Valid) { return false; } StoredProductionLink storedProductionLink = catalog?.FindTarget(link.TargetToken, link.TargetPrefabHash); if (storedProductionLink != null && storedProductionLink.Revision == link.Revision) { return string.Equals(storedProductionLink.LinkId, link.LinkId, StringComparison.Ordinal); } return false; } private static IEnumerable ResolveFuelEndpoints(Component station) { ProductionLinkRole role = ((RoleLinks(station, ProductionLinkRole.Fuel).Count <= 0) ? ProductionLinkRole.Input : ProductionLinkRole.Fuel); return ResolveRoleEndpoints(station, role); } private static bool TryResolveEndpoint(Component station, StoredProductionLink link, bool singleton, out ProductionEndpoint endpoint, out string failure) { //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0174: 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_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) endpoint = null; failure = string.Empty; if ((Object)(object)station == (Object)null || link == null || !ValheimAccess.IsNativeOwner(station)) { return Fail("The station is not a native local owner.", out failure); } Container container; ZDO targetZdo; string failure2; ProductionIdentityStatus productionIdentityStatus = ProductionEndpointIdentity.ResolveStable(link, out container, out targetZdo, out failure2); if (productionIdentityStatus == ProductionIdentityStatus.Missing && singleton && ProductionEndpointIdentity.TryResolveUniqueLegacyTarget(link, out container, out targetZdo, out failure2)) { ZNetView val = ValheimAccess.View(container); if ((Object)(object)val != (Object)null && val.IsValid() && val.IsOwner() && ProductionEndpointIdentity.TryGetOrEnsureToken(val, out var tokenValue) && ProductionLinkStore.TryUpgradeResolvedTarget(ValheimAccess.Zdo(station), link, targetZdo, tokenValue, targetZdo.GetPrefab(), out var upgraded)) { link = upgraded; productionIdentityStatus = ProductionEndpointIdentity.ResolveStable(link, out container, out targetZdo, out failure2); } } if (productionIdentityStatus != ProductionIdentityStatus.Ready || (Object)(object)container == (Object)null || targetZdo == null) { return Fail(failure2, out failure); } ZNetView val2 = ValheimAccess.View(container); ZDO val3 = ValheimAccess.Zdo(station); if ((Object)(object)val2 == (Object)null || !val2.IsValid() || !val2.IsOwner() || val3 == null) { return Fail("The exact chest is not a native local owner.", out failure); } Vector3 position = val3.GetPosition(); Vector3 position2 = targetZdo.GetPosition(); float num = Mathf.Clamp(ProductionConfig.MovedTargetTolerance?.Value ?? 0.75f, 0.1f, 3f); if (ValheimAccess.IsFinite(position) && ValheimAccess.IsFinite(position2)) { Vector3 val4 = position2 - link.ExpectedPosition; if (!(((Vector3)(ref val4)).sqrMagnitude > num * num)) { val4 = position2 - position; if (((Vector3)(ref val4)).sqrMagnitude > LinkRange * LinkRange) { return Fail("The linked chest is outside range.", out failure); } if (ValheimAccess.Creator(station) != link.StationOwnerId || ValheimAccess.Creator((Component)(object)container) != link.TargetOwnerId) { return Fail("A linked piece's creator identity changed.", out failure); } if (!ValheimAccess.ContainerAllows(container, link.OwnerId) || !ValheimAccess.WardAllows(position, link.OwnerId) || !ValheimAccess.WardAllows(position2, link.OwnerId)) { return Fail("Current vanilla chest or ward access denies automation.", out failure); } if (!NearbyIngredientContainerIndex.IsStaticNonWagon(container) || !ValheimAccess.TrySynchronizeLocallyOwnedContainer(container, out var inventory)) { return Fail("The exact chest is busy or not synchronized.", out failure); } endpoint = new ProductionEndpoint { Link = link, Container = container, Zdo = targetZdo, Inventory = inventory }; return true; } } return Fail("The linked chest moved beyond its saved tolerance.", out failure); } private static bool TrySelectPlannedDestination(ProductionStationEntry station, ReplenishmentProducerKind kind, out ProductionDestination destination) { destination = null; string text = ValheimAccess.StableId(station.Component); ZDO stationZdo = ValheimAccess.Zdo(station.Component); if (string.IsNullOrEmpty(text) || MultiReplenishmentRuntimeSupport.ReadOrMigrate(stationZdo, text, initializeEmpty: false, out var catalog, out var failure) != StoredRecordState.Valid || catalog.Destinations.Count == 0) { return false; } foreach (ReplenishmentDestinationRecord item in catalog.ActiveRoundRobinOrder()) { ReplenishmentPlan plan = item.Plan; if (item.State != ReplenishmentDestinationState.Active || plan.AdapterKind != kind || plan.Targets.Count == 0 || !IsActiveReplenishmentLink(station.Component, item.Link) || !TryResolveEndpoint(station.Component, item.Link, singleton: false, out var endpoint, out failure)) { continue; } for (int i = 0; i < plan.Targets.Count; i++) { int num = (plan.Cursor + i) % plan.Targets.Count; ReplenishmentTargetAuthorization replenishmentTargetAuthorization = plan.Targets[num]; int num2 = ExactStockInventoryMutation.CountExact(endpoint.Inventory, replenishmentTargetAuthorization.OutputPrefabId, matchWorldLevel: false); int num3 = CountInFlightOutput(station, kind, replenishmentTargetAuthorization.OutputPrefabId); if (num2 > 0 && num3 <= 0 && ReplenishmentNeedsStock(replenishmentTargetAuthorization.OutputPrefabId, num2, num3) && TargetIsCurrent(station, item, replenishmentTargetAuthorization, endpoint.Inventory)) { destination = new ProductionDestination { Endpoint = endpoint, Catalog = catalog, Record = item, Target = replenishmentTargetAuthorization, TargetIndex = num }; return true; } } } return false; } private static bool TryResolveOutputDestination(ProductionStationEntry station, string outputPrefab, int outputAmount, ReplenishmentProducerKind kind, out ProductionDestination destination) { destination = null; ZDO stationZdo = ValheimAccess.Zdo(station.Component); string stationId = ValheimAccess.StableId(station.Component); MultiReplenishmentCatalog catalog; string failure; StoredRecordState num = MultiReplenishmentRuntimeSupport.ReadOrMigrate(stationZdo, stationId, initializeEmpty: false, out catalog, out failure); bool flag = num == StoredRecordState.Valid && catalog.Destinations.Count > 0; if (num == StoredRecordState.Invalid) { return false; } StockInventoryTransition transition; if (flag) { ProductionDestination productionDestination = null; ProductionDestination productionDestination2 = null; int num2 = int.MinValue; foreach (ReplenishmentDestinationRecord item in catalog.ActiveRoundRobinOrder()) { if (item.State != ReplenishmentDestinationState.Active || item.Plan.AdapterKind != kind || !IsActiveReplenishmentLink(station.Component, item.Link)) { continue; } for (int i = 0; i < item.Plan.Targets.Count; i++) { ReplenishmentTargetAuthorization replenishmentTargetAuthorization = item.Plan.Targets[i]; if (!string.Equals(replenishmentTargetAuthorization.OutputPrefabId, outputPrefab, StringComparison.Ordinal) || !TryResolveEndpoint(station.Component, item.Link, singleton: false, out var endpoint, out failure)) { continue; } int num3 = ExactStockInventoryMutation.CountExact(endpoint.Inventory, outputPrefab, matchWorldLevel: false); if (num3 <= 0 || !TargetIsCurrent(station, item, replenishmentTargetAuthorization, endpoint.Inventory)) { continue; } ProductionDestination productionDestination3 = new ProductionDestination { Endpoint = endpoint, Catalog = catalog, Record = item, Target = replenishmentTargetAuthorization, TargetIndex = i }; StockOutputDefinition output = new StockOutputDefinition(outputPrefab, Math.Max(1, outputAmount), 1, 0, replenishmentTargetAuthorization.AuthorizedPlayerId, replenishmentTargetAuthorization.AuthorizedPlayerName); if (!ExactStockInventoryMutation.TryPrepareDestination(endpoint.Inventory, output, out transition, out failure)) { continue; } if (productionDestination2 == null) { productionDestination2 = productionDestination3; } if (_replenishmentReserves.TryGetTargetReserve(outputPrefab, out var targetReserve) && num3 < targetReserve) { int num4 = targetReserve - num3; if (productionDestination == null || num4 > num2) { productionDestination = productionDestination3; num2 = num4; } } } } destination = productionDestination ?? productionDestination2; if (destination != null) { return true; } } StockOutputDefinition output2 = new StockOutputDefinition(outputPrefab, Math.Max(1, outputAmount), 1, 0, 0L, string.Empty); foreach (ProductionEndpoint item2 in ResolveRoleEndpoints(station.Component, ProductionLinkRole.Output)) { if (ExactStockInventoryMutation.TryPrepareDestination(item2.Inventory, output2, out transition, out failure)) { destination = new ProductionDestination { Endpoint = item2 }; return true; } } return false; } private static bool TargetIsCurrent(ProductionStationEntry station, ReplenishmentDestinationRecord record, ReplenishmentTargetAuthorization target, Inventory destinationInventory) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Expected O, but got Unknown //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Expected O, but got Unknown string stationId = ValheimAccess.StableId(station.Component); string failure; switch (station.Kind) { case ProductionStationKind.Cooking: { CookingStation station3 = (CookingStation)station.Component; TimedCookingConversionDescriptor conversion; if (CookingStationCompatibility.TryValidate(station3, _cookingPolicy, requireAllowed: true, out var prefabId2, out failure) && TimedCookingStockModel.TryDescribe(station3, prefabId2, out var descriptor2, out failure) && TimedCookingPlanBuilder.TryValidate(record.Plan, descriptor2, record.Link, stationId, out failure)) { return TimedCookingPlanBuilder.TryResolveAuthorized(descriptor2, target, out conversion, out failure); } return false; } case ProductionStationKind.Recipe: { CraftingStation station2 = (CraftingStation)station.Component; ResolvedRecipeProducer producer; if (RecipeStationCompatibility.TryValidate(station2, _recipePolicy, requireAllowed: true, out var prefabId, out var _, out var _, out failure)) { return RecipeProducerResolver.TryResolveAuthorized(station2, prefabId, target, _cookingInputPolicy, _fermenterInputPolicy, out producer, out failure); } return false; } case ProductionStationKind.Fermenter: { if (FermenterCompatibility.TryValidate((Fermenter)station.Component, _fermenterPolicy, requireAllowed: true, out var descriptor, out failure)) { return FermenterPlanBuilder.TryValidate(record.Plan, descriptor, record.Link, stationId, destinationInventory, out failure); } return false; } default: return false; } } private static int CountInFlightOutput(ProductionStationEntry station, ReplenishmentProducerKind kind, string outputPrefabId) { if ((Object)(object)station?.Component == (Object)null || !StockDomainValidation.IsExactPrefabId(outputPrefabId)) { return 0; } string failure; if (kind == ReplenishmentProducerKind.TimedCooking) { Component component = station.Component; CookingStation val = (CookingStation)(object)((component is CookingStation) ? component : null); if (val != null && CookingStationCompatibility.TryValidate(val, _cookingPolicy, requireAllowed: true, out var prefabId, out failure) && TimedCookingStockModel.TryDescribe(val, prefabId, out var descriptor, out failure)) { int num = 0; for (int i = 0; i < descriptor.SlotCount; i++) { ValheimAccess.GetCookingSlot(val, i, out var item, out var _, out var _); if (!string.IsNullOrEmpty(item) && (string.Equals(item, outputPrefabId, StringComparison.Ordinal) || (descriptor.TryFromInput(item, out var conversion) && string.Equals(conversion.OutputPrefabId, outputPrefabId, StringComparison.Ordinal)))) { num++; } } return num; } } if (kind == ReplenishmentProducerKind.Fermenter) { Component component2 = station.Component; Fermenter val2 = (Fermenter)(object)((component2 is Fermenter) ? component2 : null); if (val2 != null && FermenterCompatibility.TryValidate(val2, _fermenterPolicy, requireAllowed: true, out var descriptor2, out failure) && descriptor2.TryFromInput(ValheimAccess.FermenterContent(val2), out var conversion2) && string.Equals(conversion2.OutputPrefabId, outputPrefabId, StringComparison.Ordinal)) { return Math.Max(0, conversion2.OutputAmount); } } return 0; } private static void AdvanceDestination(ZDO stationZdo, ProductionDestination destination) { if (stationZdo == null || destination == null || !destination.IsReplenishment || destination.Record?.Plan == null || destination.Record.Plan.Targets.Count == 0) { return; } ReplenishmentPlan plan = destination.Record.Plan; ReplenishmentPlan advancedPlan = plan.WithCursor((destination.TargetIndex + 1) % plan.Targets.Count); if (MultiReplenishmentCatalogPolicy.TryAdvanceAfterSelection(destination.Catalog, destination.Record.LinkId, advancedPlan, out var updated, out var _, out var _)) { MultiReplenishmentCatalogCommitCode multiReplenishmentCatalogCommitCode = MultiReplenishmentCatalogStore.TryCommit(stationZdo, destination.Catalog, updated); if (multiReplenishmentCatalogCommitCode != MultiReplenishmentCatalogCommitCode.Committed && multiReplenishmentCatalogCommitCode != MultiReplenishmentCatalogCommitCode.NoChange) { ProductionDiagnostics.Warning("A completed batch could not advance replenishment fairness: " + multiReplenishmentCatalogCommitCode.ToString() + "."); } } } private static bool ApplySourceToStation(Component station, ProductionEndpoint source, StockInventoryTransition transition, Action mutateStation, Action restoreStation, Func stationRestored, Func stationMatches) { if (!ValheimAccess.IsNativeOwner(station) || !EndpointStillOwned(source) || !transition.MatchesBefore(source.Inventory)) { return false; } try { transition.ApplyAfter(source.Inventory); if (ResolveEndpointMutation(source, transition) != ProductionEndpointMutationState.After) { throw new InvalidOperationException("source publication did not persist exactly"); } mutateStation(); if (!ValheimAccess.IsNativeOwner(station) || !stationMatches() || ResolveEndpointMutation(source, transition) != ProductionEndpointMutationState.After) { throw new InvalidOperationException("station mutation did not match"); } return true; } catch { bool flag = false; try { restoreStation(); flag = ValheimAccess.IsNativeOwner(station) && stationRestored(); } catch { flag = false; } ProductionEndpointMutationState productionEndpointMutationState = ResolveEndpointMutation(source, transition); if (productionEndpointMutationState == ProductionEndpointMutationState.After) { try { transition.ApplyBefore(source.Inventory); } catch { } productionEndpointMutationState = ResolveEndpointMutation(source, transition); } if (flag && productionEndpointMutationState == ProductionEndpointMutationState.Before) { return false; } PauseAfterIndeterminate(station, "source-to-station"); return false; } } private static bool ApplyDestinationFromStation(Component station, ProductionEndpoint destination, StockInventoryTransition transition, Action mutateStation, Action restoreStation, Func stationRestored, Func stationMatches) { if (!ValheimAccess.IsNativeOwner(station) || !EndpointStillOwned(destination) || !transition.MatchesBefore(destination.Inventory)) { return false; } try { transition.ApplyAfter(destination.Inventory); if (ResolveEndpointMutation(destination, transition) != ProductionEndpointMutationState.After) { throw new InvalidOperationException("destination publication did not persist exactly"); } mutateStation(); if (!ValheimAccess.IsNativeOwner(station) || !stationMatches() || ResolveEndpointMutation(destination, transition) != ProductionEndpointMutationState.After) { throw new InvalidOperationException("station mutation did not match"); } return true; } catch { bool flag = false; try { restoreStation(); flag = ValheimAccess.IsNativeOwner(station) && stationRestored(); } catch { flag = false; } ProductionEndpointMutationState productionEndpointMutationState = ResolveEndpointMutation(destination, transition); if (productionEndpointMutationState == ProductionEndpointMutationState.After) { try { transition.ApplyBefore(destination.Inventory); } catch { } productionEndpointMutationState = ResolveEndpointMutation(destination, transition); } if (flag && productionEndpointMutationState == ProductionEndpointMutationState.Before) { return false; } PauseAfterIndeterminate(station, "station-to-destination"); return false; } } private static bool ApplyCompositeSourcesToDestination(Component station, IReadOnlyList sources, ExactStockCompositeSourcePlan sourcePlan, ProductionEndpoint destination, StockInventoryTransition destinationTransition) { if (!ValheimAccess.IsNativeOwner(station) || sources == null || sourcePlan == null || sourcePlan.SourceCount != sources.Count || !EndpointStillOwned(destination) || !destinationTransition.MatchesBefore(destination.Inventory)) { return false; } foreach (ExactStockCompositeSourceEntry entry in sourcePlan.Entries) { if (entry.SourceIndex < 0 || entry.SourceIndex >= sources.Count) { return false; } ProductionEndpoint productionEndpoint = sources[entry.SourceIndex]; if (productionEndpoint == null || productionEndpoint.Container == destination.Container || !EndpointStillOwned(productionEndpoint) || !entry.Transition.MatchesBefore(productionEndpoint.Inventory)) { return false; } } List list = new List(); bool flag = false; try { foreach (ExactStockCompositeSourceEntry entry2 in sourcePlan.Entries) { ProductionEndpoint productionEndpoint2 = sources[entry2.SourceIndex]; list.Add(entry2); entry2.Transition.ApplyAfter(productionEndpoint2.Inventory); if (ResolveEndpointMutation(productionEndpoint2, entry2.Transition) != ProductionEndpointMutationState.After) { throw new InvalidOperationException("composite source changed"); } } if (!EndpointStillOwned(destination)) { throw new InvalidOperationException("composite destination ownership changed"); } flag = true; destinationTransition.ApplyAfter(destination.Inventory); if (ResolveEndpointMutation(destination, destinationTransition) != ProductionEndpointMutationState.After || !list.All((ExactStockCompositeSourceEntry entry) => ResolveEndpointMutation(sources[entry.SourceIndex], entry.Transition) == ProductionEndpointMutationState.After)) { throw new InvalidOperationException("composite inventory readback failed"); } return true; } catch { bool flag2 = ValheimAccess.IsNativeOwner(station); if (flag) { ProductionEndpointMutationState productionEndpointMutationState = ResolveEndpointMutation(destination, destinationTransition); if (productionEndpointMutationState == ProductionEndpointMutationState.After) { try { destinationTransition.ApplyBefore(destination.Inventory); } catch { } productionEndpointMutationState = ResolveEndpointMutation(destination, destinationTransition); } flag2 = flag2 && productionEndpointMutationState == ProductionEndpointMutationState.Before; } for (int num = list.Count - 1; num >= 0; num--) { ExactStockCompositeSourceEntry exactStockCompositeSourceEntry = list[num]; ProductionEndpoint productionEndpoint3 = sources[exactStockCompositeSourceEntry.SourceIndex]; ProductionEndpointMutationState productionEndpointMutationState2 = ResolveEndpointMutation(productionEndpoint3, exactStockCompositeSourceEntry.Transition); if (productionEndpointMutationState2 == ProductionEndpointMutationState.After) { try { exactStockCompositeSourceEntry.Transition.ApplyBefore(productionEndpoint3.Inventory); } catch { } productionEndpointMutationState2 = ResolveEndpointMutation(productionEndpoint3, exactStockCompositeSourceEntry.Transition); } flag2 = flag2 && productionEndpointMutationState2 == ProductionEndpointMutationState.Before; } if (flag2) { return false; } PauseAfterIndeterminate(station, "composite-source-to-destination"); return false; } } private static bool EndpointStillOwned(ProductionEndpoint endpoint) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)endpoint?.Container == (Object)null || endpoint.Zdo == null) { return false; } ZNetView val = ValheimAccess.View(endpoint.Container); if ((Object)(object)val != (Object)null && val.IsValid() && val.IsOwner() && val.GetZDO() != null && val.GetZDO().m_uid == endpoint.Zdo.m_uid) { return ValheimAccess.ContainerWritable(endpoint.Container); } return false; } private static ProductionEndpointMutationState ResolveEndpointMutation(ProductionEndpoint endpoint, StockInventoryTransition transition) { try { if ((Object)(object)endpoint?.Container == (Object)null || transition == null || !EndpointStillOwned(endpoint) || !ValheimAccess.TrySynchronizeLocallyOwnedContainer(endpoint.Container, out var inventory)) { return ProductionEndpointMutationState.Indeterminate; } endpoint.Inventory = inventory; if (transition.MatchesBefore(inventory)) { return ProductionEndpointMutationState.Before; } if (transition.MatchesAfter(inventory)) { return ProductionEndpointMutationState.After; } return ProductionEndpointMutationState.Indeterminate; } catch { return ProductionEndpointMutationState.Indeterminate; } } private static void PauseAfterIndeterminate(Component station, string operation) { if ((Object)(object)station != (Object)null) { SafetyPausedStations.Add(((Object)station).GetInstanceID()); } ProductionDiagnostics.Warning("Production paused this station for the session after an indeterminate " + operation + " mutation. Reloading resolves state from Valheim's persisted inventory."); throw new ProductionMutationIndeterminateException(operation); } private static IEnumerable ExactPrefabs(Inventory inventory) { if (inventory == null) { return Enumerable.Empty(); } return (from item in inventory.GetAllItems() where item != null && item.m_stack > 0 select item).Select(ValheimAccess.PrefabName).Where(StockDomainValidation.IsExactPrefabId).Distinct(StringComparer.Ordinal) .OrderBy((string value) => value, StringComparer.Ordinal); } private static bool CookingSlotIsEmpty(CookingStation station, int slot) { ValheimAccess.GetCookingSlot(station, slot, out var item, out var elapsed, out var status); if (string.IsNullOrEmpty(item) && elapsed == 0f) { return status == CookingSlotStatus.NotDone; } return false; } private static bool CookingSlotMatches(CookingStation station, int slot, string item, CookingSlotStatus expected) { return CookingSlotMatches(station, slot, item, 0f, expected); } private static bool CookingSlotMatches(CookingStation station, int slot, string item, float expectedElapsed, CookingSlotStatus expected) { ValheimAccess.GetCookingSlot(station, slot, out var item2, out var elapsed, out var status); if (string.Equals(item2, item, StringComparison.Ordinal) && elapsed == expectedElapsed) { return status == expected; } return false; } private static bool FermenterStateMatches(Fermenter station, string expectedContent, long expectedStartTicks) { if (string.Equals(ValheimAccess.FermenterContent(station), expectedContent, StringComparison.Ordinal)) { return ValheimAccess.FermenterStartTicks(station) == expectedStartTicks; } return false; } private static bool ValidateStation(ProductionStationEntry station, out string failure) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Expected O, but got Unknown //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Expected O, but got Unknown failure = string.Empty; string prefabId; switch (station.Kind) { case ProductionStationKind.Smelter: return true; case ProductionStationKind.Cooking: return CookingStationCompatibility.TryValidate((CookingStation)station.Component, _cookingPolicy, requireAllowed: true, out prefabId, out failure); case ProductionStationKind.Recipe: { ZNetView view; WearNTear wear; return RecipeStationCompatibility.TryValidate((CraftingStation)station.Component, _recipePolicy, requireAllowed: true, out prefabId, out view, out wear, out failure); } case ProductionStationKind.Fermenter: { FermenterDescriptor descriptor; return FermenterCompatibility.TryValidate((Fermenter)station.Component, _fermenterPolicy, requireAllowed: true, out descriptor, out failure); } case ProductionStationKind.Fireplace: { Fireplace val = (Fireplace)station.Component; if ((Object)(object)val != (Object)null && val.m_canRefill && !val.m_infiniteFuel && (Object)(object)val.m_fuelItem != (Object)null && val.m_maxFuel > 0f && StockDomainValidation.IsExactPrefabId(ValheimAccess.PrefabName(((Component)val.m_fuelItem).gameObject))) { return true; } return Fail("This fire or lamp has no supported native fuel slot.", out failure); } default: return Fail("Unsupported production station.", out failure); } } private static bool TryClassifyStation(GameObject target, out ProductionStationEntry station) { station = null; Smelter componentInParent = target.GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null) { station = new ProductionStationEntry(ProductionStationKind.Smelter, (Component)(object)componentInParent); return true; } CookingStation componentInParent2 = target.GetComponentInParent(); if ((Object)(object)componentInParent2 != (Object)null) { station = new ProductionStationEntry(ProductionStationKind.Cooking, (Component)(object)componentInParent2); return true; } Fermenter componentInParent3 = target.GetComponentInParent(); if ((Object)(object)componentInParent3 != (Object)null) { station = new ProductionStationEntry(ProductionStationKind.Fermenter, (Component)(object)componentInParent3); return true; } Fireplace componentInParent4 = target.GetComponentInParent(); if ((Object)(object)componentInParent4 != (Object)null) { station = new ProductionStationEntry(ProductionStationKind.Fireplace, (Component)(object)componentInParent4); return true; } CraftingStation componentInParent5 = target.GetComponentInParent(); if ((Object)(object)componentInParent5 != (Object)null) { station = new ProductionStationEntry(ProductionStationKind.Recipe, (Component)(object)componentInParent5); return true; } return false; } private static bool StationSupportsRole(ProductionStationKind station, ProductionLinkRole role) { switch (station) { case ProductionStationKind.Smelter: if (role != ProductionLinkRole.Input && role != ProductionLinkRole.Fuel) { return role == ProductionLinkRole.Output; } return true; case ProductionStationKind.Cooking: if (role != ProductionLinkRole.Input && role != ProductionLinkRole.Fuel && role != ProductionLinkRole.Output) { return role == ProductionLinkRole.Replenishment; } return true; case ProductionStationKind.Recipe: if (role != ProductionLinkRole.Input && role != ProductionLinkRole.Output) { return role == ProductionLinkRole.Replenishment; } return true; case ProductionStationKind.Fermenter: if (role != ProductionLinkRole.Input && role != ProductionLinkRole.Output) { return role == ProductionLinkRole.Replenishment; } return true; case ProductionStationKind.Fireplace: if (role != ProductionLinkRole.Input) { return role == ProductionLinkRole.Fuel; } return true; default: return false; } } private static ProductionLinkRole RefineInputRoleForTarget(ProductionStationEntry station, GameObject target, ProductionLinkRole requested) { if (requested != ProductionLinkRole.Input || (Object)(object)station?.Component == (Object)null) { return requested; } if (station.Kind == ProductionStationKind.Fireplace) { return ProductionLinkRole.Fuel; } Switch control = null; Component component = station.Component; Smelter val = (Smelter)(object)((component is Smelter) ? component : null); if (val != null) { control = val.m_addWoodSwitch; } else { Component component2 = station.Component; CookingStation val2 = (CookingStation)(object)((component2 is CookingStation) ? component2 : null); if (val2 != null && val2.m_useFuel) { control = val2.m_addFuelSwitch; } } if (!IsTargetInsideControl(target, control)) { return ProductionLinkRole.Input; } return ProductionLinkRole.Fuel; } private static bool IsTargetInsideControl(GameObject target, Switch control) { if ((Object)(object)target == (Object)null || (Object)(object)control == (Object)null || (Object)(object)((Component)control).transform == (Object)null) { return false; } Transform transform = target.transform; if ((Object)(object)transform != (Object)null) { if (transform != ((Component)control).transform) { return transform.IsChildOf(((Component)control).transform); } return true; } return false; } private static bool HasReplenishmentRecord(ZDO zdo, Component station) { string stationId = ValheimAccess.StableId(station); MultiReplenishmentCatalog catalog; string failure; return MultiReplenishmentRuntimeSupport.ReadOrMigrate(zdo, stationId, initializeEmpty: false, out catalog, out failure) switch { StoredRecordState.Valid => catalog.Destinations.Any((ReplenishmentDestinationRecord value) => IsActiveReplenishmentLink(station, value.Link)), StoredRecordState.Invalid => true, _ => false, }; } private static bool SameTarget(StoredProductionLink link, string token, int prefabHash) { if (link != null && link.TargetPrefabHash == prefabHash && ProductionEndpointIdentity.IsCanonicalToken(token)) { return string.Equals(link.TargetToken, token, StringComparison.Ordinal); } return false; } private static void RefreshLinkedHighlights(ProductionStationEntry station, float lifetimeSeconds) { if ((Object)(object)station?.Component == (Object)null || ValheimAccess.Zdo(station.Component) == null) { return; } foreach (StoredProductionLink item in RoleLinks(station.Component, ProductionLinkRole.Input)) { RefreshLinkedHighlight(item, ProductionLinkRole.Input, lifetimeSeconds); } foreach (StoredProductionLink item2 in RoleLinks(station.Component, ProductionLinkRole.Fuel)) { RefreshLinkedHighlight(item2, ProductionLinkRole.Input, lifetimeSeconds); } foreach (StoredProductionLink item3 in RoleLinks(station.Component, ProductionLinkRole.Output)) { RefreshLinkedHighlight(item3, ProductionLinkRole.Output, lifetimeSeconds); } foreach (StoredProductionLink item4 in RoleLinks(station.Component, ProductionLinkRole.Replenishment)) { RefreshLinkedHighlight(item4, ProductionLinkRole.Replenishment, lifetimeSeconds); } } private static string LinkTargetKey(StoredProductionLink link) { if (link != null) { return (link.TargetToken ?? string.Empty) + ":" + link.TargetPrefabHash; } return string.Empty; } private static void RefreshLinkedHighlight(StoredProductionLink link, ProductionLinkRole role, float lifetimeSeconds) { if (link != null) { Container container; ZDO targetZdo; string failure; ProductionIdentityStatus productionIdentityStatus = ProductionEndpointIdentity.ResolveStable(link, out container, out targetZdo, out failure); if (productionIdentityStatus == ProductionIdentityStatus.Missing && ProductionEndpointIdentity.TryResolveUniqueLegacyTarget(link, out container, out targetZdo, out failure)) { productionIdentityStatus = ProductionIdentityStatus.Ready; } if (productionIdentityStatus == ProductionIdentityStatus.Ready && (Object)(object)container != (Object)null) { ProductionLinkHighlight.Show(container, role, lifetimeSeconds); } } } private static void PruneSelections() { float now = Time.realtimeSinceStartup; long[] array = (from value in Selections where value.Value == null || (Object)(object)value.Value.Station?.Component == (Object)null || value.Value.ExpiresAt < now select value.Key).ToArray(); foreach (long key in array) { Selections.Remove(key); } } internal static void AppendHover(Component station, ProductionLinkRole role, ref string text) { if (_initialized && !((Object)(object)station == (Object)null)) { ConfigEntry showStatusOverlay = ProductionConfig.ShowStatusOverlay; if ((showStatusOverlay == null || showStatusOverlay.Value) && ProductionDiagnostics.TryGetStop(ValheimAccess.StableId(station), out var stopCode)) { text = text + "\nRunic Production: " + stopCode; } ZDO zdo = ValheimAccess.Zdo(station); int count = DistinctRoleCount(station, ProductionLinkRole.Input); int count2 = DistinctRoleCount(station, ProductionLinkRole.Fuel); int count3 = DistinctRoleCount(station, ProductionLinkRole.Output); int count4 = ReplenishmentLinkCount(station, zdo); text = text + "\nRunic Production Links\nInput: " + FormatLinkCount(count) + "\nFuel Input: " + FormatLinkCount(count2) + "\nOutput: " + FormatLinkCount(count3) + "\nReplenishment: " + FormatLinkCount(count4); ConfigEntry showControlHints = ProductionConfig.ShowControlHints; if (showControlHints == null || showControlHints.Value) { text += "\n[Alt+Left Mouse] Input (aim at the fuel control for Fuel Input) [Alt+Right Mouse] Output [Alt+Middle Mouse] Replenishment\nRepeat the same gesture on a chest within 30 seconds. Add Shift at both steps to remove a link."; } } } private static int DistinctRoleCount(Component station, params ProductionLinkRole[] roles) { HashSet hashSet = new HashSet(StringComparer.Ordinal); foreach (ProductionLinkRole role in roles) { foreach (StoredProductionLink item in RoleLinks(station, role)) { hashSet.Add(LinkTargetKey(item)); } } hashSet.Remove(string.Empty); return hashSet.Count; } private static int ReplenishmentLinkCount(Component station, ZDO zdo) { HashSet hashSet = new HashSet(StringComparer.Ordinal); foreach (StoredProductionLink item in RoleLinks(station, ProductionLinkRole.Replenishment)) { hashSet.Add(LinkTargetKey(item)); } hashSet.Remove(string.Empty); return hashSet.Count; } private static string FormatLinkCount(int count) { if (count > 0) { if (count != 1) { return count + " chests"; } return "1 chest"; } return "empty"; } private static string DisplayRole(ProductionLinkRole role) { if (role != ProductionLinkRole.Fuel) { return role.ToString(); } return "Fuel Input"; } private static string GestureName(ProductionLinkMouseButton button, bool remove) { string text = button switch { ProductionLinkMouseButton.Middle => "Middle Mouse Button", ProductionLinkMouseButton.Right => "Right Mouse Button", _ => "Left Mouse Button", }; return (remove ? "Shift+Alt+" : "Alt+") + text; } private static string StationName(Component station) { if ((Object)(object)station == (Object)null) { return "station"; } string empty = string.Empty; try { empty = (((object)station).GetType().GetField("m_name", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(station) as string) ?? string.Empty; if (!string.IsNullOrWhiteSpace(empty) && Localization.instance != null) { empty = Localization.instance.Localize(empty); } } catch { empty = string.Empty; } if (!string.IsNullOrWhiteSpace(empty)) { return empty.Trim(); } empty = ValheimAccess.PrefabName(station.gameObject) ?? string.Empty; if (empty.StartsWith("piece_", StringComparison.OrdinalIgnoreCase)) { empty = empty.Substring(6); } empty = empty.Replace('_', ' ').Trim(); if (!string.IsNullOrEmpty(empty)) { return empty; } return "station"; } private static bool ReplenishmentNeedsStock(string prefabId, int currentCount, int inFlightCount) { int reserve; if (_replenishmentReserves != null) { return _replenishmentReserves.NeedsProduction(prefabId, currentCount, Math.Max(0, inFlightCount), out reserve); } return false; } private static ProductionStationKind KindOf(Component station) { if (station is Smelter) { return ProductionStationKind.Smelter; } if (station is CookingStation) { return ProductionStationKind.Cooking; } if (station is CraftingStation) { return ProductionStationKind.Recipe; } if (station is Fermenter) { return ProductionStationKind.Fermenter; } return ProductionStationKind.Fireplace; } internal static void FailHook(string hook, Exception exception) { ProductionDiagnostics.Warning("Production hook " + hook + " failed safely: " + exception.GetType().Name + ": " + exception.Message); } internal static void FailStation(Component station, string operation, Exception exception) { ProductionDiagnostics.StopChanged(((Object)(object)station == (Object)null) ? string.Empty : ValheimAccess.StableId(station), ProductionStopCode.InternalFailure, operation + ":" + exception.GetType().Name); ProductionDiagnostics.Warning("Production station operation failed safely: " + operation + " " + exception.GetType().Name + ": " + exception.Message); } private static void Stop(Component station, ProductionStopCode code, string detail) { ProductionDiagnostics.StopChanged(ValheimAccess.StableId(station), code, detail); } private static bool Fail(string value, out string failure) { failure = value; return false; } } internal static class RecipePlanBuilder { internal static bool TryBuild(CraftingStation station, string stationId, string stationPrefabId, StoredProductionLink link, Inventory destinationInventory, Player actor, ReplenishmentPlan previous, StockStationPrefabPolicy cookingInputStations, StockStationPrefabPolicy fermenterInputStations, out ReplenishmentPlan plan, out string failure) { if ((Object)(object)actor == (Object)null) { plan = null; return Fail("Recipe target authorization inputs are unavailable.", out failure); } return TryBuild(station, stationId, stationPrefabId, link, destinationInventory, actor.GetPlayerID(), actor.GetPlayerName(), previous, cookingInputStations, fermenterInputStations, out plan, out failure); } internal static bool TryBuild(CraftingStation station, string stationId, string stationPrefabId, StoredProductionLink link, Inventory destinationInventory, long actorId, string actorName, ReplenishmentPlan previous, StockStationPrefabPolicy cookingInputStations, StockStationPrefabPolicy fermenterInputStations, out ReplenishmentPlan plan, out string failure) { plan = null; failure = string.Empty; if ((Object)(object)station == (Object)null || string.IsNullOrEmpty(stationId) || string.IsNullOrEmpty(stationPrefabId) || link == null || destinationInventory == null || actorId == 0L || string.IsNullOrWhiteSpace(actorName)) { return Fail("Recipe target authorization inputs are unavailable.", out failure); } bool flag = previous != null && previous.AdapterKind == ReplenishmentProducerKind.DirectRecipe && string.Equals(previous.StationPrefabId, stationPrefabId, StringComparison.Ordinal) && ReplenishmentPlanStore.MatchesLink(previous, link, stationId); if (previous != null && !flag) { return Fail("The prior recipe plan is not bound to this exact station and link.", out failure); } long num = (flag ? previous.AuthorizedPlayerId : actorId); string text = (flag ? previous.AuthorizedPlayerName : actorName); if (num == 0L || num != link.OwnerId || string.IsNullOrWhiteSpace(text)) { return Fail("The recipe plan principal does not match the exact link owner.", out failure); } Dictionary dictionary = new Dictionary(StringComparer.Ordinal); SortedSet sortedSet = new SortedSet(StringComparer.Ordinal); foreach (ItemData allItem in destinationInventory.GetAllItems()) { if (allItem == null || allItem.m_stack <= 0) { continue; } string text2 = ValheimAccess.PrefabName(allItem); if (StockDomainValidation.IsExactPrefabId(text2)) { sortedSet.Add(text2); if (sortedSet.Count > 32) { return Fail("The chest contains too many distinct exemplar prefabs.", out failure); } } } foreach (string item in sortedSet) { if (RecipeProducerResolver.TryAuthorize(station, stationPrefabId, item, num, text, cookingInputStations, fermenterInputStations, out var authorization, out var _)) { dictionary[item] = authorization; } } if (dictionary.Count > 32) { return Fail("The authorized target limit was exceeded.", out failure); } List list = dictionary.Values.OrderBy((ReplenishmentTargetAuthorization value) => value.OutputPrefabId, StringComparer.Ordinal).ToList(); int cursor = 0; if (flag && previous.Targets.Count > 0) { string current3 = previous.Targets[previous.Cursor].OutputPrefabId; int num2 = list.FindIndex((ReplenishmentTargetAuthorization value) => string.Equals(value.OutputPrefabId, current3, StringComparison.Ordinal)); if (num2 >= 0) { cursor = num2; } } if (flag && previous.Revision == int.MaxValue) { return Fail("The plan revision is exhausted; remove and re-add it.", out failure); } plan = new ReplenishmentPlan(flag ? previous.PlanId : Guid.NewGuid().ToString("N"), (!flag) ? 1 : (previous.Revision + 1), cursor, stationPrefabId, ReplenishmentProducerKind.DirectRecipe, ReplenishmentPlanStore.Bind(link, stationId), num, text, list); return true; } private static bool Fail(string value, out string failure) { failure = value; return false; } } internal enum RecipeCandidateDisposition { Eligible, Missing, Ambiguous, RequireOnlyOneUnsupported } internal static class RecipeCandidatePolicy { internal static RecipeCandidateDisposition Classify(int exactCandidateCount, bool uniqueCandidateRequiresOnlyOne) { if (exactCandidateCount < 0) { throw new ArgumentOutOfRangeException("exactCandidateCount"); } if (exactCandidateCount == 0) { return RecipeCandidateDisposition.Missing; } if (exactCandidateCount > 1) { return RecipeCandidateDisposition.Ambiguous; } if (!uniqueCandidateRequiresOnlyOne) { return RecipeCandidateDisposition.Eligible; } return RecipeCandidateDisposition.RequireOnlyOneUnsupported; } } internal sealed class ResolvedRecipeProducer { internal Recipe Recipe { get; } internal string OutputPrefab { get; } internal byte[] Signature { get; } internal IReadOnlyList Requirements { get; } internal ResolvedRecipeProducer(Recipe recipe, string outputPrefab, byte[] signature, IReadOnlyList requirements) { Recipe = recipe ?? throw new ArgumentNullException("recipe"); OutputPrefab = outputPrefab ?? throw new ArgumentNullException("outputPrefab"); if (signature == null) { throw new ArgumentNullException("signature"); } Signature = (byte[])signature.Clone(); Requirements = requirements ?? throw new ArgumentNullException("requirements"); } } internal static class RecipeProducerResolver { internal static bool TryAuthorize(CraftingStation station, string stationPrefabId, string outputPrefabId, Player actor, StockStationPrefabPolicy cookingInputStations, StockStationPrefabPolicy fermenterInputStations, out ReplenishmentTargetAuthorization authorization, out string failure) { if ((Object)(object)actor == (Object)null) { authorization = null; failure = "An online player is required to authorize replenishment targets."; return false; } return TryAuthorize(station, stationPrefabId, outputPrefabId, actor.GetPlayerID(), actor.GetPlayerName(), cookingInputStations, fermenterInputStations, out authorization, out failure); } internal static bool TryAuthorize(CraftingStation station, string stationPrefabId, string outputPrefabId, long authorizedPlayerId, string authorizedPlayerName, StockStationPrefabPolicy cookingInputStations, StockStationPrefabPolicy fermenterInputStations, out ReplenishmentTargetAuthorization authorization, out string failure) { authorization = null; if (authorizedPlayerId == 0L || string.IsNullOrWhiteSpace(authorizedPlayerName)) { failure = "An exact player principal is required to authorize replenishment targets."; return false; } if (!TryResolve(station, stationPrefabId, outputPrefabId, cookingInputStations, fermenterInputStations, out var producer, out failure)) { return false; } Recipe recipe = producer.Recipe; if (!RecipeStationCompatibility.IsPhysicallyUsable(station, recipe.GetRequiredStationLevel(1), out failure)) { return false; } authorization = new ReplenishmentTargetAuthorization(producer.OutputPrefab, ReplenishmentProducerKind.DirectRecipe, ((Object)recipe).name, producer.Signature, recipe.m_amount, recipe.m_craftingStation.m_name, recipe.GetRequiredStationLevel(1), authorizedPlayerId, authorizedPlayerName, producer.Requirements); return true; } internal static bool TryResolveAuthorized(CraftingStation station, string stationPrefabId, ReplenishmentTargetAuthorization authorization, StockStationPrefabPolicy cookingInputStations, StockStationPrefabPolicy fermenterInputStations, out ResolvedRecipeProducer producer, out string failure) { producer = null; if (authorization == null || authorization.ProducerKind != ReplenishmentProducerKind.DirectRecipe) { failure = "The target is not a direct recipe authorization."; return false; } if (!TryResolve(station, stationPrefabId, authorization.OutputPrefabId, cookingInputStations, fermenterInputStations, out producer, out failure)) { return false; } if (!string.Equals(authorization.ProducerId, ((Object)producer.Recipe).name, StringComparison.Ordinal) || !authorization.ProducerSignatureMatches(producer.Signature) || authorization.OutputAmount != producer.Recipe.m_amount || !string.Equals(authorization.RequiredStationName, producer.Recipe.m_craftingStation.m_name, StringComparison.Ordinal) || authorization.RequiredStationLevel != producer.Recipe.GetRequiredStationLevel(1) || !RequirementsMatch(authorization.Requirements, producer.Requirements)) { producer = null; failure = "The signed recipe definition changed; refresh targets to authorize it again."; return false; } if (!RecipeStationCompatibility.IsPhysicallyUsable(station, authorization.RequiredStationLevel, out failure)) { producer = null; return false; } return true; } internal static byte[] Signature(Recipe recipe, string stationPrefabId, string outputPrefabId) { if ((Object)(object)recipe == (Object)null) { throw new ArgumentNullException("recipe"); } return StockProducerSignature.Create(delegate(StockSignatureWriter writer) { //IL_0182: Unknown result type (might be due to invalid IL or missing references) ZNetScene instance = ZNetScene.instance; object obj; if (instance == null) { obj = null; } else { GameObject prefab = instance.GetPrefab(stationPrefabId); obj = ((prefab != null) ? prefab.GetComponent() : null); } CraftingStation val = (CraftingStation)obj; writer.WriteString("runic.production.recipe.v1"); writer.WriteString(stationPrefabId); writer.WriteString(val?.m_name ?? string.Empty); writer.WriteBoolean(val?.m_craftRequireRoof ?? false); writer.WriteBoolean(val?.m_craftRequireFire ?? false); writer.WriteString(recipe.m_craftingStation?.m_name ?? string.Empty); writer.WriteString(((Object)recipe).name ?? string.Empty); writer.WriteBoolean(recipe.m_enabled); writer.WriteString(outputPrefabId); writer.WriteInt32(recipe.m_amount); writer.WriteString(recipe.m_item?.m_itemData?.m_shared?.m_dlc ?? string.Empty); writer.WriteInt32(recipe.m_minStationLevel); writer.WriteBoolean(recipe.m_requireOnlyOneIngredient); writer.WriteSingle(recipe.m_qualityResultAmountMultiplier); SharedData val2 = recipe.m_item?.m_itemData?.m_shared; writer.WriteInt32((val2 != null) ? ((int)val2.m_itemType) : 0); writer.WriteInt32(val2?.m_maxStackSize ?? 0); writer.WriteInt32(val2?.m_maxQuality ?? 0); Requirement[] array = recipe.m_resources ?? Array.Empty(); writer.WriteInt32(array.Length); Requirement[] array2 = array; foreach (Requirement val3 in array2) { writer.WriteString(((Object)(object)val3?.m_resItem == (Object)null) ? string.Empty : ValheimAccess.PrefabName(((Component)val3.m_resItem).gameObject)); writer.WriteInt32(val3?.m_amount ?? 0); writer.WriteInt32(val3?.m_extraAmountOnlyOneIngredient ?? 0); writer.WriteInt32(val3?.m_amountPerLevel ?? 0); } }); } private static bool TryResolve(CraftingStation station, string stationPrefabId, string outputPrefabId, StockStationPrefabPolicy cookingInputStations, StockStationPrefabPolicy fermenterInputStations, out ResolvedRecipeProducer producer, out string failure) { producer = null; failure = string.Empty; if ((Object)(object)station == (Object)null || !StockDomainValidation.IsExactPrefabId(stationPrefabId) || !StockDomainValidation.IsExactPrefabId(outputPrefabId) || (Object)(object)ObjectDB.instance == (Object)null) { failure = "Recipe producer state is unavailable."; return false; } List list = ObjectDB.instance.m_recipes.Where((Recipe recipe) => IsCandidate(recipe, station, outputPrefabId)).ToList(); RecipeCandidateDisposition recipeCandidateDisposition = RecipeCandidatePolicy.Classify(list.Count, list.Count == 1 && list[0].m_requireOnlyOneIngredient); switch (recipeCandidateDisposition) { case RecipeCandidateDisposition.Missing: failure = "No enabled exact recipe at this station produces the exemplar."; return false; case RecipeCandidateDisposition.Ambiguous: failure = "More than one exact recipe at this station produces the exemplar."; return false; default: { Recipe val = list[0]; if (recipeCandidateDisposition == RecipeCandidateDisposition.RequireOnlyOneUnsupported) { failure = "Recipes that accept one of several ingredients are unsupported in v0.3."; return false; } if (val.m_amount <= 0 || val.m_amount > 1000000) { failure = "The recipe output batch is outside the supported bound."; return false; } if (!DlcInstalled(val.m_item.m_itemData.m_shared.m_dlc)) { failure = "The recipe output requires unavailable DLC."; return false; } if (!IsSupportedOutput(((Component)val.m_item).gameObject, cookingInputStations, fermenterInputStations)) { failure = "The recipe output is neither consumable nor an input to an enabled allowed cooking producer."; return false; } if (!TryRequirements(val, out var requirements, out failure)) { return false; } producer = new ResolvedRecipeProducer(val, outputPrefabId, Signature(val, stationPrefabId, outputPrefabId), requirements.AsReadOnly()); return true; } } } private static bool IsCandidate(Recipe recipe, CraftingStation station, string outputPrefabId) { if ((Object)(object)recipe != (Object)null && recipe.m_enabled && (Object)(object)recipe.m_item != (Object)null && (Object)(object)recipe.m_craftingStation != (Object)null && string.Equals(recipe.m_craftingStation.m_name, station.m_name, StringComparison.Ordinal)) { return string.Equals(ValheimAccess.PrefabName(((Component)recipe.m_item).gameObject), outputPrefabId, StringComparison.Ordinal); } return false; } private static bool TryRequirements(Recipe recipe, out List requirements, out string failure) { requirements = new List(); failure = string.Empty; Requirement[] array = recipe.m_resources ?? Array.Empty(); if (array.Length > 16) { failure = "The recipe has too many resource rows."; return false; } Requirement[] array2 = array; foreach (Requirement val in array2) { if (!((Object)(object)val?.m_resItem == (Object)null) && val.GetAmount(1) > 0) { string text = ValheimAccess.PrefabName(((Component)val.m_resItem).gameObject); if (!StockDomainValidation.IsExactPrefabId(text) || val.GetAmount(1) > 1000000) { failure = "The recipe contains an invalid exact resource requirement."; return false; } requirements.Add(new ReplenishmentRequirement(text, val.GetAmount(1))); } } if (requirements.Count == 0) { failure = "Zero-cost replenishment recipes are not supported."; return false; } return true; } private static bool IsSupportedOutput(GameObject outputPrefab, StockStationPrefabPolicy cookingInputStations, StockStationPrefabPolicy fermenterInputStations) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Invalid comparison between Unknown and I4 ItemDrop val = ((outputPrefab != null) ? outputPrefab.GetComponent() : null); if ((Object)(object)val == (Object)null) { return false; } if ((int)val.m_itemData.m_shared.m_itemType == 2) { return true; } string outputId = ValheimAccess.PrefabName(outputPrefab); if (AcceptedByCookingStation(outputId, cookingInputStations)) { return true; } return AcceptedByFermenter(outputId, fermenterInputStations); } private static bool AcceptedByCookingStation(string outputId, StockStationPrefabPolicy policy) { if (policy == null || (Object)(object)ZNetScene.instance == (Object)null) { return false; } foreach (string effectiveAllowedId in policy.EffectiveAllowedIds) { GameObject prefab = ZNetScene.instance.GetPrefab(effectiveAllowedId); CookingStation val = ((prefab != null) ? prefab.GetComponent() : null); if ((Object)(object)val == (Object)null || !((Behaviour)val).enabled || !string.Equals(ValheimAccess.PrefabName(prefab), effectiveAllowedId, StringComparison.Ordinal)) { continue; } int num = 0; foreach (ItemConversion item in val.m_conversion ?? new List()) { if ((Object)(object)item?.m_from != (Object)null && string.Equals(ValheimAccess.PrefabName(((Component)item.m_from).gameObject), outputId, StringComparison.Ordinal)) { num++; } } if (num == 1) { return true; } if (num > 1) { return false; } } return false; } private static bool AcceptedByFermenter(string outputId, StockStationPrefabPolicy policy) { if (policy == null || (Object)(object)ZNetScene.instance == (Object)null) { return false; } foreach (string effectiveAllowedId in policy.EffectiveAllowedIds) { GameObject prefab = ZNetScene.instance.GetPrefab(effectiveAllowedId); Fermenter val = ((prefab != null) ? prefab.GetComponent() : null); if ((Object)(object)val == (Object)null || !((Behaviour)val).enabled || !string.Equals(ValheimAccess.PrefabName(prefab), effectiveAllowedId, StringComparison.Ordinal)) { continue; } int num = 0; foreach (ItemConversion item in val.m_conversion ?? new List()) { if ((Object)(object)item?.m_from != (Object)null && string.Equals(ValheimAccess.PrefabName(((Component)item.m_from).gameObject), outputId, StringComparison.Ordinal)) { num++; } } if (num == 1) { return true; } if (num > 1) { return false; } } return false; } private static bool DlcInstalled(string dlc) { if (!string.IsNullOrEmpty(dlc)) { if ((Object)(object)DLCMan.instance != (Object)null) { return DLCMan.instance.IsDLCInstalled(dlc); } return false; } return true; } private static bool RequirementsMatch(IReadOnlyList left, IReadOnlyList right) { if (left == null || right == null || left.Count != right.Count) { return false; } for (int i = 0; i < left.Count; i++) { if (!string.Equals(left[i].PrefabId, right[i].PrefabId, StringComparison.Ordinal) || left[i].Amount != right[i].Amount) { return false; } } return true; } } internal static class RecipeStationCompatibility { internal static bool TryValidate(CraftingStation station, StockStationPrefabPolicy policy, bool requireAllowed, out string prefabId, out ZNetView view, out WearNTear wear, out string failure) { prefabId = string.Empty; view = null; wear = null; failure = string.Empty; if ((Object)(object)station == (Object)null || ((Component)station).gameObject.GetComponent() != station) { return Fail("The CraftingStation is not the unique root component.", out failure); } if (((Component)station).gameObject.GetComponentsInChildren(true).Length != 1 || HasPeerAdapter(((Component)station).gameObject)) { return Fail("Hybrid or duplicate station components are not supported.", out failure); } view = ((Component)station).gameObject.GetComponent(); wear = ((Component)station).gameObject.GetComponent(); if ((Object)(object)view == (Object)null || !view.IsValid() || view.GetZDO() == null || (Object)(object)wear == (Object)null || ((Component)wear).gameObject != ((Component)station).gameObject || ((Component)station).gameObject.GetComponentsInChildren(true).Length != 1 || ((Component)station).gameObject.GetComponentsInChildren(true).Length != 1) { return Fail("The station requires one valid root ZNetView and WearNTear.", out failure); } if (string.IsNullOrWhiteSpace(station.m_name) || station.m_name.Length > 128) { return Fail("The station name is missing or unbounded.", out failure); } if (station.m_craftRequireRoof && (Object)(object)station.m_roofCheckPoint == (Object)null) { return Fail("The station requires a missing roof-check point.", out failure); } if ((Object)(object)ZNetScene.instance == (Object)null) { return Fail("The registered prefab scene is unavailable.", out failure); } GameObject prefab = ZNetScene.instance.GetPrefab(view.GetZDO().GetPrefab()); CraftingStation val = ((prefab != null) ? prefab.GetComponent() : null); if ((Object)(object)prefab == (Object)null || (Object)(object)val == (Object)null || prefab.GetComponentsInChildren(true).Length != 1 || prefab.GetComponentsInChildren(true).Length != 1 || (Object)(object)prefab.GetComponent() == (Object)null || prefab.GetComponentsInChildren(true).Length != 1 || (Object)(object)prefab.GetComponent() == (Object)null || HasPeerAdapter(prefab)) { return Fail("The registered prefab is not a strict recipe-station adapter.", out failure); } prefabId = ValheimAccess.PrefabName(prefab); if (!StockDomainValidation.IsExactPrefabId(prefabId)) { return Fail("The registered station prefab ID is invalid.", out failure); } if (!string.Equals(val.m_name, station.m_name, StringComparison.Ordinal) || val.m_craftRequireRoof != station.m_craftRequireRoof || val.m_craftRequireFire != station.m_craftRequireFire) { return Fail("The live station policy differs from its registered prefab.", out failure); } if (requireAllowed && (policy == null || !policy.Allows(prefabId))) { return Fail("The exact recipe-station prefab is not locally allowed.", out failure); } return true; } internal static bool HasPeerAdapter(GameObject root) { if (!((Object)(object)root == (Object)null) && root.GetComponentsInChildren(true).Length == 0 && root.GetComponentsInChildren(true).Length == 0 && root.GetComponentsInChildren(true).Length == 0) { return root.GetComponentsInChildren(true).Length != 0; } return true; } internal static bool IsPhysicallyUsable(CraftingStation station, int requiredLevel, out string failure) { //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) failure = string.Empty; if ((Object)(object)station == (Object)null || !((Behaviour)station).isActiveAndEnabled || requiredLevel < 1) { return Fail("The recipe station or required level is invalid.", out failure); } if (station.GetLevel(true) < requiredLevel) { return Fail($"The recipe requires station level {requiredLevel}.", out failure); } if (station.m_craftRequireRoof) { if ((Object)(object)station.m_roofCheckPoint == (Object)null) { return Fail("The station roof-check point is unavailable.", out failure); } float num = default(float); bool flag = default(bool); Cover.GetCoverForPoint(station.m_roofCheckPoint.position, ref num, ref flag, 0.5f); if (!flag) { return Fail("The recipe station needs a roof.", out failure); } if (num < 0.7f) { return Fail("The recipe station is too exposed.", out failure); } } if (station.m_craftRequireFire && !EffectArea.IsPointPlus025InsideBurningArea(((Component)station).transform.position)) { return Fail("The recipe station needs a lit fire.", out failure); } return true; } private static bool Fail(string detail, out string failure) { failure = detail; return false; } } internal static class ReplenishmentPlanStore { private const int LegacySchemaVersion = 1; private const int StableIdentitySchemaVersion = 2; private const int SchemaVersion = 3; private const int MaximumEncodedCharacters = 65536; private const int MaximumDecodedBytes = 49152; private static readonly string RecordKey = "runic.production.stock.plan.record"; internal static string StorageKey => RecordKey; internal static StoredRecordState Read(ZDO zdo, out ReplenishmentPlan plan) { if (zdo != null) { return Parse(zdo.GetString(RecordKey, string.Empty), out plan); } return ReturnAbsent(out plan); } internal static void Save(ZDO zdo, ReplenishmentPlan plan) { if (zdo == null) { throw new ArgumentNullException("zdo"); } zdo.Set(RecordKey, Serialize(plan)); } internal static void Clear(ZDO zdo) { if (zdo != null) { zdo.Set(RecordKey, string.Empty); } } internal static string Serialize(ReplenishmentPlan plan) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown if (plan == null) { throw new ArgumentNullException("plan"); } ZPackage val = new ZPackage(); val.Write(3); val.Write(plan.PlanId); val.Write(plan.Revision); val.Write(plan.Cursor); val.Write(plan.StationPrefabId); val.Write((int)plan.AdapterKind); val.Write(plan.AuthorizedPlayerId); val.Write(plan.AuthorizedPlayerName); WriteLink(val, plan.Link); val.Write(plan.Targets.Count); foreach (ReplenishmentTargetAuthorization target in plan.Targets) { val.Write(target.OutputPrefabId); val.Write((int)target.ProducerKind); val.Write(target.ProducerId); val.Write(target.CopyProducerSignature()); val.Write(target.OutputAmount); val.Write(target.RequiredStationName); val.Write(target.RequiredStationLevel); val.Write(target.AuthorizedPlayerId); val.Write(target.AuthorizedPlayerName); val.Write(target.Requirements.Count); foreach (ReplenishmentRequirement requirement in target.Requirements) { val.Write(requirement.PrefabId); val.Write(requirement.Amount); } } byte[] array = val.GetArray(); if (array.Length == 0 || array.Length > 49152) { throw new InvalidOperationException("The replenishment plan exceeds its persisted size bound."); } string text = Convert.ToBase64String(array); if (text.Length > 65536) { throw new InvalidOperationException("The replenishment plan exceeds its encoded size bound."); } return text; } internal static StoredRecordState Parse(string encoded, out ReplenishmentPlan plan) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown plan = null; if (string.IsNullOrEmpty(encoded)) { return StoredRecordState.Absent; } if (encoded.Length > 65536) { return StoredRecordState.Invalid; } try { byte[] array = Convert.FromBase64String(encoded); if (array.Length == 0 || array.Length > 49152) { return StoredRecordState.Invalid; } ZPackage val = new ZPackage(array); int num = val.ReadInt(); if (num != 1 && num != 2 && num != 3) { return StoredRecordState.Invalid; } string planId = val.ReadString(); int revision = val.ReadInt(); int cursor = val.ReadInt(); string stationPrefabId = val.ReadString(); ReplenishmentProducerKind adapterKind = (ReplenishmentProducerKind)val.ReadInt(); long playerId = ((num >= 3) ? val.ReadLong() : 0); string playerName = ((num >= 3) ? val.ReadString() : string.Empty); ReplenishmentLinkBinding link = ReadLink(val, num); int num2 = val.ReadInt(); if (num2 < 0 || num2 > 32) { return StoredRecordState.Invalid; } List list = new List(num2); for (int i = 0; i < num2; i++) { string outputPrefabId = val.ReadString(); ReplenishmentProducerKind producerKind = (ReplenishmentProducerKind)val.ReadInt(); string producerId = val.ReadString(); byte[] producerSignature = val.ReadByteArray(); int outputAmount = val.ReadInt(); string requiredStationName = val.ReadString(); int requiredStationLevel = val.ReadInt(); long authorizedPlayerId = val.ReadLong(); string authorizedPlayerName = val.ReadString(); int num3 = val.ReadInt(); if (num3 <= 0 || num3 > 16) { return StoredRecordState.Invalid; } List list2 = new List(num3); for (int j = 0; j < num3; j++) { list2.Add(new ReplenishmentRequirement(val.ReadString(), val.ReadInt())); } list.Add(new ReplenishmentTargetAuthorization(outputPrefabId, producerKind, producerId, producerSignature, outputAmount, requiredStationName, requiredStationLevel, authorizedPlayerId, authorizedPlayerName, list2)); } if (num < 3 && !TryDeriveLegacyPrincipal(list, out playerId, out playerName)) { return StoredRecordState.Invalid; } if (val.GetPos() != val.Size()) { return StoredRecordState.Invalid; } plan = new ReplenishmentPlan(planId, revision, cursor, stationPrefabId, adapterKind, link, playerId, playerName, list); return StoredRecordState.Valid; } catch { plan = null; return StoredRecordState.Invalid; } } internal static bool MatchesLink(ReplenishmentPlan plan, StoredProductionLink link, string stationId) { if (plan != null && link != null && link.Role == ProductionLinkRole.Replenishment && string.Equals(plan.Link.LinkId, link.LinkId, StringComparison.Ordinal) && string.Equals(plan.Link.StationId, stationId, StringComparison.Ordinal)) { bool num; if (!string.IsNullOrEmpty(plan.Link.TargetToken)) { if (!string.Equals(plan.Link.TargetToken, link.TargetToken, StringComparison.Ordinal) || plan.Link.TargetPrefabHash != link.TargetPrefabHash) { goto IL_00dd; } num = string.Equals(plan.Link.TargetId, link.TargetToken, StringComparison.Ordinal); } else { num = string.Equals(plan.Link.TargetId, ((object)Unsafe.As(ref link.Target)/*cast due to .constrained prefix*/).ToString(), StringComparison.Ordinal); } if (num) { return plan.Link.Revision == link.Revision; } } goto IL_00dd; IL_00dd: return false; } internal static ReplenishmentLinkBinding Bind(StoredProductionLink link, string stationId) { if (link == null || link.Role != ProductionLinkRole.Replenishment) { throw new ArgumentException("An exact replenishment link is required.", "link"); } if (!StockDomainValidation.IsLegacyOrWorldObjectIdentity(link.TargetToken, link.TargetPrefabHash)) { throw new ArgumentException("The replenishment target identity is malformed.", "link"); } string targetId = (string.IsNullOrEmpty(link.TargetToken) ? ((object)Unsafe.As(ref link.Target)/*cast due to .constrained prefix*/).ToString() : link.TargetToken); return new ReplenishmentLinkBinding(link.LinkId, stationId, targetId, link.TargetToken, link.TargetPrefabHash, link.Revision); } internal static bool TryPrepareLegacyTargetRebind(ReplenishmentPlan plan, StoredProductionLink legacyLink, StoredProductionLink upgradedLink, out ReplenishmentPlan reboundPlan, out string encodedPlan) { reboundPlan = null; encodedPlan = string.Empty; if (plan == null || legacyLink == null || upgradedLink == null || !string.IsNullOrEmpty(legacyLink.TargetToken) || legacyLink.TargetPrefabHash != 0 || string.IsNullOrEmpty(upgradedLink.TargetToken) || !StockDomainValidation.IsWorldObjectToken(upgradedLink.TargetToken) || upgradedLink.TargetPrefabHash == 0 || ((ZDOID)(ref upgradedLink.Target)).IsNone() || !SameLinkEvidenceExceptTargetIdentity(legacyLink, upgradedLink) || !MatchesLink(plan, legacyLink, plan.Link.StationId)) { return false; } try { ReplenishmentLinkBinding link = new ReplenishmentLinkBinding(plan.Link.LinkId, plan.Link.StationId, upgradedLink.TargetToken, upgradedLink.TargetToken, upgradedLink.TargetPrefabHash, plan.Link.Revision); reboundPlan = new ReplenishmentPlan(plan.PlanId, plan.Revision, plan.Cursor, plan.StationPrefabId, plan.AdapterKind, link, plan.AuthorizedPlayerId, plan.AuthorizedPlayerName, plan.Targets); encodedPlan = Serialize(reboundPlan); return true; } catch { reboundPlan = null; encodedPlan = string.Empty; return false; } } private static bool SameLinkEvidenceExceptTargetIdentity(StoredProductionLink legacyLink, StoredProductionLink upgradedLink) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) if (legacyLink.Role == upgradedLink.Role && legacyLink.ExpectedPosition == upgradedLink.ExpectedPosition && legacyLink.OwnerId == upgradedLink.OwnerId && legacyLink.StationOwnerId == upgradedLink.StationOwnerId && legacyLink.TargetOwnerId == upgradedLink.TargetOwnerId && legacyLink.Revision == upgradedLink.Revision) { return string.Equals(legacyLink.LinkId, upgradedLink.LinkId, StringComparison.Ordinal); } return false; } private static StoredRecordState ReturnAbsent(out ReplenishmentPlan plan) { plan = null; return StoredRecordState.Absent; } private static void WriteLink(ZPackage package, ReplenishmentLinkBinding link) { package.Write(link.LinkId); package.Write(link.StationId); package.Write(link.TargetId); package.Write(link.TargetToken); package.Write(link.TargetPrefabHash); package.Write(link.Revision); } private static ReplenishmentLinkBinding ReadLink(ZPackage package, int schema) { string linkId = package.ReadString(); string stationId = package.ReadString(); string targetId = package.ReadString(); string targetToken = ((schema >= 2) ? package.ReadString() : string.Empty); int targetPrefabHash = ((schema >= 2) ? package.ReadInt() : 0); int revision = package.ReadInt(); return new ReplenishmentLinkBinding(linkId, stationId, targetId, targetToken, targetPrefabHash, revision); } private static bool TryDeriveLegacyPrincipal(IReadOnlyList targets, out long playerId, out string playerName) { playerId = 0L; playerName = string.Empty; if (targets == null || targets.Count == 0) { return false; } ReplenishmentTargetAuthorization replenishmentTargetAuthorization = targets[0]; if (replenishmentTargetAuthorization == null || replenishmentTargetAuthorization.AuthorizedPlayerId == 0L || string.IsNullOrWhiteSpace(replenishmentTargetAuthorization.AuthorizedPlayerName)) { return false; } for (int i = 1; i < targets.Count; i++) { ReplenishmentTargetAuthorization replenishmentTargetAuthorization2 = targets[i]; if (replenishmentTargetAuthorization2 == null || replenishmentTargetAuthorization2.AuthorizedPlayerId != replenishmentTargetAuthorization.AuthorizedPlayerId || !string.Equals(replenishmentTargetAuthorization2.AuthorizedPlayerName, replenishmentTargetAuthorization.AuthorizedPlayerName, StringComparison.Ordinal)) { return false; } } playerId = replenishmentTargetAuthorization.AuthorizedPlayerId; playerName = replenishmentTargetAuthorization.AuthorizedPlayerName; return true; } } internal sealed class StockSignatureWriter : IDisposable { private const int MaximumPayloadBytes = 65536; private const int MaximumStringBytes = 4096; private static readonly UTF8Encoding Utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); private readonly MemoryStream _stream = new MemoryStream(); private readonly BinaryWriter _writer; private bool _disposed; internal StockSignatureWriter() { _writer = new BinaryWriter(_stream, Utf8, leaveOpen: true); } internal void WriteString(string value) { EnsureUsable(); byte[] bytes = Utf8.GetBytes(value ?? string.Empty); if (bytes.Length > 4096) { throw new InvalidOperationException("A producer-signature string exceeds its bound."); } _writer.Write(bytes.Length); _writer.Write(bytes); EnsureBounded(); } internal void WriteInt32(int value) { EnsureUsable(); _writer.Write(value); EnsureBounded(); } internal void WriteInt64(long value) { EnsureUsable(); _writer.Write(value); EnsureBounded(); } internal void WriteBoolean(bool value) { EnsureUsable(); _writer.Write(value); EnsureBounded(); } internal void WriteSingle(float value) { WriteInt32(BitConverter.SingleToInt32Bits(value)); } internal byte[] ToArray() { EnsureUsable(); _writer.Flush(); EnsureBounded(); return _stream.ToArray(); } public void Dispose() { if (!_disposed) { _disposed = true; _writer.Dispose(); _stream.Dispose(); } } private void EnsureUsable() { if (_disposed) { throw new ObjectDisposedException("StockSignatureWriter"); } } private void EnsureBounded() { if (_stream.Length > 65536) { throw new InvalidOperationException("The producer-signature payload exceeds its bound."); } } } internal static class StockProducerSignature { internal const int SignatureBytes = 32; internal static byte[] Create(Action write) { if (write == null) { throw new ArgumentNullException("write"); } byte[] buffer; using (StockSignatureWriter stockSignatureWriter = new StockSignatureWriter()) { write(stockSignatureWriter); buffer = stockSignatureWriter.ToArray(); } using SHA256 sHA = SHA256.Create(); return sHA.ComputeHash(buffer); } internal static bool Matches(byte[] expected, byte[] actual) { if (expected == null || actual == null || expected.Length != 32 || actual.Length != 32) { return false; } int num = 0; for (int i = 0; i < 32; i++) { num |= expected[i] ^ actual[i]; } return num == 0; } } internal enum TimedCookingDestinationMode { None, Throughput, Replenishment, Invalid } internal static class TimedCookingDestinationPolicy { internal static TimedCookingDestinationMode Resolve(bool hasOutput, bool hasReplenishment) { if (!hasReplenishment) { if (!hasOutput) { return TimedCookingDestinationMode.None; } return TimedCookingDestinationMode.Throughput; } return TimedCookingDestinationMode.Replenishment; } internal static bool IsExactRefresh(StoredProductionLink current, ZDOID selectedTarget) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) if (current != null && current.Role == ProductionLinkRole.Replenishment) { return current.Target == selectedTarget; } return false; } } internal sealed class TimedCookingConversionDescriptor { internal int Index { get; } internal string InputPrefabId { get; } internal string OutputPrefabId { get; } internal float CookTime { get; } internal TimedCookingConversionDescriptor(int index, string inputPrefabId, string outputPrefabId, float cookTime) { Index = index; InputPrefabId = inputPrefabId; OutputPrefabId = outputPrefabId; CookTime = cookTime; } } internal sealed class TimedCookingStationDescriptor { private readonly IReadOnlyList _conversions; internal string PrefabId { get; } internal int SlotCount { get; } internal bool RequiresFire { get; } internal bool UsesFuel { get; } internal string FuelPrefabId { get; } internal int MaximumFuel { get; } internal int SecondsPerFuel { get; } internal string OvercookedPrefabId { get; } internal IReadOnlyList Conversions => _conversions; internal TimedCookingStationDescriptor(string prefabId, int slotCount, bool requiresFire, bool usesFuel, string fuelPrefabId, int maximumFuel, int secondsPerFuel, string overcookedPrefabId, IReadOnlyList conversions) { PrefabId = prefabId; SlotCount = slotCount; RequiresFire = requiresFire; UsesFuel = usesFuel; FuelPrefabId = fuelPrefabId ?? string.Empty; MaximumFuel = maximumFuel; SecondsPerFuel = secondsPerFuel; OvercookedPrefabId = overcookedPrefabId; _conversions = conversions ?? throw new ArgumentNullException("conversions"); } internal bool TryFromInput(string inputPrefabId, out TimedCookingConversionDescriptor conversion) { conversion = _conversions.FirstOrDefault((TimedCookingConversionDescriptor value) => string.Equals(value.InputPrefabId, inputPrefabId, StringComparison.Ordinal)); return conversion != null; } internal bool TryFromUniqueOutput(string outputPrefabId, out TimedCookingConversionDescriptor conversion) { conversion = null; int num = 0; foreach (TimedCookingConversionDescriptor conversion2 in _conversions) { if (string.Equals(conversion2.OutputPrefabId, outputPrefabId, StringComparison.Ordinal)) { conversion = conversion2; num++; if (num > 1) { conversion = null; return false; } } } return num == 1; } internal int OutputMatchCount(string outputPrefabId) { return _conversions.Count((TimedCookingConversionDescriptor value) => string.Equals(value.OutputPrefabId, outputPrefabId, StringComparison.Ordinal)); } } internal static class TimedCookingStockModel { internal static bool TryDescribe(CookingStation station, string prefabId, out TimedCookingStationDescriptor descriptor, out string failure) { descriptor = null; failure = string.Empty; if ((Object)(object)station == (Object)null || !StockDomainValidation.IsExactPrefabId(prefabId) || station.m_slots == null || station.m_conversion == null || (Object)(object)station.m_overCookedItem == (Object)null) { return Fail("Timed cooking producer state is unavailable.", out failure); } List list = new List(station.m_conversion.Count); for (int i = 0; i < station.m_conversion.Count; i++) { ItemConversion val = station.m_conversion[i]; string text = (((Object)(object)val?.m_from == (Object)null) ? string.Empty : ValheimAccess.PrefabName(((Component)val.m_from).gameObject)); string text2 = (((Object)(object)val?.m_to == (Object)null) ? string.Empty : ValheimAccess.PrefabName(((Component)val.m_to).gameObject)); if (!StockDomainValidation.IsExactPrefabId(text) || !StockDomainValidation.IsExactPrefabId(text2) || !(val.m_cookTime > 0f) || float.IsNaN(val.m_cookTime) || float.IsInfinity(val.m_cookTime)) { return Fail("Timed cooking conversion state is invalid.", out failure); } list.Add(new TimedCookingConversionDescriptor(i, text, text2, val.m_cookTime)); } string text3 = ((station.m_useFuel && (Object)(object)station.m_fuelItem != (Object)null) ? ValheimAccess.PrefabName(((Component)station.m_fuelItem).gameObject) : string.Empty); string text4 = ValheimAccess.PrefabName(((Component)station.m_overCookedItem).gameObject); if ((station.m_useFuel && !StockDomainValidation.IsExactPrefabId(text3)) || !StockDomainValidation.IsExactPrefabId(text4)) { return Fail("Timed cooking fuel/overcooked identity is invalid.", out failure); } descriptor = new TimedCookingStationDescriptor(prefabId, station.m_slots.Length, station.m_requireFire, station.m_useFuel, text3, station.m_maxFuel, station.m_secPerFuel, text4, list.AsReadOnly()); return true; } private static bool Fail(string value, out string failure) { failure = value; return false; } } internal static class TimedCookingProducerSignature { private const int SchemaVersion = 1; internal static string ProducerId(TimedCookingStationDescriptor station, TimedCookingConversionDescriptor conversion) { if (station == null) { throw new ArgumentNullException("station"); } if (conversion == null) { throw new ArgumentNullException("conversion"); } return "timed:" + station.PrefabId + ":" + conversion.Index; } internal static byte[] Create(TimedCookingStationDescriptor station, TimedCookingConversionDescriptor conversion) { if (station == null) { throw new ArgumentNullException("station"); } if (conversion == null) { throw new ArgumentNullException("conversion"); } return StockProducerSignature.Create(delegate(StockSignatureWriter writer) { writer.WriteString("runic.production.timed-cooking"); writer.WriteInt32(1); writer.WriteString(station.PrefabId); writer.WriteInt32(station.SlotCount); writer.WriteBoolean(station.RequiresFire); writer.WriteBoolean(station.UsesFuel); writer.WriteString(station.FuelPrefabId); writer.WriteInt32(station.MaximumFuel); writer.WriteInt32(station.SecondsPerFuel); writer.WriteString(station.OvercookedPrefabId); writer.WriteInt32(station.Conversions.Count); writer.WriteInt32(conversion.Index); writer.WriteString(conversion.InputPrefabId); writer.WriteString(conversion.OutputPrefabId); writer.WriteSingle(conversion.CookTime); }); } internal static string BurntProducerId(TimedCookingStationDescriptor station) { if (station == null) { throw new ArgumentNullException("station"); } return "timed-burnt:" + station.PrefabId; } internal static byte[] CreateBurnt(TimedCookingStationDescriptor station) { if (station == null) { throw new ArgumentNullException("station"); } return StockProducerSignature.Create(delegate(StockSignatureWriter writer) { writer.WriteString("runic.production.timed-cooking-burnt"); writer.WriteInt32(1); writer.WriteString(station.PrefabId); writer.WriteString(station.OvercookedPrefabId); writer.WriteInt32(station.SlotCount); writer.WriteBoolean(station.RequiresFire); writer.WriteBoolean(station.UsesFuel); }); } internal static string CompletionProducerId(TimedCookingStationDescriptor station, string outputPrefabId) { if (station == null) { throw new ArgumentNullException("station"); } return "timed-completion:" + station.PrefabId; } internal static byte[] CreateCompletion(TimedCookingStationDescriptor station, string outputPrefabId) { if (station == null) { throw new ArgumentNullException("station"); } return StockProducerSignature.Create(delegate(StockSignatureWriter writer) { writer.WriteString("runic.production.timed-cooking-completion"); writer.WriteInt32(1); writer.WriteString(station.PrefabId); writer.WriteString(outputPrefabId); writer.WriteInt32(station.OutputMatchCount(outputPrefabId)); foreach (TimedCookingConversionDescriptor conversion in station.Conversions) { if (string.Equals(conversion.OutputPrefabId, outputPrefabId, StringComparison.Ordinal)) { writer.WriteInt32(conversion.Index); writer.WriteString(conversion.InputPrefabId); writer.WriteSingle(conversion.CookTime); } } }); } } internal static class TimedCookingPlanBuilder { internal static bool TryBuild(TimedCookingStationDescriptor descriptor, Inventory replenishmentInventory, StoredProductionLink link, string stationId, long actorId, string actorName, ReplenishmentPlan previous, out ReplenishmentPlan plan, out string failure) { plan = null; failure = string.Empty; if (descriptor == null || replenishmentInventory == null || link == null || string.IsNullOrEmpty(stationId) || actorId == 0L || string.IsNullOrWhiteSpace(actorName)) { return Fail("Timed cooking plan inputs are unavailable.", out failure); } bool flag = previous != null && previous.AdapterKind == ReplenishmentProducerKind.TimedCooking && string.Equals(previous.StationPrefabId, descriptor.PrefabId, StringComparison.Ordinal) && ReplenishmentPlanStore.MatchesLink(previous, link, stationId); if (previous != null && !flag) { return Fail("The prior timed cooking plan is not bound to this exact station and link.", out failure); } long num = (flag ? previous.AuthorizedPlayerId : actorId); string text = (flag ? previous.AuthorizedPlayerName : actorName); if (num == 0L || num != link.OwnerId || string.IsNullOrWhiteSpace(text)) { return Fail("The timed cooking plan principal does not match the exact link owner.", out failure); } SortedSet sortedSet = new SortedSet(StringComparer.Ordinal); foreach (ItemData allItem in replenishmentInventory.GetAllItems()) { if (allItem == null || allItem.m_stack <= 0) { continue; } string text2 = ValheimAccess.PrefabName(allItem); int num2 = descriptor.OutputMatchCount(text2); if (num2 > 1) { return Fail("An exemplar is produced by more than one timed conversion; that target is ambiguous.", out failure); } if (num2 == 1) { sortedSet.Add(text2); if (sortedSet.Count > 32) { return Fail("The Replenishment chest contains too many timed cooking exemplars.", out failure); } } } List list = new List(sortedSet.Count); foreach (string item in sortedSet) { if (!descriptor.TryFromUniqueOutput(item, out var conversion)) { return Fail("A timed cooking exemplar became ambiguous.", out failure); } list.Add(new ReplenishmentTargetAuthorization(item, ReplenishmentProducerKind.TimedCooking, TimedCookingProducerSignature.ProducerId(descriptor, conversion), TimedCookingProducerSignature.Create(descriptor, conversion), 1, string.Empty, 0, num, text, new ReplenishmentRequirement[1] { new ReplenishmentRequirement(conversion.InputPrefabId, 1) })); } if (flag && previous.Revision == int.MaxValue) { return Fail("The Replenishment plan revision is exhausted; relink the destination.", out failure); } int cursor = 0; if (flag && previous.Targets.Count > 0 && list.Count > 0) { string current3 = previous.Targets[previous.Cursor].OutputPrefabId; int num3 = list.FindIndex((ReplenishmentTargetAuthorization value) => string.Equals(value.OutputPrefabId, current3, StringComparison.Ordinal)); if (num3 >= 0) { cursor = num3; } } plan = new ReplenishmentPlan(flag ? previous.PlanId : Guid.NewGuid().ToString("N"), NextRevision(previous, flag), cursor, descriptor.PrefabId, ReplenishmentProducerKind.TimedCooking, ReplenishmentPlanStore.Bind(link, stationId), num, text, list); return true; } internal static int NextRevision(ReplenishmentPlan previous, bool exactRefresh) { if (!exactRefresh) { return 1; } if (previous == null || previous.Revision == int.MaxValue) { throw new InvalidOperationException("A valid non-exhausted prior plan is required."); } return previous.Revision + 1; } internal static bool TryValidate(ReplenishmentPlan plan, TimedCookingStationDescriptor descriptor, StoredProductionLink link, string stationId, out string failure) { failure = string.Empty; if (plan == null || descriptor == null || link == null || plan.AdapterKind != ReplenishmentProducerKind.TimedCooking || !string.Equals(plan.StationPrefabId, descriptor.PrefabId, StringComparison.Ordinal) || !ReplenishmentPlanStore.MatchesLink(plan, link, stationId) || plan.AuthorizedPlayerId != link.OwnerId) { return Fail("The persisted timed cooking plan is not bound to this exact station/link.", out failure); } foreach (ReplenishmentTargetAuthorization target in plan.Targets) { if (!TryResolveAuthorized(descriptor, target, out var _, out failure)) { return false; } } return true; } internal static bool TryResolveAuthorized(TimedCookingStationDescriptor descriptor, ReplenishmentTargetAuthorization target, out TimedCookingConversionDescriptor conversion, out string failure) { conversion = null; failure = string.Empty; if (descriptor == null || target == null || target.ProducerKind != ReplenishmentProducerKind.TimedCooking || target.OutputAmount != 1 || target.Requirements.Count != 1 || target.Requirements[0].Amount != 1 || !descriptor.TryFromUniqueOutput(target.OutputPrefabId, out var conversion2) || !string.Equals(target.Requirements[0].PrefabId, conversion2.InputPrefabId, StringComparison.Ordinal) || !string.Equals(target.ProducerId, TimedCookingProducerSignature.ProducerId(descriptor, conversion2), StringComparison.Ordinal) || !target.ProducerSignatureMatches(TimedCookingProducerSignature.Create(descriptor, conversion2))) { return Fail("A signed timed cooking producer changed; explicitly refresh targets to authorize it again.", out failure); } conversion = conversion2; return true; } private static bool Fail(string value, out string failure) { failure = value; return false; } } internal enum CookingSlotStatus { NotDone, Done, Burnt } internal enum FermenterSlotStatus { Empty, Exposed, Fermenting, Ready } internal static class ValheimAccess { private delegate bool PlayerInputDelegate(Player player); private static readonly FieldRef SmelterView = AccessTools.FieldRefAccess("m_nview"); private static readonly FieldRef CookingView = AccessTools.FieldRefAccess("m_nview"); private static readonly FieldRef FermenterView = AccessTools.FieldRefAccess("m_nview"); private static readonly FieldRef FireplaceView = AccessTools.FieldRefAccess("m_nview"); private static readonly FieldRef FermenterHasRoof = AccessTools.FieldRefAccess("m_hasRoof"); private static readonly FieldRef FermenterExposed = AccessTools.FieldRefAccess("m_exposed"); private static readonly FieldRef ContainerView = AccessTools.FieldRefAccess("m_nview"); private static readonly MethodInfo QueueSizeMethod = RequiredMethod(typeof(Smelter), "GetQueueSize"); private static readonly MethodInfo QueueOreMethod = RequiredMethod(typeof(Smelter), "QueueOre", typeof(string)); private static readonly MethodInfo FuelMethod = RequiredMethod(typeof(Smelter), "GetFuel"); private static readonly MethodInfo SetFuelMethod = RequiredMethod(typeof(Smelter), "SetFuel", typeof(float)); private static readonly MethodInfo ConversionMethod = RequiredMethod(typeof(Smelter), "GetItemConversion", typeof(string)); private static readonly Type CookingStatusType = typeof(CookingStation).GetNestedType("Status", BindingFlags.NonPublic) ?? throw new MissingMemberException(typeof(CookingStation).FullName, "Status"); private static readonly MethodInfo CookingGetSlotMethod = RequiredMethod(typeof(CookingStation), "GetSlot", typeof(int), typeof(string).MakeByRefType(), typeof(float).MakeByRefType(), CookingStatusType.MakeByRefType()); private static readonly MethodInfo CookingSetSlotMethod = RequiredMethod(typeof(CookingStation), "SetSlot", typeof(int), typeof(string), typeof(float), CookingStatusType); private static readonly MethodInfo CookingGetFuelMethod = RequiredMethod(typeof(CookingStation), "GetFuel"); private static readonly MethodInfo CookingSetFuelMethod = RequiredMethod(typeof(CookingStation), "SetFuel", typeof(float)); private static readonly MethodInfo CookingIsFireLitMethod = RequiredMethod(typeof(CookingStation), "IsFireLit"); private static readonly MethodInfo FermenterUpdateCoverMethod = RequiredMethod(typeof(Fermenter), "UpdateCover", typeof(float), typeof(bool)); private static readonly MethodInfo FireplaceSetFuelMethod = RequiredMethod(typeof(Fireplace), "SetFuel", typeof(float)); private static readonly MethodInfo ContainerAccessMethod = RequiredMethod(typeof(Container), "CheckAccess", typeof(long)); private static readonly MethodInfo ContainerLoadMethod = RequiredMethod(typeof(Container), "Load"); private static readonly FieldInfo ContainerLoadingField = AccessTools.Field(typeof(Container), "m_loading") ?? throw new MissingFieldException(typeof(Container).FullName, "m_loading"); private static readonly MethodInfo PrivateAreaEnabledMethod = RequiredMethod(typeof(PrivateArea), "IsEnabled"); private static readonly MethodInfo PrivateAreaInsideMethod = RequiredMethod(typeof(PrivateArea), "IsInside", typeof(Vector3), typeof(float)); private static readonly MethodInfo PrivateAreaPermittedMethod = RequiredMethod(typeof(PrivateArea), "IsPermitted", typeof(long)); private static readonly FieldInfo AllPrivateAreasField = AccessTools.Field(typeof(PrivateArea), "m_allAreas") ?? throw new MissingFieldException(typeof(PrivateArea).FullName, "m_allAreas"); private static readonly PlayerInputDelegate PlayerTakeInput = ResolvePlayerTakeInput(); internal static void VerifySignatures() { _ = QueueSizeMethod; _ = SmelterView; _ = CookingView; _ = FermenterView; _ = FireplaceView; _ = ContainerView; _ = FermenterUpdateCoverMethod; _ = FireplaceSetFuelMethod; _ = ContainerLoadMethod; _ = AllPrivateAreasField; if (PlayerTakeInput == null) { throw new MissingMethodException(typeof(Player).FullName, "TakeInput"); } } internal static ZNetView View(Smelter station) { if (!((Object)(object)station == (Object)null)) { return SmelterView.Invoke(station); } return null; } internal static ZNetView View(CookingStation station) { if (!((Object)(object)station == (Object)null)) { return CookingView.Invoke(station); } return null; } internal static ZNetView View(Fermenter station) { if (!((Object)(object)station == (Object)null)) { return FermenterView.Invoke(station); } return null; } internal static ZNetView View(Fireplace station) { if (!((Object)(object)station == (Object)null)) { return FireplaceView.Invoke(station); } return null; } internal static ZNetView View(CraftingStation station) { if (!((Object)(object)station == (Object)null)) { return ((Component)station).GetComponent(); } return null; } internal static ZNetView View(Container container) { if ((Object)(object)container == (Object)null) { return null; } if (!((Object)(object)container.m_rootObjectOverride != (Object)null)) { return ContainerView.Invoke(container); } return container.m_rootObjectOverride; } internal static ZNetView View(Component component) { Smelter val = (Smelter)(object)((component is Smelter) ? component : null); if (val != null) { return View(val); } CookingStation val2 = (CookingStation)(object)((component is CookingStation) ? component : null); if (val2 != null) { return View(val2); } Fermenter val3 = (Fermenter)(object)((component is Fermenter) ? component : null); if (val3 != null) { return View(val3); } Fireplace val4 = (Fireplace)(object)((component is Fireplace) ? component : null); if (val4 != null) { return View(val4); } CraftingStation val5 = (CraftingStation)(object)((component is CraftingStation) ? component : null); if (val5 != null) { return View(val5); } Container val6 = (Container)(object)((component is Container) ? component : null); if (val6 != null) { return View(val6); } if (!((Object)(object)component == (Object)null)) { return component.GetComponentInParent(); } return null; } internal static ZDO Zdo(Smelter station) { ZNetView obj = View(station); if (obj == null) { return null; } return obj.GetZDO(); } internal static ZDO Zdo(CookingStation station) { ZNetView obj = View(station); if (obj == null) { return null; } return obj.GetZDO(); } internal static ZDO Zdo(Fermenter station) { ZNetView obj = View(station); if (obj == null) { return null; } return obj.GetZDO(); } internal static ZDO Zdo(Fireplace station) { ZNetView obj = View(station); if (obj == null) { return null; } return obj.GetZDO(); } internal static ZDO Zdo(CraftingStation station) { ZNetView obj = View(station); if (obj == null) { return null; } return obj.GetZDO(); } internal static ZDO Zdo(Container container) { ZNetView obj = View(container); if (obj == null) { return null; } return obj.GetZDO(); } internal static ZDO Zdo(Component component) { ZNetView obj = View(component); if (obj == null) { return null; } return obj.GetZDO(); } internal static bool IsNativeOwner(Component component) { ZNetView val = View(component); if ((Object)(object)val != (Object)null && val.IsValid() && val.IsOwner()) { return val.GetZDO() != null; } return false; } internal static bool IsFinite(Vector3 value) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: 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_0034: 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) if (!float.IsNaN(value.x) && !float.IsInfinity(value.x) && !float.IsNaN(value.y) && !float.IsInfinity(value.y) && !float.IsNaN(value.z)) { return !float.IsInfinity(value.z); } return false; } internal static string StableId(Component component) { if (!ProductionEndpointIdentity.TryGetOrEnsureToken(View(component), out var tokenValue)) { return string.Empty; } return tokenValue; } internal static bool TryGetCookingStationPrefab(CookingStation station, out string prefabId, out GameObject registeredPrefab) { prefabId = string.Empty; registeredPrefab = null; ZDO val = Zdo(station); if ((Object)(object)station == (Object)null || val == null || (Object)(object)ZNetScene.instance == (Object)null) { return false; } registeredPrefab = ZNetScene.instance.GetPrefab(val.GetPrefab()); if ((Object)(object)registeredPrefab == (Object)null || (Object)(object)registeredPrefab.GetComponent() == (Object)null) { return false; } prefabId = PrefabName(registeredPrefab); return !string.IsNullOrEmpty(prefabId); } internal static bool TryGetFermenterPrefab(Fermenter station, out string prefabId, out GameObject registeredPrefab) { prefabId = string.Empty; registeredPrefab = null; ZDO val = Zdo(station); if ((Object)(object)station == (Object)null || val == null || (Object)(object)ZNetScene.instance == (Object)null) { return false; } registeredPrefab = ZNetScene.instance.GetPrefab(val.GetPrefab()); if ((Object)(object)registeredPrefab == (Object)null || (Object)(object)registeredPrefab.GetComponent() == (Object)null) { return false; } prefabId = PrefabName(registeredPrefab); return !string.IsNullOrEmpty(prefabId); } internal static GameObject RegisteredItemPrefab(string prefabId) { if (string.IsNullOrEmpty(prefabId)) { return null; } ObjectDB instance = ObjectDB.instance; GameObject val = ((instance != null) ? instance.GetItemPrefab(prefabId) : null); if (!((Object)(object)val != (Object)null)) { ZNetScene instance2 = ZNetScene.instance; if (instance2 == null) { return null; } return instance2.GetPrefab(prefabId); } return val; } internal static int QueueSize(Smelter station) { return (int)QueueSizeMethod.Invoke(station, null); } internal static void QueueOre(Smelter station, string prefabName) { QueueOreMethod.Invoke(station, new object[1] { prefabName }); } internal static float Fuel(Smelter station) { return (float)FuelMethod.Invoke(station, null); } internal static void SetFuel(Smelter station, float value) { SetFuelMethod.Invoke(station, new object[1] { value }); } internal static ItemConversion Conversion(Smelter station, string inputPrefab) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown return (ItemConversion)ConversionMethod.Invoke(station, new object[1] { inputPrefab }); } internal static string QueueTail(Smelter station, int index) { ZDO obj = Zdo(station); return ((obj != null) ? obj.GetString("item" + index, string.Empty) : null) ?? string.Empty; } internal static void RestoreQueueTail(Smelter station, int previousCount, string previousValue) { ZDO obj = Zdo(station) ?? throw new InvalidOperationException("Smelter ZDO is unavailable."); obj.Set("item" + previousCount, previousValue ?? string.Empty); obj.Set(ZDOVars.s_queued, Math.Max(0, previousCount), false); } internal static void GetCookingSlot(CookingStation station, int slot, out string item, out float elapsed, out CookingSlotStatus status) { object[] array = new object[4] { slot, string.Empty, 0f, Enum.ToObject(CookingStatusType, 0) }; CookingGetSlotMethod.Invoke(station, array); item = (array[1] as string) ?? string.Empty; elapsed = (float)array[2]; status = (CookingSlotStatus)Convert.ToInt32(array[3]); } internal static void SetCookingSlot(CookingStation station, int slot, string item, float elapsed, CookingSlotStatus status) { CookingSetSlotMethod.Invoke(station, new object[4] { slot, item ?? string.Empty, elapsed, Enum.ToObject(CookingStatusType, (int)status) }); ZNetView val = View(station); if ((Object)(object)val != (Object)null && val.IsValid()) { val.InvokeRPC(ZNetView.Everybody, "RPC_SetSlotVisual", new object[2] { slot, item ?? string.Empty }); } } internal static float CookingFuel(CookingStation station) { return (float)CookingGetFuelMethod.Invoke(station, null); } internal static void SetCookingFuel(CookingStation station, float value) { CookingSetFuelMethod.Invoke(station, new object[1] { value }); } internal static bool CookingFireLit(CookingStation station) { return (bool)CookingIsFireLitMethod.Invoke(station, null); } internal static string FermenterContent(Fermenter station) { ZDO obj = Zdo(station); return ((obj != null) ? obj.GetString(ZDOVars.s_content, string.Empty) : null) ?? string.Empty; } internal static long FermenterStartTicks(Fermenter station) { ZDO obj = Zdo(station); if (obj == null) { return 0L; } return obj.GetLong(ZDOVars.s_startTime, 0L); } internal static void SetFermenterState(Fermenter station, string content, long startTicks) { ZDO val = Zdo(station) ?? throw new InvalidOperationException("Fermenter ZDO is unavailable."); string text = content ?? string.Empty; if (text.Length == 0) { val.Set(ZDOVars.s_content, string.Empty); val.Set(ZDOVars.s_startTime, 0L); return; } if (startTicks <= 0) { throw new ArgumentOutOfRangeException("startTicks"); } val.Set(ZDOVars.s_content, text); val.Set(ZDOVars.s_startTime, startTicks); } internal static bool FermenterCovered(Fermenter station) { if ((Object)(object)station != (Object)null && FermenterHasRoof.Invoke(station)) { return !FermenterExposed.Invoke(station); } return false; } internal static void RefreshFermenterCover(Fermenter station) { if ((Object)(object)station == (Object)null) { throw new ArgumentNullException("station"); } FermenterUpdateCoverMethod.Invoke(station, new object[2] { 0f, true }); } internal static bool FermenterDelayedTapActive(Fermenter station) { if ((Object)(object)station != (Object)null) { return ((MonoBehaviour)station).IsInvoking("DelayedTap"); } return false; } internal static FermenterSlotStatus FermenterStatus(Fermenter station) { if ((Object)(object)station == (Object)null) { return FermenterSlotStatus.Empty; } if (string.IsNullOrEmpty(FermenterContent(station))) { return FermenterSlotStatus.Empty; } long num = FermenterStartTicks(station); if (num <= 0 || (Object)(object)ZNet.instance == (Object)null) { return FermenterSlotStatus.Exposed; } try { return ((ZNet.instance.GetTime() - new DateTime(num)).TotalSeconds > (double)station.m_fermentationDuration) ? FermenterSlotStatus.Ready : FermenterSlotStatus.Fermenting; } catch (ArgumentOutOfRangeException) { return FermenterSlotStatus.Exposed; } } internal static long NetworkTimeTicks() { if ((Object)(object)ZNet.instance == (Object)null) { throw new InvalidOperationException("Network time is unavailable."); } return ZNet.instance.GetTime().Ticks; } internal static float FireplaceFuel(Fireplace station) { ZDO obj = Zdo(station); if (obj == null) { return 0f; } return obj.GetFloat(ZDOVars.s_fuel, 0f); } internal static void SetFireplaceFuel(Fireplace station, float value) { FireplaceSetFuelMethod.Invoke(station, new object[1] { value }); } internal static bool TryResolveExactContainer(GameObject root, ZDOID expected, out Container container) { //IL_0050: 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) container = null; if ((Object)(object)root == (Object)null || ((ZDOID)(ref expected)).IsNone()) { return false; } Container[] componentsInChildren = root.GetComponentsInChildren(true); if (componentsInChildren == null || componentsInChildren.Length == 0 || componentsInChildren.Length > 64) { return false; } Container[] array = componentsInChildren; foreach (Container val in array) { ZDO val2 = Zdo(val); if (!((Object)(object)val == (Object)null) && val2 != null && !(val2.m_uid != expected)) { if ((Object)(object)container != (Object)null && container != val) { container = null; return false; } container = val; } } return (Object)(object)container != (Object)null; } internal static bool TrySynchronizeLocallyOwnedContainer(Container container, out Inventory inventory) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: 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) //IL_00f8: Expected O, but got Unknown inventory = null; if ((Object)(object)container == (Object)null || !ContainerWritable(container)) { return false; } ZNetView val = View(container); ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid() && val.IsOwner()) ? val.GetZDO() : null); if (val2 == null) { return false; } ZDOID uid = val2.m_uid; try { ContainerLoadMethod.Invoke(container, null); } catch { return false; } ZNetView val3 = View(container); if ((Object)(object)val3 == (Object)null || !val3.IsValid() || !val3.IsOwner() || val3.GetZDO() == null || val3.GetZDO().m_uid != uid || (bool)ContainerLoadingField.GetValue(container)) { return false; } inventory = container.GetInventory(); if (inventory == null) { return false; } string text = val3.GetZDO().GetString(ZDOVars.s_items, string.Empty); if (string.IsNullOrEmpty(text)) { return inventory.GetAllItems().Count == 0; } ZPackage val4 = new ZPackage(); inventory.Save(val4); return string.Equals(val4.GetBase64(), text, StringComparison.Ordinal); } internal static bool ContainerAllows(Container container, long playerId) { if ((Object)(object)container == (Object)null || playerId == 0L) { return false; } try { return (bool)ContainerAccessMethod.Invoke(container, new object[1] { playerId }); } catch { return false; } } internal static bool ContainerWritable(Container container) { if ((Object)(object)container != (Object)null && !container.IsInUse()) { if (!((Object)(object)container.m_wagon == (Object)null)) { return !container.m_wagon.InUse(); } return true; } return false; } internal static bool ContainerWithinReach(Container container, Vector3 actorPosition, float interactionRange) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: 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_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)container == (Object)null || interactionRange <= 0f || !IsFinite(actorPosition)) { return false; } float num = interactionRange * interactionRange; Collider[] componentsInChildren = ((Component)container).GetComponentsInChildren(true); if (componentsInChildren == null || componentsInChildren.Length > 64) { return false; } bool flag = false; Collider[] array = componentsInChildren; Vector3 val3; foreach (Collider val in array) { if ((Object)(object)val == (Object)null || !val.enabled) { continue; } flag = true; Vector3 val2 = val.ClosestPoint(actorPosition); if (IsFinite(val2)) { val3 = actorPosition - val2; if (((Vector3)(ref val3)).sqrMagnitude <= num) { return true; } } } if (!flag && IsFinite(((Component)container).transform.position)) { val3 = actorPosition - ((Component)container).transform.position; return ((Vector3)(ref val3)).sqrMagnitude <= num; } return false; } internal static bool WardAllows(Vector3 position, long playerId) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) if (!IsFinite(position) || playerId == 0L) { return false; } if (!(AllPrivateAreasField.GetValue(null) is List list)) { return false; } bool flag = false; bool flag2 = false; bool flag3 = false; foreach (PrivateArea item in list) { if (!((Object)(object)item == (Object)null) && (bool)PrivateAreaEnabledMethod.Invoke(item, null) && (bool)PrivateAreaInsideMethod.Invoke(item, new object[2] { position, 0f })) { flag = true; Piece component = ((Component)item).GetComponent(); long num = (((Object)(object)component == (Object)null) ? 0 : component.GetCreator()); if (num == 0L) { return false; } bool flag4 = num == playerId || (bool)PrivateAreaPermittedMethod.Invoke(item, new object[1] { playerId }); flag2 = flag2 || flag4; flag3 = flag3 || !flag4; } } if (flag) { if (flag2) { return !flag3; } return false; } return true; } internal static long Creator(Component component) { Piece val = (((Object)(object)component == (Object)null) ? null : component.GetComponentInParent()); if (!((Object)(object)val == (Object)null)) { return val.GetCreator(); } return 0L; } internal static string PrefabName(ItemData item) { if (!((Object)(object)item?.m_dropPrefab == (Object)null)) { return PrefabName(item.m_dropPrefab); } return string.Empty; } internal static string PrefabName(GameObject prefab) { if ((Object)(object)prefab == (Object)null) { return string.Empty; } string text = ((Object)prefab).name ?? string.Empty; if (!text.EndsWith("(Clone)", StringComparison.Ordinal)) { return text; } return text.Substring(0, text.Length - "(Clone)".Length); } internal static bool KeyboardKeyHeld(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) try { return ZInput.GetKey(key, false); } catch { return false; } } internal static bool PlayerTakesInput(Player player) { if ((Object)(object)player != (Object)null && PlayerTakeInput != null) { return PlayerTakeInput(player); } return false; } internal static bool RawControllerAlternateUseHeld() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Invalid comparison between Unknown and I4 try { ZInput instance = ZInput.instance; ButtonDef val = ((instance != null) ? instance.GetButtonDef("JoyAltKeys") : null); return val != null && (int)val.Source == 180 && val.Held; } catch { return false; } } internal static bool RawControllerButtonHeld(string action) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Invalid comparison between Unknown and I4 try { ZInput instance = ZInput.instance; ButtonDef val = ((instance != null) ? instance.GetButtonDef(action) : null); return val != null && (int)val.Source == 180 && val.Held; } catch { return false; } } internal static void Message(Player player, string text) { if ((Object)(object)player != (Object)null && !string.IsNullOrEmpty(text)) { ((Character)player).Message((MessageType)2, text, 0, (Sprite)null); } } private static MethodInfo RequiredMethod(Type type, string name, params Type[] parameters) { MethodInfo methodInfo = AccessTools.Method(type, name, parameters, (Type[])null); if (methodInfo == null) { throw new MissingMethodException(type.FullName, name); } return methodInfo; } private static PlayerInputDelegate ResolvePlayerTakeInput() { try { MethodInfo methodInfo = AccessTools.Method(typeof(Player), "TakeInput", Type.EmptyTypes, (Type[])null); return (methodInfo == null) ? null : AccessTools.MethodDelegate(methodInfo, (object)null, true); } catch { return null; } } } } namespace RunicProduction.Core { internal sealed class CookingStationPrefabPolicy { private const int MaximumEntries = 128; private const int MaximumIdentifierCharacters = 128; private readonly HashSet _allowed; private readonly HashSet _denied; internal int AllowedCount => _allowed.Count; internal int DeniedCount => _denied.Count; private CookingStationPrefabPolicy(HashSet allowed, HashSet denied) { _allowed = allowed; _denied = denied; } internal bool Allows(string prefabId) { if (IsValidIdentifier(prefabId) && _allowed.Contains(prefabId)) { return !_denied.Contains(prefabId); } return false; } internal static bool TryParse(string allowText, string denyText, out CookingStationPrefabPolicy policy, out string failure) { policy = null; failure = string.Empty; if (!TryParseSet(allowText, "allow", out var values, out failure) || !TryParseSet(denyText, "deny", out var values2, out failure)) { return false; } policy = new CookingStationPrefabPolicy(values, values2); return true; } private static bool TryParseSet(string text, string label, out HashSet values, out string failure) { values = new HashSet(StringComparer.Ordinal); failure = string.Empty; string[] array = (text ?? string.Empty).Split(new char[4] { ',', ';', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length > 128) { failure = $"The CookingStation {label} list exceeds {128} entries."; return false; } string[] array2 = array; for (int i = 0; i < array2.Length; i++) { string text2 = array2[i].Trim(); if (!IsValidIdentifier(text2)) { failure = "The CookingStation " + label + " list contains an invalid exact prefab ID."; return false; } values.Add(text2); } return true; } private static bool IsValidIdentifier(string value) { if (string.IsNullOrEmpty(value) || value.Length > 128) { return false; } foreach (char c in value) { if (!char.IsLetterOrDigit(c) && c != '_' && c != '-' && c != '.') { return false; } } return true; } } internal static class ExplicitLinkCommitPolicy { internal static bool Allows(bool hasMatchingUnexpiredServerSelection, bool actorCanReachSelectedContainer, bool stationToContainerWithinConfiguredRange) { return hasMatchingUnexpiredServerSelection && actorCanReachSelectedContainer && stationToContainerWithinConfiguredRange; } } internal sealed class FermenterPrefabPolicy { private const int MaximumEntries = 128; private readonly HashSet _allowed; private readonly HashSet _denied; internal int AllowedCount => _allowed.Count; internal int DeniedCount => _denied.Count; private FermenterPrefabPolicy(HashSet allowed, HashSet denied) { _allowed = allowed; _denied = denied; } internal bool Allows(string prefabId) { if (StockDomainValidation.IsExactPrefabId(prefabId) && _allowed.Contains(prefabId)) { return !_denied.Contains(prefabId); } return false; } internal static bool TryParse(string allowText, string denyText, out FermenterPrefabPolicy policy, out string failure) { policy = null; failure = string.Empty; if (!TryParseSet(allowText, "allow", out var values, out failure) || !TryParseSet(denyText, "deny", out var values2, out failure)) { return false; } policy = new FermenterPrefabPolicy(values, values2); return true; } private static bool TryParseSet(string text, string label, out HashSet values, out string failure) { values = new HashSet(StringComparer.Ordinal); failure = string.Empty; string[] array = (text ?? string.Empty).Split(new char[4] { ',', ';', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length > 128) { failure = $"The Fermenter {label} list exceeds {128} entries."; return false; } string[] array2 = array; for (int i = 0; i < array2.Length; i++) { string text2 = array2[i].Trim(); if (!StockDomainValidation.IsExactPrefabId(text2)) { failure = "The Fermenter " + label + " list contains an invalid exact prefab ID."; return false; } values.Add(text2); } return true; } } internal sealed class IngredientReservePolicy { internal const int MaximumRules = 128; internal const int MaximumReserveAmount = 1000000; private const int MaximumConfigurationCharacters = 32768; private readonly Dictionary _protectedAmounts; internal bool IsValid { get; } internal int DefaultProtectedReserve { get; } internal int RuleCount => _protectedAmounts.Count; private IngredientReservePolicy(bool isValid, int defaultProtectedReserve, Dictionary protectedAmounts) { IsValid = isValid; DefaultProtectedReserve = defaultProtectedReserve; _protectedAmounts = protectedAmounts; } internal static bool TryParse(int defaultProtectedReserve, string protectedPrefabAmounts, out IngredientReservePolicy policy, out string failure) { policy = FailClosed(); failure = string.Empty; if (defaultProtectedReserve < 0 || defaultProtectedReserve > 1000000) { failure = $"DefaultProtectedReserve must be between 0 and {1000000}."; return false; } string text = protectedPrefabAmounts ?? string.Empty; if (text.Length > 32768) { failure = "The ingredient reserve configuration is too long."; return false; } Dictionary dictionary = new Dictionary(StringComparer.Ordinal); if (!string.IsNullOrWhiteSpace(text)) { string[] array = text.Split(new char[1] { ';' }, StringSplitOptions.None); if (array.Length > 128) { failure = $"The ingredient reserve configuration exceeds {128} rules."; return false; } string[] array2 = array; foreach (string text2 in array2) { if (string.IsNullOrWhiteSpace(text2)) { failure = "The ingredient reserve configuration contains an empty rule."; return false; } int num = text2.IndexOf('='); if (num <= 0 || num != text2.LastIndexOf('=')) { failure = "Each ingredient reserve rule must contain exactly one '=' separator."; return false; } string text3 = text2.Substring(0, num).Trim(); string value = text2.Substring(num + 1).Trim(); if (!StockDomainValidation.IsExactPrefabId(text3)) { failure = "The ingredient reserve configuration contains an invalid exact prefab ID."; return false; } if (!TryParseAmount(value, out var amount)) { failure = $"Ingredient reserve amounts must be decimal integers between 0 and {1000000}."; return false; } if (dictionary.ContainsKey(text3)) { failure = "The ingredient reserve configuration contains a duplicate exact prefab ID."; return false; } dictionary.Add(text3, amount); } } policy = new IngredientReservePolicy(isValid: true, defaultProtectedReserve, dictionary); return true; } internal bool TryGetProtectedReserve(string prefabId, out int protectedReserve) { protectedReserve = 0; if (!IsValid || !StockDomainValidation.IsExactPrefabId(prefabId)) { return false; } protectedReserve = (_protectedAmounts.TryGetValue(prefabId, out var value) ? value : DefaultProtectedReserve); return true; } internal long AvailableAboveReserve(string prefabId, long currentAmount) { if (currentAmount < 0 || !TryGetProtectedReserve(prefabId, out var protectedReserve)) { return 0L; } long num = currentAmount - protectedReserve; if (num <= 0) { return 0L; } return num; } internal bool AllowsConsumption(string prefabId, long currentAmount, long requestedAmount) { if (requestedAmount > 0) { return AvailableAboveReserve(prefabId, currentAmount) >= requestedAmount; } return false; } private static IngredientReservePolicy FailClosed() { return new IngredientReservePolicy(isValid: false, 1000000, new Dictionary(StringComparer.Ordinal)); } private static bool TryParseAmount(string value, out int amount) { amount = 0; if (string.IsNullOrEmpty(value)) { return false; } long num = 0L; foreach (char c in value) { if (c < '0' || c > '9') { return false; } num = num * 10 + (c - 48); if (num > 1000000) { return false; } } amount = (int)num; return true; } } internal enum ReplenishmentDestinationState { Active = 1, Draining, Faulted, NeedsRefresh } internal enum ReplenishmentCatalogChangeCode { Applied, NoChange, SoftLimitReached, HardLimitReached, TargetAuthorizationLimitReached, DuplicateLinkId, DuplicateTarget, LinkNotFound, LinkIdentityChanged, RevisionExhausted, OrdinalExhausted } internal sealed class ReplenishmentDestinationRecord { private readonly StoredProductionLink _link; internal int Slot { get; } internal long Ordinal { get; } internal int RecordRevision { get; } internal ReplenishmentDestinationState State { get; } internal ProductionStopCode FaultCode { get; } internal string LinkId => _link.LinkId; internal ReplenishmentPlan Plan { get; } internal StoredProductionLink Link => CopyLink(_link); internal ReplenishmentDestinationRecord(int slot, long ordinal, int recordRevision, ReplenishmentDestinationState state, ProductionStopCode faultCode, StoredProductionLink link, ReplenishmentPlan plan) { if (slot < 0 || slot >= 16) { throw new ArgumentOutOfRangeException("slot"); } if (ordinal < 1) { throw new ArgumentOutOfRangeException("ordinal"); } if (recordRevision < 1) { throw new ArgumentOutOfRangeException("recordRevision"); } if (!Enum.IsDefined(typeof(ReplenishmentDestinationState), state)) { throw new ArgumentOutOfRangeException("state"); } if (!Enum.IsDefined(typeof(ProductionStopCode), faultCode)) { throw new ArgumentOutOfRangeException("faultCode"); } if (state == ReplenishmentDestinationState.Faulted) { if (faultCode == ProductionStopCode.Ready) { throw new ArgumentException("A faulted destination requires an exact non-ready stop code.", "faultCode"); } } else if (faultCode != ProductionStopCode.Ready) { throw new ArgumentException("Only a faulted destination may carry a stop code.", "faultCode"); } StoredProductionLink storedProductionLink = CopyAndValidateLink(link, "link"); Plan = plan ?? throw new ArgumentNullException("plan"); if (!string.Equals(plan.Link.LinkId, storedProductionLink.LinkId, StringComparison.Ordinal) || !PlanMatchesTarget(plan.Link, storedProductionLink) || plan.Link.Revision != storedProductionLink.Revision || plan.AuthorizedPlayerId != storedProductionLink.OwnerId) { throw new ArgumentException("The replenishment plan must be bound to the exact stored link revision and owner.", "plan"); } Slot = slot; Ordinal = ordinal; RecordRevision = recordRevision; State = state; FaultCode = faultCode; _link = storedProductionLink; } internal ReplenishmentDestinationRecord With(int nextRecordRevision, ReplenishmentDestinationState state, ProductionStopCode faultCode, StoredProductionLink link, ReplenishmentPlan plan) { return new ReplenishmentDestinationRecord(Slot, Ordinal, nextRecordRevision, state, faultCode, link, plan); } internal static bool SameExactLink(StoredProductionLink left, StoredProductionLink right) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) if (left != null && right != null && left.Role == right.Role && SameTargetIdentity(left, right) && left.ExpectedPosition == right.ExpectedPosition && left.OwnerId == right.OwnerId && left.StationOwnerId == right.StationOwnerId && left.TargetOwnerId == right.TargetOwnerId && left.Revision == right.Revision && string.Equals(left.TargetToken, right.TargetToken, StringComparison.Ordinal)) { return string.Equals(left.LinkId, right.LinkId, StringComparison.Ordinal); } return false; } internal static bool SameTargetIdentity(StoredProductionLink left, StoredProductionLink right) { return MultiReplenishmentCatalogPolicy.SameTargetIdentity(left, right); } internal static StoredProductionLink CopyLink(StoredProductionLink source) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) if (source != null) { return new StoredProductionLink { LinkId = source.LinkId, Role = source.Role, TargetToken = source.TargetToken, TargetPrefabHash = source.TargetPrefabHash, Target = source.Target, ExpectedPosition = source.ExpectedPosition, OwnerId = source.OwnerId, StationOwnerId = source.StationOwnerId, TargetOwnerId = source.TargetOwnerId, Revision = source.Revision }; } return null; } private static StoredProductionLink CopyAndValidateLink(StoredProductionLink source, string parameterName) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) if (source == null) { throw new ArgumentNullException(parameterName); } if (source.Role != ProductionLinkRole.Replenishment || ((ZDOID)(ref source.Target)).IsNone() || source.Revision < 1 || !StockDomainValidation.IsLegacyOrWorldObjectIdentity(source.TargetToken, source.TargetPrefabHash) || !IsStableText(source.LinkId, 200) || !IsFinite(source.ExpectedPosition)) { throw new ArgumentException("An exact, finite replenishment link is required.", parameterName); } return CopyLink(source); } private static bool PlanMatchesTarget(ReplenishmentLinkBinding binding, StoredProductionLink link) { if (string.IsNullOrEmpty(binding.TargetToken)) { return string.Equals(binding.TargetId, ((object)Unsafe.As(ref link.Target)/*cast due to .constrained prefix*/).ToString(), StringComparison.Ordinal); } if (binding.TargetPrefabHash == link.TargetPrefabHash && string.Equals(binding.TargetToken, link.TargetToken, StringComparison.Ordinal)) { return string.Equals(binding.TargetId, link.TargetToken, StringComparison.Ordinal); } return false; } private static bool IsStableText(string value, int maximum) { if (string.IsNullOrWhiteSpace(value) || value.Length > maximum) { return false; } for (int i = 0; i < value.Length; i++) { if (char.IsControl(value[i])) { return false; } } return true; } private static bool IsFinite(Vector3 value) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: 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_0034: 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) if (!float.IsNaN(value.x) && !float.IsInfinity(value.x) && !float.IsNaN(value.y) && !float.IsInfinity(value.y) && !float.IsNaN(value.z)) { return !float.IsInfinity(value.z); } return false; } } internal sealed class MultiReplenishmentCatalog { internal const int HardMaximumDestinations = 16; internal const int MaximumTargetAuthorizations = 32; private readonly ReadOnlyCollection _destinations; internal string CatalogId { get; } internal string StationId { get; } internal int Revision { get; } internal long NextOrdinal { get; } internal long DestinationCursorOrdinal { get; } internal int TargetAuthorizationCount { get; } internal IReadOnlyList Destinations => _destinations; internal MultiReplenishmentCatalog(string catalogId, string stationId, int revision, long nextOrdinal, long destinationCursorOrdinal, IEnumerable destinations) { //IL_016e: Unknown result type (might be due to invalid IL or missing references) CatalogId = RequireStableText(catalogId, "catalogId", 200); StationId = RequireStableText(stationId, "stationId", 200); if (revision < 1) { throw new ArgumentOutOfRangeException("revision"); } if (nextOrdinal < 1) { throw new ArgumentOutOfRangeException("nextOrdinal"); } List list = new List(); if (destinations != null) { foreach (ReplenishmentDestinationRecord destination in destinations) { if (destination == null) { throw new ArgumentException("Catalog destinations cannot contain null records.", "destinations"); } list.Add(destination); if (list.Count > 16) { throw new ArgumentOutOfRangeException("destinations"); } } } list.Sort((ReplenishmentDestinationRecord left, ReplenishmentDestinationRecord right) => left.Ordinal.CompareTo(right.Ordinal)); HashSet hashSet = new HashSet(); HashSet hashSet2 = new HashSet(); HashSet hashSet3 = new HashSet(StringComparer.Ordinal); HashSet hashSet4 = new HashSet(); HashSet hashSet5 = new HashSet(StringComparer.Ordinal); int num = 0; long num2 = 0L; foreach (ReplenishmentDestinationRecord item in list) { StoredProductionLink link = item.Link; if (!hashSet.Add(item.Slot) || !hashSet2.Add(item.Ordinal) || !hashSet3.Add(item.LinkId)) { throw new ArgumentException("Slots, ordinals, and link IDs must be unique within a station catalog.", "destinations"); } if (!hashSet4.Add(link.Target)) { throw new ArgumentException("A target chest may appear at most once in one station catalog.", "destinations"); } if (!string.IsNullOrEmpty(link.TargetToken) && !hashSet5.Add(link.TargetToken)) { throw new ArgumentException("A target chest may appear at most once in one station catalog.", "destinations"); } if (!string.Equals(item.Plan.Link.StationId, StationId, StringComparison.Ordinal)) { throw new ArgumentException("Every destination plan must be bound to this exact station.", "destinations"); } num = checked(num + item.Plan.Targets.Count); if (num > 32) { throw new ArgumentOutOfRangeException("destinations"); } num2 = Math.Max(num2, item.Ordinal); } if (nextOrdinal <= num2) { throw new ArgumentOutOfRangeException("nextOrdinal", "The next ordinal must be greater than every destination ordinal."); } if (list.Count == 0) { if (destinationCursorOrdinal != 0L) { throw new ArgumentOutOfRangeException("destinationCursorOrdinal"); } } else if (destinationCursorOrdinal != 0L && !hashSet2.Contains(destinationCursorOrdinal)) { throw new ArgumentOutOfRangeException("destinationCursorOrdinal"); } Revision = revision; NextOrdinal = nextOrdinal; DestinationCursorOrdinal = destinationCursorOrdinal; TargetAuthorizationCount = num; _destinations = list.AsReadOnly(); } internal bool TryGetByLinkId(string linkId, out ReplenishmentDestinationRecord destination) { foreach (ReplenishmentDestinationRecord destination2 in _destinations) { if (string.Equals(destination2.LinkId, linkId, StringComparison.Ordinal)) { destination = destination2; return true; } } destination = null; return false; } internal bool TryGetBySlot(int slot, out ReplenishmentDestinationRecord destination) { foreach (ReplenishmentDestinationRecord destination2 in _destinations) { if (destination2.Slot == slot) { destination = destination2; return true; } } destination = null; return false; } internal bool TryGetByTarget(ZDOID target, out ReplenishmentDestinationRecord destination) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) foreach (ReplenishmentDestinationRecord destination2 in _destinations) { if (destination2.Link.Target == target) { destination = destination2; return true; } } destination = null; return false; } internal bool TryGetByTargetIdentity(StoredProductionLink link, out ReplenishmentDestinationRecord destination) { if (link != null) { foreach (ReplenishmentDestinationRecord destination2 in _destinations) { if (MultiReplenishmentCatalogPolicy.SameTargetIdentity(destination2.Link, link)) { destination = destination2; return true; } } } destination = null; return false; } internal bool TryGetByOrdinal(long ordinal, out ReplenishmentDestinationRecord destination) { foreach (ReplenishmentDestinationRecord destination2 in _destinations) { if (destination2.Ordinal == ordinal) { destination = destination2; return true; } } destination = null; return false; } internal IReadOnlyList ActiveRoundRobinOrder() { List list = new List(); foreach (ReplenishmentDestinationRecord destination in _destinations) { if (destination.State == ReplenishmentDestinationState.Active) { list.Add(destination); } } if (list.Count < 2 || DestinationCursorOrdinal == 0L) { return list.AsReadOnly(); } int num = list.FindIndex((ReplenishmentDestinationRecord destination) => destination.Ordinal >= DestinationCursorOrdinal); if (num <= 0) { return list.AsReadOnly(); } List list2 = new List(list.Count); for (int num2 = 0; num2 < list.Count; num2++) { list2.Add(list[(num + num2) % list.Count]); } return list2.AsReadOnly(); } private static string RequireStableText(string value, string parameterName, int maximum) { if (string.IsNullOrWhiteSpace(value)) { throw new ArgumentException("A stable identifier is required.", parameterName); } if (value.Length > maximum) { throw new ArgumentOutOfRangeException(parameterName); } for (int i = 0; i < value.Length; i++) { if (char.IsControl(value[i])) { throw new ArgumentException("Stable identifiers cannot contain control characters.", parameterName); } } return value; } } internal static class MultiReplenishmentCatalogPolicy { internal static MultiReplenishmentCatalog CreateEmpty(string catalogId, string stationId) { return new MultiReplenishmentCatalog(catalogId, stationId, 1, 1L, 0L, Array.Empty()); } internal static bool TryAdd(MultiReplenishmentCatalog catalog, StoredProductionLink link, ReplenishmentPlan plan, int softMaximumDestinations, out MultiReplenishmentCatalog updated, out ReplenishmentDestinationRecord added, out ReplenishmentCatalogChangeCode code) { RequireCatalog(catalog); ValidateSoftMaximum(softMaximumDestinations); ReplenishmentDestinationRecord replenishmentDestinationRecord = new ReplenishmentDestinationRecord(FindFirstFreeSlot(catalog), catalog.NextOrdinal, 1, ReplenishmentDestinationState.Active, ProductionStopCode.Ready, link, plan); ValidateStation(catalog, replenishmentDestinationRecord); if (catalog.TryGetByLinkId(replenishmentDestinationRecord.LinkId, out var destination)) { return Reject(catalog, ReplenishmentCatalogChangeCode.DuplicateLinkId, out updated, out added, out code); } if (catalog.TryGetByTargetIdentity(replenishmentDestinationRecord.Link, out destination)) { return Reject(catalog, ReplenishmentCatalogChangeCode.DuplicateTarget, out updated, out added, out code); } if (catalog.Destinations.Count >= 16) { return Reject(catalog, ReplenishmentCatalogChangeCode.HardLimitReached, out updated, out added, out code); } if (catalog.Destinations.Count >= softMaximumDestinations) { return Reject(catalog, ReplenishmentCatalogChangeCode.SoftLimitReached, out updated, out added, out code); } if (catalog.TargetAuthorizationCount + plan.Targets.Count > 32) { return Reject(catalog, ReplenishmentCatalogChangeCode.TargetAuthorizationLimitReached, out updated, out added, out code); } if (catalog.NextOrdinal == long.MaxValue) { return Reject(catalog, ReplenishmentCatalogChangeCode.OrdinalExhausted, out updated, out added, out code); } if (catalog.Revision == int.MaxValue) { return Reject(catalog, ReplenishmentCatalogChangeCode.RevisionExhausted, out updated, out added, out code); } List records = new List(catalog.Destinations) { replenishmentDestinationRecord }; updated = Rebuild(catalog, catalog.Revision + 1, catalog.NextOrdinal + 1, (catalog.DestinationCursorOrdinal == 0L) ? replenishmentDestinationRecord.Ordinal : catalog.DestinationCursorOrdinal, records); added = replenishmentDestinationRecord; code = ReplenishmentCatalogChangeCode.Applied; return true; } internal static bool TryRefresh(MultiReplenishmentCatalog catalog, string existingLinkId, StoredProductionLink refreshedLink, ReplenishmentPlan refreshedPlan, out MultiReplenishmentCatalog updated, out ReplenishmentDestinationRecord refreshed, out ReplenishmentCatalogChangeCode code) { RequireCatalog(catalog); if (!catalog.TryGetByLinkId(existingLinkId, out var destination)) { return Reject(catalog, ReplenishmentCatalogChangeCode.LinkNotFound, out updated, out refreshed, out code); } ReplenishmentDestinationRecord replenishmentDestinationRecord = new ReplenishmentDestinationRecord(destination.Slot, destination.Ordinal, (destination.RecordRevision == int.MaxValue) ? destination.RecordRevision : (destination.RecordRevision + 1), ReplenishmentDestinationState.Active, ProductionStopCode.Ready, refreshedLink, refreshedPlan); ValidateStation(catalog, replenishmentDestinationRecord); StoredProductionLink link = destination.Link; StoredProductionLink link2 = replenishmentDestinationRecord.Link; if (!string.Equals(destination.LinkId, replenishmentDestinationRecord.LinkId, StringComparison.Ordinal) || !SameTargetIdentity(link, link2) || link2.Revision < link.Revision) { return Reject(catalog, ReplenishmentCatalogChangeCode.LinkIdentityChanged, out updated, out refreshed, out code); } if (destination.RecordRevision == int.MaxValue || catalog.Revision == int.MaxValue) { return Reject(catalog, ReplenishmentCatalogChangeCode.RevisionExhausted, out updated, out refreshed, out code); } if (catalog.TargetAuthorizationCount - destination.Plan.Targets.Count + refreshedPlan.Targets.Count > 32) { return Reject(catalog, ReplenishmentCatalogChangeCode.TargetAuthorizationLimitReached, out updated, out refreshed, out code); } List records = Replace(catalog, destination, replenishmentDestinationRecord); updated = Rebuild(catalog, catalog.Revision + 1, catalog.NextOrdinal, catalog.DestinationCursorOrdinal, records); refreshed = replenishmentDestinationRecord; code = ReplenishmentCatalogChangeCode.Applied; return true; } internal static bool TryUpgradeLegacyDestinationIdentity(MultiReplenishmentCatalog catalog, string existingLinkId, StoredProductionLink upgradedLink, out MultiReplenishmentCatalog updated, out ReplenishmentDestinationRecord upgraded, out ReplenishmentCatalogChangeCode code) { RequireCatalog(catalog); if (!catalog.TryGetByLinkId(existingLinkId, out var destination)) { return Reject(catalog, ReplenishmentCatalogChangeCode.LinkNotFound, out updated, out upgraded, out code); } StoredProductionLink link = destination.Link; if (!IsExactLegacyIdentityUpgrade(link, upgradedLink) || !ReplenishmentPlanStore.TryPrepareLegacyTargetRebind(destination.Plan, link, upgradedLink, out var reboundPlan, out var _)) { return Reject(catalog, ReplenishmentCatalogChangeCode.LinkIdentityChanged, out updated, out upgraded, out code); } if (destination.RecordRevision == int.MaxValue || catalog.Revision == int.MaxValue) { return Reject(catalog, ReplenishmentCatalogChangeCode.RevisionExhausted, out updated, out upgraded, out code); } upgraded = destination.With(destination.RecordRevision + 1, destination.State, destination.FaultCode, upgradedLink, reboundPlan); updated = Rebuild(catalog, catalog.Revision + 1, catalog.NextOrdinal, catalog.DestinationCursorOrdinal, Replace(catalog, destination, upgraded)); code = ReplenishmentCatalogChangeCode.Applied; return true; } internal static bool TryAdvanceAfterSelection(MultiReplenishmentCatalog catalog, string selectedLinkId, ReplenishmentPlan advancedPlan, out MultiReplenishmentCatalog updated, out ReplenishmentDestinationRecord advanced, out ReplenishmentCatalogChangeCode code) { RequireCatalog(catalog); if (!catalog.TryGetByLinkId(selectedLinkId, out var current)) { return Reject(catalog, ReplenishmentCatalogChangeCode.LinkNotFound, out updated, out advanced, out code); } if (current.State != ReplenishmentDestinationState.Active || advancedPlan == null || !string.Equals(advancedPlan.Link.LinkId, current.LinkId, StringComparison.Ordinal) || advancedPlan.Link.Revision != current.Link.Revision || !PlanMatchesLink(advancedPlan.Link, current.Link)) { return Reject(catalog, ReplenishmentCatalogChangeCode.LinkIdentityChanged, out updated, out advanced, out code); } if (current.RecordRevision == int.MaxValue || catalog.Revision == int.MaxValue) { return Reject(catalog, ReplenishmentCatalogChangeCode.RevisionExhausted, out updated, out advanced, out code); } if (catalog.TargetAuthorizationCount - current.Plan.Targets.Count + advancedPlan.Targets.Count > 32) { return Reject(catalog, ReplenishmentCatalogChangeCode.TargetAuthorizationLimitReached, out updated, out advanced, out code); } advanced = current.With(current.RecordRevision + 1, ReplenishmentDestinationState.Active, ProductionStopCode.Ready, current.Link, advancedPlan); List list = Replace(catalog, current, advanced); List list2 = new List(); foreach (ReplenishmentDestinationRecord item in list) { if (item.State == ReplenishmentDestinationState.Active) { list2.Add(item); } } list2.Sort((ReplenishmentDestinationRecord left, ReplenishmentDestinationRecord right) => left.Ordinal.CompareTo(right.Ordinal)); int num = list2.FindIndex((ReplenishmentDestinationRecord destination) => destination.Ordinal == current.Ordinal); if (num < 0) { return Reject(catalog, ReplenishmentCatalogChangeCode.LinkNotFound, out updated, out advanced, out code); } long ordinal = list2[(num + 1) % list2.Count].Ordinal; updated = Rebuild(catalog, catalog.Revision + 1, catalog.NextOrdinal, ordinal, list); code = ReplenishmentCatalogChangeCode.Applied; return true; } internal static bool TrySetState(MultiReplenishmentCatalog catalog, string linkId, ReplenishmentDestinationState state, ProductionStopCode faultCode, out MultiReplenishmentCatalog updated, out ReplenishmentDestinationRecord changed, out ReplenishmentCatalogChangeCode code) { RequireCatalog(catalog); if (!catalog.TryGetByLinkId(linkId, out var destination)) { return Reject(catalog, ReplenishmentCatalogChangeCode.LinkNotFound, out updated, out changed, out code); } if (destination.State == state && destination.FaultCode == faultCode) { updated = catalog; changed = destination; code = ReplenishmentCatalogChangeCode.NoChange; return true; } if (destination.RecordRevision == int.MaxValue || catalog.Revision == int.MaxValue) { return Reject(catalog, ReplenishmentCatalogChangeCode.RevisionExhausted, out updated, out changed, out code); } ReplenishmentDestinationRecord replenishmentDestinationRecord = destination.With(destination.RecordRevision + 1, state, faultCode, destination.Link, destination.Plan); updated = Rebuild(catalog, catalog.Revision + 1, catalog.NextOrdinal, catalog.DestinationCursorOrdinal, Replace(catalog, destination, replenishmentDestinationRecord)); changed = replenishmentDestinationRecord; code = ReplenishmentCatalogChangeCode.Applied; return true; } internal static bool TryRemove(MultiReplenishmentCatalog catalog, string linkId, out MultiReplenishmentCatalog updated, out ReplenishmentDestinationRecord removed, out ReplenishmentCatalogChangeCode code) { RequireCatalog(catalog); if (!catalog.TryGetByLinkId(linkId, out removed)) { updated = catalog; code = ReplenishmentCatalogChangeCode.LinkNotFound; return false; } if (catalog.Revision == int.MaxValue) { updated = catalog; removed = null; code = ReplenishmentCatalogChangeCode.RevisionExhausted; return false; } List list = new List(); foreach (ReplenishmentDestinationRecord destination in catalog.Destinations) { if (destination != removed) { list.Add(destination); } } long num = catalog.DestinationCursorOrdinal; if (list.Count == 0) { num = 0L; } else if (num == removed.Ordinal) { num = list[0].Ordinal; foreach (ReplenishmentDestinationRecord item in list) { if (item.Ordinal > removed.Ordinal) { num = item.Ordinal; break; } } } updated = Rebuild(catalog, catalog.Revision + 1, catalog.NextOrdinal, num, list); code = ReplenishmentCatalogChangeCode.Applied; return true; } internal static bool TryAdvanceCursorAfter(MultiReplenishmentCatalog catalog, long selectedOrdinal, out MultiReplenishmentCatalog updated, out ReplenishmentCatalogChangeCode code) { RequireCatalog(catalog); if (!catalog.TryGetByOrdinal(selectedOrdinal, out var _)) { updated = catalog; code = ReplenishmentCatalogChangeCode.LinkNotFound; return false; } IReadOnlyList readOnlyList = catalog.ActiveRoundRobinOrder(); if (readOnlyList.Count == 0) { updated = catalog; code = ReplenishmentCatalogChangeCode.NoChange; return true; } List list = new List(readOnlyList); list.Sort((ReplenishmentDestinationRecord left, ReplenishmentDestinationRecord right) => left.Ordinal.CompareTo(right.Ordinal)); int num = list.FindIndex((ReplenishmentDestinationRecord replenishmentDestinationRecord) => replenishmentDestinationRecord.Ordinal == selectedOrdinal); long num2 = ((num < 0) ? list[0].Ordinal : list[(num + 1) % list.Count].Ordinal); if (num2 == catalog.DestinationCursorOrdinal) { updated = catalog; code = ReplenishmentCatalogChangeCode.NoChange; return true; } if (catalog.Revision == int.MaxValue) { updated = catalog; code = ReplenishmentCatalogChangeCode.RevisionExhausted; return false; } updated = Rebuild(catalog, catalog.Revision + 1, catalog.NextOrdinal, num2, catalog.Destinations); code = ReplenishmentCatalogChangeCode.Applied; return true; } internal static MultiReplenishmentCatalog FromLegacySingleton(string catalogId, string stationId, StoredProductionLink legacyLink, ReplenishmentPlan legacyPlan) { ReplenishmentDestinationRecord replenishmentDestinationRecord = new ReplenishmentDestinationRecord(0, 1L, 1, ReplenishmentDestinationState.Active, ProductionStopCode.Ready, legacyLink, legacyPlan); if (!string.Equals(replenishmentDestinationRecord.Plan.Link.StationId, stationId, StringComparison.Ordinal)) { throw new ArgumentException("The legacy plan is not bound to the requested station.", "legacyPlan"); } return new MultiReplenishmentCatalog(catalogId, stationId, 1, 2L, 1L, new ReplenishmentDestinationRecord[1] { replenishmentDestinationRecord }); } private static MultiReplenishmentCatalog Rebuild(MultiReplenishmentCatalog catalog, int revision, long nextOrdinal, long cursor, IEnumerable records) { return new MultiReplenishmentCatalog(catalog.CatalogId, catalog.StationId, revision, nextOrdinal, cursor, records); } private static List Replace(MultiReplenishmentCatalog catalog, ReplenishmentDestinationRecord current, ReplenishmentDestinationRecord replacement) { List list = new List(catalog.Destinations.Count); foreach (ReplenishmentDestinationRecord destination in catalog.Destinations) { list.Add((destination == current) ? replacement : destination); } return list; } private static int FindFirstFreeSlot(MultiReplenishmentCatalog catalog) { bool[] array = new bool[16]; foreach (ReplenishmentDestinationRecord destination in catalog.Destinations) { array[destination.Slot] = true; } for (int i = 0; i < array.Length; i++) { if (!array[i]) { return i; } } return 15; } private static void ValidateStation(MultiReplenishmentCatalog catalog, ReplenishmentDestinationRecord destination) { if (!string.Equals(catalog.StationId, destination.Plan.Link.StationId, StringComparison.Ordinal)) { throw new ArgumentException("The replenishment plan is not bound to this station catalog.", "destination"); } } internal static bool SameTargetIdentity(StoredProductionLink left, StoredProductionLink right) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) if (left == null || right == null) { return false; } bool flag = StockDomainValidation.IsWorldObjectToken(left.TargetToken); bool flag2 = StockDomainValidation.IsWorldObjectToken(right.TargetToken); if (flag || flag2) { if (flag && flag2 && left.TargetPrefabHash != 0 && left.TargetPrefabHash == right.TargetPrefabHash) { return string.Equals(left.TargetToken, right.TargetToken, StringComparison.Ordinal); } return false; } return left.Target == right.Target; } private static bool IsExactLegacyIdentityUpgrade(StoredProductionLink legacyLink, StoredProductionLink upgradedLink) { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) if (legacyLink != null && upgradedLink != null && string.IsNullOrEmpty(legacyLink.TargetToken) && legacyLink.TargetPrefabHash == 0 && upgradedLink.TargetPrefabHash != 0 && StockDomainValidation.IsWorldObjectToken(upgradedLink.TargetToken) && !((ZDOID)(ref upgradedLink.Target)).IsNone() && legacyLink.Role == ProductionLinkRole.Replenishment && upgradedLink.Role == legacyLink.Role && upgradedLink.ExpectedPosition == legacyLink.ExpectedPosition && upgradedLink.OwnerId == legacyLink.OwnerId && upgradedLink.StationOwnerId == legacyLink.StationOwnerId && upgradedLink.TargetOwnerId == legacyLink.TargetOwnerId && upgradedLink.Revision == legacyLink.Revision) { return string.Equals(upgradedLink.LinkId, legacyLink.LinkId, StringComparison.Ordinal); } return false; } private static bool PlanMatchesLink(ReplenishmentLinkBinding binding, StoredProductionLink link) { if (binding == null || link == null) { return false; } if (string.IsNullOrEmpty(binding.TargetToken)) { return string.Equals(binding.TargetId, ((object)Unsafe.As(ref link.Target)/*cast due to .constrained prefix*/).ToString(), StringComparison.Ordinal); } if (binding.TargetPrefabHash == link.TargetPrefabHash && string.Equals(binding.TargetToken, link.TargetToken, StringComparison.Ordinal)) { return string.Equals(binding.TargetId, link.TargetToken, StringComparison.Ordinal); } return false; } private static void ValidateSoftMaximum(int softMaximumDestinations) { if (softMaximumDestinations < 1 || softMaximumDestinations > 16) { throw new ArgumentOutOfRangeException("softMaximumDestinations"); } } private static void RequireCatalog(MultiReplenishmentCatalog catalog) { if (catalog == null) { throw new ArgumentNullException("catalog"); } } private static bool Reject(MultiReplenishmentCatalog catalog, ReplenishmentCatalogChangeCode rejection, out MultiReplenishmentCatalog updated, out ReplenishmentDestinationRecord destination, out ReplenishmentCatalogChangeCode code) { updated = catalog; destination = null; code = rejection; return false; } } internal sealed class NearbyIngredientSourceStock { private readonly IReadOnlyDictionary _exactCounts; internal string SourceId { get; } internal IReadOnlyDictionary ExactCounts => _exactCounts; internal NearbyIngredientSourceStock(string sourceId, IReadOnlyDictionary exactCounts) { SourceId = StockDomainValidation.RequireStableText(sourceId, "sourceId", 200); if (exactCounts == null) { throw new ArgumentNullException("exactCounts"); } SortedDictionary sortedDictionary = new SortedDictionary(StringComparer.Ordinal); foreach (KeyValuePair exactCount in exactCounts) { if (!StockDomainValidation.IsExactPrefabId(exactCount.Key) || exactCount.Value < 0) { throw new ArgumentException("Nearby source counts must use exact prefab IDs and non-negative amounts.", "exactCounts"); } if (exactCount.Value != 0) { sortedDictionary.Add(exactCount.Key, exactCount.Value); } } if (sortedDictionary.Count > 16) { throw new ArgumentOutOfRangeException("exactCounts", "Nearby source stock is bounded to the selected recipe requirements."); } _exactCounts = new ReadOnlyDictionary(sortedDictionary); } internal int Count(string prefabId) { if (!_exactCounts.TryGetValue(prefabId, out var value)) { return 0; } return value; } } internal sealed class NearbyIngredientStagingContribution { internal int SourceIndex { get; } internal string SourceId { get; } internal string PrefabId { get; } internal int Amount { get; } internal NearbyIngredientStagingContribution(int sourceIndex, string sourceId, string prefabId, int amount) { if (sourceIndex < 0 || sourceIndex >= 64) { throw new ArgumentOutOfRangeException("sourceIndex"); } SourceId = StockDomainValidation.RequireStableText(sourceId, "sourceId", 200); if (!StockDomainValidation.IsExactPrefabId(prefabId)) { throw new ArgumentException("An exact ingredient prefab is required.", "prefabId"); } if (amount <= 0 || amount > 1000000) { throw new ArgumentOutOfRangeException("amount"); } SourceIndex = sourceIndex; PrefabId = prefabId; Amount = amount; } } internal sealed class NearbyIngredientStagingPlan { private readonly ReadOnlyCollection _requirements; private readonly ReadOnlyCollection _contributions; internal IReadOnlyList Requirements => _requirements; internal IReadOnlyList Contributions => _contributions; internal NearbyIngredientStagingContribution NextContribution => _contributions[0]; internal int NextTransferAmount { get; } internal NearbyIngredientStagingPlan(IEnumerable requirements, IEnumerable contributions, int nextTransferAmount) { List list = requirements?.ToList() ?? throw new ArgumentNullException("requirements"); List list2 = contributions?.ToList() ?? throw new ArgumentNullException("contributions"); if (list.Count == 0 || list.Any((ReplenishmentRequirement value) => value == null) || list2.Count == 0 || list2.Any((NearbyIngredientStagingContribution value) => value == null)) { throw new ArgumentException("A staging plan requires exact requirements and contributions."); } if (list.Count > 16 || list2.Count > 1024 || list.Select((ReplenishmentRequirement value) => value.PrefabId).Distinct(StringComparer.Ordinal).Count() != list.Count) { throw new ArgumentOutOfRangeException("requirements", "A staging plan exceeds its exact requirement/contribution bounds or is not normalized."); } if (nextTransferAmount <= 0 || nextTransferAmount > 10 || nextTransferAmount > list2[0].Amount) { throw new ArgumentOutOfRangeException("nextTransferAmount"); } _requirements = new ReadOnlyCollection(list); _contributions = new ReadOnlyCollection(list2); NextTransferAmount = nextTransferAmount; } } internal static class NearbyIngredientStagingPlanner { internal const int HardMaximumSources = 64; internal const int HardMaximumContributions = 1024; internal const int HardMaximumPullAmount = 10; internal static bool TryPlan(IEnumerable requirements, IReadOnlyDictionary inputExactCounts, IReadOnlyList orderedSources, IngredientReservePolicy reserves, int pullBatchSize, out NearbyIngredientStagingPlan plan, out string failure) { plan = null; failure = string.Empty; if (inputExactCounts == null || orderedSources == null || reserves == null || !reserves.IsValid || pullBatchSize <= 0 || pullBatchSize > 10 || orderedSources.Count == 0 || orderedSources.Count > 64) { failure = "nearby-stage.invalid-input"; return false; } foreach (KeyValuePair inputExactCount in inputExactCounts) { if (!StockDomainValidation.IsExactPrefabId(inputExactCount.Key) || inputExactCount.Value < 0) { failure = "nearby-stage.input-count-invalid"; return false; } } if (!TryNormalize(requirements, out var normalized, out failure)) { return false; } SortedDictionary sortedDictionary = new SortedDictionary(StringComparer.Ordinal); foreach (ReplenishmentRequirement item in normalized) { if (!reserves.TryGetProtectedReserve(item.PrefabId, out var protectedReserve)) { failure = "nearby-stage.reserve-policy-unavailable"; return false; } inputExactCounts.TryGetValue(item.PrefabId, out var value); if (value < 0) { failure = "nearby-stage.input-count-invalid"; return false; } long num = (long)protectedReserve + (long)item.Amount; sortedDictionary[item.PrefabId] = Math.Max(0L, num - value); } if (sortedDictionary.Values.All((long num5) => num5 == 0)) { failure = "nearby-stage.input-already-sufficient"; return false; } List list = new List(); HashSet hashSet = new HashSet(StringComparer.Ordinal); for (int num2 = 0; num2 < orderedSources.Count; num2++) { NearbyIngredientSourceStock nearbyIngredientSourceStock = orderedSources[num2]; if (nearbyIngredientSourceStock == null || !hashSet.Add(nearbyIngredientSourceStock.SourceId)) { failure = "nearby-stage.source-invalid-or-duplicate"; return false; } foreach (ReplenishmentRequirement item2 in normalized) { long num3 = sortedDictionary[item2.PrefabId]; if (num3 == 0L) { continue; } long val = reserves.AvailableAboveReserve(item2.PrefabId, nearbyIngredientSourceStock.Count(item2.PrefabId)); int num4 = (int)Math.Min(num3, Math.Min(val, 1000000L)); if (num4 > 0) { list.Add(new NearbyIngredientStagingContribution(num2, nearbyIngredientSourceStock.SourceId, item2.PrefabId, num4)); if (list.Count > 1024) { failure = "nearby-stage.contribution-limit-exceeded"; return false; } sortedDictionary[item2.PrefabId] = num3 - num4; } } } KeyValuePair keyValuePair = sortedDictionary.FirstOrDefault((KeyValuePair pair) => pair.Value != 0); if (keyValuePair.Value != 0L) { failure = "nearby-stage.aggregate-insufficient:" + keyValuePair.Key; return false; } if (list.Count == 0) { failure = "nearby-stage.no-positive-contribution"; return false; } int nextTransferAmount = Math.Min(list[0].Amount, pullBatchSize); plan = new NearbyIngredientStagingPlan(normalized, list, nextTransferAmount); return true; } private static bool TryNormalize(IEnumerable requirements, out List normalized, out string failure) { normalized = new List(); failure = string.Empty; SortedDictionary sortedDictionary = new SortedDictionary(StringComparer.Ordinal); int num = 0; if (requirements != null) { foreach (ReplenishmentRequirement requirement in requirements) { if (requirement == null || ++num > 16) { failure = "nearby-stage.requirements-invalid-or-unbounded"; return false; } sortedDictionary.TryGetValue(requirement.PrefabId, out var value); long num2 = value + requirement.Amount; if (num2 <= 0 || num2 > 1000000) { failure = "nearby-stage.requirement-amount-out-of-range"; return false; } sortedDictionary[requirement.PrefabId] = num2; } } if (sortedDictionary.Count == 0) { failure = "nearby-stage.requirements-empty"; return false; } foreach (KeyValuePair item in sortedDictionary) { normalized.Add(new ReplenishmentRequirement(item.Key, (int)item.Value)); } return true; } } public sealed class OutputBatchRequirement { public string InputPrefab { get; } public int Amount { get; } internal OutputBatchRequirement(string inputPrefab, int amount) { InputPrefab = inputPrefab; Amount = amount; } } public static class OutputBatchPlanner { public static IReadOnlyList Plan(string processedInputPrefab, int processedAmount, string nextInputPrefab) { if (processedAmount < 0) { throw new ArgumentOutOfRangeException("processedAmount"); } string text = processedInputPrefab ?? string.Empty; string text2 = nextInputPrefab ?? string.Empty; if (processedAmount > 0 && text.Length == 0) { throw new ArgumentException("A processed batch amount requires its stable input prefab.", "processedInputPrefab"); } List list = new List(2); if (processedAmount > 0 && string.Equals(text, text2, StringComparison.Ordinal)) { list.Add(new OutputBatchRequirement(text, checked(processedAmount + 1))); return list.AsReadOnly(); } if (processedAmount > 0) { list.Add(new OutputBatchRequirement(text, processedAmount)); } if (text2.Length > 0) { list.Add(new OutputBatchRequirement(text2, 1)); } return list.AsReadOnly(); } } internal enum RepeatedControlAction { Select, CancelSelection, UnlinkExisting } internal static class ProductionControlGuidance { internal static RepeatedControlAction DecideRepeatedControl(bool pendingMatches, bool hasLink) { if (pendingMatches) { return RepeatedControlAction.CancelSelection; } return RepeatedControlAction.Select; } internal static string Format(ProductionLinkRole role, bool hasLink, int fuelReserve) { string text = role switch { ProductionLinkRole.Replenishment => "Alt+Middle Mouse", ProductionLinkRole.Output => "Alt+Right Mouse", _ => "Alt+Left Mouse", }; string text2 = ((role == ProductionLinkRole.Fuel) ? "Fuel Input" : role.ToString()); string text3 = text + ": select " + text2 + "; repeat it on the chest" + (hasLink ? " (add Shift at both steps to unlink)" : string.Empty); if (role == ProductionLinkRole.Fuel) { text3 += $" (reserve {fuelReserve})"; } return text3; } } internal enum ProductionLinkMouseButton { Left, Right, Middle } internal static class ProductionLinkGesturePolicy { internal static ProductionLinkRole RequestedRole(ProductionLinkMouseButton button) { return button switch { ProductionLinkMouseButton.Left => ProductionLinkRole.Input, ProductionLinkMouseButton.Right => ProductionLinkRole.Output, ProductionLinkMouseButton.Middle => ProductionLinkRole.Replenishment, _ => ProductionLinkRole.Input, }; } } internal sealed class ProductionRoleLinkCatalog { internal const int HardMaximumLinks = 16; private readonly ReadOnlyCollection _links; internal ProductionLinkRole Role { get; } internal int Revision { get; } internal IReadOnlyList Links => _links; internal ProductionRoleLinkCatalog(ProductionLinkRole role, int revision, IEnumerable links) { //IL_00b8: Unknown result type (might be due to invalid IL or missing references) if (role != ProductionLinkRole.Input && role != ProductionLinkRole.Fuel && role != ProductionLinkRole.Output && role != ProductionLinkRole.Replenishment) { throw new ArgumentOutOfRangeException("role"); } if (revision < 1) { throw new ArgumentOutOfRangeException("revision"); } List list = new List(); HashSet hashSet = new HashSet(StringComparer.Ordinal); HashSet hashSet2 = new HashSet(StringComparer.Ordinal); if (links != null) { foreach (StoredProductionLink link in links) { StoredProductionLink storedProductionLink = ReplenishmentDestinationRecord.CopyLink(link); if (storedProductionLink == null || storedProductionLink.Role != role || ((ZDOID)(ref storedProductionLink.Target)).IsNone() || storedProductionLink.Revision < 1 || string.IsNullOrWhiteSpace(storedProductionLink.LinkId) || !ProductionEndpointIdentity.IsCanonicalToken(storedProductionLink.TargetToken) || storedProductionLink.TargetPrefabHash == 0 || !IsFinite(storedProductionLink.ExpectedPosition)) { throw new ArgumentException("Every role link must be complete and exact.", "links"); } string item = storedProductionLink.TargetToken + ":" + storedProductionLink.TargetPrefabHash; if (!hashSet.Add(storedProductionLink.LinkId) || !hashSet2.Add(item)) { throw new ArgumentException("Role link IDs and targets must be unique.", "links"); } list.Add(storedProductionLink); if (list.Count > 16) { throw new ArgumentOutOfRangeException("links"); } } } Role = role; Revision = revision; _links = list.AsReadOnly(); } internal StoredProductionLink FindTarget(string token, int prefabHash) { if (!ProductionEndpointIdentity.IsCanonicalToken(token) || prefabHash == 0) { return null; } foreach (StoredProductionLink link in _links) { if (link.TargetPrefabHash == prefabHash && string.Equals(link.TargetToken, token, StringComparison.Ordinal)) { return ReplenishmentDestinationRecord.CopyLink(link); } } return null; } private static bool IsFinite(Vector3 value) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: 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_0034: 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) if (!float.IsNaN(value.x) && !float.IsInfinity(value.x) && !float.IsNaN(value.y) && !float.IsInfinity(value.y) && !float.IsNaN(value.z)) { return !float.IsInfinity(value.z); } return false; } } internal static class ProductionRoleLinkCatalogPolicy { internal static ProductionRoleLinkCatalog Empty(ProductionLinkRole role) { return new ProductionRoleLinkCatalog(role, 1, Array.Empty()); } internal static bool TryAddOrRefresh(ProductionRoleLinkCatalog catalog, StoredProductionLink candidate, int softMaximum, out ProductionRoleLinkCatalog updated, out StoredProductionLink published, out string failure) { updated = catalog; published = null; failure = string.Empty; if (catalog == null || candidate == null || candidate.Role != catalog.Role || softMaximum < 1 || softMaximum > 16) { return Fail("The role-link change is invalid.", out failure); } List list = new List(catalog.Links.Count + 1); int num = -1; for (int i = 0; i < catalog.Links.Count; i++) { StoredProductionLink storedProductionLink = catalog.Links[i]; if (storedProductionLink.TargetPrefabHash == candidate.TargetPrefabHash && string.Equals(storedProductionLink.TargetToken, candidate.TargetToken, StringComparison.Ordinal)) { num = i; published = ReplenishmentDestinationRecord.CopyLink(candidate); list.Add(published); } else { list.Add(ReplenishmentDestinationRecord.CopyLink(storedProductionLink)); } } if (num < 0) { if (list.Count >= 16) { return Fail("The hard 16-link role limit was reached.", out failure); } if (list.Count >= softMaximum) { return Fail("The configured per-role link limit was reached.", out failure); } published = ReplenishmentDestinationRecord.CopyLink(candidate); list.Add(published); } if (catalog.Revision == int.MaxValue) { return Fail("The role-link revision is exhausted.", out failure); } updated = new ProductionRoleLinkCatalog(catalog.Role, catalog.Revision + 1, list); return true; } internal static bool TryRemove(ProductionRoleLinkCatalog catalog, string token, int prefabHash, out ProductionRoleLinkCatalog updated, out string failure) { updated = catalog; failure = string.Empty; if (catalog == null || !ProductionEndpointIdentity.IsCanonicalToken(token) || prefabHash == 0) { return Fail("The role-link removal is invalid.", out failure); } List list = new List(); bool flag = false; foreach (StoredProductionLink link in catalog.Links) { if (!flag && link.TargetPrefabHash == prefabHash && string.Equals(link.TargetToken, token, StringComparison.Ordinal)) { flag = true; } else { list.Add(ReplenishmentDestinationRecord.CopyLink(link)); } } if (!flag) { return Fail("That chest is not linked for the selected role.", out failure); } if (catalog.Revision == int.MaxValue) { return Fail("The role-link revision is exhausted.", out failure); } updated = new ProductionRoleLinkCatalog(catalog.Role, catalog.Revision + 1, list); return true; } private static bool Fail(string value, out string failure) { failure = value; return false; } } internal enum RecipeReplenishmentCommitAction { CreateLink = 1, RefreshSameReplenishmentLink, RejectExistingLink } internal static class RecipeReplenishmentCommitPolicy { internal static bool RequiresAuthoritativeAllowlist(bool committing) { return committing; } internal static RecipeReplenishmentCommitAction Decide(ProductionLinkRole role, bool hasExistingLink, bool selectedTargetMatchesExisting) { if (role != ProductionLinkRole.Input && role != ProductionLinkRole.Replenishment) { throw new ArgumentOutOfRangeException("role"); } if (!hasExistingLink) { return RecipeReplenishmentCommitAction.CreateLink; } if (!(role == ProductionLinkRole.Replenishment && selectedTargetMatchesExisting)) { return RecipeReplenishmentCommitAction.RejectExistingLink; } return RecipeReplenishmentCommitAction.RefreshSameReplenishmentLink; } } internal enum ReplenishmentProducerKind { DirectRecipe = 1, TimedCooking, Fermenter } internal sealed class ReplenishmentRequirement { internal string PrefabId { get; } internal int Amount { get; } internal ReplenishmentRequirement(string prefabId, int amount) { if (!StockDomainValidation.IsExactPrefabId(prefabId)) { throw new ArgumentException("An exact ASCII prefab ID is required.", "prefabId"); } if (amount <= 0 || amount > 1000000) { throw new ArgumentOutOfRangeException("amount"); } PrefabId = prefabId; Amount = amount; } } internal sealed class ReplenishmentLinkBinding { internal string LinkId { get; } internal string StationId { get; } internal string TargetId { get; } internal string TargetToken { get; } internal int TargetPrefabHash { get; } internal int Revision { get; } internal ReplenishmentLinkBinding(string linkId, string stationId, string targetId, int revision) : this(linkId, stationId, targetId, string.Empty, 0, revision) { } internal ReplenishmentLinkBinding(string linkId, string stationId, string targetId, string targetToken, int targetPrefabHash, int revision) { LinkId = StockDomainValidation.RequireStableText(linkId, "linkId", 200); StationId = StockDomainValidation.RequireStableText(stationId, "stationId", 200); TargetId = StockDomainValidation.RequireStableText(targetId, "targetId", 200); if (!StockDomainValidation.IsLegacyOrWorldObjectIdentity(targetToken, targetPrefabHash)) { throw new ArgumentException("A canonical target token and non-zero prefab hash must be supplied together.", "targetToken"); } if (!string.IsNullOrEmpty(targetToken) && !string.Equals(targetId, targetToken, StringComparison.Ordinal)) { throw new ArgumentException("A persistent binding's target ID must be its canonical target token.", "targetId"); } if (revision < 1) { throw new ArgumentOutOfRangeException("revision"); } TargetToken = targetToken ?? string.Empty; TargetPrefabHash = targetPrefabHash; Revision = revision; } } internal sealed class ReplenishmentTargetAuthorization { internal const int MaximumRequirements = 16; internal const int ProducerSignatureBytes = 32; private readonly byte[] _producerSignature; private readonly ReadOnlyCollection _requirements; internal string OutputPrefabId { get; } internal ReplenishmentProducerKind ProducerKind { get; } internal string ProducerId { get; } internal int OutputAmount { get; } internal string RequiredStationName { get; } internal int RequiredStationLevel { get; } internal long AuthorizedPlayerId { get; } internal string AuthorizedPlayerName { get; } internal IReadOnlyList Requirements => _requirements; internal ReplenishmentTargetAuthorization(string outputPrefabId, ReplenishmentProducerKind producerKind, string producerId, byte[] producerSignature, int outputAmount, string requiredStationName, int requiredStationLevel, long authorizedPlayerId, string authorizedPlayerName, IEnumerable requirements) { if (!StockDomainValidation.IsExactPrefabId(outputPrefabId)) { throw new ArgumentException("An exact ASCII output prefab ID is required.", "outputPrefabId"); } if (!Enum.IsDefined(typeof(ReplenishmentProducerKind), producerKind)) { throw new ArgumentOutOfRangeException("producerKind"); } ProducerId = StockDomainValidation.RequireStableText(producerId, "producerId", 256); if (producerSignature == null || producerSignature.Length != 32) { throw new ArgumentException($"The producer signature must contain exactly {32} bytes.", "producerSignature"); } if (outputAmount <= 0 || outputAmount > 1000000) { throw new ArgumentOutOfRangeException("outputAmount"); } if (authorizedPlayerId == 0L) { throw new ArgumentOutOfRangeException("authorizedPlayerId"); } if (producerKind == ReplenishmentProducerKind.DirectRecipe) { RequiredStationName = StockDomainValidation.RequireStableText(requiredStationName, "requiredStationName", 128); if (requiredStationLevel < 1 || requiredStationLevel > 1000) { throw new ArgumentOutOfRangeException("requiredStationLevel"); } } else { if (!string.IsNullOrEmpty(requiredStationName) || requiredStationLevel != 0) { throw new ArgumentException("Only direct recipes may carry a crafting-station name and level."); } RequiredStationName = string.Empty; } List list = new List(); if (requirements != null) { foreach (ReplenishmentRequirement requirement in requirements) { if (requirement == null) { throw new ArgumentException("Requirements cannot contain null entries.", "requirements"); } list.Add(requirement); if (list.Count > 16) { throw new ArgumentOutOfRangeException("requirements"); } } } if (list.Count == 0) { throw new ArgumentException("A replenishment producer must consume at least one requirement.", "requirements"); } OutputPrefabId = outputPrefabId; ProducerKind = producerKind; _producerSignature = (byte[])producerSignature.Clone(); OutputAmount = outputAmount; RequiredStationLevel = requiredStationLevel; AuthorizedPlayerId = authorizedPlayerId; AuthorizedPlayerName = StockDomainValidation.RequireStableText(authorizedPlayerName, "authorizedPlayerName", 128); _requirements = list.AsReadOnly(); } internal byte[] CopyProducerSignature() { return (byte[])_producerSignature.Clone(); } internal bool ProducerSignatureMatches(byte[] candidate) { if (candidate == null || candidate.Length != _producerSignature.Length) { return false; } int num = 0; for (int i = 0; i < _producerSignature.Length; i++) { num |= _producerSignature[i] ^ candidate[i]; } return num == 0; } } internal sealed class ReplenishmentPlan { internal const int MaximumTargets = 32; private readonly ReadOnlyCollection _targets; internal string PlanId { get; } internal int Revision { get; } internal int Cursor { get; } internal string StationPrefabId { get; } internal ReplenishmentProducerKind AdapterKind { get; } internal ReplenishmentLinkBinding Link { get; } internal long AuthorizedPlayerId { get; } internal string AuthorizedPlayerName { get; } internal IReadOnlyList Targets => _targets; internal ReplenishmentPlan(string planId, int revision, int cursor, string stationPrefabId, ReplenishmentProducerKind adapterKind, ReplenishmentLinkBinding link, long authorizedPlayerId, string authorizedPlayerName, IEnumerable targets) { PlanId = StockDomainValidation.RequireStableText(planId, "planId", 200); if (revision < 1) { throw new ArgumentOutOfRangeException("revision"); } if (!StockDomainValidation.IsExactPrefabId(stationPrefabId)) { throw new ArgumentException("An exact ASCII station prefab ID is required.", "stationPrefabId"); } if (!Enum.IsDefined(typeof(ReplenishmentProducerKind), adapterKind)) { throw new ArgumentOutOfRangeException("adapterKind"); } Link = link ?? throw new ArgumentNullException("link"); if (authorizedPlayerId == 0L) { throw new ArgumentOutOfRangeException("authorizedPlayerId"); } AuthorizedPlayerId = authorizedPlayerId; AuthorizedPlayerName = StockDomainValidation.RequireStableText(authorizedPlayerName, "authorizedPlayerName", 128); List list = new List(); if (targets != null) { foreach (ReplenishmentTargetAuthorization target in targets) { if (target == null) { throw new ArgumentException("Targets cannot contain null entries.", "targets"); } if (target.ProducerKind != adapterKind) { throw new ArgumentException("Every target must use the plan's concrete station adapter.", "targets"); } if (target.AuthorizedPlayerId != AuthorizedPlayerId || !string.Equals(target.AuthorizedPlayerName, AuthorizedPlayerName, StringComparison.Ordinal)) { throw new ArgumentException("Every target must use the plan's exact authorization principal.", "targets"); } list.Add(target); if (list.Count > 32) { throw new ArgumentOutOfRangeException("targets"); } } } list.Sort((ReplenishmentTargetAuthorization left, ReplenishmentTargetAuthorization right) => StringComparer.Ordinal.Compare(left.OutputPrefabId, right.OutputPrefabId)); for (int num = 1; num < list.Count; num++) { if (string.Equals(list[num - 1].OutputPrefabId, list[num].OutputPrefabId, StringComparison.Ordinal)) { throw new ArgumentException("A plan cannot authorize the same exact output prefab more than once.", "targets"); } } if (cursor < 0 || (list.Count == 0 && cursor != 0) || (list.Count > 0 && cursor >= list.Count)) { throw new ArgumentOutOfRangeException("cursor"); } Revision = revision; Cursor = cursor; StationPrefabId = stationPrefabId; AdapterKind = adapterKind; _targets = list.AsReadOnly(); } internal ReplenishmentPlan WithCursor(int nextCursor) { return new ReplenishmentPlan(PlanId, Revision, nextCursor, StationPrefabId, AdapterKind, Link, AuthorizedPlayerId, AuthorizedPlayerName, _targets); } } internal static class StockDomainValidation { internal const int MaximumIdentifierCharacters = 128; internal const int MaximumItemAmount = 1000000; internal const int MaximumStationLevel = 1000; internal const int WorldObjectTokenCharacters = 32; internal static bool IsWorldObjectToken(string value) { if (string.IsNullOrEmpty(value) || value.Length != 32) { return false; } foreach (char c in value) { bool num = c >= '0' && c <= '9'; bool flag = c >= 'a' && c <= 'f'; if (!num && !flag) { return false; } } return true; } internal static bool IsLegacyOrWorldObjectIdentity(string targetToken, int targetPrefabHash) { if (!string.IsNullOrEmpty(targetToken)) { if (targetPrefabHash != 0) { return IsWorldObjectToken(targetToken); } return false; } return targetPrefabHash == 0; } internal static bool IsExactPrefabId(string value) { if (string.IsNullOrEmpty(value) || value.Length > 128) { return false; } foreach (char c in value) { bool num = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); bool flag = c >= '0' && c <= '9'; if (!num && !flag && c != '_' && c != '-' && c != '.') { return false; } } return true; } internal static string RequireStableText(string value, string parameterName, int maximumCharacters) { if (string.IsNullOrWhiteSpace(value)) { throw new ArgumentException("A stable value is required.", parameterName); } if (value.Length > maximumCharacters) { throw new ArgumentOutOfRangeException(parameterName); } for (int i = 0; i < value.Length; i++) { if (char.IsControl(value[i])) { throw new ArgumentException("Stable values cannot contain control characters.", parameterName); } } return value; } } internal enum ReplenishmentTargetStopReason { Ready, ExemplarAbsent, ProducerIneligible, IngredientsInsufficient, CompleteOutputDoesNotFit } internal readonly struct ReplenishmentTargetReadiness { internal bool ExemplarPresent { get; } internal bool ProducerEligible { get; } internal bool IngredientsSufficient { get; } internal bool CompleteOutputFits { get; } internal bool CanRunCompleteAction { get { if (ExemplarPresent && ProducerEligible && IngredientsSufficient) { return CompleteOutputFits; } return false; } } internal ReplenishmentTargetStopReason StopReason { get { if (ExemplarPresent) { if (ProducerEligible) { if (IngredientsSufficient) { if (CompleteOutputFits) { return ReplenishmentTargetStopReason.Ready; } return ReplenishmentTargetStopReason.CompleteOutputDoesNotFit; } return ReplenishmentTargetStopReason.IngredientsInsufficient; } return ReplenishmentTargetStopReason.ProducerIneligible; } return ReplenishmentTargetStopReason.ExemplarAbsent; } } internal static ReplenishmentTargetReadiness Ready { get; } = new ReplenishmentTargetReadiness(exemplarPresent: true, producerEligible: true, ingredientsSufficient: true, completeOutputFits: true); internal ReplenishmentTargetReadiness(bool exemplarPresent, bool producerEligible, bool ingredientsSufficient, bool completeOutputFits) { ExemplarPresent = exemplarPresent; ProducerEligible = producerEligible; IngredientsSufficient = ingredientsSufficient; CompleteOutputFits = completeOutputFits; } } internal sealed class ReplenishmentSelectionResult { internal bool HasAction => Target != null; internal ReplenishmentTargetAuthorization Target { get; } internal int SelectedIndex { get; } internal int NextCursor { get; } internal int InspectedTargets { get; } private ReplenishmentSelectionResult(ReplenishmentTargetAuthorization target, int selectedIndex, int nextCursor, int inspectedTargets) { Target = target; SelectedIndex = selectedIndex; NextCursor = nextCursor; InspectedTargets = inspectedTargets; } internal static ReplenishmentSelectionResult Action(ReplenishmentTargetAuthorization target, int selectedIndex, int nextCursor, int inspectedTargets) { return new ReplenishmentSelectionResult(target ?? throw new ArgumentNullException("target"), selectedIndex, nextCursor, inspectedTargets); } internal static ReplenishmentSelectionResult None(int nextCursor, int inspectedTargets) { return new ReplenishmentSelectionResult(null, -1, nextCursor, inspectedTargets); } } internal static class DeterministicReplenishmentSelector { internal static ReplenishmentSelectionResult Select(ReplenishmentPlan plan, Func observe) { if (plan == null) { throw new ArgumentNullException("plan"); } if (observe == null) { throw new ArgumentNullException("observe"); } int count = plan.Targets.Count; if (count == 0) { return ReplenishmentSelectionResult.None(0, 0); } int cursor = plan.Cursor; for (int i = 0; i < count; i++) { int num = (cursor + i) % count; ReplenishmentTargetAuthorization replenishmentTargetAuthorization = plan.Targets[num]; if (observe(replenishmentTargetAuthorization).CanRunCompleteAction) { return ReplenishmentSelectionResult.Action(replenishmentTargetAuthorization, num, (num + 1) % count, i + 1); } } return ReplenishmentSelectionResult.None((cursor + 1) % count, count); } } internal enum ReplenishmentDemandState { Invalid, ExemplarAbsent, ReserveSatisfied, ProductionNeeded } internal readonly struct ReplenishmentTargetDemand { internal ReplenishmentDemandState State { get; } internal int Reserve { get; } internal long CurrentCount { get; } internal long InFlightCount { get; } internal long ProjectedCount => CurrentCount + InFlightCount; internal bool NeedsProduction => State == ReplenishmentDemandState.ProductionNeeded; internal ReplenishmentTargetDemand(ReplenishmentDemandState state, int reserve, long currentCount, long inFlightCount) { if (!Enum.IsDefined(typeof(ReplenishmentDemandState), state)) { throw new ArgumentOutOfRangeException("state"); } if (reserve < 0 || currentCount < 0 || inFlightCount < 0 || currentCount > long.MaxValue - inFlightCount) { throw new ArgumentOutOfRangeException("currentCount"); } State = state; Reserve = reserve; CurrentCount = currentCount; InFlightCount = inFlightCount; } } internal sealed class ReplenishmentTargetReservePolicy { internal const int MaximumRules = 128; internal const int MaximumReserveAmount = 1000000; private const int MaximumConfigurationCharacters = 32768; private readonly Dictionary _targetAmounts; internal bool IsValid { get; } internal int DefaultTargetReserve { get; } internal int RuleCount => _targetAmounts.Count; private ReplenishmentTargetReservePolicy(bool isValid, int defaultTargetReserve, Dictionary targetAmounts) { IsValid = isValid; DefaultTargetReserve = defaultTargetReserve; _targetAmounts = targetAmounts; } internal static bool TryParse(int defaultTargetReserve, string exactPrefabReserves, out ReplenishmentTargetReservePolicy policy, out string failure) { policy = FailClosed(); failure = string.Empty; if (defaultTargetReserve < 1 || defaultTargetReserve > 1000000) { failure = $"DefaultReserve must be between 1 and {1000000}."; return false; } string text = exactPrefabReserves ?? string.Empty; if (text.Length > 32768) { failure = "The replenishment reserve configuration is too long."; return false; } Dictionary dictionary = new Dictionary(StringComparer.Ordinal); if (!string.IsNullOrWhiteSpace(text)) { string[] array = text.Split(new char[1] { ';' }, StringSplitOptions.None); if (array.Length > 128) { failure = $"The replenishment reserve configuration exceeds {128} rules."; return false; } string[] array2 = array; foreach (string text2 in array2) { if (string.IsNullOrWhiteSpace(text2)) { failure = "The replenishment reserve configuration contains an empty rule."; return false; } int num = text2.IndexOf('='); if (num <= 0 || num != text2.LastIndexOf('=')) { failure = "Each replenishment reserve rule must contain exactly one '=' separator."; return false; } string text3 = text2.Substring(0, num).Trim(); string value = text2.Substring(num + 1).Trim(); if (!StockDomainValidation.IsExactPrefabId(text3)) { failure = "The replenishment reserve configuration contains an invalid exact prefab ID."; return false; } if (!TryParseAmount(value, out var amount)) { failure = $"Replenishment reserves must be decimal integers between 1 and {1000000}."; return false; } if (dictionary.ContainsKey(text3)) { failure = "The replenishment reserve configuration contains a duplicate exact prefab ID."; return false; } dictionary.Add(text3, amount); } } policy = new ReplenishmentTargetReservePolicy(isValid: true, defaultTargetReserve, dictionary); return true; } internal bool TryGetTargetReserve(string prefabId, out int targetReserve) { targetReserve = 0; if (!IsValid || !StockDomainValidation.IsExactPrefabId(prefabId)) { return false; } targetReserve = (_targetAmounts.TryGetValue(prefabId, out var value) ? value : DefaultTargetReserve); return true; } internal bool TryEvaluate(string prefabId, long currentExactCount, long inFlightOutputCount, out ReplenishmentTargetDemand demand) { demand = default(ReplenishmentTargetDemand); if (currentExactCount < 0 || inFlightOutputCount < 0 || currentExactCount > long.MaxValue - inFlightOutputCount || !TryGetTargetReserve(prefabId, out var targetReserve)) { return false; } ReplenishmentDemandState state = ((currentExactCount == 0L) ? ReplenishmentDemandState.ExemplarAbsent : ((currentExactCount + inFlightOutputCount >= targetReserve) ? ReplenishmentDemandState.ReserveSatisfied : ReplenishmentDemandState.ProductionNeeded)); demand = new ReplenishmentTargetDemand(state, targetReserve, currentExactCount, inFlightOutputCount); return true; } internal bool NeedsProduction(string prefabId, long currentExactCount, long inFlightOutputCount, out int reserve) { reserve = 0; if (!TryEvaluate(prefabId, currentExactCount, inFlightOutputCount, out var demand)) { return false; } reserve = demand.Reserve; return demand.NeedsProduction; } private static ReplenishmentTargetReservePolicy FailClosed() { return new ReplenishmentTargetReservePolicy(isValid: false, 1000000, new Dictionary(StringComparer.Ordinal)); } private static bool TryParseAmount(string value, out int amount) { amount = 0; if (string.IsNullOrEmpty(value)) { return false; } long num = 0L; foreach (char c in value) { if (c < '0' || c > '9') { return false; } num = num * 10 + c - 48; if (num > 1000000) { return false; } } if (num < 1) { return false; } amount = (int)num; return true; } } public static class StackChunkPlanner { public const int MaximumChunks = 4096; public static IReadOnlyList Plan(int amount, int maximumStackSize) { if (amount <= 0) { throw new ArgumentOutOfRangeException("amount"); } if (maximumStackSize <= 0) { throw new ArgumentOutOfRangeException("maximumStackSize"); } long num = ((long)amount + (long)maximumStackSize - 1) / maximumStackSize; if (num > 4096) { throw new ArgumentOutOfRangeException("amount", "The exact insertion exceeds the bounded output-chunk limit."); } List list = new List((int)num); int num2 = amount; while (num2 > 0) { int num3 = Math.Min(num2, maximumStackSize); list.Add(num3); num2 -= num3; } return list.AsReadOnly(); } } internal sealed class StockStationPrefabPolicy { private const int MaximumEntries = 128; private readonly HashSet _allowed; private readonly HashSet _denied; private readonly ReadOnlyCollection _effectiveAllowed; internal int AllowedCount => _allowed.Count; internal int DeniedCount => _denied.Count; internal IReadOnlyList EffectiveAllowedIds => _effectiveAllowed; private StockStationPrefabPolicy(HashSet allowed, HashSet denied) { _allowed = allowed; _denied = denied; _effectiveAllowed = allowed.Where((string value) => !denied.Contains(value)).OrderBy((string value) => value, StringComparer.Ordinal).ToList() .AsReadOnly(); } internal bool Allows(string prefabId) { if (StockDomainValidation.IsExactPrefabId(prefabId) && _allowed.Contains(prefabId)) { return !_denied.Contains(prefabId); } return false; } internal static bool TryParse(string allowedText, string deniedText, out StockStationPrefabPolicy policy, out string failure) { policy = null; failure = string.Empty; if (!TryParseSet(allowedText, "allow", out var values, out failure) || !TryParseSet(deniedText, "deny", out var values2, out failure)) { return false; } policy = new StockStationPrefabPolicy(values, values2); return true; } private static bool TryParseSet(string text, string label, out HashSet values, out string failure) { values = new HashSet(StringComparer.Ordinal); failure = string.Empty; if (string.IsNullOrWhiteSpace(text)) { return true; } string[] array = text.Split(new char[4] { ',', ';', '\r', '\n' }, StringSplitOptions.None); if (array.Length > 128) { failure = $"The stock station {label} list exceeds {128} entries."; return false; } string[] array2 = array; for (int i = 0; i < array2.Length; i++) { string text2 = array2[i].Trim(); if (!StockDomainValidation.IsExactPrefabId(text2)) { failure = "The stock station " + label + " list contains an invalid exact prefab ID."; return false; } if (!values.Add(text2)) { failure = "The stock station " + label + " list contains a duplicate exact prefab ID."; return false; } } return true; } } internal enum StoredRecordState { Absent, Valid, Invalid } } namespace RunicProduction.Contracts { public enum ProductionLinkRole { Input = 1, Fuel, Output, Replenishment } public enum ProductionStopCode { Ready, Disabled, LinkMissing, LinkTargetDestroyed, LinkTargetMoved, LinkOutOfRange, OwnershipChanged, WardChanged, PermissionDenied, StationCapacityFull, InputUnavailable, FuelReserveProtected, OutputFull, OutputInaccessible, NativeOwnerRequired, InventoryUnavailable, InternalFailure, LinkTargetUnavailable, ReplenishmentPlanMissing, ReplenishmentTargetMissing, ProducerIneligible, IngredientReserveProtected, RecipeUnavailable, StationUnusable, RecordInvalid } }