Decompiled source of MagicStaffTweaker v2.3.4

MagicStaffTweaker.Core.dll

Decompiled 4 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using Microsoft.CodeAnalysis;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("MagicStaffTweaker.Core")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("MagicStaffTweaker.Core")]
[assembly: AssemblyTitle("MagicStaffTweaker.Core")]
[assembly: AssemblyVersion("1.0.0.0")]
[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 MagicStaffTweaker.Core
{
	public static class Modifier
	{
		public static float Apply(float vanilla, ModifierMode mode, float value)
		{
			return mode switch
			{
				ModifierMode.Vanilla => vanilla, 
				ModifierMode.Add => vanilla + value, 
				ModifierMode.Multiply => vanilla * value, 
				ModifierMode.Override => value, 
				_ => vanilla, 
			};
		}

		public static int Apply(int vanilla, ModifierMode mode, int value)
		{
			return mode switch
			{
				ModifierMode.Vanilla => vanilla, 
				ModifierMode.Add => vanilla + value, 
				ModifierMode.Multiply => (int)MathF.Round(vanilla * value), 
				ModifierMode.Override => value, 
				_ => vanilla, 
			};
		}
	}
	public sealed class StaffDefinition
	{
		public string Prefab { get; }

		public string DisplayName { get; }

		public StaffMagicType MagicType { get; }

		public StaffFeatures Features { get; }

		public StaffDefinition(string prefab, string displayName, StaffMagicType magicType, StaffFeatures features)
		{
			Prefab = prefab;
			DisplayName = displayName;
			MagicType = magicType;
			Features = features;
		}
	}
	[Flags]
	public enum StaffFeatures
	{
		None = 0,
		Projectile = 1,
		Explosion = 2,
		Summon = 4,
		Shield = 8,
		Area = 0x10,
		Chain = 0x20,
		SecondaryAttack = 0x40,
		BloodMagic = 0x80,
		ElementalMagic = 0x100
	}
	public enum StaffMagicType
	{
		Elemental,
		Blood
	}
	public enum ModifierMode
	{
		Vanilla,
		Add,
		Multiply,
		Override
	}
	public static class VanillaStaffs
	{
		public static readonly IReadOnlyList<StaffDefinition> All = new StaffDefinition[12]
		{
			new StaffDefinition("StaffFireball", "Staff of Embers", StaffMagicType.Elemental, StaffFeatures.Projectile | StaffFeatures.Explosion | StaffFeatures.ElementalMagic),
			new StaffDefinition("StaffIceShards", "Staff of Frost", StaffMagicType.Elemental, StaffFeatures.Projectile | StaffFeatures.ElementalMagic),
			new StaffDefinition("StaffClusterbomb", "Staff of Fracturing", StaffMagicType.Elemental, StaffFeatures.Projectile | StaffFeatures.Explosion | StaffFeatures.ElementalMagic),
			new StaffDefinition("StaffGreenRoots", "Staff of the Wild", StaffMagicType.Elemental, StaffFeatures.Projectile | StaffFeatures.Summon | StaffFeatures.Area | StaffFeatures.ElementalMagic),
			new StaffDefinition("StaffLightning", "Dundr", StaffMagicType.Elemental, StaffFeatures.Projectile | StaffFeatures.Chain | StaffFeatures.ElementalMagic),
			new StaffDefinition("StaffOrbofAhri", "Echo Spike", StaffMagicType.Blood, StaffFeatures.Projectile | StaffFeatures.Explosion | StaffFeatures.BloodMagic),
			new StaffDefinition("StaffSpiritCaller", "Spirit Caller", StaffMagicType.Blood, StaffFeatures.Projectile | StaffFeatures.Summon | StaffFeatures.BloodMagic),
			new StaffDefinition("StaffFrostOrbs", "Northern Vengeance", StaffMagicType.Blood, StaffFeatures.Projectile | StaffFeatures.Explosion | StaffFeatures.BloodMagic),
			new StaffDefinition("StaffThunderBlood", "Lightning Strike", StaffMagicType.Elemental, StaffFeatures.Area | StaffFeatures.ElementalMagic),
			new StaffDefinition("StaffShield", "Staff of Protection", StaffMagicType.Blood, StaffFeatures.Shield | StaffFeatures.Area | StaffFeatures.BloodMagic),
			new StaffDefinition("StaffSkeleton", "Dead Raiser", StaffMagicType.Blood, StaffFeatures.Summon | StaffFeatures.BloodMagic),
			new StaffDefinition("StaffRedTroll", "Trollstav", StaffMagicType.Blood, StaffFeatures.Summon | StaffFeatures.BloodMagic)
		};
	}
}

MagicStaffTweakerServer.dll

Decompiled 4 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Jotunn.Extensions;
using Jotunn.Managers;
using Jotunn.Utils;
using MagicStaffTweaker.Core;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("MagicStaffTweakerServer")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("2.3.4.0")]
[assembly: AssemblyInformationalVersion("2.3.4")]
[assembly: AssemblyProduct("MagicStaffTweakerServer")]
[assembly: AssemblyTitle("MagicStaffTweakerServer")]
[assembly: AssemblyVersion("2.3.4.0")]
[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 MagicStaffTweaker.Server
{
	public static class ConfigPersistence
	{
		public static string Key(string section, string key)
		{
			return section + "\n" + key;
		}

		public static HashSet<string> ReadKeys(string text)
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			string section = string.Empty;
			using StringReader stringReader = new StringReader(text);
			string text2;
			while ((text2 = stringReader.ReadLine()) != null)
			{
				text2 = text2.Trim();
				if (text2.Length == 0 || text2.StartsWith("#", StringComparison.Ordinal))
				{
					continue;
				}
				if (text2.StartsWith("[", StringComparison.Ordinal) && text2.EndsWith("]", StringComparison.Ordinal))
				{
					section = text2.Substring(1, text2.Length - 2);
					continue;
				}
				int num = text2.IndexOf('=');
				if (num > 0)
				{
					hashSet.Add(Key(section, text2.Substring(0, num).Trim()));
				}
			}
			return hashSet;
		}
	}
	public static class DamageTuning
	{
		public static float SpellFactor(ModifierMode mode, float value)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			float num = Modifier.Apply(1f, mode, value);
			if (!float.IsNaN(num) && !float.IsInfinity(num) && !(num < 0f))
			{
				return num;
			}
			return 1f;
		}

		public static bool ValidHealth(float value)
		{
			if (value > 0f)
			{
				return !float.IsInfinity(value);
			}
			return false;
		}

		public static bool ValidCooldown(float value)
		{
			if (value >= 0f)
			{
				return !float.IsInfinity(value);
			}
			return false;
		}
	}
	public static class HealthDefaults
	{
		private static readonly FieldInfo? DefaultField = typeof(ConfigEntryBase).GetField("<DefaultValue>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic);

		public static void Apply(ConfigEntry<float> entry, float vanilla)
		{
			if (DamageTuning.ValidHealth(vanilla))
			{
				DefaultField?.SetValue(entry, vanilla);
				if (!DamageTuning.ValidHealth(entry.Value))
				{
					entry.Value = vanilla;
				}
			}
		}

		public static void SetDefault<T>(ConfigEntry<T> entry, T vanilla)
		{
			DefaultField?.SetValue(entry, vanilla);
		}
	}
	[BepInPlugin("com.magicstafftweaker.server", "Magic Staff Tweaker Server", "2.3.4")]
	[BepInDependency("com.jotunn.jotunn", "2.30.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		[HarmonyPatch(typeof(StatusEffect), "SetLevel")]
		private static class NorthernVengeanceDurationPatch
		{
			private static void Postfix(StatusEffect __instance, int itemLevel)
			{
				Plugin instance = _instance;
				if (!((Object)(object)instance == (Object)null) && !((Object)(object)__instance == (Object)null) && IsVengeanceSphere(__instance) && instance._configs.TryGetValue("StaffFrostOrbs", out ServerStaffConfig value) && value.VengeanceReflectionHeight.IntendedVisible)
				{
					float num = Mathf.Clamp(value.VengeanceReflectionHeight.Value.Value, 0.1f, 3f);
					__instance.m_ttl = 60f * (float)Mathf.Max(1, itemLevel) * num;
				}
			}
		}

		[HarmonyPatch(typeof(Character), "Start")]
		private static class SpiritCallerStarPatch
		{
			private static void Postfix(Character __instance)
			{
				Plugin instance = _instance;
				if (!((Object)(object)instance == (Object)null) && IsSpiritCallerAnimal(__instance))
				{
					((MonoBehaviour)instance).StartCoroutine(ApplySpiritCallerStarAfterSpawn(__instance));
				}
			}
		}

		private sealed class SummonWeaponDamageBaseline
		{
			public readonly DamageTypes Damages;

			public readonly DamageTypes DamagesPerLevel;

			public SummonWeaponDamageBaseline(ItemData item)
			{
				//IL_000d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0012: Unknown result type (might be due to invalid IL or missing references)
				//IL_001e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0023: Unknown result type (might be due to invalid IL or missing references)
				Damages = item.m_shared.m_damages;
				DamagesPerLevel = item.m_shared.m_damagesPerLevel;
			}
		}

		[HarmonyPatch(typeof(Attack), "ModifyDamage", new Type[]
		{
			typeof(HitData),
			typeof(float)
		})]
		private static class StaffDamagePatch
		{
			private static void Postfix(HitData hitData, Humanoid ___m_character, ItemData ___m_weapon)
			{
				//IL_002d: Unknown result type (might be due to invalid IL or missing references)
				if (hitData != null && TryGetStaffConfig((Character)(object)___m_character, ___m_weapon, out ServerStaffConfig cfg) && cfg.DamageMultiplier.IntendedVisible)
				{
					((DamageTypes)(ref hitData.m_damage)).Modify(DamageTuning.SpellFactor(cfg.DamageMultiplier.Mode.Value, cfg.DamageMultiplier.Value.Value));
				}
			}
		}

		private sealed class AoeFactor
		{
			public float Value = 1f;
		}

		[HarmonyPatch(typeof(Aoe), "Setup", new Type[]
		{
			typeof(Character),
			typeof(Vector3),
			typeof(float),
			typeof(HitData),
			typeof(ItemData),
			typeof(ItemData)
		})]
		private static class StaffAoeSetupPatch
		{
			private static void Postfix(Aoe __instance, Character owner, HitData hitData, ItemData item)
			{
				//IL_0067: Unknown result type (might be due to invalid IL or missing references)
				AoeFactor value = AoeFactors.GetValue(__instance, (Aoe _) => new AoeFactor());
				value.Value = 1f;
				if ((!__instance.m_useAttackSettings || hitData == null) && TryGetStaffConfig(owner, item, out ServerStaffConfig cfg) && cfg.DamageMultiplier.IntendedVisible)
				{
					value.Value = DamageTuning.SpellFactor(cfg.DamageMultiplier.Mode.Value, cfg.DamageMultiplier.Value.Value);
				}
			}
		}

		[HarmonyPatch(typeof(Aoe), "GetDamage", new Type[] { typeof(int) })]
		private static class StaffAoeDamagePatch
		{
			private static void Postfix(Aoe __instance, ref DamageTypes __result)
			{
				if (AoeFactors.TryGetValue(__instance, out AoeFactor value))
				{
					((DamageTypes)(ref __result)).Modify(value.Value);
				}
			}
		}

		[HarmonyPatch(typeof(SpawnAbility), "Setup", new Type[]
		{
			typeof(Character),
			typeof(Vector3),
			typeof(float),
			typeof(HitData),
			typeof(ItemData),
			typeof(ItemData)
		})]
		private static class StaffSummonSetupPatch
		{
			private static void Prefix(SpawnAbility __instance, Character owner, ItemData item)
			{
				if (TryGetStaffConfig(owner, item, out ServerStaffConfig cfg) && cfg.SummonsPerCast.IntendedVisible)
				{
					bool flag = cfg.IgnoreSummonLimit.IntendedVisible && cfg.IgnoreSummonLimit.Value.Value;
					SummonControls.ApplyToInstance(__instance, cfg.SummonsPerCast.Value.Value, flag);
					if (cfg.SummonSpawnRadius.IntendedVisible)
					{
						__instance.m_spawnRadius = cfg.SummonSpawnRadius.Value.Value;
					}
					if (!flag && cfg.SummonMaxSpawns.IntendedVisible && cfg.SummonMaxSpawns.Value.Value > 0)
					{
						__instance.m_maxSpawned = cfg.SummonMaxSpawns.Value.Value;
					}
				}
			}
		}

		[HarmonyPatch(typeof(Humanoid), "StartAttack", new Type[]
		{
			typeof(Character),
			typeof(bool)
		})]
		private static class StaffCastStartPatch
		{
			private static void Postfix(Humanoid __instance, ref bool __result)
			{
				if (__result && TryGetCastSpeed(__instance.GetCurrentWeapon(), out var speed))
				{
					object obj = AccessTools.Field(typeof(Character), "m_zanim")?.GetValue(__instance);
					if (obj != null)
					{
						AccessTools.Method(obj.GetType(), "SetSpeed", new Type[1] { typeof(float) }, (Type[])null)?.Invoke(obj, new object[1] { speed });
					}
				}
			}
		}

		[HarmonyPatch(typeof(Humanoid), "UpdateAttack")]
		private static class StaffCastUpdatePatch
		{
			private static void Prefix(Humanoid __instance, ref float dt)
			{
				if (TryGetCastSpeed(__instance.GetCurrentWeapon(), out var speed))
				{
					dt *= speed;
				}
			}
		}

		[HarmonyPatch(typeof(Tameable), "UnsummonMaxInstances", new Type[] { typeof(int) })]
		private static class DeadRaiserTameableLimitPatch
		{
			private static void Prefix(Tameable __instance, ref int maxInstances)
			{
				Plugin instance = _instance;
				object obj;
				if (__instance == null)
				{
					obj = null;
				}
				else
				{
					GameObject gameObject = ((Component)__instance).gameObject;
					obj = ((gameObject != null) ? gameObject.GetComponent<Character>() : null);
				}
				Character val = (Character)obj;
				string text = (((Object)(object)val != (Object)null) ? GetSummonStaffPrefab(val) : null);
				if (!((Object)(object)instance == (Object)null) && text != null && instance._configs.TryGetValue(text, out ServerStaffConfig value) && value.IgnoreSummonLimit.IntendedVisible && value.IgnoreSummonLimit.Value.Value)
				{
					maxInstances = Math.Max(1, Math.Max(value.SummonsPerCast.Value.Value, value.SummonMaxSpawns.Value.Value));
				}
			}
		}

		[HarmonyPatch(typeof(Character), "Damage")]
		private static class SummonFinalDamagePatch
		{
			private static void Prefix(HitData hit)
			{
				Plugin instance = _instance;
				if ((Object)(object)instance == (Object)null || hit == null)
				{
					return;
				}
				Character attacker = hit.GetAttacker();
				if ((Object)(object)attacker == (Object)null)
				{
					return;
				}
				string summonStaffPrefab = GetSummonStaffPrefab(attacker);
				if (summonStaffPrefab == null || !instance._configs.TryGetValue(summonStaffPrefab, out ServerStaffConfig value))
				{
					return;
				}
				try
				{
					ApplyFinalSummonDamage(hit, value);
				}
				catch (Exception arg)
				{
					Debug.LogError((object)$"[MagicStaffTweaker] Final summon damage patch failed: {arg}");
				}
			}
		}

		[HarmonyPatch(typeof(Humanoid), "EquipItem")]
		private static class SummonWeaponDamagePatch
		{
			private static void Postfix(Humanoid __instance, ItemData item, bool __result)
			{
				if (!__result || item == null || (Object)(object)__instance == (Object)null || !IsDeadRaiserCreature(__instance))
				{
					return;
				}
				Plugin instance = _instance;
				if ((Object)(object)instance == (Object)null || !instance._configs.TryGetValue("StaffSkeleton", out ServerStaffConfig value))
				{
					return;
				}
				try
				{
					ApplySummonWeaponDamage(item, value);
				}
				catch (Exception arg)
				{
					Debug.LogError((object)$"[MagicStaffTweakerServer] Summon weapon damage patch failed: {arg}");
				}
			}

			private static bool IsDeadRaiserCreature(Humanoid humanoid)
			{
				string name = ((Object)((Component)humanoid).gameObject).name;
				if (!name.Contains("Skeleton_Friendly", StringComparison.Ordinal))
				{
					return name.Contains("SkeletonArcher_Friendly", StringComparison.Ordinal);
				}
				return true;
			}
		}

		private sealed class ServerBaseline
		{
			public float MaxDurability;

			public float DurabilityPerLevel;

			public bool UseDurability;

			public float MovementModifier;

			public float BlockPower;

			public float BlockPowerPerLevel;

			public float DeflectionForce;

			public float DeflectionForcePerLevel;

			public float TimedBlockBonus;

			public float BlockStaminaUseFlatValue;

			public float BlockStaminaUseModifier;

			public ServerAttackBaseline Primary = new ServerAttackBaseline();

			public ServerAttackBaseline Secondary = new ServerAttackBaseline();

			public static ServerBaseline Capture(GameObject prefab)
			{
				ServerBaseline serverBaseline = new ServerBaseline();
				SharedData val = prefab.GetComponent<ItemDrop>()?.m_itemData?.m_shared;
				if (val == null)
				{
					return serverBaseline;
				}
				serverBaseline.MaxDurability = GetFloat(val, "m_maxDurability");
				serverBaseline.DurabilityPerLevel = GetFloat(val, "m_durabilityPerLevel");
				serverBaseline.UseDurability = GetBool(val, "m_useDurability");
				serverBaseline.MovementModifier = GetFloat(val, "m_movementModifier");
				serverBaseline.BlockPower = GetFloat(val, "m_blockPower");
				serverBaseline.BlockPowerPerLevel = GetFloat(val, "m_blockPowerPerLevel");
				serverBaseline.DeflectionForce = GetFloat(val, "m_deflectionForce");
				serverBaseline.DeflectionForcePerLevel = GetFloat(val, "m_deflectionForcePerLevel");
				serverBaseline.TimedBlockBonus = GetFloat(val, "m_timedBlockBonus");
				serverBaseline.BlockStaminaUseFlatValue = GetFloat(val, "m_blockStaminaUseFlatValue");
				serverBaseline.BlockStaminaUseModifier = GetFloat(val, "m_blockStaminaUseModifier");
				serverBaseline.Primary = ServerAttackBaseline.Capture(val.m_attack, prefab);
				serverBaseline.Secondary = ServerAttackBaseline.Capture(val.m_secondaryAttack, prefab);
				return serverBaseline;
			}
		}

		private sealed class ServerAttackBaseline
		{
			public float AttackEitr;

			public float HealthPercent;

			public float Cooldown;

			public float AttackForce;

			public float AttackForceForward;

			public float ProjectileAccuracyMin;

			public float ProjectileVelocity;

			public int Projectiles;

			public float DrawTime;

			public float AttackWarmup;

			public float AttackAnimationSpeedFactor;

			public float ChainChance;

			public float ChainChancePerTarget;

			public int ChainMaxTargets;

			public float ChainDelay;

			public float ChainStartChance;

			public float ChainStartChanceFalloff;

			public float ChainStartDelay;

			public int ChainMinTargets;

			public float ChainAttackMaxTime;

			public int AttackChainLevels;

			public readonly List<GraphFieldBaseline> GraphFields = new List<GraphFieldBaseline>();

			public readonly List<GraphFieldBaseline> SummonGraphFields = new List<GraphFieldBaseline>();

			public static ServerAttackBaseline Capture(Attack? attack, GameObject staffPrefab = null)
			{
				ServerAttackBaseline serverAttackBaseline = new ServerAttackBaseline();
				if (attack == null)
				{
					return serverAttackBaseline;
				}
				serverAttackBaseline.AttackEitr = GetFloat(attack, "m_attackEitr");
				serverAttackBaseline.HealthPercent = GetFloat(attack, "m_attackHealthPercentage");
				serverAttackBaseline.Cooldown = GetFloat(attack, "m_attackCooldown");
				serverAttackBaseline.AttackForce = GetFloat(attack, "m_attackForce");
				serverAttackBaseline.AttackForceForward = GetFloat(attack, "m_attackForceForward");
				serverAttackBaseline.ProjectileAccuracyMin = GetFloat(attack, "m_projectileAccuracyMin");
				serverAttackBaseline.ProjectileVelocity = GetFloat(attack, "m_projectileVel");
				serverAttackBaseline.Projectiles = GetInt(attack, "m_projectiles");
				serverAttackBaseline.DrawTime = GetFloat(attack, "m_attackDrawTime");
				serverAttackBaseline.AttackWarmup = GetFloat(attack, "m_attackWarmup");
				serverAttackBaseline.AttackAnimationSpeedFactor = GetFloat(attack, "m_attackAnimationSpeedFactor");
				serverAttackBaseline.ChainChance = GetFloat(attack, "m_chainChance");
				serverAttackBaseline.ChainChancePerTarget = GetFloat(attack, "m_chainChancePerTarget");
				serverAttackBaseline.ChainMaxTargets = GetInt(attack, "m_chainMaxTargets");
				serverAttackBaseline.ChainDelay = GetFloat(attack, "m_chainDelay");
				serverAttackBaseline.ChainStartChance = GetFloat(attack, "m_chainStartChance");
				serverAttackBaseline.ChainStartChanceFalloff = GetFloat(attack, "m_chainStartChanceFalloff");
				serverAttackBaseline.ChainStartDelay = GetFloat(attack, "m_chainStartDelay");
				serverAttackBaseline.ChainMinTargets = GetInt(attack, "m_chainMinTargets");
				serverAttackBaseline.ChainAttackMaxTime = GetFloat(attack, "m_chainAttackMaxTime");
				serverAttackBaseline.AttackChainLevels = GetInt(attack, "m_attackChainLevels");
				serverAttackBaseline.GraphFields.AddRange(CaptureGraphFields(attack, staffPrefab));
				serverAttackBaseline.SummonGraphFields.AddRange(CaptureSummonGraphFields(attack));
				return serverAttackBaseline;
			}

			private static List<GraphFieldBaseline> CaptureSummonGraphFields(Attack attack)
			{
				List<GraphFieldBaseline> list = new List<GraphFieldBaseline>();
				HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
				Queue<(GameObject, int)> queue = new Queue<(GameObject, int)>();
				HashSet<int> hashSet2 = new HashSet<int>();
				FieldInfo[] fields = ((object)attack).GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (FieldInfo fieldInfo in fields)
				{
					if (typeof(GameObject).IsAssignableFrom(fieldInfo.FieldType))
					{
						object? value = fieldInfo.GetValue(attack);
						GameObject val = (GameObject)((value is GameObject) ? value : null);
						if (val != null && (Object)(object)val != (Object)null)
						{
							queue.Enqueue((val, 0));
						}
					}
				}
				while (queue.Count > 0)
				{
					var (val2, num) = queue.Dequeue();
					if ((Object)(object)val2 == (Object)null || !hashSet2.Add(((Object)val2).GetInstanceID()))
					{
						continue;
					}
					Component[] componentsInChildren = val2.GetComponentsInChildren<Component>(true);
					foreach (Component val3 in componentsInChildren)
					{
						if ((Object)(object)val3 == (Object)null)
						{
							continue;
						}
						string name = ((object)val3).GetType().Name;
						if (name != "Character" && name != "Humanoid" && name != "Attack" && name != "SpawnAbility" && name != "ItemDrop")
						{
							continue;
						}
						if (name == "ItemDrop")
						{
							ItemDrop val4 = (ItemDrop)(object)((val3 is ItemDrop) ? val3 : null);
							if (val4 != null && val4.m_itemData != null && val4.m_itemData.m_shared != null)
							{
								SharedData shared = val4.m_itemData.m_shared;
								FieldInfo field = ((object)shared).GetType().GetField("m_aiAttackInterval", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
								if (field != null && IsNumeric(field.FieldType))
								{
									object value2 = field.GetValue(shared);
									string item = ((Object)val3).GetInstanceID() + ":SharedData:m_aiAttackInterval";
									if (value2 != null && hashSet.Add(item))
									{
										list.Add(new GraphFieldBaseline(shared, field, value2));
									}
								}
								continue;
							}
						}
						fields = ((object)val3).GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
						foreach (FieldInfo fieldInfo2 in fields)
						{
							if (!IsSummonField(name, fieldInfo2.Name) || !IsNumeric(fieldInfo2.FieldType))
							{
								continue;
							}
							object value3 = fieldInfo2.GetValue(val3);
							if (value3 != null)
							{
								string item2 = ((Object)val3).GetInstanceID() + ":" + fieldInfo2.Name;
								if (hashSet.Add(item2))
								{
									list.Add(new GraphFieldBaseline(val3, fieldInfo2, value3));
								}
							}
						}
					}
					if (num >= 3)
					{
						continue;
					}
					componentsInChildren = val2.GetComponentsInChildren<Component>(true);
					foreach (Component val5 in componentsInChildren)
					{
						if ((Object)(object)val5 == (Object)null)
						{
							continue;
						}
						fields = ((object)val5).GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
						foreach (FieldInfo fieldInfo3 in fields)
						{
							if (typeof(GameObject).IsAssignableFrom(fieldInfo3.FieldType))
							{
								object? value4 = fieldInfo3.GetValue(val5);
								GameObject val6 = (GameObject)((value4 is GameObject) ? value4 : null);
								if (val6 != null && (Object)(object)val6 != (Object)null)
								{
									queue.Enqueue((val6, num + 1));
								}
							}
							else
							{
								if (!fieldInfo3.FieldType.IsArray || !typeof(GameObject).IsAssignableFrom(fieldInfo3.FieldType.GetElementType()) || !(fieldInfo3.GetValue(val5) is Array array))
								{
									continue;
								}
								foreach (object item3 in array)
								{
									GameObject val7 = (GameObject)((item3 is GameObject) ? item3 : null);
									if (val7 != null && (Object)(object)val7 != (Object)null)
									{
										queue.Enqueue((val7, num + 1));
									}
								}
							}
						}
					}
				}
				return list;
			}

			private static bool IsSummonField(string typeName, string fieldName)
			{
				switch (typeName)
				{
				case "Character":
					return fieldName == "m_health";
				case "Humanoid":
					if (!(fieldName == "m_health") && !(fieldName == "m_walkSpeed"))
					{
						return fieldName == "m_runSpeed";
					}
					return true;
				case "Attack":
					switch (fieldName)
					{
					default:
						return fieldName == "m_attackAnimationSpeedFactor";
					case "m_damageMultiplier":
					case "m_attackCooldown":
					case "m_attackDrawTime":
						return true;
					}
				case "SpawnAbility":
					if (!(fieldName == "m_maxSpawned"))
					{
						return fieldName == "m_spawnRadius";
					}
					return true;
				case "ItemDrop":
					return fieldName == "m_aiAttackInterval";
				default:
					return false;
				}
			}

			private static List<GraphFieldBaseline> CaptureGraphFields(Attack attack, GameObject staffPrefab)
			{
				List<GraphFieldBaseline> list = new List<GraphFieldBaseline>();
				HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
				Queue<(GameObject, int)> queue = new Queue<(GameObject, int)>();
				HashSet<int> hashSet2 = new HashSet<int>();
				if ((Object)(object)staffPrefab != (Object)null)
				{
					queue.Enqueue((staffPrefab, 0));
				}
				FieldInfo[] fields = ((object)attack).GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (FieldInfo fieldInfo in fields)
				{
					if (typeof(GameObject).IsAssignableFrom(fieldInfo.FieldType))
					{
						object? value = fieldInfo.GetValue(attack);
						GameObject val = (GameObject)((value is GameObject) ? value : null);
						if (val != null && (Object)(object)val != (Object)null)
						{
							queue.Enqueue((val, 0));
						}
					}
				}
				while (queue.Count > 0)
				{
					var (val2, num) = queue.Dequeue();
					if ((Object)(object)val2 == (Object)null || !hashSet2.Add(((Object)val2).GetInstanceID()))
					{
						continue;
					}
					Component[] componentsInChildren = val2.GetComponentsInChildren<Component>(true);
					foreach (Component val3 in componentsInChildren)
					{
						if ((Object)(object)val3 == (Object)null)
						{
							continue;
						}
						string name = ((object)val3).GetType().Name;
						if ((name != "Projectile" && name != "Aoe" && name != "EffectArea" && name != "StatusEffect" && name != "SE_Shield" && name != "ShieldDome") || (name == "Attack" && (object)val3 == attack))
						{
							continue;
						}
						fields = ((object)val3).GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
						foreach (FieldInfo fieldInfo2 in fields)
						{
							if (!IsGraphField(name, fieldInfo2.Name) || !IsNumeric(fieldInfo2.FieldType))
							{
								continue;
							}
							object value2 = fieldInfo2.GetValue(val3);
							if (value2 != null)
							{
								string item = ((Object)val3).GetInstanceID() + ":" + fieldInfo2.Name;
								if (hashSet.Add(item))
								{
									list.Add(new GraphFieldBaseline(val3, fieldInfo2, value2));
								}
							}
						}
					}
					if (num >= 2)
					{
						continue;
					}
					componentsInChildren = val2.GetComponentsInChildren<Component>(true);
					foreach (Component val4 in componentsInChildren)
					{
						if ((Object)(object)val4 == (Object)null)
						{
							continue;
						}
						fields = ((object)val4).GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
						foreach (FieldInfo fieldInfo3 in fields)
						{
							if (typeof(GameObject).IsAssignableFrom(fieldInfo3.FieldType))
							{
								object? value3 = fieldInfo3.GetValue(val4);
								GameObject val5 = (GameObject)((value3 is GameObject) ? value3 : null);
								if (val5 != null && (Object)(object)val5 != (Object)null)
								{
									queue.Enqueue((val5, num + 1));
								}
							}
							else
							{
								if (!fieldInfo3.FieldType.IsArray || !typeof(GameObject).IsAssignableFrom(fieldInfo3.FieldType.GetElementType()) || !(fieldInfo3.GetValue(val4) is Array array))
								{
									continue;
								}
								foreach (object item2 in array)
								{
									GameObject val6 = (GameObject)((item2 is GameObject) ? item2 : null);
									if (val6 != null && (Object)(object)val6 != (Object)null)
									{
										queue.Enqueue((val6, num + 1));
									}
								}
							}
						}
					}
				}
				return list;
			}

			private static bool IsGraphField(string typeName, string fieldName)
			{
				switch (typeName)
				{
				case "Projectile":
					switch (fieldName)
					{
					default:
						return fieldName == "m_forceStagger";
					case "m_ttl":
					case "m_ttlMax":
					case "m_projectileVelocity":
					case "m_projectileVelocityMax":
					case "m_maxBounces":
					case "m_damageMultiplier":
					case "m_damage":
					case "m_damagePerHit":
					case "m_force":
						return true;
					}
				case "Aoe":
				case "EffectArea":
					switch (fieldName)
					{
					default:
						return fieldName == "m_damagePerHit";
					case "m_radius":
					case "m_force":
					case "m_forceStagger":
					case "m_damage":
						return true;
					}
				case "StatusEffect":
					if (!(fieldName == "m_duration"))
					{
						return fieldName == "m_ttl";
					}
					return true;
				case "SE_Shield":
					if (!(fieldName == "m_ttl") && !(fieldName == "m_absorbDamage"))
					{
						return fieldName == "m_absorbDamagePerSkillLevel";
					}
					return true;
				case "ShieldDome":
					if (!(fieldName == "m_wrapSphereRadius"))
					{
						return fieldName == "m_reflectionHeight";
					}
					return true;
				default:
					return false;
				}
			}

			private static bool IsNumeric(Type type)
			{
				if (!(type == typeof(float)) && !(type == typeof(double)) && !(type == typeof(int)))
				{
					return type == typeof(long);
				}
				return true;
			}
		}

		private sealed class GraphFieldBaseline
		{
			public readonly object Target;

			public readonly FieldInfo Field;

			public readonly object VanillaValue;

			public string ComponentTypeName
			{
				get
				{
					if (!(Target is Character) || !(FieldName == "m_health"))
					{
						return Target.GetType().Name;
					}
					return "Character";
				}
			}

			public string FieldName => Field.Name;

			public GraphFieldBaseline(object target, FieldInfo field, object vanillaValue)
			{
				Target = target;
				Field = field;
				VanillaValue = vanillaValue;
			}
		}

		private sealed class ServerStaffConfig
		{
			public readonly ServerTuning<float> Durability;

			public readonly ServerTuning<float> DurabilityPerLevel;

			public readonly ServerTuning<bool> UseDurability;

			public readonly ServerTuning<float> MovementModifier;

			public readonly ServerTuning<float> BlockPower;

			public readonly ServerTuning<float> BlockPowerPerLevel;

			public readonly ServerTuning<float> DeflectionForce;

			public readonly ServerTuning<float> DeflectionForcePerLevel;

			public readonly ServerTuning<float> TimedBlockBonus;

			public readonly ServerTuning<float> BlockStaminaUseFlatValue;

			public readonly ServerTuning<float> BlockStaminaUseModifier;

			public readonly ServerTuning<float> EitrCost;

			public readonly ServerTuning<float> HealthCostPercent;

			public readonly ServerTuning<float> Cooldown;

			public readonly ServerTuning<float> AttackForce;

			public readonly ServerTuning<float> AttackForceForward;

			public readonly ServerTuning<float> ProjectileAccuracyMin;

			public readonly ServerTuning<float> ProjectileSpeed;

			public readonly ServerTuning<float> ProjectileLifetime;

			public readonly ServerTuning<float> ProjectileVelocity;

			public readonly ServerTuning<float> ProjectileVelocityMax;

			public readonly ServerTuning<int> ProjectileMaxBounces;

			public readonly ServerTuning<int> ProjectileCount;

			public readonly ServerTuning<float> DrawTime;

			public readonly ServerTuning<float> ChainChance;

			public readonly ServerTuning<float> ChainChancePerTarget;

			public readonly ServerTuning<int> ChainMaxTargets;

			public readonly ServerTuning<float> ChainDelay;

			public readonly ServerTuning<float> ChainStartChance;

			public readonly ServerTuning<float> ChainStartChanceFalloff;

			public readonly ServerTuning<float> ChainStartDelay;

			public readonly ServerTuning<int> ChainMinTargets;

			public readonly ServerTuning<float> ChainAttackMaxTime;

			public readonly ServerTuning<int> AttackChainLevels;

			public readonly ServerTuning<float> ExplosionRadius;

			public readonly ServerTuning<float> Knockback;

			public readonly ServerTuning<float> StatusEffectDuration;

			public readonly ServerTuning<float> ShieldAbsorption;

			public readonly ServerTuning<float> ShieldAbsorptionPerBloodMagicLevel;

			public readonly ServerTuning<float> VengeanceSphereRadius;

			public readonly ServerTuning<float> VengeanceReflectionHeight;

			public readonly ServerTuning<float> SummonHealth;

			public readonly ServerTuning<float> SummonWalkSpeed;

			public readonly ServerTuning<float> SummonRunSpeed;

			public readonly ServerTuning<float> SummonMeleeDamageMultiplier;

			public readonly ServerTuning<float> SummonArcherDamageMultiplier;

			public readonly ServerTuning<float> SummonDamagePerLevelMultiplier;

			public readonly ServerTuning<float> SummonDamageMultiplier;

			public readonly ServerTuning<float> SummonFinalDamageMultiplier;

			public readonly ServerTuning<float> SummonAttackCooldown;

			public readonly ServerTuning<int> SummonMaxSpawns;

			public readonly ServerTuning<int> SummonsPerCast;

			public readonly ServerTuning<bool> IgnoreSummonLimit;

			public readonly ServerTuning<float> SummonSpawnRadius;

			public readonly ServerTuning<bool> AllowStarredSummons;

			public readonly ServerTuning<float> OneStarChance;

			public readonly ServerTuning<float> TwoStarChance;

			public readonly ServerTuning<float> DamageMultiplier;

			public readonly ServerTuning<float> Damage;

			public readonly ServerTuning<float> DamagePerHit;

			private readonly string _displayName;

			private static readonly Dictionary<string, HashSet<string>> VisibleSettings = new Dictionary<string, HashSet<string>>(StringComparer.Ordinal)
			{
				["Staff of Embers"] = new HashSet<string>(StringComparer.Ordinal) { "Cast Time Multiplier", "Eitr Cost", "Cooldown", "Projectile Speed", "Projectile Lifetime", "Explosion / AOE Radius", "Knockback", "Attack Force", "Projectile Damage Multiplier" },
				["Staff of Frost"] = new HashSet<string>(StringComparer.Ordinal)
				{
					"Cast Time Multiplier", "Eitr Cost", "Cooldown", "Projectile Speed", "Projectile Count", "Projectile Accuracy Min", "Projectile Lifetime", "Status Effect Duration", "Knockback", "Attack Force",
					"Projectile Damage Multiplier"
				},
				["Staff of Fracturing"] = new HashSet<string>(StringComparer.Ordinal)
				{
					"Cast Time Multiplier", "Eitr Cost", "Cooldown", "Projectile Speed", "Projectile Count", "Projectile Accuracy Min", "Projectile Lifetime", "Explosion / AOE Radius", "Knockback", "Attack Force",
					"Projectile Damage Multiplier"
				},
				["Staff of the Wild"] = new HashSet<string>(StringComparer.Ordinal) { "Cast Time Multiplier", "Eitr Cost", "Cooldown", "Summons Per Cast", "Summon Max Spawns", "Summon Health", "Summon Damage Multiplier", "Summon Attack Speed", "Ignore Summon Limit", "Summon Spread Radius" },
				["Dundr"] = new HashSet<string>(StringComparer.Ordinal) { "Cast Time Multiplier", "Eitr Cost", "Cooldown", "Projectile Count", "Projectile Speed", "Projectile Accuracy Min", "Projectile Lifetime", "Knockback", "Attack Force", "Projectile Damage Multiplier" },
				["Echo Spike"] = new HashSet<string>(StringComparer.Ordinal) { "Cast Time Multiplier", "Eitr Cost", "Cooldown", "Projectile Speed", "Projectile Accuracy Min", "Projectile Lifetime", "Projectile Max Bounces", "Knockback", "Attack Force", "Projectile Damage Multiplier" },
				["Spirit Caller"] = new HashSet<string>(StringComparer.Ordinal)
				{
					"Cast Time Multiplier", "Eitr Cost", "Health Cost Percent", "Cooldown", "Summons Per Cast", "Summon Max Spawns", "Summon Health", "Summon Movement Speed", "Summon Damage Multiplier", "Summon Attack Speed",
					"Ignore Summon Limit", "Allow Starred Summons", "1-Star Summon Chance", "2-Star Summon Chance"
				},
				["Northern Vengeance"] = new HashSet<string>(StringComparer.Ordinal) { "Cast Time Multiplier", "Eitr Cost", "Health Cost Percent", "Ally Application Radius", "Sphere Duration Multiplier", "Projectile Damage Multiplier" },
				["Lightning Strike"] = new HashSet<string>(StringComparer.Ordinal) { "Cast Time Multiplier", "Eitr Cost", "Explosion / AOE Radius", "Strikes Per Cast", "Projectile Damage Multiplier" },
				["Staff of Protection"] = new HashSet<string>(StringComparer.Ordinal) { "Cast Time Multiplier", "Eitr Cost", "Health Cost Percent", "Cooldown", "Shield Absorption", "Shield Absorption Per Blood Magic Level", "Status Effect Duration" },
				["Dead Raiser"] = new HashSet<string>(StringComparer.Ordinal)
				{
					"Cast Time Multiplier", "Eitr Cost", "Health Cost Percent", "Cooldown", "Summons Per Cast", "Summon Max Spawns", "Summon Health", "Summon Movement Speed", "Summon Melee Damage Multiplier", "Summon Archer Damage Multiplier",
					"Summon Attack Speed", "Ignore Summon Limit"
				},
				["Trollstav"] = new HashSet<string>(StringComparer.Ordinal) { "Cast Time Multiplier", "Eitr Cost", "Health Cost Percent", "Cooldown", "Summons Per Cast", "Summon Max Spawns", "Summon Health", "Summon Movement Speed", "Summon Damage Multiplier", "Summon Attack Speed" }
			};

			private static readonly Dictionary<string, float> VanillaEitrCosts = new Dictionary<string, float>(StringComparer.Ordinal)
			{
				["Staff of Embers"] = 35f,
				["Staff of Frost"] = 5f,
				["Staff of Fracturing"] = 30f,
				["Staff of the Wild"] = 55f,
				["Dundr"] = 25f,
				["Echo Spike"] = 25f,
				["Spirit Caller"] = 100f,
				["Northern Vengeance"] = 100f,
				["Lightning Strike"] = 40f,
				["Staff of Protection"] = 60f,
				["Dead Raiser"] = 100f,
				["Trollstav"] = 120f
			};

			private static readonly Dictionary<string, float> VanillaHealthCosts = new Dictionary<string, float>(StringComparer.Ordinal)
			{
				["Spirit Caller"] = 40f,
				["Northern Vengeance"] = 40f,
				["Staff of Protection"] = 40f,
				["Dead Raiser"] = 40f,
				["Trollstav"] = 60f
			};

			public event Action? Changed;

			public ServerStaffConfig(ConfigFile config, StaffDefinition staff)
			{
				//IL_016e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0174: Invalid comparison between Unknown and I4
				string text = (_displayName = staff.DisplayName);
				Durability = TuneFloat(config, text, "Durability", 0f, "Staff durability.");
				DurabilityPerLevel = TuneFloat(config, text, "Durability Per Level", 0f, "Durability gained per quality level.");
				UseDurability = TuneBool(config, text, "Use Durability", value: true, "Whether durability is consumed.");
				MovementModifier = TuneFloat(config, text, "Movement Modifier", 0f, "Movement modifier while equipped.");
				BlockPower = TuneFloat(config, text, "Block Power", 0f, "Staff block power when exposed.");
				BlockPowerPerLevel = TuneFloat(config, text, "Block Power Per Level", 0f, "Staff block power gained per quality level.");
				DeflectionForce = TuneFloat(config, text, "Deflection Force", 0f, "Staff block deflection force when exposed.");
				DeflectionForcePerLevel = TuneFloat(config, text, "Deflection Force Per Level", 0f, "Staff block deflection gained per quality level.");
				TimedBlockBonus = TuneFloat(config, text, "Timed Block Bonus", 0f, "Staff timed-block/parry bonus when exposed.");
				BlockStaminaUseFlatValue = TuneFloat(config, text, "Block Stamina Use Flat", 0f, "Flat stamina use while blocking when exposed.");
				BlockStaminaUseModifier = TuneFloat(config, text, "Block Stamina Use Modifier", 0f, "Stamina-use modifier while blocking when exposed.");
				EitrCost = TuneFloat(config, text, "Eitr Cost", 0f, "Eitr consumed by the attack.");
				HealthCostPercent = TuneFloat(config, text, "Health Cost Percent", 0f, ((int)staff.MagicType == 1) ? "Percentage of current health consumed per cast." : "Percentage of health consumed per cast.");
				Cooldown = TuneFloat(config, text, "Cooldown", 0f, "Time between casts.");
				AttackForce = TuneFloat(config, text, "Attack Force", 0f, "Attack/stagger force.");
				AttackForceForward = TuneFloat(config, text, "Attack Force Forward", 0f, "Forward attack force.");
				ProjectileAccuracyMin = TuneFloat(config, text, "Projectile Accuracy Min", 0f, "Minimum spread used when firing projectiles. Lower values generally produce a tighter spread.");
				ProjectileSpeed = TuneFloat(config, text, "Projectile Speed", 0f, "Travel speed of the spell projectile.");
				ProjectileLifetime = TuneFloat(config, text, "Projectile Lifetime", 0f, "How long the projectile remains active.");
				ProjectileVelocity = TuneFloat(config, text, "Projectile Velocity", 0f, "Projectile component velocity when exposed.");
				ProjectileVelocityMax = TuneFloat(config, text, "Projectile Velocity Max", 0f, "Projectile component maximum velocity when exposed.");
				ProjectileMaxBounces = TuneInt(config, text, "Projectile Max Bounces", 0, "Maximum number of times the projectile can bounce to another target.");
				ProjectileCount = TuneInt(config, text, (text == "Lightning Strike") ? "Strikes Per Cast" : "Projectile Count", 0, (text == "Lightning Strike") ? "Number of lightning strikes produced by each cast." : "Number of projectiles fired.");
				DrawTime = TuneFloat(config, text, "Cast Time Multiplier", 1f, "Multiplier for the staff's normal cast time. 1.0 is vanilla, 0.5 casts in half the time, and 2.0 takes twice as long.");
				ChainChance = TuneFloat(config, text, "Chain Chance", 0f, "Chain chance.");
				ChainChancePerTarget = TuneFloat(config, text, "Chain Chance Per Target", 0f, "Per-target chain chance adjustment.");
				ChainMaxTargets = TuneInt(config, text, "Chain Max Targets", 0, "Maximum chain targets.");
				ChainDelay = TuneFloat(config, text, "Chain Delay", 0f, "Delay between chain targets.");
				ChainStartChance = TuneFloat(config, text, "Chain Start Chance", 0f, "Initial chain-start chance.");
				ChainStartChanceFalloff = TuneFloat(config, text, "Chain Start Chance Falloff", 0f, "Chain-start chance falloff.");
				ChainStartDelay = TuneFloat(config, text, "Chain Start Delay", 0f, "Delay before chain begins.");
				ChainMinTargets = TuneInt(config, text, "Chain Min Targets", 0, "Minimum number of targets required by the chain attack when the vanilla field exists.");
				ChainAttackMaxTime = TuneFloat(config, text, "Chain Attack Max Time", 0f, "Maximum time window for the chain attack when the vanilla field exists.");
				AttackChainLevels = TuneInt(config, text, "Attack Chain Levels", 0, "Number of chain levels supported by the attack when the vanilla field exists.");
				ExplosionRadius = TuneFloat(config, text, "Explosion / AOE Radius", 0f, "Radius of the spell's area of effect or explosion.");
				Knockback = TuneFloat(config, text, "Knockback", 0f, "Force applied when the spell or projectile hits a target.");
				StatusEffectDuration = TuneFloat(config, text, "Status Effect Duration", 0f, "Duration of the status effect applied by the staff.");
				ShieldAbsorption = TuneFloat(config, text, "Shield Absorption", 0f, "Base damage the Staff of Protection barrier can absorb.");
				ShieldAbsorptionPerBloodMagicLevel = TuneFloat(config, text, "Shield Absorption Per Blood Magic Level", 0f, "Additional barrier absorption per Blood Magic level.");
				VengeanceSphereRadius = TuneFloat(config, text, "Ally Application Radius", 1f, "Distance around the caster in which allies receive Vengeance Sphere. Vanilla: 5 m. This does not change the visual bubble size.");
				VengeanceReflectionHeight = TuneFloat(config, text, "Sphere Duration Multiplier", 1f, "Multiplier for Northern Vengeance's normal sphere duration. 1.0 is vanilla, 0.5 is half duration, and 2.0 is double duration.");
				SummonHealth = TuneFloat(config, text, "Summon Health", 0f, "Base health of creatures summoned by this staff.");
				SummonWalkSpeed = TuneFloat(config, text, "Summon Movement Speed", 1f, "Multiplier for the summon creature's normal movement speed. 1.0 is vanilla, 0.5 is half speed, and 2.0 is double speed.");
				SummonRunSpeed = TuneFloat(config, text, "Summon Run Speed", 0f, "Running speed of creatures summoned by this staff.");
				SummonMeleeDamageMultiplier = TuneFloat(config, text, "Summon Melee Damage Multiplier", 0f, "Multiplier applied to Dead Raiser melee summon damage.");
				SummonArcherDamageMultiplier = TuneFloat(config, text, "Summon Archer Damage Multiplier", 0f, "Multiplier applied to Dead Raiser archer summon damage.");
				SummonDamagePerLevelMultiplier = TuneFloat(config, text, "Summon Damage Per Level Multiplier", 0f, "Multiplier applied to summon weapon m_damagesPerLevel. This is weapon-quality scaling, not Blood Magic skill scaling.");
				SummonDamageMultiplier = TuneFloat(config, text, "Summon Attack Damage Multiplier", 0f, "Multiplier for a referenced summon Attack.m_damageMultiplier field when exposed.");
				SummonFinalDamageMultiplier = TuneFloat(config, text, "Summon Damage Multiplier", 1f, "Multiplier applied to damage dealt by summons created by this staff.");
				SummonAttackCooldown = TuneFloat(config, text, "Summon Attack Speed", 1f, "Multiplier for summon attack rate. 1.0 is vanilla, 0.5 is half speed, and 2.0 is twice as fast.");
				SummonMaxSpawns = TuneInt(config, text, "Summon Max Spawns", 0, "Maximum number of creatures this staff can keep summoned at once.");
				SummonsPerCast = TuneInt(config, text, "Summons Per Cast", 0, "Number of creatures this staff attempts to conjure with one cast.");
				IgnoreSummonLimit = TuneBool(config, text, "Ignore Summon Limit", value: false, "When enabled, this staff ignores its normal skill/quality-based summon-count restriction. The effective summon cap is raised to at least Summons Per Cast.");
				SummonSpawnRadius = TuneFloat(config, text, "Summon Spread Radius", 0f, "Radius around the cast point used to separate summoned creatures. Staff of the Wild is 0 m in vanilla.");
				AllowStarredSummons = TuneBool(config, text, "Allow Starred Summons", value: false, "Allows Spirit Caller to occasionally summon starred animal allies. Vanilla: Off.");
				OneStarChance = TuneFloat(config, text, "1-Star Summon Chance", 0f, "Chance for a Spirit Caller summon to be 1-star. Rolled after the 2-star chance.");
				TwoStarChance = TuneFloat(config, text, "2-Star Summon Chance", 0f, "Chance for a Spirit Caller summon to be 2-star. Rolled first.");
				DamageMultiplier = TuneFloat(config, text, "Projectile Damage Multiplier", 0f, "Multiplier applied to this staff's projectile or spell damage.");
				Damage = TuneFloat(config, text, "Direct Damage", 0f, "Direct projectile/AOE damage when exposed.");
				DamagePerHit = TuneFloat(config, text, "Damage Per Hit", 0f, "Damage per hit when exposed.");
				foreach (IServerSettingGroup item in Tunings())
				{
					item.SubscribeChanged(delegate
					{
						this.Changed?.Invoke();
					});
				}
			}

			private static bool IsVisible(string section, string key)
			{
				if (VisibleSettings.TryGetValue(section, out HashSet<string> value))
				{
					return value.Contains(key);
				}
				return false;
			}

			public void InitializeVanillaValues(ServerBaseline baseline)
			{
				Init(EitrCost, VanillaEitrCosts.TryGetValue(_displayName, out var value) ? value : baseline.Primary.AttackEitr);
				Init(HealthCostPercent, VanillaHealthCosts.TryGetValue(_displayName, out var value2) ? value2 : (baseline.Primary.HealthPercent * 100f));
				if (baseline.Primary.Cooldown > 0f)
				{
					Init(Cooldown, baseline.Primary.Cooldown);
				}
				else
				{
					Cooldown.SetBrowsable(visible: false);
				}
				if (DrawTime.Uninitialized || DrawTime.Value.Value <= 0.1f)
				{
					DrawTime.Value.Value = 1f;
				}
				DrawTime.Uninitialized = false;
				DrawTime.SetBrowsable(visible: true, DrawTime.UiDescription + " Vanilla: 1.0x.");
				if (baseline.Primary.AttackForce > 0f)
				{
					Init(AttackForce, baseline.Primary.AttackForce);
				}
				else
				{
					AttackForce.SetBrowsable(visible: false);
				}
				Init(ProjectileSpeed, baseline.Primary.ProjectileVelocity);
				Init(ProjectileCount, (_displayName == "Dundr" || _displayName == "Staff of Fracturing") ? 12 : ((_displayName == "Lightning Strike") ? 1 : baseline.Primary.Projectiles));
				if (baseline.Primary.ProjectileAccuracyMin > 0f)
				{
					Init(ProjectileAccuracyMin, baseline.Primary.ProjectileAccuracyMin);
				}
				else
				{
					ProjectileAccuracyMin.SetBrowsable(visible: false);
				}
				if (_displayName == "Lightning Strike")
				{
					Init(ExplosionRadius, 3f);
				}
				else
				{
					InitGraph(baseline.Primary.GraphFields, "Aoe", "m_radius", ExplosionRadius);
					InitGraph(baseline.Primary.GraphFields, "EffectArea", "m_radius", ExplosionRadius);
				}
				InitGraph(baseline.Primary.GraphFields, "StatusEffect", "m_duration", StatusEffectDuration);
				if (_displayName == "Staff of Protection")
				{
					Init(ShieldAbsorption, 200f);
					Init(ShieldAbsorptionPerBloodMagicLevel, 5f);
				}
				else
				{
					InitGraph(baseline.Primary.GraphFields, "SE_Shield", "m_absorbDamage", ShieldAbsorption);
					InitGraph(baseline.Primary.GraphFields, "SE_Shield", "m_absorbDamagePerSkillLevel", ShieldAbsorptionPerBloodMagicLevel);
				}
				if (_displayName == "Northern Vengeance")
				{
					Init(VengeanceSphereRadius, 5f);
				}
				else
				{
					InitGraph(baseline.Primary.GraphFields, "ShieldDome", "m_wrapSphereRadius", VengeanceSphereRadius);
				}
				if (_displayName == "Northern Vengeance")
				{
					if (VengeanceReflectionHeight.Uninitialized || VengeanceReflectionHeight.Value.Value <= 0.1f)
					{
						VengeanceReflectionHeight.Value.Value = 1f;
					}
					VengeanceReflectionHeight.Uninitialized = false;
					VengeanceReflectionHeight.SetBrowsable(visible: true, VengeanceReflectionHeight.UiDescription + " Vanilla: 1.0x (60 seconds per staff quality level). ");
				}
				else
				{
					VengeanceReflectionHeight.SetBrowsable(visible: false);
				}
				InitPositiveGraph(baseline.Primary.GraphFields, "Projectile", new string[2] { "m_ttl", "m_ttlMax" }, ProjectileLifetime);
				InitPositiveGraphAny(baseline.Primary.GraphFields, new string[3] { "Projectile", "Aoe", "EffectArea" }, new string[2] { "m_force", "m_forceStagger" }, Knockback);
				InitGraph(baseline.Primary.GraphFields, "Projectile", "m_maxBounces", ProjectileMaxBounces);
				Init(DamageMultiplier, 1f);
				InitSummonHealth(baseline);
				if (SummonWalkSpeed.Uninitialized || SummonWalkSpeed.Value.Value <= 0.1f)
				{
					SummonWalkSpeed.Value.Value = 1f;
				}
				SummonWalkSpeed.Uninitialized = false;
				SummonWalkSpeed.SetBrowsable(SummonWalkSpeed.IntendedVisible, SummonWalkSpeed.UiDescription + " Vanilla: 1.0x.");
				SummonRunSpeed.SetBrowsable(visible: false);
				if (SummonAttackCooldown.Uninitialized || SummonAttackCooldown.Value.Value <= 0.1f)
				{
					SummonAttackCooldown.Value.Value = 1f;
				}
				SummonAttackCooldown.Uninitialized = false;
				bool flag = baseline.Primary.SummonGraphFields.Exists((GraphFieldBaseline f) => ((f.ComponentTypeName == "Attack" && (f.FieldName == "m_attackCooldown" || f.FieldName == "m_attackDrawTime" || f.FieldName == "m_attackAnimationSpeedFactor")) || f.FieldName == "m_aiAttackInterval") && Convert.ToSingle(f.VanillaValue) > 0f);
				SummonAttackCooldown.SetBrowsable(SummonAttackCooldown.IntendedVisible && flag, SummonAttackCooldown.UiDescription + " Vanilla: 1.0x.");
				InitGraph(baseline.Primary.SummonGraphFields, "SpawnAbility", "m_maxSpawned", SummonMaxSpawns);
				Init(SummonsPerCast, 1);
				HealthDefaults.SetDefault(SummonsPerCast.Value, 1);
				if (_displayName == "Staff of the Wild")
				{
					Init(SummonSpawnRadius, 0f);
				}
				else
				{
					SummonSpawnRadius.SetBrowsable(visible: false);
				}
				Init(SummonMeleeDamageMultiplier, 1f);
				Init(SummonArcherDamageMultiplier, 1f);
				Init(SummonFinalDamageMultiplier, 1f);
				if (_displayName == "Trollstav")
				{
					Init(SummonMaxSpawns, 2);
				}
			}

			private static void Init(ServerTuning<float> setting, float vanilla)
			{
				if (vanilla < 0f)
				{
					setting.SetBrowsable(visible: false);
					return;
				}
				if (setting.Uninitialized)
				{
					setting.Value.Value = vanilla;
					setting.Uninitialized = false;
				}
				setting.SetBrowsable(setting.IntendedVisible, setting.UiDescription + " Vanilla: " + FormatVanilla(vanilla) + ".");
			}

			private void InitSummonHealth(ServerBaseline baseline)
			{
				List<GraphFieldBaseline>[] array = new List<GraphFieldBaseline>[2]
				{
					baseline.Primary.SummonGraphFields,
					baseline.Secondary.SummonGraphFields
				};
				for (int i = 0; i < array.Length; i++)
				{
					foreach (GraphFieldBaseline item in array[i])
					{
						if (!(item.ComponentTypeName != "Character") && !(item.FieldName != "m_health"))
						{
							float? num = Convert.ToSingle(item.VanillaValue);
							if (num.HasValue && DamageTuning.ValidHealth(num.Value))
							{
								HealthDefaults.Apply(SummonHealth.Value, num.Value);
								Init(SummonHealth, num.Value);
								return;
							}
						}
					}
				}
				SummonHealth.SetBrowsable(visible: false);
			}

			private static void Init(ServerTuning<int> setting, int vanilla)
			{
				if (vanilla < 0)
				{
					setting.SetBrowsable(visible: false);
					return;
				}
				if (setting.Uninitialized)
				{
					setting.Value.Value = vanilla;
					setting.Uninitialized = false;
				}
				setting.SetBrowsable(setting.IntendedVisible, setting.UiDescription + " Vanilla: " + vanilla + ".");
			}

			private static string FormatVanilla(float value)
			{
				if (!(Math.Abs(value - (float)Math.Round(value)) < 0.0001f))
				{
					return value.ToString("0.##");
				}
				return ((int)Math.Round(value)).ToString();
			}

			private static void InitPositiveGraphAny(List<GraphFieldBaseline> fields, string[] components, string[] fieldNames, ServerTuning<float> setting)
			{
				foreach (string text in components)
				{
					foreach (string text2 in fieldNames)
					{
						foreach (GraphFieldBaseline field in fields)
						{
							if (field.ComponentTypeName == text && field.Field.Name == text2)
							{
								float num = Convert.ToSingle(field.VanillaValue);
								if (num > 0f)
								{
									Init(setting, num);
									return;
								}
							}
						}
					}
				}
				setting.SetBrowsable(visible: false);
			}

			private static void InitPositiveGraph(List<GraphFieldBaseline> fields, string component, string[] fieldNames, ServerTuning<float> setting)
			{
				foreach (string text in fieldNames)
				{
					foreach (GraphFieldBaseline field in fields)
					{
						if (field.ComponentTypeName == component && field.Field.Name == text)
						{
							float num = Convert.ToSingle(field.VanillaValue);
							if (num > 0f)
							{
								Init(setting, num);
								return;
							}
						}
					}
				}
				setting.SetBrowsable(visible: false);
			}

			private static void InitGraph(List<GraphFieldBaseline> fields, string component, string field, ServerTuning<float> setting)
			{
				foreach (GraphFieldBaseline field2 in fields)
				{
					if (field2.ComponentTypeName == component && field2.Field.Name == field)
					{
						Init(setting, Convert.ToSingle(field2.VanillaValue));
						return;
					}
				}
				setting.SetBrowsable(visible: false);
			}

			private static void InitGraph(List<GraphFieldBaseline> fields, string component, string field, ServerTuning<int> setting)
			{
				foreach (GraphFieldBaseline field2 in fields)
				{
					if (field2.ComponentTypeName == component && field2.Field.Name == field)
					{
						Init(setting, Convert.ToInt32(field2.VanillaValue));
						return;
					}
				}
				setting.SetBrowsable(visible: false);
			}

			private IEnumerable<IServerSettingGroup> Tunings()
			{
				yield return Durability;
				yield return DurabilityPerLevel;
				yield return UseDurability;
				yield return MovementModifier;
				yield return BlockPower;
				yield return BlockPowerPerLevel;
				yield return DeflectionForce;
				yield return DeflectionForcePerLevel;
				yield return TimedBlockBonus;
				yield return BlockStaminaUseFlatValue;
				yield return BlockStaminaUseModifier;
				yield return EitrCost;
				yield return HealthCostPercent;
				yield return Cooldown;
				yield return AttackForce;
				yield return AttackForceForward;
				yield return ProjectileAccuracyMin;
				yield return ProjectileSpeed;
				yield return ProjectileLifetime;
				yield return ProjectileVelocity;
				yield return ProjectileVelocityMax;
				yield return ProjectileMaxBounces;
				yield return ProjectileCount;
				yield return DrawTime;
				yield return ChainChance;
				yield return ChainChancePerTarget;
				yield return ChainMaxTargets;
				yield return ChainDelay;
				yield return ChainStartChance;
				yield return ChainStartChanceFalloff;
				yield return ChainStartDelay;
				yield return ExplosionRadius;
				yield return Knockback;
				yield return StatusEffectDuration;
				yield return ShieldAbsorption;
				yield return ShieldAbsorptionPerBloodMagicLevel;
				yield return VengeanceSphereRadius;
				yield return VengeanceReflectionHeight;
				yield return SummonHealth;
				yield return SummonWalkSpeed;
				yield return SummonRunSpeed;
				yield return SummonMeleeDamageMultiplier;
				yield return SummonArcherDamageMultiplier;
				yield return SummonDamagePerLevelMultiplier;
				yield return SummonDamageMultiplier;
				yield return SummonFinalDamageMultiplier;
				yield return SummonsPerCast;
				yield return SummonMaxSpawns;
				yield return IgnoreSummonLimit;
				yield return SummonSpawnRadius;
				yield return AllowStarredSummons;
				yield return OneStarChance;
				yield return TwoStarChance;
				yield return SummonAttackCooldown;
				yield return DamageMultiplier;
				yield return Damage;
				yield return DamagePerHit;
			}

			private IEnumerable<ConfigEntryBase> Entries()
			{
				yield return (ConfigEntryBase)(object)Durability.Mode;
				yield return (ConfigEntryBase)(object)Durability.Value;
				yield return (ConfigEntryBase)(object)DurabilityPerLevel.Mode;
				yield return (ConfigEntryBase)(object)DurabilityPerLevel.Value;
				yield return (ConfigEntryBase)(object)UseDurability.Mode;
				yield return (ConfigEntryBase)(object)UseDurability.Value;
				yield return (ConfigEntryBase)(object)MovementModifier.Mode;
				yield return (ConfigEntryBase)(object)MovementModifier.Value;
				yield return (ConfigEntryBase)(object)BlockPower.Mode;
				yield return (ConfigEntryBase)(object)BlockPower.Value;
				yield return (ConfigEntryBase)(object)BlockPowerPerLevel.Mode;
				yield return (ConfigEntryBase)(object)BlockPowerPerLevel.Value;
				yield return (ConfigEntryBase)(object)DeflectionForce.Mode;
				yield return (ConfigEntryBase)(object)DeflectionForce.Value;
				yield return (ConfigEntryBase)(object)DeflectionForcePerLevel.Mode;
				yield return (ConfigEntryBase)(object)DeflectionForcePerLevel.Value;
				yield return (ConfigEntryBase)(object)TimedBlockBonus.Mode;
				yield return (ConfigEntryBase)(object)TimedBlockBonus.Value;
				yield return (ConfigEntryBase)(object)BlockStaminaUseFlatValue.Mode;
				yield return (ConfigEntryBase)(object)BlockStaminaUseFlatValue.Value;
				yield return (ConfigEntryBase)(object)BlockStaminaUseModifier.Mode;
				yield return (ConfigEntryBase)(object)BlockStaminaUseModifier.Value;
				yield return (ConfigEntryBase)(object)EitrCost.Mode;
				yield return (ConfigEntryBase)(object)EitrCost.Value;
				yield return (ConfigEntryBase)(object)HealthCostPercent.Mode;
				yield return (ConfigEntryBase)(object)HealthCostPercent.Value;
				yield return (ConfigEntryBase)(object)Cooldown.Mode;
				yield return (ConfigEntryBase)(object)Cooldown.Value;
				yield return (ConfigEntryBase)(object)AttackForce.Mode;
				yield return (ConfigEntryBase)(object)AttackForce.Value;
				yield return (ConfigEntryBase)(object)AttackForceForward.Mode;
				yield return (ConfigEntryBase)(object)AttackForceForward.Value;
				yield return (ConfigEntryBase)(object)ProjectileAccuracyMin.Mode;
				yield return (ConfigEntryBase)(object)ProjectileAccuracyMin.Value;
				yield return (ConfigEntryBase)(object)ProjectileSpeed.Mode;
				yield return (ConfigEntryBase)(object)ProjectileSpeed.Value;
				yield return (ConfigEntryBase)(object)ProjectileLifetime.Mode;
				yield return (ConfigEntryBase)(object)ProjectileLifetime.Value;
				yield return (ConfigEntryBase)(object)ProjectileVelocity.Mode;
				yield return (ConfigEntryBase)(object)ProjectileVelocity.Value;
				yield return (ConfigEntryBase)(object)ProjectileVelocityMax.Mode;
				yield return (ConfigEntryBase)(object)ProjectileVelocityMax.Value;
				yield return (ConfigEntryBase)(object)ProjectileMaxBounces.Mode;
				yield return (ConfigEntryBase)(object)ProjectileMaxBounces.Value;
				yield return (ConfigEntryBase)(object)ProjectileCount.Mode;
				yield return (ConfigEntryBase)(object)ProjectileCount.Value;
				yield return (ConfigEntryBase)(object)DrawTime.Mode;
				yield return (ConfigEntryBase)(object)DrawTime.Value;
				yield return (ConfigEntryBase)(object)ChainChance.Mode;
				yield return (ConfigEntryBase)(object)ChainChance.Value;
				yield return (ConfigEntryBase)(object)ChainChancePerTarget.Mode;
				yield return (ConfigEntryBase)(object)ChainChancePerTarget.Value;
				yield return (ConfigEntryBase)(object)ChainMaxTargets.Mode;
				yield return (ConfigEntryBase)(object)ChainMaxTargets.Value;
				yield return (ConfigEntryBase)(object)ChainDelay.Mode;
				yield return (ConfigEntryBase)(object)ChainDelay.Value;
				yield return (ConfigEntryBase)(object)ChainStartChance.Mode;
				yield return (ConfigEntryBase)(object)ChainStartChance.Value;
				yield return (ConfigEntryBase)(object)ChainStartChanceFalloff.Mode;
				yield return (ConfigEntryBase)(object)ChainStartChanceFalloff.Value;
				yield return (ConfigEntryBase)(object)ChainStartDelay.Mode;
				yield return (ConfigEntryBase)(object)ChainStartDelay.Value;
				yield return (ConfigEntryBase)(object)ExplosionRadius.Mode;
				yield return (ConfigEntryBase)(object)ExplosionRadius.Value;
				yield return (ConfigEntryBase)(object)Knockback.Mode;
				yield return (ConfigEntryBase)(object)Knockback.Value;
				yield return (ConfigEntryBase)(object)StatusEffectDuration.Mode;
				yield return (ConfigEntryBase)(object)StatusEffectDuration.Value;
				yield return (ConfigEntryBase)(object)ShieldAbsorption.Mode;
				yield return (ConfigEntryBase)(object)ShieldAbsorption.Value;
				yield return (ConfigEntryBase)(object)ShieldAbsorptionPerBloodMagicLevel.Mode;
				yield return (ConfigEntryBase)(object)ShieldAbsorptionPerBloodMagicLevel.Value;
				yield return (ConfigEntryBase)(object)VengeanceSphereRadius.Mode;
				yield return (ConfigEntryBase)(object)VengeanceSphereRadius.Value;
				yield return (ConfigEntryBase)(object)VengeanceReflectionHeight.Mode;
				yield return (ConfigEntryBase)(object)VengeanceReflectionHeight.Value;
				yield return (ConfigEntryBase)(object)SummonHealth.Mode;
				yield return (ConfigEntryBase)(object)SummonHealth.Value;
				yield return (ConfigEntryBase)(object)SummonWalkSpeed.Mode;
				yield return (ConfigEntryBase)(object)SummonWalkSpeed.Value;
				yield return (ConfigEntryBase)(object)SummonRunSpeed.Mode;
				yield return (ConfigEntryBase)(object)SummonRunSpeed.Value;
				yield return (ConfigEntryBase)(object)SummonMeleeDamageMultiplier.Mode;
				yield return (ConfigEntryBase)(object)SummonMeleeDamageMultiplier.Value;
				yield return (ConfigEntryBase)(object)SummonArcherDamageMultiplier.Mode;
				yield return (ConfigEntryBase)(object)SummonArcherDamageMultiplier.Value;
				yield return (ConfigEntryBase)(object)SummonDamagePerLevelMultiplier.Mode;
				yield return (ConfigEntryBase)(object)SummonDamagePerLevelMultiplier.Value;
				yield return (ConfigEntryBase)(object)SummonDamageMultiplier.Mode;
				yield return (ConfigEntryBase)(object)SummonDamageMultiplier.Value;
				yield return (ConfigEntryBase)(object)SummonFinalDamageMultiplier.Mode;
				yield return (ConfigEntryBase)(object)SummonFinalDamageMultiplier.Value;
				yield return (ConfigEntryBase)(object)SummonsPerCast.Mode;
				yield return (ConfigEntryBase)(object)SummonsPerCast.Value;
				yield return (ConfigEntryBase)(object)SummonMaxSpawns.Mode;
				yield return (ConfigEntryBase)(object)SummonMaxSpawns.Value;
				yield return (ConfigEntryBase)(object)IgnoreSummonLimit.Mode;
				yield return (ConfigEntryBase)(object)IgnoreSummonLimit.Value;
				yield return (ConfigEntryBase)(object)AllowStarredSummons.Mode;
				yield return (ConfigEntryBase)(object)AllowStarredSummons.Value;
				yield return (ConfigEntryBase)(object)OneStarChance.Mode;
				yield return (ConfigEntryBase)(object)OneStarChance.Value;
				yield return (ConfigEntryBase)(object)TwoStarChance.Mode;
				yield return (ConfigEntryBase)(object)TwoStarChance.Value;
				yield return (ConfigEntryBase)(object)SummonSpawnRadius.Mode;
				yield return (ConfigEntryBase)(object)SummonSpawnRadius.Value;
				yield return (ConfigEntryBase)(object)SummonAttackCooldown.Mode;
				yield return (ConfigEntryBase)(object)SummonAttackCooldown.Value;
				yield return (ConfigEntryBase)(object)DamageMultiplier.Mode;
				yield return (ConfigEntryBase)(object)DamageMultiplier.Value;
				yield return (ConfigEntryBase)(object)Damage.Mode;
				yield return (ConfigEntryBase)(object)Damage.Value;
				yield return (ConfigEntryBase)(object)DamagePerHit.Mode;
				yield return (ConfigEntryBase)(object)DamagePerHit.Value;
			}

			private static AcceptableValueBase? RangeFor(string key)
			{
				switch (key)
				{
				case "Health Cost Percent":
					return (AcceptableValueBase?)(object)new AcceptableValueRange<float>(0f, 100f);
				case "Ally Application Radius":
					return (AcceptableValueBase?)(object)new AcceptableValueRange<float>(1f, 20f);
				case "Summon Spread Radius":
					return (AcceptableValueBase?)(object)new AcceptableValueRange<float>(0f, 10f);
				case "1-Star Summon Chance":
				case "2-Star Summon Chance":
					return (AcceptableValueBase?)(object)new AcceptableValueRange<float>(0f, 100f);
				case "Explosion / AOE Radius":
					return (AcceptableValueBase?)(object)new AcceptableValueRange<float>(0.5f, 20f);
				case "Summon Movement Speed":
					return (AcceptableValueBase?)(object)new AcceptableValueRange<float>(0.1f, 3f);
				case "Summon Attack Speed":
					return (AcceptableValueBase?)(object)new AcceptableValueRange<float>(0.1f, 3f);
				case "Cast Time Multiplier":
				case "Sphere Duration Multiplier":
					return (AcceptableValueBase?)(object)new AcceptableValueRange<float>(0.1f, 3f);
				default:
					if (key.Contains("Chance", StringComparison.Ordinal))
					{
						return (AcceptableValueBase?)(object)new AcceptableValueRange<float>(0f, 1f);
					}
					if (key.Contains("Multiplier", StringComparison.Ordinal))
					{
						return (AcceptableValueBase?)(object)new AcceptableValueRange<float>(0f, 5f);
					}
					return null;
				}
			}

			private static ServerTuning<float> TuneFloat(ConfigFile config, string section, string key, float value, string description)
			{
				//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_001a: Expected O, but got Unknown
				//IL_0060: Unknown result type (might be due to invalid IL or missing references)
				//IL_0096: Unknown result type (might be due to invalid IL or missing references)
				//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
				//IL_00b3: 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_00c9: Expected O, but got Unknown
				bool flag = IsVisible(section, key);
				ConfigurationManagerAttributes val = new ConfigurationManagerAttributes
				{
					Browsable = false
				};
				if (key.Contains("Multiplier", StringComparison.Ordinal))
				{
					goto IL_005f;
				}
				switch (key)
				{
				case "Summon Movement Speed":
				case "Summon Attack Speed":
				case "Cast Time Multiplier":
				case "Sphere Duration Multiplier":
					goto IL_005f;
				}
				int num = 3;
				goto IL_0060;
				IL_005f:
				num = 2;
				goto IL_0060;
				IL_0060:
				ModifierMode val2 = (ModifierMode)num;
				bool flag2 = _instance?._savedSettingKeys.Contains(ConfigPersistence.Key(section, key)) ?? false;
				ConfigEntry<ModifierMode> obj = ConfigFileExtensions.BindConfigInOrder<ModifierMode>(config, "Internal / " + section, key + " Mode", val2, "Internal modifier mode.", true, true, true, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, val);
				obj.Value = val2;
				ConfigurationManagerAttributes val3 = new ConfigurationManagerAttributes
				{
					Browsable = flag,
					Description = description
				};
				ConfigEntry<float> value2 = ConfigFileExtensions.BindConfigInOrder<float>(config, section, key, value, description, true, false, true, RangeFor(key), (Action<ConfigEntryBase>)null, val3);
				return new ServerTuning<float>(obj, value2, !flag2, val3, description, flag);
			}

			private static ServerTuning<int> TuneInt(ConfigFile config, string section, string key, int value, string description)
			{
				//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_001a: Expected O, but got Unknown
				//IL_00df: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
				//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
				//IL_00fa: Expected O, but got Unknown
				bool flag = IsVisible(section, key);
				ConfigurationManagerAttributes val = new ConfigurationManagerAttributes
				{
					Browsable = false
				};
				bool flag2 = _instance?._savedSettingKeys.Contains(ConfigPersistence.Key(section, key)) ?? false;
				ConfigEntry<ModifierMode> obj = ConfigFileExtensions.BindConfigInOrder<ModifierMode>(config, "Internal / " + section, key + " Mode", (ModifierMode)3, "Internal modifier mode.", true, true, true, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, val);
				obj.Value = (ModifierMode)3;
				AcceptableValueBase val2 = (AcceptableValueBase)(key switch
				{
					"Projectile Max Bounces" => new AcceptableValueRange<int>(0, 200), 
					"Summon Max Spawns" => new AcceptableValueRange<int>(1, 100), 
					"Summons Per Cast" => new AcceptableValueRange<int>(1, 20), 
					"Strikes Per Cast" => new AcceptableValueRange<int>(1, 20), 
					"Projectile Count" => new AcceptableValueRange<int>(1, 50), 
					_ => null, 
				});
				ConfigurationManagerAttributes val3 = new ConfigurationManagerAttributes
				{
					Browsable = flag,
					Description = description
				};
				ConfigEntry<int> value2 = ConfigFileExtensions.BindConfigInOrder<int>(config, section, key, value, description, true, false, true, val2, (Action<ConfigEntryBase>)null, val3);
				return new ServerTuning<int>(obj, value2, !flag2, val3, description, flag);
			}

			private static ServerTuning<bool> TuneBool(ConfigFile config, string section, string key, bool value, string description)
			{
				//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_001a: Expected O, but got Unknown
				//IL_0049: Unknown result type (might be due to invalid IL or missing references)
				//IL_004e: Unknown result type (might be due to invalid IL or missing references)
				//IL_005a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0063: Expected O, but got Unknown
				bool flag = IsVisible(section, key);
				ConfigurationManagerAttributes val = new ConfigurationManagerAttributes
				{
					Browsable = false
				};
				ConfigEntry<ModifierMode> obj = ConfigFileExtensions.BindConfigInOrder<ModifierMode>(config, "Internal / " + section, key + " Mode", (ModifierMode)3, "Internal modifier mode.", true, true, true, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, val);
				obj.Value = (ModifierMode)3;
				ConfigurationManagerAttributes val2 = new ConfigurationManagerAttributes
				{
					Browsable = flag,
					Description = description
				};
				ConfigEntry<bool> value2 = ConfigFileExtensions.BindConfigInOrder<bool>(config, section, key, value, description, true, false, true, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, val2);
				return new ServerTuning<bool>(obj, value2, uninitialized: false, val2, description, flag);
			}
		}

		private interface IServerSettingGroup
		{
			void SubscribeChanged(Action handler);
		}

		private sealed class ServerTuning<T> : IServerSettingGroup
		{
			public ConfigEntry<ModifierMode> Mode { get; }

			public ConfigEntry<T> Value { get; }

			public bool Uninitialized { get; set; }

			public ConfigurationManagerAttributes Ui { get; }

			public string UiDescription { get; }

			public bool IntendedVisible { get; }

			public ServerTuning(ConfigEntry<ModifierMode> mode, ConfigEntry<T> value, bool uninitialized, ConfigurationManagerAttributes ui, string description, bool intendedVisible)
			{
				Mode = mode;
				Value = value;
				Uninitialized = uninitialized;
				Ui = ui;
				UiDescription = description;
				IntendedVisible = intendedVisible;
			}

			public void SubscribeChanged(Action handler)
			{
				Mode.SettingChanged += delegate
				{
					handler();
				};
				Value.SettingChanged += delegate
				{
					handler();
				};
			}

			public void SetBrowsable(bool visible, string? description = null)
			{
				Ui.Browsable = visible;
				if (description != null)
				{
					Ui.Description = description;
				}
			}
		}

		public const string Guid = "com.magicstafftweaker.server";

		public const string Name = "Magic Staff Tweaker Server";

		public const string Version = "2.3.4";

		private readonly Dictionary<string, ServerStaffConfig> _configs = new Dictionary<string, ServerStaffConfig>();

		private readonly Dictionary<string, ServerBaseline> _baselines = new Dictionary<string, ServerBaseline>();

		private static Plugin? _instance;

		private Harmony? _harmony;

		private bool _ready;

		private HashSet<string> _savedSettingKeys = new HashSet<string>(StringComparer.Ordinal);

		private static readonly Dictionary<object, float> _vengeanceDurationBaseline = new Dictionary<object, float>();

		private static readonly Dictionary<object, float> _vengeanceRadiusBaseline = new Dictionary<object, float>();

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

		private static readonly ConditionalWeakTable<SharedData, SummonWeaponDamageBaseline> SummonWeaponBaselines = new ConditionalWeakTable<SharedData, SummonWeaponDamageBaseline>();

		private static readonly ConditionalWeakTable<Aoe, AoeFactor> AoeFactors = new ConditionalWeakTable<Aoe, AoeFactor>();

		private void Awake()
		{
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Expected O, but got Unknown
			_instance = this;
			_savedSettingKeys = ConfigPersistence.ReadKeys(File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath) ? File.ReadAllText(((BaseUnityPlugin)this).Config.ConfigFilePath) : string.Empty);
			foreach (StaffDefinition item in VanillaStaffs.All)
			{
				ServerStaffConfig serverStaffConfig = new ServerStaffConfig(((BaseUnityPlugin)this).Config, item);
				_configs[item.Prefab] = serverStaffConfig;
				serverStaffConfig.Changed += ApplyAll;
			}
			PrefabManager.OnVanillaPrefabsAvailable += OnVanillaPrefabsAvailable;
			SynchronizationManager.OnConfigurationSynchronized += OnConfigurationSynchronized;
			_harmony = new Harmony("com.magicstafftweaker.server");
			_harmony.PatchAll();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Magic Staff Tweaker Server 2.3.4 loaded. Server-side staff tuning is authoritative.");
		}

		private void OnDestroy()
		{
			_instance = null;
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			PrefabManager.OnVanillaPrefabsAvailable -= OnVanillaPrefabsAvailable;
			SynchronizationManager.OnConfigurationSynchronized -= OnConfigurationSynchronized;
			foreach (ServerStaffConfig value in _configs.Values)
			{
				value.Changed -= ApplyAll;
			}
		}

		private void OnConfigurationSynchronized(object? sender, ConfigurationSynchronizationEventArgs e)
		{
			if (_ready)
			{
				ApplyAll();
			}
		}

		private void OnVanillaPrefabsAvailable()
		{
			_ready = true;
			CaptureBaselines();
			ApplyAll();
		}

		private void CaptureBaselines()
		{
			foreach (StaffDefinition item in VanillaStaffs.All)
			{
				GameObject prefab = PrefabManager.Instance.GetPrefab(item.Prefab);
				if ((Object)(object)prefab == (Object)null)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not find vanilla staff prefab '" + item.Prefab + "'."));
					continue;
				}
				ServerBaseline serverBaseline = ServerBaseline.Capture(prefab);
				CaptureKnownSummonHealth(item.Prefab, serverBaseline);
				_baselines[item.Prefab] = serverBaseline;
				if (_configs.TryGetValue(item.Prefab, out ServerStaffConfig value))
				{
					value.InitializeVanillaValues(serverBaseline);
				}
			}
		}

		private void ApplyAll()
		{
			if (!_ready)
			{
				return;
			}
			foreach (StaffDefinition item in VanillaStaffs.All)
			{
				if (_baselines.TryGetValue(item.Prefab, out ServerBaseline value))
				{
					GameObject prefab = PrefabManager.Instance.GetPrefab(item.Prefab);
					if (!((Object)(object)prefab == (Object)null))
					{
						ApplyStaff(prefab, value, _configs[item.Prefab]);
					}
				}
			}
		}

		private static void CaptureKnownSummonHealth(string staffPrefab, ServerBaseline baseline)
		{
			object obj = staffPrefab switch
			{
				"StaffSkeleton" => new string[2] { "Skeleton_Friendly", "SkeletonArcher_Friendly" }, 
				"StaffRedTroll" => new string[1] { "Troll_Summoned" }, 
				"StaffGreenRoots" => new string[1] { "staff_greenroots_tentaroot" }, 
				"StaffSpiritCaller" => new string[4] { "Boar_spiritcaller", "Wolf_spiritcaller", "Moose_spiritcaller", "Bjorn_spiritcaller" }, 
				_ => Array.Empty<string>(), 
			};
			List<GraphFieldBaseline> summonGraphFields = baseline.Primary.SummonGraphFields;
			string[] array = (string[])obj;
			foreach (string text in array)
			{
				GameObject prefab = PrefabManager.Instance.GetPrefab(text);
				Character creature = ((prefab != null) ? prefab.GetComponent<Character>() : null);
				if (!((Object)(object)creature == (Object)null))
				{
					FieldInfo field = ((object)creature).GetType().GetField("m_health", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					if (!(field == null) && !summonGraphFields.Exists((GraphFieldBaseline f) => f.Target == creature && f.FieldName == "m_health") && !baseline.Secondary.SummonGraphFields.Exists((GraphFieldBaseline f) => f.Target == creature && f.FieldName == "m_health"))
					{
						summonGraphFields.Add(new GraphFieldBaseline(creature, field, field.GetValue(creature)));
					}
				}
			}
		}

		private static void ApplyStaff(GameObject prefab, ServerBaseline baseline, ServerStaffConfig cfg)
		{
			SharedData val = prefab.GetComponent<ItemDrop>()?.m_itemData?.m_shared;
			if (val != null)
			{
				ApplyAttack(val.m_attack, baseline.Primary, cfg, primary: true);
				ApplyAttack(val.m_secondaryAttack, baseline.Secondary, cfg, primary: false);
				ApplyGraph(baseline.Primary, cfg);
				ApplyGraph(baseline.Secondary, cfg);
				ApplySummonGraph(baseline.Primary, cfg);
				ApplySummonGraph(baseline.Secondary, cfg);
				if (((Object)prefab).name.StartsWith("StaffFrostOrbs", StringComparison.Ordinal))
				{
					ApplyNorthernVengeanceEffect(val.m_attack, cfg);
				}
			}
		}

		private static void ApplyNorthernVengeanceEffect(Attack? attack, ServerStaffConfig cfg)
		{
			if (attack == null)
			{
				return;
			}
			object obj = ((object)attack).GetType().GetField("m_attackStatusEffect", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(attack);
			if (obj == null)
			{
				return;
			}
			FieldInfo field = obj.GetType().GetField("m_radius", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (field != null && field.FieldType == typeof(float) && cfg.VengeanceSphereRadius.IntendedVisible)
			{
				if (!_vengeanceRadiusBaseline.ContainsKey(obj))
				{
					_vengeanceRadiusBaseline[obj] = Convert.ToSingle(field.GetValue(obj));
				}
				field.SetValue(obj, cfg.VengeanceSphereRadius.Value.Value);
			}
		}

		private static bool IsVengeanceSphere(StatusEffect effect)
		{
			if ((Object)(object)effect == (Object)null)
			{
				return false;
			}
			string text = ((Object)effect).name ?? string.Empty;
			string text2 = effect.m_name ?? string.Empty;
			if (text.IndexOf("FrostOrbs", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("Vengeance", StringComparison.OrdinalIgnoreCase) < 0 && text2.IndexOf("frostorbs", StringComparison.OrdinalIgnoreCase) < 0)
			{
				return text2.IndexOf("vengeance", StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return true;
		}

		private static bool IsSpiritCallerAnimal(Character character)
		{
			if ((Object)(object)character == (Object)null)
			{
				return false;
			}
			GameObject gameObject = ((Component)character).gameObject;
			string text = ((gameObject != null) ? ((Object)gameObject).name : null) ?? string.Empty;
			if (text.IndexOf("Boar_spiritcaller", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("Wolf_spiritcaller", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("Bjorn_spiritcaller", StringComparison.OrdinalIgnoreCase) < 0)
			{
				return text.IndexOf("Moose_spiritcaller", StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return true;
		}

		private static bool IsNetworkOwner(Character character)
		{
			try
			{
				object obj = AccessTools.Field(typeof(Character), "m_nview")?.GetValue(character);
				if (obj == null)
				{
					return true;
				}
				MethodInfo methodInfo = AccessTools.Method(obj.GetType(), "IsOwner", Type.EmptyTypes, (Type[])null);
				return methodInfo == null || Convert.ToBoolean(methodInfo.Invoke(obj, null));
			}
			catch
			{
				return true;
			}
		}

		private static IEnumerator ApplySpiritCallerStarAfterSpawn(Character character)
		{
			yield return (object)new WaitForSeconds(0.35f);
			Plugin instance = _instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)character == (Object)null || !IsSpiritCallerAnimal(character))
			{
				yield break;
			}
			int instanceID = ((Object)character).GetInstanceID();
			if (_spiritStarProcessed.Contains(instanceID) || !instance._configs.TryGetValue("StaffSpiritCaller", out ServerStaffConfig value) || !value.AllowStarredSummons.IntendedVisible || !value.AllowStarredSummons.Value.Value || !IsNetworkOwner(character))
			{
				yield break;
			}
			float num = Mathf.Clamp(value.TwoStarChance.Value.Value, 0f, 100f);
			float num2 = Mathf.Clamp(value.OneStarChance.Value.Value, 0f, 100f);
			float num3 = Random.Range(0f, 100f);
			int level = ((num3 < num) ? 3 : ((!(num3 < Mathf.Min(100f, num + num2))) ? 1 : 2));
			_spiritStarProcessed.Add(instanceID);
			if (level <= 1)
			{
				yield break;
			}
			MethodInfo setLevel = AccessTools.Method(((object)character).GetType(), "SetLevel", new Type[1] { typeof(int) }, (Type[])null) ?? AccessTools.Method(typeof(Character), "SetLevel", new Type[1] { typeof(int) }, (Type[])null);
			setLevel?.Invoke(character, new object[1] { level });
			try
			{
				object obj = AccessTools.Field(typeof(Character), "m_nview")?.GetValue(character);
				object obj2 = ((obj == null) ? null : AccessTools.Method(obj.GetType(), "GetZDO", Type.EmptyTypes, (Type[])null))?.Invoke(obj, null);
				if (obj2 != null)
				{
					AccessTools.Method(obj2.GetType(), "Set", new Type[2]
					{
						typeof(string),
						typeof(int)
					}, (Type[])null)?.Invoke(obj2, new object[2] { "level", level });
				}
			}
			catch
			{
			}
			yield return (object)new WaitForSeconds(0.25f);
			if ((Object)(object)character != (Object)null)
			{
				setLevel?.Invoke(character, new object[1] { level });
			}
		}

		private static void ApplyAttack(Attack? attack, ServerAttackBaseline baseline, ServerStaffConfig cfg, bool primary)
		{
			if (attack == null)
			{
				return;
			}
			SetFloat(attack, "m_attackEitr", baseline.AttackEitr, cfg.EitrCost);
			SetPercentFloat(attack, "m_attackHealthPercentage", baseline.HealthPercent, cfg.HealthCostPercent);
			SetFloat(attack, "m_attackCooldown", baseline.Cooldown, cfg.Cooldown);
			SetFloat(attack, "m_attackForce", baseline.AttackForce, cfg.AttackForce);
			SetFloat(attack, "m_attackForceForward", baseline.AttackForceForward, cfg.AttackForceForward);
			SetFloat(attack, "m_projectileVel", baseline.ProjectileVelocity, cfg.ProjectileSpeed);
			SetFloat(attack, "m_projectileAccuracyMin", baseline.ProjectileAccuracyMin, cfg.ProjectileAccuracyMin);
			if (primary && cfg.SummonsPerCast.IntendedVisible)
			{
				SetField(attack, "m_projectiles", 1);
			}
			else
			{
				SetInt(attack, "m_projectiles", baseline.Projectiles, cfg.ProjectileCount);
			}
			if (cfg.DrawTime.IntendedVisible)
			{
				float num = MathF.Round(MathF.Max(0.1f, MathF.Min(3f, cfg.DrawTime.Value.Value)) * 10f) / 10f;
				if (Math.Abs(cfg.DrawTime.Value.Value - num) > 0.0001f)
				{
					cfg.DrawTime.Value.Value = num;
				}
				if (baseline.DrawTime > 0f)
				{
					SetField(attack, "m_attackDrawTime", baseline.DrawTime * num);
				}
				if (baseline.AttackWarmup > 0f)
				{
					SetField(attack, "m_attackWarmup", baseline.AttackWarmup * num);
				}
				if (baseline.AttackAnimationSpeedFactor > 0f)
				{
					SetField(attack, "m_attackAnimationSpeedFactor", baseline.AttackAnimationSpeedFactor / num);
				}
			}
			SetFloat(attack, "m_chainChance", baseline.ChainChance, cfg.ChainChance);
			SetFloat(attack, "m_chainChancePerTarget", baseline.ChainChancePerTarget, cfg.ChainChancePerTarget);
			SetInt(attack, "m_chainMaxTargets", baseline.ChainMaxTargets, cfg.ChainMaxTargets);
			SetFloat(attack, "m_chainDelay", baseline.ChainDelay, cfg.ChainDelay);
			SetFloat(attack, "m_chainStartChance", baseline.ChainStartChance, cfg.ChainStartChance);
			SetFloat(attack, "m_chainStartChanceFalloff", baseline.ChainStartChanceFalloff, cfg.ChainStartChanceFalloff);
			SetFloat(attack, "m_chainStartDelay", baseline.ChainStartDelay, cfg.ChainStartDelay);
			SetInt(attack, "m_chainMinTargets", baseline.ChainMinTargets, cfg.ChainMinTargets);
			SetFloat(attack, "m_chainAttackMaxTime", baseline.ChainAttackMaxTime, cfg.ChainAttackMaxTime);
			SetInt(attack, "m_attackChainLevels", baseline.AttackChainLevels, cfg.AttackChainLevels);
		}

		private static void ApplySummonGraph(ServerAttackBaseline baseline, ServerStaffConfig cfg)
		{
			if (cfg.SummonWalkSpeed.IntendedVisible)
			{
				float num = MathF.Round(MathF.Max(0.1f, MathF.Min(3f, cfg.SummonWalkSpeed.Value.Value)) * 10f) / 10f;
				if (Math.Abs(cfg.SummonWalkSpeed.Value.Value - num) > 0.0001f)
				{
					cfg.SummonWalkSpeed.Value.Value = num;
				}
			}
			ApplyGraphField(baseline.SummonGraphFields, new string[1] { "Character" }, "m_health", cfg.SummonHealth);
			ApplyGraphField(baseline.SummonGraphFields, new string[1] { "Humanoid" }, "m_walkSpeed", cfg.SummonWalkSpeed);
			ApplyGraphField(baseline.SummonGraphFields, new string[1] { "Humanoid" }, "m_runSpeed", cfg.SummonWalkSpeed);
			ApplyGraphField(baseline.SummonGraphFields, new string[1] { "Attack" }, "m_damageMultiplier", cfg.SummonDamageMultiplier);
			if (cfg.SummonAttackCooldown.IntendedVisible)
			{
				float num2 = MathF.Round(MathF.Max(0.1f, MathF.Min(3f, cfg.SummonAttackCooldown.Value.Value)) * 10f) / 10f;
				if (Math.Abs(cfg.SummonAttackCooldown.Value.Value - num2) > 0.0001f)
				{
					cfg.SummonAttackCooldown.Value.Value = num2;
				}
				foreach (GraphFieldBaseline summonGraphField in baseline.SummonGraphFields)
				{
					if (summonGraphField.ComponentTypeName == "Attack" && summonGraphField.FieldName == "m_attackCooldown")
					{
						float num3 = Convert.ToSingle(summonGraphField.VanillaValue, CultureInfo.InvariantCulture);
						summonGraphField.Field.SetValue(summonGraphField.Target, num3 / num2);
					}
					else if (summonGraphField.ComponentTypeName == "Attack" && summonGraphField.FieldName == "m_attackDrawTime")
					{
						float num4 = Convert.ToSingle(summonGraphField.VanillaValue, CultureInfo.InvariantCulture);
						if (num4 > 0f)
						{
							summonGraphField.Field.SetValue(summonGraphField.Target, num4 / num2);
						}
					}
					else if (summonGraphField.ComponentTypeName == "Attack" && summonGraphField.FieldName == "m_attackAnimationSpeedFactor")
					{
						float num5 = Convert.ToSingle(summonGraphField.VanillaValue, CultureInfo.InvariantCulture);
						if (num5 > 0f)
						{
							summonGraphField.Field.SetValue(summonGraphField.Target, num5 * num2);
						}
					}
					else if (summonGraphField.FieldName == "m_aiAttackInterval")
					{
						float num6 = Convert.ToSingle(summonGraphField.VanillaValue, CultureInfo.InvariantCulture);
						if (num6 > 0f)
						{
							summonGraphField.Field.SetValue(summonGraphField.Target, num6 / num2);
						}
					}
				}
			}
			ApplyGraphField(baseline.SummonGraphFields, new string[1] { "SpawnAbility" }, "m_spawnRadius", cfg.SummonSpawnRadius);
		}

		private static string? GetSummonStaffPrefab(Character character)
		{
			if ((Object)(object)character == (Object)null)
			{
				return null;
			}
			string name = ((Object)((Component)character).gameObject).name;
			if (name.Contains("Skeleton_Friendly", StringComparison.Ordinal) || name.Contains("SkeletonArcher_Friendly", StringComparison.Ordinal))
			{
				return "StaffSkeleton";
			}
			if (name.Contains("Troll_Summoned", StringComparison.Ordinal))
			{
				return "StaffRedTroll";
			}
			if (name.Contains("staff_greenroots_tentaroot", StringComparison.Ordinal))
			{
				return "StaffGreenRoots";
			}
			if (name.Contains("Boar_spiritcaller", StringComparison.Ordinal) || name.Contains("Wolf_spiritcaller", StringComparison.Ordinal) || name.Contains("Moose_spiritcaller", StringComparison.Ordinal) || name.Contains("Bjorn_spiritcaller", StringComparison.Ordinal))
			{
				return "StaffSpiritCaller";
			}
			return null;
		}

		private static bool IsDeadRaiserSummon(Character character)
		{
			return GetSummonStaffPrefab(character) == "StaffSkeleton";
		}

		private static void ApplyFinalSummonDamage(HitData hit, ServerStaffConfig cfg)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			if (hit != null && !((Object)(object)hit.GetAttacker() == (Object)null))
			{
				ServerTuning<float> summonFinalDamageMultiplier = cfg.SummonFinalDamageMultiplier;
				if (summonFinalDamageMultiplier.IntendedVisible && (int)summonFinalDamageMultiplier.Mode.Value != 0)
				{
					float num = DamageTuning.SpellFactor(summonFinalDamageMultiplier.Mode.Value, summonFinalDamageMultiplier.Value.Value);
					hit.m_damage.m_damage *= num;
					hit.m_damage.m_blunt *= num;
					hit.m_damage.m_slash *= num;
					hit.m_damage.m_pierce *= num;
					hit.m_damage.m_chop *= num;
					hit.m_damage.m_pickaxe *= num;
					hit.m_damage.m_fire *= num;
					hit.m_damage.m_frost *= num;
					hit.m_damage.m_lightning *= num;
					hit.m_damage.m_poison *= num;
					hit.m_damage.m_spirit *= num;
				}
			}
		}

		private static void ApplySummonWeaponDamage(ItemData item, ServerStaffConfig cfg)
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Invalid comparison between Unknown and I4
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			if (item?.m_shared != null)
			{
				SummonWeaponDamageBaseline value = SummonWeaponBaselines.GetValue(item.m_shared, (SharedData _) => new SummonWeaponDamageBaseline(item));
				bool flag = (int)item.m_shared.m_itemType == 4;
				DamageTypes damages = value.Damages;
				ApplyDamageTypes(ref damages, flag ? cfg.SummonArcherDamageMultiplier : cfg.SummonMeleeDamageMultiplier);
				item.m_shared.m_damages = damages;
				DamageTypes damages2 = value.DamagesPerLevel;
				ApplyDamageTypes(ref damages2, cfg.SummonDamagePerLevelMultiplier);
				item.m_shared.m_damagesPerLevel = damages2;
			}
		}

		private static void ApplyDamageTypes(ref DamageTypes damages, ServerTuning<float> setting)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: 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_019c: Unknown result type (might be due to invalid IL or missing references)
			if (setting.IntendedVisible)
			{
				damages.m_damage = Modifier.Apply(damages.m_damage, setting.Mode.Value, setting.Value.Value);
				damages.m_blunt = Modifier.Apply(damages.m_blunt, setting.Mode.Value, setting.Value.Value);
				damages.m_slash = Modifier.Apply(damages.m_slash, setting.Mode.Value, setting.Value.Value);
				damages.m_pierce = Modifier.Apply(damages.m_pierce, setting.Mode.Value, setting.Value.Value);
				damages.m_chop = Modifier.Apply(damages.m_chop, setting.Mode.Value, setting.Value.Value);
				damages.m_pickaxe = Modifier.Apply(damages.m_pickaxe, setting.Mode.Value, setting.Value.Value);
				damages.m_fire = Modifier.Apply(damages.m_fire, setting.Mode.Value, setting.Value.Value);
				damages.m_frost = Modifier.Apply(damages.m_frost, setting.Mode.Value, setting.Value.Value);
				damages.m_lightning = Modifier.Apply(damages.m_lightning, setting.Mode.Value, setting.Value.Value);
				damages.m_poison = Modifier.Apply(damages.m_poison, setting.Mode.Value, setting.Value.Value);
				damages.m_spirit = Modifier.Apply(damages.m_spirit, setting.Mode.Value, setting.Value.Value);
			}
		}

		private static bool TryGetStaffConfig(Character owner, ItemData item, out ServerStaffConfig cfg)
		{
			cfg = null;
			if ((Object)(object)_instance != (Object)null && owner is Player && (Object)(object)item?.m_dropPrefab != (Object)null)
			{
				return _instance._configs.TryGetValue(((Object)item.m_dropPrefab).name, out cfg);
			}
			return false;
		}

		private static bool TryGetCastSpeed(ItemData? weapon, out float speed)
		{
			speed = 1f;
			Plugin instance = _instance;
			object obj;
			if (weapon == null)
			{
				obj = null;
			}
			else
			{
				GameObject dropPrefab = weapon.m_dropPrefab;
				obj = ((dropPrefab != null) ? ((Object)dropPrefab).name : null);
			}
			string text = (string)obj;
			if ((Object)(object)instance == (Object)null || string.IsNullOrEmpty(text) || !instance._configs.TryGetValue(text, out ServerStaffConfig value))
			{
				return false;
			}
			if (!value.DrawTime.IntendedVisible)
			{
				return false;
			}
			float value2 = value.DrawTime.Value.Value;
			if (value2 <= 0f || float.IsNaN(value2) || float.IsInfinity(value2))
			{
				return false;
			}
			speed = 1f / value2;
			return !Mathf.Approximately(speed, 1f);
		}

		private static void ApplyGraph(ServerAttackBaseline baseline, ServerStaffConfig cfg)
		{
			ApplyGraphField(baseline.GraphFields, new string[2] { "Aoe", "EffectArea" }, "m_radius", cfg.ExplosionRadius);
			ApplyGraphField(baseline.GraphFields, new string[3] { "Projectile", "Aoe", "EffectArea" }, "m_force", cfg.Knockback);
			ApplyGraphField(baseline.GraphFields, new string[3] { "Projectile", "Aoe", "EffectArea" }, "m_forceStagger", cfg.Knockback);
			ApplyGraphField(baseline.GraphFields, new string[1] { "StatusEffect" }, "m_duration", cfg.StatusEffectDuration);
			ApplyGraphField(baseline.GraphFields, new string[1] { "StatusEffect" }, "m_ttl", cfg.StatusEffectDuration);
			if (cfg.VengeanceReflectionHeight.IntendedVisible)
			{
				float num = MathF.Max(0.1f, MathF.Min(3f, cfg.VengeanceReflectionHeight.Value.Value));
				foreach (GraphFieldBaseline graphField in baseline.GraphFields)
				{
					if (graphField.ComponentTypeName == "StatusEffect" && (graphField.FieldName == "m_duration" || graphField.FieldName == "m_ttl"))
					{
						float num2 = Convert.ToSingle(graphField.VanillaValue, CultureInfo.InvariantCulture);
						if (num2 > 0f)
						{
							graphField.Field.SetValue(graphField.Target, num2 * num);
						}
					}
				}
			}
			ApplyGraphField(baseline.GraphFields, new string[1] { "SE_Shield" }, "m_absorbDamage", cfg.ShieldAbsorption);
			ApplyGraphField(baseline.GraphFields, new string[1] { "SE_Shield" }, "m_absorbDamagePerSkillLevel", cfg.ShieldAbsorptionPerBloodMagicLevel);
			if (cfg.VengeanceSphereRadius.IntendedVisible)
			{
				ApplyGraphField(baseline.GraphFields, new string[2] { "Aoe", "EffectArea" }, "m_radius", cfg.VengeanceSphereRadius);
			}
			ApplyGraphField(baseline.GraphFields, new string[1] { "Projectile" }, "m_ttl", cfg.ProjectileLifetime);
			ApplyGraphField(baseline.GraphFields, new string[1] { "Projectile" }, "m_ttlMax", cfg.ProjectileLifetime);
			ApplyGraphField(baseline.GraphFields, new string[1] { "Projectile" }, "m_projectileVelocity", cfg.ProjectileVelocity);
			ApplyGraphField(baseline.GraphFields, new string[1] { "Projectile" }, "m_projectileVelocityMax", cfg.ProjectileVelocityMax);
			ApplyGraphField(baseline.GraphFields, new string[1] { "Projectile" }, "m_maxBounces", cfg.ProjectileMaxBounces);
			ApplyGraphField(baseline.GraphFields, new string[3] { "Projectile", "Aoe", "EffectArea" }, "m_damage", cfg.Damage);
			ApplyGraphField(baseline.GraphFields, new string[3] { "Projectile", "Aoe", "EffectArea" }, "m_damagePerHit", cfg.DamagePerHit);
		}

		private static void ApplyGraphField(List<GraphFieldBaseline> fields, string[] componentTypes, string fieldName, ServerTuning<float> setting)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			if (!setting.IntendedVisible)
			{
				return;
			}
			foreach (GraphFieldBaseline field in fields)
			{
				if (Array.IndexOf(componentTypes, field.ComponentTypeName) >= 0 && !(field.FieldName != fieldName))
				{
					float num = Modifier.Apply(Convert.ToSingle(field.VanillaValue, CultureInfo.InvariantCulture), setting.Mode.Value, setting.Value.Value);
					if (fieldName == "m_health" && !DamageTuning.ValidHealth(num))
					{
						field.Field.SetValue(field.Target, field.VanillaValue);
					}
					else if (field.Field.FieldType == typeof(float))
					{
						field.Field.SetValue(field.Target, num);
					}
					else if (field.Field.FieldType == typeof(double))
					{
						field.Field.SetValue(field.Target, (double)num);
					}
					else if (field.Field.FieldType == typeof(int))
					{
						field.Field.SetValue(field.Target, (int)MathF.Round(num));
					}
					else if (field.Field.FieldType == typeof(long))
					{
						field.Field.SetValue(field.Target, (long)MathF.Round(num));
					}
				}
			}
		}

		private static void ApplyGraphField(List<GraphFieldBaseline> fields, string[] componentTypes, string fieldName, ServerTuning<int> setting)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			if (!setting.IntendedVisible)
			{
				return;
			}
			foreach (GraphFieldBaseline field in fields)
			{
				if (Array.IndexOf(componentTypes, field.ComponentTypeName) >= 0 && !(field.FieldName != fieldName))
				{
					int num = Modifier.Apply(Convert.ToInt32(field.VanillaValue, CultureInfo.InvariantCulture), setting.Mode.Value, setting.Value.Value);
					if (fieldName == "m_health" && !DamageTuning.ValidHealth(num))
					{
						field.Field.SetValue(field.Target, field.VanillaValue);
					}
					else if (field.Field.FieldType == typeof(int))
					{
						field.Field.SetValue(field.Target, num);
					}
					else if (field.Field.FieldType == typeof(long))
					{
						field.Field.SetValue(field.Target, (long)num);
					}
				}
			}
		}

		private static void SetPercentFloat(object target, string field, float vanilla, ServerTuning<float> setting)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Invalid comparison between Unknown and I4
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			if (setting.IntendedVisible)
			{
				float num = (((int)setting.Mode.Value == 0) ? vanilla : (((int)setting.Mode.Value == 3) ? setting.Value.Value : Modifier.Apply(vanilla, setting.Mode.Value, setting.Value.Value)));
				SetField(target, field, num);
			}
		}

		private static void SetFloat(object target, string field, float vanilla, ServerTuning<float> setting)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			if (setting.IntendedVisible)
			{
				float num = Modifier.Apply(vanilla, setting.Mode.Value, setting.Value.Value);
				SetField(target, field, num);
			}
		}

		private static void SetInt(object target, string field, int vanilla, ServerTuning<int> setting)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			if (setting.IntendedVisible)
			{
				int num = Modifier.Apply(vanilla, setting.Mode.Value, setting.Value.Value);
				SetField(target, field, num);
			}
		}

		private static void SetBool(object target, string field, bool vanilla, ServerTuning<bool> setting)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			if (setting.IntendedVisible)
			{
				bool flag = (((int)setting.Mode.Value == 0) ? vanilla : setting.Value.Value);
				SetField(target, field, flag);
			}
		}

		private static void SetField(object target, string name, object value)
		{
			FieldInfo field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (!(field == null) && field.FieldType.IsAssignableFrom(value.GetType()))
			{
				field.SetValue(target, value);
			}
		}

		private static float GetFloat(object target, string name)
		{
			object field = GetField(target, name, 0f);
			if (field is float)
			{
				return (floa