Decompiled source of ForgeRework v1.0.0

ForgeRework.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ForgeRework")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("ForgeRework")]
[assembly: AssemblyTitle("ForgeRework")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ForgeRework
{
	[BepInPlugin("MerlinofMines.ForgeRework", "Forge Rework", "1.0.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Id = "MerlinofMines.ForgeRework";

		internal static ProbabilityCurve Curve = new ProbabilityCurve();

		private ConfigEntry<int> startingLevel;

		private ConfigEntry<int> maxLevel;

		private ConfigEntry<double> startingProbability;

		private ConfigEntry<double> minimumProbability;

		private ConfigEntry<double> dropoff;

		private Harmony harmony;

		private void Awake()
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Expected O, but got Unknown
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Expected O, but got Unknown
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Expected O, but got Unknown
			//IL_0161: Unknown result type (might be due to invalid IL or missing references)
			//IL_016b: Expected O, but got Unknown
			//IL_019e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a8: Expected O, but got Unknown
			HashSet<string> savedKeys = ReadSavedKeys(((BaseUnityPlugin)this).Config.ConfigFilePath);
			startingLevel = BindSetting(savedKeys, "Levels", "Min Level", "StartingLevel", 0, new ConfigDescription("Item quality where probability starts decreasing. Whole number, 0 or higher.", (AcceptableValueBase)(object)new NonNegativeInteger(), Array.Empty<object>()));
			maxLevel = BindSetting(savedKeys, "Levels", "Max Level", "MaxLevel", 10, new ConfigDescription("Highest reachable item quality. Whole number; 0 disables the cap.", (AcceptableValueBase)(object)new NonNegativeInteger(), Array.Empty<object>()));
			startingProbability = BindSetting(savedKeys, "Probabilities", "Max Probability", "StartingProbability", 100.0, new ConfigDescription("Upgrade chance at or below Min Level (%).", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 100.0), new object[1]
			{
				new ConfigurationManagerAttributes()
			}));
			minimumProbability = BindSetting(savedKeys, "Probabilities", "Min Probability", "MinimumProbability", 0.0, new ConfigDescription("Lowest upgrade chance (%). Must not exceed Max Probability.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 100.0), new object[1]
			{
				new ConfigurationManagerAttributes()
			}));
			dropoff = BindSetting(savedKeys, "Probabilities", "Probability Dropoff Per Level", "DropoffPerLevel", 10.0, new ConfigDescription("The reduction in success probability at each successive level above Min Level, in percentage points.", (AcceptableValueBase)(object)new AcceptableValueRange<double>(0.0, 100.0), new object[1]
			{
				new ConfigurationManagerAttributes()
			}));
			((BaseUnityPlugin)this).Config.Save();
			RefreshCurve();
			((BaseUnityPlugin)this).Config.SettingChanged += delegate
			{
				RefreshCurve();
			};
			harmony = new Harmony("MerlinofMines.ForgeRework");
			harmony.PatchAll(typeof(Plugin).Assembly);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Forge Rework loaded: safe failures and configurable success odds.");
		}

		private static HashSet<string> ReadSavedKeys(string path)
		{
			HashSet<string> hashSet = new HashSet<string>();
			if (!File.Exists(path))
			{
				return hashSet;
			}
			string text = "";
			string[] array = File.ReadAllLines(path);
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				if (text2.StartsWith("#"))
				{
					continue;
				}
				if (text2.StartsWith("[") && text2.EndsWith("]"))
				{
					text = text2.Substring(1, text2.Length - 2);
					continue;
				}
				int num = text2.IndexOf('=');
				if (num >= 0)
				{
					hashSet.Add(text + "/" + text2.Substring(0, num).Trim());
				}
			}
			return hashSet;
		}

		private ConfigEntry<T> BindSetting<T>(HashSet<string> savedKeys, string section, string key, string oldKey, T defaultValue, ConfigDescription description)
		{
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Expected O, but got Unknown
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Expected O, but got Unknown
			ConfigEntry<T> val = ((BaseUnityPlugin)this).Config.Bind<T>(section, key, defaultValue, description);
			if (savedKeys.Contains("Probability/" + oldKey))
			{
				ConfigEntry<T> val2 = ((BaseUnityPlugin)this).Config.Bind<T>("Probability", oldKey, defaultValue, description);
				if (!savedKeys.Contains(section + "/" + key))
				{
					val.Value = val2.Value;
				}
				((BaseUnityPlugin)this).Config.Remove(new ConfigDefinition("Probability", oldKey));
			}
			if (section == "Levels" && key == "Min Level" && savedKeys.Contains("Levels/Starting Level"))
			{
				ConfigEntry<T> val3 = ((BaseUnityPlugin)this).Config.Bind<T>("Levels", "Starting Level", defaultValue, description);
				if (!savedKeys.Contains("Levels/Min Level"))
				{
					val.Value = val3.Value;
				}
				((BaseUnityPlugin)this).Config.Remove(new ConfigDefinition("Levels", "Starting Level"));
			}
			return val;
		}

		private void RefreshCurve()
		{
			try
			{
				Curve = new ProbabilityCurve(startingLevel.Value, startingProbability.Value, minimumProbability.Value, dropoff.Value, maxLevel.Value);
			}
			catch (ArgumentException ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Invalid probability settings; retaining last valid curve. " + ex.Message));
			}
		}

		private void OnDestroy()
		{
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
		}
	}
	internal sealed class ConfigurationManagerAttributes
	{
		public bool? ShowRangeAsPercent = true;
	}
	internal sealed class NonNegativeInteger : AcceptableValueBase
	{
		public NonNegativeInteger()
			: base(typeof(int))
		{
		}

		public override object Clamp(object value)
		{
			return Math.Max(0, (int)value);
		}

		public override bool IsValid(object value)
		{
			if (value is int num)
			{
				return num >= 0;
			}
			return false;
		}

		public override string ToDescriptionString()
		{
			return "# Acceptable values: non-negative integers";
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "AddRecipeToList")]
	internal static class RecipeAvailabilityPatch
	{
		private static readonly FieldInfo Rows = AccessTools.Field(typeof(InventoryGui), "m_availableRecipes");

		private static readonly PropertyInfo Element = AccessTools.Property(Rows.FieldType.GetGenericArguments()[0], "InterfaceElement");

		internal static bool IsMax(Player player, ItemData item)
		{
			if (Forge.IsForge(player) && item != null)
			{
				return !Plugin.Curve.CanAttempt(item.m_quality);
			}
			return false;
		}

		private static void Prefix(Player player, ItemData item, ref bool canCraft)
		{
			if (IsMax(player, item))
			{
				canCraft = false;
			}
		}

		private static void Postfix(InventoryGui __instance, Player player, ItemData item)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			if (!IsMax(player, item))
			{
				return;
			}
			IList list = (IList)Rows.GetValue(__instance);
			if (list.Count != 0)
			{
				object obj = list[list.Count - 1];
				Transform obj2 = ((GameObject)Element.GetValue(obj)).transform.Find("icon");
				Image val = ((obj2 != null) ? ((Component)obj2).GetComponent<Image>() : null);
				if ((Object)(object)val != (Object)null)
				{
					((Graphic)val).color = new Color(0.45f, 0.45f, 0.45f, 1f);
				}
			}
		}
	}
	internal static class Forge
	{
		private static readonly FieldInfo Selected = AccessTools.Field(typeof(InventoryGui), "m_selectedRecipe");

		private static readonly PropertyInfo SelectedItem = AccessTools.Property(Selected.FieldType, "ItemData");

		private static readonly PropertyInfo SelectedRecipe = AccessTools.Property(Selected.FieldType, "Recipe");

		private static readonly MethodInfo Refresh = AccessTools.Method(typeof(InventoryGui), "UpdateCraftingPanel", (Type[])null, (Type[])null);

		private static readonly MethodInfo InventoryChanged = AccessTools.Method(typeof(Inventory), "Changed", (Type[])null, (Type[])null);

		internal static ItemData Item(InventoryGui gui)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			return (ItemData)SelectedItem.GetValue(Selected.GetValue(gui));
		}

		internal static Recipe Recipe(InventoryGui gui)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			return (Recipe)SelectedRecipe.GetValue(Selected.GetValue(gui));
		}

		internal static void RefreshPanel(InventoryGui gui)
		{
			Refresh.Invoke(gui, new object[1] { false });
		}

		internal static bool IsForge(Player player)
		{
			if ((Object)(object)player != (Object)null && (Object)(object)player.GetCurrentCraftingStation() != (Object)null)
			{
				return player.GetCurrentCraftingStation().m_upgrader;
			}
			return false;
		}

		internal static bool Eligible(Player player, Recipe recipe, ItemData item)
		{
			if ((Object)(object)recipe == (Object)null || item == null || !((Humanoid)player).GetInventory().ContainsItem(item))
			{
				return false;
			}
			if (!Plugin.Curve.CanAttempt(item.m_quality))
			{
				return false;
			}
			if (!player.GetCurrentCraftingStation().CheckUsable(player, false))
			{
				return false;
			}
			if (!recipe.m_resources.Any((Requirement r) => r.m_upgraderResource && (Object)(object)r.m_resItem != (Object)null))
			{
				return false;
			}
			if (!string.IsNullOrEmpty(item.m_shared.m_dlc) && !DLCMan.instance.IsDLCInstalled(item.m_shared.m_dlc))
			{
				return false;
			}
			if (!Free(player))
			{
				return player.HaveRequirements(recipe, false, item.m_quality + 1, 1);
			}
			return true;
		}

		private static bool Free(Player player)
		{
			if (!player.NoCostCheat())
			{
				return ZoneSystem.instance.GetGlobalKey((GlobalKeys)25);
			}
			return true;
		}

		internal static void Attempt(InventoryGui gui, Player player, Recipe recipe, ItemData item)
		{
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cc: Unknown result type (might be due to invalid IL or missing references)
			if (!Eligible(player, recipe, item))
			{
				return;
			}
			int targetQuality = item.m_quality + 1;
			int need = default(int);
			ItemData singleResource = default(ItemData);
			recipe.GetAmount(targetQuality, ref need, ref singleResource, 1);
			if (recipe.m_requireOnlyOneIngredient && singleResource == null)
			{
				return;
			}
			CraftingStation currentCraftingStation = player.GetCurrentCraftingStation();
			bool cheatedResources = ((Humanoid)player).GetInventory().ItemCheated(recipe.m_resources, -1, true) || player.NoCostCheat();
			ZNetView component = ((Component)currentCraftingStation).GetComponent<ZNetView>();
			bool? obj;
			if (component == null)
			{
				obj = null;
			}
			else
			{
				ZDO zDO = component.GetZDO();
				obj = ((zDO != null) ? new bool?(zDO.GetBool(ZDOVars.s_cheated, false)) : ((bool?)null));
			}
			bool? flag = obj;
			bool cheatedStation = flag == true;
			switch (Refinement.Attempt(Plugin.Curve, item.m_quality, () => Random.value, delegate
			{
				if (!Free(player))
				{
					if (singleResource != null)
					{
						((Humanoid)player).GetInventory().RemoveItem(singleResource.m_shared.m_name, need, singleResource.m_quality, true);
					}
					else
					{
						player.ConsumeResources(recipe.m_resources, targetQuality, -1, 1);
					}
				}
			}, delegate(int quality)
			{
				((Humanoid)player).UnequipItem(item, true);
				item.m_quality = quality;
				item.m_durability = item.GetMaxDurability();
				ItemData obj2 = item;
				obj2.m_cheated |= (cheatedResources || cheatedStation) && !PlayerProfile.s_bypassCheatChecks;
				InventoryChanged.Invoke(((Humanoid)player).GetInventory(), new object[2] { false, false });
			}))
			{
			case AttemptResult.Blocked:
				return;
			case AttemptResult.Succeeded:
				((Character)player).Message((MessageType)2, Localization.instance.Localize("$msg_upgrader_success", new string[2]
				{
					item.m_shared.m_name,
					item.m_quality.ToString()
				}), 0, (Sprite)null, false);
				currentCraftingStation.m_craftItemDoneEffects.Create(((Component)player).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID));
				break;
			default:
				((Character)player).Message((MessageType)2, "Refinement failed. Your item is unchanged.", 0, (Sprite)null, false);
				break;
			}
			PlayerProfile playerProfile = Game.instance.GetPlayerProfile();
			playerProfile.IncrementStat((PlayerStatType)1, 1f, cheatedResources);
			playerProfile.IncrementStat((PlayerStatType)14, 1f, cheatedResources);
			Refresh.Invoke(gui, new object[1] { false });
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "DoCrafting")]
	internal static class CraftPatch
	{
		private static bool Prefix(InventoryGui __instance, Player player, Recipe ___m_craftRecipe, ItemData ___m_craftUpgradeItem)
		{
			if (!Forge.IsForge(player))
			{
				return true;
			}
			Forge.Attempt(__instance, player, ___m_craftRecipe, ___m_craftUpgradeItem);
			return false;
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "OnCraftPressed")]
	internal static class StartPatch
	{
		private static bool Prefix(InventoryGui __instance, ref Recipe ___m_craftRecipe, ref ItemData ___m_craftUpgradeItem, ref float ___m_craftTimer, ref bool ___m_multiCrafting, ref bool ___m_touchMultiCrafting)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if (!Forge.IsForge(localPlayer))
			{
				return true;
			}
			ItemData val = Forge.Item(__instance);
			Recipe val2 = Forge.Recipe(__instance);
			if (___m_craftTimer >= 0f || !Forge.Eligible(localPlayer, val2, val))
			{
				return false;
			}
			___m_craftRecipe = val2;
			___m_craftUpgradeItem = val;
			___m_multiCrafting = false;
			___m_touchMultiCrafting = false;
			___m_craftTimer = 0f;
			localPlayer.GetCurrentCraftingStation().m_craftItemEffects.Create(((Component)localPlayer).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID));
			return false;
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "UpdateRecipe")]
	internal static class DisplayPatch
	{
		private static InventoryGui lastGui;

		private static ProbabilityCurve lastCurve;

		private static void Postfix(InventoryGui __instance, Player player)
		{
			if (!Forge.IsForge(player))
			{
				return;
			}
			if (lastGui != __instance || lastCurve != Plugin.Curve)
			{
				lastGui = __instance;
				lastCurve = Plugin.Curve;
				Forge.RefreshPanel(__instance);
			}
			ItemData val = Forge.Item(__instance);
			if (val == null)
			{
				return;
			}
			if (!Plugin.Curve.CanAttempt(val.m_quality))
			{
				__instance.m_itemCraftType.text = "MAX";
				((Selectable)__instance.m_craftButton).interactable = false;
				TMP_Text componentInChildren = ((Component)__instance.m_craftButton).GetComponentInChildren<TMP_Text>();
				if ((Object)(object)componentInChildren != (Object)null)
				{
					componentInChildren.text = "MAX";
				}
			}
			else
			{
				double num = Plugin.Curve.Chance(val.m_quality);
				__instance.m_itemCraftType.text = $"Chance To Upgrade: {num:0.##}%";
			}
		}
	}
	public sealed class ProbabilityCurve
	{
		public int StartingLevel { get; }

		public double StartingProbability { get; }

		public double MinimumProbability { get; }

		public double Dropoff { get; }

		public int MaxLevel { get; }

		public ProbabilityCurve(int startingLevel = 0, double startingProbability = 100.0, double minimumProbability = 0.0, double dropoff = 10.0, int maxLevel = 10)
		{
			if (startingLevel < 0)
			{
				throw new ArgumentOutOfRangeException("startingLevel");
			}
			if (maxLevel < 0)
			{
				throw new ArgumentOutOfRangeException("maxLevel");
			}
			ValidatePercent(startingProbability, "startingProbability");
			ValidatePercent(minimumProbability, "minimumProbability");
			ValidatePercent(dropoff, "dropoff");
			if (minimumProbability > startingProbability)
			{
				throw new ArgumentException("Minimum probability cannot exceed starting probability.");
			}
			StartingLevel = startingLevel;
			StartingProbability = startingProbability;
			MinimumProbability = minimumProbability;
			Dropoff = dropoff;
			MaxLevel = maxLevel;
		}

		private static void ValidatePercent(double value, string name)
		{
			if (double.IsNaN(value) || double.IsInfinity(value) || value < 0.0 || value > 100.0)
			{
				throw new ArgumentOutOfRangeException(name, "Must be a finite percentage from 0 to 100.");
			}
		}

		public double Chance(int currentLevel)
		{
			if (currentLevel < 0)
			{
				throw new ArgumentOutOfRangeException("currentLevel");
			}
			return Math.Max(MinimumProbability, StartingProbability - Dropoff * (double)Math.Max(0L, (long)currentLevel - (long)StartingLevel));
		}

		public bool CanAttempt(int currentLevel)
		{
			if (Chance(currentLevel) > 0.0 && currentLevel < int.MaxValue)
			{
				if (MaxLevel != 0)
				{
					return currentLevel < MaxLevel;
				}
				return true;
			}
			return false;
		}

		public bool Succeeds(int currentLevel, double roll)
		{
			if (double.IsNaN(roll) || double.IsInfinity(roll) || roll < 0.0 || roll > 1.0)
			{
				throw new ArgumentOutOfRangeException("roll");
			}
			double num = Chance(currentLevel);
			if (CanAttempt(currentLevel))
			{
				if (!(num >= 100.0))
				{
					return roll < num / 100.0;
				}
				return true;
			}
			return false;
		}
	}
	public enum AttemptResult
	{
		Blocked,
		Failed,
		Succeeded
	}
	public static class Refinement
	{
		public static AttemptResult Attempt(ProbabilityCurve curve, int currentLevel, Func<double> roll, Action consumeResources, Action<int> upgrade)
		{
			if (!curve.CanAttempt(currentLevel))
			{
				return AttemptResult.Blocked;
			}
			bool num = curve.Succeeds(currentLevel, roll());
			consumeResources();
			if (!num)
			{
				return AttemptResult.Failed;
			}
			upgrade(currentLevel + 1);
			return AttemptResult.Succeeded;
		}
	}
}