Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of BetterSummon v1.0.1
plugins/BetterSummon/BetterSummon.dll
Decompiled 2 weeks agousing 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) { } } }