Decompiled source of BetterSummon v1.0.1

plugins/BetterSummon/BetterSummon.dll

Decompiled 2 weeks 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 System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using BetterSummon.Config;
using BetterSummon.Features;
using BetterSummon.Game;
using HarmonyLib;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using UnityEngine;
using Valheim.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Kara")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyInformationalVersion("1.0.1+c61c7f48eeb95936d3c87ca5458a8b0be4f8b4c5")]
[assembly: AssemblyProduct("BetterSummon")]
[assembly: AssemblyTitle("BetterSummon")]
[assembly: InternalsVisibleTo("BetterSummon.Tests")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.1.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 BetterSummon
{
	internal static class PatchTargets
	{
		public static readonly (string Type, string Method)[] All = new(string, string)[8]
		{
			("ZNetScene", "Awake"),
			("ObjectDB", "CopyOtherDB"),
			("ObjectDB", "Awake"),
			("BaseAI", "Follow"),
			("Player", "SetControls"),
			("SpawnAbility", "Setup"),
			("Humanoid", "Awake"),
			("MonsterAI", "UpdateAI")
		};

		public static IEnumerable<string> Missing(IEnumerable<MethodBase> patched)
		{
			HashSet<(string, string Name)> found = patched.Select((MethodBase m) => (m.DeclaringType?.Name ?? "", Name: m.Name)).ToHashSet();
			return from t in All
				where !found.Contains(t)
				select t.Type + "." + t.Method;
		}
	}
	[BepInPlugin("kara.bettersummon", "BetterSummon", "1.0.1")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		internal static ManualLogSource Log;

		internal static ModConfig Cfg;

		private Harmony? _harmony;

		private void Awake()
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Cfg = new ModConfig(((BaseUnityPlugin)this).Config);
			_harmony = new Harmony("kara.bettersummon");
			_harmony.PatchAll(typeof(Plugin).Assembly);
			List<MethodBase> list = _harmony.GetPatchedMethods().ToList();
			int num = PatchTargets.All.Length;
			foreach (MethodBase item in list)
			{
				Log.LogDebug((object)("patch : " + item.DeclaringType?.Name + "." + item.Name));
			}
			if (list.Count < num)
			{
				Log.LogError((object)(string.Format("{0} {1} : {2}/{3} patches appliqués — ", "BetterSummon", "1.0.1", list.Count, num) + "cibles manquantes : " + string.Join(", ", PatchTargets.Missing(list))));
			}
			else
			{
				Log.LogInfo((object)string.Format("{0} {1} chargé ({2} patches, compilé pour Valheim {3})", "BetterSummon", "1.0.1", list.Count, "1.0.17"));
			}
		}

		internal static string ResolveLanguage()
		{
			string text = Cfg.Language.Value.Trim().ToLowerInvariant();
			if (text == "french" || text == "english")
			{
				return text;
			}
			if (!((Localization.instance != null) ? Localization.instance.GetSelectedLanguage() : "English").Equals("French", StringComparison.OrdinalIgnoreCase))
			{
				return "english";
			}
			return "french";
		}

		private void OnDestroy()
		{
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class ModInfo
	{
		public const string Guid = "kara.bettersummon";

		public const string Name = "BetterSummon";

		public const string Version = "1.0.1";

		public const string GameVersion = "1.0.17";
	}
}
namespace BetterSummon.Patches
{
	[HarmonyPatch(typeof(BaseAI), "Follow")]
	internal static class BaseAI_Follow_Patch
	{
		private const float RunDistance = 10f;

		private const float LeaveFactor = 3f;

		private static readonly HashSet<int> _settled = new HashSet<int>();

		private static bool Prefix(BaseAI __instance, GameObject go, float dt)
		{
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0168: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			ModConfig cfg = Plugin.Cfg;
			if (!cfg.Enabled.Value || !cfg.Formation.Value)
			{
				return true;
			}
			Player val = (Object.op_Implicit((Object)(object)go) ? go.GetComponent<Player>() : null);
			Character character = __instance.m_character;
			if ((Object)(object)val == (Object)null || !FollowerRegistry.IsSummon(character, out Tameable _))
			{
				return true;
			}
			if (!FollowerRegistry.TryGetSlot(character, val, out var index, out var count, out var maxRadius))
			{
				return true;
			}
			float spacing = Mathf.Max(cfg.FormationSpacing.Value, 2f * maxRadius + 1f);
			float radius = cfg.FormationRadius.Value + maxRadius;
			float num = Mathf.Max(1f, character.GetRadius());
			Vector3 val2 = FollowerRegistry.Heading(val);
			Vector3 position = ((Component)val).transform.position;
			var (num2, num3) = Formation.WorldSlot(position.x, position.z, val2.x, val2.z, index, count, radius, spacing);
			Vector3 val3 = default(Vector3);
			((Vector3)(ref val3))..ctor(num2, position.y, num3);
			Vector3 position2 = ((Component)__instance).transform.position;
			float num4 = Utils.DistanceXZ(val3, position2);
			int instanceID = ((Object)__instance).GetInstanceID();
			bool flag = _settled.Contains(instanceID);
			if (num4 < num || (flag && num4 < num * 3f))
			{
				_settled.Add(instanceID);
				__instance.StopMoving();
				return false;
			}
			_settled.Remove(instanceID);
			if (!__instance.HavePath(val3))
			{
				return true;
			}
			__instance.MoveTo(dt, val3, 0f, Vector3.Distance(position, position2) > 10f);
			return false;
		}
	}
	[HarmonyPatch(typeof(MonsterAI), "UpdateAI")]
	internal static class MonsterAI_UpdateAI_Patch
	{
		private static void Prefix(MonsterAI __instance, float dt)
		{
			if (Plugin.Cfg.Enabled.Value)
			{
				SummonBehaviour.Tick(__instance, dt);
			}
		}
	}
	[HarmonyPatch(typeof(Player), "SetControls")]
	internal static class Player_SetControls_Patch
	{
		private static readonly ClickHold Gesture = new ClickHold();

		private static void Prefix(Player __instance, ref bool attack, ref bool attackHold)
		{
			if (!Plugin.Cfg.Enabled.Value || (Object)(object)__instance != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			StaffInfo staffInfo = SummonSelection.CurrentStaff(__instance);
			if (staffInfo == null)
			{
				Gesture.Reset();
				return;
			}
			if (CastRequest.Take())
			{
				attack = true;
				attackHold = false;
				return;
			}
			if (Hud.InRadial())
			{
				Gesture.Reset();
				attack = false;
				attackHold = false;
				return;
			}
			bool pressed = attackHold | attack;
			bool button = ZInput.GetButton(Plugin.Cfg.NextVariantButton.Value);
			ClickHold.Action action = Gesture.Update(pressed, button, Time.time, Plugin.Cfg.HoldSeconds.Value);
			attack = false;
			attackHold = false;
			switch (action)
			{
			case ClickHold.Action.Cast:
				attack = true;
				break;
			case ClickHold.Action.CastNext:
				SummonSelection.SelectNext(__instance, staffInfo);
				((Character)__instance).Message((MessageType)1, SummonSelection.VariantsOf(staffInfo)[SummonSelection.CurrentIndex(__instance, staffInfo)].Label, 0, (Sprite)null, false);
				attack = true;
				break;
			case ClickHold.Action.OpenWheel:
				SummonOpener.Open(__instance, staffInfo);
				break;
			}
		}
	}
	[HarmonyPatch(typeof(SpawnAbility), "Setup")]
	internal static class SpawnAbility_Setup_Patch
	{
		private static void Prefix(SpawnAbility __instance, Character owner, ItemData item)
		{
			if (!Plugin.Cfg.Enabled.Value)
			{
				return;
			}
			Player val = (Player)(object)((owner is Player) ? owner : null);
			if (val != null && !((Object)(object)val != (Object)(object)Player.m_localPlayer) && item != null)
			{
				StaffInfo staffInfo = SummonSelection.CurrentStaff(val);
				if (staffInfo != null)
				{
					SummonSelection.OnCast(__instance, val, staffInfo);
				}
			}
		}
	}
	[HarmonyPatch(typeof(Humanoid), "Awake")]
	internal static class Humanoid_Awake_Patch
	{
		private static void Postfix(Humanoid __instance)
		{
			if (Plugin.Cfg.Enabled.Value && !(__instance is Player))
			{
				SummonSelection.OnHumanoidAwake(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(ZNetScene), "Awake")]
	internal static class ZNetScene_Awake_Patch
	{
		private static void Postfix()
		{
			CatalogLogger.Reset();
			CatalogLogger.TryLog();
		}
	}
	[HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")]
	internal static class ObjectDB_CopyOtherDB_Patch
	{
		private static void Postfix()
		{
			CatalogLogger.TryLog();
		}
	}
	[HarmonyPatch(typeof(ObjectDB), "Awake")]
	internal static class ObjectDB_Awake_Patch
	{
		private static void Postfix()
		{
			CatalogLogger.TryLog();
		}
	}
	internal static class CatalogLogger
	{
		private static bool _done;

		public static void Reset()
		{
			_done = false;
		}

		public static void TryLog()
		{
			if (_done)
			{
				return;
			}
			if (!SummonScanner.Ready)
			{
				if (Plugin.Cfg.Debug.Value)
				{
					Plugin.Log.LogDebug((object)("catalogue en attente : ObjectDB=" + (Object.op_Implicit((Object)(object)ObjectDB.instance) ? ObjectDB.instance.m_items.Count.ToString() : "null") + " items, ZNetScene=" + (Object.op_Implicit((Object)(object)ZNetScene.instance) ? "ok" : "null")));
				}
				return;
			}
			_done = true;
			SummonCatalog summonCatalog = (SummonSelection.Catalog = SummonScanner.Scan());
			if (Plugin.Cfg.LogCatalog.Value)
			{
				Plugin.Log.LogInfo((object)("Catalogue des invocations :\n" + summonCatalog.Report()));
			}
		}
	}
}
namespace BetterSummon.Game
{
	internal static class FollowerRegistry
	{
		private const float RefreshInterval = 1f;

		private static readonly Dictionary<string, List<string>> _byPlayer = new Dictionary<string, List<string>>();

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

		private static readonly Dictionary<string, Vector3> _heading = new Dictionary<string, Vector3>();

		private static float _lastRefresh = -1f;

		public static bool IsSummon(Character character, out Tameable tameable)
		{
			tameable = ((Component)character).GetComponent<Tameable>();
			if ((Object)(object)tameable != (Object)null && character.IsTamed())
			{
				return SummonScanner.PlayerSummonPrefabs.Contains(PrefabName(character));
			}
			return false;
		}

		public static string PrefabName(Character c)
		{
			string name = ((Object)((Component)c).gameObject).name;
			if (!name.EndsWith("(Clone)"))
			{
				return name;
			}
			return name.Substring(0, name.Length - 7);
		}

		public static bool TryGetSlot(Character character, Player player, out int index, out int count, out float maxRadius)
		{
			Refresh();
			index = -1;
			count = 0;
			maxRadius = 0.5f;
			string playerName = player.GetPlayerName();
			if (!_byPlayer.TryGetValue(playerName, out List<string> value))
			{
				return false;
			}
			index = value.IndexOf(Id(character));
			count = value.Count;
			_maxRadius.TryGetValue(playerName, out maxRadius);
			return index >= 0;
		}

		public static Vector3 Heading(Player player)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			string playerName = player.GetPlayerName();
			Vector3 velocity = ((Character)player).GetVelocity();
			velocity.y = 0f;
			if (((Vector3)(ref velocity)).sqrMagnitude > 0.25f)
			{
				_heading[playerName] = ((Vector3)(ref velocity)).normalized;
			}
			else if (!_heading.ContainsKey(playerName))
			{
				Vector3 forward = ((Component)player).transform.forward;
				forward.y = 0f;
				_heading[playerName] = ((Vector3)(ref forward)).normalized;
			}
			return _heading[playerName];
		}

		private static void Refresh()
		{
			if (Time.time - _lastRefresh < 1f)
			{
				return;
			}
			_lastRefresh = Time.time;
			_byPlayer.Clear();
			_maxRadius.Clear();
			foreach (Character allCharacter in Character.GetAllCharacters())
			{
				if ((Object)(object)allCharacter == (Object)null || allCharacter.IsPlayer() || !IsSummon(allCharacter, out Tameable _))
				{
					continue;
				}
				ZDO obj = (Object.op_Implicit((Object)(object)allCharacter.m_nview) ? allCharacter.m_nview.GetZDO() : null);
				string text = ((obj != null) ? obj.GetString(ZDOVars.s_follow, "") : null) ?? "";
				if (text.Length != 0)
				{
					if (!_byPlayer.TryGetValue(text, out List<string> value))
					{
						value = (_byPlayer[text] = new List<string>());
					}
					value.Add(Id(allCharacter));
					float radius = allCharacter.GetRadius();
					if (!_maxRadius.TryGetValue(text, out var value2) || radius > value2)
					{
						_maxRadius[text] = radius;
					}
				}
			}
			foreach (List<string> value3 in _byPlayer.Values)
			{
				value3.Sort(string.CompareOrdinal);
			}
		}

		private static string Id(Character c)
		{
			if (!Object.op_Implicit((Object)(object)c.m_nview) || c.m_nview.GetZDO() == null)
			{
				return ((Object)c).GetInstanceID().ToString();
			}
			return ((object)Unsafe.As<ZDOID, ZDOID>(ref c.m_nview.GetZDO().m_uid)/*cast due to .constrained prefix*/).ToString();
		}
	}
	internal static class SummonBehaviour
	{
		private sealed class State
		{
			public float NextTick;

			public float HearRange = -1f;

			public float ViewRange = -1f;

			public Posture Applied;
		}

		private static readonly int KeyOwner = StringExtensionMethods.GetStableHashCode("kara.bs.owner");

		private static readonly int KeyPosture = StringExtensionMethods.GetStableHashCode("kara.bs.posture");

		private static readonly int KeyMode = StringExtensionMethods.GetStableHashCode("kara.bs.mode");

		private const string PlayerPostureKey = "kara.bettersummon.posture";

		private const string PlayerModeKey = "kara.bettersummon.mode";

		private static readonly Dictionary<int, State> _states = new Dictionary<int, State>();

		public static Posture PlayerPosture(Player p)
		{
			return (Posture)ReadInt(p, "kara.bettersummon.posture");
		}

		public static FollowMode PlayerMode(Player p)
		{
			return (FollowMode)ReadInt(p, "kara.bettersummon.mode");
		}

		public static void SetPosture(Player p, Posture posture)
		{
			Dictionary<string, string> customData = p.m_customData;
			int num = (int)posture;
			customData["kara.bettersummon.posture"] = num.ToString();
			foreach (Tameable item in Summons.Mine(p))
			{
				Write(item, KeyPosture, (int)posture);
			}
		}

		public static void SetMode(Player p, FollowMode mode)
		{
			Dictionary<string, string> customData = p.m_customData;
			int num = (int)mode;
			customData["kara.bettersummon.mode"] = num.ToString();
			foreach (Tameable item in Summons.Mine(p))
			{
				Write(item, KeyMode, (int)mode);
			}
		}

		private static int ReadInt(Player p, string key)
		{
			if (!p.m_customData.TryGetValue(key, out var value) || !int.TryParse(value, out var result))
			{
				return 0;
			}
			return result;
		}

		private static void Write(Tameable t, int key, int value)
		{
			ZNetView nview = t.m_nview;
			if (Object.op_Implicit((Object)(object)nview) && nview.IsValid())
			{
				nview.ClaimOwnership();
				nview.GetZDO().Set(key, value, false);
			}
		}

		public static string? OwnerName(ZDO zdo)
		{
			string text = zdo.GetString(KeyOwner, "");
			if (text.Length == 0)
			{
				text = zdo.GetString(ZDOVars.s_follow, "");
			}
			if (text.Length != 0)
			{
				return text;
			}
			return null;
		}

		public static void Tick(MonsterAI ai, float dt)
		{
			Character character = ((BaseAI)ai).m_character;
			if ((Object)(object)character == (Object)null || !FollowerRegistry.IsSummon(character, out Tameable _))
			{
				return;
			}
			ZNetView nview = ((BaseAI)ai).m_nview;
			if (!Object.op_Implicit((Object)(object)nview) || !nview.IsValid() || !nview.IsOwner())
			{
				return;
			}
			int instanceID = ((Object)ai).GetInstanceID();
			if (!_states.TryGetValue(instanceID, out State value))
			{
				value = (_states[instanceID] = new State());
			}
			if (!(Time.time < value.NextTick))
			{
				value.NextTick = Time.time + 0.5f;
				ZDO zDO = nview.GetZDO();
				string text = Initialize(zDO);
				if (text != null)
				{
					ApplyPosture(ai, value, (Posture)zDO.GetInt(KeyPosture, 0));
					ApplyMode(ai, zDO, text, (FollowMode)zDO.GetInt(KeyMode, 0));
				}
			}
		}

		private static string? Initialize(ZDO zdo)
		{
			string text = zdo.GetString(KeyOwner, "");
			if (text.Length > 0)
			{
				return text;
			}
			string text2 = zdo.GetString(ZDOVars.s_follow, "");
			if (text2.Length == 0)
			{
				return null;
			}
			zdo.Set(KeyOwner, text2);
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null && localPlayer.GetPlayerName() == text2)
			{
				zdo.Set(KeyPosture, (int)PlayerPosture(localPlayer), false);
				zdo.Set(KeyMode, (int)PlayerMode(localPlayer), false);
			}
			return text2;
		}

		private static void ApplyPosture(MonsterAI ai, State state, Posture posture)
		{
			if (state.HearRange < 0f)
			{
				state.HearRange = ((BaseAI)ai).m_hearRange;
				state.ViewRange = ((BaseAI)ai).m_viewRange;
			}
			if (posture == Posture.Passive)
			{
				((BaseAI)ai).m_hearRange = 0f;
				((BaseAI)ai).m_viewRange = 0f;
				if (state.Applied != Posture.Passive)
				{
					ai.m_targetCreature = null;
				}
			}
			else
			{
				((BaseAI)ai).m_hearRange = state.HearRange;
				((BaseAI)ai).m_viewRange = state.ViewRange;
			}
			state.Applied = posture;
		}

		private static void ApplyMode(MonsterAI ai, ZDO zdo, string ownerName, FollowMode mode)
		{
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			Player val = FindPlayer(ownerName);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			bool flag = (Object)(object)ai.GetFollowTarget() != (Object)null;
			if (mode == FollowMode.Follow)
			{
				if (!flag)
				{
					((BaseAI)ai).ResetPatrolPoint();
					ai.SetFollowTarget(((Component)val).gameObject);
					zdo.Set(ZDOVars.s_follow, ownerName);
				}
				return;
			}
			if (zdo.GetString(ZDOVars.s_follow, "").Length > 0)
			{
				zdo.Set(ZDOVars.s_follow, "");
			}
			if (!zdo.GetBool(ZDOVars.s_patrol, false) && !flag)
			{
				((BaseAI)ai).SetPatrolPoint();
			}
			switch (GuardLogic.Decide(flag, Vector3.Distance(((Component)ai).transform.position, ((Component)val).transform.position), Plugin.Cfg.GuardLeashDistance.Value, Plugin.Cfg.GuardStopMargin.Value))
			{
			case GuardStep.Resume:
				((BaseAI)ai).ResetPatrolPoint();
				ai.SetFollowTarget(((Component)val).gameObject);
				break;
			case GuardStep.Reanchor:
				ai.SetFollowTarget((GameObject)null);
				((BaseAI)ai).SetPatrolPoint();
				break;
			}
		}

		private static Player? FindPlayer(string name)
		{
			foreach (Player allPlayer in Player.GetAllPlayers())
			{
				if (allPlayer.GetPlayerName() == name)
				{
					return allPlayer;
				}
			}
			return null;
		}
	}
	internal sealed class SummonOpener : OpenRadialConfig, IRadialConfig
	{
		public SummonGroupConfig? Group;

		void IRadialConfig.InitRadialConfig(RadialBase radial)
		{
			radial.OnInteractionDelay = delegate(float delay)
			{
				PlayerController.SetTakeInputDelay(delay);
			};
			radial.ShouldAnimateIn = true;
			if ((Object)(object)Group != (Object)null)
			{
				radial.Open((IRadialConfig)(object)Group, (IRadialConfig)null);
			}
		}

		public static void Open(Player player, StaffInfo staff)
		{
			Hud instance = Hud.instance;
			if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance.m_radialMenu == (Object)null) && !Hud.InRadial())
			{
				SummonGroupConfig summonGroupConfig = ScriptableObject.CreateInstance<SummonGroupConfig>();
				summonGroupConfig.Player = player;
				summonGroupConfig.Staff = staff;
				SummonOpener summonOpener = ScriptableObject.CreateInstance<SummonOpener>();
				summonOpener.Group = summonGroupConfig;
				instance.m_radialMenu.CanOpen = true;
				instance.m_radialMenu.Open((IRadialConfig)(object)summonOpener, (IRadialConfig)null);
			}
		}
	}
	internal sealed class SummonGroupConfig : ScriptableObject, IRadialConfig
	{
		public Player? Player;

		public StaffInfo? Staff;

		public string LocalizedName => Staff?.StaffName ?? Text.Get("summons");

		public Sprite Sprite
		{
			get
			{
				if (Staff == null)
				{
					return null;
				}
				return Icons.Item(Staff.StaffPrefab);
			}
		}

		public void InitRadialConfig(RadialBase radial)
		{
			Text.Language = Plugin.ResolveLanguage();
			List<RadialMenuElement> list = new List<RadialMenuElement>();
			if ((Object)(object)Player == (Object)null || Staff == null)
			{
				radial.ConstructRadial(list);
				return;
			}
			List<Variant> list2 = SummonSelection.VariantsOf(Staff);
			int num = SummonSelection.CurrentIndex(Player, Staff);
			for (int i = 0; i < list2.Count; i++)
			{
				int index = i;
				Variant variant = list2[i];
				list.Add(Element(variant.Label + ((i == num) ? "  ✓" : ""), Icons.Item(variant.IconItem) ?? Sprite, delegate
				{
					SummonSelection.Select(Player, Staff, index);
					if (Plugin.Cfg.CastOnSelect.Value)
					{
						CastRequest.Arm();
					}
					return true;
				}));
			}
			Posture posture = SummonBehaviour.PlayerPosture(Player);
			list.Add(Element(Text.Get("stance", GuardLogic.Label(posture)), Icons.Item((posture == Posture.Aggressive) ? "SwordIron" : "Raspberry"), delegate
			{
				Posture posture2 = GuardLogic.Toggle(posture);
				SummonBehaviour.SetPosture(Player, posture2);
				((Character)Player).Message((MessageType)1, Text.Get("stance.msg", GuardLogic.Label(posture2).ToLowerInvariant()), 0, (Sprite)null, false);
				return true;
			}));
			FollowMode mode = SummonBehaviour.PlayerMode(Player);
			list.Add(Element(Text.Get("orders", GuardLogic.Label(mode)), Icons.Item((mode == FollowMode.Follow) ? "CapeDeerHide" : "Hoe"), delegate
			{
				FollowMode followMode = GuardLogic.Toggle(mode);
				SummonBehaviour.SetMode(Player, followMode);
				((Character)Player).Message((MessageType)1, Text.Get("orders.msg", GuardLogic.Label(followMode).ToLowerInvariant()), 0, (Sprite)null, false);
				return true;
			}));
			int count = Summons.Mine(Player).Count;
			list.Add(Element((count > 0) ? string.Format("{0} ({1})", Text.Get("dismiss"), count) : Text.Get("dismiss"), Icons.Item("TrophySkeleton"), delegate
			{
				int num2 = Summons.DismissAll(Player);
				((Character)Player).Message((MessageType)2, (num2 > 0) ? Text.Get("dismissed", num2) : Text.Get("none"), 0, (Sprite)null, false);
				return true;
			}));
			radial.ConstructRadial(list);
		}

		private static RadialMenuElement Element(string name, Sprite? icon, Func<bool> interact)
		{
			EmoteElement obj = Object.Instantiate<EmoteElement>(RadialData.SO.EmoteElement);
			((RadialMenuElement)obj).Name = name;
			((RadialMenuElement)obj).Interact = interact;
			((RadialMenuElement)obj).CloseOnInteract = () => true;
			((Component)((RadialMenuElement)obj).m_icon).gameObject.SetActive((Object)(object)icon != (Object)null);
			((RadialMenuElement)obj).m_icon.sprite = icon;
			return (RadialMenuElement)(object)obj;
		}
	}
	internal static class Icons
	{
		public static Sprite? Item(string? prefabName)
		{
			if (string.IsNullOrEmpty(prefabName) || (Object)(object)ObjectDB.instance == (Object)null)
			{
				return null;
			}
			GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(prefabName);
			ItemDrop val = (Object.op_Implicit((Object)(object)itemPrefab) ? itemPrefab.GetComponent<ItemDrop>() : null);
			if (!((Object)(object)val != (Object)null))
			{
				return null;
			}
			return val.m_itemData.GetIcon();
		}
	}
	internal static class CastRequest
	{
		private static float _until;

		public static void Arm()
		{
			_until = Time.time + 2f;
		}

		public static bool Take()
		{
			if (_until <= 0f || Hud.InRadial())
			{
				return false;
			}
			if (Time.time > _until)
			{
				_until = 0f;
				return false;
			}
			_until = 0f;
			return true;
		}
	}
	internal static class Summons
	{
		public static List<Tameable> Mine(Player player)
		{
			List<Tameable> list = new List<Tameable>();
			string playerName = player.GetPlayerName();
			foreach (Character allCharacter in Character.GetAllCharacters())
			{
				if (!((Object)(object)allCharacter == (Object)null) && !allCharacter.IsPlayer() && FollowerRegistry.IsSummon(allCharacter, out Tameable tameable))
				{
					ZDO val = (Object.op_Implicit((Object)(object)allCharacter.m_nview) ? allCharacter.m_nview.GetZDO() : null);
					if (val != null && SummonBehaviour.OwnerName(val) == playerName)
					{
						list.Add(tameable);
					}
				}
			}
			return list;
		}

		public static int DismissAll(Player player)
		{
			List<Tameable> list = Mine(player);
			foreach (Tameable item in list)
			{
				item.UnSummon();
			}
			return list.Count;
		}
	}
	internal static class SummonScanner
	{
		public static readonly HashSet<string> PlayerSummonPrefabs = new HashSet<string>();

		public static bool Ready
		{
			get
			{
				if ((Object)(object)ObjectDB.instance != (Object)null && ObjectDB.instance.m_items.Count > 0)
				{
					return (Object)(object)ZNetScene.instance != (Object)null;
				}
				return false;
			}
		}

		public static SummonCatalog Scan()
		{
			SummonCatalog summonCatalog = new SummonCatalog();
			foreach (GameObject item in ObjectDB.instance.m_items)
			{
				ItemDrop val = (Object.op_Implicit((Object)(object)item) ? item.GetComponent<ItemDrop>() : null);
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				SharedData shared = val.m_itemData.m_shared;
				GameObject[] array = (GameObject[])(object)new GameObject[4]
				{
					shared.m_attack?.m_attackProjectile,
					shared.m_attack?.m_spawnOnTrigger,
					shared.m_secondaryAttack?.m_attackProjectile,
					shared.m_secondaryAttack?.m_spawnOnTrigger
				};
				foreach (GameObject val2 in array)
				{
					SpawnAbility val3 = (Object.op_Implicit((Object)(object)val2) ? val2.GetComponent<SpawnAbility>() : null);
					if ((Object)(object)val3 == (Object)null)
					{
						continue;
					}
					StaffInfo staffInfo = Describe(item, shared, val2, val3);
					summonCatalog.Staffs.Add(staffInfo);
					if (!staffInfo.CommandOnSpawn)
					{
						continue;
					}
					foreach (VariantInfo variant in staffInfo.Variants)
					{
						PlayerSummonPrefabs.Add(variant.Prefab);
					}
				}
			}
			return summonCatalog;
		}

		private static StaffInfo Describe(GameObject itemPrefab, SharedData shared, GameObject projectile, SpawnAbility ability)
		{
			StaffInfo staffInfo = new StaffInfo
			{
				StaffPrefab = ((Object)itemPrefab).name,
				StaffName = Localization.instance.Localize(shared.m_name),
				ProjectilePrefab = ((Object)projectile).name,
				MaxSpawned = ability.m_maxSpawned,
				MaxFromWeaponLevel = ability.m_setMaxInstancesFromWeaponLevel,
				CommandOnSpawn = ability.m_commandOnSpawn,
				LevelUpSteps = (ability.m_levelUpSettings?.Count ?? 0)
			};
			GameObject[] array = ability.m_spawnPrefab ?? Array.Empty<GameObject>();
			foreach (GameObject val in array)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				VariantInfo variantInfo = new VariantInfo
				{
					Prefab = ((Object)val).name
				};
				Character component = val.GetComponent<Character>();
				if ((Object)(object)component != (Object)null)
				{
					variantInfo.CreatureName = Localization.instance.Localize(component.m_name);
				}
				Tameable component2 = val.GetComponent<Tameable>();
				if ((Object)(object)component2 != (Object)null)
				{
					variantInfo.UnsummonDistance = component2.m_unsummonDistance;
					variantInfo.Commandable = component2.m_commandable;
				}
				Humanoid component3 = val.GetComponent<Humanoid>();
				if ((Object)(object)component3 != (Object)null)
				{
					variantInfo.DefaultItems.AddRange(from val3 in component3.m_defaultItems
						where Object.op_Implicit((Object)(object)val3)
						select ((Object)val3).name);
					variantInfo.RandomWeapons.AddRange(component3.m_randomWeapon.Select(Name));
					variantInfo.RandomShields.AddRange(component3.m_randomShield.Select(Name));
					variantInfo.RandomArmors.AddRange(component3.m_randomArmor.Select(Name));
					ItemSet[] randomSets = component3.m_randomSets;
					foreach (ItemSet val2 in randomSets)
					{
						ItemSetInfo itemSetInfo = new ItemSetInfo
						{
							Name = val2.m_name
						};
						itemSetInfo.Items.AddRange(from val3 in val2.m_items
							where Object.op_Implicit((Object)(object)val3)
							select ((Object)val3).name);
						variantInfo.RandomSets.Add(itemSetInfo);
					}
				}
				staffInfo.Variants.Add(variantInfo);
			}
			return staffInfo;
		}

		private static string? Name(GameObject? go)
		{
			if (!Object.op_Implicit((Object)(object)go))
			{
				return null;
			}
			return ((Object)go).name;
		}
	}
	internal static class SummonSelection
	{
		public static SummonCatalog? Catalog;

		private static readonly Dictionary<string, List<Variant>> _variants = new Dictionary<string, List<Variant>>();

		private static Variant? _pending;

		private static int _pendingLeft;

		private static float _pendingUntil;

		public static StaffInfo? CurrentStaff(Player player)
		{
			ItemData currentWeapon = ((Humanoid)player).GetCurrentWeapon();
			if (currentWeapon == null || Catalog == null)
			{
				return null;
			}
			string text = (Object.op_Implicit((Object)(object)currentWeapon.m_dropPrefab) ? ((Object)currentWeapon.m_dropPrefab).name : "");
			foreach (StaffInfo staff in Catalog.Staffs)
			{
				if (staff.CommandOnSpawn && staff.StaffPrefab == text)
				{
					return staff;
				}
			}
			return null;
		}

		public static List<Variant> VariantsOf(StaffInfo staff)
		{
			if (!_variants.TryGetValue(staff.StaffPrefab, out List<Variant> value))
			{
				value = (_variants[staff.StaffPrefab] = Variants.For(staff));
			}
			return value;
		}

		public static int CurrentIndex(Player player, StaffInfo staff)
		{
			return SelectionStore.Current(Load(player), staff.StaffPrefab, VariantsOf(staff).Count);
		}

		public static void Select(Player player, StaffInfo staff, int index)
		{
			Dictionary<string, int> dictionary = Load(player);
			dictionary[staff.StaffPrefab] = index;
			player.m_customData["kara.bettersummon.selection"] = SelectionStore.Encode(dictionary);
		}

		public static void SelectNext(Player player, StaffInfo staff)
		{
			Select(player, staff, SelectionStore.Next(Load(player), staff.StaffPrefab, VariantsOf(staff).Count));
		}

		private static Dictionary<string, int> Load(Player player)
		{
			string value;
			return SelectionStore.Decode(player.m_customData.TryGetValue("kara.bettersummon.selection", out value) ? value : null);
		}

		public static void OnCast(SpawnAbility ability, Player player, StaffInfo staff)
		{
			List<Variant> list = VariantsOf(staff);
			if (list.Count == 0)
			{
				return;
			}
			Variant variant = list[CurrentIndex(player, staff)];
			if (variant.Kind == VariantKind.Prefab)
			{
				GameObject prefab = ZNetScene.instance.GetPrefab(variant.Prefab);
				if ((Object)(object)prefab != (Object)null)
				{
					ability.m_spawnPrefab = (GameObject[])(object)new GameObject[1] { prefab };
				}
				return;
			}
			_pending = variant;
			_pendingLeft = Mathf.Max(1, ability.m_maxToSpawn);
			_pendingUntil = Time.time + 10f;
			if (Plugin.Cfg.Debug.Value)
			{
				Plugin.Log.LogDebug((object)("variante en attente : " + variant.Label + " pour " + variant.Prefab));
			}
		}

		public static void OnHumanoidAwake(Humanoid humanoid)
		{
			if (_pending == null || Time.time > _pendingUntil)
			{
				_pending = null;
			}
			else
			{
				if (!Object.op_Implicit((Object)(object)((Character)humanoid).m_nview) || !((Character)humanoid).m_nview.IsValid() || !((Character)humanoid).m_nview.IsOwner() || FollowerRegistry.PrefabName((Character)(object)humanoid) != _pending.Prefab)
				{
					return;
				}
				int? num = SeedFinder.Find(humanoid, _pending, humanoid.m_seed);
				if (num.HasValue)
				{
					humanoid.m_seed = num.Value;
					((Character)humanoid).m_nview.GetZDO().Set(ZDOVars.s_seed, num.Value, true);
					if (Plugin.Cfg.Debug.Value)
					{
						Plugin.Log.LogDebug((object)$"graine {num.Value} → {_pending.Label}");
					}
				}
				else
				{
					Plugin.Log.LogWarning((object)("aucune graine trouvée pour la variante " + _pending.Label));
				}
				if (--_pendingLeft <= 0)
				{
					_pending = null;
				}
			}
		}
	}
	internal static class SeedFinder
	{
		private const int MaxTries = 20000;

		public static int? Find(Humanoid h, Variant v, int start)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: 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)
			State state = Random.state;
			try
			{
				for (int i = 0; i < 20000; i++)
				{
					int num = start + i * 7919;
					if (num != 0)
					{
						Random.InitState(num);
						int shield = ((h.m_randomShield.Length != 0) ? Random.Range(0, h.m_randomShield.Length) : (-1));
						int weapon = ((h.m_randomWeapon.Length != 0) ? Random.Range(0, h.m_randomWeapon.Length) : (-1));
						if (h.m_randomArmor.Length != 0)
						{
							Random.Range(0, h.m_randomArmor.Length);
						}
						int set = ((h.m_randomSets.Length != 0) ? Random.Range(0, h.m_randomSets.Length) : (-1));
						if (Matches(v, shield, weapon, set))
						{
							return num;
						}
					}
				}
				return null;
			}
			finally
			{
				Random.state = state;
			}
		}

		private static bool Matches(Variant v, int shield, int weapon, int set)
		{
			if (v.Kind == VariantKind.Set)
			{
				return Array.IndexOf(v.SetIndices, set) >= 0;
			}
			bool num = v.WeaponIndices.Length == 0 || Array.IndexOf(v.WeaponIndices, weapon) >= 0;
			bool flag = ((v.ShieldIndices.Length != 0) ? (Array.IndexOf(v.ShieldIndices, shield) >= 0) : (shield < 0 || IsNullShield(v)));
			return num && flag;
		}

		private static bool IsNullShield(Variant v)
		{
			return true;
		}
	}
}
namespace BetterSummon.Features
{
	public enum Posture
	{
		Aggressive,
		Passive
	}
	public enum FollowMode
	{
		Follow,
		Guard
	}
	public enum GuardStep
	{
		None,
		Resume,
		Reanchor
	}
	public static class GuardLogic
	{
		public static GuardStep Decide(bool following, float selfToMaster, float leashDistance, float stopMargin)
		{
			if (!following)
			{
				if (!(selfToMaster > leashDistance))
				{
					return GuardStep.None;
				}
				return GuardStep.Resume;
			}
			if (!(selfToMaster <= leashDistance - stopMargin))
			{
				return GuardStep.None;
			}
			return GuardStep.Reanchor;
		}

		public static Posture Toggle(Posture p)
		{
			if (p != Posture.Aggressive)
			{
				return Posture.Aggressive;
			}
			return Posture.Passive;
		}

		public static FollowMode Toggle(FollowMode m)
		{
			if (m != FollowMode.Follow)
			{
				return FollowMode.Follow;
			}
			return FollowMode.Guard;
		}

		public static string Label(Posture p)
		{
			return Text.Get((p == Posture.Aggressive) ? "aggressive" : "passive");
		}

		public static string Label(FollowMode m)
		{
			return Text.Get((m == FollowMode.Follow) ? "follow" : "guard");
		}
	}
	public sealed class ClickHold
	{
		public enum Action
		{
			None,
			Cast,
			CastNext,
			OpenWheel
		}

		private bool _held;

		private float _pressedAt;

		private bool _wheelOpened;

		public bool IsHolding => _held;

		public Action Update(bool pressed, bool modifier, float now, float holdSeconds)
		{
			if (pressed && !_held)
			{
				_held = true;
				_pressedAt = now;
				_wheelOpened = false;
				return Action.None;
			}
			if (pressed && _held)
			{
				if (!_wheelOpened && now - _pressedAt >= holdSeconds)
				{
					_wheelOpened = true;
					return Action.OpenWheel;
				}
				return Action.None;
			}
			if (!pressed && _held)
			{
				_held = false;
				if (_wheelOpened)
				{
					return Action.None;
				}
				if (!modifier)
				{
					return Action.Cast;
				}
				return Action.CastNext;
			}
			return Action.None;
		}

		public void Reset()
		{
			_held = false;
			_wheelOpened = false;
		}
	}
	public static class Formation
	{
		public static (float dx, float dz) SlotOffset(int index, int count, float radius, float spacing)
		{
			if (count <= 0 || index < 0 || index >= count)
			{
				return (dx: 0f, dz: 0f - radius);
			}
			double num = 2.0 * Math.Asin(Math.Min(1.0, (double)spacing / (2.0 * (double)radius)));
			double num2 = num * (double)(count - 1);
			double num3 = Math.PI + ((double)index - (double)(count - 1) / 2.0) * num;
			if (num2 > 4.71238898038469)
			{
				num3 = Math.PI + ((double)index - (double)(count - 1) / 2.0) * (4.71238898038469 / (double)Math.Max(1, count - 1));
			}
			return (dx: (float)(Math.Sin(num3) * (double)radius), dz: (float)(Math.Cos(num3) * (double)radius));
		}

		public static (float x, float z) WorldSlot(float playerX, float playerZ, float headingX, float headingZ, int index, int count, float radius, float spacing)
		{
			(float x, float z) tuple = Normalize(headingX, headingZ);
			float item = tuple.x;
			float item2 = tuple.z;
			(float dx, float dz) tuple2 = SlotOffset(index, count, radius, spacing);
			float item3 = tuple2.dx;
			float item4 = tuple2.dz;
			float item5 = playerX + item3 * item2 + item4 * item;
			float item6 = playerZ - item3 * item + item4 * item2;
			return (x: item5, z: item6);
		}

		public static (float x, float z) Normalize(float x, float z)
		{
			float num = (float)Math.Sqrt(x * x + z * z);
			if (!(num < 0.0001f))
			{
				return (x: x / num, z: z / num);
			}
			return (x: 0f, z: 1f);
		}
	}
	public static class SelectionStore
	{
		public const string Key = "kara.bettersummon.selection";

		public static Dictionary<string, int> Decode(string? text)
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			if (string.IsNullOrEmpty(text))
			{
				return dictionary;
			}
			string[] array = text.Split(';');
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Split('=');
				if (array2.Length == 2 && int.TryParse(array2[1], out var result) && array2[0].Length > 0)
				{
					dictionary[array2[0]] = result;
				}
			}
			return dictionary;
		}

		public static string Encode(IReadOnlyDictionary<string, int> selection)
		{
			return string.Join(";", from kv in selection.OrderBy<KeyValuePair<string, int>, string>((KeyValuePair<string, int> kv) => kv.Key, StringComparer.Ordinal)
				select $"{kv.Key}={kv.Value}");
		}

		public static int Current(IReadOnlyDictionary<string, int> selection, string staff, int count)
		{
			if (count > 0)
			{
				if (!selection.TryGetValue(staff, out var value) || value < 0 || value >= count)
				{
					return 0;
				}
				return value;
			}
			return 0;
		}

		public static int Next(IReadOnlyDictionary<string, int> selection, string staff, int count)
		{
			if (count > 0)
			{
				return (Current(selection, staff, count) + 1) % count;
			}
			return 0;
		}
	}
	public sealed class SummonCatalog
	{
		public readonly List<StaffInfo> Staffs = new List<StaffInfo>();

		public string Report()
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine($"{Staffs.Count} bâton(s) d'invocation trouvé(s)");
			foreach (StaffInfo staff in Staffs)
			{
				stringBuilder.AppendLine($"── {staff.StaffPrefab} ({staff.StaffName}) → projectile {staff.ProjectilePrefab}, max {staff.MaxSpawned}" + (staff.MaxFromWeaponLevel ? " (= niveau de l'arme)" : "") + (staff.CommandOnSpawn ? ", suit le lanceur" : "") + $", {staff.LevelUpSteps} palier(s) de compétence");
				foreach (VariantInfo variant in staff.Variants)
				{
					stringBuilder.AppendLine("   • " + variant.Prefab + " « " + variant.CreatureName + " »" + ((variant.UnsummonDistance > 0f) ? $", renvoi au-delà de {variant.UnsummonDistance} m" : "") + (variant.Commandable ? ", commandable" : ""));
					Append(stringBuilder, "défaut", variant.DefaultItems);
					Append(stringBuilder, "armes", variant.RandomWeapons);
					Append(stringBuilder, "boucliers", variant.RandomShields);
					Append(stringBuilder, "armures", variant.RandomArmors);
					foreach (ItemSetInfo randomSet in variant.RandomSets)
					{
						stringBuilder.AppendLine("       set « " + randomSet.Name + " » : " + string.Join(", ", randomSet.Items));
					}
				}
			}
			return stringBuilder.ToString().TrimEnd();
		}

		private static void Append(StringBuilder sb, string label, IReadOnlyList<string?> items)
		{
			if (items.Count > 0)
			{
				sb.AppendLine("       " + label + " : " + string.Join(", ", items.Select((string i) => i ?? "(aucun)")));
			}
		}
	}
	public sealed class StaffInfo
	{
		public string StaffPrefab = "";

		public string StaffName = "";

		public string ProjectilePrefab = "";

		public int MaxSpawned;

		public bool MaxFromWeaponLevel;

		public bool CommandOnSpawn;

		public int LevelUpSteps;

		public readonly List<VariantInfo> Variants = new List<VariantInfo>();
	}
	public sealed class VariantInfo
	{
		public string Prefab = "";

		public string CreatureName = "";

		public float UnsummonDistance;

		public bool Commandable;

		public readonly List<string> DefaultItems = new List<string>();

		public readonly List<string?> RandomWeapons = new List<string>();

		public readonly List<string?> RandomShields = new List<string>();

		public readonly List<string?> RandomArmors = new List<string>();

		public readonly List<ItemSetInfo> RandomSets = new List<ItemSetInfo>();
	}
	public sealed class ItemSetInfo
	{
		public string Name = "";

		public readonly List<string> Items = new List<string>();
	}
	public static class Text
	{
		public static string Language = "english";

		private static readonly Dictionary<string, (string en, string fr)> T = new Dictionary<string, (string, string)>
		{
			["summons"] = ("Summons", "Invocations"),
			["dismiss"] = ("Dismiss", "Renvoyer"),
			["dismissed"] = ("{0} summon(s) dismissed", "{0} invocation(s) renvoyée(s)"),
			["none"] = ("No summons", "Aucune invocation"),
			["stance"] = ("Stance: {0}", "Posture : {0}"),
			["orders"] = ("Orders: {0}", "Ordre : {0}"),
			["stance.msg"] = ("Summons: {0} stance", "Invocations : posture {0}"),
			["orders.msg"] = ("Summons: {0}", "Invocations : {0}"),
			["aggressive"] = ("Aggressive", "Agressive"),
			["passive"] = ("Passive", "Passive"),
			["follow"] = ("Follow", "Suivre"),
			["guard"] = ("Guard", "Garder"),
			["Bjorn_spiritcaller"] = ("Bear", "Ours"),
			["Moose_spiritcaller"] = ("Moose", "Élan"),
			["Wolf_spiritcaller"] = ("Wolf", "Loup"),
			["Boar_spiritcaller"] = ("Boar", "Sanglier"),
			["skeleton_sword2"] = ("Sword", "Épée"),
			["skeleton_sword"] = ("Sword", "Épée"),
			["skeleton_bow2"] = ("Archer", "Archer"),
			["skeleton_bow"] = ("Archer", "Archer"),
			["ShieldWood"] = ("wood shield", "bouclier bois"),
			["ShieldBronzeBuckler"] = ("bronze buckler", "bouclier bronze"),
			["Unarmed"] = ("Fists", "Poings"),
			["Log"] = ("Log", "Tronc")
		};

		public static bool Has(string key)
		{
			return T.ContainsKey(key);
		}

		public static string Get(string key, params object[] args)
		{
			if (!T.TryGetValue(key, out (string, string) value))
			{
				return key;
			}
			string text;
			if (!(Language == "french"))
			{
				(text, _) = value;
			}
			else
			{
				text = value.Item2;
			}
			string text2 = text;
			if (args.Length != 0)
			{
				return string.Format(text2, args);
			}
			return text2;
		}
	}
	public enum VariantKind
	{
		Prefab,
		Equipment,
		Set
	}
	public sealed class Variant
	{
		public VariantKind Kind;

		public string Prefab = "";

		public string Label = "";

		public string? IconItem;

		public int[] WeaponIndices = Array.Empty<int>();

		public int[] ShieldIndices = Array.Empty<int>();

		public int[] SetIndices = Array.Empty<int>();
	}
	public static class Variants
	{
		public static readonly Dictionary<string, string?> Icons = new Dictionary<string, string>
		{
			["Bjorn_spiritcaller"] = "TrophyBjorn",
			["Moose_spiritcaller"] = "TrophyMoose",
			["Wolf_spiritcaller"] = "TrophyWolf",
			["Boar_spiritcaller"] = "TrophyBoar",
			["skeleton_sword2"] = "SwordBronze",
			["skeleton_sword"] = "SwordBronze",
			["skeleton_bow2"] = "Bow",
			["skeleton_bow"] = "Bow",
			["ShieldWood"] = "ShieldWood",
			["ShieldBronzeBuckler"] = "ShieldBronzeBuckler"
		};

		public static List<Variant> For(StaffInfo staff)
		{
			List<Variant> list = new List<Variant>();
			if (staff.Variants.Count > 1)
			{
				foreach (VariantInfo variant in staff.Variants)
				{
					list.Add(new Variant
					{
						Kind = VariantKind.Prefab,
						Prefab = variant.Prefab,
						Label = Label(variant.Prefab, variant.CreatureName),
						IconItem = Icon(variant.Prefab)
					});
				}
				return list;
			}
			if (staff.Variants.Count == 0)
			{
				return list;
			}
			VariantInfo variantInfo = staff.Variants[0];
			if (variantInfo.RandomSets.Count > 0)
			{
				foreach (IGrouping<string, (ItemSetInfo, int)> item in from x in variantInfo.RandomSets.Select((ItemSetInfo s, int i) => (s: s, i: i))
					group x by x.s.Name)
				{
					list.Add(new Variant
					{
						Kind = VariantKind.Set,
						Prefab = variantInfo.Prefab,
						Label = Label(item.Key, ""),
						IconItem = Icon(item.Key),
						SetIndices = item.Select<(ItemSetInfo, int), int>(((ItemSetInfo s, int i) x) => x.i).ToArray()
					});
				}
				return list;
			}
			List<IGrouping<string, (string, int)>> list2 = (from x in variantInfo.RandomWeapons.Select((string n, int i) => (n: n, i: i))
				where x.n != null
				group x by x.n).ToList();
			List<IGrouping<string, (string, int)>> list3 = (from x in variantInfo.RandomShields.Select((string n, int i) => (n: n, i: i))
				group x by x.n).ToList();
			if (list2.Count == 0 && list3.Count == 0)
			{
				list.Add(new Variant
				{
					Kind = VariantKind.Prefab,
					Prefab = variantInfo.Prefab,
					Label = Label(variantInfo.Prefab, variantInfo.CreatureName),
					IconItem = Icon(variantInfo.Prefab)
				});
				return list;
			}
			foreach (IGrouping<string, (string, int)> item2 in list2)
			{
				bool twoHanded = item2.Key.ToLowerInvariant().Contains("bow");
				IGrouping<string, (string, int)>[] array2;
				IGrouping<string, (string, int)>[] array;
				if (list3.Count != 0)
				{
					array = list3.Where((IGrouping<string, (string n, int i)> s) => !twoHanded || s.Key == null).Cast<IGrouping<string, (string, int)>>().ToArray();
					array2 = array;
				}
				else
				{
					array2 = new IGrouping<string, (string, int)>[1];
				}
				array = array2;
				foreach (IGrouping<string, (string, int)> grouping in array)
				{
					string text = grouping?.Key;
					string label = Label(item2.Key, "") + ((text != null) ? (" + " + Label(text, "")) : "");
					list.Add(new Variant
					{
						Kind = VariantKind.Equipment,
						Prefab = variantInfo.Prefab,
						Label = label,
						IconItem = ((text != null) ? Icon(text) : Icon(item2.Key)),
						WeaponIndices = item2.Select<(string, int), int>(((string n, int i) x) => x.i).ToArray(),
						ShieldIndices = (grouping?.Select<(string, int), int>(((string n, int i) x) => x.i).ToArray() ?? Array.Empty<int>())
					});
				}
			}
			return list;
		}

		private static string Label(string key, string fallback)
		{
			if (!Text.Has(key))
			{
				if (fallback.Length <= 0)
				{
					return key;
				}
				return fallback;
			}
			return Text.Get(key);
		}

		private static string? Icon(string key)
		{
			if (!Icons.TryGetValue(key, out string value))
			{
				return null;
			}
			return value;
		}
	}
}
namespace BetterSummon.Config
{
	internal sealed class ModConfig
	{
		public readonly ConfigEntry<bool> Enabled;

		public readonly ConfigEntry<bool> Debug;

		public readonly ConfigEntry<bool> LogCatalog;

		public readonly ConfigEntry<string> Language;

		public readonly ConfigEntry<bool> Formation;

		public readonly ConfigEntry<float> FormationRadius;

		public readonly ConfigEntry<float> FormationSpacing;

		public readonly ConfigEntry<float> HoldSeconds;

		public readonly ConfigEntry<string> NextVariantButton;

		public readonly ConfigEntry<bool> CastOnSelect;

		public readonly ConfigEntry<float> GuardLeashDistance;

		public readonly ConfigEntry<float> GuardStopMargin;

		public ModConfig(ConfigFile file)
		{
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Expected O, but got Unknown
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Expected O, but got Unknown
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Expected O, but got Unknown
			//IL_015f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0169: Expected O, but got Unknown
			//IL_01d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01de: Expected O, but got Unknown
			//IL_020d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0217: Expected O, but got Unknown
			Enabled = file.Bind<bool>("General", "Enabled", true, "Enable the mod.");
			Debug = file.Bind<bool>("General", "Debug", false, "Verbose logging in LogOutput.log (no effect in game).");
			LogCatalog = file.Bind<bool>("General", "LogCatalog", true, "On world load, write the list of summoning staves and their variants (game data) to LogOutput.log.");
			Language = file.Bind<string>("General", "Language", "auto", new ConfigDescription("Language of the wheel and messages.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[3] { "auto", "english", "french" }), Array.Empty<object>()));
			Formation = file.Bind<bool>("Formation", "Enabled", true, "Following summons walk in an arc behind you instead of converging on your position (no more shoving).");
			FormationRadius = file.Bind<float>("Formation", "Radius", 3.5f, new ConfigDescription("Distance of the arc behind the player, in meters (grows with creature size).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 10f), Array.Empty<object>()));
			FormationSpacing = file.Bind<float>("Formation", "Spacing", 2f, new ConfigDescription("Minimum space between two neighbouring summons, in meters.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 5f), Array.Empty<object>()));
			HoldSeconds = file.Bind<float>("Wheel", "HoldSeconds", 1f, new ConfigDescription("How long to hold left click (summoning staff in hand) to open the wheel. A shorter click summons.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.3f, 3f), Array.Empty<object>()));
			NextVariantButton = file.Bind<string>("Wheel", "NextVariantButton", "Run", "Game button to hold with the click to summon the next variant (ZInput name: Run = Shift, Crouch = Ctrl...).");
			CastOnSelect = file.Bind<bool>("Wheel", "CastOnSelect", true, "Picking a variant in the wheel casts the summon right away.");
			GuardLeashDistance = file.Bind<float>("Guard", "LeashDistance", 40f, new ConfigDescription("Guard order: summons stay put while you are within this distance; beyond it they walk towards you until back in range, without coming all the way.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 150f), Array.Empty<object>()));
			GuardStopMargin = file.Bind<float>("Guard", "StopMargin", 3f, new ConfigDescription("Margin inside the leash at which they stop (avoids bouncing on the edge).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 10f), Array.Empty<object>()));
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}