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 RepairRequiresMaterials v1.0.0
RepairRequiresMaterials.dll
Decompiled 10 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using Microsoft.CodeAnalysis; using ServerSync; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Core.ObjectPool; using YamlDotNet.Core.Tokens; using YamlDotNet.Helpers; using YamlDotNet.Serialization; using YamlDotNet.Serialization.BufferedDeserialization; using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators; using YamlDotNet.Serialization.Callbacks; using YamlDotNet.Serialization.Converters; using YamlDotNet.Serialization.EventEmitters; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NodeDeserializers; using YamlDotNet.Serialization.NodeTypeResolvers; using YamlDotNet.Serialization.ObjectFactories; using YamlDotNet.Serialization.ObjectGraphTraversalStrategies; using YamlDotNet.Serialization.ObjectGraphVisitors; using YamlDotNet.Serialization.Schemas; using YamlDotNet.Serialization.TypeInspectors; using YamlDotNet.Serialization.TypeResolvers; using YamlDotNet.Serialization.Utilities; using YamlDotNet.Serialization.ValueDeserializers; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("RepairRequiresMaterials")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("sighsorry")] [assembly: AssemblyProduct("RepairRequiresMaterials")] [assembly: AssemblyCopyright("Copyright © 2021")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("4358610B-F3F4-4843-B7AF-98B7BC60DCDE")] [assembly: AssemblyFileVersion("1.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace RepairRequiresMaterials { internal static class AdminCommands { private const string SetDurabilityCommand = "rrm_setdurability"; private static bool _registered; internal static void Register() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_003d: Expected O, but got Unknown //IL_0038: Unknown result type (might be due to invalid IL or missing references) if (!_registered) { _registered = true; new ConsoleCommand("rrm_setdurability", "<0-100> - set all durability-bearing equipment in your inventory to a percentage of its quality-adjusted maximum", new ConsoleEventFailable(SetInventoryEquipmentDurability), false, false, false, false, false, new ConsoleOptionsFetcher(GetDurabilityOptions), false, false, false); } } private static object SetInventoryEquipmentDurability(ConsoleEventArgs args) { //IL_00a9: Unknown result type (might be due to invalid IL or missing references) float num = default(float); if (args.Length != 2 || !args.TryParameterFloat(1, ref num) || float.IsNaN(num) || float.IsInfinity(num) || num < 0f || num > 100f) { return "Usage: rrm_setdurability <0-100>"; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return "A local player is not available."; } if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.LocalPlayerIsAdminOrHost()) { return "Administrator or host privileges are required."; } Inventory inventory = ((Humanoid)localPlayer).GetInventory(); int num2 = 0; int num3 = 0; float num4 = num / 100f; foreach (ItemData allItem in inventory.GetAllItems()) { if (allItem == null || !EquipmentTypeRules.IsEquipment(allItem.m_shared.m_itemType) || !allItem.m_shared.m_useDurability) { continue; } float maxDurability = allItem.GetMaxDurability(); if (maxDurability > 0f && !float.IsNaN(maxDurability) && !float.IsInfinity(maxDurability)) { num2++; float num5 = Mathf.Clamp(maxDurability * num4, 0f, maxDurability); if (!allItem.m_durability.Equals(num5)) { allItem.m_durability = num5; num3++; } } } if (num3 > 0) { inventory.Changed(); } string text = num.ToString("0.##", CultureInfo.InvariantCulture); Terminal context = args.Context; if (context != null) { context.AddString(string.Format("{0}: set {1} of {2} eligible equipment items to {3}% durability.", "RepairRequiresMaterials", num3, num2, text)); } return true; } private static List<string> GetDurabilityOptions() { return new List<string> { "0", "25", "50", "75", "100" }; } } internal static class AzuCraftyBoxesCompat { private sealed class ContainerConsumption { internal object Container { get; } internal MethodInfo GetPrefabNameMethod { get; } internal MethodInfo ItemCountMethod { get; } internal MethodInfo ProcessInventoryMethod { get; } internal int Amount { get; } internal ContainerConsumption(object container, MethodInfo getPrefabNameMethod, MethodInfo itemCountMethod, MethodInfo processInventoryMethod, int amount) { Container = container; GetPrefabNameMethod = getPrefabNameMethod; ItemCountMethod = itemCountMethod; ProcessInventoryMethod = processInventoryMethod; Amount = amount; } } private sealed class RequirementConsumption { internal string ItemName { get; } internal string PrefabName { get; } internal int InventoryAmount { get; } internal List<ContainerConsumption> Containers { get; } = new List<ContainerConsumption>(); internal RequirementConsumption(string itemName, string prefabName, int inventoryAmount) { ItemName = itemName; PrefabName = prefabName; InventoryAmount = inventoryAmount; } } private const string DeferredKgDrawerTypeName = "AzuCraftyBoxes.IContainers.kgDrawer"; internal const string PluginGuid = "Azumatt.AzuCraftyBoxes"; private static bool _initialized; private static bool _available; private static bool _failureLogged; private static FieldInfo? _rangeField; private static MethodInfo? _queryFrameGetMethod; private static MethodInfo? _shouldPreventMethod; private static MethodInfo? _canItemBePulledMethod; private static MethodInfo? _checkAndDecrementMethod; internal static bool ShouldUseNearbyContainers() { try { return EnsureInitialized() && !ShouldPrevent(); } catch (Exception exception) { DisableAfterFailure("availability check", exception); return false; } } internal static bool TryCountAvailable(Player player, RepairMaterialCost cost, int currentAmount, out int totalAmount) { totalAmount = currentAmount; if (!_available) { return false; } try { Requirement sourceRequirement = cost.SourceRequirement; if ((Object)(object)sourceRequirement?.m_resItem == (Object)null) { return false; } IList nearbyContainers = GetNearbyContainers(player); if (nearbyContainers == null) { return false; } string name = sourceRequirement.m_resItem.m_itemData.m_shared.m_name; string resourcePrefabName = cost.ResourcePrefabName; foreach (object item in nearbyContainers) { if (item != null && TryGetContainerMethods(item, out MethodInfo getPrefabNameMethod, out MethodInfo itemCountMethod, out MethodInfo _) && CanPull(item, getPrefabNameMethod, resourcePrefabName)) { totalAmount += GetPullableAmount(item, itemCountMethod, name); } } return true; } catch (Exception exception) { DisableAfterFailure("container count", exception); totalAmount = currentAmount; return false; } } internal static bool TryConsume(Player player, IReadOnlyList<RepairMaterialCost> costs, out bool shouldCompleteRepair) { shouldCompleteRepair = false; if (!ShouldUseNearbyContainers()) { return false; } try { IList nearbyContainers = GetNearbyContainers(player); if (nearbyContainers == null) { return false; } Inventory inventory = ((Humanoid)player).GetInventory(); List<RequirementConsumption> list = new List<RequirementConsumption>(costs.Count); foreach (RepairMaterialCost cost in costs) { Requirement sourceRequirement = cost.SourceRequirement; if ((Object)(object)sourceRequirement?.m_resItem == (Object)null) { return false; } int num = Math.Max(0, cost.RequiredAmount); if (num <= 0) { continue; } string name = sourceRequirement.m_resItem.m_itemData.m_shared.m_name; string resourcePrefabName = cost.ResourcePrefabName; int num2 = Math.Min(num, inventory.CountItems(name, -1, true)); int num3 = num - num2; RequirementConsumption requirementConsumption = new RequirementConsumption(name, resourcePrefabName, num2); foreach (object item in nearbyContainers) { if (num3 <= 0) { break; } if (item != null && TryGetContainerMethods(item, out MethodInfo getPrefabNameMethod, out MethodInfo itemCountMethod, out MethodInfo processInventoryMethod) && CanPull(item, getPrefabNameMethod, resourcePrefabName)) { int num4 = Math.Min(num3, GetPullableAmount(item, itemCountMethod, name)); if (num4 > 0) { requirementConsumption.Containers.Add(new ContainerConsumption(item, getPrefabNameMethod, itemCountMethod, processInventoryMethod, num4)); num3 -= num4; } } } if (num3 > 0) { return false; } list.Add(requirementConsumption); } if (!ValidateConsumptionPlan(inventory, list)) { return false; } return ExecuteConsumptionPlan(inventory, list, out shouldCompleteRepair); } catch (Exception exception) { DisableAfterFailure("material consumption", exception, shouldCompleteRepair); return false; } } private static bool ValidateConsumptionPlan(Inventory inventory, IEnumerable<RequirementConsumption> plan) { foreach (RequirementConsumption item in plan) { if (inventory.CountItems(item.ItemName, -1, true) < item.InventoryAmount) { return false; } foreach (ContainerConsumption container in item.Containers) { if (!CanPull(container.Container, container.GetPrefabNameMethod, item.PrefabName) || GetPullableAmount(container.Container, container.ItemCountMethod, item.ItemName) < container.Amount) { return false; } } } return true; } private static bool ExecuteConsumptionPlan(Inventory inventory, IEnumerable<RequirementConsumption> plan, out bool shouldCompleteRepair) { shouldCompleteRepair = false; foreach (RequirementConsumption item in plan) { if (item.InventoryAmount > 0) { int num = inventory.CountItems(item.ItemName, -1, true); int num2; try { inventory.RemoveItem(item.ItemName, item.InventoryAmount, -1, true); num2 = num - inventory.CountItems(item.ItemName, -1, true); } catch (Exception exception) { try { shouldCompleteRepair |= num - inventory.CountItems(item.ItemName, -1, true) > 0; } catch { shouldCompleteRepair = true; } DisableAfterFailure("player inventory material removal", exception, shouldCompleteRepair); return false; } shouldCompleteRepair |= num2 > 0; if (num2 != item.InventoryAmount) { return DisableAfterConsumptionMismatch("player inventory changed during material removal", shouldCompleteRepair); } } foreach (ContainerConsumption container in item.Containers) { int rawContainerAmount = GetRawContainerAmount(container.Container, container.ItemCountMethod, item.ItemName); int num3; int rawContainerAmount2; try { num3 = Convert.ToInt32(container.ProcessInventoryMethod.Invoke(container.Container, new object[3] { item.ItemName, 0, container.Amount }) ?? ((object)0)); rawContainerAmount2 = GetRawContainerAmount(container.Container, container.ItemCountMethod, item.ItemName); } catch (Exception exception2) { try { shouldCompleteRepair |= rawContainerAmount - GetRawContainerAmount(container.Container, container.ItemCountMethod, item.ItemName) > 0; } catch { shouldCompleteRepair = true; } DisableAfterFailure("container material removal", exception2, shouldCompleteRepair); return false; } int num4 = rawContainerAmount - rawContainerAmount2; shouldCompleteRepair |= num4 > 0; if (num3 != container.Amount || num4 != container.Amount) { return DisableAfterConsumptionMismatch("container '" + GetContainerName(container) + "' did not remove the planned amount of " + item.ItemName, shouldCompleteRepair); } } } return true; } private static bool EnsureInitialized() { if (_initialized) { return _available; } _initialized = true; if (!Chainloader.PluginInfos.TryGetValue("Azumatt.AzuCraftyBoxes", out var value) || (Object)(object)value.Instance == (Object)null) { return false; } Assembly assembly = ((object)value.Instance).GetType().Assembly; Type type = assembly.GetType("AzuCraftyBoxes.AzuCraftyBoxesPlugin"); Type type2 = assembly.GetType("AzuCraftyBoxes.Util.Functions.Boxes"); Type type3 = assembly.GetType("AzuCraftyBoxes.Util.Functions.Boxes+QueryFrame"); Type type4 = assembly.GetType("AzuCraftyBoxes.Util.Functions.MiscFunctions"); _rangeField = type?.GetField("mRange", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _queryFrameGetMethod = type3?.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo method) => method.Name == "Get" && method.IsGenericMethodDefinition); _shouldPreventMethod = type4?.GetMethod("ShouldPrevent", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _canItemBePulledMethod = type2?.GetMethod("CanItemBePulled", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _checkAndDecrementMethod = type2?.GetMethod("CheckAndDecrement", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _available = type != null && type2 != null && type3 != null && type4 != null && _rangeField != null && _queryFrameGetMethod != null && _shouldPreventMethod != null && _canItemBePulledMethod != null && _checkAndDecrementMethod != null; if (!_available && !_failureLogged) { _failureLogged = true; RepairRequiresMaterialsPlugin.Log.LogWarning((object)"AzuCraftyBoxes was found, but its compatible container API was not available. Nearby-container repair support is disabled."); } return _available; } private static bool TryGetContainerMethods(object container, out MethodInfo getPrefabNameMethod, out MethodInfo itemCountMethod, out MethodInfo processInventoryMethod) { Type type = container.GetType(); getPrefabNameMethod = type.GetMethod("GetPrefabName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); itemCountMethod = type.GetMethod("ItemCount", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); processInventoryMethod = type.GetMethod("ProcessContainerInventory", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (getPrefabNameMethod != null && itemCountMethod != null && processInventoryMethod != null) { return SupportsVerifiedRemoval(container); } return false; } private static bool CanPull(object container, MethodInfo getPrefabNameMethod, string prefabName) { string text = Convert.ToString(getPrefabNameMethod.Invoke(container, null)) ?? string.Empty; return Convert.ToBoolean(_canItemBePulledMethod.Invoke(null, new object[3] { text, prefabName, string.Empty }) ?? ((object)false)); } private static bool SupportsVerifiedRemoval(object container) { return !string.Equals(container.GetType().FullName, "AzuCraftyBoxes.IContainers.kgDrawer", StringComparison.Ordinal); } private static int GetPullableAmount(object container, MethodInfo itemCountMethod, string itemName) { int rawContainerAmount = GetRawContainerAmount(container, itemCountMethod, itemName); return Math.Max(0, Convert.ToInt32(_checkAndDecrementMethod.Invoke(null, new object[1] { rawContainerAmount }) ?? ((object)0))); } private static int GetRawContainerAmount(object container, MethodInfo itemCountMethod, string itemName) { return Math.Max(0, Convert.ToInt32(itemCountMethod.Invoke(container, new object[1] { itemName }) ?? ((object)0))); } private static string GetContainerName(ContainerConsumption container) { return Convert.ToString(container.GetPrefabNameMethod.Invoke(container.Container, null)) ?? "unknown"; } private static bool ShouldPrevent() { return Convert.ToBoolean(_shouldPreventMethod.Invoke(null, null) ?? ((object)true)); } private static IList? GetNearbyContainers(Player player) { object value = _rangeField.GetValue(null); float num = Convert.ToSingle((value?.GetType().GetProperty("Value"))?.GetValue(value) ?? ((object)0f)); return _queryFrameGetMethod.MakeGenericMethod(typeof(Player)).Invoke(null, new object[2] { player, num }) as IList; } private static bool DisableAfterConsumptionMismatch(string reason, bool repairWillComplete) { _available = false; if (!_failureLogged) { _failureLogged = true; string text = (repairWillComplete ? "The selected item will still be repaired because materials were already consumed." : "The repair was cancelled because no material removal was confirmed."); RepairRequiresMaterialsPlugin.Log.LogWarning((object)("AzuCraftyBoxes consumption verification failed; nearby-container repair support is disabled for this session. " + text + " Reason: " + reason)); } return false; } private static void DisableAfterFailure(string operation, Exception exception, bool repairWillComplete = false) { _available = false; if (!_failureLogged) { _failureLogged = true; Exception ex = ((exception is TargetInvocationException && exception.InnerException != null) ? exception.InnerException : exception); string text = (repairWillComplete ? " The selected item will still be repaired because consumption may already have started." : string.Empty); RepairRequiresMaterialsPlugin.Log.LogWarning((object)("AzuCraftyBoxes " + operation + " failed; nearby-container repair support is disabled for this session: " + ex.Message + "." + text)); } } } [HarmonyPatch] internal static class PlayerCraftingEquipSpeedPatch { private readonly struct QueueState { internal readonly MinorActionData? ExistingAction; internal readonly ActionType ActionType; internal QueueState(MinorActionData? existingAction, ActionType actionType) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) ExistingAction = existingAction; ActionType = actionType; } } private static bool _missingTargetWarningLogged; private static IEnumerable<MethodBase> TargetMethods() { MethodBase methodBase = AccessTools.Method(typeof(Player), "QueueEquipAction", new Type[1] { typeof(ItemData) }, (Type[])null); if (methodBase != null) { yield return methodBase; } else { LogMissingTarget("QueueEquipAction"); } MethodBase methodBase2 = AccessTools.Method(typeof(Player), "QueueUnequipAction", new Type[1] { typeof(ItemData) }, (Type[])null); if (methodBase2 != null) { yield return methodBase2; } else { LogMissingTarget("QueueUnequipAction"); } } private static void Prefix(Player __instance, ItemData item, MethodBase __originalMethod, out QueueState __state) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) ActionType actionType = (ActionType)(__originalMethod.Name == "QueueUnequipAction"); __state = new QueueState(FindAction(__instance, item, actionType), actionType); } [HarmonyPriority(0)] private static void Postfix(Player __instance, ItemData item, QueueState __state) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_0039: Unknown result type (might be due to invalid IL or missing references) if ((Object)__instance == (Object)null || __instance != Player.m_localPlayer || item == null) { return; } float value = RepairRequiresMaterialsPlugin.CraftingEquipTimeReductionAtLevel100.Value; if (value <= 0f || float.IsNaN(value)) { return; } MinorActionData val = FindAction(__instance, item, __state.ActionType); if (val != null && val != __state.ExistingAction) { val.m_duration = CalculateAdjustedDuration(val.m_duration, ((Character)__instance).GetSkillFactor((SkillType)107), value); if (val.m_duration < 1f) { val.m_startEffect = null; } } } private static MinorActionData? FindAction(Player player, ItemData? item, ActionType actionType) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (item == null) { return null; } for (int num = player.m_actionQueue.Count - 1; num >= 0; num--) { MinorActionData val = player.m_actionQueue[num]; if (val.m_type == actionType && val.m_item == item) { return val; } } return null; } private static float CalculateAdjustedDuration(float baseDuration, float skillFactor, float maximumReductionPercent) { if (float.IsNaN(baseDuration) || float.IsInfinity(baseDuration) || baseDuration <= 0f) { return 0f; } double num = ClampUnit(skillFactor); double num2 = ClampPercent(maximumReductionPercent) / 100.0; if (num <= 0.0 || num2 <= 0.0) { return baseDuration; } double num3 = Math.Max(0.0, 1.0 - num * num2); return (float)((double)baseDuration * num3); } private static double ClampUnit(float value) { if (float.IsNaN(value) || value <= 0f) { return 0.0; } if (!float.IsPositiveInfinity(value) && !(value >= 1f)) { return value; } return 1.0; } private static double ClampPercent(float value) { if (float.IsNaN(value) || value <= 0f) { return 0.0; } if (!float.IsPositiveInfinity(value) && !(value >= 100f)) { return value; } return 100.0; } private static void LogMissingTarget(string methodName) { if (!_missingTargetWarningLogged) { _missingTargetWarningLogged = true; RepairRequiresMaterialsPlugin.Log.LogWarning((object)("Crafting equip-time reduction was not fully applied because Player." + methodName + " was not found.")); } } } internal static class CraftingFreeRepairSystem { private enum TicketOutcome { None, Free, Paid } private sealed class TicketState { internal string ItemId { get; set; } internal ulong Cycle { get; set; } internal TicketOutcome Outcome { get; set; } internal string PlanFingerprint { get; set; } internal TicketState(string itemId, ulong cycle, TicketOutcome outcome, string planFingerprint) { ItemId = itemId; Cycle = cycle; Outcome = outcome; PlanFingerprint = planFingerprint; } internal string Serialize() { char c = Outcome switch { TicketOutcome.Free => 'F', TicketOutcome.Paid => 'P', _ => 'N', }; return string.Join("|", "v1", ItemId, Cycle.ToString(CultureInfo.InvariantCulture), c.ToString(), PlanFingerprint); } } private const string TicketKey = "sighsorry.RepairRequiresMaterials.SkillFreeRepairTicket"; private const string TicketSchema = "v1"; private const string RollDomain = "sighsorry.RepairRequiresMaterials.SkillFreeRepairRoll.v1"; private static readonly Dictionary<string, ulong> SessionMinimumCycles = new Dictionary<string, ulong>(StringComparer.Ordinal); internal static RepairPreview ResolvePreview(Player player, RepairPreview stationPreview) { if (stationPreview.PaymentKind != RepairPaymentKind.StationMaterials) { return stationPreview; } ItemData item = stationPreview.Item; Inventory inventory = ((Humanoid)player).GetInventory(); string text = BuildPlanFingerprint(stationPreview); bool flag = HasMaterialCost(stationPreview); TicketState ticketState = ReadState(item, inventory, out var corruptState); if (corruptState) { if (!flag) { return stationPreview; } ticketState = new TicketState(Guid.NewGuid().ToString("N"), 0uL, TicketOutcome.Paid, text); if (!WriteState(item, inventory, ticketState)) { return stationPreview; } return stationPreview.WithPayment(RepairPaymentKind.StationMaterials, ticketState.Serialize()); } if (ticketState != null) { NormalizeSessionCycle(item, inventory, ticketState); } if (ticketState != null && ticketState.Outcome == TicketOutcome.Paid) { return stationPreview.WithPayment(RepairPaymentKind.StationMaterials, ticketState.Serialize()); } if (ticketState != null && ticketState.Outcome == TicketOutcome.Free) { if (!string.Equals(ticketState.PlanFingerprint, text, StringComparison.Ordinal)) { ticketState.Outcome = TicketOutcome.Paid; if (!WriteState(item, inventory, ticketState)) { return stationPreview; } return stationPreview.WithPayment(RepairPaymentKind.StationMaterials, ticketState.Serialize()); } RepairPaymentKind paymentKind = ((!IsFeatureEnabled()) ? RepairPaymentKind.StationMaterials : RepairPaymentKind.CraftingSkillFree); return stationPreview.WithPayment(paymentKind, ticketState.Serialize()); } if (!flag || !IsFeatureEnabled()) { return stationPreview; } if (ticketState == null) { ticketState = new TicketState(Guid.NewGuid().ToString("N"), 0uL, TicketOutcome.None, string.Empty); } double num = CalculateFreeRepairChance(((Character)player).GetSkillFactor((SkillType)107), RepairRequiresMaterialsPlugin.CraftingSkillFreeRepairChanceAtLevel0.Value, RepairRequiresMaterialsPlugin.CraftingSkillFreeRepairChanceAtLevel100.Value); ticketState.Outcome = ((GetDeterministicRoll(ticketState.ItemId, ticketState.Cycle) < num) ? TicketOutcome.Free : TicketOutcome.Paid); ticketState.PlanFingerprint = text; if (!WriteState(item, inventory, ticketState)) { return stationPreview; } return stationPreview.WithPayment((ticketState.Outcome != TicketOutcome.Free) ? RepairPaymentKind.StationMaterials : RepairPaymentKind.CraftingSkillFree, ticketState.Serialize()); } internal static void CompleteSuccessfulRepair(Player player, RepairPreview preview) { if (!preview.HasRawMaterialCost || string.IsNullOrEmpty(preview.SkillFreeTicketToken)) { return; } try { if (!TryParseState(preview.SkillFreeTicketToken, out TicketState state) || state == null) { return; } ulong num = ((state.Cycle == ulong.MaxValue) ? 0 : (state.Cycle + 1)); string itemId = ((state.Cycle == ulong.MaxValue) ? Guid.NewGuid().ToString("N") : state.ItemId); if (!SessionMinimumCycles.TryGetValue(state.ItemId, out var value) || num > value) { SessionMinimumCycles[state.ItemId] = num; } ItemData item = preview.Item; Inventory inventory = ((Humanoid)player).GetInventory(); bool corruptState; TicketState ticketState = ReadState(item, inventory, out corruptState); if (ticketState != null && (!string.Equals(ticketState.ItemId, state.ItemId, StringComparison.Ordinal) || ticketState.Cycle != state.Cycle)) { RepairRequiresMaterialsPlugin.Log.LogWarning((object)"A skill-free repair ticket changed during a completed repair; the newer item state was preserved."); return; } TicketState state2 = new TicketState(itemId, num, TicketOutcome.None, string.Empty); if (!WriteState(item, inventory, state2)) { RepairRequiresMaterialsPlugin.Log.LogWarning((object)"A completed skill-free repair ticket could not be persisted; its cycle remains advanced for this session."); } } catch (Exception ex) { RepairRequiresMaterialsPlugin.Log.LogWarning((object)("Could not complete the skill-free repair ticket: " + ex.GetType().Name + ": " + ex.Message)); } } private static bool IsFeatureEnabled() { return RepairRequiresMaterialsPlugin.EnableCraftingSkillFreeRepairs.Value.IsOn(); } internal static double CalculateFreeRepairChance(float skillFactor, float chanceAtLevel0Percent, float chanceAtLevel100Percent) { double num = NormalizePercent(chanceAtLevel100Percent) / 100.0; double num2 = Math.Min(NormalizePercent(chanceAtLevel0Percent) / 100.0, num); double num3 = NormalizeSkillFactor(skillFactor); return num2 + (num - num2) * num3; } private static double NormalizeSkillFactor(float value) { if (float.IsNaN(value) || value <= 0f) { return 0.0; } if (!float.IsPositiveInfinity(value) && !(value >= 1f)) { return value; } return 1.0; } private static double NormalizePercent(float value) { if (float.IsNaN(value) || value <= 0f) { return 0.0; } if (!float.IsPositiveInfinity(value) && !(value >= 100f)) { return value; } return 100.0; } private static bool HasMaterialCost(RepairPreview preview) { return preview.Costs.Any((RepairMaterialCost cost) => cost.RequiredAmount > 0); } private static TicketState? ReadState(ItemData item, Inventory inventory, out bool corruptState) { corruptState = false; if (item.m_customData == null || !item.m_customData.TryGetValue("sighsorry.RepairRequiresMaterials.SkillFreeRepairTicket", out var value)) { return null; } if (TryParseState(value, out TicketState state)) { return state; } corruptState = true; try { item.m_customData.Remove("sighsorry.RepairRequiresMaterials.SkillFreeRepairTicket"); RepairService.MarkInventoryDirty(inventory); } catch { } return null; } private static bool TryParseState(string serialized, out TicketState? state) { state = null; string[] array = serialized.Split(new char[1] { '|' }, 5); if (array.Length != 5 || !string.Equals(array[0], "v1", StringComparison.Ordinal) || !Guid.TryParseExact(array[1], "N", out var _) || !ulong.TryParse(array[2], NumberStyles.None, CultureInfo.InvariantCulture, out var result2) || array[3].Length != 1) { return false; } TicketOutcome ticketOutcome = array[3][0] switch { 'F' => TicketOutcome.Free, 'P' => TicketOutcome.Paid, 'N' => TicketOutcome.None, _ => (TicketOutcome)(-1), }; if (ticketOutcome < TicketOutcome.None || (ticketOutcome != TicketOutcome.None && array[4].Length == 0) || (ticketOutcome == TicketOutcome.None && array[4].Length != 0)) { return false; } state = new TicketState(array[1], result2, ticketOutcome, array[4]); return true; } private static void NormalizeSessionCycle(ItemData item, Inventory inventory, TicketState state) { if (SessionMinimumCycles.TryGetValue(state.ItemId, out var value) && state.Cycle < value) { state.Cycle = value; state.Outcome = TicketOutcome.None; state.PlanFingerprint = string.Empty; WriteState(item, inventory, state); } } private static bool WriteState(ItemData item, Inventory inventory, TicketState state) { try { if (item.m_customData == null) { item.m_customData = new Dictionary<string, string>(); } string text = state.Serialize(); if (item.m_customData.TryGetValue("sighsorry.RepairRequiresMaterials.SkillFreeRepairTicket", out var value) && string.Equals(value, text, StringComparison.Ordinal)) { return true; } item.m_customData["sighsorry.RepairRequiresMaterials.SkillFreeRepairTicket"] = text; RepairService.MarkInventoryDirty(inventory); return true; } catch (Exception ex) { RepairRequiresMaterialsPlugin.Log.LogWarning((object)("Could not persist a skill-free repair ticket: " + ex.GetType().Name + ": " + ex.Message)); return false; } } private static string BuildPlanFingerprint(RepairPreview preview) { StringBuilder stringBuilder = new StringBuilder(); AppendPart(stringBuilder, "plan-v1"); AppendPart(stringBuilder, ResolveItemPrefabName(preview.Item)); AppendPart(stringBuilder, preview.Item.m_quality.ToString(CultureInfo.InvariantCulture)); AppendPart(stringBuilder, preview.DurabilityBucketPercent.ToString(CultureInfo.InvariantCulture)); foreach (RepairMaterialCost item in preview.Costs.Where((RepairMaterialCost cost) => cost.RequiredAmount > 0).OrderBy<RepairMaterialCost, string>((RepairMaterialCost cost) => cost.ResourcePrefabName, StringComparer.Ordinal).ThenBy((RepairMaterialCost cost) => cost.RequiredAmount)) { AppendPart(stringBuilder, item.ResourcePrefabName); AppendPart(stringBuilder, item.RequiredAmount.ToString(CultureInfo.InvariantCulture)); } return Sha256Hex(stringBuilder.ToString()); } private static void AppendPart(StringBuilder builder, string value) { builder.Append(value.Length.ToString(CultureInfo.InvariantCulture)); builder.Append(':'); builder.Append(value); builder.Append('|'); } private static string ResolveItemPrefabName(ItemData item) { string text = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : item.m_shared.m_name)?.Trim() ?? string.Empty; if (!text.EndsWith("(Clone)", StringComparison.OrdinalIgnoreCase)) { return text; } return text.Substring(0, text.Length - "(Clone)".Length).Trim(); } private static double GetDeterministicRoll(string itemId, ulong cycle) { string s = string.Join("|", "sighsorry.RepairRequiresMaterials.SkillFreeRepairRoll.v1", itemId, cycle.ToString(CultureInfo.InvariantCulture)); byte[] array; using (SHA256 sHA = SHA256.Create()) { array = sHA.ComputeHash(Encoding.UTF8.GetBytes(s)); } ulong num = 0uL; for (int i = 0; i < 8; i++) { num = (num << 8) | array[i]; } return (double)(num >> 11) / 9007199254740992.0; } private static string Sha256Hex(string value) { byte[] array; using (SHA256 sHA = SHA256.Create()) { array = sHA.ComputeHash(Encoding.UTF8.GetBytes(value)); } StringBuilder stringBuilder = new StringBuilder(array.Length * 2); byte[] array2 = array; foreach (byte b in array2) { stringBuilder.Append(b.ToString("x2", CultureInfo.InvariantCulture)); } return stringBuilder.ToString(); } } internal static class CraftingProductionBonusSystem { internal const int UseVanillaBonus = -1; internal const string DefaultExcludedOutputPrefabPatterns = "Simple_*_Socket, Advanced_*_Socket, Perfect_*_Socket"; private const int MaximumIndependentRolls = 10000; private static bool _largeOutputWarningLogged; private static volatile PrefabPatternMatcher _excludedOutputPrefabs = PrefabPatternMatcher.Empty; internal static void SetExcludedOutputPrefabPatterns(string? patterns) { _excludedOutputPrefabs = PrefabPatternMatcher.Parse(patterns); } internal static float CalculatePerItemChance(float skillFactor, float bonusOutputChanceAtLevel100Percent) { double num = NormalizeNonNegative(skillFactor); double num2 = ClampLevel100ChancePercent(bonusOutputChanceAtLevel100Percent) / 100.0; if (num <= 0.0 || num2 <= 0.0) { return 0f; } return (float)ClampProbability(num * num2); } internal static int RollBonusItems(int baseItemCount, float itemChance, Func<float> nextRandomValue) { if (baseItemCount <= 0) { return 0; } int num = int.MaxValue - baseItemCount; if (num <= 0) { return 0; } double num2 = ClampProbability(itemChance); if (num2 <= 0.0) { return 0; } if (num2 >= 1.0) { return Math.Min(baseItemCount, num); } if (nextRandomValue == null) { throw new ArgumentNullException("nextRandomValue"); } int num3 = 0; for (int i = 0; i < baseItemCount; i++) { if (num3 >= num) { break; } if ((double)nextRandomValue() < num2) { num3++; } } return num3; } internal static int CalculateCraftingSkillBonusOrUseVanilla(InventoryGui gui, Player player, CraftingStation station, int baseItemCount) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Expected O, but got Unknown if ((Object)station == (Object)null || (int)station.m_craftingSkill != 107) { return -1; } if ((Object)player == (Object)null || baseItemCount <= 0) { return 0; } Recipe craftRecipe = gui.m_craftRecipe; if ((Object)craftRecipe == (Object)null || (Object)craftRecipe.m_item == (Object)null) { return 0; } string name = ((Object)((Component)craftRecipe.m_item).gameObject).name; if (_excludedOutputPrefabs.IsMatch(name) || craftRecipe.m_item.m_itemData.m_shared.m_maxStackSize <= 1) { return 0; } float value = RepairRequiresMaterialsPlugin.CraftingBonusOutputChanceAtLevel100.Value; float num = CalculatePerItemChance(((Character)player).GetSkillFactor((SkillType)107), value); if (num <= 0f) { return 0; } if (baseItemCount > 10000 && num < 1f) { if (!_largeOutputWarningLogged) { _largeOutputWarningLogged = true; RepairRequiresMaterialsPlugin.Log.LogWarning((object)($"A crafting result exceeded {10000} base items; " + "using Valheim's production-bonus calculation to avoid a long main-thread roll loop.")); } return -1; } return RollBonusItems(baseItemCount, num, NextRandomValue); } private static double NormalizeNonNegative(float value) { if (float.IsNaN(value) || value <= 0f) { return 0.0; } return value; } private static double ClampProbability(double value) { if (double.IsNaN(value) || value <= 0.0) { return 0.0; } if (!double.IsPositiveInfinity(value) && !(value >= 1.0)) { return value; } return 1.0; } private static double ClampLevel100ChancePercent(float value) { if (float.IsNaN(value) || value <= 0f) { return 0.0; } if (!float.IsPositiveInfinity(value) && !(value >= 25f)) { return value; } return 25.0; } private static float NextRandomValue() { return Random.value; } } [HarmonyPatch(typeof(InventoryGui), "DoCrafting")] [HarmonyPriority(0)] internal static class InventoryGuiCraftingProductionBonusPatch { private static readonly MethodInfo GetAmountMethod = AccessTools.Method(typeof(Recipe), "GetAmount", new Type[4] { typeof(int), typeof(int).MakeByRefType(), typeof(ItemData).MakeByRefType(), typeof(int) }, (Type[])null); private static readonly MethodInfo GetCurrentCraftingStationMethod = AccessTools.Method(typeof(Player), "GetCurrentCraftingStation", (Type[])null, (Type[])null); private static readonly MethodInfo BonusHelperMethod = AccessTools.Method(typeof(CraftingProductionBonusSystem), "CalculateCraftingSkillBonusOrUseVanilla", (Type[])null, (Type[])null); private static readonly MethodInfo RandomValueGetter = AccessTools.PropertyGetter(typeof(Random), "value"); private static readonly FieldInfo CraftUpgradeItemField = AccessTools.Field(typeof(InventoryGui), "m_craftUpgradeItem"); private static readonly FieldInfo CraftBonusChanceField = AccessTools.Field(typeof(InventoryGui), "m_craftBonusChance"); private static readonly FieldInfo CraftBonusAmountField = AccessTools.Field(typeof(InventoryGui), "m_craftBonusAmount"); private static bool _patternWarningLogged; [HarmonyTranspiler] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator) { List<CodeInstruction> list = new List<CodeInstruction>(instructions); try { if (!TryInject(list, generator, out string failure)) { LogPatternFailure(failure); } } catch (Exception arg) { LogPatternFailure($"unexpected transpiler error: {arg}"); } return list; } private static bool TryInject(List<CodeInstruction> codes, ILGenerator generator, out string failure) { //IL_02c1: Unknown result type (might be due to invalid IL or missing references) //IL_02c8: Expected O, but got Unknown //IL_02e2: Unknown result type (might be due to invalid IL or missing references) //IL_02ec: Expected O, but got Unknown //IL_02f3: Unknown result type (might be due to invalid IL or missing references) //IL_02fd: Expected O, but got Unknown //IL_032e: Unknown result type (might be due to invalid IL or missing references) //IL_0338: Expected O, but got Unknown //IL_0365: Unknown result type (might be due to invalid IL or missing references) //IL_036f: Expected O, but got Unknown //IL_037c: Unknown result type (might be due to invalid IL or missing references) //IL_0386: Expected O, but got Unknown //IL_03b3: Unknown result type (might be due to invalid IL or missing references) //IL_03bd: Expected O, but got Unknown //IL_03de: Unknown result type (might be due to invalid IL or missing references) //IL_03e8: Expected O, but got Unknown failure = string.Empty; int num = FindCall(codes, GetAmountMethod, 0); int num2 = FindCall(codes, GetCurrentCraftingStationMethod, 0); if (num < 0 || num2 < 0 || num >= num2) { failure = "could not locate Recipe.GetAmount and GetCurrentCraftingStation anchors"; return false; } if (num + 1 >= codes.Count || !TryGetStoredLocal(codes[num + 1], out var localIndex)) { failure = "could not resolve the pre-bonus result amount local"; return false; } int index = num2 + 1; int index2 = num2 + 2; int index3 = num2 + 3; int num3 = num2 + 4; if (num3 >= codes.Count || !TryGetStoredLocal(codes[index], out var localIndex2) || !IsLoadConstantZero(codes[index2]) || !TryGetStoredLocal(codes[index3], out var localIndex3) || !LoadsLocal(codes[num3], localIndex2)) { failure = "the vanilla crafting-bonus entry pattern changed"; return false; } int num4 = FindFieldLoad(codes, CraftBonusChanceField, num3); int num5 = FindFieldLoad(codes, CraftBonusAmountField, num3); int num6 = FindCall(codes, RandomValueGetter, num3); if (num6 <= num3 || num4 <= num6 || num5 <= num4) { failure = "could not locate the ordered vanilla crafting-bonus roll"; return false; } int num7 = num5 - 2; int index4 = num5 + 2; int index5 = num5 + 3; int index6 = num5 + 4; int num8 = num5 + 6; if (num7 < num3 || num8 >= codes.Count || !LoadsLocal(codes[num7], localIndex3) || codes[num5 - 1].opcode != OpCodes.Ldarg_0 || codes[num5 + 1].opcode != OpCodes.Add || !StoresLocal(codes[index4], localIndex3) || !LoadsLocal(codes[index5], localIndex) || !LoadsLocal(codes[index6], localIndex3) || codes[num5 + 5].opcode != OpCodes.Add || !StoresLocal(codes[num8], localIndex)) { failure = "the vanilla crafting-bonus accumulation pattern changed"; return false; } int num9 = FindFieldLoad(codes, CraftUpgradeItemField, num8 + 1); int num10 = num9 - 1; if (num9 <= num3 || num10 < 0 || num9 + 1 >= codes.Count || codes[num10].opcode != OpCodes.Ldarg_0 || !IsBranchTrue(codes[num9 + 1])) { failure = "could not locate the post-bonus inventory-capacity check"; return false; } if (num6 >= num10 || num4 >= num10 || num8 >= num10) { failure = "the vanilla crafting-bonus anchors crossed the capacity-check boundary"; return false; } Label label = generator.DefineLabel(); Label label2; if (codes[num10].labels.Count > 0) { label2 = codes[num10].labels[0]; } else { label2 = generator.DefineLabel(); codes[num10].labels.Add(label2); } CodeInstruction val = new CodeInstruction(OpCodes.Ldc_I4_0, (object)null); val.labels.Add(label); List<CodeInstruction> list = new List<CodeInstruction> { new CodeInstruction(OpCodes.Ldarg_0, (object)null), new CodeInstruction(OpCodes.Ldarg_1, (object)null), CloneWithoutMetadata(codes[num3]), CloneWithoutMetadata(codes[index5]), new CodeInstruction(OpCodes.Call, (object)BonusHelperMethod), CloneWithoutMetadata(codes[index3]), CloneWithoutMetadata(codes[index6]), new CodeInstruction(OpCodes.Ldc_I4_0, (object)null), new CodeInstruction(OpCodes.Blt, (object)label), CloneWithoutMetadata(codes[index5]), CloneWithoutMetadata(codes[index6]), new CodeInstruction(OpCodes.Add, (object)null), CloneWithoutMetadata(codes[num + 1]), new CodeInstruction(OpCodes.Br, (object)label2), val, CloneWithoutMetadata(codes[index3]) }; list[0].labels.AddRange(codes[num3].labels); codes[num3].labels.Clear(); list[0].blocks.AddRange(codes[num3].blocks); codes[num3].blocks.Clear(); codes.InsertRange(num3, list); return true; } private static int FindCall(List<CodeInstruction> codes, MethodInfo method, int startIndex) { for (int i = Math.Max(0, startIndex); i < codes.Count; i++) { if ((codes[i].opcode == OpCodes.Call || codes[i].opcode == OpCodes.Callvirt) && object.Equals(codes[i].operand, method)) { return i; } } return -1; } private static int FindFieldLoad(List<CodeInstruction> codes, FieldInfo field, int startIndex) { for (int i = Math.Max(0, startIndex); i < codes.Count; i++) { if ((codes[i].opcode == OpCodes.Ldfld || codes[i].opcode == OpCodes.Ldsfld) && object.Equals(codes[i].operand, field)) { return i; } } return -1; } private static bool LoadsLocal(CodeInstruction instruction, int localIndex) { if (TryGetLocalIndex(instruction, load: true, out var localIndex2)) { return localIndex2 == localIndex; } return false; } private static bool StoresLocal(CodeInstruction instruction, int localIndex) { if (TryGetLocalIndex(instruction, load: false, out var localIndex2)) { return localIndex2 == localIndex; } return false; } private static bool TryGetStoredLocal(CodeInstruction instruction, out int localIndex) { return TryGetLocalIndex(instruction, load: false, out localIndex); } private static bool TryGetLocalIndex(CodeInstruction instruction, bool load, out int localIndex) { localIndex = -1; OpCode opcode = instruction.opcode; if (load) { if (opcode == OpCodes.Ldloc_0) { localIndex = 0; return true; } if (opcode == OpCodes.Ldloc_1) { localIndex = 1; return true; } if (opcode == OpCodes.Ldloc_2) { localIndex = 2; return true; } if (opcode == OpCodes.Ldloc_3) { localIndex = 3; return true; } if (opcode != OpCodes.Ldloc && opcode != OpCodes.Ldloc_S) { return false; } } else { if (opcode == OpCodes.Stloc_0) { localIndex = 0; return true; } if (opcode == OpCodes.Stloc_1) { localIndex = 1; return true; } if (opcode == OpCodes.Stloc_2) { localIndex = 2; return true; } if (opcode == OpCodes.Stloc_3) { localIndex = 3; return true; } if (opcode != OpCodes.Stloc && opcode != OpCodes.Stloc_S) { return false; } } object operand = instruction.operand; if (!(operand is LocalBuilder localBuilder)) { if (!(operand is LocalVariableInfo localVariableInfo)) { if (!(operand is byte b)) { if (operand is int num) { localIndex = num; return true; } return false; } localIndex = b; return true; } localIndex = localVariableInfo.LocalIndex; return true; } localIndex = localBuilder.LocalIndex; return true; } private static bool IsLoadConstantZero(CodeInstruction instruction) { if (!(instruction.opcode == OpCodes.Ldc_I4_0) && (!(instruction.opcode == OpCodes.Ldc_I4) || !object.Equals(instruction.operand, 0))) { if (instruction.opcode == OpCodes.Ldc_I4_S) { return Convert.ToInt32(instruction.operand) == 0; } return false; } return true; } private static bool IsBranchTrue(CodeInstruction instruction) { if (!(instruction.opcode == OpCodes.Brtrue)) { return instruction.opcode == OpCodes.Brtrue_S; } return true; } private static CodeInstruction CloneWithoutMetadata(CodeInstruction source) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown return new CodeInstruction(source.opcode, source.operand); } private static void LogPatternFailure(string reason) { if (!_patternWarningLogged) { _patternWarningLogged = true; RepairRequiresMaterialsPlugin.Log.LogWarning((object)("Per-item Crafting bonus patch was not applied; vanilla behavior remains active (" + reason + ").")); } } } internal static class CraftingSkillTooltipText { internal const string HeadingToken = "$rrm_skill_crafting_heading"; internal const string FreeRepairToken = "$rrm_skill_crafting_free_repair"; internal const string BonusOutputToken = "$rrm_skill_crafting_bonus_output"; internal const string EquipSpeedToken = "$rrm_skill_crafting_equip_speed"; internal static string Append(string? original, bool freeRepairEnabled, float freeRepairChanceAtLevel0, float freeRepairChanceAtLevel100, float bonusOutputChanceAtLevel100, float equipTimeReductionAtLevel100) { if (original == null) { original = string.Empty; } if (HasRepairRequiresMaterialsHeading(original)) { return original; } float value = (float)(CraftingFreeRepairSystem.CalculateFreeRepairChance(0f, freeRepairChanceAtLevel0, freeRepairChanceAtLevel100) * 100.0); float num = (float)(CraftingFreeRepairSystem.CalculateFreeRepairChance(1f, freeRepairChanceAtLevel0, freeRepairChanceAtLevel100) * 100.0); float num2 = NormalizePercent(bonusOutputChanceAtLevel100, 25f); float num3 = NormalizePercent(equipTimeReductionAtLevel100, 100f); bool flag = freeRepairEnabled && num > 0f; bool flag2 = num2 > 0f; bool flag3 = num3 > 0f; if (!flag && !flag2 && !flag3) { return original; } StringBuilder stringBuilder = new StringBuilder("$rrm_skill_crafting_heading"); if (flag) { stringBuilder.Append('\n').Append(RepairRequiresMaterialsLocalization.Localize("$rrm_skill_crafting_free_repair", FormatPercent(value), FormatPercent(num))); } if (flag2) { stringBuilder.Append('\n').Append(RepairRequiresMaterialsLocalization.Localize("$rrm_skill_crafting_bonus_output", FormatPercent(num2))); } if (flag3) { stringBuilder.Append('\n').Append(RepairRequiresMaterialsLocalization.Localize("$rrm_skill_crafting_equip_speed", FormatPercent(num3))); } if (original.Length <= 0) { return stringBuilder.ToString(); } return original + "\n\n" + stringBuilder; } internal static bool MatchesSkillDescription(string? tooltipText, string? skillDescription) { if (!string.IsNullOrWhiteSpace(tooltipText) && !string.IsNullOrWhiteSpace(skillDescription)) { return tooltipText.IndexOf(skillDescription, StringComparison.Ordinal) >= 0; } return false; } internal static bool HasRepairRequiresMaterialsHeading(string? tooltipText) { if (!string.IsNullOrEmpty(tooltipText)) { return tooltipText.IndexOf("$rrm_skill_crafting_heading", StringComparison.Ordinal) >= 0; } return false; } private static float NormalizePercent(float value, float maximum) { if (float.IsNaN(value) || value <= 0f) { return 0f; } if (!float.IsPositiveInfinity(value) && !(value >= maximum)) { return value; } return maximum; } private static string FormatPercent(float value) { return value.ToString("0.##", CultureInfo.InvariantCulture); } } [HarmonyPatch(typeof(UITooltip), "UpdateTextElements")] internal static class CraftingSkillTooltipAlignmentPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(UITooltip __instance) { if ((Object)(object)__instance == (Object)null || !CraftingSkillTooltipText.HasRepairRequiresMaterialsHeading(__instance.m_text) || ((Object)(object)UITooltip.m_current != (Object)null && (Object)(object)UITooltip.m_current != (Object)(object)__instance) || (Object)(object)UITooltip.m_tooltip == (Object)null) { return; } TMP_Text[] componentsInChildren = UITooltip.m_tooltip.GetComponentsInChildren<TMP_Text>(true); foreach (TMP_Text val in componentsInChildren) { if ((Object)(object)val != (Object)null && string.Equals(((Object)val).name, "Text", StringComparison.Ordinal)) { val.horizontalAlignment = (HorizontalAlignmentOptions)1; break; } } } } [HarmonyPatch(typeof(SkillsDialog), "Setup")] internal static class CraftingSkillTooltipPatch { private static bool _failureLogged; [HarmonyPostfix] [HarmonyPriority(0)] [HarmonyAfter(new string[] { "randyknapp.mods.epicloot" })] private static void Postfix(SkillsDialog __instance, Player player) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Invalid comparison between Unknown and I4 //IL_0128: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || (Object)(object)player == (Object)null) { return; } try { Skills skills = ((Character)player).GetSkills(); List<Skill> list = ((skills != null) ? skills.GetSkillList() : null); if (list == null) { return; } Skill val = null; int craftingIndex = -1; for (int i = 0; i < list.Count; i++) { Skill val2 = list[i]; if (val2 != null && (int)(val2.m_info?.m_skill).GetValueOrDefault() == 107) { val = val2; craftingIndex = i; break; } } if (val?.m_info == null) { return; } UITooltip val3 = FindCraftingTooltip(__instance, craftingIndex, val.m_info.m_description); if (!((Object)(object)val3 == (Object)null)) { bool freeRepairEnabled = RepairRequiresMaterialsPlugin.EnableCraftingSkillFreeRepairs.Value.IsOn(); string text = CraftingSkillTooltipText.Append(val3.m_text, freeRepairEnabled, RepairRequiresMaterialsPlugin.CraftingSkillFreeRepairChanceAtLevel0.Value, RepairRequiresMaterialsPlugin.CraftingSkillFreeRepairChanceAtLevel100.Value, RepairRequiresMaterialsPlugin.CraftingBonusOutputChanceAtLevel100.Value, RepairRequiresMaterialsPlugin.CraftingEquipTimeReductionAtLevel100.Value); if (!string.Equals(text, val3.m_text, StringComparison.Ordinal)) { val3.Set(val3.m_topic, text, val3.m_anchor, val3.m_fixedPosition); } } } catch (Exception ex) { if (!_failureLogged) { _failureLogged = true; RepairRequiresMaterialsPlugin.Log.LogWarning((object)("Could not extend the Crafting skill tooltip: " + ex.GetBaseException().Message)); } } } private static UITooltip? FindCraftingTooltip(SkillsDialog dialog, int craftingIndex, string craftingDescription) { if (dialog.m_elements != null && craftingIndex >= 0 && craftingIndex < dialog.m_elements.Count) { GameObject obj = dialog.m_elements[craftingIndex]; UITooltip val = ((obj != null) ? obj.GetComponentInChildren<UITooltip>() : null); if ((Object)(object)val != (Object)null && CraftingSkillTooltipText.MatchesSkillDescription(val.m_text, craftingDescription)) { return val; } } InventoryGui componentInParent = ((Component)dialog).GetComponentInParent<InventoryGui>(); if ((Object)(object)componentInParent == (Object)null) { return null; } UITooltip[] componentsInChildren = ((Component)componentInParent).GetComponentsInChildren<UITooltip>(true); UITooltip val2 = null; UITooltip[] array = componentsInChildren; foreach (UITooltip val3 in array) { if ((Object)(object)val3 != (Object)null && CraftingSkillTooltipText.MatchesSkillDescription(val3.m_text, craftingDescription)) { if (((Component)val3).gameObject.activeInHierarchy) { return val3; } if (val2 == null) { val2 = val3; } } } return val2; } } internal static class EquipmentTypeRules { internal static bool IsEquipment(ItemType itemType) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected I4, but got Unknown return (itemType - 3) switch { 16 => true, 0 => true, 11 => true, 19 => true, 1 => true, 2 => true, 12 => true, 3 => true, 4 => true, 8 => true, 14 => true, 15 => true, 21 => true, _ => false, }; } } internal enum IncineratorDismantleResponse { Failed, Success, NoEligibleItems, NoRoom, Busy, NoAccess, InventoryChanged } internal sealed class IncineratorDismantleController : MonoBehaviour { private const string RequestRpc = "sighsorry.RepairRequiresMaterials.RequestIncineratorDismantle"; private const string ResponseRpc = "sighsorry.RepairRequiresMaterials.IncineratorDismantleResponse"; private const int RequestSchemaVersion = 1; private const int MaxKnownRecipeCount = 4096; private const int RequestHeaderBytes = 16; private const int RecipeTokenBytes = 16; private const int MaxRequestBytes = 65552; private Incinerator? _incinerator; private ZNetView? _nview; private bool _registered; private bool _operationInProgress; private void Start() { TryRegisterRpcs(); } private void OnDisable() { ReleaseOperation(); } internal void Initialize(Incinerator incinerator) { _incinerator = incinerator; _nview = ((Component)incinerator).GetComponent<ZNetView>(); TryRegisterRpcs(); } internal bool RequestDismantle(Player player) { TryRegisterRpcs(); if ((Object)(object)_nview == (Object)null || !_nview.IsValid() || !_nview.HasOwner()) { return false; } if (!TryBuildRequest(player, out ZPackage request) || request == null) { return false; } _nview.InvokeRPC("sighsorry.RepairRequiresMaterials.RequestIncineratorDismantle", new object[1] { request }); return true; } private void TryRegisterRpcs() { if (!_registered && !((Object)(object)_nview == (Object)null) && _nview.IsValid()) { _nview.Register<ZPackage>("sighsorry.RepairRequiresMaterials.RequestIncineratorDismantle", (Action<long, ZPackage>)RpcRequestDismantle); _nview.Register<int, int>("sighsorry.RepairRequiresMaterials.IncineratorDismantleResponse", (Action<long, int, int>)RpcDismantleResponse); _registered = true; } } private void RpcRequestDismantle(long senderUid, ZPackage request) { if ((Object)(object)_incinerator == (Object)null || (Object)(object)_nview == (Object)null || !_nview.IsValid() || !_nview.IsOwner()) { return; } if (!RepairRequiresMaterialsPlugin.EnableIncineratorDismantling.Value.IsOn()) { SendResponse(senderUid, IncineratorDismantleResponse.Failed); return; } if (_operationInProgress || _incinerator.isInUse || _incinerator.m_container.IsInUse() || _incinerator.m_container.m_loading) { SendResponse(senderUid, IncineratorDismantleResponse.Busy); return; } if (!TryReadRequestHeader(request, out var playerId, out var knownRecipeCount)) { SendResponse(senderUid, IncineratorDismantleResponse.Failed); return; } if (!TryValidateRequester(senderUid, playerId, requireProximity: true)) { SendResponse(senderUid, IncineratorDismantleResponse.NoAccess); return; } if (!TryReadKnownRecipeTokens(request, knownRecipeCount, out HashSet<IncineratorKnownRecipeToken> knownRecipeTokens) || knownRecipeTokens == null) { SendResponse(senderUid, IncineratorDismantleResponse.Failed); return; } _operationInProgress = true; _incinerator.isInUse = true; try { Inventory inventory = _incinerator.m_container.GetInventory(); IncineratorDismantleRollSeed rollSeed = IncineratorDismantleRollSeed.CreateRandom(); if (!IncineratorDismantleCostSystem.TryBuildPlan(inventory, knownRecipeTokens, rollSeed, out IncineratorDismantlePlan plan) || plan == null) { SendResponse(senderUid, IncineratorDismantleResponse.NoEligibleItems); ReleaseOperation(); } else if (!IncineratorDismantleCostSystem.CanApplyPlan(inventory, plan)) { SendResponse(senderUid, IncineratorDismantleResponse.NoRoom); ReleaseOperation(); } else { byte[] fingerprint = IncineratorDismantleCostSystem.GetFingerprint(inventory); ((MonoBehaviour)this).StartCoroutine(DismantleCoroutine(senderUid, playerId, fingerprint, knownRecipeTokens, rollSeed)); } } catch (Exception ex) { RepairRequiresMaterialsPlugin.Log.LogError((object)("Could not start incinerator dismantling: " + ex.GetType().Name + ": " + ex.Message)); SendResponse(senderUid, IncineratorDismantleResponse.Failed); ReleaseOperation(); } } private IEnumerator DismantleCoroutine(long senderUid, long playerId, byte[] expectedFingerprint, HashSet<IncineratorKnownRecipeToken> knownRecipeTokens, IncineratorDismantleRollSeed rollSeed) { bool leverPulled = false; try { if ((Object)(object)_incinerator == (Object)null || (Object)(object)_nview == (Object)null) { yield break; } _nview.InvokeRPC(ZNetView.Everybody, "RPC_AnimateLever", Array.Empty<object>()); leverPulled = true; _incinerator.m_leverEffects.Create(((Component)this).transform.position, ((Component)this).transform.rotation, (Transform)null, 1f, -1); yield return (object)new WaitForSeconds(Random.Range(_incinerator.m_effectDelayMin, _incinerator.m_effectDelayMax)); _nview.InvokeRPC(ZNetView.Everybody, "RPC_AnimateLeverReturn", Array.Empty<object>()); leverPulled = false; if (!_operationInProgress || !TryValidateRequester(senderUid, playerId, requireProximity: false) || _incinerator.m_container.IsInUse() || _incinerator.m_container.m_loading) { SendResponse(senderUid, IncineratorDismantleResponse.NoAccess); yield break; } Inventory inventory = _incinerator.m_container.GetInventory(); if (!expectedFingerprint.SequenceEqual(IncineratorDismantleCostSystem.GetFingerprint(inventory))) { SendResponse(senderUid, IncineratorDismantleResponse.InventoryChanged); yield break; } if (!IncineratorDismantleCostSystem.TryBuildPlan(inventory, knownRecipeTokens, rollSeed, out IncineratorDismantlePlan plan) || plan == null) { SendResponse(senderUid, IncineratorDismantleResponse.NoEligibleItems); yield break; } if (!IncineratorDismantleCostSystem.CanApplyPlan(inventory, plan)) { SendResponse(senderUid, IncineratorDismantleResponse.NoRoom); yield break; } if (!IncineratorDismantleCostSystem.TryApplyPlan(_incinerator.m_container, inventory, plan)) { SendResponse(senderUid, IncineratorDismantleResponse.Failed); yield break; } if ((Object)(object)_incinerator.m_lightingAOEs != (Object)null) { Object.Instantiate<GameObject>(_incinerator.m_lightingAOEs, ((Component)this).transform.position, ((Component)this).transform.rotation); } SendResponse(senderUid, IncineratorDismantleResponse.Success, plan.SourceUnitCount); yield return (object)new WaitForSeconds(4f); } finally { IncineratorDismantleController incineratorDismantleController = this; if (leverPulled && (Object)(object)incineratorDismantleController._nview != (Object)null && incineratorDismantleController._nview.IsValid()) { incineratorDismantleController._nview.InvokeRPC(ZNetView.Everybody, "RPC_AnimateLeverReturn", Array.Empty<object>()); } incineratorDismantleController.ReleaseOperation(); } } private bool TryValidateRequester(long senderUid, long playerId, bool requireProximity) { //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_incinerator == (Object)null || (Object)(object)_nview == (Object)null || !_nview.IsValid() || !_nview.IsOwner() || !_incinerator.m_container.IsOwner() || playerId == 0L) { return false; } Player player = Player.GetPlayer(playerId); if ((Object)(object)player == (Object)null || ((Character)player).IsDead() || (Object)(object)((Character)player).m_nview == (Object)null || !((Character)player).m_nview.IsValid() || ((Character)player).GetOwner() != senderUid) { return false; } if (requireProximity) { Transform val = (((Object)(object)_incinerator.m_incinerateSwitch != (Object)null) ? ((Component)_incinerator.m_incinerateSwitch).transform : ((Component)this).transform); float num = Math.Max(0f, player.m_maxInteractDistance) + 1f; if (Vector3.Distance(((Character)player).GetEyePoint(), val.position) > num) { return false; } } if (!_incinerator.m_container.CheckAccess(playerId) || !HasWardAccess(((Component)this).transform.position, playerId)) { return false; } return true; } private bool TryBuildRequest(Player player, out ZPackage? request) { //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Expected O, but got Unknown request = null; if ((Object)(object)player == (Object)null || player.GetPlayerID() == 0L || (Object)(object)_incinerator == (Object)null || (Object)(object)_incinerator.m_container == (Object)null || (Object)(object)_incinerator.m_container.m_nview == (Object)null || !_incinerator.m_container.m_nview.IsValid()) { return false; } HashSet<IncineratorKnownRecipeToken> hashSet = new HashSet<IncineratorKnownRecipeToken>(); _incinerator.m_container.CheckForChanges(); foreach (ItemData allItem in _incinerator.m_container.GetInventory().GetAllItems()) { string text = allItem?.m_shared?.m_name; if (IncineratorDismantleCostSystem.IsDismantleCandidate(allItem) && !string.IsNullOrEmpty(text) && player.IsRecipeKnown(text)) { hashSet.Add(IncineratorKnownRecipeToken.Create(text)); } } if (hashSet.Count > 4096) { RepairRequiresMaterialsPlugin.Log.LogWarning((object)$"Known-recipe dismantle request exceeded the {4096} recipe limit."); return false; } ZPackage val = new ZPackage(); val.Write(1); val.Write(player.GetPlayerID()); val.Write(hashSet.Count); foreach (IncineratorKnownRecipeToken item in from value in hashSet orderby value.First, value.Second select value) { val.Write(item.First); val.Write(item.Second); } if (val.Size() > 65552) { RepairRequiresMaterialsPlugin.Log.LogWarning((object)$"Known-recipe dismantle request exceeded the {65552}-byte limit."); return false; } request = val; return true; } private static bool TryReadRequestHeader(ZPackage request, out long playerId, out int knownRecipeCount) { playerId = 0L; knownRecipeCount = 0; if (request == null || request.Size() <= 0 || request.Size() > 65552) { return false; } try { request.SetPos(0); if (request.ReadInt() != 1) { return false; } playerId = request.ReadLong(); knownRecipeCount = request.ReadInt(); if (playerId == 0L || knownRecipeCount < 0 || knownRecipeCount > 4096) { return false; } int num = 16 + knownRecipeCount * 16; return request.Size() == num; } catch (Exception) { return false; } } private static bool TryReadKnownRecipeTokens(ZPackage request, int knownRecipeCount, out HashSet<IncineratorKnownRecipeToken>? knownRecipeTokens) { knownRecipeTokens = null; if (request == null || knownRecipeCount < 0 || knownRecipeCount > 4096 || request.Size() != 16 + knownRecipeCount * 16) { return false; } try { request.SetPos(16); HashSet<IncineratorKnownRecipeToken> hashSet = new HashSet<IncineratorKnownRecipeToken>(); for (int i = 0; i < knownRecipeCount; i++) { IncineratorKnownRecipeToken item = new IncineratorKnownRecipeToken(request.ReadULong(), request.ReadULong()); if (!hashSet.Add(item)) { return false; } } if (request.GetPos() != request.Size()) { return false; } knownRecipeTokens = hashSet; return true; } catch (Exception) { return false; } } private static bool HasWardAccess(Vector3 position, long playerId) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) bool flag = false; bool flag2 = false; foreach (PrivateArea item in PrivateArea.m_allAreas.ToList()) { if (!((Object)(object)item == (Object)null) && item.IsEnabled() && item.IsInside(position, 0f)) { bool flag3 = (Object)(object)item.m_piece != (Object)null && (item.m_piece.GetCreator() == playerId || item.IsPermitted(playerId)); flag = flag || flag3; flag2 = flag2 || !flag3; } } if (!flag) { return !flag2; } return true; } private void SendResponse(long targetUid, IncineratorDismantleResponse response, int dismantledCount = 0) { if ((Object)(object)_nview != (Object)null && _nview.IsValid()) { _nview.InvokeRPC(targetUid, "sighsorry.RepairRequiresMaterials.IncineratorDismantleResponse", new object[2] { (int)response, dismantledCount }); } } private static void RpcDismantleResponse(long senderUid, int responseValue, int dismantledCount) { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { ((Character)localPlayer).Message((MessageType)2, (IncineratorDismantleResponse)(Enum.IsDefined(typeof(IncineratorDismantleResponse), responseValue) ? responseValue : 0) switch { IncineratorDismantleResponse.Success => RepairRequiresMaterialsLocalization.Localize("$rrm_dismantle_success", Math.Max(0, dismantledCount)), IncineratorDismantleResponse.NoEligibleItems => "$rrm_dismantle_no_equipment", IncineratorDismantleResponse.NoRoom => "$rrm_dismantle_no_room", IncineratorDismantleResponse.Busy => "$rrm_dismantle_busy", IncineratorDismantleResponse.NoAccess => "$rrm_dismantle_no_access", IncineratorDismantleResponse.InventoryChanged => "$rrm_dismantle_inventory_changed", _ => "$rrm_dismantle_failed", }, 0, (Sprite)null); } } private void ReleaseOperation() { if (_operationInProgress && (Object)(object)_incinerator != (Object)null) { _incinerator.isInUse = false; } _operationInProgress = false; } } internal readonly struct IncineratorKnownRecipeToken : IEquatable<IncineratorKnownRecipeToken> { private const string HashDomain = "sighsorry.RepairRequiresMaterials.KnownDismantleRecipe.v1\0"; internal ulong First { get; } internal ulong Second { get; } internal IncineratorKnownRecipeToken(ulong first, ulong second) { First = first; Second = second; } internal static IncineratorKnownRecipeToken Create(string recipeName) { byte[] array; using (SHA256 sHA = SHA256.Create()) { array = sHA.ComputeHash(Encoding.UTF8.GetBytes("sighsorry.RepairRequiresMaterials.KnownDismantleRecipe.v1\0" + recipeName)); } ulong num = 0uL; ulong num2 = 0uL; for (int i = 0; i < 8; i++) { num = (num << 8) | array[i]; num2 = (num2 << 8) | array[i + 8]; } return new IncineratorKnownRecipeToken(num, num2); } public bool Equals(IncineratorKnownRecipeToken other) { if (First == other.First) { return Second == other.Second; } return false; } public override bool Equals(object? obj) { if (obj is IncineratorKnownRecipeToken other) { return Equals(other); } return false; } public override int GetHashCode() { return (First.GetHashCode() * 397) ^ Second.GetHashCode(); } } internal readonly struct IncineratorDismantleRollSeed { internal ulong First { get; } internal ulong Second { get; } internal IncineratorDismantleRollSeed(ulong first, ulong second) { First = first; Second = second; } internal static IncineratorDismantleRollSeed CreateRandom() { byte[] array = new byte[16]; using (RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create()) { randomNumberGenerator.GetBytes(array); } ulong num = 0uL; ulong num2 = 0uL; for (int i = 0; i < 8; i++) { num = (num << 8) | array[i]; num2 = (num2 << 8) | array[i + 8]; } return new IncineratorDismantleRollSeed(num, num2); } } internal sealed class IncineratorDismantleOutput { internal ItemDrop Resource { get; } internal string PrefabName { get; } internal int Amount { get; } internal IncineratorDismantleOutput(ItemDrop resource, string prefabName, int amount) { Resource = resource; PrefabName = prefabName; Amount = amount; } } internal sealed class IncineratorDismantlePlan { internal IReadOnlyList<ItemData> SourceItems { get; } internal IReadOnlyList<IncineratorDismantleOutput> Outputs { get; } internal int SourceUnitCount { get; } internal IncineratorDismantlePlan(IReadOnlyList<ItemData> sourceItems, IReadOnlyList<IncineratorDismantleOutput> outputs) { SourceItems = sourceItems; Outputs = outputs; int num = 0; foreach (ItemData sourceItem in sourceItems) { int num2 = Math.Max(0, sourceItem.m_stack); num = ((num >= int.MaxValue - num2) ? int.MaxValue : (num + num2)); } SourceUnitCount = num; } } internal static class IncineratorDismantleCostSystem { private sealed class RecipeMaterialAmount { internal ItemDrop Resource { get; } internal string PrefabName { get; } internal decimal BaseAmount { get; set; } internal decimal UpgradeAmount { get; set; } internal RecipeMaterialAmount(ItemDrop resource, string prefabName) { Resource = resource; PrefabName = prefabName; } } private sealed class RawMaterialTotal { internal ItemDrop Resource { get; } internal string PrefabName { get; } internal decimal Amount { get; set; } internal RawMaterialTotal(ItemDrop resource, string prefabName) { Resource = resource; PrefabName = prefabName; } } private sealed class ItemReferenceComparer : IEqualityComparer<ItemData> { internal static readonly ItemReferenceComparer Instance = new ItemReferenceComparer(); public bool Equals(ItemData? x, ItemData? y) { return x == y; } public int GetHashCode(ItemData obj) { return RuntimeHelpers.GetHashCode(obj); } } private const string FractionalReturnHashDomain = "sighsorry.RepairRequiresMaterials.DismantleFractionalReturn.v1\0"; private const decimal UInt64Range = 18446744073709551616m; private static volatile PrefabPatternMatcher _additionalDismantleablePrefabs = PrefabPatternMatcher.Empty; internal static void SetAdditionalDismantleablePrefabPatterns(string? patterns) { _additionalDismantleablePrefabs = PrefabPatternMatcher.Parse(patterns); } internal static bool TryBuildPlan(Inventory inventory, HashSet<IncineratorKnownRecipeToken>? knownRecipeTokens, IncineratorDismantleRollSeed rollSeed, out IncineratorDismantlePlan? plan) { plan = null; if (inventory == null) { return false; } decimal num = (decimal)Mathf.Clamp(RepairRequiresMaterialsPlugin.DismantleBaseReturnPercent.Value, 0f, 100f) / 100m; decimal num2 = (decimal)Mathf.Clamp(RepairRequiresMaterialsPlugin.DismantleUpgradeReturnPercent.Value, 0f, 100f) / 100m; if (num <= 0m && num2 <= 0m) { return false; } Dictionary<string, RawMaterialTotal> dictionary = new Dictionary<string, RawMaterialTotal>(StringComparer.Ordinal); List<ItemData> list = new List<ItemData>(); foreach (ItemData item in inventory.GetAllItems().ToList()) { if (!TryGetRecipeMaterials(item, knownRecipeTokens, out List<RecipeMaterialAmount> selectedMaterials) || selectedMaterials == null) { continue; } bool flag = false; foreach (RecipeMaterialAmount item2 in selectedMaterials) { decimal num3 = item2.BaseAmount * num + item2.UpgradeAmount * num2; if (!(num3 <= 0m)) { flag = true; if (!dictionary.TryGetValue(item2.PrefabName, out var value)) { value = new RawMaterialTotal(item2.Resource, item2.PrefabName); dictionary.Add(item2.PrefabName, value); } value.Amount += num3; } } if (flag) { list.Add(item); } } if (list.Count == 0) { return false; } List<IncineratorDismantleOutput> list2 = new List<IncineratorDismantleOutput>(dictionary.Count); foreach (RawMaterialTotal item3 in dictionary.Values.OrderBy<RawMaterialTotal, string>((RawMaterialTotal rawMaterialTotal) => rawMaterialTotal.PrefabName, StringComparer.Ordinal)) { decimal num4 = decimal.Floor(item3.Amount); int num5; if (num4 >= 2147483647m) { num5 = int.MaxValue; } else { num5 = decimal.ToInt32(num4); decimal num6 = item3.Amount - num4; if (num6 > 0m && ShouldRoundFractionUp(rollSeed, item3.PrefabName, num6)) { num5++; } } if (num5 > 0) { list2.Add(new IncineratorDismantleOutput(item3.Resource, item3.PrefabName, num5)); } } plan = new IncineratorDismantlePlan(list, list2); return true; } private static bool ShouldRoundFractionUp(IncineratorDismantleRollSeed rollSeed, string materialPrefabName, decimal fraction) { if (fraction <= 0m) { return false; } if (fraction >= 1m) { return true; } byte[] bytes = Encoding.UTF8.GetBytes("sighsorry.RepairRequiresMaterials.DismantleFractionalReturn.v1\0" + materialPrefabName); byte[] array = new byte[16 + bytes.Length]; WriteUInt64BigEndian(array, 0, rollSeed.First); WriteUInt64BigEndian(array, 8, rollSeed.Second); Buffer.BlockCopy(bytes, 0, array, 16, bytes.Length); byte[] array2; using (SHA256 sHA = SHA256.Create()) { array2 = sHA.ComputeHash(array); } ulong num = 0uL; for (int i = 0; i < 8; i++) { num = (num << 8) | array2[i]; } return (decimal)num / 18446744073709551616m < fraction; } private static void WriteUInt64BigEndian(byte[] destination, int offset, ulong value) { for (int num = 7; num >= 0; num--) { destination[offset + num] = (byte)value; value >>= 8; } } internal static bool CanApplyPlan(Inventory inventory, IncineratorDismantlePlan plan) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown Inventory val = new Inventory(inventory.GetName(), inventory.GetBkg(), inventory.GetWidth(), inventory.GetHeight()); HashSet<ItemData> hashSet = new HashSet<ItemData>(plan.SourceItems, ItemReferenceComparer.Instance); foreach (ItemData allItem in inventory.GetAllItems()) { if (!hashSet.Contains(allItem)) { val.GetAllItems().Add(allItem.Clone()); } } val.Changed(); foreach (IncineratorDismantleOutput output in plan.Outputs) { if (!HasCompatibleSharedName(val, output) || !TryAddOutput(val, output)) { return false; } } return true; } internal static bool TryApplyPlan(Container container, Inventory inventory, IncineratorDismantlePlan plan) { if ((Object)(object)container == (Object)null || inventory == null || container.m_loading) { return false; } List<ItemData> list = (from item in inventory.GetAllItems() select item.Clone()).ToList(); bool loading = container.m_loading; bool result = false; container.m_loading = true; try { foreach (ItemData sourceItem in plan.SourceItems) { if (!inventory.RemoveItem(sourceItem)) { throw new InvalidOperationException("The incinerator inventory changed before dismantling completed."); } } foreach (IncineratorDismantleOutput output in plan.Outputs) { if (!TryAddOutput(inventory, output)) { throw new InvalidOperationException("The incinerator could not accept dismantle output '" + output.PrefabName + "'."); } } result = true; } catch (Exception ex) { RepairRequiresMaterialsPlugin.Log.LogWarning((object)("Dismantle transaction rolled back: " + ex.GetType().Name + ": " + ex.Message)); inventory.GetAllItems().Clear(); foreach (ItemData item in list) { inventory.GetAllItems().Add(item); } } finally { container.m_loading = loading; inventory.Changed(); } return result; } internal static byte[] GetFingerprint(Inventory inventory) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); inventory.Save(val); return val.GetArray(); } internal static bool IsDismantleCandidate(ItemData? item) { string prefabName; bool isEquipment; return TryGetDismantleCandidatePrefab(item, out prefabName, out isEquipment); } private static bool TryGetRecipeMaterials(ItemData item, HashSet<IncineratorKnownRecipeToken>? knownRecipeTokens, out List<RecipeMaterialAmount>? selectedMaterials) { selectedMaterials = null; if (!TryGetDismantleCandidatePrefab(item, out string prefabName, out bool isEquipment) || string.IsNullOrEmpty(item.m_shared.m_name) || knownRecipeTokens == null || !knownRecipeTokens.Contains(IncineratorKnownRecipeToken.Create(item.m_shared.m_name))) { return false; } List<List<RecipeMaterialAmount>> list = new List<List<RecipeMaterialAmount>>(); foreach (Recipe recipe in RepairRecipeCatalog.GetRecipes(item)) { if (!((Object)(object)recipe == (Object)null) && recipe.m_enabled && recipe.m_amount > 0 && (!isEquipment || recipe.m_amount == 1) && !recipe.m_requireOnlyOneIngredient && TryBuildRecipeMaterialAmounts(item, prefabName, recipe, out List<RecipeMaterialAmount> materials) && materials != null) { list.Add(materials); } } if (list.Count == 0) { return false; } List<RecipeMaterialAmount> list2 = list[0]; for (int i = 1; i < list.Count; i++) { if (!HaveEquivalentMaterialCosts(list2, list[i])) { return false; } } selectedMaterials = list2; return true; } private static bool TryBuildRecipeMaterialAmounts(ItemData item, string itemPrefabName, Recipe recipe, out List<RecipeMaterialAmount>? materials) { materials = null; Dictionary<string, RecipeMaterialAmount> dictionary = new Dictionary<string, RecipeMaterialAmount>(StringComparer.Ordinal); int val = Math.Max(1, item.m_shared.m_maxQuality); int num = Math.Max(1, Math.Min(item.m_quality, val)); int sourceStack = Math.Max(1, item.m_stack); int amount = recipe.m_amount; Requirement[] array = recipe.m_resources ?? Array.Empty<Requirement>(); foreach (Requirement val2 in array) { if (val2 == null || (Object)(object)val2.m_resItem == (Object)null) { return false; } ItemDrop resItem = val2.m_resItem; string text = ResolveItemDropPrefabName(resItem); if (text.Length == 0 || string.Equals(text, itemPrefabName, StringComparison.Ordinal)) { return false; } decimal num2 = ScaleRecipeAmount(Math.Max(0, val2.GetAmount(1)), sourceStack, amount); decimal amountPerCraft = default(decimal); for (int j = 2; j <= num; j++) { amountPerCraft += (decimal)Math.Max(0, val2.GetAmount(j)); } decimal num3 = ScaleRecipeAmount(amountPerCraft, sourceStack, amount); if (!(num2 <= 0m) || !(num3 <= 0m)) { if (!dictionary.TryGetValue(text, out var value)) { value = new RecipeMaterialAmount(resItem, text); dictionary.Add(text, value); } value.BaseAmount += num2; value.UpgradeAmount += num3; } } if (dictionary.Count == 0) { return false; } materials = dictionary.Values.OrderBy<RecipeMaterialAmount, string>((RecipeMaterialAmount material) => material.PrefabName, StringComparer.Ordinal).ToList(); return true; } internal static decimal ScaleRecipeAmount(decimal amountPerCraft, int sourceStack, int recipeOutputAmount) { if (!(amountPerCraft <= 0m) && sourceStack > 0 && recipeOutputAmount > 0) { return amountPerCraft * (decimal)sourceStack / (decimal)recipeOutputAmount; } return 0m; } private static bool HaveEquivalentMaterialCosts(IReadOnlyList<RecipeMaterialAmount> first, IReadOnlyList<RecipeMaterialAmount> second) { if (first.Count != second.Count) { return false; } for (int i = 0; i < first.Count; i++) { RecipeMaterialAmount recipeMaterialAmount = first[i]; RecipeMaterialAmount recipeMaterialAmount2 = second[i]; if (!string.Equals(recipeMaterialAmount.PrefabName, recipeMaterialAmount2.PrefabName, StringComparison.Ordinal) || recipeMaterialAmount.BaseAmount != recipeMaterialAmount2.BaseAmount || recipeMaterialAmount.UpgradeAmount != recipeMaterialAmount2.UpgradeAmount) { return false; } } return true; } private static bool HasCompatibleSharedName(Inventory inventory, IncineratorDismantleOutput output) { string name = output.Resource.m_itemData.m_shared.m_name; foreach (ItemData allItem in inventory.GetAllItems()) { if (string.Equals(allItem.m_shared.m_name, name, StringComparison.Ordinal) && !string.Equals(CleanPrefabName(((Object)(object)allItem.m_dropPrefab != (Object)null) ? ((Object)allItem.m_dropPrefab).name : string.Empty), output.PrefabName, StringComparison.Ordinal)) { return false; } } return true; } private static bool TryAddOutput(Inventory inventory, IncineratorDismantleOutput output) { GameObject gameObject = ((Component)output.Resource).gameObject; int val = Math.Max(1, output.Resource.m_itemData.m_shared.m_maxStackSize); int num = output.Amount; while (num > 0) { int num2 = Math.Min(num, val); if (!inventory.AddItem(gameObject, num2)) { return false; } num -= num2; } return true; } private static bool IsBlacklisted(string prefabName) { string value = RepairRequiresMaterialsPlugin.DismantleBlacklist.Value; if (string.IsNullOrWhiteSpace(value)) { return false; } string[] array = value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { if (string.Equals(CleanPrefabName(array[i]), prefabName, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static bool TryGetDismantleCandidatePrefab(ItemData? item, out string prefabName, out bool isEquipment) { //IL_0067: Unknown result type (might be due to invalid IL or missing references) prefabName = string.Empty; isEquipment = false; if (item == null || item.m_shared.m_questItem || item.m_stack <= 0) { return false; } prefabName = CleanPrefabName(((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : string.Empty); if (prefabName.Length == 0 || IsBlacklisted(prefabName)) { return false; } isEquipment = EquipmentTypeRules.IsEquipment(item.m_shared.m_itemType); if (isEquipment) { if (item.m_shared.m_maxStackSize == 1) { return item.m_stack == 1; } return false; } return _additionalDismantleablePrefabs.IsMatch(prefabName); } private static string ResolveItemDropPrefabName(ItemDrop itemDrop) { string text = (((Object)(object)itemDrop.m_itemData.m_dropPrefab != (Object)null) ? ((Object)itemDrop.m_itemData.m_dropPrefab).name : string.Empty); return CleanPrefabName(string.IsNullOrWhiteSpace(text) ? ((Object)itemDrop).name : text); } private static string CleanPrefabName(string? value) { string text = value?.Trim() ?? string.Empty; if (!text.EndsWith("(Clone)", StringComparison.OrdinalIgnoreCase)) { return text; } return text.Substring(0, text.Length - "(Clone)".Length).Trim(); } } [HarmonyPatch] internal static class IncineratorBuildRecipeLifecyclePatch { private static IEnumerable<MethodBase> TargetMethods() { yield return AccessTools.Method(typeof(ZNetScene), "Awake", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(ObjectDB), "UpdateRegisters", (Type[])null, (Type[])null); } [HarmonyPriority(0)] private static void Postfix() { IncineratorBuildRecipeSystem.Apply(); } } internal static class IncineratorBuildRecipeSystem { internal const string DefaultRecipe = "Iron:8,Copper:4,Thunderstone:1"; private const string IncineratorPrefabName = "incinerator"; private static Piece? _trackedPiece; private static Requirement[] _originalRequirements = Array.Empty<Requirement>(); private static string _lastWarningKey = string.Empty; internal static void Apply() { ZNetScene instance = ZNetScene.instance; if ((Object)(object)instance == (Object)null) { return; } GameObject prefab = instance.GetPrefab("incinerator"); if ((Object)(object)prefab == (Object)null) { return; } Piece component = prefab.GetComponent<Piece>(); if ((Object)(object)component == (Object)null || (Object)(object)prefab.GetComponent<Incinerator>() == (Object)null) { return; } TrackOriginalRecipe(component); ObjectDB instance2 = ObjectDB.instance; if (!((Object)(object)instance2 == (Object)null)) { string text = RepairRequiresMaterialsPlugin.IncineratorBuildRecipe.Value?.Trim() ?? string.Empty; Requirement[] requirements; string error; if (text.Length == 0) { ApplyRequirements(component, CloneRequirements(_originalRequirements)); _lastWarningKey = string.Empty; } else if (text.Equals("None", StringComparison.OrdinalIgnoreCase) || text.Equals("Free", StringComparison.OrdinalIgnoreCase) || text == "-") { ApplyRequirements(component, Array.Empty<Requirement>()); _lastWarningKey = string.Empty; } else if (!TryCreateRequirements(instance2, text, out requirements, out error)) { ApplyRequirements(component, CloneRequirements(_originalRequirements)); WarnInvalidRecipeOnce(text, error); } else { ApplyRequirements(component, requirements); _lastWarningKey = string.Empty; } } } private static void TrackOriginalRecipe(Piece piece) { if (_trackedPiece != piece) { _trackedPiece = piece; _originalRequirements = CloneRequirements(piece.m_resources); _lastWarningKey = string.Empty; } } private static bool TryCreateRequirements(ObjectDB objectDb, string recipe, out Requirement[] requirements, out string error) { //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Expected O, but got Unknown requirements = Array.Empty<Requirement>(); error = string.Empty; string[] array = recipe.Split(new char[4] { ',', ';', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length == 0) { error = "the recipe has no ingredients"; return false; } List<Requirement> list = new List<Requirement>(array.Length); HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal); string[] array2 = array; for (int i = 0; i < array2.Length; i++) { string text = array2[i].Trim(); int num = text.IndexOf(':'); if (num <= 0 || num != text.LastIndexOf(':') || num >= text.Length - 1) { error = "'" + text + "' is not an ItemPrefab:Amount entry"; return false; } string text2 = text.Substring(0, num).Trim(); string s = text.Substring(num + 1).Trim(); if (text2.Length == 0 || !int.TryParse(s, NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result <= 0) { error = "'" + text + "' does not contain an exact item prefab and a positive integer amount"; return false; } GameObject itemPrefab = objectDb.GetItemPrefab(text2); ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent<ItemDrop>() : null); if ((Object)(object)val == (Object)null) { error = "item prefab '" + text2 + "' is not registered"; return false; } string text3 = val.m_itemData.m_shared.m_name?.Trim() ?? string.Empty; if (!hashSet.Add(text2) || text3.Length == 0 || !hashSet2.Add(text3)) { error = "item prefab '" + text2 + "' duplicates another ingredient"; return false; } list.Add(new Requirement { m_resItem = val, m_amount = result, m_extraAmountOnlyOneIngredient = 0, m_amountPerLevel = 1, m_recover = true }); } requirements = list.ToArray(); return true; } private static void ApplyRequirements(Piece piece, Requirement[] requirements) { piece.m_resources = requirements; Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { localPlayer.UpdateAvailablePiecesList(); } } private static Requirement[] CloneRequirements(Requirement[]? source) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown if (source == null || source.Length == 0) { return Array.Empty<Requirement>(); } Requirement[] array = (Requirement[])(object)new Requirement[source.Length]; for (int i = 0; i < source.Length; i++) { Requirement val = source[i]; array[i] = new Requirement { m_resItem = val.m_resItem, m_amount = val.m_amount, m_extraAmountOnlyOneIngredient = val.m_extraAmountOnlyOneIngredient, m_amountPerLevel = val.m_amountPerLevel, m_recover = val.m_recover }; } return array; } private static void WarnInvalidRecipeOnce(string recipe, string error) { string text = recipe + "\0" + error; if (!string.Equals(_lastWarningKey, text, StringComparison.Ordinal)) { _lastWarningKey = text; RepairRequiresMaterialsPlugin.Log.LogWarning((object)("Invalid Incinerator Build Recipe '" + recipe + "': " + error + ". Restored the original recipe.")); } } } [HarmonyPatch(typeof(Incinerator), "Awake")] internal static class IncineratorDismantleAwakePatch { [HarmonyPriority(0)] private static void Postfix(Incinerator __instance) { if (IncineratorDismantlePatches.IsSupportedIncinerator(__instance)) { IncineratorDismantleController incineratorDismantleController = ((Component)__instance).GetComponent<IncineratorDismantleController>(); if ((Object)(object)incineratorDismantleController == (Object)null) { incineratorDismantleController = ((Component)__instance).gameObject.AddComponent<IncineratorDismantleController>(); } incineratorDismantleController.Initialize(__instance); } } } [HarmonyPatch(typeof(Switch), "Interact")] internal static class IncineratorDismantleSwitchInteractPatch { [HarmonyPriority(800)] private static bool Prefix(Switch __instance, Humanoid character, bool hold, ref bool __result) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) if (!RepairRequiresMaterialsPlugin.EnableIncineratorDismantling.Value.IsOn() || !IncineratorDismantlePatches.TryGetIncineratorLever(__instance, out Incinerator incinerator) || (Object)(object)incinerator == (Object)null) { return true; } KeyCode value = RepairRequiresMaterialsPlugin.DismantleModifierKey.Value; if ((int)value == 0 || !ZInput.GetKey(value, false)) { return true; } __result = false; if (hold) { return false; } Player val = (Player)(object)((character is Player) ? character : null); if (val == null) { return false; } if (!PrivateArea.CheckAccess(((Component)incinerator).transform.position, 0f, true, false) || !incinerator.m_container.CheckAccess(val.GetPlayerID())) { ((Character)val).Message((MessageType)2, "$piece_noaccess", 0, (Sprite)null); return false; } IncineratorDismantleController component = ((Component)incinerator).GetComponent<IncineratorDismantleController>(); if ((Object)(object)component == (Object)null || !component.RequestDismantle(val)) { ((Character)val).Message((MessageType)2, "$rrm_dismantle_unavailable", 0, (Sprite)null); return false; } __result = true; return false; } } [HarmonyPatch(typeof(Incinerator), "GetLeverHoverText")] internal static class IncineratorDismantleHoverTextPatch { [HarmonyPriority(0)] private static void Postfix(Incinerator __instance, ref string __result) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) if (RepairRequiresMaterialsPlugin.EnableIncineratorDismantling.Value.IsOn() && IncineratorDismantlePatches.IsSupportedIncinerator(__instance) && (int)RepairRequiresMaterialsPlugin.DismantleModifierKey.Value != 0 && PrivateArea.CheckAccess(((Component)__instance).transform.position, 0f, false, false)) { string text = ((object)RepairRequiresMaterialsPlugin.DismantleModifierKey.Value/*cast due to .constrained prefix*/).ToString(); _