Decompiled source of BenowyQol v0.3.1

BepInEx/plugins/BenowyQol/BenowyQol.dll

Decompiled 2 months ago
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace BenowyQol;

internal static class ClockTime
{
	public static string Format(float dayFraction)
	{
		if (float.IsNaN(dayFraction) || float.IsInfinity(dayFraction))
		{
			return "--:--";
		}
		double num = (double)dayFraction - Math.Floor(dayFraction);
		int num2 = (int)Math.Floor(num * 1440.0);
		return (num2 / 60).ToString("00", CultureInfo.InvariantCulture) + ":" + (num2 % 60).ToString("00", CultureInfo.InvariantCulture);
	}
}
internal static class FoodFactorPolicy
{
	public static float Apply(float nativeFactor, bool active)
	{
		if (!active || !(nativeFactor > 0f))
		{
			return nativeFactor;
		}
		return 1f;
	}
}
internal abstract class QolFeature : IDisposable
{
	private readonly ConfigEntry<bool> setting;

	private readonly ToggleState state;

	public string Title { get; private set; }

	public string Description { get; private set; }

	public string Status { get; protected set; }

	public bool Available { get; private set; }

	public bool Running { get; private set; }

	public bool Enabled => state.Enabled;

	protected QolFeature(ConfigFile config, string key, string title, string description)
	{
		Title = title;
		Description = description;
		setting = config.Bind<bool>("Features", key, true, description);
		state = new ToggleState(setting.Value, OnToggle);
		Available = true;
		Running = true;
		Status = "";
	}

	public void SetEnabled(bool value)
	{
		if (Available && state.Set(value))
		{
			setting.Value = value;
		}
	}

	public virtual void Initialize()
	{
	}

	public void Tick()
	{
		if (Available && Running && Enabled)
		{
			OnTick();
		}
	}

	protected virtual void OnTick()
	{
	}

	protected virtual void OnToggle(bool next)
	{
	}

	public void Fail(Exception error)
	{
		Available = false;
		Status = "Unavailable: " + error.Message;
		Dispose();
	}

	public void Dispose()
	{
		if (Running)
		{
			Running = false;
			OnStop();
		}
	}

	protected virtual void OnStop()
	{
	}
}
internal sealed class FullFoodFeature : QolFeature
{
	private const string Id = "benowy.qol.food";

	private static FullFoodFeature instance;

	private Harmony harmony;

	public FullFoodFeature(ConfigFile config)
		: base(config, "FullDurationFood", "Full-duration food", "Personal: full food health, stamina and Eitr benefits until normal expiry. Changes apply on the next normal food tick; timers and regeneration stay normal.")
	{
	}

	public override void Initialize()
	{
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0016: Expected O, but got Unknown
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0087: Expected O, but got Unknown
		instance = this;
		harmony = new Harmony("benowy.qol.food");
		MethodInfo methodInfo = AccessTools.Method(typeof(Player), "UpdateFood", new Type[2]
		{
			typeof(float),
			typeof(bool)
		}, (Type[])null);
		if (methodInfo == null)
		{
			throw new MissingMethodException("Player.UpdateFood changed.");
		}
		harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(FullFoodFeature), "RemoveDecay", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
	}

	private static float AdjustFactor(float nativeFactor, Player player)
	{
		bool active = instance != null && instance.Running && instance.Available && instance.Enabled && Object.op_Implicit((Object)(object)player) && (Object)(object)player == (Object)(object)Player.m_localPlayer;
		return FoodFactorPolicy.Apply(nativeFactor, active);
	}

	private static IEnumerable<CodeInstruction> RemoveDecay(IEnumerable<CodeInstruction> instructions)
	{
		//IL_0051: Unknown result type (might be due to invalid IL or missing references)
		//IL_005b: Expected O, but got Unknown
		//IL_007a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0084: Expected O, but got Unknown
		List<CodeInstruction> list = new List<CodeInstruction>(instructions);
		int num = -1;
		for (int i = 11; i + 25 < list.Count; i++)
		{
			if (MatchesDecay(list, i))
			{
				if (num >= 0)
				{
					throw new InvalidOperationException("Multiple food-decay sites; refusing an ambiguous patch.");
				}
				num = i;
			}
		}
		if (num < 0)
		{
			throw new InvalidOperationException("Expected food-decay calculation not found; game or another food mod changed it.");
		}
		list.Insert(num + 1, new CodeInstruction(OpCodes.Ldarg_0, (object)null));
		list.Insert(num + 2, new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(FullFoodFeature), "AdjustFactor", (Type[])null, (Type[])null)));
		return list;
	}

	private static bool MatchesDecay(List<CodeInstruction> code, int i)
	{
		if (!Calls(code[i], typeof(Mathf), "Pow") || code[i - 1].opcode != OpCodes.Ldc_R4 || !(code[i - 1].operand is float) || (float)code[i - 1].operand != 0.3f || !Calls(code[i - 4], typeof(Mathf), "Clamp01") || code[i - 5].opcode != OpCodes.Div || !Field(code[i - 6], OpCodes.Ldfld, typeof(SharedData), "m_foodBurnTime") || !Field(code[i - 10], OpCodes.Ldfld, typeof(Food), "m_time"))
		{
			return false;
		}
		int num = Local(code[i + 1], store: true);
		if (num < 0 || Local(code[i - 3], store: true) != num || Local(code[i - 2], store: false) != num)
		{
			return false;
		}
		string[] array = new string[3] { "m_food", "m_foodStamina", "m_foodEitr" };
		string[] array2 = new string[3] { "m_health", "m_stamina", "m_eitr" };
		for (int j = 0; j < 3; j++)
		{
			int num2 = i + j * 8;
			if (!Field(code[num2 + 6], OpCodes.Ldfld, typeof(SharedData), array[j]) || Local(code[num2 + 7], store: false) != num || code[num2 + 8].opcode != OpCodes.Mul || !Field(code[num2 + 9], OpCodes.Stfld, typeof(Food), array2[j]))
			{
				return false;
			}
		}
		return true;
	}

	private static bool Calls(CodeInstruction instruction, Type type, string name)
	{
		MethodInfo methodInfo = instruction.operand as MethodInfo;
		if (instruction.opcode == OpCodes.Call && methodInfo != null && methodInfo.DeclaringType == type)
		{
			return methodInfo.Name == name;
		}
		return false;
	}

	private static bool Field(CodeInstruction instruction, OpCode opcode, Type type, string name)
	{
		FieldInfo fieldInfo = instruction.operand as FieldInfo;
		if (instruction.opcode == opcode && fieldInfo != null && fieldInfo.DeclaringType == type)
		{
			return fieldInfo.Name == name;
		}
		return false;
	}

	private static int Local(CodeInstruction instruction, bool store)
	{
		OpCode opcode = instruction.opcode;
		if (opcode == (store ? OpCodes.Stloc_0 : OpCodes.Ldloc_0))
		{
			return 0;
		}
		if (opcode == (store ? OpCodes.Stloc_1 : OpCodes.Ldloc_1))
		{
			return 1;
		}
		if (opcode == (store ? OpCodes.Stloc_2 : OpCodes.Ldloc_2))
		{
			return 2;
		}
		if (opcode == (store ? OpCodes.Stloc_3 : OpCodes.Ldloc_3))
		{
			return 3;
		}
		if (opcode != (store ? OpCodes.Stloc : OpCodes.Ldloc) && opcode != (store ? OpCodes.Stloc_S : OpCodes.Ldloc_S))
		{
			return -1;
		}
		if (!(instruction.operand is LocalBuilder localBuilder))
		{
			return Convert.ToInt32(instruction.operand);
		}
		return localBuilder.LocalIndex;
	}

	protected override void OnStop()
	{
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
		if (instance == this)
		{
			instance = null;
		}
	}
}
internal sealed class InputGuard : IDisposable
{
	private Harmony harmony;

	private static InputGuard current;

	private readonly MenuInputState state = new MenuInputState();

	private KeyCode releaseKey;

	private static bool readingMenuInput;

	public bool Blocking => state.Blocking;

	private static bool Block
	{
		get
		{
			if (current != null && current.Blocking)
			{
				return !readingMenuInput;
			}
			return false;
		}
	}

	public void Install()
	{
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0016: Expected O, but got Unknown
		current = this;
		harmony = new Harmony("benowy.qol.menu");
		Patch(typeof(Player), "TakeInput", Type.EmptyTypes, "BlockBoolean");
		Patch(typeof(PlayerController), "TakeInput", new Type[1] { typeof(bool) }, "BlockBoolean");
		Patch(typeof(GameCamera), "UpdateMouseCapture", Type.EmptyTypes, "CaptureCursor");
		string[] array = new string[3] { "GetButton", "GetButtonDown", "GetButtonUp" };
		foreach (string method in array)
		{
			Patch(typeof(ZInput), method, new Type[1] { typeof(string) }, "BlockBoolean");
		}
		string[] array2 = new string[3] { "GetMouseButton", "GetMouseButtonDown", "GetMouseButtonUp" };
		foreach (string method2 in array2)
		{
			Patch(typeof(ZInput), method2, new Type[1] { typeof(int) }, "BlockBoolean");
		}
		string[] array3 = new string[3] { "GetKey", "GetKeyDown", "GetKeyUp" };
		foreach (string method3 in array3)
		{
			Patch(typeof(ZInput), method3, new Type[2]
			{
				typeof(KeyCode),
				typeof(bool)
			}, "BlockBoolean");
		}
		string[] array4 = new string[4] { "GetJoyLeftStickX", "GetJoyLeftStickY", "GetJoyRightStickX", "GetJoyRightStickY" };
		foreach (string method4 in array4)
		{
			Patch(typeof(ZInput), method4, new Type[1] { typeof(bool) }, "BlockFloat");
		}
		string[] array5 = new string[3] { "GetMouseScrollWheel", "GetJoyLTrigger", "GetJoyRTrigger" };
		foreach (string method5 in array5)
		{
			Patch(typeof(ZInput), method5, Type.EmptyTypes, "BlockFloat");
		}
		Patch(typeof(ZInput), "GetMouseDelta", Type.EmptyTypes, "BlockVector");
	}

	private void Patch(Type type, string method, Type[] args, string prefix)
	{
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Expected O, but got Unknown
		MethodInfo methodInfo = AccessTools.Method(type, method, args, (Type[])null);
		if (methodInfo == null)
		{
			throw new MissingMethodException(type.Name, method);
		}
		harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(InputGuard), prefix, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
	}

	public bool KeyDown(KeyCode key)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		readingMenuInput = true;
		try
		{
			return ZInput.GetKeyDown(key, true);
		}
		finally
		{
			readingMenuInput = false;
		}
	}

	public void SetOpen(bool value, KeyCode key)
	{
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		state.SetOpen(value, Time.unscaledTime);
		if (value)
		{
			Unlock();
			return;
		}
		releaseKey = key;
		PlayerController.SetTakeInputDelay(0.2f);
		ZInput.IgnoreMouseInputForFrames(2);
	}

	public void Update()
	{
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		if (state.Open)
		{
			Unlock();
		}
		else if (state.Blocking)
		{
			readingMenuInput = true;
			try
			{
				bool closingInputHeld = ZInput.GetKey(releaseKey, true) || ZInput.GetKey((KeyCode)27, true) || ZInput.GetMouseButton(0) || ZInput.GetMouseButton(1) || ZInput.GetMouseButton(2) || ZInput.GetButton("Attack") || ZInput.GetButton("SecondaryAttack") || ZInput.GetJoyLTrigger() > 0.2f || ZInput.GetJoyRTrigger() > 0.2f;
				state.Update(Time.unscaledTime, closingInputHeld);
			}
			finally
			{
				readingMenuInput = false;
			}
		}
	}

	private static void Unlock()
	{
		ZCursor.LockState = (CursorLockMode)0;
		ZCursor.Show();
	}

	private static bool CaptureCursor()
	{
		if (!Block)
		{
			return true;
		}
		Unlock();
		return false;
	}

	private static bool BlockBoolean(ref bool __result)
	{
		if (!Block)
		{
			return true;
		}
		__result = false;
		return false;
	}

	private static bool BlockFloat(ref float __result)
	{
		if (!Block)
		{
			return true;
		}
		__result = 0f;
		return false;
	}

	private static bool BlockVector(ref Vector2 __result)
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		if (!Block)
		{
			return true;
		}
		__result = Vector2.zero;
		return false;
	}

	public void Dispose()
	{
		state.Reset();
		if (harmony != null)
		{
			harmony.UnpatchSelf();
			harmony = null;
		}
		if (current == this)
		{
			current = null;
		}
	}
}
internal sealed class MenuInputState
{
	private bool pending;

	private float releaseAfter;

	public bool Open { get; private set; }

	public bool Blocking
	{
		get
		{
			if (!Open)
			{
				return pending;
			}
			return true;
		}
	}

	public void SetOpen(bool value, float now)
	{
		Open = value;
		pending = !value;
		releaseAfter = now + 0.2f;
	}

	public void Update(float now, bool closingInputHeld)
	{
		if (!Open && pending && now >= releaseAfter && !closingInputHeld)
		{
			pending = false;
		}
	}

	public void Reset()
	{
		Open = false;
		pending = false;
	}
}
internal sealed class MinimapClockFeature : QolFeature
{
	private TextMeshProUGUI label;

	private Minimap owner;

	public MinimapClockFeature(ConfigFile config)
		: base(config, "MinimapClock", "24-hour minimap clock", "Show Valheim's in-game time above the minimap (HH:mm).")
	{
	}

	protected override void OnTick()
	{
		//IL_00df: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e5: Expected O, but got Unknown
		//IL_015d: Unknown result type (might be due to invalid IL or missing references)
		//IL_01bf: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c5: 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)
		//IL_01de: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
		//IL_0208: Unknown result type (might be due to invalid IL or missing references)
		Minimap instance = Minimap.instance;
		if ((Object)(object)owner != (Object)(object)instance)
		{
			Clear();
		}
		if (!Object.op_Implicit((Object)(object)instance) || !Object.op_Implicit((Object)(object)instance.m_smallRoot) || !Object.op_Implicit((Object)(object)instance.m_mapImageSmall) || !Object.op_Implicit((Object)(object)instance.m_biomeNameSmall))
		{
			return;
		}
		if (!Object.op_Implicit((Object)(object)Player.m_localPlayer) || !Object.op_Implicit((Object)(object)EnvMan.instance) || !Object.op_Implicit((Object)(object)Hud.instance) || !Hud.instance.IsVisible() || !instance.m_smallRoot.activeInHierarchy)
		{
			if (Object.op_Implicit((Object)(object)label))
			{
				((Component)label).gameObject.SetActive(false);
			}
			return;
		}
		if (!Object.op_Implicit((Object)(object)label))
		{
			owner = instance;
			GameObject val = new GameObject("BenowyQolClock", new Type[1] { typeof(RectTransform) });
			val.SetActive(false);
			val.transform.SetParent(((Component)instance.m_mapImageSmall).transform, false);
			label = val.AddComponent<TextMeshProUGUI>();
			((TMP_Text)label).font = instance.m_biomeNameSmall.font;
			((TMP_Text)label).fontSharedMaterial = instance.m_biomeNameSmall.fontSharedMaterial;
			((TMP_Text)label).fontSize = 20f;
			((TMP_Text)label).fontStyle = (FontStyles)1;
			((Graphic)label).color = Color.white;
			((TMP_Text)label).alignment = (TextAlignmentOptions)514;
			((Graphic)label).raycastTarget = false;
			((MaskableGraphic)label).maskable = false;
			((TMP_Text)label).textWrappingMode = (TextWrappingModes)0;
			((TMP_Text)label).overflowMode = (TextOverflowModes)0;
			RectTransform rectTransform = ((TMP_Text)label).rectTransform;
			Vector2 anchorMin = (rectTransform.anchorMax = new Vector2(0.5f, 1f));
			rectTransform.anchorMin = anchorMin;
			rectTransform.pivot = new Vector2(0.5f, 0f);
			rectTransform.sizeDelta = new Vector2(120f, 26f);
			rectTransform.anchoredPosition = new Vector2(0f, 4f);
		}
		bool flag = !((Component)label).gameObject.activeSelf;
		((Component)label).gameObject.SetActive(true);
		((TMP_Text)label).text = ClockTime.Format(EnvMan.instance.GetDayFraction());
		if (flag)
		{
			label.canvasRenderer.cull = false;
			((Graphic)label).SetAllDirty();
			((TMP_Text)label).ForceMeshUpdate(false, false);
		}
	}

	protected override void OnToggle(bool next)
	{
		if (next)
		{
			OnTick();
		}
		else if (Object.op_Implicit((Object)(object)label))
		{
			((Component)label).gameObject.SetActive(false);
		}
	}

	protected override void OnStop()
	{
		Clear();
	}

	private void Clear()
	{
		if (Object.op_Implicit((Object)(object)label))
		{
			((Component)label).gameObject.SetActive(false);
			Object.Destroy((Object)(object)((Component)label).gameObject);
		}
		label = null;
		owner = null;
	}
}
internal sealed class OrderedSummonsFeature : QolFeature
{
	private sealed class Cursor
	{
		public ConfigEntry<int> Saved;

		public SummonCycleState State;
	}

	private sealed class Ticket
	{
		public long Caster;

		public int SelectedIndex;

		public GameObject Created;
	}

	private static OrderedSummonsFeature instance;

	private static readonly FieldInfo Owner = AccessTools.Field(typeof(SpawnAbility), "m_owner");

	private static readonly FieldInfo Weapon = AccessTools.Field(typeof(SpawnAbility), "m_weapon");

	private readonly ConfigFile config;

	private readonly Action<string> warn;

	private Harmony harmony;

	private readonly Dictionary<long, Cursor> cursors = new Dictionary<long, Cursor>();

	private readonly Dictionary<SpawnAbility, Ticket> tickets = new Dictionary<SpawnAbility, Ticket>();

	private readonly List<SpawnAbility> expired = new List<SpawnAbility>();

	public OrderedSummonsFeature(ConfigFile config, Action<string> warn)
		: base(config, "OrderedSpiritSummons", "Ordered Spirit Caller summons", "Personal cast order: boar > wolf > bear > moose. Advances after a successful spawn; native caps/costs stay normal. Saves per character. In-flight casts finish their selected mode.")
	{
		this.config = config;
		this.warn = warn;
	}

	public override void Initialize()
	{
		//IL_0061: Unknown result type (might be due to invalid IL or missing references)
		//IL_006b: Expected O, but got Unknown
		//IL_0084: Unknown result type (might be due to invalid IL or missing references)
		//IL_0090: Expected O, but got Unknown
		instance = this;
		if (Owner == null || Weapon == null)
		{
			throw new MissingFieldException("SpawnAbility caster/item fields changed.");
		}
		MethodInfo methodInfo = AccessTools.EnumeratorMoveNext((MethodBase)AccessTools.Method(typeof(SpawnAbility), "Spawn", (Type[])null, (Type[])null));
		if (methodInfo == null)
		{
			throw new MissingMethodException("Spawn coroutine changed.");
		}
		harmony = new Harmony("benowy.qol.orderedspirits");
		harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(OrderedSummonsFeature), "PatchSpawn", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
	}

	private Cursor GetCursor(long id)
	{
		if (!cursors.TryGetValue(id, out var value))
		{
			ConfigEntry<int> val = config.Bind<int>("SpiritCycle", "Character_" + id.ToString(CultureInfo.InvariantCulture), 0, "Next ordered spirit for this character: 0 boar, 1 wolf, 2 bear, 3 moose. Saved after successful ordered spawns; independent of world and army composition.");
			Cursor cursor = new Cursor();
			cursor.Saved = val;
			cursor.State = new SummonCycleState(val.Value);
			value = cursor;
			cursors.Add(id, value);
		}
		return value;
	}

	private static bool Caster(SpawnAbility ability, out Player player)
	{
		player = null;
		if (!Object.op_Implicit((Object)(object)ability) || PrefabName(((Component)ability).gameObject) != "staff_SpiritCaller_spawn" || ability.m_minToSpawn != 1 || ability.m_maxToSpawn != 1)
		{
			return false;
		}
		object? value = Owner.GetValue(ability);
		player = (Player)((value is Player) ? value : null);
		object? value2 = Weapon.GetValue(ability);
		ItemData val = (ItemData)((value2 is ItemData) ? value2 : null);
		if (Object.op_Implicit((Object)(object)player) && (Object)(object)player == (Object)(object)Player.m_localPlayer && player.GetPlayerID() != 0 && val != null && Object.op_Implicit((Object)(object)val.m_dropPrefab))
		{
			return ((Object)val.m_dropPrefab).name == "StaffSpiritCaller";
		}
		return false;
	}

	private static GameObject[] Prefabs(SpawnAbility ability)
	{
		if (ability.m_spawnPrefab == null || ability.m_spawnPrefab.Length != 4)
		{
			return null;
		}
		GameObject[] array = (GameObject[])(object)new GameObject[4];
		GameObject[] spawnPrefab = ability.m_spawnPrefab;
		foreach (GameObject val in spawnPrefab)
		{
			if (!Object.op_Implicit((Object)(object)val))
			{
				return null;
			}
			int num = Array.IndexOf(SummonCycleState.PrefabOrder, ((Object)val).name);
			if (num < 0 || Object.op_Implicit((Object)(object)array[num]))
			{
				return null;
			}
			array[num] = val;
		}
		return array;
	}

	private static string PrefabName(GameObject obj)
	{
		string name = ((Object)obj).name;
		if (!name.EndsWith("(Clone)", StringComparison.Ordinal))
		{
			return name;
		}
		return name.Substring(0, name.Length - 7);
	}

	private static GameObject Select(GameObject nativeRandom, SpawnAbility ability)
	{
		OrderedSummonsFeature orderedSummonsFeature = instance;
		if (orderedSummonsFeature == null || !orderedSummonsFeature.Running || !orderedSummonsFeature.Available)
		{
			return nativeRandom;
		}
		orderedSummonsFeature.Prune();
		orderedSummonsFeature.tickets.Remove(ability);
		if (!orderedSummonsFeature.Enabled)
		{
			return nativeRandom;
		}
		try
		{
			if (!Caster(ability, out var player))
			{
				return nativeRandom;
			}
			GameObject[] array = Prefabs(ability);
			if (array == null)
			{
				return nativeRandom;
			}
			long playerID = player.GetPlayerID();
			int nextIndex = orderedSummonsFeature.GetCursor(playerID).State.NextIndex;
			orderedSummonsFeature.tickets[ability] = new Ticket
			{
				Caster = playerID,
				SelectedIndex = nextIndex
			};
			return array[nextIndex];
		}
		catch (Exception ex)
		{
			orderedSummonsFeature.warn("Ordered selection skipped: " + ex.Message);
			return nativeRandom;
		}
	}

	private static GameObject Create(GameObject selected, Vector3 position, Quaternion rotation, SpawnAbility ability, Vector3 target)
	{
		//IL_010a: Unknown result type (might be due to invalid IL or missing references)
		//IL_010b: Unknown result type (might be due to invalid IL or missing references)
		//IL_009e: Unknown result type (might be due to invalid IL or missing references)
		OrderedSummonsFeature orderedSummonsFeature = instance;
		Ticket value = null;
		GameObject val = selected;
		if (orderedSummonsFeature != null && orderedSummonsFeature.Running && orderedSummonsFeature.Available && orderedSummonsFeature.tickets.TryGetValue(ability, out value))
		{
			try
			{
				GameObject[] array = Prefabs(ability);
				if (!Caster(ability, out var player) || player.GetPlayerID() != value.Caster || array == null)
				{
					orderedSummonsFeature.tickets.Remove(ability);
					value = null;
				}
				else
				{
					value.SelectedIndex = orderedSummonsFeature.GetCursor(value.Caster).State.NextIndex;
					val = array[value.SelectedIndex];
					if (ability.m_maxSpawned > 0 && SpawnSystem.GetNrOfInstances(val, target, 0f, false, false) >= ability.m_maxSpawned)
					{
						orderedSummonsFeature.tickets.Remove(ability);
						((Character)player).Message((MessageType)2, ability.m_maxSummonReached, 0, (Sprite)null, false);
						return null;
					}
				}
			}
			catch (Exception ex)
			{
				orderedSummonsFeature.tickets.Remove(ability);
				value = null;
				val = selected;
				orderedSummonsFeature.warn("Ordered spawn preparation skipped: " + ex.Message);
			}
		}
		GameObject val2 = Object.Instantiate<GameObject>(val, position, rotation);
		if (value != null)
		{
			value.Created = val2;
		}
		return val2;
	}

	private static void Commit(GameObject created, SpawnAbility ability)
	{
		OrderedSummonsFeature orderedSummonsFeature = instance;
		if (orderedSummonsFeature == null || !orderedSummonsFeature.tickets.TryGetValue(ability, out var value))
		{
			return;
		}
		orderedSummonsFeature.tickets.Remove(ability);
		try
		{
			if (!Object.op_Implicit((Object)(object)created) || (Object)(object)created != (Object)(object)value.Created || !Object.op_Implicit((Object)(object)created.GetComponent<Character>()) || PrefabName(created) != SummonCycleState.PrefabOrder[value.SelectedIndex])
			{
				return;
			}
			ZNetView component = created.GetComponent<ZNetView>();
			if (!Object.op_Implicit((Object)(object)component) || !component.IsValid())
			{
				return;
			}
			Cursor cursor = orderedSummonsFeature.GetCursor(value.Caster);
			if (!cursor.State.Commit(value.SelectedIndex))
			{
				orderedSummonsFeature.warn("Ordered cycle changed unexpectedly during spawn; cursor was not advanced.");
				return;
			}
			cursor.Saved.Value = cursor.State.NextIndex;
			try
			{
				orderedSummonsFeature.config.Save();
			}
			catch (Exception ex)
			{
				orderedSummonsFeature.warn("Cycle advanced for this session, but could not save: " + ex.Message);
			}
		}
		catch (Exception ex2)
		{
			orderedSummonsFeature.warn("Could not record successful ordered spawn: " + ex2.Message);
		}
	}

	protected override void OnTick()
	{
		Prune();
	}

	private void Prune()
	{
		expired.Clear();
		foreach (KeyValuePair<SpawnAbility, Ticket> ticket in tickets)
		{
			if (!Object.op_Implicit((Object)(object)ticket.Key))
			{
				expired.Add(ticket.Key);
			}
		}
		foreach (SpawnAbility item in expired)
		{
			tickets.Remove(item);
		}
	}

	protected override void OnStop()
	{
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
		tickets.Clear();
		if (instance == this)
		{
			instance = null;
		}
	}

	private static IEnumerable<CodeInstruction> PatchSpawn(IEnumerable<CodeInstruction> instructions, MethodBase original, ILGenerator generator)
	{
		//IL_03d9: Unknown result type (might be due to invalid IL or missing references)
		//IL_03e3: Expected O, but got Unknown
		//IL_03eb: Unknown result type (might be due to invalid IL or missing references)
		//IL_03f5: Expected O, but got Unknown
		//IL_03fd: Unknown result type (might be due to invalid IL or missing references)
		//IL_0407: Expected O, but got Unknown
		//IL_040f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0419: Expected O, but got Unknown
		//IL_0458: Unknown result type (might be due to invalid IL or missing references)
		//IL_0462: Expected O, but got Unknown
		//IL_046a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0474: Expected O, but got Unknown
		//IL_0491: Unknown result type (might be due to invalid IL or missing references)
		//IL_049b: Expected O, but got Unknown
		//IL_04a9: Unknown result type (might be due to invalid IL or missing references)
		//IL_04b3: Expected O, but got Unknown
		//IL_04c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_04cb: Expected O, but got Unknown
		//IL_04d3: Unknown result type (might be due to invalid IL or missing references)
		//IL_04dd: Expected O, but got Unknown
		//IL_04e6: Unknown result type (might be due to invalid IL or missing references)
		//IL_04f0: Expected O, but got Unknown
		//IL_04fe: Unknown result type (might be due to invalid IL or missing references)
		//IL_0508: Expected O, but got Unknown
		//IL_0510: Unknown result type (might be due to invalid IL or missing references)
		//IL_051a: Expected O, but got Unknown
		//IL_0522: Unknown result type (might be due to invalid IL or missing references)
		//IL_052c: Expected O, but got Unknown
		//IL_0549: Unknown result type (might be due to invalid IL or missing references)
		//IL_0553: Expected O, but got Unknown
		List<CodeInstruction> list = new List<CodeInstruction>(instructions);
		FieldInfo fieldInfo = AccessTools.Field(original.DeclaringType, "<>4__this");
		FieldInfo fieldInfo2 = AccessTools.Field(original.DeclaringType, "<targetPosition>5__5");
		int num = -1;
		int num2 = -1;
		int num3 = -1;
		object obj = null;
		int num4 = 0;
		int num5 = 0;
		int num6 = 0;
		int num7 = 0;
		for (int i = 1; i + 1 < list.Count; i++)
		{
			MethodInfo methodInfo = list[i].operand as MethodInfo;
			FieldInfo fieldInfo3 = list[i].operand as FieldInfo;
			if (list[i].opcode == OpCodes.Ldelem_Ref && CodeInstructionExtensions.Calls(list[i - 1], AccessTools.Method(typeof(Random), "Range", new Type[2]
			{
				typeof(int),
				typeof(int)
			}, (Type[])null)) && list[i + 1].opcode == OpCodes.Stfld && ((FieldInfo)list[i + 1].operand).Name == "<prefab>5__9")
			{
				num = i;
				num4++;
			}
			if (list[i].opcode == OpCodes.Call && methodInfo != null && methodInfo.DeclaringType == typeof(Object) && methodInfo.Name == "Instantiate" && methodInfo.IsGenericMethod && methodInfo.GetGenericArguments()[0] == typeof(GameObject) && methodInfo.GetParameters().Length == 3)
			{
				num2 = i;
				num5++;
			}
			if (fieldInfo3 != null && fieldInfo3.DeclaringType == typeof(SpawnAbility) && fieldInfo3.Name == "m_preSpawnEffects" && i >= 2 && (list[i - 2].opcode == OpCodes.Br || list[i - 2].opcode == OpCodes.Br_S))
			{
				obj = list[i - 2].operand;
				num7++;
			}
			if (fieldInfo3 != null && fieldInfo3.DeclaringType == typeof(SpawnAbility) && fieldInfo3.Name == "m_spawnDelay" && i >= 3 && list[i - 2].opcode == OpCodes.Pop && list[i - 3].operand is MethodInfo && ((MethodInfo)list[i - 3].operand).DeclaringType == typeof(EffectList) && ((MethodInfo)list[i - 3].operand).Name == "Create")
			{
				num3 = i - 2;
				num6++;
			}
		}
		if (fieldInfo == null || fieldInfo2 == null || num4 != 1 || num5 != 1 || num6 != 1 || num7 != 1 || num >= num2 || num2 >= num3 || !(obj is Label) || list[num2 + 1].opcode != OpCodes.Stloc_2 || list[num2].labels.Count != 0 || list[num2].blocks.Count != 0)
		{
			throw new InvalidOperationException("Spirit spawn structure changed; ordered feature not patched.");
		}
		Label label = generator.DefineLabel();
		list[num2 + 1].labels.Add(label);
		List<CodeInstruction> list2 = new List<CodeInstruction>();
		for (int j = 0; j < list.Count; j++)
		{
			if (j == num2)
			{
				list2.Add(new CodeInstruction(OpCodes.Ldarg_0, (object)null));
				list2.Add(new CodeInstruction(OpCodes.Ldfld, (object)fieldInfo));
				list2.Add(new CodeInstruction(OpCodes.Ldarg_0, (object)null));
				list2.Add(new CodeInstruction(OpCodes.Ldfld, (object)fieldInfo2));
				list[j].operand = AccessTools.Method(typeof(OrderedSummonsFeature), "Create", (Type[])null, (Type[])null);
			}
			list2.Add(list[j]);
			if (j == num)
			{
				list2.Add(new CodeInstruction(OpCodes.Ldarg_0, (object)null));
				list2.Add(new CodeInstruction(OpCodes.Ldfld, (object)fieldInfo));
				list2.Add(new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(OrderedSummonsFeature), "Select", (Type[])null, (Type[])null)));
			}
			if (j == num2)
			{
				list2.Add(new CodeInstruction(OpCodes.Dup, (object)null));
				list2.Add(new CodeInstruction(OpCodes.Brtrue, (object)label));
				list2.Add(new CodeInstruction(OpCodes.Pop, (object)null));
				list2.Add(new CodeInstruction(OpCodes.Br, obj));
			}
			if (j == num3)
			{
				list2.Add(new CodeInstruction(OpCodes.Ldloc_2, (object)null));
				list2.Add(new CodeInstruction(OpCodes.Ldarg_0, (object)null));
				list2.Add(new CodeInstruction(OpCodes.Ldfld, (object)fieldInfo));
				list2.Add(new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(OrderedSummonsFeature), "Commit", (Type[])null, (Type[])null)));
			}
		}
		return list2;
	}
}
[BepInPlugin("benowy.qol", "BenowyQol", "0.3.1")]
[BepInIncompatibility("local.spiritcaller.passthrough")]
[BepInIncompatibility("local.food.fullduration")]
[DefaultExecutionOrder(-10000)]
public sealed class Plugin : BaseUnityPlugin
{
	public const string Id = "benowy.qol";

	private readonly List<QolFeature> features = new List<QolFeature>();

	private ConfigEntry<KeyCode> menuKey;

	private SettingsMenu menu;

	private InputGuard input;

	private bool ready;

	private void Awake()
	{
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		//IL_004b: Invalid comparison between Unknown and I4
		((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
		menuKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Menu", "Hotkey", (KeyCode)289, "Keyboard key for the BenowyQol menu. Also changeable in-game. Default F8.");
		if ((int)menuKey.Value == 0 || (int)menuKey.Value == 27)
		{
			menuKey.Value = (KeyCode)289;
		}
		features.Add(new SpiritCollisionFeature(((BaseUnityPlugin)this).Config));
		features.Add(new FullFoodFeature(((BaseUnityPlugin)this).Config));
		features.Add(new MinimapClockFeature(((BaseUnityPlugin)this).Config));
		features.Add(new OrderedSummonsFeature(((BaseUnityPlugin)this).Config, delegate(string message)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)message);
		}));
		foreach (QolFeature feature in features)
		{
			try
			{
				feature.Initialize();
			}
			catch (Exception ex)
			{
				feature.Fail(ex);
				((BaseUnityPlugin)this).Logger.LogError((object)(feature.Title + ": " + ex));
			}
		}
		try
		{
			input = new InputGuard();
			input.Install();
			menu = new SettingsMenu(features, menuKey, SaveSettings, input);
		}
		catch (Exception ex2)
		{
			if (input != null)
			{
				input.Dispose();
			}
			((BaseUnityPlugin)this).Logger.LogError((object)("Menu input protection could not initialize; menu disabled. Features remain configurable in benowy.qol.cfg. " + ex2));
		}
		ready = true;
		SaveSettings();
		((BaseUnityPlugin)this).Logger.LogInfo((object)"BenowyQol ready. F8 by default opens settings in-game. Collision requires matching settings on every client and dedicated server; food is personal.");
	}

	private string SaveSettings()
	{
		try
		{
			((BaseUnityPlugin)this).Config.Save();
			return "Saved. Food changes apply on the next normal food tick.";
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Settings active but could not save: " + ex.Message));
			return "Active, but could not save settings: " + ex.Message;
		}
	}

	private void Update()
	{
		if (ready && menu != null)
		{
			menu.Update();
		}
	}

	private void FixedUpdate()
	{
		if (!ready)
		{
			return;
		}
		foreach (QolFeature feature in features)
		{
			try
			{
				feature.Tick();
			}
			catch (Exception ex)
			{
				feature.Fail(ex);
				((BaseUnityPlugin)this).Logger.LogError((object)(feature.Title + " stopped safely: " + ex));
			}
		}
	}

	private void OnGUI()
	{
		if (ready && menu != null)
		{
			menu.Draw();
		}
	}

	private void OnDisable()
	{
		ready = false;
		if (menu != null)
		{
			menu.Dispose();
		}
		if (input != null)
		{
			input.Dispose();
		}
		foreach (QolFeature feature in features)
		{
			feature.Dispose();
		}
	}

	private void OnDestroy()
	{
		OnDisable();
	}
}
internal sealed class SettingsMenu : IDisposable
{
	private readonly List<QolFeature> features;

	private readonly ConfigEntry<KeyCode> hotkey;

	private readonly Func<string> save;

	private readonly InputGuard input;

	private readonly UiShield shield = new UiShield();

	private bool open;

	private bool capturing;

	private Player menuPlayer;

	private string status = "Changes save automatically. The world keeps running.";

	private Rect window;

	private Vector2 scroll;

	private GUIStyle windowStyle;

	private GUIStyle labelStyle;

	private GUIStyle titleStyle;

	private GUIStyle buttonStyle;

	private Texture2D background;

	public SettingsMenu(List<QolFeature> features, ConfigEntry<KeyCode> hotkey, Func<string> save, InputGuard input)
	{
		this.features = features;
		this.hotkey = hotkey;
		this.save = save;
		this.input = input;
	}

	public void Update()
	{
		//IL_0087: Unknown result type (might be due to invalid IL or missing references)
		//IL_0166: Unknown result type (might be due to invalid IL or missing references)
		//IL_016b: Unknown result type (might be due to invalid IL or missing references)
		//IL_017d: Unknown result type (might be due to invalid IL or missing references)
		input.Update();
		shield.SetActive(input.Blocking);
		Player localPlayer = Player.m_localPlayer;
		if (open && (!Object.op_Implicit((Object)(object)localPlayer) || (Object)(object)localPlayer != (Object)(object)menuPlayer || ((Character)localPlayer).IsDead()))
		{
			Close();
		}
		else
		{
			if (capturing)
			{
				return;
			}
			if (open && input.KeyDown((KeyCode)27))
			{
				Close();
			}
			else if (input.KeyDown(hotkey.Value))
			{
				if (open)
				{
					Close();
				}
				else if (Object.op_Implicit((Object)(object)localPlayer) && !((Character)localPlayer).IsDead() && !Console.IsVisible() && !TextInput.IsVisible() && !Menu.IsVisible() && !InventoryGui.IsVisible() && !StoreGui.IsVisible() && !Minimap.IsOpen() && (!Object.op_Implicit((Object)(object)Chat.instance) || !Chat.instance.HasFocus()))
				{
					open = true;
					menuPlayer = localPlayer;
					window = new Rect((float)(Screen.width - Math.Min(640, Screen.width - 24)) / 2f, Math.Max(12f, (float)(Screen.height - 480) / 2f), (float)Math.Min(640, Screen.width - 24), (float)Math.Min(480, Screen.height - 24));
					input.SetOpen(value: true, hotkey.Value);
					shield.SetActive(value: true);
				}
			}
		}
	}

	private void Close()
	{
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		open = false;
		capturing = false;
		input.SetOpen(value: false, hotkey.Value);
	}

	public void Draw()
	{
		//IL_0088: Unknown result type (might be due to invalid IL or missing references)
		//IL_008d: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
		//IL_0192: Unknown result type (might be due to invalid IL or missing references)
		//IL_019e: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b3: Expected O, but got Unknown
		//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0024: Invalid comparison between Unknown and I4
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: Invalid comparison between Unknown and I4
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0060: Unknown result type (might be due to invalid IL or missing references)
		if (!open)
		{
			return;
		}
		EnsureStyles();
		Event current = Event.current;
		if (capturing && (int)current.type == 4 && (int)current.keyCode != 0)
		{
			if ((int)current.keyCode == 27)
			{
				capturing = false;
				status = "Key change cancelled.";
			}
			else if (ZInput.IsKeyCodeValid(current.keyCode))
			{
				hotkey.Value = current.keyCode;
				capturing = false;
				status = save();
			}
			current.Use();
		}
		Color color = GUI.color;
		GUI.color = new Color(0f, 0f, 0f, 0.5f);
		GUI.DrawTexture(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), (Texture)(object)Texture2D.whiteTexture);
		GUI.color = color;
		((Rect)(ref window)).width = Math.Min(640, Screen.width - 24);
		((Rect)(ref window)).height = Math.Min(480, Screen.height - 24);
		((Rect)(ref window)).x = Mathf.Clamp(((Rect)(ref window)).x, 0f, Math.Max(0f, (float)Screen.width - ((Rect)(ref window)).width));
		((Rect)(ref window)).y = Mathf.Clamp(((Rect)(ref window)).y, 0f, Math.Max(0f, (float)Screen.height - ((Rect)(ref window)).height));
		window = GUI.Window(194732, window, new WindowFunction(DrawWindow), "BenowyQol", windowStyle);
	}

	private void DrawWindow(int id)
	{
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//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_019d: Unknown result type (might be due to invalid IL or missing references)
		//IL_025b: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a4: Unknown result type (might be due to invalid IL or missing references)
		GUILayout.BeginArea(new Rect(18f, 36f, ((Rect)(ref window)).width - 36f, ((Rect)(ref window)).height - 50f));
		scroll = GUILayout.BeginScrollView(scroll, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Label("Choose what to enable", titleStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		foreach (QolFeature feature in features)
		{
			GUILayout.Space(12f);
			bool enabled = GUI.enabled;
			GUI.enabled = feature.Available;
			if (GUILayout.Button((feature.Enabled ? "ON   " : "OFF   ") + feature.Title, buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.MinHeight(38f) }))
			{
				try
				{
					feature.SetEnabled(!feature.Enabled);
					status = save();
				}
				catch (Exception ex)
				{
					feature.Fail(ex);
					status = "Could not change this feature: " + ex.Message;
				}
			}
			GUI.enabled = enabled;
			GUILayout.Label(feature.Description, labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			if (!feature.Available)
			{
				GUILayout.Label(feature.Status, labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
			}
		}
		GUILayout.Space(18f);
		GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[0]);
		if (GUILayout.Button(capturing ? "Press a key (Esc cancels)" : string.Concat("Menu key: ", hotkey.Value, "  / Change"), buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.MinHeight(34f) }))
		{
			capturing = !capturing;
		}
		if (GUILayout.Button("Close", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[2]
		{
			GUILayout.Width(100f),
			GUILayout.MinHeight(34f)
		}))
		{
			Close();
		}
		GUILayout.EndHorizontal();
		GUILayout.Space(10f);
		GUILayout.Label(status, labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.Label(string.Concat("Close with ", hotkey.Value, " or Esc. This menu does not pause the world."), labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[0]);
		GUILayout.EndScrollView();
		GUILayout.EndArea();
		GUI.DragWindow(new Rect(0f, 0f, ((Rect)(ref window)).width, 30f));
	}

	private void EnsureStyles()
	{
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0016: Expected O, but got Unknown
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_0052: Unknown result type (might be due to invalid IL or missing references)
		//IL_0058: Expected O, but got Unknown
		//IL_0087: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cd: Expected O, but got Unknown
		//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_010d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0113: Expected O, but got Unknown
		//IL_0133: Unknown result type (might be due to invalid IL or missing references)
		//IL_0139: Expected O, but got Unknown
		if (windowStyle == null)
		{
			background = new Texture2D(1, 1);
			background.SetPixel(0, 0, new Color(0.08f, 0.1f, 0.13f, 0.98f));
			background.Apply();
			GUIStyle val = new GUIStyle(GUI.skin.window);
			val.fontSize = 22;
			windowStyle = val;
			windowStyle.normal.background = background;
			windowStyle.normal.textColor = Color.white;
			windowStyle.onNormal.background = background;
			windowStyle.onNormal.textColor = Color.white;
			GUIStyle val2 = new GUIStyle(GUI.skin.label);
			val2.fontSize = 15;
			val2.wordWrap = true;
			labelStyle = val2;
			labelStyle.normal.textColor = new Color(0.9f, 0.92f, 0.95f);
			GUIStyle val3 = new GUIStyle(labelStyle);
			val3.fontSize = 19;
			val3.fontStyle = (FontStyle)1;
			titleStyle = val3;
			GUIStyle val4 = new GUIStyle(GUI.skin.button);
			val4.fontSize = 17;
			val4.wordWrap = true;
			buttonStyle = val4;
		}
	}

	public void Dispose()
	{
		if (open)
		{
			Close();
		}
		if (Object.op_Implicit((Object)(object)background))
		{
			Object.Destroy((Object)(object)background);
		}
		shield.Dispose();
	}
}
internal sealed class SpiritCollisionFeature : QolFeature
{
	private sealed class Pair
	{
		public Collider A;

		public Collider B;

		public bool PreviouslyIgnored;
	}

	private sealed class Bodies
	{
		public Collider Root;

		public Rigidbody Body;

		public float RefreshAt;

		public readonly List<Collider> Colliders = new List<Collider>();
	}

	private struct Node
	{
		public Character Character;

		public bool Spirit;
	}

	private static readonly HashSet<int> SpiritPrefabs = new HashSet<int>
	{
		StringExtensionMethods.GetStableHashCode("Boar_spiritcaller"),
		StringExtensionMethods.GetStableHashCode("Wolf_spiritcaller"),
		StringExtensionMethods.GetStableHashCode("Moose_spiritcaller"),
		StringExtensionMethods.GetStableHashCode("Bjorn_spiritcaller")
	};

	private readonly Dictionary<ulong, Pair> pairs = new Dictionary<ulong, Pair>();

	private readonly HashSet<ulong> seen = new HashSet<ulong>();

	private readonly List<ulong> removedPairs = new List<ulong>();

	private readonly Dictionary<Character, Bodies> bodyCache = new Dictionary<Character, Bodies>();

	private readonly HashSet<Character> live = new HashSet<Character>();

	private readonly List<Character> removedCharacters = new List<Character>();

	private readonly List<Node> characters = new List<Node>();

	public SpiritCollisionFeature(ConfigFile config)
		: base(config, "FriendlySpiritCollisions", "Friendly Spirit Caller collisions", "Anyone's four Spirit Caller summons pass through friendly characters. Every client and dedicated server must use matching settings; PvP-enabled players retain collision.")
	{
	}

	protected override void OnTick()
	{
		UpdatePairs();
	}

	protected override void OnToggle(bool next)
	{
		if (!next)
		{
			Clear();
		}
	}

	protected override void OnStop()
	{
		Clear();
	}

	private void UpdatePairs()
	{
		seen.Clear();
		live.Clear();
		characters.Clear();
		foreach (Character allCharacter in Character.GetAllCharacters())
		{
			if (Object.op_Implicit((Object)(object)allCharacter) && ((Component)allCharacter).gameObject.activeInHierarchy && !allCharacter.IsDead())
			{
				ZNetView component = ((Component)allCharacter).GetComponent<ZNetView>();
				if (Object.op_Implicit((Object)(object)component) && component.IsValid())
				{
					live.Add(allCharacter);
					characters.Add(new Node
					{
						Character = allCharacter,
						Spirit = SpiritPrefabs.Contains(component.GetZDO().GetPrefab())
					});
				}
			}
		}
		for (int i = 0; i < characters.Count; i++)
		{
			Node node = characters[i];
			if (!node.Spirit)
			{
				continue;
			}
			for (int j = 0; j < characters.Count; j++)
			{
				Node node2 = characters[j];
				if (i == j || (node2.Spirit && j < i) || !Friendly(node.Character, node2.Character))
				{
					continue;
				}
				List<Collider> bodies = GetBodies(node.Character);
				List<Collider> bodies2 = GetBodies(node2.Character);
				foreach (Collider item in bodies)
				{
					foreach (Collider item2 in bodies2)
					{
						if (ActiveBody(item, node.Character) && ActiveBody(item2, node2.Character) && (Object)(object)item != (Object)(object)item2)
						{
							AddPair(item, item2);
						}
					}
				}
			}
		}
		removedPairs.Clear();
		foreach (KeyValuePair<ulong, Pair> pair in pairs)
		{
			if (!seen.Contains(pair.Key))
			{
				removedPairs.Add(pair.Key);
			}
		}
		foreach (ulong removedPair in removedPairs)
		{
			Restore(pairs[removedPair]);
			pairs.Remove(removedPair);
		}
		removedCharacters.Clear();
		foreach (KeyValuePair<Character, Bodies> item3 in bodyCache)
		{
			if (!live.Contains(item3.Key))
			{
				removedCharacters.Add(item3.Key);
			}
		}
		foreach (Character removedCharacter in removedCharacters)
		{
			bodyCache.Remove(removedCharacter);
		}
	}

	private static bool Friendly(Character a, Character b)
	{
		if ((a.IsPlayer() && a.IsPVPEnabled()) || (b.IsPlayer() && b.IsPVPEnabled()))
		{
			return false;
		}
		if (!BaseAI.IsEnemy(a, b))
		{
			return !BaseAI.IsEnemy(b, a);
		}
		return false;
	}

	private List<Collider> GetBodies(Character character)
	{
		if (!bodyCache.TryGetValue(character, out var value))
		{
			value = new Bodies();
			bodyCache.Add(character, value);
		}
		Collider collider = (Collider)(object)character.GetCollider();
		Rigidbody val = (Object.op_Implicit((Object)(object)collider) ? collider.attachedRigidbody : null);
		if ((Object)(object)value.Root != (Object)(object)collider || (Object)(object)value.Body != (Object)(object)val || Time.time >= value.RefreshAt)
		{
			value.Root = collider;
			value.Body = val;
			value.RefreshAt = Time.time + 0.5f;
			value.Colliders.Clear();
			if (Object.op_Implicit((Object)(object)collider))
			{
				value.Colliders.Add(collider);
			}
			if (Object.op_Implicit((Object)(object)val))
			{
				Collider[] componentsInChildren = ((Component)character).GetComponentsInChildren<Collider>(true);
				foreach (Collider val2 in componentsInChildren)
				{
					if ((Object)(object)val2 != (Object)(object)collider && (Object)(object)val2.attachedRigidbody == (Object)(object)val && (Object)(object)((Component)val2).GetComponentInParent<Character>() == (Object)(object)character)
					{
						value.Colliders.Add(val2);
					}
				}
			}
		}
		return value.Colliders;
	}

	private static bool ActiveBody(Collider collider, Character character)
	{
		if (!Object.op_Implicit((Object)(object)collider) || !collider.enabled || !((Component)collider).gameObject.activeInHierarchy || collider.isTrigger)
		{
			return false;
		}
		Collider collider2 = (Collider)(object)character.GetCollider();
		if ((Object)(object)collider == (Object)(object)collider2)
		{
			return true;
		}
		if (Object.op_Implicit((Object)(object)collider2) && Object.op_Implicit((Object)(object)collider2.attachedRigidbody) && (Object)(object)collider.attachedRigidbody == (Object)(object)collider2.attachedRigidbody)
		{
			return (Object)(object)((Component)collider).GetComponentInParent<Character>() == (Object)(object)character;
		}
		return false;
	}

	private void AddPair(Collider a, Collider b)
	{
		int num = ((Object)a).GetInstanceID();
		int num2 = ((Object)b).GetInstanceID();
		if (num > num2)
		{
			Collider val = a;
			a = b;
			b = val;
			int num3 = num;
			num = num2;
			num2 = num3;
		}
		ulong num4 = ((ulong)(uint)num << 32) | (uint)num2;
		seen.Add(num4);
		if (pairs.TryGetValue(num4, out var value) && ((Object)(object)value.A != (Object)(object)a || (Object)(object)value.B != (Object)(object)b))
		{
			Restore(value);
			pairs.Remove(num4);
		}
		if (!pairs.TryGetValue(num4, out value))
		{
			Pair pair = new Pair();
			pair.A = a;
			pair.B = b;
			pair.PreviouslyIgnored = Physics.GetIgnoreCollision(a, b);
			value = pair;
			pairs.Add(num4, value);
		}
		if (!Physics.GetIgnoreCollision(a, b))
		{
			Physics.IgnoreCollision(a, b, true);
		}
	}

	private static void Restore(Pair pair)
	{
		if (Object.op_Implicit((Object)(object)pair.A) && Object.op_Implicit((Object)(object)pair.B) && !pair.PreviouslyIgnored)
		{
			Physics.IgnoreCollision(pair.A, pair.B, false);
		}
	}

	private void Clear()
	{
		foreach (Pair value in pairs.Values)
		{
			Restore(value);
		}
		pairs.Clear();
		bodyCache.Clear();
		seen.Clear();
		live.Clear();
		characters.Clear();
	}
}
internal sealed class SummonCycleState
{
	public static readonly string[] PrefabOrder = new string[4] { "Boar_spiritcaller", "Wolf_spiritcaller", "Bjorn_spiritcaller", "Moose_spiritcaller" };

	public int NextIndex { get; private set; }

	public SummonCycleState(int savedIndex)
	{
		NextIndex = ((savedIndex >= 0 && savedIndex < 4) ? savedIndex : 0);
	}

	public bool Commit(int createdIndex)
	{
		if (createdIndex != NextIndex)
		{
			return false;
		}
		NextIndex = (NextIndex + 1) % 4;
		return true;
	}
}
internal sealed class ToggleState
{
	private readonly Action<bool> transition;

	public bool Enabled { get; private set; }

	public ToggleState(bool initial, Action<bool> transition)
	{
		Enabled = initial;
		this.transition = transition;
	}

	public bool Set(bool next)
	{
		if (next == Enabled)
		{
			return false;
		}
		transition(next);
		Enabled = next;
		return true;
	}
}
internal sealed class UiShield
{
	private GameObject overlay;

	private EventSystem eventSystem;

	private GameObject priorSelection;

	private bool active;

	public void SetActive(bool value)
	{
		//IL_005b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0065: Expected O, but got Unknown
		//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bf: Expected O, but got Unknown
		//IL_00de: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
		//IL_0125: Unknown result type (might be due to invalid IL or missing references)
		if (value && !active)
		{
			if (!Object.op_Implicit((Object)(object)overlay))
			{
				overlay = new GameObject("BenowyQol input shield", new Type[3]
				{
					typeof(RectTransform),
					typeof(Canvas),
					typeof(GraphicRaycaster)
				});
				Object.DontDestroyOnLoad((Object)(object)overlay);
				Canvas component = overlay.GetComponent<Canvas>();
				component.renderMode = (RenderMode)0;
				component.sortingOrder = 32767;
				GameObject val = new GameObject("Block underlying UI", new Type[2]
				{
					typeof(RectTransform),
					typeof(Image)
				});
				val.transform.SetParent(overlay.transform, false);
				RectTransform component2 = val.GetComponent<RectTransform>();
				component2.anchorMin = Vector2.zero;
				component2.anchorMax = Vector2.one;
				component2.offsetMin = Vector2.zero;
				component2.offsetMax = Vector2.zero;
				Image component3 = val.GetComponent<Image>();
				((Graphic)component3).color = new Color(0f, 0f, 0f, 0.001f);
				((Graphic)component3).raycastTarget = true;
			}
			eventSystem = EventSystem.current;
			priorSelection = (Object.op_Implicit((Object)(object)eventSystem) ? eventSystem.currentSelectedGameObject : null);
			overlay.SetActive(true);
		}
		if (value && Object.op_Implicit((Object)(object)EventSystem.current))
		{
			EventSystem.current.SetSelectedGameObject((GameObject)null);
		}
		if (!value && active)
		{
			if (Object.op_Implicit((Object)(object)overlay))
			{
				overlay.SetActive(false);
			}
			if (Object.op_Implicit((Object)(object)eventSystem) && (Object)(object)EventSystem.current == (Object)(object)eventSystem && Object.op_Implicit((Object)(object)priorSelection) && priorSelection.activeInHierarchy)
			{
				eventSystem.SetSelectedGameObject(priorSelection);
			}
			eventSystem = null;
			priorSelection = null;
		}
		active = value;
	}

	public void Dispose()
	{
		SetActive(value: false);
		if (Object.op_Implicit((Object)(object)overlay))
		{
			Object.Destroy((Object)(object)overlay);
		}
	}
}