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 LoadoutBuffs v1.2.0
LoadoutBuffs.dll
Decompiled 16 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Logging; using HarmonyLib; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.UI; using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Core.ObjectPool; using YamlDotNet.Core.Tokens; using YamlDotNet.Helpers; using YamlDotNet.RepresentationModel; using YamlDotNet.Serialization; using YamlDotNet.Serialization.BufferedDeserialization; using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators; using YamlDotNet.Serialization.Callbacks; using YamlDotNet.Serialization.Converters; using YamlDotNet.Serialization.EventEmitters; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NodeDeserializers; using YamlDotNet.Serialization.NodeTypeResolvers; using YamlDotNet.Serialization.ObjectFactories; using YamlDotNet.Serialization.ObjectGraphTraversalStrategies; using YamlDotNet.Serialization.ObjectGraphVisitors; using YamlDotNet.Serialization.Schemas; using YamlDotNet.Serialization.TypeInspectors; using YamlDotNet.Serialization.TypeResolvers; using YamlDotNet.Serialization.Utilities; using YamlDotNet.Serialization.ValueDeserializers; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("LoadoutBuffs")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.2.0.0")] [assembly: AssemblyInformationalVersion("1.2.0+043a66d5c7643460896458e27a82aa62fdcf50e3")] [assembly: AssemblyProduct("LoadoutBuffs")] [assembly: AssemblyTitle("LoadoutBuffs")] [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.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace LoadoutBuffs { internal static class AllyAssist { private static readonly List<Character> s_nearby = new List<Character>(); private static readonly int s_shieldHash = StringExtensionMethods.GetStableHashCode("Staff_shield"); private static Character s_bubbleSource; private static ShieldRequest s_bubble; private static bool s_bubbleErrorLogged; private static bool s_noShieldWarned; private static bool s_noSkillFactorWarned; public static void Self(Character character, ParryAssist assist) { if (assist.Heal > 0f) { character.Heal(assist.Heal, true); } if (assist.Stamina > 0f) { character.AddStamina(assist.Stamina); } } public static void Others(Character source, ParryAssist assist) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0073: 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_00d0: 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_00d6: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)source == (Object)null || !assist.ReachesOthers || !source.IsPlayer()) { return; } Vector3 position = ((Component)source).transform.position; float num = ((assist.HealAllies > 0f || assist.StaminaAllies > 0f) ? assist.Radius : 0f); float num2 = ((assist.HealTamed > 0f) ? assist.TamedRadius : 0f); s_nearby.Clear(); Character.GetCharactersInRange(position, Mathf.Max(num, num2), s_nearby); foreach (Character item in s_nearby) { if ((Object)(object)item == (Object)null || (Object)(object)item == (Object)(object)source || item.IsDead()) { continue; } Vector3 val = ((Component)item).transform.position - position; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (item.IsPlayer()) { if (!(sqrMagnitude >= num * num)) { if (assist.HealAllies > 0f) { item.Heal(assist.HealAllies, true); } if (assist.StaminaAllies > 0f) { item.UseStamina(0f - assist.StaminaAllies); } } } else if (item.IsTamed() && assist.HealTamed > 0f && sqrMagnitude < num2 * num2) { item.Heal(assist.HealTamed, true); } } } public static void RequestBubble(Character source, ParryAssist assist) { if (!((Object)(object)source == (Object)null) && assist != null && !(assist.Shield <= 0f) && source.IsPlayer()) { ShieldRequest shieldRequest = new ShieldRequest(assist.Shield, assist.Radius, assist.TamedRadius, assist.ShieldMinutes); s_bubble = ((s_bubbleSource == source) ? ShieldRequest.Merge(s_bubble, shieldRequest) : shieldRequest); s_bubbleSource = source; } } public static void FlushBubble() { //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) if (s_bubbleSource == null) { return; } Character val = s_bubbleSource; ShieldRequest shieldRequest = s_bubble; s_bubbleSource = null; s_bubble = null; try { if ((Object)(object)val == (Object)null || shieldRequest == null || val.IsDead()) { return; } ObjectDB instance = ObjectDB.instance; SE_Shield val2 = (SE_Shield)(((Object)(object)instance == (Object)null) ? null : /*isinst with value type is only supported in some contexts*/); if ((Object)(object)val2 == (Object)null) { WarnOnce(ref s_noShieldWarned, "No bubble sent: the game has no Staff_shield effect."); return; } if (!ShieldMath.TrySkillFor(shieldRequest.Amount, val2.m_absorbDamage, val2.m_absorbDamagePerSkillLevel, val2.m_absorbDamageWorldLevel, Game.m_worldLevel, out var skill)) { WarnOnce(ref s_noSkillFactorWarned, "Staff_shield has no per-skill strength (another mod?): bubbles get the game's base strength."); } int itemLevel = ShieldMath.ItemLevelFor(shieldRequest.Minutes); Give(val, itemLevel, skill); Vector3 position = ((Component)val).transform.position; s_nearby.Clear(); Character.GetCharactersInRange(position, Mathf.Max(shieldRequest.PlayerReach, shieldRequest.TamedReach), s_nearby); foreach (Character item in s_nearby) { if ((Object)(object)item == (Object)null || (Object)(object)item == (Object)(object)val || item.IsDead()) { continue; } Vector3 val3 = ((Component)item).transform.position - position; float sqrMagnitude = ((Vector3)(ref val3)).sqrMagnitude; bool num; if (!item.IsPlayer()) { if (!item.IsTamed()) { continue; } num = sqrMagnitude < shieldRequest.TamedReach * shieldRequest.TamedReach; } else { num = sqrMagnitude < shieldRequest.PlayerReach * shieldRequest.PlayerReach; } if (num) { Give(item, itemLevel, skill); } } } catch (Exception arg) { if (!s_bubbleErrorLogged) { s_bubbleErrorLogged = true; Plugin.Log.LogError((object)$"Parry bubble failed (logged once): {arg}"); } } } private static void Give(Character character, int itemLevel, float skill) { SEMan sEMan = character.GetSEMan(); if (sEMan != null) { sEMan.AddStatusEffect(s_shieldHash, false, itemLevel, skill, (short)(-1)); } } private static void WarnOnce(ref bool warned, string message) { if (!warned) { warned = true; Plugin.Log.LogWarning((object)message); } } } internal static class BundleEffects { public static bool HookInstalled; public static bool ParryHookInstalled; public static bool BlockHookInstalled; public static bool DamageHookInstalled; public static bool EitrHookInstalled; private static readonly Dictionary<BundleSlot, StatusEffect> s_effects = new Dictionary<BundleSlot, StatusEffect>(); private static readonly Dictionary<BundleSlot, StatBlock> s_stats = new Dictionary<BundleSlot, StatBlock>(); private static string s_statsName; private static bool s_showHudBuff; private static ParryAssist s_parry; private static bool s_parryErrorLogged; private static bool s_woodcutter; private static bool s_miner; private static bool s_classErrorLogged; private static float s_blockArmor; private static float s_blockForce; private static bool s_blockErrorLogged; private static readonly Dictionary<string, float> s_addDamage = new Dictionary<string, float>(StringComparer.Ordinal); private static bool s_damageErrorLogged; private static float s_eitrCost; private static bool s_eitrErrorLogged; public const short ClassToolTier = 1000; private static bool s_hookErrorLogged; private static readonly FieldInfo s_nview = AccessTools.Field(typeof(Character), "m_nview"); private static readonly FieldInfo s_helmet = AccessTools.Field(typeof(Humanoid), "m_helmetItem"); private static readonly FieldInfo s_chest = AccessTools.Field(typeof(Humanoid), "m_chestItem"); private static readonly FieldInfo s_legs = AccessTools.Field(typeof(Humanoid), "m_legItem"); private static readonly FieldInfo s_cape = AccessTools.Field(typeof(Humanoid), "m_shoulderItem"); private static readonly FieldInfo s_right = AccessTools.Field(typeof(Humanoid), "m_rightItem"); private static readonly FieldInfo s_left = AccessTools.Field(typeof(Humanoid), "m_leftItem"); public static BundleFile File { get; private set; } = new BundleFile(); public static BundleState State { get; private set; } = new BundleState(); public static GameEffectCatalog Catalog { get; private set; } public static BundleState Apply(ObjectDB db) { File = BundleFile.Parse(Read(out var error)); if (error != null) { File.Warnings.Insert(0, error); } Dictionary<string, StatusEffect> dictionary = GameGlue.CollectStatusEffects(db); Catalog = new GameEffectCatalog(db, dictionary); if (File.OnlyMisplaced) { RemoveMisplaced(); } State = BundleRules.Evaluate(File, Catalog); if (State.Enabled && !HookInstalled) { State.Enabled = false; State.OffReason = "this game version isn't supported by the buff hook (see LogOutput.log)."; } s_effects.Clear(); s_stats.Clear(); s_parry = null; s_woodcutter = (s_miner = false); s_blockArmor = (s_blockForce = (s_eitrCost = 0f)); s_addDamage.Clear(); s_statsName = State.Active?.Name; s_showHudBuff = BundleRules.ShowsHudBuff(State); if (State.Enabled) { State.Warnings.AddRange(BundleRules.MissingHookWarnings(State.Stats.Values, ParryHookInstalled, BlockHookInstalled, DamageHookInstalled, EitrHookInstalled)); } if (State.Enabled) { foreach (KeyValuePair<BundleSlot, string> effect in State.Effects) { if (dictionary.TryGetValue(effect.Value, out var value) && (Object)(object)value != (Object)null) { s_effects[effect.Key] = value; } } foreach (KeyValuePair<BundleSlot, StatBlock> stat in State.Stats) { s_stats[stat.Key] = stat.Value; } } return State; } private static void RemoveMisplaced() { try { string text = File.Serialize((string code) => Catalog?.DisplayName(code)); System.IO.File.WriteAllText(Plugin.BundlesPath, text); foreach (string item in File.Misplaced) { Plugin.Log.LogInfo((object)("LoadoutBuffs.buffs.yaml: removed " + item + ".")); } File = BundleFile.Parse(text); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not remove the stats on the wrong slots from LoadoutBuffs.buffs.yaml: " + ex.Message)); } } public static BundleState ReloadFile() { ObjectDB instance = ObjectDB.instance; if (!GameGlue.IsReady(instance)) { return State; } Apply(instance); foreach (string warning in State.Warnings) { Plugin.Log.LogWarning((object)warning); } Plugin.Log.LogInfo((object)State.StatusLine()); Refresh(); return State; } public static BundleState Save(BundleFile file) { string contents = file.Serialize((string code) => Catalog?.DisplayName(code)); System.IO.File.WriteAllText(Plugin.BundlesPath, contents); return ReloadFile(); } public static void Refresh() { GameGlue.RefreshEquipmentEffects(Player.m_localPlayer); } public static void AddTo(Humanoid humanoid, HashSet<StatusEffect> set) { try { if ((!s_showHudBuff && s_effects.Count == 0 && s_stats.Count == 0) || set == null || !(humanoid is Player)) { return; } object? obj = s_nview?.GetValue(humanoid); ZNetView val = (ZNetView)((obj is ZNetView) ? obj : null); if ((Object)(object)val == (Object)null || !val.IsValid() || !val.IsOwner()) { return; } foreach (KeyValuePair<BundleSlot, StatusEffect> s_effect in s_effects) { if ((Object)(object)s_effect.Value != (Object)null && Worn(humanoid, s_effect.Key) != null) { set.Add(s_effect.Value); } } StatBlock statBlock = StatBlock.Sum(from p in s_stats where Worn(humanoid, p.Key) != null select p.Value); s_parry = statBlock.ToParryAssist(); s_woodcutter = statBlock.Scalars.ContainsKey("woodcutter"); s_miner = statBlock.Scalars.ContainsKey("miner"); s_blockArmor = statBlock.BlockArmor; s_blockForce = statBlock.BlockForce; s_eitrCost = statBlock.EitrCost; s_addDamage.Clear(); foreach (KeyValuePair<string, float> item in statBlock.AddDamage) { s_addDamage[item.Key] = item.Value; } if (s_showHudBuff) { string effectNames = string.Join(", ", from p in s_effects where (Object)(object)p.Value != (Object)null && Worn(humanoid, p.Key) != null orderby p.Key select EffectText.DisplayName(p.Value.m_name)); BundleStatsEffect template = BundleStatsEffect.Template; template.SetTotals(statBlock, s_statsName ?? "Buff", effectNames); if (((Character)humanoid).GetSEMan().GetStatusEffect(((StatusEffect)template).NameHash()) is BundleStatsEffect bundleStatsEffect && (Object)(object)bundleStatsEffect != (Object)(object)template) { bundleStatsEffect.SetTotals(statBlock, s_statsName ?? "Buff", effectNames); } set.Add((StatusEffect)(object)template); } } catch (Exception arg) { if (!s_hookErrorLogged) { s_hookErrorLogged = true; Plugin.Log.LogError((object)$"Buff effects failed (logged once): {arg}"); } } } public static void OnParry(Humanoid humanoid) { try { ParryAssist parryAssist = s_parry; if (parryAssist != null && humanoid is Player) { object? obj = s_nview?.GetValue(humanoid); ZNetView val = (ZNetView)((obj is ZNetView) ? obj : null); if (!((Object)(object)val == (Object)null) && val.IsValid() && val.IsOwner()) { AllyAssist.Self((Character)(object)humanoid, parryAssist); AllyAssist.Others((Character)(object)humanoid, parryAssist); AllyAssist.RequestBubble((Character)(object)humanoid, parryAssist); } } } catch (Exception arg) { if (!s_parryErrorLogged) { s_parryErrorLogged = true; Plugin.Log.LogError((object)$"Buff on-parry stats failed (logged once): {arg}"); } } } public static void OnDestructibleHit(object target, HitData hit) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Invalid comparison between Unknown and I4 try { if ((!s_woodcutter && !s_miner) || hit == null) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)hit.GetAttacker() != (Object)(object)localPlayer) { return; } ItemData currentWeapon = ((Humanoid)localPlayer).GetCurrentWeapon(); BundleSlot? bundleSlot = ((currentWeapon == null) ? ((BundleSlot?)null) : BundleSlots.HandSlot(currentWeapon.m_shared.m_itemType, currentWeapon.m_shared.m_skillType)); if (bundleSlot != BundleSlot.Melee && bundleSlot != BundleSlot.Ranged) { return; } int num; if (!(target is TreeBase) && !(target is TreeLog)) { Destructible val = (Destructible)((target is Destructible) ? target : null); num = ((val != null && (int)val.m_destructibleType == 2) ? 1 : 0); } else { num = 1; } bool flag = (byte)num != 0; float val2 = hit.m_damage.m_blunt + hit.m_damage.m_slash + hit.m_damage.m_pierce; if (flag && s_woodcutter) { hit.m_damage.m_chop = Math.Max(hit.m_damage.m_chop, val2); } else { if (flag || !s_miner) { return; } hit.m_damage.m_pickaxe = Math.Max(hit.m_damage.m_pickaxe, val2); } if (hit.m_toolTier < 1000) { hit.m_toolTier = 1000; } } catch (Exception arg) { if (!s_classErrorLogged) { s_classErrorLogged = true; Plugin.Log.LogError((object)$"Buff classes failed (logged once): {arg}"); } } } public static float WithBlockArmor(ItemData item, float value) { try { if (s_blockArmor == 0f || item == null) { return value; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return value; } return ((((Humanoid)localPlayer).LeftItem ?? ((Humanoid)localPlayer).GetCurrentWeapon()) == item) ? BundleStatCatalog.WithBlockArmor(value, s_blockArmor) : value; } catch (Exception arg) { if (!s_blockErrorLogged) { s_blockErrorLogged = true; Plugin.Log.LogError((object)$"Buff block armor failed (logged once): {arg}"); } return value; } } public static float WithBlockForce(ItemData item, float value) { try { if (s_blockForce == 0f || item == null) { return value; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return value; } return ((((Humanoid)localPlayer).LeftItem ?? ((Humanoid)localPlayer).GetCurrentWeapon()) == item) ? BundleStatCatalog.WithBlockForce(value, s_blockForce) : value; } catch (Exception arg) { if (!s_blockErrorLogged) { s_blockErrorLogged = true; Plugin.Log.LogError((object)$"Buff block force failed (logged once): {arg}"); } return value; } } public static void WithAddedDamage(ItemData item, ref DamageTypes damages) { //IL_002d: 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) try { if (s_addDamage.Count != 0 && item != null) { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && ((Humanoid)localPlayer).GetCurrentWeapon() == item) { damages = StatBlock.AddDamageTo(damages, s_addDamage); } } } catch (Exception arg) { if (!s_damageErrorLogged) { s_damageErrorLogged = true; Plugin.Log.LogError((object)$"Buff added damage failed (logged once): {arg}"); } } } public static float WithEitrCost(Player player, float eitr) { try { if (s_eitrCost == 0f || (Object)(object)player == (Object)null || player != Player.m_localPlayer) { return eitr; } return BundleStatCatalog.WithEitrCost(eitr, s_eitrCost); } catch (Exception arg) { if (!s_eitrErrorLogged) { s_eitrErrorLogged = true; Plugin.Log.LogError((object)$"Buff eitr cost failed (logged once): {arg}"); } return eitr; } } public static ItemData CurrentWeapon() { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { return ((Humanoid)localPlayer).GetCurrentWeapon(); } return null; } public static Dictionary<string, float> OwnDamage(ItemData weapon) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) Dictionary<string, float> dictionary = StatBlock.DamageByType(weapon.GetDamage()); if (CurrentWeapon() != weapon) { return dictionary; } foreach (KeyValuePair<string, float> item in s_addDamage) { if (dictionary.TryGetValue(item.Key, out var value)) { if (value - item.Value > 0.001f) { dictionary[item.Key] = value - item.Value; } else { dictionary.Remove(item.Key); } } } return dictionary; } public static ItemData[] GearSnapshot(Humanoid h) { if ((Object)(object)h == (Object)null) { return (ItemData[])(object)new ItemData[0]; } List<ItemData> list = BundleSlots.All.Select((BundleSlot s) => Worn(h, s)).ToList(); list.Add(h.GetCurrentWeapon()); return list.ToArray(); } public static StatBlock WornTotals(Humanoid h, BundleDef bundle) { if ((Object)(object)h == (Object)null || bundle == null) { return new StatBlock(); } return StatBlock.Sum(from e in bundle.Entries where e.Stats != null && Worn(h, e.Slot) != null select e.Stats); } public static ItemData Worn(Humanoid h, BundleSlot slot) { //IL_008b: 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) switch (slot) { case BundleSlot.Helmet: return Get(s_helmet, h); case BundleSlot.Chest: return Get(s_chest, h); case BundleSlot.Legs: return Get(s_legs, h); case BundleSlot.Cape: return Get(s_cape, h); case BundleSlot.Melee: case BundleSlot.Ranged: case BundleSlot.Shield: { ItemData[] array = (ItemData[])(object)new ItemData[2] { Get(s_right, h), Get(s_left, h) }; foreach (ItemData val in array) { if (val != null && BundleSlots.HandSlot(val.m_shared.m_itemType, val.m_shared.m_skillType) == slot) { return val; } } return null; } default: return null; } } private static ItemData Get(FieldInfo field, Humanoid h) { if (!((Object)(object)h == (Object)null)) { object? obj = field?.GetValue(h); return (ItemData)((obj is ItemData) ? obj : null); } return null; } private static string Read(out string error) { error = null; try { return System.IO.File.Exists(Plugin.BundlesPath) ? System.IO.File.ReadAllText(Plugin.BundlesPath) : null; } catch (Exception ex) { error = "Could not read " + Plugin.BundlesPath + ": " + ex.Message; return null; } } } internal sealed class GameEffectCatalog : IEffectCatalog { private readonly Dictionary<string, StatusEffect> _all; private readonly List<EffectInfo> _allowed; public IReadOnlyList<EffectInfo> Allowed => _allowed; public GameEffectCatalog(ObjectDB db, Dictionary<string, StatusEffect> all) { _all = all; Dictionary<string, StatusEffect> dictionary = new Dictionary<string, StatusEffect>(StringComparer.Ordinal); foreach (GameObject item in db.m_items) { if ((Object)(object)item == (Object)null) { continue; } ItemDrop component = item.GetComponent<ItemDrop>(); if ((Object)(object)component == (Object)null) { continue; } SharedData shared = component.m_itemData.m_shared; StatusEffect[] array = (StatusEffect[])(object)new StatusEffect[2] { shared.m_equipStatusEffect, shared.m_setStatusEffect }; foreach (StatusEffect val in array) { if ((Object)(object)val != (Object)null && !dictionary.ContainsKey(((Object)val).name)) { dictionary[((Object)val).name] = val; } } } _allowed = dictionary.Values.Select((StatusEffect se) => new EffectInfo { Code = ((Object)se).name, DisplayName = EffectText.DisplayName(se.m_name) }).OrderBy<EffectInfo, string>((EffectInfo e) => e.DisplayName, StringComparer.OrdinalIgnoreCase).ToList(); } public bool Exists(string code) { if (code != null) { return _all.ContainsKey(code); } return false; } public StatusEffect Get(string code) { if (code == null || !_all.TryGetValue(code, out var value)) { return null; } return value; } public string DisplayName(string code) { return _allowed.FirstOrDefault((EffectInfo e) => e.Code == code)?.DisplayName; } } internal sealed class BundleEntry { public BundleSlot Slot; public string Effect; public StatBlock Stats; public long Line; } internal sealed class BundleDef { public string Name; public long Line; public readonly List<BundleEntry> Entries = new List<BundleEntry>(); public string Get(BundleSlot slot) { return Entries.FirstOrDefault((BundleEntry e) => e.Slot == slot)?.Effect; } public StatBlock GetStats(BundleSlot slot) { return Entries.FirstOrDefault((BundleEntry e) => e.Slot == slot)?.Stats; } public void Set(BundleSlot slot, string effect) { Update(slot, string.IsNullOrEmpty(effect) ? null : effect, GetStats(slot)); } public void SetStats(BundleSlot slot, StatBlock stats) { Update(slot, Get(slot), stats); } private void Update(BundleSlot slot, string effect, StatBlock stats) { BundleEntry bundleEntry = Entries.FirstOrDefault((BundleEntry e) => e.Slot == slot); if (effect == null && (stats == null || stats.IsEmpty)) { if (bundleEntry != null) { Entries.Remove(bundleEntry); } return; } if (bundleEntry == null) { List<BundleEntry> entries = Entries; BundleEntry obj = new BundleEntry { Slot = slot }; bundleEntry = obj; entries.Add(obj); } bundleEntry.Effect = effect; bundleEntry.Stats = ((stats == null || stats.IsEmpty) ? null : stats); } } internal sealed class BundleFile { public const string FileName = "LoadoutBuffs.buffs.yaml"; public string Active; public readonly List<BundleDef> Bundles = new List<BundleDef>(); public readonly List<string> Warnings = new List<string>(); public bool Invalid; public readonly List<string> Misplaced = new List<string>(); public static readonly string[] Header = new string[28] { "# LoadoutBuffs: extra \"while worn\" effects and stats, per equipment slot.", "#", "# Pick and edit buffs in game: inventory -> Buffs (or the console command lb_buffs).", "# Or edit this file and run lb_reload in the F5 console. The Buffs window rewrites this", "# file, so comments you add here are not kept.", "#", "# active: the buff in use for every character in this profile, or none.", "# Slots: helmet, chest, legs, cape, melee (swords, knives, clubs, atgeirs, spears, axes, fists),", "# ranged (bows, crossbows, magic staffs), shield. A slot counts while something is equipped there", "# (weapons: in your hands); your gear keeps its own effects. Pickaxes, the fishing rod, torches and", "# tools fill no slot. Effect: code name or in-game name of an effect gear has in the game (the", "# Buffs window lists them).", "# A slot can also have custom stats (long form):", "# chest:", "# effect: SetEffect_DeepNorthMediumArmor", "# stats: { movementSpeed: 10, armor: 15, resist: { Fire: Resistant }, skills: { Bows: 15 } }", "# Stats: movementSpeed, carryWeight, armor, blockArmor, blockForce (what you block with), fallDamage,", "# healthRegen, staminaRegen, eitrRegen, runStamina, jumpStamina, attackStamina, blockStamina,", "# dodgeStamina, eitrCost (percent stats in percent: 10 = +10 %), resist, skills, fields (raw SE_Stats", "# fields).", "# Melee, ranged or shield slot only, on every parry: healOnParry, staminaOnParry (you),", "# healAlliesOnParry, staminaAlliesOnParry (other players), healTamedOnParry, shieldOnParry (a Magic", "# barrier absorbing that much, for all of you), shieldMinutes (its time, default 1), parryRadius", "# (reach in metres, default 10).", "# Melee or ranged slot only: damage (%), addDamage (flat, e.g. { spirit: 30 }), and the classes", "# woodcutter: true (that weapon's hits fell any tree) and miner: true (they break any rock or ore).", "# The stats of all filled slots add up (reach and bubble time take the largest) and show on your HUD", "# as one buff named after it." }; public bool OnlyMisplaced { get { if (!Invalid && Misplaced.Count > 0) { return Warnings.Count == Misplaced.Count; } return false; } } public BundleDef Find(string name) { if (name != null) { return Bundles.FirstOrDefault((BundleDef b) => string.Equals(b.Name, name, StringComparison.OrdinalIgnoreCase)); } return null; } public static BundleFile Parse(string text) { BundleFile bundleFile = new BundleFile(); if (string.IsNullOrWhiteSpace(text)) { return bundleFile; } text = text.Replace('\u00a0', ' ').Replace('\u202f', ' ').Replace('\u2007', ' '); YamlStream yamlStream = new YamlStream(); try { yamlStream.Load(new StringReader(text)); } catch (YamlException ex) { bundleFile.Invalid = true; bundleFile.Warnings.Add(string.Format("{0} is not valid YAML (around line {1}, column {2}), ", "LoadoutBuffs.buffs.yaml", ex.Start.Line, ex.Start.Column) + "so no buff is used: " + ex.Message); return bundleFile; } if (yamlStream.Documents.Count == 0) { return bundleFile; } YamlNode rootNode = yamlStream.Documents[0].RootNode; if (IsNull(rootNode)) { return bundleFile; } if (!(rootNode is YamlMappingNode yamlMappingNode)) { bundleFile.Warnings.Add(string.Format("{0}: expected 'active:' and 'buffs:' (line {1}).", "LoadoutBuffs.buffs.yaml", rootNode.Start.Line)); return bundleFile; } foreach (KeyValuePair<YamlNode, YamlNode> child in yamlMappingNode.Children) { string text2 = Scalar(child.Key); string text3 = text2?.ToLowerInvariant(); if (!(text3 == "active")) { if (text3 == "buffs") { bundleFile.ParseBundles(child.Value); } else { bundleFile.Warnings.Add(string.Format("{0}: unknown key '{1}', skipped (line {2}). Use 'active' and 'buffs'.", "LoadoutBuffs.buffs.yaml", text2, child.Key.Start.Line)); } } else { bundleFile.ParseActive(child.Value); } } if (bundleFile.Active != null && bundleFile.Find(bundleFile.Active) == null) { bundleFile.Warnings.Add("LoadoutBuffs.buffs.yaml: active buff '" + bundleFile.Active + "' doesn't exist, so no buff is used. Buffs: " + ((bundleFile.Bundles.Count == 0) ? "none" : string.Join(", ", bundleFile.Bundles.Select((BundleDef b) => b.Name))) + "."); } return bundleFile; } private void ParseActive(YamlNode node) { if (!IsNull(node) && !IsNone(node)) { if (node is YamlScalarNode yamlScalarNode) { Active = yamlScalarNode.Value.Trim(); } else { Warnings.Add(string.Format("{0}: 'active' must be a buff name or none (line {1}).", "LoadoutBuffs.buffs.yaml", node.Start.Line)); } } } private void ParseBundles(YamlNode node) { if (IsNull(node)) { return; } if (!(node is YamlMappingNode yamlMappingNode)) { Warnings.Add(string.Format("{0}: 'buffs' must be a list of named buffs like 'Warrior: {{ chest: Vanguard }}' (line {1}).", "LoadoutBuffs.buffs.yaml", node.Start.Line)); return; } foreach (KeyValuePair<YamlNode, YamlNode> child in yamlMappingNode.Children) { string text = Scalar(child.Key)?.Trim(); long line = child.Key.Start.Line; if (string.IsNullOrEmpty(text)) { Warnings.Add(string.Format("{0}: expected a buff name (line {1}).", "LoadoutBuffs.buffs.yaml", line)); continue; } if (Find(text) != null) { Warnings.Add(string.Format("{0}: buff '{1}' appears twice (names ignore upper/lower case); the second one is skipped (line {2}).", "LoadoutBuffs.buffs.yaml", text, line)); continue; } BundleDef bundleDef = new BundleDef { Name = text, Line = line }; Bundles.Add(bundleDef); if (IsNull(child.Value)) { continue; } if (!(child.Value is YamlMappingNode yamlMappingNode2)) { Warnings.Add($"{text}: expected slots like 'chest: Vanguard' (line {line})."); continue; } HashSet<BundleSlot> seen = new HashSet<BundleSlot>(); foreach (KeyValuePair<YamlNode, YamlNode> child2 in yamlMappingNode2.Children) { ParseSlot(bundleDef, child2.Key, child2.Value, seen); } } } private void ParseSlot(BundleDef bundle, YamlNode keyNode, YamlNode valueNode, HashSet<BundleSlot> seen) { string text = Scalar(keyNode); long line = keyNode.Start.Line; if (!BundleSlots.TryParse(text, out var slot)) { string text2 = BundleSlots.RemovedSlotHint(text); Warnings.Add(bundle.Name + "." + text + ": " + ((text2 != null) ? (text2 + ", so it's skipped") : "unknown slot, skipped") + string.Format(" (line {0}). Slots: {1}.", line, string.Join(", ", BundleSlots.All.Select(BundleSlots.Key)))); return; } string text3 = bundle.Name + "." + BundleSlots.Key(slot); if (!seen.Add(slot)) { Warnings.Add($"{text3}: the slot appears twice (slots ignore upper/lower case); the second one is skipped (line {line})."); } else { if (IsNull(valueNode) || IsNone(valueNode)) { return; } if (valueNode is YamlScalarNode yamlScalarNode) { bundle.Entries.Add(new BundleEntry { Slot = slot, Effect = yamlScalarNode.Value.Trim(), Line = line }); return; } if (!(valueNode is YamlMappingNode yamlMappingNode)) { Warnings.Add($"{text3}: expected an effect name, or 'effect:' and 'stats:'; skipped (line {line})."); return; } BundleEntry bundleEntry = new BundleEntry { Slot = slot, Line = line }; foreach (KeyValuePair<YamlNode, YamlNode> child in yamlMappingNode.Children) { string text4 = Scalar(child.Key)?.Trim(); string text5 = text4?.ToLowerInvariant(); if (!(text5 == "effect")) { if (text5 == "stats") { if (IsNull(child.Value)) { continue; } if (child.Value is YamlMappingNode map) { List<(string, string)> list = new List<(string, string)>(); bundleEntry.Stats = StatBlock.Parse(map, text3, Warnings, slot, list); foreach (var (text6, text7) in list) { Misplaced.Add(text6 + " from " + bundle.Name + "'s " + BundleSlots.Key(slot) + "; it only goes on the " + text7 + " now"); } } else { Warnings.Add($"{text3}.stats: expected stats like {{ armor: 15 }}; skipped (line {child.Key.Start.Line})."); } } else { Warnings.Add($"{text3}.{text4}: unknown key, skipped (line {child.Key.Start.Line}). Use 'effect' and 'stats'."); } } else if (!IsNull(child.Value) && !IsNone(child.Value)) { if (child.Value is YamlScalarNode yamlScalarNode2) { bundleEntry.Effect = yamlScalarNode2.Value.Trim(); } else { Warnings.Add($"{text3}.effect: expected one effect name; skipped (line {child.Key.Start.Line})."); } } } if (bundleEntry.Stats != null && bundleEntry.Stats.IsEmpty) { bundleEntry.Stats = null; } if (bundleEntry.Effect != null || bundleEntry.Stats != null) { bundle.Entries.Add(bundleEntry); } } } public string Serialize(Func<string, string> displayName) { StringBuilder stringBuilder = new StringBuilder(); string[] header = Header; foreach (string value in header) { stringBuilder.Append(value).Append('\n'); } stringBuilder.Append('\n'); stringBuilder.Append("active: ").Append((Active == null) ? "none" : Values.Quote(Active)).Append('\n'); stringBuilder.Append('\n'); if (Bundles.Count == 0) { stringBuilder.Append("buffs: {}\n"); return stringBuilder.ToString(); } stringBuilder.Append("buffs:\n"); foreach (BundleDef bundle in Bundles) { if (bundle.Entries.Count == 0) { stringBuilder.Append(" ").Append(Values.Quote(bundle.Name)).Append(": {}\n"); continue; } stringBuilder.Append(" ").Append(Values.Quote(bundle.Name)).Append(":\n"); BundleSlot[] all = BundleSlots.All; foreach (BundleSlot slot in all) { string text = bundle.Get(slot); StatBlock stats = bundle.GetStats(slot); if (text == null && stats == null) { continue; } string text2 = ((text == null) ? null : displayName?.Invoke(text)); string comment = ((!string.IsNullOrEmpty(text2) && text2 != text) ? text2.Replace('\n', ' ') : null); if (stats == null) { stringBuilder.Append(WithComment(" " + BundleSlots.Key(slot) + ": " + Values.Quote(text), comment)).Append('\n'); continue; } stringBuilder.Append(" " + BundleSlots.Key(slot) + ":\n"); if (text != null) { stringBuilder.Append(WithComment(" effect: " + Values.Quote(text), comment)).Append('\n'); } stringBuilder.Append(" stats: " + stats.Serialize() + "\n"); } } return stringBuilder.ToString(); } private static string WithComment(string line, string comment) { if (comment != null) { return line.PadRight(46) + " # " + comment; } return line; } private static string Scalar(YamlNode node) { return (node as YamlScalarNode)?.Value; } private static bool IsNull(YamlNode node) { if (!(node is YamlScalarNode { Style: ScalarStyle.Plain } yamlScalarNode)) { return false; } string value = yamlScalarNode.Value; if (value != null && value.Length != 0 && !(value == "~")) { return string.Equals(value, "null", StringComparison.OrdinalIgnoreCase); } return true; } private static bool IsNone(YamlNode node) { if (node is YamlScalarNode { Style: ScalarStyle.Plain } yamlScalarNode) { return string.Equals(yamlScalarNode.Value?.Trim(), "none", StringComparison.OrdinalIgnoreCase); } return false; } } [HarmonyPatch] internal static class BundlePatches { [HarmonyPatch(typeof(Humanoid), "UpdateEquipmentStatusEffects")] [HarmonyTranspiler] private static IEnumerable<CodeInstruction> UpdateEquipmentStatusEffects(IEnumerable<CodeInstruction> instructions) { //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Expected O, but got Unknown //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Expected O, but got Unknown BundleEffects.HookInstalled = false; List<CodeInstruction> list = instructions.ToList(); MethodInfo methodInfo = AccessTools.Method(typeof(BundleEffects), "AddTo", (Type[])null, (Type[])null); for (int i = 0; i < list.Count - 1; i++) { if (!(list[i].opcode != OpCodes.Newobj) && list[i].operand is ConstructorInfo constructorInfo && !(constructorInfo.DeclaringType != typeof(HashSet<StatusEffect>)) && constructorInfo.GetParameters().Length == 0) { CodeInstruction val = LoadFor(list[i + 1]); if (val == null) { break; } list.InsertRange(i + 2, (IEnumerable<CodeInstruction>)(object)new CodeInstruction[3] { new CodeInstruction(OpCodes.Ldarg_0, (object)null), val, new CodeInstruction(OpCodes.Call, (object)methodInfo) }); BundleEffects.HookInstalled = true; Plugin.Log.LogInfo((object)"Buff hook installed in Humanoid.UpdateEquipmentStatusEffects"); return list; } } Plugin.Log.LogError((object)"Buffs disabled: Humanoid.UpdateEquipmentStatusEffects doesn't look as expected (game update?)."); return list; } private static CodeInstruction LoadFor(CodeInstruction store) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Expected O, but got Unknown if (store.opcode == OpCodes.Stloc_0) { return new CodeInstruction(OpCodes.Ldloc_0, (object)null); } if (store.opcode == OpCodes.Stloc_1) { return new CodeInstruction(OpCodes.Ldloc_1, (object)null); } if (store.opcode == OpCodes.Stloc_2) { return new CodeInstruction(OpCodes.Ldloc_2, (object)null); } if (store.opcode == OpCodes.Stloc_3) { return new CodeInstruction(OpCodes.Ldloc_3, (object)null); } if (store.opcode == OpCodes.Stloc_S) { return new CodeInstruction(OpCodes.Ldloc_S, store.operand); } if (store.opcode == OpCodes.Stloc) { return new CodeInstruction(OpCodes.Ldloc, store.operand); } return null; } } [HarmonyPatch] internal static class BundleParryPatches { [HarmonyPatch(typeof(Humanoid), "BlockAttack")] [HarmonyTranspiler] private static IEnumerable<CodeInstruction> BlockAttack(IEnumerable<CodeInstruction> instructions) { //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Expected O, but got Unknown //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Expected O, but got Unknown BundleEffects.ParryHookInstalled = false; List<CodeInstruction> list = instructions.ToList(); FieldInfo objB = AccessTools.Field(typeof(Humanoid), "m_perfectBlockEffect"); MethodInfo methodInfo = AccessTools.Method(typeof(BundleEffects), "OnParry", (Type[])null, (Type[])null); for (int i = 0; i < list.Count; i++) { if (list[i].opcode != OpCodes.Ldfld || !object.Equals(list[i].operand, objB)) { continue; } for (int j = i + 1; j < list.Count - 1 && j < i + 20; j++) { if (list[j].operand is MethodInfo methodInfo2 && !(methodInfo2.DeclaringType != typeof(EffectList)) && !(methodInfo2.Name != "Create")) { if (list[j + 1].opcode != OpCodes.Pop) { break; } list.InsertRange(j + 2, (IEnumerable<CodeInstruction>)(object)new CodeInstruction[2] { new CodeInstruction(OpCodes.Ldarg_0, (object)null), new CodeInstruction(OpCodes.Call, (object)methodInfo) }); BundleEffects.ParryHookInstalled = true; Plugin.Log.LogInfo((object)"Buff parry hook installed in Humanoid.BlockAttack"); return list; } } break; } Plugin.Log.LogError((object)"Buff on-parry stats disabled: Humanoid.BlockAttack doesn't look as expected (game update?). Everything else is not affected."); return list; } } [HarmonyPatch] internal static class BundleBlockPatches { [HarmonyPatch(typeof(ItemData), "GetBaseBlockPower", new Type[] { typeof(int) })] [HarmonyPostfix] private static void BlockArmor(ItemData __instance, ref float __result) { __result = BundleEffects.WithBlockArmor(__instance, __result); } [HarmonyPatch(typeof(ItemData), "GetDeflectionForce", new Type[] { typeof(int) })] [HarmonyPostfix] private static void BlockForce(ItemData __instance, ref float __result) { __result = BundleEffects.WithBlockForce(__instance, __result); } } [HarmonyPatch] internal static class BundleDamagePatches { [HarmonyPatch(typeof(ItemData), "GetDamage", new Type[] { typeof(int), typeof(float) })] [HarmonyPostfix] private static void AddedDamage(ItemData __instance, ref DamageTypes __result) { BundleEffects.WithAddedDamage(__instance, ref __result); } } [HarmonyPatch] internal static class BundleEitrPatches { [HarmonyPatch(typeof(Player), "UseEitr", new Type[] { typeof(float) })] [HarmonyPrefix] private static void Use(Player __instance, ref float v) { v = BundleEffects.WithEitrCost(__instance, v); } [HarmonyPatch(typeof(Player), "HaveEitr", new Type[] { typeof(float) })] [HarmonyPrefix] private static void Have(Player __instance, ref float amount) { amount = BundleEffects.WithEitrCost(__instance, amount); } } [HarmonyPatch] internal static class BundleClassPatches { [HarmonyPatch(typeof(TreeBase), "Damage")] [HarmonyPrefix] private static void Tree(TreeBase __instance, HitData hit) { BundleEffects.OnDestructibleHit(__instance, hit); } [HarmonyPatch(typeof(TreeLog), "Damage")] [HarmonyPrefix] private static void Log(TreeLog __instance, HitData hit) { BundleEffects.OnDestructibleHit(__instance, hit); } [HarmonyPatch(typeof(MineRock), "Damage")] [HarmonyPrefix] private static void Rock(MineRock __instance, HitData hit) { BundleEffects.OnDestructibleHit(__instance, hit); } [HarmonyPatch(typeof(MineRock5), "Damage")] [HarmonyPrefix] private static void BigRock(MineRock5 __instance, HitData hit) { BundleEffects.OnDestructibleHit(__instance, hit); } [HarmonyPatch(typeof(Destructible), "Damage")] [HarmonyPrefix] private static void Other(Destructible __instance, HitData hit) { BundleEffects.OnDestructibleHit(__instance, hit); } } internal enum BundleSlot { Helmet, Chest, Legs, Cape, Melee, Ranged, Shield } internal static class BundleSlots { public static readonly BundleSlot[] All = (BundleSlot[])Enum.GetValues(typeof(BundleSlot)); private static readonly HashSet<SkillType> s_ranged = new HashSet<SkillType> { (SkillType)8, (SkillType)14, (SkillType)9, (SkillType)10 }; private static readonly HashSet<SkillType> s_melee = new HashSet<SkillType> { (SkillType)1, (SkillType)2, (SkillType)3, (SkillType)4, (SkillType)5, (SkillType)7, (SkillType)11 }; public static string Key(BundleSlot slot) { return slot.ToString().ToLowerInvariant(); } public static string RemovedSlotHint(string key) { switch (key?.Trim().ToLowerInvariant()) { case "weapon": return "the weapon slot was split: use melee or ranged"; case "utility": case "trinket": return "the " + key.Trim().ToLowerInvariant() + " slot was removed (those items already have their own effects)"; default: return null; } } public static BundleSlot? HandSlot(ItemType type, SkillType skill) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_0028: 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_0018: Invalid comparison between Unknown and I4 //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Invalid comparison between Unknown and I4 //IL_003c: Unknown result type (might be due to invalid IL or missing references) if ((int)type <= 5) { if (type - 3 <= 1) { goto IL_0023; } if ((int)type == 5) { return BundleSlot.Shield; } } else if ((int)type == 14 || (int)type == 22) { goto IL_0023; } return null; IL_0023: if (s_ranged.Contains(skill)) { return BundleSlot.Ranged; } if (s_melee.Contains(skill)) { return BundleSlot.Melee; } return null; } public static bool TryParse(string key, out BundleSlot slot) { slot = BundleSlot.Helmet; if (string.IsNullOrEmpty(key)) { return false; } BundleSlot[] all = All; foreach (BundleSlot bundleSlot in all) { if (string.Equals(Key(bundleSlot), key.Trim(), StringComparison.OrdinalIgnoreCase)) { slot = bundleSlot; return true; } } return false; } } internal sealed class EffectInfo { public string Code; public string DisplayName; } internal interface IEffectCatalog { IReadOnlyList<EffectInfo> Allowed { get; } bool Exists(string code); } internal sealed class BundleState { public bool Enabled = true; public string OffReason; public BundleDef Active; public readonly Dictionary<BundleSlot, string> Effects = new Dictionary<BundleSlot, string>(); public readonly Dictionary<BundleSlot, StatBlock> Stats = new Dictionary<BundleSlot, StatBlock>(); public readonly List<string> Warnings = new List<string>(); public int StatCount => StatBlock.Sum(Stats.Values).Count; public string StatusLine() { if (!Enabled) { return "Buffs off: " + OffReason; } if (Active == null) { return "No buff active"; } int statCount = StatCount; return string.Format("Buff '{0}' active: {1} effect{2}", Active.Name, Effects.Count, (Effects.Count == 1) ? "" : "s") + ((statCount > 0) ? string.Format(", {0} stat{1}", statCount, (statCount == 1) ? "" : "s") : ""); } } internal static class BundleRules { public static bool ShowsHudBuff(BundleState state) { if (state != null && state.Enabled) { return state.Active != null; } return false; } public static List<string> MissingHookWarnings(IEnumerable<StatBlock> stats, bool parryHook, bool blockHook, bool damageHook, bool eitrHook) { List<StatBlock> source = stats.Where((StatBlock b) => b != null).ToList(); List<string> list = new List<string>(); if (!parryHook && source.Any((StatBlock b) => b.ToParryAssist() != null)) { list.Add("The buff's on-parry stats can't work with this game version (the parry hook isn't installed, see LogOutput.log)."); } if (!blockHook && source.Any((StatBlock b) => b.BlockArmor != 0f || b.BlockForce != 0f)) { list.Add("The buff's block armor / force can't work (the block hook isn't installed, see LogOutput.log)."); } if (!damageHook && source.Any((StatBlock b) => b.AddDamage.Count > 0)) { list.Add("The buff's added damage can't work (the added damage hook isn't installed, see LogOutput.log)."); } if (!eitrHook && source.Any((StatBlock b) => b.EitrCost != 0f)) { list.Add("The buff's eitr cost can't work (the eitr hook isn't installed, see LogOutput.log)."); } return list; } public static string Resolve(string text, IEffectCatalog catalog, out string error) { error = null; string t = text?.Trim() ?? ""; EffectInfo effectInfo = catalog.Allowed.FirstOrDefault((EffectInfo e) => string.Equals(e.Code, t, StringComparison.Ordinal)) ?? catalog.Allowed.FirstOrDefault((EffectInfo e) => string.Equals(e.Code, t, StringComparison.OrdinalIgnoreCase)); if (effectInfo != null) { return effectInfo.Code; } List<EffectInfo> list = catalog.Allowed.Where((EffectInfo e) => string.Equals(e.DisplayName, t, StringComparison.OrdinalIgnoreCase)).ToList(); if (list.Count == 1) { return list[0].Code; } if (list.Count > 1) { error = "'" + t + "' is the in-game name of several effects; use one of their code names: " + string.Join(", ", list.Select((EffectInfo e) => e.Code)); return null; } error = (catalog.Exists(t) ? ("'" + t + "' can't be used in a buff: only effects that gear has in the game can (the Buffs window lists them)") : ("unknown effect '" + t + "'; use a code name or in-game name from the Buffs window's list")); return null; } public static BundleState Evaluate(BundleFile file, IEffectCatalog catalog) { BundleState bundleState = new BundleState(); file = file ?? new BundleFile(); bundleState.Warnings.AddRange(file.Warnings); Dictionary<BundleDef, Dictionary<BundleSlot, string>> dictionary = new Dictionary<BundleDef, Dictionary<BundleSlot, string>>(); foreach (BundleDef bundle in file.Bundles) { dictionary[bundle] = ResolveBundle(bundle, catalog, bundleState.Warnings); } if (file.Invalid) { bundleState.Enabled = false; bundleState.OffReason = "LoadoutBuffs.buffs.yaml is not valid YAML (see the warning)."; return bundleState; } bundleState.Active = file.Find(file.Active); if (bundleState.Active == null) { return bundleState; } foreach (KeyValuePair<BundleSlot, string> item in dictionary[bundleState.Active]) { bundleState.Effects[item.Key] = item.Value; } foreach (BundleEntry entry in bundleState.Active.Entries) { if (entry.Stats != null && !entry.Stats.IsEmpty) { bundleState.Stats[entry.Slot] = entry.Stats; } } return bundleState; } public static Dictionary<BundleSlot, string> ResolveBundle(BundleDef bundle, IEffectCatalog catalog, List<string> warnings) { Dictionary<BundleSlot, string> dictionary = new Dictionary<BundleSlot, string>(); Dictionary<string, BundleSlot> dictionary2 = new Dictionary<string, BundleSlot>(StringComparer.Ordinal); foreach (BundleEntry entry in bundle.Entries) { if (entry.Effect != null) { string text = bundle.Name + "." + BundleSlots.Key(entry.Slot); string text2 = ((entry.Line > 0) ? $" (line {entry.Line})" : ""); string error; string text3 = Resolve(entry.Effect, catalog, out error); BundleSlot value; if (text3 == null) { warnings?.Add(text + ": " + error + "; skipped" + text2 + "."); } else if (dictionary2.TryGetValue(text3, out value)) { warnings?.Add(text + ": " + text3 + " is already in the " + BundleSlots.Key(value) + " slot, and an effect only counts once; skipped" + text2 + "."); } else { dictionary2[text3] = entry.Slot; dictionary[entry.Slot] = text3; } } } return dictionary; } } internal static class BundleWindowRules { public static List<string> DamagePercentRows(IReadOnlyDictionary<string, float> dealt, IReadOnlyDictionary<string, float> set) { if (dealt == null) { return BundleStatCatalog.DamageTypes.ToList(); } return BundleStatCatalog.DamageTypes.Where(delegate(string t) { string key = t.ToLowerInvariant(); float value; return (dealt.TryGetValue(key, out value) && value > 0f) || (set != null && set.ContainsKey(key)); }).ToList(); } public static string AddedDamageNote(float own) { if (!(own > 0f)) { return null; } return "has " + own.ToString("0.##", CultureInfo.InvariantCulture); } public static List<string> WornEffectNames(BundleDef bundle, IEffectCatalog catalog, Func<BundleSlot, bool> isWorn) { if (bundle == null || catalog == null) { return new List<string>(); } return (from p in BundleRules.ResolveBundle(bundle, catalog, null) where isWorn(p.Key) orderby p.Key select catalog.Allowed.FirstOrDefault((EffectInfo e) => e.Code == p.Value)?.DisplayName ?? p.Value).ToList(); } } internal enum StatKind { Percent, RegenPercent, Flat, Parry, Toggle, Blocker, EitrCost } internal sealed class StatDef { public string Key; public string Label; public string Group; public string Field; public StatKind Kind; public float Step; public float Min; public float Max; public float Default; public bool TakesLargest; public string Unit = ""; public bool LowerIsBetter; public BundleSlot[] OnlyOn; public string WhereAllowed { get { if (OnlyOn != null) { return BundleStatCatalog.SlotsText(OnlyOn); } return "any slot"; } } public bool IsPercent { get { if (Kind != StatKind.Percent && Kind != StatKind.RegenPercent) { return Kind == StatKind.EitrCost; } return true; } } public bool Allows(BundleSlot slot) { if (OnlyOn != null) { return Array.IndexOf(OnlyOn, slot) >= 0; } return true; } public bool Helps(float value) { if (!LowerIsBetter) { return value > 0f; } return value < 0f; } } internal static class BundleStatCatalog { public const string General = "General"; public const string Regen = "Regen"; public const string Costs = "Costs"; public const string OnParry = "On parry"; public const string Class = "Class"; public const string WoodcutterKey = "woodcutter"; public const string MinerKey = "miner"; public const string Resistances = "Resistances"; public const string Damage = "Damage %"; public const string Skills = "Skills"; public const string ResistKey = "resist"; public const string DamageKey = "damage"; public const string SkillsKey = "skills"; public const string FieldsKey = "fields"; private static readonly BundleSlot[] HandSlots = new BundleSlot[3] { BundleSlot.Melee, BundleSlot.Ranged, BundleSlot.Shield }; public static readonly StatDef[] Scalars = new StatDef[23] { Def("movementSpeed", "Movement speed", "General", "m_speedModifier", StatKind.Percent, 5f, -50f, 100f), Def("carryWeight", "Carry weight", "General", "m_addMaxCarryWeight", StatKind.Flat, 25f, -100f, 500f), Def("armor", "Armor", "General", "m_addArmor", StatKind.Flat, 5f, -50f, 200f), Def("blockArmor", "Block armor", "General", null, StatKind.Blocker, 5f, -50f, 200f), Def("blockForce", "Block force", "General", null, StatKind.Blocker, 5f, -50f, 300f), Cost("fallDamage", "Fall damage", "General", "m_fallDamageModifier", 10f, -100f, 100f), Def("healthRegen", "Health regen", "Regen", "m_healthRegenMultiplier", StatKind.RegenPercent, 10f, -50f, 300f), Def("staminaRegen", "Stamina regen", "Regen", "m_staminaRegenMultiplier", StatKind.RegenPercent, 10f, -50f, 300f), Def("eitrRegen", "Eitr regen", "Regen", "m_eitrRegenMultiplier", StatKind.RegenPercent, 10f, -50f, 300f), Cost("runStamina", "Run stamina cost", "Costs", "m_runStaminaDrainModifier", 5f, -100f, 100f), Cost("jumpStamina", "Jump stamina cost", "Costs", "m_jumpStaminaUseModifier", 5f, -100f, 100f), Cost("attackStamina", "Attack stamina cost", "Costs", "m_attackStaminaUseModifier", 5f, -100f, 100f), Cost("blockStamina", "Block stamina cost", "Costs", "m_blockStaminaUseModifier", 5f, -100f, 100f), Cost("dodgeStamina", "Dodge stamina cost", "Costs", "m_dodgeStaminaUseModifier", 5f, -100f, 100f), new StatDef { Key = "eitrCost", Label = "Eitr cost", Group = "Costs", Kind = StatKind.EitrCost, Step = 5f, Min = -100f, Max = 100f, LowerIsBetter = true }, Parry("healOnParry", "Heal you", 10f, 0f, 300f), Parry("staminaOnParry", "Stamina to you", 10f, 0f, 300f), Parry("healAlliesOnParry", "Heal allies", 10f, 0f, 300f), Parry("staminaAlliesOnParry", "Stamina to allies", 10f, 0f, 300f), Parry("healTamedOnParry", "Heal tamed", 10f, 0f, 300f), Parry("shieldOnParry", "Bubble health", 100f, 0f, 3000f), new StatDef { Key = "shieldMinutes", Label = "Bubble time", Group = "On parry", Kind = StatKind.Parry, Step = 1f, Min = 1f, Max = 10f, Default = 1f, TakesLargest = true, Unit = " min", OnlyOn = HandSlots }, new StatDef { Key = "parryRadius", Label = "Reach", Group = "On parry", Kind = StatKind.Parry, Step = 5f, Min = 5f, Max = 50f, Default = 10f, TakesLargest = true, Unit = " m", OnlyOn = HandSlots } }; public const string ParryRadiusKey = "parryRadius"; public const string EitrCostKey = "eitrCost"; private static readonly BundleSlot[] WeaponSlots = new BundleSlot[2] { BundleSlot.Melee, BundleSlot.Ranged }; private static readonly (string Key, string Group)[] s_weaponOnlyNested = new(string, string)[2] { ("damage", "Damage %"), ("addDamage", "Added damage") }; public const string BlockArmorKey = "blockArmor"; public const string BlockForceKey = "blockForce"; public static readonly StatDef[] Toggles = new StatDef[2] { new StatDef { Key = "woodcutter", Label = "Woodcutter", Group = "Class", Kind = StatKind.Toggle, Step = 1f, Min = 0f, Max = 1f, TakesLargest = true, OnlyOn = WeaponSlots }, new StatDef { Key = "miner", Label = "Miner", Group = "Class", Kind = StatKind.Toggle, Step = 1f, Min = 0f, Max = 1f, TakesLargest = true, OnlyOn = WeaponSlots } }; public const float DamageStep = 5f; public const float DamageMin = -50f; public const float DamageMax = 200f; public const string AddedDamage = "Added damage"; public const string AddDamageKey = "addDamage"; public const float AddDamageStep = 5f; public const float AddDamageMin = 0f; public const float AddDamageMax = 300f; public static readonly string[] AddDamageTypes = new string[8] { "Blunt", "Slash", "Pierce", "Fire", "Frost", "Lightning", "Poison", "Spirit" }; public const float SkillStep = 5f; public const float SkillMin = -50f; public const float SkillMax = 100f; public static readonly string[] DamageTypes = Catalog.ModifierTypes; public static readonly string[] ModifierCycle = new string[8] { "Normal", "SlightlyResistant", "Resistant", "VeryResistant", "Immune", "SlightlyWeak", "Weak", "VeryWeak" }; private static readonly string[] s_protection = new string[9] { "Immune", "Ignore", "VeryResistant", "Resistant", "SlightlyResistant", "Normal", "SlightlyWeak", "Weak", "VeryWeak" }; public static readonly string[] SkillNames = (from n in Enum.GetNames(typeof(SkillType)) where n != "None" && n != "Step" select n).ToArray(); public static readonly string[] WindowSkills = SkillNames.Where((string n) => n != "All").ToArray(); public static string WeaponSlotsText => SlotsText(WeaponSlots); public static float WithEitrCost(float cost, float percent) { return cost * Math.Max(0f, 1f + percent / 100f); } public static bool IsWeaponSlot(BundleSlot slot) { return Array.IndexOf(WeaponSlots, slot) >= 0; } public static string SlotsText(BundleSlot[] slots) { List<string> list = slots.Select(BundleSlots.Key).ToList(); string text = list[list.Count - 1]; return ((list.Count == 1) ? text : (string.Join(", ", list.Take(list.Count - 1)) + " or " + text)) + " slot"; } public static (string Key, string Group)? WeaponOnlyNested(string key) { (string, string)[] array = s_weaponOnlyNested; for (int i = 0; i < array.Length; i++) { (string, string) value = array[i]; if (string.Equals(value.Item1, key?.Trim(), StringComparison.OrdinalIgnoreCase)) { return value; } } return null; } public static float WithBlockForce(float baseBlockForce, float bonus) { return Math.Max(0f, baseBlockForce + bonus); } public static float WithBlockArmor(float baseBlockArmor, float bonus) { return Math.Max(Math.Min(baseBlockArmor, 1f), baseBlockArmor + bonus); } public static int Protection(string modifier) { int num = Array.IndexOf(s_protection, modifier); if (num >= 0) { return num; } return int.MaxValue; } public static StatDef Find(string key) { return Scalars.Concat(Toggles).FirstOrDefault((StatDef d) => string.Equals(d.Key, key?.Trim(), StringComparison.OrdinalIgnoreCase)); } public static string ModifierLabel(string modifier) { return modifier switch { "SlightlyResistant" => "Slightly resistant", "VeryResistant" => "Very resistant", "SlightlyWeak" => "Slightly weak", "VeryWeak" => "Very weak", _ => modifier, }; } public static FieldInfo RawField(string name, out string error) { error = null; FieldInfo field = typeof(SE_Stats).GetField(name?.Trim() ?? "", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field == null) { error = "'" + name + "' is not a field of SE_Stats"; return null; } if (field.DeclaringType != typeof(SE_Stats)) { error = field.Name + " can't be set from a buff (only SE_Stats' own stat fields can)"; return null; } Type fieldType = field.FieldType; if (fieldType == typeof(float) || fieldType == typeof(int) || fieldType == typeof(bool) || fieldType.IsEnum) { return field; } error = name + " (" + fieldType.Name + ") can't be set from a buff"; return null; } private static StatDef Def(string key, string label, string group, string field, StatKind kind, float step, float min, float max) { return new StatDef { Key = key, Label = label, Group = group, Field = field, Kind = kind, Step = step, Min = min, Max = max }; } private static StatDef Parry(string key, string label, float step, float min, float max) { StatDef statDef = Def(key, label, "On parry", null, StatKind.Parry, step, min, max); statDef.OnlyOn = HandSlots; return statDef; } private static StatDef Cost(string key, string label, string group, string field, float step, float min, float max) { StatDef statDef = Def(key, label, group, field, StatKind.Percent, step, min, max); statDef.LowerIsBetter = true; return statDef; } } internal sealed class StatBlock { public readonly Dictionary<string, float> Scalars = new Dictionary<string, float>(StringComparer.Ordinal); public readonly Dictionary<string, string> Resist = new Dictionary<string, string>(StringComparer.Ordinal); public readonly Dictionary<string, float> Damage = new Dictionary<string, float>(StringComparer.Ordinal); public readonly Dictionary<string, float> Skills = new Dictionary<string, float>(StringComparer.Ordinal); public readonly Dictionary<string, float> AddDamage = new Dictionary<string, float>(StringComparer.Ordinal); public readonly List<KeyValuePair<string, string>> Fields = new List<KeyValuePair<string, string>>(); public float BlockArmor { get { if (!Scalars.TryGetValue("blockArmor", out var value)) { return 0f; } return value; } } public float BlockForce { get { if (!Scalars.TryGetValue("blockForce", out var value)) { return 0f; } return value; } } public float EitrCost { get { if (!Scalars.TryGetValue("eitrCost", out var value)) { return 0f; } return value; } } public int Count => Scalars.Count + Resist.Count + Damage.Count + AddDamage.Count + Skills.Count + Fields.Select((KeyValuePair<string, string> f) => f.Key).Distinct().Count(); public bool IsEmpty => Count == 0; public StatBlock Clone() { StatBlock statBlock = new StatBlock(); foreach (KeyValuePair<string, float> scalar in Scalars) { statBlock.Scalars[scalar.Key] = scalar.Value; } foreach (KeyValuePair<string, string> item in Resist) { statBlock.Resist[item.Key] = item.Value; } foreach (KeyValuePair<string, float> item2 in Damage) { statBlock.Damage[item2.Key] = item2.Value; } foreach (KeyValuePair<string, float> skill in Skills) { statBlock.Skills[skill.Key] = skill.Value; } foreach (KeyValuePair<string, float> item3 in AddDamage) { statBlock.AddDamage[item3.Key] = item3.Value; } statBlock.Fields.AddRange(Fields); return statBlock; } public ParryAssist ToParryAssist() { ParryAssist parryAssist = new ParryAssist { Heal = Get("healOnParry"), Stamina = Get("staminaOnParry"), HealAllies = Get("healAlliesOnParry"), StaminaAllies = Get("staminaAlliesOnParry"), HealTamed = Get("healTamedOnParry"), Shield = Get("shieldOnParry") }; if (Scalars.TryGetValue("parryRadius", out var value)) { parryAssist.Radius = value; } if (Scalars.TryGetValue("shieldMinutes", out var value2)) { parryAssist.ShieldMinutes = value2; } parryAssist.TamedRadius = parryAssist.Radius; if (!parryAssist.DoesSomething) { return null; } return parryAssist; float Get(string key) { if (!Scalars.TryGetValue(key, out var value3)) { return 0f; } return value3; } } public static void SetNumber(Dictionary<string, float> into, string key, float value) { if (Math.Abs(value) < 0.0001f) { into.Remove(key); } else { into[key] = value; } } public void SetResist(string type, string modifier) { if (modifier == null || modifier == "Normal") { Resist.Remove(type); } else { Resist[type] = modifier; } } public static StatBlock Sum(IEnumerable<StatBlock> blocks) { StatBlock statBlock = new StatBlock(); foreach (StatBlock block in blocks) { if (block == null) { continue; } foreach (KeyValuePair<string, float> scalar in block.Scalars) { bool flag = BundleStatCatalog.Find(scalar.Key)?.TakesLargest ?? false; SetNumber(statBlock.Scalars, scalar.Key, (!statBlock.Scalars.TryGetValue(scalar.Key, out var value)) ? scalar.Value : (flag ? Math.Max(value, scalar.Value) : (value + scalar.Value))); } foreach (KeyValuePair<string, float> item in block.Damage) { SetNumber(statBlock.Damage, item.Key, statBlock.Damage.TryGetValue(item.Key, out var value2) ? (value2 + item.Value) : item.Value); } foreach (KeyValuePair<string, float> skill in block.Skills) { SetNumber(statBlock.Skills, skill.Key, statBlock.Skills.TryGetValue(skill.Key, out var value3) ? (value3 + skill.Value) : skill.Value); } foreach (KeyValuePair<string, float> item2 in block.AddDamage) { SetNumber(statBlock.AddDamage, item2.Key, statBlock.AddDamage.TryGetValue(item2.Key, out var value4) ? (value4 + item2.Value) : item2.Value); } foreach (KeyValuePair<string, string> item3 in block.Resist) { if (!statBlock.Resist.TryGetValue(item3.Key, out var value5) || BundleStatCatalog.Protection(item3.Value) < BundleStatCatalog.Protection(value5)) { statBlock.Resist[item3.Key] = item3.Value; } } statBlock.Fields.AddRange(block.Fields); } return statBlock; } public string Summary() { List<string> groups = new List<string>(); List<StatDef> source = BundleStatCatalog.Scalars.Where((StatDef d) => Scalars.ContainsKey(d.Key)).ToList(); Group(null, from d in source where d.Group == "General" || d.Group == "Regen" || d.Key == "eitrCost" select Value(d, Scalars[d.Key]) + " " + d.Label.ToLowerInvariant()); Group("stamina cost", from d in source where d.Group == "Costs" && d.Key != "eitrCost" select d.Label.Split(new char[1] { ' ' })[0].ToLowerInvariant() + " " + Value(d, Scalars[d.Key])); Group("on parry", from d in source where d.Group == "On parry" select d.Label.ToLowerInvariant() + " " + Value(d, Scalars[d.Key])); Group("class", from d in BundleStatCatalog.Toggles where Scalars.ContainsKey(d.Key) select d.Label); Group("resist", from t in BundleStatCatalog.DamageTypes.Where(Resist.ContainsKey) select t + " " + BundleStatCatalog.ModifierLabel(Resist[t])); Group("damage", from t in BundleStatCatalog.DamageTypes.Select((string t) => t.ToLowerInvariant()).Where(Damage.ContainsKey) select t + " " + Number(Damage[t]) + "%"); Group("added", from t in AddedTypes() select t + " " + Number(AddDamage[t])); Group("skills", from p in Skills.OrderBy<KeyValuePair<string, float>, string>((KeyValuePair<string, float> p) => p.Key, StringComparer.Ordinal) select p.Key + " " + Number(p.Value)); Group("fields", Fields.Select((KeyValuePair<string, string> f) => f.Key).Distinct()); if (groups.Count != 0) { return string.Join("; ", groups); } return "none"; void Group(string title, IEnumerable<string> items) { List<string> list = items.ToList(); if (list.Count > 0) { groups.Add((title == null) ? string.Join(", ", list) : (title + ": " + string.Join(", ", list))); } } static string Value(StatDef def, float v) { if (!def.TakesLargest) { return Number(v) + (def.IsPercent ? "%" : ""); } return Plain(v) + def.Unit; } } public string Serialize() { List<string> list = new List<string>(); StatDef[] scalars = BundleStatCatalog.Scalars; foreach (StatDef statDef in scalars) { if (Scalars.TryGetValue(statDef.Key, out var value)) { list.Add(statDef.Key + ": " + Plain(value)); } } scalars = BundleStatCatalog.Toggles; foreach (StatDef statDef2 in scalars) { if (Scalars.ContainsKey(statDef2.Key)) { list.Add(statDef2.Key + ": true"); } } if (Resist.Count > 0) { list.Add("resist: { " + string.Join(", ", from t in BundleStatCatalog.DamageTypes.Where(Resist.ContainsKey) select t + ": " + Resist[t]) + " }"); } if (Damage.Count > 0) { list.Add("damage: { " + string.Join(", ", from t in BundleStatCatalog.DamageTypes.Select((string t) => t.ToLowerInvariant()).Where(Damage.ContainsKey) select t + ": " + Plain(Damage[t])) + " }"); } if (AddDamage.Count > 0) { list.Add("addDamage: { " + string.Join(", ", from t in AddedTypes() select t + ": " + Plain(AddDamage[t])) + " }"); } if (Skills.Count > 0) { list.Add("skills: { " + string.Join(", ", from p in Skills.OrderBy<KeyValuePair<string, float>, string>((KeyValuePair<string, float> p) => p.Key, StringComparer.Ordinal) select p.Key + ": " + Plain(p.Value)) + " }"); } if (Fields.Count > 0) { list.Add("fields: { " + string.Join(", ", Fields.Select((KeyValuePair<string, string> p) => p.Key + ": " + Values.Quote(p.Value))) + " }"); } if (list.Count != 0) { return "{ " + string.Join(", ", list) + " }"; } return "{}"; } public static StatBlock Parse(YamlMappingNode map, string label, List<string> warnings, BundleSlot? slot = null, List<(string What, string Where)> misplaced = null) { StatBlock statBlock = new StatBlock(); foreach (KeyValuePair<YamlNode, YamlNode> child in map.Children) { string text = (child.Key as YamlScalarNode)?.Value?.Trim() ?? ""; long line = child.Key.Start.Line; StatDef statDef = BundleStatCatalog.Find(text); if (statDef != null && slot.HasValue && !statDef.Allows(slot.Value)) { warnings.Add($"{label}.{statDef.Key}: only works on the {statDef.WhereAllowed}; skipped (line {line})."); misplaced?.Add((statDef.Label, statDef.WhereAllowed)); continue; } (string, string)? tuple = BundleStatCatalog.WeaponOnlyNested(text); if (tuple.HasValue && slot.HasValue && !BundleStatCatalog.IsWeaponSlot(slot.Value)) { string weaponSlotsText = BundleStatCatalog.WeaponSlotsText; warnings.Add($"{label}.{tuple.Value.Item1}: only works on the {weaponSlotsText}; skipped (line {line})."); misplaced?.Add((tuple.Value.Item2, weaponSlotsText)); continue; } if (statDef != null && statDef.Kind == StatKind.Toggle) { string text2 = (child.Value as YamlScalarNode)?.Value?.Trim().ToLowerInvariant(); switch (text2) { case "true": case "yes": case "on": case "1": statBlock.Scalars[statDef.Key] = 1f; continue; case "false": case "no": case "off": case "0": statBlock.Scalars.Remove(statDef.Key); continue; } warnings.Add($"{label}.{statDef.Key}: '{text2}' is not true or false; skipped (line {line})."); continue; } if (statDef != null) { if (!TryNumber(child.Value, out var value, out var error)) { warnings.Add($"{label}.{statDef.Key}: {error}; skipped (line {line})."); } else if (statDef.Kind == StatKind.Parry && value <= 0f) { warnings.Add($"{label}.{statDef.Key}: '{(child.Value as YamlScalarNode)?.Value}' must be more than 0; skipped (line {line})."); } else { SetNumber(statBlock.Scalars, statDef.Key, value); } continue; } switch (text.ToLowerInvariant()) { case "resist": foreach (var item16 in Entries(child.Value, label + ".resist", "{ Fire: Resistant }", warnings)) { string item = item16.name; string item2 = item16.value; long item3 = item16.line; string text3 = Catalog.MatchIgnoreCase(BundleStatCatalog.DamageTypes, item); string text4 = Catalog.MatchIgnoreCase(Enum.GetNames(typeof(DamageModifier)), item2?.Replace(" ", "")); if (text3 == null) { warnings.Add(string.Format("{0}.{1}.{2}: unknown damage type, skipped (line {3}). Use: {4}.", label, "resist", item, item3, string.Join(", ", BundleStatCatalog.DamageTypes))); } else if (text4 == null) { warnings.Add(string.Format("{0}.{1}.{2}: '{3}' is not one of: {4}; skipped (line {5}).", label, "resist", text3, item2, string.Join(", ", Enum.GetNames(typeof(DamageModifier))), item3)); } else { statBlock.SetResist(text3, text4); } } break; case "damage": foreach (var item17 in Entries(child.Value, label + ".damage", "{ slash: 10 }", warnings)) { string item4 = item17.name; string item5 = item17.value; long item6 = item17.line; string text5 = Catalog.MatchIgnoreCase(BundleStatCatalog.DamageTypes, item4); float value2; string error2; if (text5 == null) { warnings.Add(string.Format("{0}.{1}.{2}: unknown damage type, skipped (line {3}). Use: {4}.", label, "damage", item4, item6, string.Join(", ", BundleStatCatalog.DamageTypes.Select((string t) => t.ToLowerInvariant())))); } else if (TryNumber(item5, out value2, out error2)) { SetNumber(statBlock.Damage, text5.ToLowerInvariant(), value2); } else { warnings.Add(string.Format("{0}.{1}.{2}: {3}; skipped (line {4}).", label, "damage", text5.ToLowerInvariant(), error2, item6)); } } break; case "adddamage": foreach (var item18 in Entries(child.Value, label + ".addDamage", "{ spirit: 30 }", warnings)) { string item13 = item18.name; string item14 = item18.value; long item15 = item18.line; string text7 = Catalog.MatchIgnoreCase(BundleStatCatalog.AddDamageTypes, item13)?.ToLowerInvariant(); string arg = label + ".addDamage." + (text7 ?? item13); float value5; string error5; if (text7 == null) { warnings.Add($"{arg}: unknown or unsupported damage type, skipped (line {item15}). Use: " + string.Join(", ", BundleStatCatalog.AddDamageTypes.Select((string t) => t.ToLowerInvariant())) + "."); } else if (!TryNumber(item14, out value5, out error5)) { warnings.Add($"{arg}: {error5}; skipped (line {item15})."); } else if (value5 <= 0f) { warnings.Add($"{arg}: '{item14}' must be more than 0; skipped (line {item15})."); } else { SetNumber(statBlock.AddDamage, text7, value5); } } break; case "skills": foreach (var item19 in Entries(child.Value, label + ".skills", "{ Bows: 15 }", warnings)) { string item10 = item19.name; string item11 = item19.value; long item12 = item19.line; string text6 = Catalog.MatchIgnoreCase(BundleStatCatalog.SkillNames, item10); float value4; string error4; if (text6 == null) { warnings.Add(string.Format("{0}.{1}.{2}: unknown skill, skipped (line {3}). Skills: {4}.", label, "skills", item10, item12, string.Join(", ", BundleStatCatalog.SkillNames))); } else if (TryNumber(item11, out value4, out error4)) { SetNumber(statBlock.Skills, text6, value4); } else { warnings.Add(string.Format("{0}.{1}.{2}: {3}; skipped (line {4}).", label, "skills", text6, error4, item12)); } } break; case "fields": foreach (var item20 in Entries(child.Value, label + ".fields", "{ m_swimSpeedModifier: 0.2 }", warnings)) { string item7 = item20.name; string item8 = item20.value; long item9 = item20.line; string error3; FieldInfo fieldInfo = BundleStatCatalog.RawField(item7, out error3); object value3; if (fieldInfo == null) { warnings.Add(string.Format("{0}.{1}.{2}: {3}; skipped (line {4}).", label, "fields", item7, error3, item9)); } else if (!Values.TryParse(item8, fieldInfo.FieldType, out value3, out error3)) { warnings.Add(string.Format("{0}.{1}.{2}: {3}; skipped (line {4}).", label, "fields", fieldInfo.Name, error3, item9)); } else { statBlock.Fields.Add(new KeyValuePair<string, string>(fieldInfo.Name, item8.Trim())); } } break; default: warnings.Add($"{label}.{text}: unknown stat, skipped (line {line}). Stats: " + string.Join(", ", from d in BundleStatCatalog.Scalars.Concat(BundleStatCatalog.Toggles) select d.Key) + ", resist, damage, addDamage, skills, fields."); break; } } return statBlock; } private static IEnumerable<(string name, string value, long line)> Entries(YamlNode node, string label, string example, List<string> warnings) { if (!(node is YamlMappingNode yamlMappingNode)) { warnings.Add($"{label}: expected a mapping like {example}; skipped (line {node.Start.Line})."); yield break; } foreach (KeyValuePair<YamlNode, YamlNode> child in yamlMappingNode.Children) { string text = (child.Key as YamlScalarNode)?.Value ?? ""; if (child.Value is YamlScalarNode yamlScalarNode) { yield return (name: text, value: yamlScalarNode.Value, line: child.Key.Start.Line); } else { warnings.Add($"{label}.{text}: expected a single value; skipped (line {child.Key.Start.Line})."); } } } private static bool TryNumber(YamlNode node, out float value, out string error) { value = 0f; if (!(node is YamlScalarNode yamlScalarNode)) { error = "expected a number"; return false; } return TryNumber(yamlScalarNode.Value, out value, out error); } public static bool TryNumber(string text, out float value, out string error) { error = null; value = 0f; string text2 = (text ?? "").Trim(); if (text2.EndsWith("%", StringComparison.Ordinal)) { text2 = text2.Substring(0, text2.Length - 1).Trim(); } if (double.TryParse(text2, NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && !double.IsNaN(result) && !double.IsInfinity(result) && Math.Abs(result) < 1000000.0) { value = (float)result; return true; } error = "'" + text + "' is not a number"; return false; } public static Dictionary<string, float> DamageByType(DamageTypes damages) { //IL_000b: 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_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0071: 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_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) Dictionary<string, float> dictionary = new Dictionary<string, float> { ["blunt"] = damages.m_blunt, ["slash"] = damages.m_slash, ["pierce"] = damages.m_pierce, ["chop"] = damages.m_chop, ["pickaxe"] = damages.m_pickaxe, ["fire"] = damages.m_fire, ["frost"] = damages.m_frost, ["lightning"] = damages.m_lightning, ["poison"] = damages.m_poison, ["spirit"] = damages.m_spirit }; Dictionary<string, float> dictionary2 = new Dictionary<string, float>(StringComparer.Ordinal); foreach (string item in BundleStatCatalog.DamageTypes.Select((string t) => t.ToLowerInvariant())) { if (dictionary.TryGetValue(item, out var value) && value > 0f) { dictionary2[item] = value; } } return dictionary2; } private IEnumerable<string> AddedTypes() { return BundleStatCatalog.AddDamageTypes.Select((string t) => t.ToLowerInvariant()).Where(AddDamage.ContainsKey); } public static DamageTypes AddDamageTo(DamageTypes damages, IReadOnlyDictionary<string, float> add) { //IL_01e4: Unknown result type (might be due to invalid IL or missing references) foreach (KeyValuePair<string, float> item in add) { switch (item.Key) { case "blunt": damages.m_blunt += item.Value; break; case "slash": damages.m_slash += item.Value; break; case "pierce": damages.m_pierce += item.Value; break; case "fire": damages.m_fire += item.Value; break; case "frost": damages.m_frost += item.Value; break; case "lightning": damages.m_lightning += item.Value; break; case "poison": damages.m_poison += item.Value; break; case "spirit": damages.m_spirit += item.Value; break; } } return damages; } public static string Number(float v) { return v.ToString("+0.##;-0.##;0", CultureInfo.InvariantCulture); } private static string Plain(float v) { return v.ToString("0.##", CultureInfo.InvariantCulture); } } public sealed class BundleStatsEffect : SE_Stats { public const string AssetName = "LoadoutBuffs_BundleStats"; public Dictionary<SkillType, float> m_bundleSkills = new Dictionary<SkillType, float>(); public int m_statCount; internal ParryAssist m_parry; internal string m_classes; internal string m_effectNames; internal float m_blockArmor; internal float m_blockForce; internal float m_eitrCost; internal Dictionary<string, float> m_addDamage = new Dictionary<string, float>(); private static BundleStatsEffect s_template; private static SE_Stats s_defaults; private static Sprite s_icon; private static readonly HashSet<string> s_rawFieldsSet = new HashSet<string>(StringComparer.Ordinal); internal static BundleStatsEffect Template { get { if ((Object)(object)s_template != (Object)null) { return s_template; } s_template = ScriptableObject.CreateInstance<BundleStatsEffect>(); ((Object)s_template).name = "LoadoutBuffs_BundleStats"; ((StatusEffect)s_template).m_name = "Buff"; ((StatusEffect)s_template).m_tooltip = "Your buff in use: its effects and custom stats from the slots you fill."; ((StatusEffect)s_template).m_icon = Icon(); ((StatusEffect)s_template).m_ttl = 0f; ((Object)s_template).hideFlags = (HideFlags)61; return s_template; } } public override void ModifySkillLevel(SkillType skill, ref float value) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Invalid comparison between Unknown and I4 ((SE_Stats)this).ModifySkillLevel(skill, ref value); if (m_bundleSkills == null) { return; } foreach (KeyValuePair<SkillType, float> bundleSkill in m_bundleSkills) { if (bundleSkill.Key == skill || (int)bundleSkill.Key == 999) { value += bundleSkill.Value; } } } public override string GetIconText() { if (m_statCount <= 0) { return ""; } return string.Format("{0} stat{1}", m_statCount, (m_statCount == 1) ? "" : "s"); } public override string GetTooltipString() { //IL_0079: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(((SE_Stats)this).GetTooltipString()); if (!string.IsNullOrEmpty(m_effectNames)) { stringBuilder.AppendFormat("Effects: <color=orange>{0}</color>\n", m_effectNames); } if (m_bundleSkills != null) { foreach (KeyValuePair<SkillType, float> item in m_bundleSkills.OrderBy<KeyValuePair<SkillType, float>, string>((KeyValuePair<SkillType, float> p) => ((object)p.Key/*cast due to .constrained prefix*/).ToString(), StringComparer.Ordinal)) { stringBuilder.AppendFormat("{0}: <color=orange>{1}</color>\n", SkillName(item.Key), StatBlock.Number(item.Value)); } } if (m_parry != null) { stringBuilder.AppendFormat("On parry: <color=orange>{0}</color>\n", StatsReport.DescribeParry(m_parry)); } if (m_addDamage != null && m_addDamage.Count > 0) { stringBuilder.AppendFormat("Added damage: <color=orange>{0}</color> (the weapon you attack with)\n", string.Join(", ", from t in BundleStatCatalog.AddDamageTypes.Select((string t) => t.ToLowerInvariant()).Where(m_addDamage.ContainsKey) select t + " " + StatBlock.Number(m_addDamage[t]))); } if (m_blockArmor != 0f) { stringBuilder.AppendFormat("Block armor: <color=orange>{0}</color> (what you block with)\n", StatBlock.Number(m_blockArmor)); } if (m_blockForce != 0f) { stringBuilder.AppendFormat("Block force: <color=orange>{0}</color> (what you block with)\n", StatBlock.Number(m_blockForce)); } if (m_eitrCost != 0f) { stringBuilder.AppendFormat("Eitr cost: <color=orange>{0}%</color>\n", StatBlock.Number(m_eitrCost)); } if (!string.IsNullOrEmpty(m_classes)) { stringBuilder.AppendFormat("Class: <color=orange>{0}</color> (your weapon's hits)\n", m_classes); } return stringBuilder.ToString(); } internal void SetTotals(StatBlock total, string bundleName, string effectNames) { //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) ((StatusEffect)this).m_name = bundleName; m_effectNames = effectNames; m_statCount = total.Count; m_parry = total.ToParryAssist(); m_blockArmor = total.BlockArmor; m_blockForce = total.BlockForce; m_eitrCost = total.EitrCost; m_addDamage = new Dictionary<string, float>(total.AddDamage); m_classes = string.Join(", ", from d in BundleStatCatalog.Toggles where total.Scalars.ContainsKey(d.Key) select d.Label); StatDef[] scalars = BundleStatCatalog.Scalars; foreach (StatDef statDef in scalars) { if (statDef.Field != null) { total.Scalars.TryGetValue(statDef.Key, out var value); SetField(value: statDef.Kind switch { StatKind.Percent => value / 100f, StatKind.RegenPercent => 1f + value / 100f, _ => value, }, name: statDef.Field); } } base.m_mods = ((IEnumerable<KeyValuePair<string, string>>)total.Resist).Select((Func<KeyValuePair<string, string>, DamageModPair>)((KeyValuePair<string, string> p) => new DamageModPair { m_type = (DamageType)Enum.Parse(typeof(DamageType), p.Key), m_modifier = (DamageModifier)Enum.Parse(typeof(DamageModifier), p.Value) })).ToList(); object obj = (object)default(DamageTypes); foreach (KeyValuePair<string, float> item in total.Damage) { typeof(DamageTypes).GetField("m_" + item.Key)?.SetValue(obj, item.Value / 100f); } base.m_percentigeDamageModifiers = (DamageTypes)obj; m_bundleSkills = ((IEnumerable<KeyValuePair<string, float>>)total.Skills).ToDictionary((Func<KeyValuePair<string, float>, SkillType>)((KeyValuePair<string, float> p) => (SkillType)Enum.Parse(typeof(SkillType), p.Key)), (Func<KeyValuePair<string, float>, float>)((KeyValuePair<string, float> p) => p.Value)); SetRawFields(total.Fields); } private void SetRawFields(List<KeyValuePair<string, string>> fields) { SE_Stats obj = Defaults(); foreach (string item in s_rawFieldsSet) { FieldInfo field = typeof(SE_Stats).GetField(item, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); field?.SetValue(this, field.GetValue(obj)); } foreach (IGrouping<string, KeyValuePair<string, string>> item2 in from p in fields group p by p.Key) { string error; FieldInfo fieldInfo = BundleStatCatalog.RawField(item2.Key, out error); if (fieldInfo == null) { continue; } object value = fieldInfo.GetValue(obj); object obj2 = value; foreach (KeyValuePair<string, string> item3 in item2) { if (Values.TryParse(item3.Value, fieldInfo.FieldType, out var value2, out error)) { obj2 = ((!(fieldInfo.FieldType == typeof(float))) ? ((!(fieldInfo.FieldType == typeof(int))) ? value2 : ((object)((int)obj2 + ((int)value2 - (int)value)))) : ((object)((float)obj2 + ((float)value2 - (float)value)))); } } fieldInfo.SetValue(this, obj2); s_rawFieldsSet.Add(fieldInfo.Name); } } private void SetField(string name, float value) { typeof(SE_Stats).GetField(name)?.SetValue(this, value); } private static SE_Stats Defaults() { if ((Object)(object)s_defaults != (Object)null) { return s_defaults; } s_defaults = ScriptableObject.CreateInstance<SE_Stats>(); ((Object)s_defaults).hideFlags = (HideFlags)61; return s_defaults; } private unsafe static string SkillName(SkillType skill) { string text = "$skill_" + ((object)(*(SkillType*)(&skill))/*cast due to .constrained prefix*/).ToString().ToLowerInvariant(); Localization instance = Localization.instance; string text2 = ((instance != null) ? instance.Localize(text) : null); if (!string.IsNullOrEmpty(text2) && !text2.StartsWith("[", StringComparison.Ordinal)) { return text2; } return ((object)(*(SkillType*)(&skill))/*cast due to .constrained prefix*/).ToString(); } private static Sprite Icon() { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)s_icon != (Object)null) { return s_icon; } try { using Stream stream = typeof(BundleStatsEffect).Assembly.GetManifestResourceStream("LoadoutBuffs.icon.png"); byte[] array = new byte[stream.Length]; for (int i = 0; i < array.Length; i += stream.Read(array, i, array.Length - i)) { } Texture2D val = new Texture2D(2, 2) { hideFlags = (HideFlags)61 }; MethodInfo methodInfo = Type.GetType("UnityEngine.ImageConversion, UnityEngine.ImageConversionModule")?.GetMethod("LoadImage", new Type[2] { typeof(Texture2D), typeof(byte[]) }); if (methodInfo == null || !(bool)methodInfo.Invoke(null, new object[2] { val, array })) { throw new InvalidOperationException("ImageConversion.LoadImage unavailable or failed"); } s_icon = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f)); ((Object)s_icon).hideFlags = (HideFlags)61; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Buff stats icon could not be loaded, the buff shows no icon: " + ex.Message)); } return s_icon; } } internal static class InventoryButton { [CompilerGenerated] private static class <>O { public static UnityAction <0>__Toggle; } private const string ObjectName = "LoadoutBuffs_BundlesButton"; private const string Label = "BUFFS"; private static GameObject s_button; private static TMP_Text s_label; private static bool s_isTab; public static void Ensure(InventoryGui gui) { if (!((Object)(object)s_button != (Object)null) && !((Object)(object)gui == (Object)null)) { s_isTab = false; s_button = TryCloneTab(gui) ?? JotunnButton(gui); Layout(gui); } } private static GameObject TryCloneTab(InventoryGui gui) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Expected O, but got Unknown Button tabCraft = gui.m_tabCraft; if ((Object)(object)tabCraft == (Object)null || (Object)(object)gui.m_tabUpgrade == (Object)null) { Plugin.Log.LogInfo((object)"Buffs button: crafting tabs not found, using a Jötunn button."); return null; } GameObject val = Object.Instantiate<GameObject>(((Component)tabCraft).gameObject, ((Component)tabCraft).transform.parent); ((Object)val).name = "LoadoutBuffs_BundlesButton"; Button component = val.GetComponent<Button>(); component.onClick = new ButtonClickedEvent(); ButtonClickedEvent onClick = component.onClick; object obj = <>O.<0>__Toggle; if (obj == null) { UnityAction val2 = BundleWindow.Toggle; <>O.<0>__Toggle = val2; obj = (object)val2; } ((UnityEvent)onClick).AddListener((UnityAction)obj); ((Selectable)component).interactable = true; s_label = val.GetComponentInChildren<TMP_Text>(true); if ((Object)(object)s_label != (Object)null) { s_label.text = "BUFFS"; } s_isTab = true; val.SetActive(true); Plugin.Log.LogInfo((object)("Buffs button: copied the CRAFT tab (parent '" + ((Object)val.transform.parent).name + "', label " + (((Object)(object)s_label != (Object)null) ? "set" : "not found") + ").")); return val; } public static void Layout(InventoryGui gui) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0086: 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_008c: 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_0091: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Expected O, but got Unknown //IL_00a6: 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_00b6: Unknown result type (might be due to invalid IL or missing references) if (s_isTab && !((Object)(object)s_button == (Object)null) && !((Object)(object)gui == (Object)null)) { RectTransform val = (RectTransform)((Component)gui.m_tabCraft).transform; RectTransform val2 = (RectTransform)((Component)gui.m_tabUpgrade).transform; Vector2 val3 = val2.anchoredPosition - val.anchoredPosition; RectTransform val4 = (((Component)val2).gameObject.activeSelf ? val2 : (((Component)val).gameObject.activeSelf ? val : null)); Vector2 val5 = (((Object)(object)val4 != (Object)null) ? (val4.anchoredPosition + val3) : val.anchoredPosition); RectTransform val6 = (RectTransform)s_button.transform; if (val6.anchoredPosition != val5) { val6.anchoredPosition = val5; } if (!s_button.activeSelf) { s_button.SetActive(true); } if ((Object)(object)s_labe