Decompiled source of Runes v1.0.0

Runes.dll

Decompiled 17 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 System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HG;
using On.RoR2;
using R2API;
using R2API.Utils;
using RoR2;
using RoR2.ContentManagement;
using RoR2.Orbs;
using RoR2.Skills;
using UnityEngine;
using UnityEngine.Networking;

[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("Runes")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+453ae485f8dc2ed63bdb9a695c3b68c51abe0931")]
[assembly: AssemblyProduct("Runes")]
[assembly: AssemblyTitle("Runes")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
namespace Runes;

public static class KeystoneAPI
{
	internal static readonly HashSet<string> registered = new HashSet<string>();

	internal static readonly HashSet<string> excluded = new HashSet<string>();

	public static BuffDef ConquerorBuff => Conqueror.buff;

	public static BuffDef LethalTempoBuff => LethalTempo.buff;

	public static BuffDef StormraidersSurgeBuff => StormraidersSurge.speedBuff;

	public static BuffDef ElectrocuteBuff => Electrocute.buff;

	public static BuffDef GraspReadyBuff => GraspOfTheUndying.readyBuff;

	public static BuffDef GraspHealthBuff => GraspOfTheUndying.healthBuff;

	public static void RegisterSurvivor(GameObject bodyPrefab)
	{
		if (Object.op_Implicit((Object)(object)bodyPrefab))
		{
			WarnIfLate(((Object)bodyPrefab).name);
			registered.Add(((Object)bodyPrefab).name);
		}
	}

	public static void ExcludeSurvivor(GameObject bodyPrefab)
	{
		if (Object.op_Implicit((Object)(object)bodyPrefab))
		{
			WarnIfLate(((Object)bodyPrefab).name);
			excluded.Add(((Object)bodyPrefab).name);
		}
	}

	public static KeystoneType GetKeystone(CharacterBody body)
	{
		if (!Object.op_Implicit((Object)(object)body))
		{
			return KeystoneType.None;
		}
		KeystoneHandler component = ((Component)body).GetComponent<KeystoneHandler>();
		if (!Object.op_Implicit((Object)(object)component))
		{
			return KeystoneType.None;
		}
		return component.CurrentKeystone;
	}

	public static bool HasKeystoneSlot(CharacterBody body)
	{
		if (Object.op_Implicit((Object)(object)body))
		{
			return Object.op_Implicit((Object)(object)((Component)body).GetComponent<KeystoneHandler>());
		}
		return false;
	}

	private static void WarnIfLate(string bodyName)
	{
		if (KeystonePlugin.slotsInjected)
		{
			KeystonePlugin.Log.LogWarning((object)("KeystoneAPI called for " + bodyName + " after survivors were already set up; call it from your plugin's Awake instead."));
		}
	}
}
internal static class KeystoneConfig
{
	internal static ConfigFile file;

	internal static ConfigEntry<bool> allSurvivors;

	internal static void Init(ConfigFile config)
	{
		file = config;
		allSurvivors = config.Bind<bool>("General", "Enable For All Survivors", true, "Give every survivor a Keystone loadout slot. When false, only survivors whose mod registers them through KeystoneAPI get one. Individual survivors can still be switched off in the [Survivors] section, which fills in after the first launch.");
		Keystone[] keystones = KeystoneContent.keystones;
		for (int i = 0; i < keystones.Length; i++)
		{
			keystones[i].BindConfig(config);
		}
	}

	internal static ConfigEntry<bool> BindSurvivor(string bodyName, string displayName)
	{
		return file.Bind<bool>("Survivors", Sanitize(bodyName), true, "Give " + displayName + " a Keystone slot.");
	}

	private static string Sanitize(string key)
	{
		char[] array = new char[8] { '=', '\n', '\t', '\\', '"', '\'', '[', ']' };
		foreach (char oldChar in array)
		{
			key = key.Replace(oldChar, '_');
		}
		return key;
	}
}
internal static class KeystoneContent
{
	internal static readonly Keystone[] keystones = new Keystone[5]
	{
		new Conqueror(),
		new LethalTempo(),
		new StormraidersSurge(),
		new Electrocute(),
		new GraspOfTheUndying()
	};

	internal static SkillFamily keystoneFamily;

	internal static readonly List<BuffDef> buffDefs = new List<BuffDef>();

	internal static readonly List<SkillDef> skillDefs = new List<SkillDef>();

	internal static readonly List<EffectDef> effectDefs = new List<EffectDef>();

	private static GameObject prefabHolder;

	private static readonly Dictionary<SkillDef, Keystone> keystoneBySkill = new Dictionary<SkillDef, Keystone>();

	internal static Keystone Get(SkillDef skillDef)
	{
		if ((Object)(object)skillDef != (Object)null && keystoneBySkill.TryGetValue(skillDef, out var value))
		{
			return value;
		}
		return null;
	}

	internal static void Init()
	{
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0094: Unknown result type (might be due to invalid IL or missing references)
		Sprite val = LoadSprite("None_rune");
		List<Variant> list = new List<Variant>();
		list.Add(Variant(AddSkill("NONE", val, null)));
		LanguageAPI.Add("KEYSTONE_NONE_NAME", "<color=#9a9a9a>No Keystone</color>");
		LanguageAPI.Add("KEYSTONE_NONE_DESC", "Play without a keystone.");
		Keystone[] array = keystones;
		foreach (Keystone keystone in array)
		{
			keystone.icon = LoadSprite(keystone.IconName) ?? val;
			keystone.CreateBuffs();
			keystone.CreateEffects();
			list.Add(Variant(AddSkill(keystone.Id, keystone.icon, keystone)));
			LanguageAPI.Add("KEYSTONE_" + keystone.Id + "_NAME", "<color=#ffa700>" + keystone.DisplayName + "</color>");
			LanguageAPI.Add("KEYSTONE_" + keystone.Id + "_DESC", keystone.Description());
		}
		keystoneFamily = ScriptableObject.CreateInstance<SkillFamily>();
		((Object)keystoneFamily).name = "KeystoneFamily";
		keystoneFamily.defaultVariantIndex = 0u;
		keystoneFamily.variants = list.ToArray();
	}

	internal static BuffDef AddBuff(string name, Sprite icon, bool canStack, bool isDebuff)
	{
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		BuffDef val = ScriptableObject.CreateInstance<BuffDef>();
		((Object)val).name = name;
		val.buffColor = Color.white;
		val.canStack = canStack;
		val.isDebuff = isDebuff;
		val.eliteDef = null;
		val.iconSprite = icon;
		buffDefs.Add(val);
		return val;
	}

	internal static GameObject CreatePrefab(string name)
	{
		//IL_0031: 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)
		//IL_004d: Expected O, but got Unknown
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Expected O, but got Unknown
		if (!Object.op_Implicit((Object)(object)prefabHolder))
		{
			prefabHolder = new GameObject("RunesPrefabs");
			prefabHolder.SetActive(false);
			Object.DontDestroyOnLoad((Object)(object)prefabHolder);
		}
		GameObject val = new GameObject(name);
		val.transform.SetParent(prefabHolder.transform, false);
		return val;
	}

	internal static void AddEffect(GameObject prefab)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: Expected O, but got Unknown
		effectDefs.Add(new EffectDef(prefab)
		{
			shouldPool = false
		});
	}

	private static SkillDef AddSkill(string id, Sprite icon, Keystone keystone)
	{
		SkillDef val = ScriptableObject.CreateInstance<SkillDef>();
		val.skillName = "KEYSTONE_" + id;
		((Object)val).name = "KEYSTONE_" + id;
		val.skillNameToken = "KEYSTONE_" + id + "_NAME";
		val.skillDescriptionToken = "KEYSTONE_" + id + "_DESC";
		val.icon = icon;
		val.baseMaxStock = 1;
		val.rechargeStock = 1;
		val.requiredStock = 1;
		val.stockToConsume = 1;
		val.isCombatSkill = false;
		val.fullRestockOnAssign = true;
		val.keywordTokens = new string[0];
		skillDefs.Add(val);
		if (keystone != null)
		{
			keystoneBySkill[val] = keystone;
		}
		return val;
	}

	private static Variant Variant(SkillDef skillDef)
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_002c: Expected O, but got Unknown
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		Variant result = new Variant
		{
			skillDef = skillDef,
			unlockableDef = null
		};
		((Variant)(ref result)).viewableNode = new Node(skillDef.skillNameToken, false, (Node)null);
		return result;
	}

	private static Sprite LoadSprite(string name)
	{
		//IL_0063: Unknown result type (might be due to invalid IL or missing references)
		//IL_0069: Expected O, but got Unknown
		//IL_0098: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
		using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Runes.Icons." + name + ".png");
		if (stream == null)
		{
			KeystonePlugin.Log.LogError((object)("Missing embedded icon: " + name));
			return null;
		}
		byte[] array;
		using (MemoryStream memoryStream = new MemoryStream())
		{
			stream.CopyTo(memoryStream);
			array = memoryStream.ToArray();
		}
		Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
		ImageConversion.LoadImage(val, array);
		((Texture)val).wrapMode = (TextureWrapMode)1;
		((Texture)val).filterMode = (FilterMode)1;
		return Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f);
	}
}
internal class KeystoneContentPack : IContentPackProvider
{
	private readonly ContentPack contentPack = new ContentPack();

	public string identifier => "com.Lemonlust.Runes";

	internal void Initialize()
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Expected O, but got Unknown
		ContentManager.collectContentPackProviders += (CollectContentPackProvidersDelegate)delegate(AddContentPackProviderDelegate add)
		{
			add.Invoke((IContentPackProvider)(object)this);
		};
	}

	public IEnumerator LoadStaticContentAsync(LoadStaticContentAsyncArgs args)
	{
		contentPack.identifier = identifier;
		contentPack.buffDefs.Add(KeystoneContent.buffDefs.ToArray());
		contentPack.skillDefs.Add(KeystoneContent.skillDefs.ToArray());
		contentPack.effectDefs.Add(KeystoneContent.effectDefs.ToArray());
		contentPack.skillFamilies.Add((SkillFamily[])(object)new SkillFamily[1] { KeystoneContent.keystoneFamily });
		args.ReportProgress(1f);
		yield break;
	}

	public IEnumerator GenerateContentPackAsync(GetContentPackAsyncArgs args)
	{
		ContentPack.Copy(contentPack, args.output);
		args.ReportProgress(1f);
		yield break;
	}

	public IEnumerator FinalizeAsync(FinalizeAsyncArgs args)
	{
		args.ReportProgress(1f);
		yield break;
	}
}
public class KeystoneHandler : MonoBehaviour, IOnDamageDealtServerReceiver
{
	private const float DecayInterval = 0.5f;

	internal CharacterBody body;

	internal HealthComponent healthComponent;

	private GenericSkill keystoneSlot;

	private float uptimeStopwatch;

	private float decayStopwatch;

	internal Keystone Current
	{
		get
		{
			if (!Object.op_Implicit((Object)(object)keystoneSlot))
			{
				return null;
			}
			return KeystoneContent.Get(keystoneSlot.skillDef);
		}
	}

	public KeystoneType CurrentKeystone => Current?.Type ?? KeystoneType.None;

	private void Awake()
	{
		body = ((Component)this).GetComponent<CharacterBody>();
		healthComponent = ((Component)this).GetComponent<HealthComponent>();
		GenericSkill[] components = ((Component)this).GetComponents<GenericSkill>();
		foreach (GenericSkill val in components)
		{
			if ((Object)(object)val.skillFamily == (Object)(object)KeystoneContent.keystoneFamily)
			{
				keystoneSlot = val;
				break;
			}
		}
	}

	public void OnDamageDealtServer(DamageReport damageReport)
	{
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		if (damageReport == null || damageReport.damageInfo == null || !Object.op_Implicit((Object)(object)damageReport.victimBody) || (Object)(object)damageReport.victimBody == (Object)(object)body || (Object)(object)damageReport.attackerBody != (Object)(object)body || damageReport.victimTeamIndex == damageReport.attackerTeamIndex)
		{
			return;
		}
		Keystone current = Current;
		if (current == null)
		{
			return;
		}
		bool flag = damageReport.damageInfo.procCoefficient > 0f;
		if (flag || !current.RequiresProcCoefficient)
		{
			if (flag)
			{
				uptimeStopwatch = 0f;
			}
			current.OnHitServer(this, damageReport);
		}
	}

	private void FixedUpdate()
	{
		if (NetworkServer.active && Object.op_Implicit((Object)(object)body))
		{
			Current?.FixedUpdateServer(this);
		}
	}

	internal void AddStack(BuffDef buff, int maxStacks)
	{
		if (body.GetBuffCount(buff) < maxStacks)
		{
			body.AddBuff(buff);
		}
	}

	internal void DecayStacks(BuffDef buff, float duration)
	{
		if (body.GetBuffCount(buff) <= 0)
		{
			uptimeStopwatch = 0f;
			return;
		}
		uptimeStopwatch += Time.fixedDeltaTime;
		decayStopwatch += Time.fixedDeltaTime;
		if (uptimeStopwatch > duration && decayStopwatch >= 0.5f)
		{
			body.RemoveBuff(buff);
			decayStopwatch = 0f;
		}
	}
}
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
[BepInPlugin("com.Lemonlust.Runes", "Runes", "1.0.0")]
public class KeystonePlugin : BaseUnityPlugin
{
	public const string GUID = "com.Lemonlust.Runes";

	public const string NAME = "Runes";

	public const string VERSION = "1.0.0";

	internal static ManualLogSource Log;

	internal static bool slotsInjected;

	private void Awake()
	{
		//IL_003d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0047: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		try
		{
			KeystoneConfig.Init(((BaseUnityPlugin)this).Config);
			KeystoneContent.Init();
			new KeystoneContentPack().Initialize();
			ContentManager.onContentPacksAssigned += AddKeystoneSlots;
			CharacterBody.RecalculateStats += new hook_RecalculateStats(CharacterBody_RecalculateStats);
			GlobalEventManager.onCharacterDeathGlobal += OnCharacterDeathGlobal;
			Keystone[] keystones = KeystoneContent.keystones;
			for (int i = 0; i < keystones.Length; i++)
			{
				keystones[i].Hook();
			}
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)ex);
		}
	}

	private void OnDestroy()
	{
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Expected O, but got Unknown
		ContentManager.onContentPacksAssigned -= AddKeystoneSlots;
		CharacterBody.RecalculateStats -= new hook_RecalculateStats(CharacterBody_RecalculateStats);
		GlobalEventManager.onCharacterDeathGlobal -= OnCharacterDeathGlobal;
		Keystone[] keystones = KeystoneContent.keystones;
		for (int i = 0; i < keystones.Length; i++)
		{
			keystones[i].Unhook();
		}
	}

	private static void AddKeystoneSlots(ReadOnlyArray<ReadOnlyContentPack> packs)
	{
		//IL_001f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		if (slotsInjected)
		{
			return;
		}
		slotsInjected = true;
		int num = 0;
		for (int i = 0; i < packs.Length; i++)
		{
			AssetEnumerator<SurvivorDef> enumerator = ((ReadOnlyContentPack)packs[i]).survivorDefs.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					SurvivorDef current = enumerator.Current;
					if (!Object.op_Implicit((Object)(object)current) || !Object.op_Implicit((Object)(object)current.bodyPrefab))
					{
						continue;
					}
					try
					{
						if (ShouldHaveSlot(current))
						{
							AddSlot(current.bodyPrefab);
							num++;
						}
					}
					catch (Exception ex)
					{
						Log.LogError((object)("Couldn't add a Keystone slot to " + ((Object)current.bodyPrefab).name + ": " + ex));
					}
				}
			}
			finally
			{
				((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
		}
		Log.LogInfo((object)("Added Keystone slot to " + num + " survivors."));
	}

	private static bool ShouldHaveSlot(SurvivorDef survivorDef)
	{
		string name = ((Object)survivorDef.bodyPrefab).name;
		if (KeystoneAPI.excluded.Contains(name))
		{
			return false;
		}
		if (!KeystoneConfig.BindSurvivor(name, survivorDef.displayNameToken ?? name).Value)
		{
			return false;
		}
		if (!KeystoneConfig.allSurvivors.Value)
		{
			return KeystoneAPI.registered.Contains(name);
		}
		return true;
	}

	private static void AddSlot(GameObject bodyPrefab)
	{
		GenericSkill[] components = bodyPrefab.GetComponents<GenericSkill>();
		for (int i = 0; i < components.Length; i++)
		{
			if ((Object)(object)components[i].skillFamily == (Object)(object)KeystoneContent.keystoneFamily)
			{
				return;
			}
		}
		GenericSkill obj = bodyPrefab.AddComponent<GenericSkill>();
		obj.skillName = "Keystone";
		obj._skillFamily = KeystoneContent.keystoneFamily;
		obj.hideInCharacterSelect = false;
		if (!Object.op_Implicit((Object)(object)bodyPrefab.GetComponent<KeystoneHandler>()))
		{
			bodyPrefab.AddComponent<KeystoneHandler>();
		}
	}

	private static void CharacterBody_RecalculateStats(orig_RecalculateStats orig, CharacterBody self)
	{
		orig.Invoke(self);
		if (Object.op_Implicit((Object)(object)self))
		{
			float levelSteps = Mathf.Floor(self.level / 4f);
			Keystone[] keystones = KeystoneContent.keystones;
			for (int i = 0; i < keystones.Length; i++)
			{
				keystones[i].RecalculateStats(self, levelSteps);
			}
		}
	}

	private static void OnCharacterDeathGlobal(DamageReport damageReport)
	{
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		//IL_002d: Unknown result type (might be due to invalid IL or missing references)
		if (NetworkServer.active && damageReport != null && Object.op_Implicit((Object)(object)damageReport.attackerBody) && Object.op_Implicit((Object)(object)damageReport.victimBody) && damageReport.victimTeamIndex != damageReport.attackerTeamIndex)
		{
			KeystoneHandler component = ((Component)damageReport.attackerBody).GetComponent<KeystoneHandler>();
			(Object.op_Implicit((Object)(object)component) ? component.Current : null)?.OnKillServer(component, damageReport);
		}
	}
}
internal sealed class Conqueror : Keystone
{
	internal static ConfigEntry<int> maxStacks;

	internal static ConfigEntry<float> duration;

	internal static ConfigEntry<float> damagePerStack;

	internal static ConfigEntry<float> damagePerStackPerLevelStep;

	internal static ConfigEntry<float> healFraction;

	internal static BuffDef buff;

	public override KeystoneType Type => KeystoneType.Conqueror;

	public override string Id => "CONQUEROR";

	public override string DisplayName => "Conqueror";

	public override string IconName => "Conqueror_rune";

	internal override void BindConfig(ConfigFile config)
	{
		maxStacks = config.Bind<int>("Conqueror", "Max Stacks", 12, "");
		duration = config.Bind<float>("Conqueror", "Stack Duration", 4f, "Seconds without hitting anything before stacks start falling off.");
		damagePerStack = config.Bind<float>("Conqueror", "Base Damage Per Stack", 0.6f, "Flat base damage added per stack.");
		damagePerStackPerLevelStep = config.Bind<float>("Conqueror", "Base Damage Per Stack Per 4 Levels", 0.045f, "");
		healFraction = config.Bind<float>("Conqueror", "Heal Fraction At Max Stacks", 0.06f, "Fraction of damage dealt returned as healing while fully stacked.");
	}

	internal override void CreateBuffs()
	{
		buff = KeystoneContent.AddBuff("KeystoneConquerorBuff", icon, canStack: true, isDebuff: false);
	}

	internal override string Description()
	{
		return "<color=#c9aa71>Hits with a proc coefficient</color> against enemies grant <color=#ffffff>1</color> stack of Conqueror, up to " + maxStacks.Value + " stacks. Each stack grants <color=#f68835>" + Keystone.Num(damagePerStack.Value) + "</color> <color=#d62d20>(+" + Keystone.Num(damagePerStackPerLevelStep.Value) + " every 4 levels)</color> bonus base damage. While fully stacked, <color=#c9aa71>heal</color> for <color=#008744>" + Keystone.Pct(healFraction.Value) + " of damage dealt</color>.";
	}

	internal override void OnHitServer(KeystoneHandler handler, DamageReport damageReport)
	{
		//IL_0057: 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)
		handler.AddStack(buff, maxStacks.Value);
		if (handler.body.GetBuffCount(buff) >= maxStacks.Value && Object.op_Implicit((Object)(object)handler.healthComponent))
		{
			handler.healthComponent.Heal(damageReport.damageDealt * healFraction.Value, default(ProcChainMask), true);
		}
	}

	internal override void FixedUpdateServer(KeystoneHandler handler)
	{
		handler.DecayStacks(buff, duration.Value);
	}

	internal override void RecalculateStats(CharacterBody body, float levelSteps)
	{
		int buffCount = body.GetBuffCount(buff);
		if (buffCount > 0)
		{
			body.damage += (float)buffCount * (damagePerStack.Value + levelSteps * damagePerStackPerLevelStep.Value);
		}
	}
}
internal sealed class Electrocute : Keystone
{
	private const int HitsToStrike = 3;

	internal static ConfigEntry<float> window;

	internal static ConfigEntry<float> damage;

	internal static ConfigEntry<float> damagePerLevelStep;

	internal static ConfigEntry<float> retargetRange;

	internal static BuffDef buff;

	public override KeystoneType Type => KeystoneType.Electrocute;

	public override string Id => "ELECTROCUTE";

	public override string DisplayName => "Electrocute";

	public override string IconName => "Electrocute_rune";

	internal override void BindConfig(ConfigFile config)
	{
		window = config.Bind<float>("Electrocute", "Stack Window", 3f, "Seconds each Electrocute stack lasts. Three stacks at once call down lightning.");
		damage = config.Bind<float>("Electrocute", "Damage Coefficient", 2f, "Lightning damage as a multiple of your damage stat.");
		damagePerLevelStep = config.Bind<float>("Electrocute", "Damage Coefficient Per 4 Levels", 0.75f, "");
		retargetRange = config.Bind<float>("Electrocute", "Retarget Range", 30f, "If the third hit kills its target, the lightning strikes the nearest enemy within this many meters instead.");
	}

	internal override void CreateBuffs()
	{
		buff = KeystoneContent.AddBuff("KeystoneElectrocuteBuff", icon, canStack: true, isDebuff: false);
	}

	internal override string Description()
	{
		return "<color=#c9aa71>Hits with a proc coefficient</color> against enemies grant <color=#c9aa71>1</color> stack. Reaching <color=#ffffff>3</color> stacks within " + Keystone.Num(window.Value) + " seconds, on any enemies, strikes the enemy you hit with lightning for <color=#f68835>" + Keystone.Pct(damage.Value) + "</color> <color=#d62d20>(+" + Keystone.Pct(damagePerLevelStep.Value) + " every 4 levels)</color> damage. If that hit was a kill, the lightning strikes the nearest enemy instead.";
	}

	internal override void OnHitServer(KeystoneHandler handler, DamageReport damageReport)
	{
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		if (!((ProcChainMask)(ref damageReport.damageInfo.procChainMask)).HasProc((ProcType)18))
		{
			CharacterBody body = handler.body;
			if (body.GetBuffCount(buff) + 1 >= 3)
			{
				body.ClearTimedBuffs(buff.buffIndex);
				Fire(body, damageReport.victimBody);
			}
			else
			{
				body.AddTimedBuff(buff, window.Value);
			}
		}
	}

	private static void Fire(CharacterBody attacker, CharacterBody victim)
	{
		//IL_0086: Unknown result type (might be due to invalid IL or missing references)
		//IL_009a: Unknown result type (might be due to invalid IL or missing references)
		//IL_009f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ad: 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_00c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d8: 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_00de: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f5: Expected O, but got Unknown
		//IL_0045: 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)
		HurtBox val = null;
		if (Object.op_Implicit((Object)(object)victim) && Object.op_Implicit((Object)(object)victim.healthComponent) && victim.healthComponent.alive)
		{
			val = victim.mainHurtBox;
		}
		if (!Object.op_Implicit((Object)(object)val))
		{
			val = FindNearestEnemy(attacker, Object.op_Implicit((Object)(object)victim) ? victim.corePosition : attacker.corePosition, victim);
		}
		if (Object.op_Implicit((Object)(object)val))
		{
			float num = Mathf.Floor(attacker.level / 4f);
			float num2 = damage.Value + num * damagePerLevelStep.Value;
			ProcChainMask procChainMask = default(ProcChainMask);
			((ProcChainMask)(ref procChainMask)).AddProc((ProcType)18);
			OrbManager.instance.AddOrb((Orb)new SimpleLightningStrikeOrb
			{
				attacker = ((Component)attacker).gameObject,
				damageColorIndex = (DamageColorIndex)3,
				damageValue = attacker.damage * num2,
				isCrit = Util.CheckRoll(attacker.crit, attacker.master),
				procChainMask = procChainMask,
				procCoefficient = 1f,
				target = val
			});
		}
	}

	private static HurtBox FindNearestEnemy(CharacterBody attacker, Vector3 origin, CharacterBody exclude)
	{
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		//IL_0050: Expected O, but got Unknown
		if (!Object.op_Implicit((Object)(object)attacker.teamComponent))
		{
			return null;
		}
		BullseyeSearch val = new BullseyeSearch
		{
			searchOrigin = origin,
			maxDistanceFilter = retargetRange.Value,
			teamMaskFilter = TeamMask.GetEnemyTeams(attacker.teamComponent.teamIndex),
			sortMode = (SortMode)1,
			filterByLoS = false
		};
		val.RefreshCandidates();
		if (Object.op_Implicit((Object)(object)exclude))
		{
			val.FilterOutGameObject(((Component)exclude).gameObject);
		}
		foreach (HurtBox result in val.GetResults())
		{
			if (Object.op_Implicit((Object)(object)result) && Object.op_Implicit((Object)(object)result.healthComponent) && result.healthComponent.alive)
			{
				return result;
			}
		}
		return null;
	}
}
internal sealed class GraspOfTheUndying : Keystone
{
	internal static ConfigEntry<float> cooldown;

	internal static ConfigEntry<float> damage;

	internal static ConfigEntry<float> heal;

	internal static ConfigEntry<float> healthPerStack;

	internal static BuffDef readyBuff;

	internal static BuffDef healthBuff;

	public override KeystoneType Type => KeystoneType.GraspOfTheUndying;

	public override string Id => "GRASP_OF_THE_UNDYING";

	public override string DisplayName => "Grasp of the Undying";

	public override string IconName => "Grasp_of_the_Undying_rune";

	internal override void BindConfig(ConfigFile config)
	{
		cooldown = config.Bind<float>("Grasp of the Undying", "Cooldown", 4f, "Seconds after an empowered hit before the next one is ready.");
		damage = config.Bind<float>("Grasp of the Undying", "Damage Max Health Fraction", 0.05f, "Bonus damage on an empowered hit, as a fraction of your max health.");
		heal = config.Bind<float>("Grasp of the Undying", "Heal Max Health Fraction", 0.02f, "Healing on an empowered hit, as a fraction of your max health.");
		healthPerStack = config.Bind<float>("Grasp of the Undying", "Max Health Per Proc", 1f, "Permanent max health gained per empowered hit. Lasts for the rest of the run.");
	}

	internal override void CreateBuffs()
	{
		readyBuff = KeystoneContent.AddBuff("KeystoneGraspReadyBuff", icon, canStack: false, isDebuff: false);
		healthBuff = KeystoneContent.AddBuff("KeystoneGraspHealthBuff", icon, canStack: true, isDebuff: false);
	}

	internal override string Description()
	{
		return "Every <color=#ffffff>" + Keystone.Num(cooldown.Value) + "</color> seconds, your next <color=#c9aa71>hit with a proc coefficient</color> against an enemy deals bonus damage equal to <color=#f68835>" + Keystone.Pct(damage.Value) + " of your max health</color>, <color=#c9aa71>heals</color> you for <color=#008744>" + Keystone.Pct(heal.Value) + " of your max health</color>, and permanently increases your <color=#c9aa71>max health</color> by <color=#008744>" + Keystone.Num(healthPerStack.Value) + "</color> for the rest of the run.";
	}

	internal override void OnHitServer(KeystoneHandler handler, DamageReport damageReport)
	{
		//IL_010f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0115: Unknown result type (might be due to invalid IL or missing references)
		//IL_007e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0083: Unknown result type (might be due to invalid IL or missing references)
		//IL_008f: Unknown result type (might be due to invalid IL or missing references)
		//IL_009b: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e2: 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_00ee: Expected O, but got Unknown
		//IL_013a: Unknown result type (might be due to invalid IL or missing references)
		CharacterBody body = handler.body;
		if (body.HasBuff(readyBuff))
		{
			body.RemoveBuff(readyBuff);
			GraspTracker tracker = GetTracker(body);
			if (Object.op_Implicit((Object)(object)tracker))
			{
				tracker.stopwatch = 0f;
			}
			float num = (Object.op_Implicit((Object)(object)handler.healthComponent) ? handler.healthComponent.fullHealth : body.maxHealth);
			CharacterBody victimBody = damageReport.victimBody;
			HealthComponent healthComponent = victimBody.healthComponent;
			if (Object.op_Implicit((Object)(object)healthComponent) && healthComponent.alive)
			{
				healthComponent.TakeDamage(new DamageInfo
				{
					attacker = ((Component)body).gameObject,
					inflictor = ((Component)body).gameObject,
					damage = num * damage.Value,
					crit = false,
					position = victimBody.corePosition,
					force = Vector3.zero,
					procChainMask = default(ProcChainMask),
					procCoefficient = 0f,
					damageColorIndex = (DamageColorIndex)3
				});
			}
			if (Object.op_Implicit((Object)(object)handler.healthComponent))
			{
				handler.healthComponent.Heal(num * heal.Value, default(ProcChainMask), true);
			}
			if (Object.op_Implicit((Object)(object)tracker))
			{
				tracker.stacks++;
				body.SetBuffCount(healthBuff.buffIndex, tracker.stacks);
			}
			else
			{
				body.AddBuff(healthBuff);
			}
		}
	}

	internal override void FixedUpdateServer(KeystoneHandler handler)
	{
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		CharacterBody body = handler.body;
		GraspTracker tracker = GetTracker(body);
		if (!Object.op_Implicit((Object)(object)tracker))
		{
			return;
		}
		if ((Object)(object)tracker.body != (Object)(object)body)
		{
			tracker.body = body;
			tracker.stopwatch = 0f;
			if (tracker.stacks > 0)
			{
				body.SetBuffCount(healthBuff.buffIndex, tracker.stacks);
			}
		}
		if (!body.HasBuff(readyBuff))
		{
			tracker.stopwatch += Time.fixedDeltaTime;
			if (tracker.stopwatch >= cooldown.Value)
			{
				body.AddBuff(readyBuff);
			}
		}
	}

	internal override void RecalculateStats(CharacterBody body, float levelSteps)
	{
		int buffCount = body.GetBuffCount(healthBuff);
		if (buffCount > 0)
		{
			float num = ((body.cursePenalty > 0f) ? body.cursePenalty : 1f);
			body.maxHealth += (float)buffCount * healthPerStack.Value / num;
		}
	}

	private static GraspTracker GetTracker(CharacterBody body)
	{
		CharacterMaster master = body.master;
		if (!Object.op_Implicit((Object)(object)master))
		{
			return null;
		}
		GraspTracker graspTracker = ((Component)master).GetComponent<GraspTracker>();
		if (!Object.op_Implicit((Object)(object)graspTracker))
		{
			graspTracker = ((Component)master).gameObject.AddComponent<GraspTracker>();
		}
		return graspTracker;
	}
}
public class GraspTracker : MonoBehaviour
{
	public int stacks;

	internal float stopwatch;

	internal CharacterBody body;
}
public enum KeystoneType
{
	None = 0,
	Conqueror = 1,
	LethalTempo = 2,
	StormraidersSurge = 4,
	Electrocute = 8,
	GraspOfTheUndying = 0x10,
	GlacialAugment = 0x20
}
internal abstract class Keystone
{
	protected const string Hit = "<color=#c9aa71>Hits with a proc coefficient</color>";

	internal Sprite icon;

	public abstract KeystoneType Type { get; }

	public abstract string Id { get; }

	public abstract string DisplayName { get; }

	public abstract string IconName { get; }

	internal virtual bool RequiresProcCoefficient => true;

	internal abstract void BindConfig(ConfigFile config);

	internal virtual void CreateBuffs()
	{
	}

	internal virtual void CreateEffects()
	{
	}

	internal abstract string Description();

	internal virtual void Hook()
	{
	}

	internal virtual void Unhook()
	{
	}

	internal virtual void OnHitServer(KeystoneHandler handler, DamageReport damageReport)
	{
	}

	internal virtual void FixedUpdateServer(KeystoneHandler handler)
	{
	}

	internal virtual void OnKillServer(KeystoneHandler handler, DamageReport damageReport)
	{
	}

	internal virtual void RecalculateStats(CharacterBody body, float levelSteps)
	{
	}

	protected static string Num(float v)
	{
		return v.ToString("0.###", CultureInfo.InvariantCulture);
	}

	protected static string Pct(float v)
	{
		return Num(v * 100f) + "%";
	}
}
internal sealed class LethalTempo : Keystone
{
	internal static ConfigEntry<int> maxStacks;

	internal static ConfigEntry<float> duration;

	internal static ConfigEntry<float> attackSpeedPerStack;

	internal static BuffDef buff;

	public override KeystoneType Type => KeystoneType.LethalTempo;

	public override string Id => "LETHAL_TEMPO";

	public override string DisplayName => "Lethal Tempo";

	public override string IconName => "Lethal_Tempo_rune";

	internal override void BindConfig(ConfigFile config)
	{
		maxStacks = config.Bind<int>("Lethal Tempo", "Max Stacks", 6, "");
		duration = config.Bind<float>("Lethal Tempo", "Stack Duration", 6f, "Seconds without hitting anything before stacks start falling off.");
		attackSpeedPerStack = config.Bind<float>("Lethal Tempo", "Attack Speed Per Stack", 0.1f, "Fraction of attack speed added per stack.");
	}

	internal override void CreateBuffs()
	{
		buff = KeystoneContent.AddBuff("KeystoneLethalTempoBuff", icon, canStack: true, isDebuff: false);
	}

	internal override string Description()
	{
		return "<color=#c9aa71>Hits with a proc coefficient</color> against enemies grant <color=#ffffff>1</color> stack of Lethal Tempo, up to " + maxStacks.Value + " stacks. Gain <color=#f68835>" + Keystone.Pct(attackSpeedPerStack.Value) + "</color> bonus attack speed per stack, up to <color=#f68835>" + Keystone.Pct(attackSpeedPerStack.Value * (float)maxStacks.Value) + "</color> at maximum stacks.";
	}

	internal override void OnHitServer(KeystoneHandler handler, DamageReport damageReport)
	{
		handler.AddStack(buff, maxStacks.Value);
	}

	internal override void FixedUpdateServer(KeystoneHandler handler)
	{
		handler.DecayStacks(buff, duration.Value);
	}

	internal override void RecalculateStats(CharacterBody body, float levelSteps)
	{
		int buffCount = body.GetBuffCount(buff);
		if (buffCount > 0)
		{
			body.attackSpeed += body.attackSpeed * (float)buffCount * attackSpeedPerStack.Value;
		}
	}
}
internal sealed class StormraidersSurge : Keystone
{
	internal static ConfigEntry<float> damageThreshold;

	internal static ConfigEntry<float> window;

	internal static ConfigEntry<float> duration;

	internal static ConfigEntry<float> speed;

	internal static ConfigEntry<float> slowResistance;

	internal static BuffDef speedBuff;

	private static readonly string[] slowBuffNames = new string[7] { "bdSlow30", "bdSlow50", "bdSlow60", "bdSlow80", "bdClayGoo", "bdCripple", "bdJailerSlow" };

	private static readonly float[] slowBuffAmounts = new float[7] { 0.3f, 0.5f, 0.6f, 0.8f, 0.5f, 1f, 1f };

	private static BuffIndex[] slowBuffs;

	public override KeystoneType Type => KeystoneType.StormraidersSurge;

	public override string Id => "PHASE_RUSH";

	public override string DisplayName => "Stormraider's Surge";

	public override string IconName => "Stormraiders_Surge_rune";

	internal override bool RequiresProcCoefficient => false;

	internal override void BindConfig(ConfigFile config)
	{
		damageThreshold = config.Bind<float>("Stormraiders Surge", "Damage Threshold", 0.25f, "Damage to one enemy, as a fraction of its max health (plus shield), needed to trigger.");
		window = config.Bind<float>("Stormraiders Surge", "Damage Window", 3f, "Seconds the damage has to be dealt in.");
		duration = config.Bind<float>("Stormraiders Surge", "Duration", 4f, "");
		speed = config.Bind<float>("Stormraiders Surge", "Movement Speed", 0.48f, "Fraction of movement speed added.");
		slowResistance = config.Bind<float>("Stormraiders Surge", "Slow Resistance", 0.5f, "Fraction of incoming slows ignored while active.");
	}

	internal override void CreateBuffs()
	{
		speedBuff = KeystoneContent.AddBuff("KeystoneStormraidersSurgeBuff", icon, canStack: false, isDebuff: false);
	}

	internal override string Description()
	{
		return "Dealing damage equal to <color=#f68835>" + Keystone.Pct(damageThreshold.Value) + " of an enemy's max health</color> within " + Keystone.Num(window.Value) + " seconds grants <color=#f68835>" + Keystone.Pct(speed.Value) + "</color> bonus <color=#c9aa71>movement speed</color> and <color=#c9aa71>" + Keystone.Pct(slowResistance.Value) + " slow resistance</color> for " + Keystone.Num(duration.Value) + " seconds.";
	}

	internal override void OnHitServer(KeystoneHandler handler, DamageReport damageReport)
	{
		CharacterBody body = handler.body;
		HealthComponent healthComponent = damageReport.victimBody.healthComponent;
		if (Object.op_Implicit((Object)(object)healthComponent) && !(damageReport.damageDealt <= 0f))
		{
			SurgeTracker surgeTracker = ((Component)body).GetComponent<SurgeTracker>();
			if (!Object.op_Implicit((Object)(object)surgeTracker))
			{
				surgeTracker = ((Component)body).gameObject.AddComponent<SurgeTracker>();
			}
			float num = surgeTracker.AddDamage(healthComponent, damageReport.damageDealt, window.Value);
			if (!body.HasBuff(speedBuff) && num >= healthComponent.fullCombinedHealth * damageThreshold.Value)
			{
				surgeTracker.Clear();
				body.AddTimedBuff(speedBuff, duration.Value);
			}
		}
	}

	internal override void RecalculateStats(CharacterBody body, float levelSteps)
	{
		if (body.HasBuff(speedBuff))
		{
			body.moveSpeed += body.moveSpeed * speed.Value;
			float num = Mathf.Clamp01(slowResistance.Value);
			float num2 = TotalSlow(body);
			if (num > 0f && num2 > 0f)
			{
				body.moveSpeed *= (1f + num2) / (1f + num2 * (1f - num));
			}
		}
	}

	private static float TotalSlow(CharacterBody body)
	{
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_002f: Expected I4, but got Unknown
		if (slowBuffs == null)
		{
			slowBuffs = (BuffIndex[])(object)new BuffIndex[slowBuffNames.Length];
			for (int i = 0; i < slowBuffNames.Length; i++)
			{
				slowBuffs[i] = (BuffIndex)(int)BuffCatalog.FindBuffIndex(slowBuffNames[i]);
			}
		}
		float num = 0f;
		for (int j = 0; j < slowBuffs.Length; j++)
		{
			if ((int)slowBuffs[j] != -1 && body.HasBuff(slowBuffs[j]))
			{
				num += slowBuffAmounts[j];
			}
		}
		return num;
	}
}
public class SurgeTracker : MonoBehaviour
{
	private struct Hit
	{
		public float time;

		public float damage;
	}

	private readonly Dictionary<HealthComponent, List<Hit>> hits = new Dictionary<HealthComponent, List<Hit>>();

	private readonly List<HealthComponent> stale = new List<HealthComponent>();

	internal float AddDamage(HealthComponent victim, float damage, float window)
	{
		float now = Time.fixedTime;
		stale.Clear();
		foreach (KeyValuePair<HealthComponent, List<Hit>> hit in hits)
		{
			hit.Value.RemoveAll((Hit h) => now - h.time > window);
			if (hit.Value.Count == 0 || !Object.op_Implicit((Object)(object)hit.Key))
			{
				stale.Add(hit.Key);
			}
		}
		foreach (HealthComponent item in stale)
		{
			hits.Remove(item);
		}
		if (!hits.TryGetValue(victim, out var value))
		{
			value = new List<Hit>();
			hits[victim] = value;
		}
		value.Add(new Hit
		{
			time = now,
			damage = damage
		});
		float num = 0f;
		foreach (Hit item2 in value)
		{
			num += item2.damage;
		}
		return num;
	}

	internal void Clear()
	{
		hits.Clear();
	}
}