Decompiled source of ForgeOfPotentialRedux v1.2.2

ForgeOfPotentialRedux.dll

Decompiled 12 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using ConditionalConfigSync;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("ForgeOfPotentialRedux")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.2.2.0")]
[assembly: AssemblyInformationalVersion("1.2.2")]
[assembly: AssemblyProduct("ForgeOfPotentialRedux")]
[assembly: AssemblyTitle("ForgeOfPotentialRedux")]
[assembly: AssemblyVersion("1.2.2.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 ForgeOfPotentialRedux
{
	[BepInPlugin("com.dreich.fopredux", "Forge of Potential Redux", "1.2.2")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.dreich.fopredux";

		public const string PluginName = "Forge of Potential Redux";

		public const string PluginVersion = "1.2.2";

		internal const string ConfigSyncGuid = "_shudnal.ConditionalConfigSync";

		internal static ManualLogSource Log;

		internal static bool ConfigSyncAvailable;

		internal static ConfigEntry<bool> ResetToLevelOne;

		internal static ConfigEntry<int> LevelsLostOnFail;

		internal static ConfigEntry<bool> SpawnEnemyOnFail;

		internal static ConfigEntry<string> EnemyPrefabNames;

		internal static ConfigEntry<int> EnemyCount;

		internal static ConfigEntry<int> EnemyStars;

		internal static ConfigEntry<float> SpawnDistance;

		internal static ConfigEntry<bool> UseLastBossBiome;

		internal static ConfigEntry<bool> TrophyBoostEnabled;

		internal static ConfigEntry<string> TrophyBonuses;

		internal static ConfigEntry<bool> TrophyConsumed;

		internal static ConfigEntry<bool> PenaltyEnabled;

		internal static ConfigEntry<float> PenaltyPerSuccess;

		internal static ConfigEntry<float> PenaltyMax;

		internal static readonly Dictionary<Biome, ConfigEntry<string>> BiomeCreatures = new Dictionary<Biome, ConfigEntry<string>>();

		private void Awake()
		{
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Expected O, but got Unknown
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Expected O, but got Unknown
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Expected O, but got Unknown
			//IL_0190: Unknown result type (might be due to invalid IL or missing references)
			//IL_019a: Expected O, but got Unknown
			//IL_030c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0316: Expected O, but got Unknown
			//IL_034e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0358: Expected O, but got Unknown
			//IL_0376: Unknown result type (might be due to invalid IL or missing references)
			//IL_037b: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			ConfigSyncAvailable = Chainloader.PluginInfos.ContainsKey("_shudnal.ConditionalConfigSync");
			if (ConfigSyncAvailable)
			{
				ConfigSyncCompat.Initialize("com.dreich.fopredux", "Forge of Potential Redux", "1.2.2");
			}
			else
			{
				Log.LogInfo((object)"ConditionalConfigSync not found; configuration will not be synced from the server.");
			}
			ResetToLevelOne = Synced<bool>(((BaseUnityPlugin)this).Config.Bind<bool>("Forge of Potential", "Reset to level 1 on failure", true, "If true, a failed upgrade always sets the item back to level 1 and 'Levels lost on fail' is ignored."));
			LevelsLostOnFail = Synced<int>(((BaseUnityPlugin)this).Config.Bind<int>("Forge of Potential", "Levels lost on fail", 1, new ConfigDescription("How many levels an item loses when an upgrade fails (only used when 'Reset to level 1' is false). 0 = no levels lost. The item never drops below level 1.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 20), Array.Empty<object>())));
			SpawnEnemyOnFail = Synced<bool>(((BaseUnityPlugin)this).Config.Bind<bool>("Enemy Spawn", "Spawn enemy on fail", true, "If true, a failed upgrade spawns enemies next to the player."));
			EnemyPrefabNames = Synced<string>(((BaseUnityPlugin)this).Config.Bind<string>("Enemy Spawn", "Enemy prefabs", "Greydwarf", "Comma-separated internal prefab names, e.g. 'Greydwarf,Skeleton,Draugr'. Each spawned enemy picks one at random from the enemies to spawn. Names are case-sensitive."));
			EnemyCount = Synced<int>(((BaseUnityPlugin)this).Config.Bind<int>("Enemy Spawn", "Enemy count", 1, new ConfigDescription("How many enemies spawn on a failed upgrade.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>())));
			EnemyStars = Synced<int>(((BaseUnityPlugin)this).Config.Bind<int>("Enemy Spawn", "Enemy stars", 1, new ConfigDescription("Star level of the spawned enemies. 0 = normal, 1 = one star, 2 = two stars (the vanilla maximum). Values above 2 are stronger than anything the game normally spawns.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 5), Array.Empty<object>())));
			SpawnDistance = Synced<float>(((BaseUnityPlugin)this).Config.Bind<float>("Enemy Spawn", "Spawn distance", 5f, new ConfigDescription("How far from the player the enemy appears, in meters.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 20f), Array.Empty<object>())));
			UseLastBossBiome = Synced<bool>(((BaseUnityPlugin)this).Config.Bind<bool>("Boss Biome Spawn", "Use last boss biome", false, "If true, failed upgrades spawn a random creature from the biome of the last boss you helped kill (using the per-biome lists below). If you have not killed a boss on this character, the highest boss defeated on the world is used; if none have been defeated, the regular 'Enemy prefabs' list is used."));
			BindBiome((Biome)1, "Meadows (Eikthyr)", "Boar,Neck,Greyling");
			BindBiome((Biome)8, "Black Forest (The Elder)", "Greydwarf,Greydwarf_Elite,Greydwarf_Shaman,Troll,Skeleton");
			BindBiome((Biome)2, "Swamp (Bonemass)", "Draugr,Draugr_Elite,Blob,Leech,Wraith");
			BindBiome((Biome)4, "Mountain (Moder)", "Wolf,Fenring,Hatchling,StoneGolem,Ulv");
			BindBiome((Biome)16, "Plains (Yagluth)", "Goblin,GoblinArcher,GoblinBrute,GoblinShaman,Deathsquito,Lox");
			BindBiome((Biome)512, "Mistlands (The Queen)", "Seeker,SeekerBrute,Gjall,Tick");
			BindBiome((Biome)32, "Ashlands (Fader)", "Charred_Melee,Charred_Archer,Charred_Mage,Asksvin,Volture,Morgen");
			TrophyBoostEnabled = Synced<bool>(((BaseUnityPlugin)this).Config.Bind<bool>("Trophy Boost", "Enabled", false, "If true, carrying a creature trophy adds to the success chance of Forge of Potential upgrades."));
			TrophyBonuses = Synced<string>(((BaseUnityPlugin)this).Config.Bind<string>("Trophy Boost", "Trophy bonuses", "TrophyBoar=2,TrophyDeer=2,TrophyNeck=3,TrophyGreydwarf=4,TrophySkeleton=5,TrophyGreydwarfBrute=6,TrophyGreydwarfShaman=6,TrophyTroll=8,TrophyBlob=6,TrophyDraugr=7,TrophyLeech=7,TrophyDraugrElite=10,TrophySurtling=10,TrophyWraith=12,TrophyAbomination=15,TrophyWolf=10,TrophyHatchling=10,TrophyFenring=12,TrophySGolem=14,TrophyCultist=15,TrophyGoblin=14,TrophyDeathsquito=16,TrophyGoblinShaman=16,TrophyGoblinBrute=18,TrophyLox=20,TrophySeeker=20,TrophyTick=20,TrophySeekerBrute=25,TrophyGjall=28,TrophyEikthyr=10,TrophyTheElder=15,TrophyBonemass=20,TrophyDragonQueen=25,TrophyGoblinKing=30,TrophySeekerQueen=35,TrophyFader=40", "Comma-separated 'TrophyPrefab=percent' pairs. The percent is added to the upgrade's success chance (capped at 100%). If several listed trophies are carried, only the best one applies and is used up. Names are case-sensitive; unknown names are skipped with a warning."));
			TrophyConsumed = Synced<bool>(((BaseUnityPlugin)this).Config.Bind<bool>("Trophy Boost", "Consumed on use", true, "If true, one trophy is used up per upgrade attempt. If false, carrying it boosts every attempt."));
			PenaltyEnabled = Synced<bool>(((BaseUnityPlugin)this).Config.Bind<bool>("Success Penalty", "Enabled", false, "If true, every successful upgrade lowers the success chance of that item's next upgrade, stacking with each success in a row. A failed upgrade resets it. Tracked per item."));
			PenaltyPerSuccess = Synced<float>(((BaseUnityPlugin)this).Config.Bind<float>("Success Penalty", "Penalty per success (%)", 5f, new ConfigDescription("Percentage points removed from the success chance for each successful upgrade in a row.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 50f), Array.Empty<object>())));
			PenaltyMax = Synced<float>(((BaseUnityPlugin)this).Config.Bind<float>("Success Penalty", "Maximum penalty (%)", 50f, new ConfigDescription("The most percentage points the penalty can remove, no matter how long the streak. Trophy bonuses are applied on top of the penalty.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>())));
			Log.LogInfo((object)"Forge of Potential Redux 1.2.2 loaded, applying patches...");
			Harmony val = new Harmony("com.dreich.fopredux");
			val.PatchAll();
			foreach (MethodBase patchedMethod in val.GetPatchedMethods())
			{
				Log.LogInfo((object)("Patched: " + patchedMethod.DeclaringType?.Name + "." + patchedMethod.Name));
			}
			LocalizationTweaks.Apply(Localization.instance);
		}

		private static ConfigEntry<T> Synced<T>(ConfigEntry<T> entry)
		{
			if (ConfigSyncAvailable)
			{
				ConfigSyncCompat.AddServerControlled<T>(entry);
			}
			return entry;
		}

		private void BindBiome(Biome biome, string label, string defaultPrefabs)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			ConfigEntry<string> value = Synced<string>(((BaseUnityPlugin)this).Config.Bind<string>("Boss Biome Spawn", label, defaultPrefabs, "Comma-separated prefab names that can spawn when this was the last boss you helped kill. Names are case-sensitive."));
			BiomeCreatures[biome] = value;
		}
	}
	internal static class ConfigSyncCompat
	{
		private static object configSync;

		[MethodImpl(MethodImplOptions.NoInlining)]
		internal static void Initialize(string guid, string name, string version)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected O, but got Unknown
			configSync = (object)new ConfigSync(guid)
			{
				DisplayName = name,
				CurrentVersion = version,
				MinimumRequiredVersion = version,
				ModRequired = false
			};
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		internal static void AddServerControlled<T>(ConfigEntry<T> entry)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			((ConditionalConfigSync)(ConfigSync)configSync).AddConfigEntry<T>(entry, (ConfigSyncMode)0);
		}
	}
	internal static class IlHelpers
	{
		public static int LocalIndex(this CodeInstruction instruction)
		{
			OpCode opcode = instruction.opcode;
			if (opcode == OpCodes.Ldloc_0 || opcode == OpCodes.Stloc_0)
			{
				return 0;
			}
			if (opcode == OpCodes.Ldloc_1 || opcode == OpCodes.Stloc_1)
			{
				return 1;
			}
			if (opcode == OpCodes.Ldloc_2 || opcode == OpCodes.Stloc_2)
			{
				return 2;
			}
			if (opcode == OpCodes.Ldloc_3 || opcode == OpCodes.Stloc_3)
			{
				return 3;
			}
			object operand = instruction.operand;
			if (!(operand is LocalBuilder localBuilder))
			{
				if (operand is byte)
				{
					return (byte)operand;
				}
				if (operand is sbyte)
				{
					return (sbyte)operand;
				}
				if (operand is short)
				{
					return (short)operand;
				}
				if (operand is ushort)
				{
					return (ushort)operand;
				}
				if (operand is int)
				{
					return (int)operand;
				}
				throw new ArgumentException("Instruction does not reference a local variable: " + (object)instruction);
			}
			return localBuilder.LocalIndex;
		}

		public static CodeInstruction LoadLocal(int index)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Expected O, but got Unknown
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Expected O, but got Unknown
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Expected O, but got Unknown
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Expected O, but got Unknown
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Expected O, but got Unknown
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Expected O, but got Unknown
			switch (index)
			{
			case 0:
				return new CodeInstruction(OpCodes.Ldloc_0, (object)null);
			case 1:
				return new CodeInstruction(OpCodes.Ldloc_1, (object)null);
			case 2:
				return new CodeInstruction(OpCodes.Ldloc_2, (object)null);
			case 3:
				return new CodeInstruction(OpCodes.Ldloc_3, (object)null);
			default:
				if (index <= 255)
				{
					return new CodeInstruction(OpCodes.Ldloc_S, (object)(byte)index);
				}
				return new CodeInstruction(OpCodes.Ldloc, (object)(short)index);
			}
		}

		public static CodeInstruction StoreLocal(int index)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Expected O, but got Unknown
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Expected O, but got Unknown
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Expected O, but got Unknown
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Expected O, but got Unknown
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Expected O, but got Unknown
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Expected O, but got Unknown
			switch (index)
			{
			case 0:
				return new CodeInstruction(OpCodes.Stloc_0, (object)null);
			case 1:
				return new CodeInstruction(OpCodes.Stloc_1, (object)null);
			case 2:
				return new CodeInstruction(OpCodes.Stloc_2, (object)null);
			case 3:
				return new CodeInstruction(OpCodes.Stloc_3, (object)null);
			default:
				if (index <= 255)
				{
					return new CodeInstruction(OpCodes.Stloc_S, (object)(byte)index);
				}
				return new CodeInstruction(OpCodes.Stloc, (object)(short)index);
			}
		}
	}
	public static class BossTracker
	{
		internal const string CustomDataKey = "FoP_LastBossBiome";

		private const string BossKilledRpc = "FoP_BossKilled";

		private const float RecordRange = 100f;

		private static readonly (string globalKey, Biome biome)[] Bosses = new(string, Biome)[7]
		{
			("defeated_eikthyr", (Biome)1),
			("defeated_gdking", (Biome)8),
			("defeated_bonemass", (Biome)2),
			("defeated_dragon", (Biome)4),
			("defeated_goblinking", (Biome)16),
			("defeated_queen", (Biome)512),
			("defeated_fader", (Biome)32)
		};

		public static void RegisterRpc()
		{
			if (ZRoutedRpc.instance != null)
			{
				ZRoutedRpc.instance.Register<string, Vector3>("FoP_BossKilled", (Action<long, string, Vector3>)OnBossKilled);
			}
		}

		public static void BroadcastBossKill(Character boss)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			string defeatSetGlobalKey = boss.m_defeatSetGlobalKey;
			if (!string.IsNullOrEmpty(defeatSetGlobalKey) && ZRoutedRpc.instance != null)
			{
				ZRoutedRpc.instance.InvokeRoutedRPC(0L, "FoP_BossKilled", new object[2]
				{
					defeatSetGlobalKey,
					((Component)boss).transform.position
				});
			}
		}

		private static void OnBossKilled(long sender, string globalKey, Vector3 position)
		{
			//IL_0015: 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_004b: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null || Vector3.Distance(((Component)localPlayer).transform.position, position) > 100f)
				{
					return;
				}
				(string, Biome)[] bosses = Bosses;
				for (int i = 0; i < bosses.Length; i++)
				{
					(string, Biome) tuple = bosses[i];
					if (!(tuple.Item1 != globalKey))
					{
						RecordBossKill(localPlayer, tuple.Item2);
						break;
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Failed to record boss kill: " + ex.Message));
			}
		}

		public static Biome? GetLastDefeatedBiome()
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ZoneSystem.instance == (Object)null)
			{
				return null;
			}
			for (int num = Bosses.Length - 1; num >= 0; num--)
			{
				if (ZoneSystem.instance.GetGlobalKey(Bosses[num].globalKey))
				{
					return Bosses[num].biome;
				}
			}
			return null;
		}

		private static string CurrentWorldName()
		{
			if (!((Object)(object)ZNet.instance != (Object)null))
			{
				return "";
			}
			return ZNet.instance.GetWorldName() ?? "";
		}

		public unsafe static void RecordBossKill(Player player, Biome biome)
		{
			player.m_customData["FoP_LastBossBiome"] = ((object)(*(Biome*)(&biome))/*cast due to .constrained prefix*/).ToString() + "|" + CurrentWorldName();
		}

		public static Biome? GetLastBossBiome()
		{
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null && localPlayer.m_customData.TryGetValue("FoP_LastBossBiome", out var value))
			{
				string[] array = value.Split(new char[1] { '|' }, 2);
				if ((array.Length < 2 || array[1] == CurrentWorldName()) && Enum.TryParse<Biome>(array[0], out Biome result))
				{
					return result;
				}
			}
			return GetLastDefeatedBiome();
		}

		public static string GetPrefabNameList()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.UseLastBossBiome.Value)
			{
				Biome? lastBossBiome = GetLastBossBiome();
				if (lastBossBiome.HasValue && Plugin.BiomeCreatures.TryGetValue(lastBossBiome.Value, out var value))
				{
					return value.Value;
				}
			}
			return Plugin.EnemyPrefabNames.Value;
		}
	}
	[HarmonyPatch(typeof(Game), "Start")]
	internal static class BossRpcRegisterPatch
	{
		private static void Postfix()
		{
			try
			{
				BossTracker.RegisterRpc();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Failed to register boss kill RPC: " + ex.Message));
			}
		}
	}
	[HarmonyPatch(typeof(Character), "OnDeath")]
	internal static class BossDeathPatch
	{
		private static void Postfix(Character __instance)
		{
			try
			{
				if ((Object)(object)__instance != (Object)null && __instance.IsBoss())
				{
					BossTracker.BroadcastBossKill(__instance);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Failed to announce boss kill: " + ex.Message));
			}
		}
	}
	public static class TrophyBoost
	{
		private static string parsedFrom;

		private static Dictionary<string, float> bonusTable = new Dictionary<string, float>();

		private static readonly HashSet<string> warned = new HashSet<string>();

		public static Dictionary<string, float> GetBonusTable()
		{
			string value = Plugin.TrophyBonuses.Value;
			if (value == parsedFrom)
			{
				return bonusTable;
			}
			Dictionary<string, float> dictionary = new Dictionary<string, float>();
			string[] array = value.Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length == 0)
				{
					continue;
				}
				string[] array2 = text.Split(new char[1] { '=' });
				if (array2.Length != 2 || !float.TryParse(array2[1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || result <= 0f)
				{
					if (warned.Add("bad:" + text))
					{
						Plugin.Log.LogWarning((object)("Ignoring invalid trophy bonus entry '" + text + "'; expected 'TrophyPrefab=percent'."));
					}
				}
				else
				{
					dictionary[array2[0].Trim()] = result;
				}
			}
			if ((Object)(object)ObjectDB.instance != (Object)null)
			{
				foreach (string key in dictionary.Keys)
				{
					if ((Object)(object)ObjectDB.instance.GetItemPrefab(key) == (Object)null && warned.Add("missing:" + key))
					{
						Plugin.Log.LogWarning((object)("Trophy prefab '" + key + "' not found; it will have no effect."));
					}
				}
			}
			bonusTable = dictionary;
			parsedFrom = value;
			return dictionary;
		}

		private static string GetPrefabName(ItemData item)
		{
			GameObject val = item.m_dropPrefab;
			if ((Object)(object)val == (Object)null && (Object)(object)ObjectDB.instance != (Object)null)
			{
				val = ObjectDB.instance.GetItemPrefab(item.m_shared);
			}
			if (!((Object)(object)val != (Object)null))
			{
				return null;
			}
			return Utils.GetPrefabName(val);
		}

		public static float GetBestTrophy(Inventory inventory, out ItemData bestItem)
		{
			bestItem = null;
			float num = 0f;
			Dictionary<string, float> dictionary = GetBonusTable();
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				string prefabName = GetPrefabName(allItem);
				if (prefabName != null && dictionary.TryGetValue(prefabName, out var value) && value > num)
				{
					bestItem = allItem;
					num = value;
				}
			}
			return num;
		}

		public static float ApplyBoost(float roll)
		{
			try
			{
				if (!Plugin.TrophyBoostEnabled.Value)
				{
					return roll;
				}
				Player localPlayer = Player.m_localPlayer;
				Inventory val = ((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null);
				if (val == null)
				{
					return roll;
				}
				ItemData bestItem;
				float bestTrophy = GetBestTrophy(val, out bestItem);
				if (bestItem == null)
				{
					return roll;
				}
				string arg = Localization.instance.Localize(bestItem.m_shared.m_name);
				if (Plugin.TrophyConsumed.Value)
				{
					val.RemoveItem(bestItem, 1);
				}
				UpgradeOdds.QueueNotice($"{arg}: +{bestTrophy:0.#}% upgrade chance");
				return Math.Max(0f, roll - bestTrophy / 100f);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Trophy boost check failed: " + ex.Message));
				return roll;
			}
		}
	}
	public static class UpgradeOdds
	{
		private const string StreakKey = "FoP_UpgradeStreak";

		private static readonly FieldInfo UpgradeItemField = AccessTools.Field(typeof(InventoryGui), "m_craftUpgradeItem");

		private static readonly FieldInfo SelectedRecipeField = AccessTools.Field(typeof(InventoryGui), "m_selectedRecipe");

		private static bool warnedNoRecipe;

		private static string cachedRateText = "";

		private static ItemData cachedItem;

		private static Recipe cachedRecipe;

		private static float cachedAt = -1f;

		private static readonly List<string> PendingNotices = new List<string>();

		private static int GetStreak(ItemData item)
		{
			if (item?.m_customData != null && item.m_customData.TryGetValue("FoP_UpgradeStreak", out var value) && int.TryParse(value, out var result))
			{
				return Math.Max(0, result);
			}
			return 0;
		}

		private static float GetPenaltyPercent(ItemData item, out int streak)
		{
			streak = 0;
			if (!Plugin.PenaltyEnabled.Value)
			{
				return 0f;
			}
			streak = GetStreak(item);
			return Math.Min((float)streak * Plugin.PenaltyPerSuccess.Value, Plugin.PenaltyMax.Value);
		}

		private static bool TryGetBaseChance(Recipe recipe, out float chance)
		{
			chance = 0f;
			if (recipe?.m_resources == null)
			{
				return false;
			}
			Requirement[] resources = recipe.m_resources;
			foreach (Requirement val in resources)
			{
				if (val != null && val.m_upgraderResource)
				{
					SharedData val2 = val.m_resItem?.m_itemData?.m_shared;
					if (val2 == null)
					{
						return false;
					}
					chance = val2.m_upgradeChance;
					return true;
				}
			}
			return false;
		}

		private static Recipe GetSelectedRecipe(InventoryGui gui)
		{
			object obj = SelectedRecipeField?.GetValue(gui);
			if (obj == null)
			{
				return null;
			}
			Type type = obj.GetType();
			object? obj2 = AccessTools.Property(type, "Recipe")?.GetValue(obj, null) ?? AccessTools.Field(type, "Recipe")?.GetValue(obj);
			return (Recipe)((obj2 is Recipe) ? obj2 : null);
		}

		private static float ComputeRate(Recipe recipe, ItemData item, out float basePercent, out float penalty, out int streak, out float trophy)
		{
			basePercent = 0f;
			penalty = 0f;
			streak = 0;
			trophy = 0f;
			if (!TryGetBaseChance(recipe, out var chance))
			{
				return -1f;
			}
			basePercent = chance * 100f;
			penalty = GetPenaltyPercent(item, out streak);
			if (Plugin.TrophyBoostEnabled.Value)
			{
				Player localPlayer = Player.m_localPlayer;
				Inventory val = ((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null);
				if (val != null)
				{
					trophy = TrophyBoost.GetBestTrophy(val, out var _);
				}
			}
			return Math.Max(0f, Math.Min(100f, basePercent - penalty + trophy));
		}

		private static string BuildRateText(InventoryGui gui, ItemData item)
		{
			Recipe selectedRecipe = GetSelectedRecipe(gui);
			if (item == cachedItem && (Object)(object)selectedRecipe == (Object)(object)cachedRecipe && Time.unscaledTime - cachedAt < 0.25f)
			{
				return cachedRateText;
			}
			cachedItem = item;
			cachedRecipe = selectedRecipe;
			cachedAt = Time.unscaledTime;
			cachedRateText = "";
			if ((Object)(object)selectedRecipe == (Object)null && !warnedNoRecipe)
			{
				warnedNoRecipe = true;
				Plugin.Log.LogWarning((object)"Could not read the selected recipe; the success rate will not be shown.");
			}
			float basePercent;
			float penalty;
			int streak;
			float trophy;
			float num = ComputeRate(selectedRecipe, item, out basePercent, out penalty, out streak, out trophy);
			if (num < 0f)
			{
				return cachedRateText;
			}
			string text = string.Format("\n<size={0}><color=yellow>Success rate: {1:0.#}%</color></size>", "100%", num);
			if (penalty > 0f || trophy > 0f)
			{
				string text2 = $"base {basePercent:0.#}%";
				if (penalty > 0f)
				{
					text2 += $", {streak} in a row -{penalty:0.#}%";
				}
				if (trophy > 0f)
				{
					text2 += $", trophy +{trophy:0.#}%";
				}
				text = text + " <size=80%>(" + text2 + ")</size>";
			}
			cachedRateText = text;
			return cachedRateText;
		}

		public static string AppendRate(string warningText, InventoryGui gui, ItemData item)
		{
			try
			{
				return warningText + BuildRateText(gui, item);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not build the success rate text: " + ex.Message));
				return warningText;
			}
		}

		public static void QueueNotice(string notice)
		{
			PendingNotices.Add(notice);
		}

		public static string AppendNotices(string message)
		{
			try
			{
				if (PendingNotices.Count == 0)
				{
					return message;
				}
				string result = message + "\n" + string.Join("\n", PendingNotices.ToArray());
				PendingNotices.Clear();
				return result;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not append upgrade notices: " + ex.Message));
				PendingNotices.Clear();
				return message;
			}
		}

		public static float AdjustRoll(InventoryGui gui, float roll)
		{
			try
			{
				float num = roll;
				PendingNotices.Clear();
				if (Plugin.PenaltyEnabled.Value)
				{
					object? obj = UpgradeItemField?.GetValue(gui);
					int streak;
					float penaltyPercent = GetPenaltyPercent((ItemData)((obj is ItemData) ? obj : null), out streak);
					if (penaltyPercent > 0f)
					{
						num += penaltyPercent / 100f;
						QueueNotice($"{streak} successful upgrades in a row: -{penaltyPercent:0.#}% success chance");
					}
				}
				num = TrophyBoost.ApplyBoost(num);
				return Math.Min(num, 1.5f);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Upgrade odds adjustment failed: " + ex.Message));
				return roll;
			}
		}

		public static void OnUpgradeSucceeded(InventoryGui gui, ItemData newItem)
		{
			try
			{
				if (Plugin.PenaltyEnabled.Value && newItem != null && newItem.m_customData != null)
				{
					object? obj = UpgradeItemField?.GetValue(gui);
					ItemData item = (ItemData)((obj is ItemData) ? obj : null);
					newItem.m_customData["FoP_UpgradeStreak"] = (GetStreak(item) + 1).ToString();
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not record upgrade streak: " + ex.Message));
			}
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "UpdateRecipe")]
	internal static class UpdateRecipePatch
	{
		private static readonly MethodInfo AppendRateMethod = AccessTools.Method(typeof(UpgradeOdds), "AppendRate", (Type[])null, (Type[])null);

		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> originalInstructions)
		{
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Expected O, but got Unknown
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Expected O, but got Unknown
			List<CodeInstruction> list = new List<CodeInstruction>(originalInstructions);
			int num = list.FindIndex((CodeInstruction instruction) => instruction.opcode == OpCodes.Ldstr && instruction.operand is string text && text == "$inventory_upgraderwarning");
			int num2 = -1;
			for (int num3 = 0; num3 < list.Count - 1; num3++)
			{
				if ((list[num3].operand is MethodBase { Name: "get_ItemData" } || list[num3].operand is FieldInfo { Name: "ItemData" }) && CodeInstructionExtensions.IsStloc(list[num3 + 1], (LocalBuilder)null))
				{
					num2 = list[num3 + 1].LocalIndex();
					break;
				}
			}
			if (num < 0 || num + 1 >= list.Count || !(list[num + 1].operand is MethodBase methodBase2) || methodBase2.Name != "Localize" || num2 < 0)
			{
				Plugin.Log.LogWarning((object)"Could not find the upgrader warning in UpdateRecipe; the success rate will not be shown.");
				return list;
			}
			list.InsertRange(num + 2, (IEnumerable<CodeInstruction>)(object)new CodeInstruction[3]
			{
				new CodeInstruction(OpCodes.Ldarg_0, (object)null),
				IlHelpers.LoadLocal(num2),
				new CodeInstruction(OpCodes.Call, (object)AppendRateMethod)
			});
			return list;
		}
	}
	public static class FailSpawner
	{
		private static readonly Random random = new Random();

		private static readonly HashSet<string> warnedPrefabs = new HashSet<string>();

		private static void WarnOnce(string key, string message)
		{
			if (warnedPrefabs.Add(key))
			{
				Plugin.Log.LogWarning((object)message);
			}
		}

		public static void SpawnPunishmentEnemies()
		{
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0181: Unknown result type (might be due to invalid IL or missing references)
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ed: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!Plugin.SpawnEnemyOnFail.Value)
				{
					return;
				}
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null || (Object)(object)ZNetScene.instance == (Object)null)
				{
					return;
				}
				List<GameObject> list = new List<GameObject>();
				string[] array = BossTracker.GetPrefabNameList().Split(new char[1] { ',' });
				for (int i = 0; i < array.Length; i++)
				{
					string text = array[i].Trim();
					if (text.Length != 0)
					{
						GameObject prefab = ZNetScene.instance.GetPrefab(text);
						if ((Object)(object)prefab == (Object)null)
						{
							WarnOnce("missing:" + text, "Creature '" + text + "' not found; skipping it.");
						}
						else if ((Object)(object)prefab.GetComponent<Character>() == (Object)null)
						{
							WarnOnce("notcreature:" + text, "'" + text + "' is not a creature; skipping it.");
						}
						else
						{
							list.Add(prefab);
						}
					}
				}
				if (list.Count == 0)
				{
					Plugin.Log.LogWarning((object)"No valid enemy prefabs configured; nothing spawned.");
					return;
				}
				float num2 = default(float);
				for (int j = 0; j < Plugin.EnemyCount.Value; j++)
				{
					GameObject obj = list[random.Next(list.Count)];
					float num = (float)(random.NextDouble() * Math.PI * 2.0);
					float value = Plugin.SpawnDistance.Value;
					Vector3 val = ((Component)localPlayer).transform.position + new Vector3(Mathf.Cos(num) * value, 0f, Mathf.Sin(num) * value);
					float y = ((Component)localPlayer).transform.position.y;
					if ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetSolidHeight(val, ref num2, 3) && num2 >= y - 3f && num2 <= y + 1.5f)
					{
						val.y = num2 + 0.5f;
					}
					else
					{
						val.y = y + 0.5f;
					}
					Character component = Object.Instantiate<GameObject>(obj, val, Quaternion.identity).GetComponent<Character>();
					if ((Object)(object)component != (Object)null)
					{
						component.SetLevel(Plugin.EnemyStars.Value + 1);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Failed to spawn punishment enemy: " + ex.Message));
			}
		}
	}
	public static class LocalizationTweaks
	{
		internal const string TextSize = "100%";

		private static readonly Dictionary<string, string> Translations = new Dictionary<string, string>
		{
			{ "inventory_upgraderwarning", "<size=100%>Warning - Item could lose upgrades.</size>" },
			{ "msg_upgrader_failed", "<size=100%>$1 level was reduced!</size>" }
		};

		private static readonly MethodInfo AddWordMethod = AccessTools.Method(typeof(Localization), "AddWord", new Type[2]
		{
			typeof(string),
			typeof(string)
		}, (Type[])null);

		private static readonly FieldInfo CacheField = AccessTools.Field(typeof(Localization), "m_cache");

		private static bool hasWarnedAboutMissingMethod;

		public static void Apply(Localization localization)
		{
			if (localization == null)
			{
				return;
			}
			if (AddWordMethod == null)
			{
				if (!hasWarnedAboutMissingMethod)
				{
					hasWarnedAboutMissingMethod = true;
					Plugin.Log.LogWarning((object)"Localization.AddWord(string, string) not found; the crafting panel text was not changed.");
				}
				return;
			}
			try
			{
				foreach (KeyValuePair<string, string> translation in Translations)
				{
					AddWordMethod.Invoke(localization, new object[2] { translation.Key, translation.Value });
				}
				object obj = CacheField?.GetValue(localization);
				if (obj != null)
				{
					AccessTools.Method(obj.GetType(), "EvictAll", (Type[])null, (Type[])null)?.Invoke(obj, null);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not add custom translations: " + ex.Message));
			}
		}
	}
	[HarmonyPatch]
	internal static class SetupLanguagePatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.Method(typeof(Localization), "SetupLanguage", (Type[])null, (Type[])null);
		}

		private static bool Prepare()
		{
			return TargetMethod() != null;
		}

		private static void Postfix(Localization __instance)
		{
			LocalizationTweaks.Apply(__instance);
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "DoCrafting")]
	internal static class DoCraftingPatch
	{
		private static readonly FieldInfo CraftRecipeField = AccessTools.Field(typeof(InventoryGui), "m_craftRecipe");

		private static readonly MethodInfo LevelAfterFailureMethod = AccessTools.Method(typeof(DoCraftingPatch), "GetLevelAfterFailure", (Type[])null, (Type[])null);

		private static readonly MethodInfo SpawnEnemiesMethod = AccessTools.Method(typeof(FailSpawner), "SpawnPunishmentEnemies", (Type[])null, (Type[])null);

		private static readonly MethodInfo BoostMethod = AccessTools.Method(typeof(UpgradeOdds), "AdjustRoll", (Type[])null, (Type[])null);

		private static readonly MethodInfo SuccessMethod = AccessTools.Method(typeof(UpgradeOdds), "OnUpgradeSucceeded", (Type[])null, (Type[])null);

		private static readonly MethodInfo AppendNoticesMethod = AccessTools.Method(typeof(UpgradeOdds), "AppendNotices", (Type[])null, (Type[])null);

		public static int GetLevelAfterFailure(int attemptedLevel)
		{
			if (Plugin.ResetToLevelOne.Value)
			{
				return 1;
			}
			int num = attemptedLevel - 1;
			return Math.Max(1, num - Plugin.LevelsLostOnFail.Value);
		}

		private static void Prefix(InventoryGui __instance)
		{
			object? obj = CraftRecipeField?.GetValue(__instance);
			Recipe val = (Recipe)((obj is Recipe) ? obj : null);
			if ((Object)(object)val == (Object)null || val.m_resources == null)
			{
				return;
			}
			Requirement[] resources = val.m_resources;
			foreach (Requirement val2 in resources)
			{
				if (val2 != null && val2.m_upgraderResource)
				{
					ItemDrop resItem = val2.m_resItem;
					if (!((Object)(object)resItem == (Object)null) && resItem.m_itemData != null && resItem.m_itemData.m_shared != null)
					{
						resItem.m_itemData.m_shared.m_breakChance = -1f;
					}
				}
			}
		}

		private static bool InsertBoostHook(List<CodeInstruction> instructions)
		{
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Expected O, but got Unknown
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Expected O, but got Unknown
			for (int i = 0; i < instructions.Count - 2; i++)
			{
				if (instructions[i].operand is MethodBase methodBase && !(methodBase.Name != "Range") && !(methodBase.DeclaringType != typeof(Random)) && CodeInstructionExtensions.IsStloc(instructions[i + 1], (LocalBuilder)null))
				{
					ParameterInfo[] parameters = methodBase.GetParameters();
					if (parameters.Length == 2 && !(parameters[0].ParameterType != typeof(float)))
					{
						int index = instructions[i + 1].LocalIndex();
						CodeInstruction val = new CodeInstruction(OpCodes.Ldarg_0, (object)null);
						val.labels.AddRange(instructions[i + 2].labels);
						instructions[i + 2].labels.Clear();
						instructions.InsertRange(i + 2, (IEnumerable<CodeInstruction>)(object)new CodeInstruction[4]
						{
							val,
							IlHelpers.LoadLocal(index),
							new CodeInstruction(OpCodes.Call, (object)BoostMethod),
							IlHelpers.StoreLocal(index)
						});
						return true;
					}
				}
			}
			return false;
		}

		private static bool InsertSuccessHook(List<CodeInstruction> instructions)
		{
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Expected O, but got Unknown
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Expected O, but got Unknown
			int num = instructions.FindIndex((CodeInstruction instruction) => instruction.opcode == OpCodes.Ldstr && instruction.operand is string text && text.Contains("succeeded"));
			if (num < 0)
			{
				return false;
			}
			for (int num2 = num; num2 < instructions.Count - 1; num2++)
			{
				if (instructions[num2].operand is MethodBase { Name: "AddItem" } && CodeInstructionExtensions.IsStloc(instructions[num2 + 1], (LocalBuilder)null))
				{
					int index = instructions[num2 + 1].LocalIndex();
					instructions.InsertRange(num2 + 2, (IEnumerable<CodeInstruction>)(object)new CodeInstruction[3]
					{
						new CodeInstruction(OpCodes.Ldarg_0, (object)null),
						IlHelpers.LoadLocal(index),
						new CodeInstruction(OpCodes.Call, (object)SuccessMethod)
					});
					return true;
				}
			}
			return false;
		}

		private static int InsertNoticeHooks(List<CodeInstruction> instructions)
		{
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Expected O, but got Unknown
			int num = 0;
			for (int i = 0; i < instructions.Count; i++)
			{
				if (instructions[i].opcode != OpCodes.Ldstr || !(instructions[i].operand is string text) || (text != "$msg_upgrader_success" && text != "$msg_upgrader_failed"))
				{
					continue;
				}
				for (int j = i + 1; j < instructions.Count; j++)
				{
					if (instructions[j].operand is MethodBase { Name: "Localize" })
					{
						instructions.Insert(j + 1, new CodeInstruction(OpCodes.Call, (object)AppendNoticesMethod));
						num++;
						i = j + 1;
						break;
					}
				}
			}
			return num;
		}

		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> originalInstructions)
		{
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: Expected O, but got Unknown
			List<CodeInstruction> list = new List<CodeInstruction>(originalInstructions);
			int num = list.FindIndex((CodeInstruction instruction) => instruction.opcode == OpCodes.Ldstr && instruction.operand is string text && text.Contains("FAILED"));
			if (num < 0)
			{
				Plugin.Log.LogError((object)"Game build differs from the one the mod was written for; transpiler skipped.");
				return list;
			}
			int num2 = 0;
			for (int num3 = num; num3 < list.Count - 2 && !(list[num3].operand is MethodBase { Name: "AddItem" }); num3++)
			{
				if (CodeInstructionExtensions.IsLdloc(list[num3], (LocalBuilder)null) && list[num3 + 1].opcode == OpCodes.Ldc_I4_2 && list[num3 + 2].opcode == OpCodes.Sub)
				{
					list[num3 + 1].opcode = OpCodes.Call;
					list[num3 + 1].operand = LevelAfterFailureMethod;
					list.RemoveAt(num3 + 2);
					num2++;
				}
			}
			if (num2 == 0)
			{
				Plugin.Log.LogWarning((object)"Could not find the level loss in DoCrafting; failed upgrades will use the vanilla level loss.");
			}
			CodeInstruction val = new CodeInstruction(OpCodes.Call, (object)SpawnEnemiesMethod);
			val.labels.AddRange(list[num].labels);
			list[num].labels.Clear();
			list.Insert(num, val);
			if (!InsertBoostHook(list))
			{
				Plugin.Log.LogWarning((object)"Could not find the upgrade roll in DoCrafting; trophy boosts and the success penalty will not apply.");
			}
			if (!InsertSuccessHook(list))
			{
				Plugin.Log.LogWarning((object)"Could not find the upgrade success in DoCrafting; success streaks will not be recorded.");
			}
			if (InsertNoticeHooks(list) == 0)
			{
				Plugin.Log.LogWarning((object)"Could not find the upgrade messages in DoCrafting; trophy and streak notices will not be shown.");
			}
			return list;
		}
	}
}