Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Runes v1.0.0
Runes.dll
Decompiled 11 hours agousing 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(); } }