Decompiled source of Odins Favor v0.2.0

OdinsFavor.dll

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

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: AssemblyVersion("0.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 OdinsFavor
{
	public interface ICondition
	{
		bool IsMet();

		string Describe();
	}
	internal class Boss
	{
		public string Key;

		public string Name;

		public string Message;

		public static readonly Dictionary<string, Boss> ByAlias;

		static Boss()
		{
			ByAlias = new Dictionary<string, Boss>(StringComparer.OrdinalIgnoreCase);
			Add(new Boss
			{
				Key = "defeated_eikthyr",
				Name = "Eikthyr",
				Message = "The thunder of Eikthyr awakens {mod}"
			}, "eikthyr");
			Add(new Boss
			{
				Key = "defeated_gdking",
				Name = "The Elder",
				Message = "The roots of The Elder release {mod}"
			}, "elder", "theelder", "gdking");
			Add(new Boss
			{
				Key = "defeated_bonemass",
				Name = "Bonemass",
				Message = "Bonemass's rot is cleansed. {mod} awakens"
			}, "bonemass");
			Add(new Boss
			{
				Key = "defeated_dragon",
				Name = "Moder",
				Message = "Moder's frost breaks. {mod} awakens"
			}, "moder", "dragon");
			Add(new Boss
			{
				Key = "defeated_goblinking",
				Name = "Yagluth",
				Message = "Yagluth's flame is quenched. {mod} awakens"
			}, "yagluth", "goblinking");
			Add(new Boss
			{
				Key = "defeated_queen",
				Name = "The Queen",
				Message = "The Queen's mist lifts. {mod} awakens"
			}, "queen", "thequeen", "seekerqueen");
			Add(new Boss
			{
				Key = "defeated_fader",
				Name = "Fader",
				Message = "Fader falls. {mod} awakens"
			}, "fader");
		}

		private static void Add(Boss boss, params string[] aliases)
		{
			foreach (string key in aliases)
			{
				ByAlias[key] = boss;
			}
		}
	}
	internal class KeyCondition : ICondition
	{
		public readonly string Key;

		public readonly Boss Boss;

		public KeyCondition(string key, Boss boss)
		{
			Key = key;
			Boss = boss;
		}

		public bool IsMet()
		{
			if ((Object)(object)ZoneSystem.instance != (Object)null)
			{
				return ZoneSystem.instance.GetGlobalKey(Key);
			}
			return false;
		}

		public string Describe()
		{
			if (Boss == null)
			{
				return "world key " + Key;
			}
			return Boss.Name + " defeated";
		}
	}
	internal class DaysCondition : ICondition
	{
		private readonly int _days;

		public DaysCondition(int days)
		{
			_days = days;
		}

		public bool IsMet()
		{
			if ((Object)(object)EnvMan.instance != (Object)null)
			{
				return EnvMan.instance.GetDay() >= _days;
			}
			return false;
		}

		public string Describe()
		{
			return $"day {_days}";
		}
	}
	internal class SkillCondition : ICondition
	{
		private readonly SkillType _skill;

		private readonly float _level;

		public SkillCondition(SkillType skill, float level)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			_skill = skill;
			_level = level;
		}

		public bool IsMet()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null)
			{
				return ((Character)localPlayer).GetSkills().GetSkillLevel(_skill) >= _level;
			}
			return false;
		}

		public string Describe()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			return $"{ConditionParser.Pretty(((object)_skill/*cast due to .constrained prefix*/).ToString())} level {_level}";
		}
	}
	internal class BiomeCondition : ICondition
	{
		private static readonly FieldRef<Player, HashSet<string>> KnownBiomes = AccessTools.FieldRefAccess<Player, HashSet<string>>("m_knownBiome");

		private readonly Biome _biome;

		private readonly string _key;

		public unsafe BiomeCondition(Biome biome)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			_biome = biome;
			_key = "$biome_" + ((object)(*(Biome*)(&biome))/*cast due to .constrained prefix*/).ToString().ToLower();
		}

		public bool IsMet()
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return false;
			}
			return KnownBiomes.Invoke(localPlayer).Any((string b) => b == _key || b.EndsWith(" " + _key));
		}

		public string Describe()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			return "discovered " + ConditionParser.Pretty(((object)_biome/*cast due to .constrained prefix*/).ToString());
		}
	}
	internal abstract class PrefabCondition : ICondition
	{
		protected readonly string Prefab;

		private string _internalName;

		private bool _warned;

		protected PrefabCondition(string prefab)
		{
			Prefab = prefab;
		}

		public abstract bool IsMet();

		public abstract string Describe();

		protected abstract string Lookup();

		protected string InternalName()
		{
			if (_internalName != null)
			{
				return _internalName;
			}
			if (Prefab.StartsWith("$"))
			{
				return _internalName = Prefab;
			}
			string text = Lookup();
			if (text == null)
			{
				return null;
			}
			if (text.Length == 0)
			{
				if (!_warned)
				{
					ConditionParser.Warn?.Invoke("No item or creature called \"" + Prefab + "\" exists. Check the spelling (use the spawn name, e.g. Greyling, Iron).");
				}
				_warned = true;
				return null;
			}
			return _internalName = text;
		}
	}
	internal class ItemCondition : PrefabCondition
	{
		public ItemCondition(string prefab)
			: base(prefab)
		{
		}

		protected override string Lookup()
		{
			if ((Object)(object)ObjectDB.instance == (Object)null || ObjectDB.instance.m_items.Count == 0)
			{
				return null;
			}
			GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(Prefab);
			ItemDrop val = default(ItemDrop);
			if (!((Object)(object)itemPrefab != (Object)null) || !itemPrefab.TryGetComponent<ItemDrop>(ref val))
			{
				return "";
			}
			return val.m_itemData.m_shared.m_name;
		}

		public override bool IsMet()
		{
			Player localPlayer = Player.m_localPlayer;
			string text = InternalName();
			if ((Object)(object)localPlayer != (Object)null && text != null)
			{
				return localPlayer.IsKnownMaterial(text);
			}
			return false;
		}

		public override string Describe()
		{
			return "found " + Prefab;
		}
	}
	internal class KillsCondition : PrefabCondition
	{
		private readonly int _count;

		public KillsCondition(string prefab, int count)
			: base(prefab)
		{
			_count = count;
		}

		protected override string Lookup()
		{
			if ((Object)(object)ZNetScene.instance == (Object)null)
			{
				return null;
			}
			GameObject prefab = ZNetScene.instance.GetPrefab(Prefab);
			Character val = default(Character);
			if (!((Object)(object)prefab != (Object)null) || !prefab.TryGetComponent<Character>(ref val))
			{
				return "";
			}
			return val.m_name;
		}

		public override bool IsMet()
		{
			Game instance = Game.instance;
			PlayerProfile val = ((instance != null) ? instance.GetPlayerProfile() : null);
			string text = InternalName();
			if (val == null || text == null)
			{
				return false;
			}
			if (val.m_playerStats[0].m_enemyStats[0].TryGetValue(text, out var value))
			{
				return value >= (float)_count;
			}
			return false;
		}

		public override string Describe()
		{
			return $"{_count} {Prefab} kills";
		}
	}
	internal class AllCondition : ICondition
	{
		public readonly List<ICondition> Parts;

		public AllCondition(List<ICondition> parts)
		{
			Parts = parts;
		}

		public bool IsMet()
		{
			return Parts.All((ICondition p) => p.IsMet());
		}

		public string Describe()
		{
			return string.Join(" and ", Parts.Select((ICondition p) => (!(p is AnyCondition)) ? p.Describe() : ("(" + p.Describe() + ")")));
		}
	}
	internal class AnyCondition : ICondition
	{
		public readonly List<ICondition> Parts;

		public AnyCondition(List<ICondition> parts)
		{
			Parts = parts;
		}

		public bool IsMet()
		{
			return Parts.Any((ICondition p) => p.IsMet());
		}

		public string Describe()
		{
			return string.Join(" or ", Parts.Select((ICondition p) => p.Describe()));
		}
	}
	public static class ConditionParser
	{
		public const string Help = "boss:bonemass   days:30   skill:woodcutting:50   item:Iron   biome:Mistlands   kills:Greyling:100   key:defeated_eikthyr\nCombine with 'and' / 'or' (or & and |), group with ( ).";

		public static Action<string> Warn;

		private static readonly Dictionary<string, string> PrettyNames = new Dictionary<string, string>
		{
			{ "WoodCutting", "Woodcutting" },
			{ "AshLands", "Ashlands" }
		};

		public static ICondition Parse(string text)
		{
			List<string> list = Tokenize(text);
			if (list.Count == 0)
			{
				throw new FormatException("The rule is empty.");
			}
			int pos = 0;
			ICondition result = ParseAny(list, ref pos);
			if (pos < list.Count)
			{
				throw new FormatException("Didn't expect \"" + list[pos] + "\" there.");
			}
			return result;
		}

		internal static string Pretty(string name)
		{
			if (PrettyNames.TryGetValue(name, out var value))
			{
				return value;
			}
			return string.Concat(name.Select((char c, int i) => (i <= 0 || !char.IsUpper(c)) ? c.ToString() : (" " + c)));
		}

		internal static Boss FirstBoss(ICondition condition)
		{
			if (!(condition is KeyCondition keyCondition))
			{
				if (!(condition is AllCondition allCondition))
				{
					if (condition is AnyCondition anyCondition)
					{
						return anyCondition.Parts.Select(FirstBoss).FirstOrDefault((Boss b) => b != null);
					}
					return null;
				}
				return allCondition.Parts.Select(FirstBoss).FirstOrDefault((Boss b) => b != null);
			}
			return keyCondition.Boss;
		}

		private static List<string> Tokenize(string text)
		{
			List<string> list = new List<string>();
			string text2 = "";
			string text3 = text ?? "";
			for (int i = 0; i < text3.Length; i++)
			{
				char c = text3[i];
				if (char.IsWhiteSpace(c) || c == '(' || c == ')' || c == '&' || c == '|')
				{
					if (text2.Length > 0)
					{
						list.Add(text2);
					}
					text2 = "";
					if (!char.IsWhiteSpace(c))
					{
						list.Add(c.ToString());
					}
				}
				else
				{
					text2 += c;
				}
			}
			if (text2.Length > 0)
			{
				list.Add(text2);
			}
			return list.Select((string t) => (!t.Equals("and", StringComparison.OrdinalIgnoreCase)) ? ((!t.Equals("or", StringComparison.OrdinalIgnoreCase)) ? t : "|") : "&").ToList();
		}

		private static ICondition ParseAny(List<string> tokens, ref int pos)
		{
			List<ICondition> list = new List<ICondition> { ParseAll(tokens, ref pos) };
			while (pos < tokens.Count && tokens[pos] == "|")
			{
				pos++;
				list.Add(ParseAll(tokens, ref pos));
			}
			if (list.Count != 1)
			{
				return new AnyCondition(list);
			}
			return list[0];
		}

		private static ICondition ParseAll(List<string> tokens, ref int pos)
		{
			List<ICondition> list = new List<ICondition> { ParseAtom(tokens, ref pos) };
			while (pos < tokens.Count && tokens[pos] == "&")
			{
				pos++;
				list.Add(ParseAtom(tokens, ref pos));
			}
			if (list.Count != 1)
			{
				return new AllCondition(list);
			}
			return list[0];
		}

		private static ICondition ParseAtom(List<string> tokens, ref int pos)
		{
			if (pos >= tokens.Count)
			{
				throw new FormatException("The rule ends too early. Is something missing after 'and' / 'or'?");
			}
			string text = tokens[pos++];
			switch (text)
			{
			case "(":
			{
				ICondition result = ParseAny(tokens, ref pos);
				if (pos >= tokens.Count || tokens[pos] != ")")
				{
					throw new FormatException("A '(' is never closed with ')'.");
				}
				pos++;
				return result;
			}
			case ")":
			case "&":
			case "|":
				throw new FormatException("Didn't expect \"" + text + "\" there.");
			default:
				return ParseSingle(text);
			}
		}

		private static ICondition ParseSingle(string token)
		{
			//IL_02e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f4: Invalid comparison between Unknown and I4
			//IL_0313: Unknown result type (might be due to invalid IL or missing references)
			//IL_0267: Unknown result type (might be due to invalid IL or missing references)
			//IL_026a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0270: Invalid comparison between Unknown and I4
			//IL_028f: Unknown result type (might be due to invalid IL or missing references)
			string[] parts = token.Split(new char[1] { ':' });
			switch (parts[0].ToLowerInvariant())
			{
			case "boss":
			{
				Expect(parts, 2, token, "boss:bonemass");
				if (!Boss.ByAlias.TryGetValue(parts[1], out var value))
				{
					throw new FormatException("Unknown boss \"" + parts[1] + "\". Use one of: eikthyr, elder, bonemass, moder, yagluth, queen, fader.");
				}
				return new KeyCondition(value.Key, value);
			}
			case "key":
				Expect(parts, 2, token, "key:defeated_eikthyr");
				return new KeyCondition(parts[1], Boss.ByAlias.Values.FirstOrDefault((Boss b) => b.Key == parts[1]));
			case "days":
			case "day":
				Expect(parts, 2, token, "days:30");
				return new DaysCondition(Number(parts[1], token));
			case "skill":
			{
				Expect(parts, 3, token, "skill:woodcutting:50");
				if (!Enum.TryParse<SkillType>(parts[1], ignoreCase: true, out SkillType result2) || (int)result2 == 0 || (int)result2 == 999)
				{
					throw new FormatException("Unknown skill \"" + parts[1] + "\". Examples: woodcutting, pickaxes, run, swords, cooking.");
				}
				return new SkillCondition(result2, Number(parts[2], token));
			}
			case "item":
				Expect(parts, 2, token, "item:Iron");
				return new ItemCondition(parts[1]);
			case "biome":
			{
				Expect(parts, 2, token, "biome:Mistlands");
				if (!Enum.TryParse<Biome>(parts[1], ignoreCase: true, out Biome result) || (int)result == 0 || (int)result == 895)
				{
					throw new FormatException("Unknown biome \"" + parts[1] + "\". Use one of: Meadows, BlackForest, Swamp, Mountain, Plains, Mistlands, AshLands, DeepNorth, Ocean.");
				}
				return new BiomeCondition(result);
			}
			case "kill":
			case "kills":
				Expect(parts, 3, token, "kills:Greyling:100");
				return new KillsCondition(parts[1], Number(parts[2], token));
			default:
				throw new FormatException("Unknown rule \"" + token + "\". Rules look like: boss:bonemass, days:30, skill:woodcutting:50, item:Iron, biome:Mistlands, kills:Greyling:100, key:defeated_eikthyr.");
			}
		}

		private static void Expect(string[] parts, int count, string token, string example)
		{
			if (parts.Length != count || parts.Any(string.IsNullOrWhiteSpace))
			{
				throw new FormatException("\"" + token + "\" should look like " + example + ".");
			}
		}

		private static int Number(string text, string token)
		{
			if (!int.TryParse(text, out var result) || result < 0)
			{
				throw new FormatException("\"" + text + "\" in \"" + token + "\" should be a whole number.");
			}
			return result;
		}
	}
	public enum UnlockAfter
	{
		AlwaysOn,
		Eikthyr,
		TheElder,
		Bonemass,
		Moder,
		Yagluth,
		TheQueen,
		Fader,
		Custom
	}
	[BepInPlugin("giridhar.odinsfavor", "Odin's Favor", "0.2.0")]
	public class OdinsFavorPlugin : BaseUnityPlugin
	{
		private enum PatchKind
		{
			Prefix,
			Postfix,
			Transpiler,
			Finalizer,
			ILManipulator
		}

		private class SavedPatch
		{
			public MethodBase Target;

			public Patch Patch;

			public PatchKind Kind;
		}

		private class Gate
		{
			public PluginInfo Info;

			public ConfigEntry<UnlockAfter> Entry;

			public ConfigEntry<string> Rule;

			public ConfigEntry<string> Message;

			public bool Running = true;

			public bool HasRun;

			public ICondition Condition;

			public string ConditionSource;

			public string PendingSource;

			public float PendingSince;

			public bool Earned;

			public readonly List<SavedPatch> Saved = new List<SavedPatch>();

			public readonly HashSet<int> SetupDoneBeforeLock = new HashSet<int>();

			public readonly Dictionary<MethodBase, int> SetupRunsAtLock = new Dictionary<MethodBase, int>();
		}

		public const string Guid = "giridhar.odinsfavor";

		public const string ModName = "Odin's Favor";

		public const string Version = "0.2.0";

		private const string DefaultRuleMessage = "Proven worthy: {condition}|{mod} awakens";

		private const float QuietSecondsAfterSpawn = 3f;

		private const float SettingSettleSeconds = 2f;

		private const float SecondsBetweenMessages = 4f;

		private static readonly string[] AlwaysOnNamespaces = new string[2] { "ServerSync", "LocalizationManager" };

		private static readonly HashSet<string> SetupMethods = new HashSet<string> { "Awake", "Start" };

		private static readonly Dictionary<MethodBase, int> SetupRuns = new Dictionary<MethodBase, int>();

		private readonly HashSet<MethodBase> _tracked = new HashSet<MethodBase>();

		private ConfigEntry<bool> _modEnabled;

		private ConfigEntry<bool> _showMessages;

		private readonly List<Gate> _gates = new List<Gate>();

		private Harmony _harmony;

		private ZoneSystem _lastWorld;

		private PlayerProfile _lastProfile;

		private float _playerSeenAt = -1f;

		private float _nextCheck;

		private readonly Queue<string> _pendingWarnings = new Queue<string>();

		private readonly Queue<string> _pendingUnlocks = new Queue<string>();

		private float _nextUnlockMessageAt;

		private static void TrackSetup(MethodBase __originalMethod)
		{
			SetupRuns.TryGetValue(__originalMethod, out var value);
			SetupRuns[__originalMethod] = value + 1;
		}

		private static int RunsOf(MethodBase method)
		{
			if (!SetupRuns.TryGetValue(method, out var value))
			{
				return 0;
			}
			return value;
		}

		private void Awake()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Expected O, but got Unknown
			_harmony = new Harmony("giridhar.odinsfavor");
			ConditionParser.Warn = Warn;
			_modEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "Enabled", true, "Turn Odin's Favor off to run every mod normally.");
			_showMessages = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "ShowUnlockMessages", true, "Show an on-screen message when a locked mod unlocks.");
		}

		private void Start()
		{
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Expected O, but got Unknown
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Expected O, but got Unknown
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Expected O, but got Unknown
			foreach (PluginInfo item in Chainloader.PluginInfos.Values.OrderBy((PluginInfo p) => p.Metadata.Name))
			{
				if (!((Object)(object)item.Instance == (Object)null) && !(item.Metadata.GUID == "giridhar.odinsfavor"))
				{
					ConfigEntry<UnlockAfter> entry = ((BaseUnityPlugin)this).Config.Bind<UnlockAfter>("2 - Mods", CleanKey(item.Metadata.Name), UnlockAfter.AlwaysOn, new ConfigDescription("When should " + item.Metadata.Name + " turn on? Pick a boss, or Custom to write your own rule in '4 - Custom rules'.\nGUID: " + item.Metadata.GUID, (AcceptableValueBase)null, Array.Empty<object>()));
					ConfigEntry<string> message = ((BaseUnityPlugin)this).Config.Bind<string>("3 - Messages", CleanKey(item.Metadata.Name), "", new ConfigDescription("Message shown when " + item.Metadata.Name + " unlocks. Leave empty for the default.\nUse {mod}, {boss} and {condition} as placeholders, and | for a second line.\nExample: The power of {boss} surges|Torches now burn forever", (AcceptableValueBase)null, Array.Empty<object>()));
					ConfigEntry<string> rule = ((BaseUnityPlugin)this).Config.Bind<string>("4 - Custom rules", CleanKey(item.Metadata.Name), "", new ConfigDescription("Your own unlock rule for " + item.Metadata.Name + ". Only used when '2 - Mods' is set to Custom.\nboss:bonemass   days:30   skill:woodcutting:50   item:Iron   biome:Mistlands   kills:Greyling:100   key:defeated_eikthyr\nCombine with 'and' / 'or' (or & and |), group with ( ).\nExample: boss:bonemass and (days:50 or skill:woodcutting:50)", (AcceptableValueBase)null, Array.Empty<object>()));
					_gates.Add(new Gate
					{
						Info = item,
						Entry = entry,
						Rule = rule,
						Message = message
					});
				}
			}
			((BaseUnityPlugin)this).Config.Save();
			((BaseUnityPlugin)this).Logger.LogInfo((object)$"Odin's Favor found {_gates.Count} mods.");
		}

		private void Update()
		{
			if (Time.unscaledTime < _nextCheck)
			{
				return;
			}
			_nextCheck = Time.unscaledTime + 1f;
			ZoneSystem instance = ZoneSystem.instance;
			Game instance2 = Game.instance;
			PlayerProfile val = ((instance2 != null) ? instance2.GetPlayerProfile() : null);
			if ((Object)(object)instance != (Object)(object)_lastWorld || val != _lastProfile)
			{
				foreach (Gate gate in _gates)
				{
					gate.Earned = false;
				}
				_lastWorld = instance;
				_lastProfile = val;
			}
			if ((Object)(object)Player.m_localPlayer == (Object)null)
			{
				_playerSeenAt = -1f;
			}
			else if (_playerSeenAt < 0f)
			{
				_playerSeenAt = Time.unscaledTime;
			}
			bool flag = _playerSeenAt >= 0f && Time.unscaledTime - _playerSeenAt >= 3f;
			foreach (Gate gate2 in _gates)
			{
				try
				{
					bool flag2 = ShouldRun(gate2, instance);
					if (flag2 == gate2.Running)
					{
						continue;
					}
					if (flag2)
					{
						Resume(gate2);
						if (flag && _modEnabled.Value && _showMessages.Value)
						{
							ShowUnlockMessage(gate2);
						}
					}
					else
					{
						Suspend(gate2);
					}
					goto IL_0184;
				}
				catch (Exception arg)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)$"Error while switching {gate2.Info.Metadata.Name}; it may be partly locked: {arg}");
					goto IL_0184;
				}
				IL_0184:
				if (gate2.Running)
				{
					gate2.HasRun = true;
				}
			}
			if ((Object)(object)instance != (Object)null && (Object)(object)MessageHud.instance != (Object)null)
			{
				while (_pendingWarnings.Count > 0)
				{
					MessageHud.instance.ShowMessage((MessageType)1, _pendingWarnings.Dequeue(), 0, (Sprite)null, false, true);
				}
				if (_pendingUnlocks.Count > 0 && Time.unscaledTime >= _nextUnlockMessageAt)
				{
					MessageHud.instance.ShowMessage((MessageType)2, _pendingUnlocks.Dequeue(), 0, (Sprite)null, false, true);
					_nextUnlockMessageAt = Time.unscaledTime + 4f;
				}
			}
			else
			{
				_pendingUnlocks.Clear();
			}
		}

		private void Warn(string text)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)text);
			_pendingWarnings.Enqueue("Odin's Favor: " + text);
		}

		private void ShowUnlockMessage(Gate gate)
		{
			if (gate.Condition != null)
			{
				Boss boss = ConditionParser.FirstBoss(gate.Condition);
				string text = ((!string.IsNullOrWhiteSpace(gate.Message.Value)) ? gate.Message.Value : ((gate.Condition is KeyCondition { Boss: not null } keyCondition) ? keyCondition.Boss.Message : "Proven worthy: {condition}|{mod} awakens"));
				string text2 = gate.Condition.Describe();
				text = text.Replace("{mod}", gate.Info.Metadata.Name).Replace("{boss}", boss?.Name ?? "").Replace("{condition}", char.ToUpper(text2[0]) + text2.Substring(1))
					.Replace("|", "\n");
				_pendingUnlocks.Enqueue(text);
			}
		}

		private bool ShouldRun(Gate gate, ZoneSystem world)
		{
			if (!_modEnabled.Value)
			{
				return true;
			}
			UpdateCondition(gate);
			if (gate.Condition == null)
			{
				return true;
			}
			if (gate.Earned)
			{
				return true;
			}
			if ((Object)(object)world == (Object)null)
			{
				return false;
			}
			return gate.Earned = gate.Condition.IsMet();
		}

		private void UpdateCondition(Gate gate)
		{
			UnlockAfter value = gate.Entry.Value;
			string text = ((value == UnlockAfter.Custom) ? ("custom:" + gate.Rule.Value) : value.ToString());
			if (text == gate.ConditionSource)
			{
				gate.PendingSource = null;
				return;
			}
			if (gate.ConditionSource != null)
			{
				if (text != gate.PendingSource)
				{
					gate.PendingSource = text;
					gate.PendingSince = Time.unscaledTime;
					return;
				}
				if (Time.unscaledTime - gate.PendingSince < 2f)
				{
					return;
				}
			}
			gate.PendingSource = null;
			ICondition condition = null;
			switch (value)
			{
			case UnlockAfter.Custom:
				try
				{
					condition = ConditionParser.Parse(gate.Rule.Value);
				}
				catch (FormatException ex)
				{
					string text2 = (string.IsNullOrWhiteSpace(gate.Rule.Value) ? "it's set to Custom but has no rule in '4 - Custom rules'" : ex.Message);
					Warn((gate.Condition != null) ? ("Can't read the new rule for " + gate.Info.Metadata.Name + ", so it keeps the old one (" + gate.Condition.Describe() + "): " + text2) : ("Can't read the rule for " + gate.Info.Metadata.Name + ", so it stays on: " + text2));
					gate.ConditionSource = text;
					return;
				}
				break;
			default:
			{
				Boss boss = Boss.ByAlias[value.ToString()];
				condition = new KeyCondition(boss.Key, boss);
				break;
			}
			case UnlockAfter.AlwaysOn:
				break;
			}
			gate.ConditionSource = text;
			gate.Condition = condition;
			gate.Earned = false;
			if (condition != null)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)(gate.Info.Metadata.Name + " unlocks when: " + condition.Describe()));
			}
		}

		private void Suspend(Gate gate)
		{
			Assembly assembly = ((object)gate.Info.Instance).GetType().Assembly;
			foreach (MethodBase item in Harmony.GetAllPatchedMethods().ToList())
			{
				Patches patchInfo = Harmony.GetPatchInfo(item);
				if (patchInfo != null)
				{
					Collect(gate, item, patchInfo.Prefixes, PatchKind.Prefix, assembly);
					Collect(gate, item, patchInfo.Postfixes, PatchKind.Postfix, assembly);
					Collect(gate, item, patchInfo.Transpilers, PatchKind.Transpiler, assembly);
					Collect(gate, item, patchInfo.Finalizers, PatchKind.Finalizer, assembly);
					Collect(gate, item, patchInfo.ILManipulators, PatchKind.ILManipulator, assembly);
				}
			}
			gate.SetupDoneBeforeLock.Clear();
			gate.SetupRunsAtLock.Clear();
			foreach (SavedPatch item2 in SetupTargets(gate))
			{
				TrackSetupMethod(item2.Target);
				gate.SetupRunsAtLock[item2.Target] = RunsOf(item2.Target);
				foreach (Object item3 in LiveInstances(item2.Target))
				{
					gate.SetupDoneBeforeLock.Add(item3.GetInstanceID());
				}
			}
			((Behaviour)gate.Info.Instance).enabled = false;
			gate.Running = false;
			((BaseUnityPlugin)this).Logger.LogInfo((object)$"Locked {gate.Info.Metadata.Name} ({gate.Saved.Count} patches suspended).");
			if (gate.HasRun)
			{
				string text = gate.Info.Metadata.Name + " is locked again. Restart the game to lock it fully.";
				((BaseUnityPlugin)this).Logger.LogWarning((object)text);
				MessageHud instance = MessageHud.instance;
				if (instance != null)
				{
					instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false, true);
				}
			}
		}

		private void Collect(Gate gate, MethodBase target, IEnumerable<Patch> patches, PatchKind kind, Assembly asm)
		{
			if (patches == null)
			{
				return;
			}
			foreach (Patch item in patches.Where((Patch p) => p.PatchMethod?.DeclaringType?.Assembly == asm && !IsAlwaysOn(p, target)).ToList())
			{
				try
				{
					_harmony.Unpatch(target, item.PatchMethod);
					gate.Saved.Add(new SavedPatch
					{
						Target = target,
						Patch = item,
						Kind = kind
					});
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not suspend a patch from " + gate.Info.Metadata.Name + " on " + target.Name + ": " + ex.Message));
				}
			}
		}

		private void Resume(Gate gate)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Expected O, but got Unknown
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Expected O, but got Unknown
			foreach (SavedPatch item in gate.Saved)
			{
				try
				{
					Patch patch = item.Patch;
					HarmonyMethod val = new HarmonyMethod(patch.PatchMethod, patch.priority, patch.before, patch.after, (bool?)patch.debug)
					{
						wrapTryCatch = patch.wrapTryCatch
					};
					Harmony val2 = new Harmony(patch.owner);
					switch (item.Kind)
					{
					case PatchKind.Prefix:
						val2.Patch(item.Target, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
						break;
					case PatchKind.Postfix:
						val2.Patch(item.Target, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
						break;
					case PatchKind.Transpiler:
						val2.Patch(item.Target, (HarmonyMethod)null, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null);
						break;
					case PatchKind.Finalizer:
						val2.Patch(item.Target, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, val, (HarmonyMethod)null);
						break;
					case PatchKind.ILManipulator:
						val2.Patch(item.Target, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, val);
						break;
					}
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not restore a patch for " + gate.Info.Metadata.Name + " on " + item.Target.Name + ": " + ex.Message));
				}
			}
			int num = ReplayMissedSetup(gate);
			((BaseUnityPlugin)this).Logger.LogInfo((object)$"Unlocked {gate.Info.Metadata.Name} ({gate.Saved.Count} patches restored, {num} setup steps replayed).");
			gate.Saved.Clear();
			gate.SetupDoneBeforeLock.Clear();
			gate.SetupRunsAtLock.Clear();
			((Behaviour)gate.Info.Instance).enabled = true;
			gate.Running = true;
		}

		private static bool IsAlwaysOn(Patch patch, MethodBase target)
		{
			if (target.DeclaringType?.FullName == "Localization")
			{
				return true;
			}
			string ns = patch.PatchMethod.DeclaringType?.Namespace ?? "";
			return AlwaysOnNamespaces.Any((string n) => ns == n || ns.StartsWith(n + "."));
		}

		private static IEnumerable<SavedPatch> SetupTargets(Gate gate)
		{
			return gate.Saved.Where((SavedPatch s) => s.Kind == PatchKind.Postfix && !s.Target.IsStatic && SetupMethods.Contains(s.Target.Name) && typeof(Object).IsAssignableFrom(s.Target.DeclaringType));
		}

		private static IEnumerable<Object> LiveInstances(MethodBase target)
		{
			return from o in Object.FindObjectsByType(target.DeclaringType, (FindObjectsSortMode)0)
				where o != (Object)null
				select o;
		}

		private void TrackSetupMethod(MethodBase target)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Expected O, but got Unknown
			if (!_tracked.Add(target))
			{
				return;
			}
			try
			{
				_harmony.Patch(target, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(OdinsFavorPlugin), "TrackSetup", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not watch " + target.DeclaringType?.Name + "." + target.Name + ": " + ex.Message));
			}
		}

		private int ReplayMissedSetup(Gate gate)
		{
			int num = 0;
			foreach (SavedPatch item in SetupTargets(gate))
			{
				gate.SetupRunsAtLock.TryGetValue(item.Target, out var value);
				if (RunsOf(item.Target) <= value)
				{
					continue;
				}
				List<object> list = (from o in LiveInstances(item.Target)
					where !gate.SetupDoneBeforeLock.Contains(o.GetInstanceID())
					select o).Cast<object>().ToList();
				if (list.Count == 0)
				{
					if (NeedsInstance(item))
					{
						continue;
					}
					list.Add(null);
				}
				foreach (object item2 in list)
				{
					try
					{
						if (InvokePostfix(item, item2))
						{
							num++;
						}
					}
					catch (Exception ex)
					{
						Exception ex2 = ((ex is TargetInvocationException { InnerException: not null } ex3) ? ex3.InnerException : ex);
						((BaseUnityPlugin)this).Logger.LogWarning((object)$"Could not replay {gate.Info.Metadata.Name}'s setup on {item.Target.DeclaringType.Name}.{item.Target.Name}: {ex2}");
					}
				}
			}
			return num;
		}

		private static bool NeedsInstance(SavedPatch saved)
		{
			return saved.Patch.PatchMethod.GetParameters().Any((ParameterInfo p) => p.Name == "__instance" || p.Name.StartsWith("___"));
		}

		private bool InvokePostfix(SavedPatch saved, object instance)
		{
			MethodInfo patchMethod = saved.Patch.PatchMethod;
			ParameterInfo[] parameters = patchMethod.GetParameters();
			object[] array = new object[parameters.Length];
			Dictionary<int, FieldInfo> dictionary = new Dictionary<int, FieldInfo>();
			for (int i = 0; i < parameters.Length; i++)
			{
				ParameterInfo parameterInfo = parameters[i];
				Type type = (parameterInfo.ParameterType.IsByRef ? parameterInfo.ParameterType.GetElementType() : parameterInfo.ParameterType);
				if (parameterInfo.Name == "__instance")
				{
					array[i] = instance;
					continue;
				}
				if (parameterInfo.Name == "__originalMethod")
				{
					array[i] = saved.Target;
					continue;
				}
				if (parameterInfo.Name == "__runOriginal")
				{
					array[i] = true;
					continue;
				}
				if (parameterInfo.Name == "__args")
				{
					array[i] = new object[0];
					continue;
				}
				if (parameterInfo.Name == "__state")
				{
					array[i] = (type.IsValueType ? Activator.CreateInstance(type) : null);
					continue;
				}
				if (parameterInfo.Name.StartsWith("___"))
				{
					FieldInfo fieldInfo = AccessTools.Field(saved.Target.DeclaringType, parameterInfo.Name.Substring(3));
					if (fieldInfo == null)
					{
						return Skip(saved, "unknown field " + parameterInfo.Name);
					}
					array[i] = fieldInfo.GetValue(instance);
					if (parameterInfo.ParameterType.IsByRef)
					{
						dictionary[i] = fieldInfo;
					}
					continue;
				}
				return Skip(saved, "unsupported argument " + parameterInfo.Name);
			}
			patchMethod.Invoke(null, array);
			foreach (KeyValuePair<int, FieldInfo> item in dictionary)
			{
				item.Value.SetValue(instance, array[item.Key]);
			}
			return true;
		}

		private bool Skip(SavedPatch saved, string reason)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Skipped replaying " + saved.Patch.PatchMethod.DeclaringType?.Name + "." + saved.Patch.PatchMethod.Name + " (" + reason + ")."));
			return false;
		}

		private static string CleanKey(string name)
		{
			char[] array = new char[8] { '=', '\n', '\t', '\\', '"', '\'', '[', ']' };
			foreach (char oldChar in array)
			{
				name = name.Replace(oldChar, '_');
			}
			return name.Trim();
		}
	}
}