Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of RunicProduction v1.0.0
RunicProduction.dll
Decompiled 9 hours ago
The result has been truncated due to the large size, download it to view full contents!
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<bool> Enabled { get; private set; } internal static ConfigEntry<float> MaximumLinkRange { get; private set; } internal static ConfigEntry<float> MovedTargetTolerance { get; private set; } internal static ConfigEntry<int> FuelReserve { get; private set; } internal static ConfigEntry<int> PullBatchSize { get; private set; } internal static ConfigEntry<string> CookingStationPrefabAllowList { get; private set; } internal static ConfigEntry<string> CookingStationPrefabDenyList { get; private set; } internal static ConfigEntry<string> RecipeStationPrefabAllowList { get; private set; } internal static ConfigEntry<string> RecipeStationPrefabDenyList { get; private set; } internal static ConfigEntry<bool> RecipeNearbyIngredientsEnabled { get; private set; } internal static ConfigEntry<int> RecipeNearbyMaximumSourceChests { get; private set; } internal static ConfigEntry<string> FermenterPrefabAllowList { get; private set; } internal static ConfigEntry<string> FermenterPrefabDenyList { get; private set; } internal static ConfigEntry<int> DefaultIngredientReserve { get; private set; } internal static ConfigEntry<string> IngredientReserveRules { get; private set; } internal static ConfigEntry<int> MaximumLinksPerRole { get; private set; } internal static ConfigEntry<int> DefaultReplenishmentReserve { get; private set; } internal static ConfigEntry<string> ReplenishmentReserveRules { get; private set; } internal static ConfigEntry<float> StockSchedulerIntervalSeconds { get; private set; } internal static ConfigEntry<int> StockSchedulerMaximumOperations { get; private set; } internal static ConfigEntry<bool> ShowStatusOverlay { get; private set; } internal static ConfigEntry<bool> ShowControlHints { get; private set; } internal static ConfigEntry<bool> 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<bool>("General", "Enabled", true, "Enable explicit Runic Input, Output, and Replenishment links. Disabling immediately stops automation without leaving operation state behind."); MaximumLinkRange = config.Bind<float>("Links", "MaximumLinkRange", 12f, new ConfigDescription("Maximum station-to-container distance in meters.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 30f), Array.Empty<object>())); MovedTargetTolerance = config.Bind<float>("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<float>(0.1f, 3f), Array.Empty<object>())); FuelReserve = config.Bind<int>("Fuel", "ProtectedReserve", 10, new ConfigDescription("Minimum fuel items retained in every linked fuel container.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000), Array.Empty<object>())); PullBatchSize = config.Bind<int>("Transfers", "PullBatchSize", 1, new ConfigDescription("Maximum items pulled per station update. Capacity and authorization are checked per item.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>())); CookingStationPrefabAllowList = config.Bind<string>("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<string>("Cooking Stations", "DeniedPrefabIds", string.Empty, "Comma-separated exact prefab IDs denied CookingStation automation. Deny entries override the allow list."); RecipeStationPrefabAllowList = config.Bind<string>("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<string>("Recipe Stations", "DeniedPrefabIds", string.Empty, "Comma-separated exact recipe-station prefab IDs denied replenishment. Deny entries override the allow list."); RecipeNearbyIngredientsEnabled = config.Bind<bool>("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<int>("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<int>(1, 64), Array.Empty<object>())); FermenterPrefabAllowList = config.Bind<string>("Fermenters", "AllowedPrefabIds", "fermenter", "Comma-separated exact Fermenter prefab IDs allowed to participate in replenishment production."); FermenterPrefabDenyList = config.Bind<string>("Fermenters", "DeniedPrefabIds", string.Empty, "Comma-separated exact Fermenter prefab IDs denied replenishment. Deny entries override the allow list."); DefaultIngredientReserve = config.Bind<int>("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<int>(0, 1000000), Array.Empty<object>())); IngredientReserveRules = config.Bind<string>("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<int>("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<int>(1, 16), Array.Empty<object>())); DefaultReplenishmentReserve = config.Bind<int>("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<int>(1, 1000000), Array.Empty<object>())); ReplenishmentReserveRules = config.Bind<string>("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<float>("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<float>(0.5f, 10f), Array.Empty<object>())); StockSchedulerMaximumOperations = config.Bind<int>("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<int>(1, 256), Array.Empty<object>())); ShowStatusOverlay = config.Bind<bool>("UI", "ShowStatusOverlay", true, "Append the most recent compact automation result to station hover text."); ShowControlHints = config.Bind<bool>("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<bool>("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<string, ProductionStopCode> LastStopByStation = new Dictionary<string, ProductionStopCode>(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<bool> 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<bool> 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<CookingStation>() : registeredPrefab.GetComponents<CookingStation>()); ZNetView[] array2 = (((Object)(object)registeredPrefab == (Object)null) ? Array.Empty<ZNetView>() : registeredPrefab.GetComponents<ZNetView>()); 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<WearNTear>() == (Object)null) { return Fail("CookingStation, ZNetView, and WearNTear require one registered root.", out failure); } station = array[0]; if (registeredPrefab.GetComponentsInChildren<Smelter>(true).Length != 0 || registeredPrefab.GetComponentsInChildren<Fermenter>(true).Length != 0 || registeredPrefab.GetComponentsInChildren<CraftingStation>(true).Length != 0 || registeredPrefab.GetComponentsInChildren<Container>(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<int> hashSet = new HashSet<int>(); 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<ParticleSystem>() == (Object)null || (Object)(object)((Component)val).GetComponent<AudioSource>() == (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<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal); HashSet<string> hashSet3 = new HashSet<string>(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<ItemDrop>() : null) == (Object)null)) { GameObject obj2 = ValheimAccess.RegisteredItemPrefab(text2); if (!((Object)(object)((obj2 != null) ? obj2.GetComponent<ItemDrop>() : 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<ItemDrop>() : 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<CookingStation>() != (Object)(object)station || ((Component)station).gameObject.GetComponents<CookingStation>().Length != 1 || (Object)(object)((Component)station).gameObject.GetComponent<ZNetView>() != (Object)(object)ValheimAccess.View(station) || (Object)(object)((Component)station).gameObject.GetComponent<WearNTear>() == (Object)null || registeredPrefab.GetComponents<CookingStation>().Length != 1 || (Object)(object)registeredPrefab.GetComponent<ZNetView>() == (Object)null || (Object)(object)registeredPrefab.GetComponent<WearNTear>() == (Object)null) { return Fail("CookingStation, ZNetView, and WearNTear must share one unambiguous registered prefab root.", out failure); } if (((Component)station).GetComponentsInChildren<Smelter>(true).Length != 0 || registeredPrefab.GetComponentsInChildren<Smelter>(true).Length != 0 || ((Component)station).GetComponentsInChildren<Fermenter>(true).Length != 0 || registeredPrefab.GetComponentsInChildren<Fermenter>(true).Length != 0 || ((Component)station).GetComponentsInChildren<CraftingStation>(true).Length != 0 || registeredPrefab.GetComponentsInChildren<CraftingStation>(true).Length != 0) { return Fail("Hybrid Smelter, Fermenter, CraftingStation, or CookingStation prefabs are ambiguous and unsupported.", out failure); } if (((Component)station).GetComponentsInChildren<Container>(true).Length != 0 || registeredPrefab.GetComponentsInChildren<Container>(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<int> hashSet = new HashSet<int>(); 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<ParticleSystem>() == (Object)null || (Object)(object)((Component)val2).GetComponent<AudioSource>() == (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<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal); HashSet<string> hashSet3 = new HashSet<string>(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<ItemDrop>() : null) == (Object)null)) { GameObject obj2 = ValheimAccess.RegisteredItemPrefab(text2); if (!((Object)(object)((obj2 != null) ? obj2.GetComponent<ItemDrop>() : 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<ItemDrop>() : 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<ItemDrop>() : 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<string> hashSet = new HashSet<string>(station.m_conversion.Select((ItemConversion conversion) => ValheimAccess.PrefabName(((Component)conversion.m_from).gameObject)), StringComparer.Ordinal); HashSet<string> hashSet2 = new HashSet<string>(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<ReplenishmentRequirement> Requirements { get; } internal StockOutputDefinition Output { get; } internal ExactStockMutationPlan(StockInventoryTransition source, StockInventoryTransition destination, IEnumerable<ReplenishmentRequirement> requirements, StockOutputDefinition output) { Source = source ?? throw new ArgumentNullException("source"); Destination = destination ?? throw new ArgumentNullException("destination"); List<ReplenishmentRequirement> list = new List<ReplenishmentRequirement>(); 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<ReplenishmentRequirement>(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<ExactStockCompositeSourceEntry> Entries { get; } internal ExactStockCompositeSourcePlan(int sourceCount, IEnumerable<ExactStockCompositeSourceEntry> entries) { if (sourceCount < 1 || sourceCount > 64) { throw new ArgumentOutOfRangeException("sourceCount"); } List<ExactStockCompositeSourceEntry> 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<ExactStockCompositeSourceEntry>(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<ReplenishmentRequirement> 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<ReplenishmentRequirement> requirements, IngredientReservePolicy reserves, out StockInventoryTransition transition, out IReadOnlyList<ReplenishmentRequirement> 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<ReplenishmentRequirement> requirements, IngredientReservePolicy reserves, out StockInventoryTransition transition, out IReadOnlyList<ReplenishmentRequirement> normalizedRequirements, out StockSourcePreparationFailureKind failureKind, out string failure) { transition = null; normalizedRequirements = Array.Empty<ReplenishmentRequirement>(); 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<ReplenishmentRequirement>(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<ItemDrop>() : 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<Inventory> orderedSources, IEnumerable<ReplenishmentRequirement> 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<ExactStockCompositeSourceEntry> list = new List<ExactStockCompositeSourceEntry>(); 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<Inventory> orderedSources, IReadOnlyList<string> orderedSourceIds, IEnumerable<ReplenishmentRequirement> 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<StockInventoryState> list = new List<StockInventoryState>(orderedSources.Count); List<Inventory> list2 = new List<Inventory>(orderedSources.Count); List<NearbyIngredientSourceStock> list3 = new List<NearbyIngredientSourceStock>(orderedSources.Count); HashSet<Inventory> hashSet = new HashSet<Inventory>(); HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal); List<ReplenishmentRequirement> 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<string, int> 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<string, int> ExactCounts(Inventory inventory, IEnumerable<ReplenishmentRequirement> requirements) { SortedDictionary<string, int> sortedDictionary = new SortedDictionary<string, int>(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<ReplenishmentRequirement> requirements, out List<ReplenishmentRequirement> normalized, out string failure) { normalized = new List<ReplenishmentRequirement>(); failure = string.Empty; SortedDictionary<string, long> sortedDictionary = new SortedDictionary<string, long>(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<string, long> 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<ItemData> 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<ItemData> 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<ItemData> 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<Vector2i> list2 = new List<Vector2i>(); 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<ItemData> 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<Vector2i> list2 = new List<Vector2i>(); 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<string, string> left, IDictionary<string, string> right) { int num = left?.Count ?? 0; if (num != (right?.Count ?? 0)) { return false; } if (num == 0) { return true; } foreach (KeyValuePair<string, string> 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<FermenterConversionDescriptor> _conversions; private readonly Dictionary<string, FermenterConversionDescriptor> _byInput; private readonly Dictionary<string, FermenterConversionDescriptor> _byOutput; internal string PrefabId { get; } internal IReadOnlyList<FermenterConversionDescriptor> Conversions => _conversions; internal FermenterDescriptor(string prefabId, IEnumerable<FermenterConversionDescriptor> conversions) { PrefabId = prefabId; List<FermenterConversionDescriptor> list = conversions.OrderBy<FermenterConversionDescriptor, string>((FermenterConversionDescriptor value) => value.InputPrefabId, StringComparer.Ordinal).ToList(); _conversions = list.AsReadOnly(); _byInput = list.ToDictionary<FermenterConversionDescriptor, string>((FermenterConversionDescriptor value) => value.InputPrefabId, StringComparer.Ordinal); _byOutput = list.ToDictionary<FermenterConversionDescriptor, string>((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<Smelter>() != (Object)null || (Object)(object)((Component)station).gameObject.GetComponent<CookingStation>() != (Object)null || ((Component)station).GetComponentsInChildren<CraftingStation>(true).Length != 0 || registeredPrefab.GetComponentsInChildren<CraftingStation>(true).Length != 0 || ((Component)station).GetComponentsInChildren<Container>(true).Length != 0 || registeredPrefab.GetComponentsInChildren<Container>(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<Fermenter>(); 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<Smelter>() != (Object)null || (Object)(object)((Component)registered).gameObject.GetComponent<CookingStation>() != (Object)null || ((Component)registered).GetComponentsInChildren<CraftingStation>(true).Length != 0 || ((Component)registered).GetComponentsInChildren<Container>(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<ItemConversion> source, out List<FermenterConversionDescriptor> descriptors, out string failure) { descriptors = new List<FermenterConversionDescriptor>(); 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<string> hashSet = new HashSet<string>(StringComparer.Ordinal); HashSet<string> hashSet2 = new HashSet<string>(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<ItemDrop>() : null) == (Object)null)) { GameObject obj2 = ValheimAccess.RegisteredItemPrefab(text2); if (!((Object)(object)((obj2 != null) ? obj2.GetComponent<ItemDrop>() : 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<Fermenter>(true).Length == 1 && root.GetComponent<Fermenter>() == station && root.GetComponentsInChildren<ZNetView>(true).Length == 1 && root.GetComponent<ZNetView>() == expectedView && root.GetComponentsInChildren<WearNTear>(true).Length == 1) { return (Object)(object)root.GetComponent<WearNTear>() != (Object)null; } return false; } private static bool RegisteredRootIsExact(GameObject root) { if ((Object)(object)root != (Object)null && root.GetComponentsInChildren<Fermenter>(true).Length == 1 && (Object)(object)root.GetComponent<Fermenter>() != (Object)null && root.GetComponentsInChildren<ZNetView>(true).Length == 1 && (Object)(object)root.GetComponent<ZNetView>() != (Object)null && root.GetComponentsInChildren<WearNTear>(true).Length == 1) { return (Object)(object)root.GetComponent<WearNTear>() != (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<FermenterConversionDescriptor> left, IEnumerable<FermenterConversionDescriptor> right) { FermenterConversionDescriptor[] array = left.OrderBy<FermenterConversionDescriptor, string>((FermenterConversionDescriptor value) => value.InputPrefabId, StringComparer.Ordinal).ToArray(); FermenterConversionDescriptor[] array2 = right.OrderBy<FermenterConversionDescriptor, string>((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<string> sortedSet = new SortedSet<string>(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<ReplenishmentTargetAuthorization> list = new List<ReplenishmentTargetAuthorization>(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 { Commi