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 Passive Trinket Modifiers v0.35.1
plugins/TrinketModifiers.dll
Decompiled 2 weeks agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [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("TrinketModifiers")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.32.2.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("TrinketModifiers")] [assembly: AssemblyTitle("TrinketModifiers")] [assembly: AssemblyVersion("0.32.2.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace TrinketModifiers { [BepInPlugin("com.gdall.trinketmodifiers", "Trinket Modifiers", "0.32.2")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.gdall.trinketmodifiers"; public const string PluginName = "Trinket Modifiers"; public const string PluginVersion = "0.32.2"; internal static ManualLogSource Log; private ConfigEntry<bool> _enabled; private ConfigEntry<bool> _debugLogging; internal static ConfigEntry<bool> DoublingOnAdrenaline; internal static ConfigEntry<float> AdrenalineGainMultiplier; internal static bool DebugLogging { get; private set; } private void Awake() { //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Expected O, but got Unknown //IL_01a5: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; _enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enables the Trinket Modifiers plugin."); _debugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DebugLogging", false, "Writes additional diagnostic messages to LogOutput.log."); DebugLogging = _debugLogging.Value; DoublingOnAdrenaline = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DoublingOnAdrenaline", false, "When adrenaline fills, use each equipped trinket's doubled values for its configured duration."); DoublingOnAdrenaline.SettingChanged += OnDoublingOnAdrenalineChanged; AdrenalineGainMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("General", "AdrenalineGainMultiplier", 0f, new ConfigDescription("Global adrenaline gain multiplier. 0.0 disables gain; 1.0 is unchanged; 2.0 is double gain.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, float.MaxValue), Array.Empty<object>())); AdrenalineGainMultiplier.SettingChanged += OnAdrenalineGainMultiplierChanged; ClampAdrenalineGainMultiplier(); TrinketConfigRegistry.Initialize(((BaseUnityPlugin)this).Config); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Trinket Modifiers v0.32.2 loaded."); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Configuration created: " + ((BaseUnityPlugin)this).Config.ConfigFilePath)); ((BaseUnityPlugin)this).Logger.LogInfo((object)($"Enabled={_enabled.Value}; DebugLogging={_debugLogging.Value}; " + $"DoublingOnAdrenaline={DoublingOnAdrenaline.Value}; " + $"AdrenalineGainMultiplier={AdrenalineGainMultiplier.Value}")); if (_enabled.Value) { new Harmony("com.gdall.trinketmodifiers").PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Native configurable passive trinket effect engine installed."); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Save safety protection active: player serialization target found=" + $"{PlayerSaveDataPatch.IsAvailable}. Runtime cloned status effects are removed before saving.")); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Native SE_Stats path active: persistent passive modifiers are written to one status effect per equipped trinket."); ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Configured item-hover tooltip patch active: target found={TrinketTooltipPatch.IsAvailable}."); } if (_debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Debug logging is enabled."); } } private void OnAdrenalineGainMultiplierChanged(object sender, EventArgs args) { ClampAdrenalineGainMultiplier(); } private void OnDoublingOnAdrenalineChanged(object sender, EventArgs args) { TrinketEffectManager.OnDoublingSettingChanged(DoublingOnAdrenaline.Value); } private void ClampAdrenalineGainMultiplier() { float value = AdrenalineGainMultiplier.Value; if (float.IsNaN(value) || float.IsInfinity(value) || value < 0f) { AdrenalineGainMultiplier.Value = 0f; ((BaseUnityPlugin)this).Logger.LogWarning((object)($"Clamped General/AdrenalineGainMultiplier from '{value}' to '0.0'. " + "Negative values are not supported.")); } } } [HarmonyPatch(typeof(Player), "OnSpawned")] internal static class PlayerSpawnPatch { [HarmonyPostfix] private static void Postfix(Player __instance) { TrinketEffectManager.OnPlayerSpawned(__instance); } } internal sealed class TrinketDefinition { private readonly Dictionary<string, ConfigEntry<float>> _passiveDamage; private readonly Dictionary<string, ConfigEntry<float>> _doubledDamage; internal static readonly string[] DamageTypes = new string[10] { "Blunt", "Slash", "Pierce", "Chop", "Pickaxe", "Fire", "Frost", "Lightning", "Poison", "Spirit" }; internal static readonly string[] SkillNames = new string[23] { "None", "Axes", "Bows", "Clubs", "Crossbows", "ElementalMagic", "BloodMagic", "Dodge", "Sneak", "Knives", "Pickaxes", "Polearms", "Run", "Spears", "Swords", "Unarmed", "Blocking", "Jump", "Swim", "Fishing", "Riding", "WoodCutting", "Farming" }; internal string Section { get; } internal string DoubledSection { get; } internal string DisplayName { get; } internal string PrefabName { get; } internal IReadOnlyList<string> PrefabAliases { get; } internal ConfigEntry<float> PassiveHealthRegen { get; } internal ConfigEntry<float> PassiveStaminaRegen { get; } internal ConfigEntry<float> PassiveEitrRegen { get; } internal ConfigEntry<float> DoubledHealthRegen { get; } internal ConfigEntry<float> DoubledStaminaRegen { get; } internal ConfigEntry<float> DoubledEitrRegen { get; } internal ConfigEntry<float> PassiveArmor { get; } internal ConfigEntry<float> DoubledArmor { get; } internal ConfigEntry<float> PassiveSpeed { get; } internal ConfigEntry<float> DoubledSpeed { get; } internal ConfigEntry<float> PassiveSwimSpeed { get; } internal ConfigEntry<float> DoubledSwimSpeed { get; } internal ConfigEntry<float> PassiveJump { get; } internal ConfigEntry<float> DoubledJump { get; } internal ConfigEntry<float> PassiveFallDamage { get; } internal ConfigEntry<float> DoubledFallDamage { get; } internal ConfigEntry<float> PassiveNoise { get; } internal ConfigEntry<float> DoubledNoise { get; } internal ConfigEntry<float> PassiveStealth { get; } internal ConfigEntry<float> DoubledStealth { get; } internal ConfigEntry<float> PassiveMaxCarryWeight { get; } internal ConfigEntry<float> DoubledMaxCarryWeight { get; } internal ConfigEntry<float> PassiveTimedBlockBonus { get; } internal ConfigEntry<float> DoubledTimedBlockBonus { get; } internal ConfigEntry<float> PassiveStagger { get; } internal ConfigEntry<float> DoubledStagger { get; } internal ConfigEntry<float> PassiveDamageModifier { get; } internal ConfigEntry<float> DoubledDamageModifier { get; } internal ConfigEntry<float> PassiveRunStaminaUse { get; } internal ConfigEntry<float> DoubledRunStaminaUse { get; } internal ConfigEntry<float> PassiveJumpStaminaUse { get; } internal ConfigEntry<float> DoubledJumpStaminaUse { get; } internal ConfigEntry<float> PassiveAttackStaminaUse { get; } internal ConfigEntry<float> DoubledAttackStaminaUse { get; } internal ConfigEntry<float> PassiveBlockStaminaUse { get; } internal ConfigEntry<float> DoubledBlockStaminaUse { get; } internal ConfigEntry<float> PassiveDodgeStaminaUse { get; } internal ConfigEntry<float> DoubledDodgeStaminaUse { get; } internal ConfigEntry<float> PassiveSwimStaminaUse { get; } internal ConfigEntry<float> DoubledSwimStaminaUse { get; } internal ConfigEntry<float> PassiveHomeItemStaminaUse { get; } internal ConfigEntry<float> DoubledHomeItemStaminaUse { get; } internal ConfigEntry<float> PassiveSneakStaminaUse { get; } internal ConfigEntry<float> DoubledSneakStaminaUse { get; } internal ConfigEntry<string> PassiveResistance { get; } internal ConfigEntry<string> DoubledResistance { get; } internal ConfigEntry<string> PassiveSkill1 { get; } internal ConfigEntry<float> PassiveSkill1Bonus { get; } internal ConfigEntry<string> PassiveSkill2 { get; } internal ConfigEntry<float> PassiveSkill2Bonus { get; } internal ConfigEntry<string> PassiveRaiseSkill { get; } internal ConfigEntry<float> PassiveRaiseSkillBonus { get; } internal ConfigEntry<string> DoubledSkill1 { get; } internal ConfigEntry<float> DoubledSkill1Bonus { get; } internal ConfigEntry<string> DoubledSkill2 { get; } internal ConfigEntry<float> DoubledSkill2Bonus { get; } internal ConfigEntry<string> DoubledRaiseSkill { get; } internal ConfigEntry<float> DoubledRaiseSkillBonus { get; } internal ConfigEntry<float> Duration { get; } internal TrinketDefinition(ConfigFile config, string section, string displayName, string prefabName, Dictionary<string, float> numericDefaults, Dictionary<string, string> textDefaults, string passiveResistanceDefault, string doubledResistanceDefault, string[]? prefabAliases) { Section = section; DisplayName = displayName; PrefabName = prefabName; PrefabAliases = prefabAliases ?? Array.Empty<string>(); DoubledSection = section + ".Doubled"; PassiveHealthRegen = BindFloat(config, Section, "HealthRegenMultiplier", numericDefaults, 1f, "Health regeneration multiplier. 1.15 means +15%."); PassiveStaminaRegen = BindFloat(config, Section, "StaminaRegenMultiplier", numericDefaults, 1f, "Stamina regeneration multiplier. 1.15 means +15%."); PassiveEitrRegen = BindFloat(config, Section, "EitrRegenMultiplier", numericDefaults, 1f, "Eitr regeneration multiplier. 1.15 means +15%."); DoubledHealthRegen = BindFloat(config, DoubledSection, "HealthRegenMultiplier", numericDefaults, 1f, "Health regeneration multiplier during adrenaline doubling."); DoubledStaminaRegen = BindFloat(config, DoubledSection, "StaminaRegenMultiplier", numericDefaults, 1f, "Stamina regeneration multiplier during adrenaline doubling."); DoubledEitrRegen = BindFloat(config, DoubledSection, "EitrRegenMultiplier", numericDefaults, 1f, "Eitr regeneration multiplier during adrenaline doubling."); PassiveArmor = BindFloat(config, Section, "Armor", numericDefaults, 0f, "Flat armor while active."); DoubledArmor = BindFloat(config, DoubledSection, "Armor", numericDefaults, 0f, "Flat armor during doubling."); PassiveSpeed = BindFloat(config, Section, "Speed", numericDefaults, 0f, "Movement-speed modifier. 0.10 means +10%."); DoubledSpeed = BindFloat(config, DoubledSection, "Speed", numericDefaults, 0f, "Movement-speed modifier during doubling."); PassiveSwimSpeed = BindFloat(config, Section, "SwimSpeed", numericDefaults, 0f, "Swim-speed modifier. 0.50 means +50%."); DoubledSwimSpeed = BindFloat(config, DoubledSection, "SwimSpeed", numericDefaults, 0f, "Swim-speed modifier during doubling."); PassiveJump = BindFloat(config, Section, "Jump", numericDefaults, 0f, "Jump modifier."); DoubledJump = BindFloat(config, DoubledSection, "Jump", numericDefaults, 0f, "Jump modifier during doubling."); PassiveFallDamage = BindFloat(config, Section, "FallDamageMultiplier", numericDefaults, 1f, "Fall-damage multiplier. 0.75 means 25% less fall damage."); DoubledFallDamage = BindFloat(config, DoubledSection, "FallDamageMultiplier", numericDefaults, 1f, "Fall-damage multiplier during doubling."); PassiveNoise = BindFloat(config, Section, "NoiseMultiplier", numericDefaults, 1f, "Noise multiplier. 1.0 is unchanged; 0.75 means 25% less noise."); DoubledNoise = BindFloat(config, DoubledSection, "NoiseMultiplier", numericDefaults, 1f, "Noise multiplier during doubling."); PassiveStealth = BindFloat(config, Section, "StealthMultiplier", numericDefaults, 1f, "Stealth multiplier. 1.0 is unchanged; 1.25 means +25% stealth."); DoubledStealth = BindFloat(config, DoubledSection, "StealthMultiplier", numericDefaults, 1f, "Stealth multiplier during doubling."); PassiveMaxCarryWeight = BindFloat(config, Section, "MaxCarryWeight", numericDefaults, 0f, "Flat maximum carry-weight bonus."); DoubledMaxCarryWeight = BindFloat(config, DoubledSection, "MaxCarryWeight", numericDefaults, 0f, "Flat maximum carry-weight bonus during doubling."); PassiveTimedBlockBonus = BindFloat(config, Section, "TimedBlockBonus", numericDefaults, 0f, "Timed-block bonus."); DoubledTimedBlockBonus = BindFloat(config, DoubledSection, "TimedBlockBonus", numericDefaults, 0f, "Timed-block bonus during doubling."); PassiveStagger = BindFloat(config, Section, "StaggerMultiplier", numericDefaults, 1f, "Stagger multiplier. 1.0 is unchanged; 0.75 means 25% less stagger."); DoubledStagger = BindFloat(config, DoubledSection, "StaggerMultiplier", numericDefaults, 1f, "Stagger multiplier during doubling."); PassiveDamageModifier = BindFloat(config, Section, "DamageModifier", numericDefaults, 0f, "General damage modifier."); DoubledDamageModifier = BindFloat(config, DoubledSection, "DamageModifier", numericDefaults, 0f, "General damage modifier during doubling."); PassiveRunStaminaUse = BindFloat(config, Section, "RunStaminaUseMultiplier", numericDefaults, 1f, "Run-stamina-use multiplier. 0.75 means 25% less cost."); DoubledRunStaminaUse = BindFloat(config, DoubledSection, "RunStaminaUseMultiplier", numericDefaults, 1f, "Run-stamina-use multiplier during doubling."); PassiveJumpStaminaUse = BindFloat(config, Section, "JumpStaminaUseMultiplier", numericDefaults, 1f, "Jump-stamina-use multiplier. 0.75 means 25% less cost."); DoubledJumpStaminaUse = BindFloat(config, DoubledSection, "JumpStaminaUseMultiplier", numericDefaults, 1f, "Jump-stamina-use multiplier during doubling."); PassiveAttackStaminaUse = BindFloat(config, Section, "AttackStaminaUseMultiplier", numericDefaults, 1f, "Attack-stamina-use multiplier. 0.75 means 25% less cost."); DoubledAttackStaminaUse = BindFloat(config, DoubledSection, "AttackStaminaUseMultiplier", numericDefaults, 1f, "Attack-stamina-use multiplier during doubling."); PassiveBlockStaminaUse = BindFloat(config, Section, "BlockStaminaUseMultiplier", numericDefaults, 1f, "Block-stamina-use multiplier. 0.75 means 25% less cost."); DoubledBlockStaminaUse = BindFloat(config, DoubledSection, "BlockStaminaUseMultiplier", numericDefaults, 1f, "Block-stamina-use multiplier during doubling."); PassiveDodgeStaminaUse = BindFloat(config, Section, "DodgeStaminaUseMultiplier", numericDefaults, 1f, "Dodge-stamina-use multiplier. 0.75 means 25% less cost."); DoubledDodgeStaminaUse = BindFloat(config, DoubledSection, "DodgeStaminaUseMultiplier", numericDefaults, 1f, "Dodge-stamina-use multiplier during doubling."); PassiveSwimStaminaUse = BindFloat(config, Section, "SwimStaminaUseMultiplier", numericDefaults, 1f, "Swim-stamina-use multiplier. 0.75 means 25% less cost."); DoubledSwimStaminaUse = BindFloat(config, DoubledSection, "SwimStaminaUseMultiplier", numericDefaults, 1f, "Swim-stamina-use multiplier during doubling."); PassiveHomeItemStaminaUse = BindFloat(config, Section, "HomeItemStaminaUseMultiplier", numericDefaults, 1f, "Home-item-stamina-use multiplier. 0.75 means 25% less cost."); DoubledHomeItemStaminaUse = BindFloat(config, DoubledSection, "HomeItemStaminaUseMultiplier", numericDefaults, 1f, "Home-item-stamina-use multiplier during doubling."); PassiveSneakStaminaUse = BindFloat(config, Section, "SneakStaminaUseMultiplier", numericDefaults, 1f, "Sneak-stamina-use multiplier. 0.75 means 25% less cost."); DoubledSneakStaminaUse = BindFloat(config, DoubledSection, "SneakStaminaUseMultiplier", numericDefaults, 1f, "Sneak-stamina-use multiplier during doubling."); PassiveResistance = config.Bind<string>(Section, "Resistance", passiveResistanceDefault, "Damage resistance: Normal, SlightlyResistant, Resistant, VeryResistant, or Immune."); DoubledResistance = config.Bind<string>(DoubledSection, "Resistance", doubledResistanceDefault, "Damage resistance during doubling: Normal, SlightlyResistant, Resistant, VeryResistant, or Immune."); PassiveSkill1 = BindSkill(config, Section, "Skill1", textDefaults, "None"); PassiveSkill1Bonus = BindFloat(config, Section, "Skill1Bonus", numericDefaults, 0f, "Flat bonus to the selected first skill."); PassiveSkill2 = BindSkill(config, Section, "Skill2", textDefaults, "None"); PassiveSkill2Bonus = BindFloat(config, Section, "Skill2Bonus", numericDefaults, 0f, "Flat bonus to the selected second skill."); PassiveRaiseSkill = BindSkill(config, Section, "RaiseSkill", textDefaults, "None"); PassiveRaiseSkillBonus = BindFloat(config, Section, "RaiseSkillBonus", numericDefaults, 1f, "Skill-gain multiplier for the selected skill. 1.25 means +25% gain."); DoubledSkill1 = BindSkill(config, DoubledSection, "Skill1", textDefaults, "None"); DoubledSkill1Bonus = BindFloat(config, DoubledSection, "Skill1Bonus", numericDefaults, 0f, "Flat bonus to the selected first skill during doubling."); DoubledSkill2 = BindSkill(config, DoubledSection, "Skill2", textDefaults, "None"); DoubledSkill2Bonus = BindFloat(config, DoubledSection, "Skill2Bonus", numericDefaults, 0f, "Flat bonus to the selected second skill during doubling."); DoubledRaiseSkill = BindSkill(config, DoubledSection, "RaiseSkill", textDefaults, "None"); DoubledRaiseSkillBonus = BindFloat(config, DoubledSection, "RaiseSkillBonus", numericDefaults, 1f, "Skill-gain multiplier during doubling. 1.25 means +25% gain."); Duration = BindFloat(config, DoubledSection, "Duration", numericDefaults, 0f, "Duration in seconds for adrenaline doubling."); _passiveDamage = new Dictionary<string, ConfigEntry<float>>(StringComparer.OrdinalIgnoreCase); _doubledDamage = new Dictionary<string, ConfigEntry<float>>(StringComparer.OrdinalIgnoreCase); string[] damageTypes = DamageTypes; foreach (string text in damageTypes) { _passiveDamage[text] = BindFloat(config, Section, text + "Damage", numericDefaults, 0f, text + " damage modifier. 0.15 means +15%."); _doubledDamage[text] = BindFloat(config, DoubledSection, text + "Damage", numericDefaults, 0f, text + " damage modifier during doubling."); } } internal string GetSkillName(int slot, bool doubled) { return NormalizeSkill(((slot != 1) ? (doubled ? DoubledSkill2 : PassiveSkill2) : (doubled ? DoubledSkill1 : PassiveSkill1)).Value); } internal float GetSkillBonus(int slot, bool doubled) { return ((slot != 1) ? (doubled ? DoubledSkill2Bonus : PassiveSkill2Bonus) : (doubled ? DoubledSkill1Bonus : PassiveSkill1Bonus)).Value; } internal string GetRaiseSkillName(bool doubled) { return NormalizeSkill((doubled ? DoubledRaiseSkill : PassiveRaiseSkill).Value); } internal float GetRaiseSkillBonus(bool doubled) { return (doubled ? DoubledRaiseSkillBonus : PassiveRaiseSkillBonus).Value; } internal float GetDamage(string damageType, bool doubled) { string text = Normalize(damageType); foreach (KeyValuePair<string, ConfigEntry<float>> item in doubled ? _doubledDamage : _passiveDamage) { if (Normalize(item.Key) == text) { return item.Value.Value; } } return 0f; } private static string NormalizeSkill(string value) { string[] skillNames = SkillNames; foreach (string text in skillNames) { if (string.Equals(Normalize(text), Normalize(value), StringComparison.OrdinalIgnoreCase)) { return text; } } return "None"; } private static string Normalize(string value) { return (value ?? string.Empty).Replace(" ", string.Empty).Replace("_", string.Empty).ToLowerInvariant(); } private static ConfigEntry<string> BindSkill(ConfigFile config, string configSection, string key, Dictionary<string, string> defaults, string neutral) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown string key2 = (configSection.EndsWith(".Doubled", StringComparison.Ordinal) ? ("Doubled." + key) : ("Passive." + key)); string value2; string value = (defaults.TryGetValue(key2, out value2) ? value2 : neutral); return config.Bind<string>(configSection, key, NormalizeSkill(value), new ConfigDescription("Native Valheim skill slot.", (AcceptableValueBase)(object)new AcceptableValueList<string>(SkillNames), Array.Empty<object>())); } private ConfigEntry<float> BindFloat(ConfigFile config, string configSection, string key, Dictionary<string, float> defaults, float neutral, string description) { //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Expected O, but got Unknown string key2 = (configSection.EndsWith(".Doubled", StringComparison.Ordinal) ? ("Doubled." + key) : ("Passive." + key)); float value; float num = (defaults.TryGetValue(key2, out value) ? value : neutral); GetSafetyBounds(key, out var minimum, out var maximum); ConfigEntry<float> entry = config.Bind<float>(configSection, key, num, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(minimum, maximum), Array.Empty<object>())); entry.SettingChanged += delegate { ClampEntry(entry, configSection, key, minimum, maximum); }; ClampEntry(entry, configSection, key, minimum, maximum); return entry; } private static void GetSafetyBounds(string key, out float minimum, out float maximum) { minimum = 0f; maximum = float.MaxValue; if (key.EndsWith("Multiplier", StringComparison.OrdinalIgnoreCase) || key.EndsWith("StaminaUseMultiplier", StringComparison.OrdinalIgnoreCase)) { minimum = 0f; } } private static void ClampEntry(ConfigEntry<float> entry, string section, string key, float minimum, float maximum) { float value = entry.Value; float num = value; if (float.IsNaN(num) || float.IsInfinity(num)) { num = minimum; } else if (num < minimum) { num = minimum; } else if (num > maximum) { num = maximum; } if (Math.Abs(value - num) > 0.0001f) { entry.Value = num; Plugin.Log.LogWarning((object)$"Clamped config value [{section}] {key} from '{value}' to '{num}'."); } } } internal static class TrinketConfigRegistry { private static readonly List<TrinketDefinition> Definitions = new List<TrinketDefinition>(); internal static IReadOnlyList<TrinketDefinition> All => Definitions; internal static void Initialize(ConfigFile config) { if (Definitions.Count <= 0) { Definitions.Add(Create(config, "Bracelets of the Brave", "TrinketBlackDamageHealth", Defaults(Pair("Passive.Skill1Bonus", 20f), Pair("Passive.BluntDamage", 0.15f), Pair("Doubled.Skill1Bonus", 40f), Pair("Doubled.BluntDamage", 0.3f), Pair("Doubled.Duration", 60f)), TextDefaults(Text("Passive.Skill1", "Clubs"), Text("Doubled.Skill1", "Clubs")))); Definitions.Add(Create(config, "Brimstone", "TrinketFlametalStaminaHealth", Defaults(Pair("Passive.BluntDamage", 0.15f), Pair("Passive.SlashDamage", 0.15f), Pair("Passive.PierceDamage", 0.15f), Pair("Passive.ChopDamage", 0.15f), Pair("Passive.PickaxeDamage", 0.15f), Pair("Doubled.BluntDamage", 0.3f), Pair("Doubled.SlashDamage", 0.3f), Pair("Doubled.PierceDamage", 0.3f), Pair("Doubled.ChopDamage", 0.3f), Pair("Doubled.PickaxeDamage", 0.3f), Pair("Doubled.Duration", 60f)), TextDefaults())); Definitions.Add(Create(config, "Bronze Pendant", "TrinketBronzeStamina", Defaults(Pair("Passive.StaminaRegenMultiplier", 1.25f), Pair("Doubled.StaminaRegenMultiplier", 1.5f), Pair("Doubled.Duration", 60f)), TextDefaults())); Definitions.Add(Create(config, "Crystal Heart", "TrinketSilverResist", Defaults(Pair("Doubled.Duration", 50f)), TextDefaults(), "SlightlyResistant", "Resistant")); Definitions.Add(Create(config, "Evasion Mantle", "TrinketBlackStamina", Defaults(Pair("Passive.BlockStaminaUseMultiplier", 0.5f), Pair("Passive.TimedBlockBonus", 0.5f), Pair("Passive.Skill1Bonus", 20f), Pair("Doubled.BlockStaminaUseMultiplier", 0f), Pair("Doubled.TimedBlockBonus", 1f), Pair("Doubled.Skill1Bonus", 40f), Pair("Doubled.Duration", 120f)), TextDefaults(Text("Passive.Skill1", "Dodge"), Text("Doubled.Skill1", "Dodge")))); Definitions.Add(Create(config, "Fins of Destiny", "TrinketChitinSwim", Defaults(Pair("Passive.SwimSpeed", 0.5f), Pair("Passive.SwimStaminaUseMultiplier", 0.4f), Pair("Doubled.SwimSpeed", 1f), Pair("Doubled.SwimStaminaUseMultiplier", 0.4f), Pair("Doubled.Duration", 120f)), TextDefaults())); Definitions.Add(Create(config, "Heart of the Forest", "TrinketBronzeHealth", Defaults(Pair("Passive.HealthRegenMultiplier", 1.25f), Pair("Doubled.HealthRegenMultiplier", 1.5f), Pair("Doubled.Duration", 60f)), TextDefaults())); Definitions.Add(Create(config, "Iron Brooch", "TrinketIronHealth", Defaults(Pair("Passive.Armor", 10f), Pair("Doubled.Armor", 20f), Pair("Doubled.Duration", 30f)), TextDefaults())); Definitions.Add(Create(config, "Jormundling", "TrinketFlametalEitr", Defaults(Pair("Passive.Skill1Bonus", 20f), Pair("Passive.Skill2Bonus", 20f), Pair("Doubled.Skill1Bonus", 40f), Pair("Doubled.Skill2Bonus", 40f), Pair("Doubled.Duration", 60f)), TextDefaults(Text("Passive.Skill1", "ElementalMagic"), Text("Passive.Skill2", "BloodMagic"), Text("Doubled.Skill1", "ElementalMagic"), Text("Doubled.Skill2", "BloodMagic")))); Definitions.Add(Create(config, "Nimble Anklet", "TrinketIronStamina", Defaults(Pair("Passive.Speed", 0.1f), Pair("Doubled.Speed", 0.2f), Pair("Doubled.Duration", 30f)), TextDefaults())); Definitions.Add(Create(config, "Neckstabber", "TrinketBloodGoldHealth", Defaults(Pair("Passive.HealthRegenMultiplier", 1.2f), Pair("Passive.RunStaminaUseMultiplier", 0.75f), Pair("Passive.AttackStaminaUseMultiplier", 0.75f)), TextDefaults(), "Normal", "Normal", new string[4] { "TrinketNeckstabber", "Neckstabber", "$item_neckstabber", "$item_trinketneckstabber" })); Definitions.Add(Create(config, "Pulsating Earrings", "TrinketCarapaceEitr", Defaults(Pair("Passive.EitrRegenMultiplier", 1.25f), Pair("Doubled.EitrRegenMultiplier", 1.5f), Pair("Doubled.Duration", 60f)), TextDefaults())); Definitions.Add(Create(config, "Resounding Shackle", "TrinketScaleStaminaDamage", Defaults(Pair("Passive.SlashDamage", 0.2f), Pair("Doubled.SlashDamage", 0.33f), Pair("Doubled.Duration", 60f)), TextDefaults())); Definitions.Add(Create(config, "Wolf Sight", "TrinketSilverDamage", Defaults(Pair("Passive.Skill1Bonus", 20f), Pair("Passive.Skill2Bonus", 20f), Pair("Passive.PierceDamage", 0.15f), Pair("Doubled.Skill1Bonus", 40f), Pair("Doubled.Skill2Bonus", 40f), Pair("Doubled.PierceDamage", 0.3f), Pair("Doubled.Duration", 30f)), TextDefaults(Text("Passive.Skill1", "Bows"), Text("Passive.Skill2", "Sneak"), Text("Doubled.Skill1", "Bows"), Text("Doubled.Skill2", "Sneak")))); Definitions.Add(Create(config, "Witch Crown", "TrinketBloodGoldStamina", Defaults(Pair("Passive.HealthRegenMultiplier", 1.33f), Pair("Passive.StaminaRegenMultiplier", 1.33f), Pair("Passive.EitrRegenMultiplier", 1.33f)), TextDefaults(), "Normal", "Normal", new string[4] { "TrinketWitchCrown", "WitchCrown", "$item_witchcrown", "$item_trinketwitchcrown" })); config.Save(); } } internal static TrinketDefinition? FindByPrefab(string prefabName, string? itemName = null) { TrinketDefinition trinketDefinition = FindByIdentifier(prefabName); if (trinketDefinition != null) { return trinketDefinition; } if (itemName == null || itemName.Length == 0) { return null; } return FindByIdentifier(itemName); } private static TrinketDefinition? FindByIdentifier(string identifier) { string text = NormalizeIdentifier(identifier); foreach (TrinketDefinition definition in Definitions) { if (string.Equals(definition.PrefabName, identifier, StringComparison.OrdinalIgnoreCase)) { return definition; } foreach (string prefabAlias in definition.PrefabAliases) { if (string.Equals(text, NormalizeIdentifier(prefabAlias), StringComparison.Ordinal)) { return definition; } } string text2 = NormalizeIdentifier(definition.DisplayName); if (text2.Length > 0 && text.Contains(text2)) { return definition; } } return null; } private static string NormalizeIdentifier(string value) { if (string.IsNullOrEmpty(value)) { return string.Empty; } char[] array = new char[value.Length]; int length = 0; foreach (char c in value) { if (char.IsLetterOrDigit(c)) { array[length++] = char.ToLowerInvariant(c); } } return new string(array, 0, length); } private static TrinketDefinition Create(ConfigFile config, string section, string prefabName, Dictionary<string, float> numericDefaults, Dictionary<string, string> textDefaults, string passiveResistance = "Normal", string doubledResistance = "Normal", string[]? prefabAliases = null) { return new TrinketDefinition(config, section, section, prefabName, numericDefaults, textDefaults, passiveResistance, doubledResistance, prefabAliases); } private static Dictionary<string, float> Defaults(params KeyValuePair<string, float>[] values) { Dictionary<string, float> dictionary = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase); for (int i = 0; i < values.Length; i++) { KeyValuePair<string, float> keyValuePair = values[i]; dictionary[keyValuePair.Key] = keyValuePair.Value; } return dictionary; } private static Dictionary<string, string> TextDefaults(params KeyValuePair<string, string>[] values) { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); for (int i = 0; i < values.Length; i++) { KeyValuePair<string, string> keyValuePair = values[i]; dictionary[keyValuePair.Key] = keyValuePair.Value; } return dictionary; } private static KeyValuePair<string, float> Pair(string key, float value) { return new KeyValuePair<string, float>(key, value); } private static KeyValuePair<string, string> Text(string key, string value) { return new KeyValuePair<string, string>(key, value); } } internal static class TrinketEffectManager { private sealed class EquippedTrinket { internal TrinketDefinition Definition { get; } internal ItemData Item { get; } internal List<string> StatusEffectNames { get; } internal EquippedTrinket(TrinketDefinition definition, ItemData item, List<string> statusEffectNames) { Definition = definition; Item = item; StatusEffectNames = statusEffectNames; } } private sealed class PlayerState { internal string Signature = string.Empty; internal float NextRefresh; internal float DoubledUntil; internal bool AdrenalineWasFull; internal bool HasAppliedDoubledState; internal bool AppliedDoubledState; internal bool LoggedMissingStatusEffect; internal bool SaveSuspended; internal bool ForcedRefreshInProgress; internal object? GlobalAdrenalineStatusEffect; internal readonly List<object> AddedPassiveStatusEffects = new List<object>(); internal readonly Dictionary<string, object> PassiveStatusEffectsByPrefab = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); } private static readonly Dictionary<int, PlayerState> States = new Dictionary<int, PlayerState>(); private static readonly Dictionary<string, float> NativeNeutralValues = new Dictionary<string, float>(StringComparer.Ordinal); private static readonly BindingFlags AnyInstance = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static readonly BindingFlags AnyMember = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; internal static void OnPlayerSpawned(Player player) { PlayerState state = GetState(player); RemoveAddedPassiveStatusEffects(player, state); state.Signature = string.Empty; state.NextRefresh = Time.time + 0.5f; state.DoubledUntil = 0f; state.AdrenalineWasFull = false; state.HasAppliedDoubledState = false; state.AppliedDoubledState = false; state.LoggedMissingStatusEffect = false; state.SaveSuspended = false; } internal static void OnDoublingSettingChanged(bool enabled) { foreach (PlayerState value in States.Values) { value.DoubledUntil = 0f; value.AdrenalineWasFull = false; value.NextRefresh = 0f; if (!enabled && value.HasAppliedDoubledState) { value.AppliedDoubledState = true; } } Plugin.Log.LogInfo((object)("Adrenaline doubling " + (enabled ? "enabled" : "disabled") + "; active trinket effects will be refreshed from the appropriate config section.")); } internal static void Tick(Player player) { PlayerState state = GetState(player); if (!state.SaveSuspended && !state.ForcedRefreshInProgress && !(Time.time < state.NextRefresh)) { state.NextRefresh = Time.time + 0.25f; Refresh(player); } } internal static void RefreshAfterEquipmentStatusEffects(Player player) { if ((Object)(object)player == (Object)null) { return; } PlayerState state = GetState(player); if (state.SaveSuspended || state.ForcedRefreshInProgress) { return; } state.ForcedRefreshInProgress = true; try { state.NextRefresh = 0f; Refresh(player); state.NextRefresh = Time.time + 0.25f; } finally { state.ForcedRefreshInProgress = false; } } private static void Refresh(Player player) { if ((Object)(object)player == (Object)null) { return; } PlayerState state = GetState(player); if (!state.SaveSuspended && IsPlayerReady(player)) { List<EquippedTrinket> equipped = FindEquippedTrinkets(player); string text = BuildSignature(equipped); if (!string.Equals(text, state.Signature, StringComparison.Ordinal)) { Plugin.Log.LogInfo((object)("Trinket equipment changed: '" + state.Signature + "' -> '" + text + "'. Detected: " + DescribeEquippedTrinkets(equipped))); state.Signature = text; state.LoggedMissingStatusEffect = false; RemoveAddedPassiveStatusEffects(player, state); } bool value = Plugin.DoublingOnAdrenaline.Value; if (!value) { state.DoubledUntil = 0f; state.AdrenalineWasFull = false; } bool flag = IsAdrenalineFull(player); if (value && flag && !state.AdrenalineWasFull) { float longestDuration = GetLongestDuration(equipped); state.DoubledUntil = Time.time + longestDuration; Plugin.Log.LogInfo((object)$"Adrenaline doubling started for {longestDuration:0.##} seconds."); } state.AdrenalineWasFull = flag; bool flag2 = value && state.DoubledUntil > Time.time; if (state.HasAppliedDoubledState && state.AppliedDoubledState != flag2) { RemoveAddedPassiveStatusEffects(player, state); } EnsurePassiveStatusEffects(player, equipped, flag2, state); ApplyActiveStatusEffects(player, equipped, flag2, state); ApplyGlobalAdrenalineModifier(player, state); state.HasAppliedDoubledState = true; state.AppliedDoubledState = flag2; if (!flag2 && state.DoubledUntil > 0f && Plugin.DebugLogging) { Plugin.Log.LogInfo((object)"Adrenaline doubling expired; passive values restored."); state.DoubledUntil = 0f; } } } private static bool IsPlayerReady(Player player) { object memberValue = GetMemberValue(player, "m_dead"); bool flag = default(bool); int num; if (memberValue is bool) { flag = (bool)memberValue; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { return false; } float? num2 = InvokeNumberGetter(player, "GetHealth"); if (num2.HasValue) { if (num2.Value > 0f && !float.IsNaN(num2.Value)) { return !float.IsInfinity(num2.Value); } return false; } return true; } private static List<EquippedTrinket> FindEquippedTrinkets(Player player) { List<EquippedTrinket> list = new List<EquippedTrinket>(); Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null) { return list; } foreach (ItemData allItem in inventory.GetAllItems()) { if (allItem != null && IsEquipped(allItem) && !((Object)(object)allItem.m_dropPrefab == (Object)null)) { string itemName = ((allItem.m_shared == null) ? string.Empty : allItem.m_shared.m_name); TrinketDefinition trinketDefinition = TrinketConfigRegistry.FindByPrefab(((Object)allItem.m_dropPrefab).name, itemName); if (trinketDefinition != null) { list.Add(new EquippedTrinket(trinketDefinition, allItem, GetStatusEffectNames(allItem))); } } } return list; } private static string DescribeEquippedTrinkets(List<EquippedTrinket> equipped) { if (equipped.Count == 0) { return "<none>"; } List<string> list = new List<string>(); foreach (EquippedTrinket item in equipped) { TrinketDefinition definition = item.Definition; list.Add(definition.PrefabName + $"(armor={definition.PassiveArmor.Value:0.##}," + $"speed={definition.PassiveSpeed.Value:0.##})"); } return string.Join(", ", list); } private static string BuildSignature(List<EquippedTrinket> equipped) { if (equipped.Count == 0) { return string.Empty; } List<string> list = new List<string>(); foreach (EquippedTrinket item in equipped) { list.Add(item.Definition.PrefabName); } list.Sort(StringComparer.OrdinalIgnoreCase); return string.Join("|", list); } private static void ApplyActiveStatusEffects(Player player, List<EquippedTrinket> equipped, bool doubled, PlayerState state) { object obj = InvokeNoArgumentMethod(player, "GetSEMan"); if (obj == null) { return; } List<object> activeStatusEffects = GetActiveStatusEffects(player, obj); foreach (EquippedTrinket item in equipped) { bool flag = false; foreach (object item2 in activeStatusEffects) { if (MatchesStatusEffect(item.StatusEffectNames, item2)) { ApplyStatusEffectFields(item2, item.Definition, doubled); flag = true; if (Plugin.DebugLogging) { Plugin.Log.LogDebug((object)("Applied " + (doubled ? "doubled" : "passive") + " values to status effect '" + GetObjectName(item2) + "' for trinket '" + item.Definition.PrefabName + "'.")); } } } if (!flag && !state.LoggedMissingStatusEffect && Plugin.DebugLogging) { Plugin.Log.LogDebug((object)("No active status effect matched configured trinket '" + item.Definition.PrefabName + "'. The item was found and equipped, but its game status effect was not active yet.")); state.LoggedMissingStatusEffect = true; } } } private static void EnsurePassiveStatusEffects(Player player, List<EquippedTrinket> equipped, bool doubled, PlayerState state) { object obj = InvokeNoArgumentMethod(player, "GetSEMan"); if (obj == null) { return; } List<object> activeStatusEffects = GetActiveStatusEffects(player, obj); foreach (EquippedTrinket item in equipped) { if (state.PassiveStatusEffectsByPrefab.TryGetValue(item.Definition.PrefabName, out object value)) { if (IsUsableUnityObject(value)) { ApplyStatusEffectFields(value, item.Definition, doubled); if (!activeStatusEffects.Contains(value)) { activeStatusEffects.Add(value); } continue; } state.PassiveStatusEffectsByPrefab.Remove(item.Definition.PrefabName); } AddStatusEffectIdentifier(item.StatusEffectNames, GetSyntheticStatusEffectName(item.Definition.PrefabName)); if (HasMatchingStatusEffect(activeStatusEffects, item.StatusEffectNames)) { continue; } object trinketStatusEffectTemplate = GetTrinketStatusEffectTemplate(item); if (trinketStatusEffectTemplate != null) { AddStatusEffectIdentifiers(item.StatusEffectNames, trinketStatusEffectTemplate); if (HasMatchingStatusEffect(activeStatusEffects, item.StatusEffectNames)) { continue; } } bool flag = trinketStatusEffectTemplate == null; object obj2 = ((!flag) ? CloneStatusEffect(trinketStatusEffectTemplate) : CreateSyntheticStatusEffect(item)); if (obj2 == null) { Plugin.Log.LogWarning((object)("Could not create a passive status effect for '" + item.Definition.PrefabName + "'.")); continue; } ApplyStatusEffectFields(obj2, item.Definition, doubled); ApplyFallbackStatusEffectIcon(obj2, item.Item); if (Plugin.DebugLogging) { string memberText = GetMemberText(obj2, "m_dodgeStaminaUseModifier"); string memberText2 = GetMemberText(obj2, "m_fallDamageModifier"); string memberText3 = GetMemberText(obj2, "m_adrenalineModifier"); Plugin.Log.LogDebug((object)("Dodge stamina field for '" + item.Definition.PrefabName + "': " + $"configured multiplier={(doubled ? item.Definition.DoubledDodgeStaminaUse.Value : item.Definition.PassiveDodgeStaminaUse.Value):0.##}; " + "native m_dodgeStaminaUseModifier=" + memberText + ".")); Plugin.Log.LogDebug((object)("Neutral-sensitive fields for '" + item.Definition.PrefabName + "': " + $"configured fall multiplier={(doubled ? item.Definition.DoubledFallDamage.Value : item.Definition.PassiveFallDamage.Value):0.##}; " + "native m_fallDamageModifier=" + memberText2 + "; native m_adrenalineModifier=" + memberText3 + ".")); } if (!AddStatusEffect(obj, obj2)) { Plugin.Log.LogWarning((object)("Could not add the passive status effect for '" + item.Definition.PrefabName + "'.")); DestroyClonedStatusEffect(obj2); continue; } state.AddedPassiveStatusEffects.Add(obj2); state.PassiveStatusEffectsByPrefab[item.Definition.PrefabName] = obj2; activeStatusEffects.Add(obj2); if (Plugin.DebugLogging) { Plugin.Log.LogDebug((object)("Added passive status effect '" + GetObjectName(obj2) + "' for '" + item.Definition.PrefabName + "' (" + (flag ? "native fallback" : "item template") + ") with native passive values.")); } } } internal static void BeforePlayerSave(Player player) { if (!((Object)(object)player == (Object)null)) { PlayerState state = GetState(player); if (!state.SaveSuspended) { state.SaveSuspended = true; RemoveAddedPassiveStatusEffects(player, state); } } } internal static void AfterPlayerSave(Player player) { if ((Object)(object)player == (Object)null) { return; } PlayerState state = GetState(player); if (state.SaveSuspended) { state.SaveSuspended = false; state.NextRefresh = Time.time + 0.1f; if (IsPlayerReady(player)) { Refresh(player); } } } private static bool HasMatchingStatusEffect(List<object> activeEffects, List<string> identifiers) { foreach (object activeEffect in activeEffects) { if (MatchesStatusEffect(identifiers, activeEffect)) { return true; } } return false; } private static void RemoveAddedPassiveStatusEffects(Player player, PlayerState state) { if (state.AddedPassiveStatusEffects.Count == 0) { state.PassiveStatusEffectsByPrefab.Clear(); state.GlobalAdrenalineStatusEffect = null; return; } object obj = InvokeNoArgumentMethod(player, "GetSEMan"); foreach (object addedPassiveStatusEffect in state.AddedPassiveStatusEffects) { if (obj != null) { RemoveStatusEffect(obj, addedPassiveStatusEffect); } DestroyClonedStatusEffect(addedPassiveStatusEffect); } state.AddedPassiveStatusEffects.Clear(); state.PassiveStatusEffectsByPrefab.Clear(); state.GlobalAdrenalineStatusEffect = null; } private static object? GetTrinketStatusEffectTemplate(EquippedTrinket trinket) { object shared = trinket.Item.m_shared; string[] array = new string[3] { "m_equipStatusEffect", "m_setStatusEffect", "m_attackStatusEffect" }; foreach (string memberName in array) { object obj = GetMemberValue(shared, memberName) ?? GetMemberValue(trinket.Item, memberName); if (IsUsableUnityObject(obj)) { return obj; } } object obj2 = FindStatusEffectInObjectDb(trinket.Definition.PrefabName); if (IsUsableUnityObject(obj2)) { if (Plugin.DebugLogging) { Plugin.Log.LogDebug((object)("Found status-effect template for '" + trinket.Definition.PrefabName + "' by prefab name: '" + GetObjectName(obj2) + "'.")); } return obj2; } foreach (string statusEffectName in trinket.StatusEffectNames) { object obj3 = FindStatusEffectInObjectDb(statusEffectName); if (IsUsableUnityObject(obj3)) { return obj3; } } return null; } private static string GetSyntheticStatusEffectName(string prefabName) { return "TrinketModifiers_" + prefabName; } private static object? CreateSyntheticStatusEffect(EquippedTrinket trinket) { Type type = typeof(Player).Assembly.GetType("SE_Stats"); if (type == null || !typeof(ScriptableObject).IsAssignableFrom(type)) { return null; } try { ScriptableObject obj = ScriptableObject.CreateInstance(type); string syntheticStatusEffectName = GetSyntheticStatusEffectName(trinket.Definition.PrefabName); ((Object)obj).name = syntheticStatusEffectName; SetText(obj, "m_name", trinket.Definition.DisplayName); SetText(obj, "m_tooltip", "Active while the trinket is equipped."); ApplyFallbackStatusEffectIcon(obj, trinket.Item); return obj; } catch (Exception ex) { if (Plugin.DebugLogging) { Plugin.Log.LogDebug((object)("Creating synthetic status effect for '" + trinket.Definition.PrefabName + "' failed: " + ex.GetBaseException().Message)); } return null; } } private static object? CreateGlobalAdrenalineStatusEffect() { Type type = typeof(Player).Assembly.GetType("SE_Stats"); if (type == null || !typeof(ScriptableObject).IsAssignableFrom(type)) { return null; } try { ScriptableObject obj = ScriptableObject.CreateInstance(type); ((Object)obj).name = "TrinketModifiers_GlobalAdrenaline"; SetText(obj, "m_name", "Trinket Modifiers"); SetText(obj, "m_tooltip", "Global adrenaline gain modifier."); SetNumber(obj, "m_ttl", 0f); SetNumber(obj, "m_adrenalineModifier", TranslateMultiplier("m_adrenalineModifier", Plugin.AdrenalineGainMultiplier.Value, 0f)); return obj; } catch (Exception ex) { if (Plugin.DebugLogging) { Plugin.Log.LogDebug((object)("Creating the global adrenaline status effect failed: " + ex.GetBaseException().Message)); } return null; } } private static void ApplyFallbackStatusEffectIcon(object effect, ItemData item) { if (GetMemberValue(effect, "m_icon") == null) { Sprite trinketIcon = GetTrinketIcon(item); if ((Object)(object)trinketIcon != (Object)null) { SetObject(effect, "m_icon", trinketIcon); } } SetBooleanOrNumber(effect, "m_iconAlways", value: true); } private static Sprite? GetTrinketIcon(ItemData item) { object? obj = InvokeNoArgumentMethod(item, "GetIcon"); Sprite val = (Sprite)((obj is Sprite) ? obj : null); if (val != null && (Object)(object)val != (Object)null) { return val; } object shared = item.m_shared; object? memberValue = GetMemberValue(shared, "m_icon"); Sprite val2 = (Sprite)((memberValue is Sprite) ? memberValue : null); if (val2 != null && (Object)(object)val2 != (Object)null) { return val2; } if (GetMemberValue(shared, "m_icons") is IEnumerable enumerable) { foreach (object item2 in enumerable) { Sprite val3 = (Sprite)((item2 is Sprite) ? item2 : null); if (val3 != null && (Object)(object)val3 != (Object)null) { return val3; } } } object? memberValue2 = GetMemberValue(item, "m_icon"); Sprite val4 = (Sprite)((memberValue2 is Sprite) ? memberValue2 : null); if (val4 == null || !((Object)(object)val4 != (Object)null)) { return null; } return val4; } private static object? FindStatusEffectInObjectDb(string identifier) { Type type = typeof(Player).Assembly.GetType("ObjectDB"); if (type == null) { return null; } object staticMemberValue = GetStaticMemberValue(type, "instance"); if (staticMemberValue == null) { return null; } MethodInfo[] methods = staticMemberValue.GetType().GetMethods(AnyInstance); foreach (MethodInfo methodInfo in methods) { if (!string.Equals(methodInfo.Name, "GetStatusEffect", StringComparison.Ordinal) || methodInfo.ContainsGenericParameters) { continue; } ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length != 1) { continue; } object obj = null; object converted; if (parameters[0].ParameterType == typeof(string)) { obj = identifier; } else if (TryConvertIdentifier(identifier, parameters[0].ParameterType, out converted)) { obj = converted; } if (obj == null && parameters[0].ParameterType.IsValueType) { continue; } try { object obj2 = methodInfo.Invoke(staticMemberValue, new object[1] { obj }); if (IsUsableUnityObject(obj2)) { return obj2; } } catch { } } string[] array = new string[4] { "m_StatusEffects", "m_statusEffects", "m_statusEffectPrefabs", "m_StatusEffectPrefabs" }; foreach (string memberName in array) { if (!(GetMemberValue(staticMemberValue, memberName) is IEnumerable enumerable)) { continue; } foreach (object item in enumerable) { if (item != null && MatchesIdentifier(item, identifier) && IsUsableUnityObject(item)) { return item; } } } return null; } private static bool MatchesIdentifier(object candidate, string identifier) { List<string> list = new List<string>(); AddStatusEffectIdentifiers(list, candidate); foreach (string item in list) { if (ContainsName(new List<string> { identifier }, item)) { return true; } } return false; } private static object? CloneStatusEffect(object template) { Object val = (Object)((template is Object) ? template : null); if (val != null) { if (val == (Object)null) { return null; } Object val2 = Object.Instantiate(val); if (!(val2 == (Object)null)) { return val2; } return null; } return template; } private static void DestroyClonedStatusEffect(object effect) { Object val = (Object)((effect is Object) ? effect : null); if (val != null && val != (Object)null) { Object.Destroy(val); } } private static bool AddStatusEffect(object seMan, object effect) { return InvokeWithFirstArgument(seMan, "AddStatusEffect", effect); } private static bool RemoveStatusEffect(object seMan, object effect) { if (InvokeWithFirstArgument(seMan, "RemoveStatusEffect", effect)) { return true; } object memberValue = GetMemberValue(effect, "m_hash"); if (memberValue != null) { return InvokeWithFirstArgument(seMan, "RemoveStatusEffect", memberValue); } return false; } private static bool InvokeWithFirstArgument(object target, string methodName, object firstArgument) { MethodInfo[] methods = target.GetType().GetMethods(AnyInstance); foreach (MethodInfo methodInfo in methods) { if (!string.Equals(methodInfo.Name, methodName, StringComparison.Ordinal) || methodInfo.ContainsGenericParameters || !TryBuildArguments(methodInfo.GetParameters(), firstArgument, out object[] arguments)) { continue; } try { methodInfo.Invoke(target, arguments); return true; } catch (Exception ex) { if (Plugin.DebugLogging) { Plugin.Log.LogDebug((object)("Calling " + methodName + " failed: " + ex.GetBaseException().Message)); } } } return false; } private static bool TryBuildArguments(ParameterInfo[] parameters, object firstArgument, out object?[] arguments) { arguments = new object[parameters.Length]; if (parameters.Length == 0 || !CanAssign(parameters[0].ParameterType, firstArgument)) { return false; } arguments[0] = firstArgument; for (int i = 1; i < parameters.Length; i++) { ParameterInfo parameterInfo = parameters[i]; if (parameterInfo.HasDefaultValue && parameterInfo.DefaultValue != null && parameterInfo.DefaultValue != DBNull.Value && parameterInfo.DefaultValue != Missing.Value) { arguments[i] = parameterInfo.DefaultValue; } else if (parameterInfo.ParameterType == typeof(bool)) { arguments[i] = false; } else if (parameterInfo.ParameterType.IsValueType) { arguments[i] = Activator.CreateInstance(parameterInfo.ParameterType); } else { arguments[i] = null; } } return true; } private static bool CanAssign(Type parameterType, object value) { if (parameterType.IsInstanceOfType(value)) { return true; } return parameterType == typeof(object); } private static bool TryConvertIdentifier(string identifier, Type targetType, out object? converted) { if (targetType == typeof(int) && int.TryParse(identifier, out var result)) { converted = result; return true; } if (targetType == typeof(uint) && uint.TryParse(identifier, out var result2)) { converted = result2; return true; } converted = null; return false; } private static void ApplyStatusEffectFields(object effect, TrinketDefinition definition, bool doubled) { float value = (doubled ? definition.DoubledHealthRegen.Value : definition.PassiveHealthRegen.Value); float value2 = (doubled ? definition.DoubledStaminaRegen.Value : definition.PassiveStaminaRegen.Value); float value3 = (doubled ? definition.DoubledEitrRegen.Value : definition.PassiveEitrRegen.Value); float value4 = (doubled ? definition.DoubledArmor.Value : definition.PassiveArmor.Value); float value5 = (doubled ? definition.DoubledSpeed.Value : definition.PassiveSpeed.Value); float value6 = (doubled ? definition.DoubledSwimSpeed.Value : definition.PassiveSwimSpeed.Value); float value7 = (doubled ? definition.DoubledJump.Value : definition.PassiveJump.Value); float configuredMultiplier = (doubled ? definition.DoubledFallDamage.Value : definition.PassiveFallDamage.Value); float configuredMultiplier2 = (doubled ? definition.DoubledNoise.Value : definition.PassiveNoise.Value); float configuredMultiplier3 = (doubled ? definition.DoubledStealth.Value : definition.PassiveStealth.Value); float value8 = (doubled ? definition.DoubledMaxCarryWeight.Value : definition.PassiveMaxCarryWeight.Value); float value9 = (doubled ? definition.DoubledTimedBlockBonus.Value : definition.PassiveTimedBlockBonus.Value); float configuredMultiplier4 = (doubled ? definition.DoubledStagger.Value : definition.PassiveStagger.Value); float value10 = (doubled ? definition.DoubledDamageModifier.Value : definition.PassiveDamageModifier.Value); float configuredMultiplier5 = (doubled ? definition.DoubledRunStaminaUse.Value : definition.PassiveRunStaminaUse.Value); float configuredMultiplier6 = (doubled ? definition.DoubledJumpStaminaUse.Value : definition.PassiveJumpStaminaUse.Value); float configuredMultiplier7 = (doubled ? definition.DoubledAttackStaminaUse.Value : definition.PassiveAttackStaminaUse.Value); float configuredMultiplier8 = (doubled ? definition.DoubledBlockStaminaUse.Value : definition.PassiveBlockStaminaUse.Value); float configuredMultiplier9 = (doubled ? definition.DoubledDodgeStaminaUse.Value : definition.PassiveDodgeStaminaUse.Value); float configuredMultiplier10 = (doubled ? definition.DoubledSwimStaminaUse.Value : definition.PassiveSwimStaminaUse.Value); float configuredMultiplier11 = (doubled ? definition.DoubledHomeItemStaminaUse.Value : definition.PassiveHomeItemStaminaUse.Value); float configuredMultiplier12 = (doubled ? definition.DoubledSneakStaminaUse.Value : definition.PassiveSneakStaminaUse.Value); SetNumber(effect, "m_ttl", 0f); SetNumber(effect, "m_healthUpFront", 0f); SetNumber(effect, "m_staminaUpFront", 0f); SetNumber(effect, "m_eitrUpFront", 0f); SetNumber(effect, "m_healthRegenMultiplier", value); SetNumber(effect, "m_staminaRegenMultiplier", value2); SetNumber(effect, "m_eitrRegenMultiplier", value3); SetNumber(effect, "m_addArmor", value4); SetNumber(effect, "m_speedModifier", value5); SetNumber(effect, "m_swimSpeedModifier", value6); SetNumber(effect, "m_jumpModifier", value7); SetNumber(effect, "m_fallDamageModifier", TranslateMultiplier("m_fallDamageModifier", configuredMultiplier, 1f)); SetNumber(effect, "m_noiseModifier", TranslateMultiplier("m_noiseModifier", configuredMultiplier2, 0f)); SetNumber(effect, "m_stealthModifier", TranslateMultiplier("m_stealthModifier", configuredMultiplier3, 0f)); SetNumber(effect, "m_addMaxCarryWeight", value8); SetNumber(effect, "m_timedBlockBonus", value9); SetNumber(effect, "m_staggerModifier", TranslateMultiplier("m_staggerModifier", configuredMultiplier4, 0f)); SetNumber(effect, "m_damageModifier", value10); SetNumber(effect, "m_runStaminaUseModifier", TranslateMultiplier("m_runStaminaUseModifier", configuredMultiplier5, 0f)); SetNumber(effect, "m_runStaminaDrainModifier", TranslateMultiplier("m_runStaminaDrainModifier", configuredMultiplier5, 0f)); SetNumber(effect, "m_jumpStaminaUseModifier", TranslateMultiplier("m_jumpStaminaUseModifier", configuredMultiplier6, 0f)); SetNumber(effect, "m_attackStaminaUseModifier", TranslateMultiplier("m_attackStaminaUseModifier", configuredMultiplier7, 0f)); SetNumber(effect, "m_blockStaminaUseModifier", TranslateMultiplier("m_blockStaminaUseModifier", configuredMultiplier8, 0f)); SetNumber(effect, "m_dodgeStaminaUseModifier", TranslateMultiplier("m_dodgeStaminaUseModifier", configuredMultiplier9, 0f)); SetNumber(effect, "m_swimStaminaUseModifier", TranslateMultiplier("m_swimStaminaUseModifier", configuredMultiplier10, 0f)); SetNumber(effect, "m_homeItemStaminaUseModifier", TranslateMultiplier("m_homeItemStaminaUseModifier", configuredMultiplier11, 0f)); SetNumber(effect, "m_sneakStaminaUseModifier", TranslateMultiplier("m_sneakStaminaUseModifier", configuredMultiplier12, 0f)); SetNumber(effect, "m_adrenalineModifier", GetNativeNeutralValue("m_adrenalineModifier", 0f)); ApplyNativeSkillModifiers(effect, definition, doubled); ApplyNativeDamageModifiers(effect, definition, doubled); string resistance = (doubled ? definition.DoubledResistance.Value : definition.PassiveResistance.Value); ApplyResistance(effect, resistance); } private static void ApplyNativeSkillModifiers(object effect, TrinketDefinition definition, bool doubled) { string skillName = definition.GetSkillName(1, doubled); string skillName2 = definition.GetSkillName(2, doubled); string raiseSkillName = definition.GetRaiseSkillName(doubled); SetEnumOrString(effect, "m_skillLevel", skillName); SetNumber(effect, "m_skillLevelModifier", (skillName == "None") ? 0f : definition.GetSkillBonus(1, doubled)); SetEnumOrString(effect, "m_skillLevel2", skillName2); SetNumber(effect, "m_skillLevelModifier2", (skillName2 == "None") ? 0f : definition.GetSkillBonus(2, doubled)); SetEnumOrString(effect, "m_raiseSkill", raiseSkillName); SetNumber(effect, "m_raiseSkillModifier", (raiseSkillName == "None") ? 1f : definition.GetRaiseSkillBonus(doubled)); } private static void ApplyNativeDamageModifiers(object effect, TrinketDefinition definition, bool doubled) { object memberValue = GetMemberValue(effect, "m_percentigeDamageModifiers"); if (memberValue != null) { string[] damageTypes = TrinketDefinition.DamageTypes; foreach (string text in damageTypes) { SetNumber(memberValue, "m_" + text.ToLowerInvariant(), definition.GetDamage(text, doubled)); } SetNumber(memberValue, "m_nonPlayer", 0f); SetNumber(effect, "m_damageModifier", doubled ? definition.DoubledDamageModifier.Value : definition.PassiveDamageModifier.Value); SetFieldValue(effect, "m_percentigeDamageModifiers", memberValue); } } private static void ApplyResistance(object effect, string resistance) { if (!(GetMemberValue(effect, "m_mods") is IEnumerable enumerable)) { return; } foreach (object item in enumerable) { if (item != null) { SetEnumOrString(item, "m_modifier", resistance); } } } private static void ApplyGlobalAdrenalineModifier(Player player, PlayerState state) { object obj = InvokeNoArgumentMethod(player, "GetSEMan"); if (obj == null) { return; } float value = Plugin.AdrenalineGainMultiplier.Value; if (Math.Abs(value - 1f) <= 0.0001f) { if (state.GlobalAdrenalineStatusEffect != null) { object globalAdrenalineStatusEffect = state.GlobalAdrenalineStatusEffect; RemoveStatusEffect(obj, globalAdrenalineStatusEffect); DestroyClonedStatusEffect(globalAdrenalineStatusEffect); state.AddedPassiveStatusEffects.Remove(globalAdrenalineStatusEffect); state.GlobalAdrenalineStatusEffect = null; } return; } if (state.GlobalAdrenalineStatusEffect == null) { object obj2 = CreateGlobalAdrenalineStatusEffect(); if (obj2 == null || !AddStatusEffect(obj, obj2)) { if (obj2 != null) { DestroyClonedStatusEffect(obj2); } return; } state.GlobalAdrenalineStatusEffect = obj2; state.AddedPassiveStatusEffects.Add(obj2); } SetNumber(state.GlobalAdrenalineStatusEffect, "m_adrenalineModifier", TranslateMultiplier("m_adrenalineModifier", value, 0f)); } private static bool IsAdrenalineFull(Player player) { float? num = InvokeNumberGetter(player, "GetAdrenaline"); float? num2 = InvokeNumberGetter(player, "GetMaxAdrenaline"); if (num.HasValue && num2.HasValue && num2.Value > 0f) { return num.Value >= num2.Value - 0.01f; } return false; } private static float GetLongestDuration(List<EquippedTrinket> equipped) { float num = 0f; foreach (EquippedTrinket item in equipped) { num = Math.Max(num, item.Definition.Duration.Value); } return num; } private static List<object> GetActiveStatusEffects(Player player, object seMan) { List<object> result = new List<object>(); AddEnumerableObjects(result, GetMemberValue(player, "m_equipmentStatusEffects")); if (InvokeNoArgumentMethod(seMan, "GetStatusEffects") is IEnumerable values) { AddEnumerableObjects(result, values); } object memberValue = GetMemberValue(seMan, "m_statusEffects"); AddEnumerableObjects(result, memberValue); return result; } private static void AddEnumerableObjects(List<object> result, object? values) { if (!(values is IEnumerable enumerable)) { return; } foreach (object item in enumerable) { if (item != null && !result.Contains(item)) { result.Add(item); } } } private static List<string> GetStatusEffectNames(ItemData item) { List<string> list = new List<string>(); object shared = item.m_shared; string[] array = new string[3] { "m_equipStatusEffect", "m_setStatusEffect", "m_attackStatusEffect" }; foreach (string memberName in array) { AddStatusEffectIdentifiers(list, GetMemberValue(shared, memberName)); AddStatusEffectIdentifiers(list, GetMemberValue(item, memberName)); } return list; } private static void AddStatusEffectIdentifiers(List<string> names, object? value) { if (value != null) { AddStatusEffectIdentifier(names, GetMemberValue(value, "m_name")); AddStatusEffectIdentifier(names, GetMemberValue(value, "m_hash")); Object val = (Object)((value is Object) ? value : null); if (val != null && val != (Object)null) { AddStatusEffectIdentifier(names, val.name); } } } private static void AddStatusEffectIdentifier(List<string> names, object? value) { if (value != null) { string text = value.ToString() ?? string.Empty; if (!string.IsNullOrWhiteSpace(text) && !ContainsName(names, text)) { names.Add(text); } } } private static bool MatchesStatusEffect(List<string> configuredIdentifiers, object effect) { List<string> list = new List<string>(); AddStatusEffectIdentifiers(list, effect); foreach (string item in list) { if (ContainsName(configuredIdentifiers, item)) { return true; } } return false; } private static bool ContainsName(List<string> names, string candidate) { string b = NormalizeStatusIdentifier(candidate); foreach (string name in names) { if (string.Equals(NormalizeStatusIdentifier(name), b, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static string NormalizeStatusIdentifier(string value) { return value.Replace("(Clone)", string.Empty).Trim(); } private static bool IsEquipped(ItemData item) { object obj = GetMemberValue(item, "m_equipped") ?? GetMemberValue(item, "m_equiped"); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } private static string GetObjectName(object? value) { if (value == null) { return string.Empty; } Object val = (Object)((value is Object) ? value : null); if (val != null) { if (!(val == (Object)null)) { return val.name; } return string.Empty; } object memberValue = GetMemberValue(value, "m_name"); if (memberValue != null) { return memberValue.ToString() ?? string.Empty; } return value.ToString() ?? string.Empty; } private static int GetObjectKey(object value) { Object val = (Object)((value is Object) ? value : null); if (val != null && val != (Object)null) { return val.GetInstanceID(); } return RuntimeHelpers.GetHashCode(value); } private static string GetMemberText(object? instance, string memberName) { return GetMemberValue(instance, memberName)?.ToString() ?? "<unavailable>"; } private static object? GetMemberValue(object? instance, string memberName) { if (instance == null) { return null; } try { FieldInfo fieldInfo = FindField(instance.GetType(), memberName); if (fieldInfo != null) { return fieldInfo.GetValue(instance); } return FindProperty(instance.GetType(), memberName)?.GetValue(instance, null); } catch (Exception ex) { if (Plugin.DebugLogging) { Plugin.Log.LogDebug((object)("Could not read member '" + memberName + "' from '" + instance.GetType().Name + "': " + ex.GetBaseException().Message)); } return null; } } private static object? GetStaticMemberValue(Type type, string memberName) { FieldInfo fieldInfo = FindField(type, memberName); if (fieldInfo != null && fieldInfo.IsStatic) { return fieldInfo.GetValue(null); } PropertyInfo propertyInfo = FindProperty(type, memberName); if (!(propertyInfo != null) || !(propertyInfo.GetMethod != null) || !propertyInfo.GetMethod.IsStatic) { return null; } return propertyInfo.GetValue(null, null); } private static bool IsUsableUnityObject(object? value) { if (value == null) { return false; } Object val = (Object)((value is Object) ? value : null); if (val != null) { return val != (Object)null; } return true; } private static float TranslateMultiplier(string memberName, float configuredMultiplier, float fallbackNeutral) { return GetNativeNeutralValue(memberName, fallbackNeutral) + configuredMultiplier - 1f; } private static float GetNativeNeutralValue(string memberName, float fallback) { if (NativeNeutralValues.TryGetValue(memberName, out var value)) { return value; } float num = fallback; Type type = typeof(Player).Assembly.GetType("SE_Stats"); ScriptableObject val = null; try { if (type != null && typeof(ScriptableObject).IsAssignableFrom(type)) { val = ScriptableObject.CreateInstance(type); if (TryReadNumber(GetMemberValue(val, memberName), out var number)) { num = number; } } } catch (Exception ex) { if (Plugin.DebugLogging) { Plugin.Log.LogDebug((object)("Could not read native neutral value for SE_Stats." + memberName + "; " + $"using fallback {fallback:0.###}: {ex.GetBaseException().Message}")); } } finally { if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } } NativeNeutralValues[memberName] = num; if (Plugin.DebugLogging) { Plugin.Log.LogDebug((object)($"Native neutral SE_Stats.{memberName}={num:0.###}; " + $"configured multiplier 1.0 maps to {num:0.###}.")); } return num; } private static bool TryReadNumber(object? value, out float number) { if (value is float num) { number = num; return true; } if (value is double num2) { number = (float)num2; return true; } if (value is int num3) { number = num3; return true; } if (value is long num4) { number = num4; return true; } number = 0f; return false; } private static void SetText(object? instance, string memberName, string value) { if (instance == null) { return; } try { FieldInfo fieldInfo = FindField(instance.GetType(), memberName); if (fieldInfo != null && fieldInfo.FieldType == typeof(string) && !fieldInfo.IsInitOnly) { fieldInfo.SetValue(instance, value); return; } PropertyInfo propertyInfo = FindProperty(instance.GetType(), memberName); if (propertyInfo != null && propertyInfo.CanWrite && propertyInfo.PropertyType == typeof(string)) { propertyInfo.SetValue(instance, value, null); } } catch (Exception exception) { LogReflectionWriteFailure(instance, memberName, exception); } } private static void SetObject(object? instance, string memberName, object value) { if (instance == null) { return; } try { FieldInfo fieldInfo = FindField(instance.GetType(), memberName); if (fieldInfo != null && !fieldInfo.IsInitOnly && fieldInfo.FieldType.IsInstanceOfType(value)) { fieldInfo.SetValue(instance, value); return; } PropertyInfo propertyInfo = FindProperty(instance.GetType(), memberName); if (propertyInfo != null && propertyInfo.CanWrite && propertyInfo.PropertyType.IsInstanceOfType(value)) { propertyInfo.SetValue(instance, value, null); } } catch (Exception exception) { LogReflectionWriteFailure(instance, memberName, exception); } } private static void SetFieldValue(object? instance, string memberName, object value) { if (instance == null) { return; } try { FieldInfo fieldInfo = FindField(instance.GetType(), memberName); if (fieldInfo != null && !fieldInfo.IsInitOnly && fieldInfo.FieldType.IsInstanceOfType(value)) { fieldInfo.SetValue(instance, value); } } catch (Exception exception) { LogReflectionWriteFailure(instance, memberName, exception); } } private static void SetNumber(object? instance, string memberName, float value) { if (instance == null) { return; } try { FieldInfo fieldInfo = FindField(instance.GetType(), memberName); if ((fieldInfo == null || !fieldInfo.IsInitOnly) && fieldInfo != null && TryConvertNumber(value, fieldInfo.FieldType, out object converted)) { fieldInfo.SetValue(instance, converted); return; } PropertyInfo propertyInfo = FindProperty(instance.GetType(), memberName); if (propertyInfo != null && propertyInfo.CanWrite && TryConvertNumber(value, propertyInfo.PropertyType, out object converted2)) { propertyInfo.SetValue(instance, converted2, null); } } catch (Exception exception) { LogReflectionWriteFailure(instance, memberName, exception); } } private static void SetBooleanOrNumber(object? instance, string memberName, bool value) { if (instance == null) { return; } try { FieldInfo fieldInfo = FindField(instance.GetType(), memberName); if (fieldInfo != null) { object converted; if (fieldInfo.FieldType == typeof(bool)) { fieldInfo.SetValue(instance, value); } else if (TryConvertNumber(value ? 1f : 0f, fieldInfo.FieldType, out converted)) { fieldInfo.SetValue(instance, converted); } } } catch (Exception exception) { LogReflectionWriteFailure(instance, memberName, exception); } } private static void SetEnumOrString(object? instance, string memberName, string value) { if (instance == null) { return; } try { FieldInfo fieldInfo = FindField(instance.GetType(), memberName); if (fieldInfo == null) { return; } if (fieldInfo.FieldType == typeof(string)) { fieldInfo.SetValue(instance, value); } else if (fieldInfo.FieldType.IsEnum) { try { fieldInfo.SetValue(instance, Enum.Parse(fieldInfo.FieldType, value, ignoreCase: true)); return; } catch (ArgumentException) { return; } } } catch (Exception exception) { LogReflectionWriteFailure(instance, memberName, exception); } } private static void LogReflectionWriteFailure(object instance, string memberName, Exception exception) { if (Plugin.DebugLogging) { Plugin.Log.LogDebug((object)("Could not write member '" + memberName + "' on '" + instance.GetType().Name + "': " + exception.GetBaseException().Message)); } } private static bool TryConvertNumber(float value, Type targetType, out object? converted) { if (targetType == typeof(float)) { converted = value; return true; } if (targetType == typeof(double)) { converted = (double)value; return true; } if (targetType == typeof(int)) { converted = (int)value; return true; } if (targetType == typeof(bool)) { converted = Math.Abs(value) > 0.001f; return true; } converted = null; return false; } private static object? InvokeNoArgumentMethod(object target, string methodName) { MethodInfo[] methods = target.GetType().GetMethods(AnyInstance); foreach (MethodInfo methodInfo in methods) { if (string.Equals(methodInfo.Name, methodName, StringComparison.Ordinal) && methodInfo.GetParameters().Length == 0) { try { return methodInfo.Invoke(target, null); } catch { return null; } } } return null; } private static float? InvokeNumberGetter(object target, string methodName) { object obj = InvokeNoArgumentMethod(target, methodName); if (obj is float) { return (float)obj; } if (obj is double num) { return (float)num; } if (obj is int num2) { return num2; } return null; } private static PlayerState GetState(Player player) { int instanceID = ((Object)player).GetInstanceID(); if (!States.TryGetValue(instanceID, out PlayerState value)) { value = new PlayerState(); States[instanceID] = value; } return value; } private static FieldInfo? FindField(Type type, string name) { Type type2 = type; while (type2 != null) { FieldInfo field = type2.GetField(name, AnyMember); if (field != null) { return field; } type2 = type2.BaseType; } return null; } private static PropertyInfo? FindProperty(Type type, string name) { Type type2 = type; while (type2 != null) { PropertyInfo property = type2.GetProperty(name, AnyMember); if (property != null) { return property; } type2 = type2.BaseType; } return null; } } [HarmonyPatch] internal static class PlayerUpdatePatch { private static MethodBase? TargetMethod() { return AccessTools.Method(typeof(Player), "Update", (Type[])null, (Type[])null) ?? AccessTools.Method(typeof(Character), "Update", (Type[])null, (Type[])null); } private static bool Prepare() { return TargetMethod() != null; } [HarmonyPostfix] private static void Postfix(object __instance) { Player val = (Player)((__instance is Player) ? __instance : null); if (val != null) { TrinketEffectManager.Tick(val); } } } [HarmonyPatch] internal static class EquipmentStatusEffectsPatch { private static MethodBase? TargetMethod() { return AccessTools.Method(typeof(Player), "UpdateEquipmentStatusEffects", (Type[])null, (Type[])null) ?? AccessTools.Method(typeof(Character), "UpdateEquipmentStatusEffects", (Type[])null, (Type[])null); } private static bool Prepare() { return TargetMethod() != null; } [HarmonyPostfix] private static void Postfix(object __instance) { Player val = (Player)((__instance is Player) ? __instance : null); if (val != null) { TrinketEffectManager.RefreshAfterEquipmentStatusEffects(val); } } } [HarmonyPatch] internal static class PlayerSaveDataPatch { private static readonly BindingFlags AnyInstance = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static readonly BindingFlags AnyStatic = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; internal static bool IsAvailable => HasTargetMethods(); private static IEnumerable<MethodBase> TargetMethods() { List<MethodBase> found = new List<MethodBase>(); MethodInfo[] methods = typeof(Player).GetMethods(AnyInstance); foreach (MethodInfo methodInfo in methods) { if (IsPlayerSaveMethod(methodInfo) && !found.Contains(methodInfo)) { found.Add(methodInfo); yield return methodInfo; } } methods = typeof(ZNet).GetMethods(AnyInstance); foreach (MethodInfo methodInfo2 in methods) { if (string.Equals(methodInfo2.Name, "Save", StringComparison.Ordinal) && !found.Contains(methodInfo2)) { found.Add(methodInfo2); yield return methodInfo2; } } } private static bool HasTargetMethods() { using (IEnumerator<MethodBase> enumerator = TargetMethods().GetEnumerator()) { if (enumerator.MoveNext()) { _ = enumerator.Current; return true; } } return false; } private static bool Prepare() { return HasTargetMethods(); } private static bool IsPlayerSaveMethod(MethodInfo method) { if (!string.Equals(method.Name, "GetSaveData", StringComparison.Ordinal) && !string.Equals(method.Name, "SavePlayerData", StringComparison.Ordinal) && !string.Equals(method.Name, "SerializePlayerData", StringComparison.Ordinal)) { return string.Equals(method.Name, "Save", StringComparison.Ordinal); } return true; } private static Player? GetCurrentPlayer(object instance) { Player val = (Player)((instance is Player) ? instance : null); if (val != null) { return val; } object? obj = typeof(Player).GetField("m_localPlayer", AnyStatic)?.GetValue(null); Player val2 = (Player)((obj is Player) ? obj : null); if (val2 != null) { return val2; } object? obj2 = typeof(Player).GetProperty("localPlayer", AnyStatic)?.GetValue(null, null); return (Player?)((obj2 is Player) ? obj2 : null); } [HarmonyPrefix] private static void Prefix(object __instance) { Player currentPlayer = GetCurrentPlayer(__instance); if ((Object)(object)currentPlayer != (Object)null) { TrinketEffectManager.BeforePlayerSave(currentPlayer); } } [HarmonyPostfix] private static void Postfix(object __instance) { Player currentPlayer = GetCurrentPlayer(__instance); if ((Object)(object)currentPlayer != (Object)null) { TrinketEffectManager.AfterPlayerSave(currentPlayer); } } [HarmonyFinalizer] private static Exception? Finalizer(object __instance, Exception? __exception) { Player currentPlayer = GetCurrentPlayer(__instance); if ((Object)(object)currentPlayer != (Object)null) { TrinketEffectManager.AfterPlayerSave(currentPlayer); } return __exception; } } [HarmonyPatch] internal static class TrinketTooltipPatch { private const BindingFlags AnyInstance = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private const BindingFlags AnyStatic = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; internal static bool IsAvailable => HasTargetMethods(); private static IEnumerable<MethodBase> TargetMethods() { MethodInfo[] methods = typeof(ItemData).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (string.Equals(methodInfo.Name, "GetTooltip", StringComparison.Ordinal) && methodInfo.ReturnType == typeof(string)) { yield return methodInfo; } } } private static bool HasTargetMethods() { using (IEnumerator<MethodBase> enumerator = TargetMethods().GetEnumerator()) { if (enumerator.MoveNext()) { _ = enumerator.Current; return true; } } return false; } private static bool Prepare() { return HasTargetMethods(); } [HarmonyPostfix] private static void Postfix(ItemData __instance, ref string __result) { if (__instance == null || (Object)(object)__instance.m_dropPrefab == (Object)null) { return; } string itemName = ((__instance.m_shared == null) ? string.Empty : __instance.m_shared.m_name); TrinketDefinition trinketDefinition = TrinketConfigRegistry.FindByPrefab(((Object)__instance.m_dropPrefab).name, itemName); if (trinketDefinition == null) { return; } try { string configuredDescription = BuildConfiguredDescription(trinketDefinition); __result = ReplaceVanillaDescription(__instance, __result ?? string.Empty, configuredDescription); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not update tooltip for '" + trinketDefinition.PrefabName + "': " + ex.GetBaseException().Message)); } } private static string BuildConfiguredDescription(TrinketDefinition definition) { StringBuilder stringBuilder = new StringBuilder(); StringBuilder stringBuilder2 = new StringBuilder(); StringBuilder stringBuilder3 = new StringBuilder(); AppendEffects(stringBuilder2, definition, doubled: false); if (Plugin.DoublingOnAdrenaline.Value) { AppendEffects(stringBuilder3, definition, doubled: true); } stringBuilder.AppendLine("<color=#D9B56D>Configured passive effects:</color>"); stringBuilder.Append((stringBuilder2.Length == 0) ? "None\n" : stringBuilder2.ToString()); if (stringBuilder3.Length > 0) { stringBuilder.AppendLine(); stringBuilder.AppendLine("<color=#D9B56D>When adrenaline is doubled:</color>"); stringBuilder.Append((object?)stringBuilder3); } return stringBuilder.ToString().TrimEnd('\r', '\n'); } private static void AppendEffects(StringBuilder output, TrinketDefinition definition, bool doubled) { float value = (doubled ? definition.DoubledHealthRegen.Value : definition.PassiveHealthRegen.Value); float value2 = (doubled ? definition.DoubledStaminaRegen.Value : definition.PassiveStaminaRegen.Value); float value3 = (doubled ? definition.DoubledEitrRegen.Value : definition.PassiveEitrRegen.Value); float value4 = (doubled ? definition.DoubledArmor.Value : definition.PassiveArmor.Value); float value5 = (doubled ? definition.DoubledSpeed.Value : definition.PassiveSpeed.Value); float value6 = (doubled ? definition.DoubledSwimSpeed.Value : definition.PassiveSwimSpeed.Value); float value7 = (doubled ? definition.DoubledJump.Value : definition.PassiveJump.Value); float value8 = (doubled ? definition.DoubledFallDamage.Value : definition.PassiveFallDamage.Value); float value9 = (doubled ? definition.DoubledNoise.Value : definition.PassiveNoise.Value); float value10 = (doubled ? definition.DoubledStealth.Value : definition.PassiveStealth.Value); float value11 = (doubled ? definition.DoubledMaxCarryWeight.Value : definition.PassiveMaxCarryWeight.Value); float value12 = (doubled ? definition.DoubledTimedBlockBonus.Value : definition.PassiveTimedBlockBonus.Value); float value13 = (doubled ? definition.DoubledStagger.Value : definition.PassiveStagger.Value); float value14 = (doubled ? definition.DoubledDamageModifier.Value : definition.PassiveDamageModifier.Value); string text = (doubled ? definition.DoubledResistance.Value : definition.PassiveResistance.Value); AddMultiplier(output, "health regeneration", value); AddMultiplier(output, "stamina regeneration", value2); AddMultiplier(output, "eitr regeneration", value3); AddFlat(output, "armor", value4); AddPercent(output, "movement speed", value5); AddPercent(output, "swim speed", value6); AddValue(output, "jump modifier", value7); AddMultiplier(output, "fall damage", value8); AddMultiplier(output, "noise", value9); AddMultiplier(output, "stealth effectiveness", value10); AddMultiplier(output, "stagger", value13); AddFlat(output, "maximum carry weight", value11); AddFlat(output, "timed-block bonus", value12); AddPercent(output, "general damage", value14); AddStaminaMultiplier(output, "run", doubled ? definition.DoubledRunStaminaUse.Value : definition.PassiveRunStaminaUse.Value); AddStaminaMultiplier(output, "jump", doubled ? definition.DoubledJumpStaminaUse.Value : definition.PassiveJumpStaminaUse.Value); AddStaminaMultiplier(output, "attack", doubled ? definition.DoubledAttackStaminaUse.Value : definition.PassiveAttackStaminaUse.Value); AddStaminaMultiplier(output, "block", doubled ? definition.DoubledBlockStaminaUse.Value : definition.PassiveBlockStaminaUse.Value); AddStaminaMultiplier(output, "dodge", doubled ? definition.DoubledDodgeStaminaUse.Value : definition.PassiveDodgeStaminaUse.Value); AddStaminaMultiplier(output, "swim", doubled ? definition.DoubledSwimStaminaUse.Value : definition.PassiveSwimStaminaUse.Value); AddStaminaMultiplier(output, "home-item", doubled ? definition.DoubledHomeItemStaminaUse.Value : definition.PassiveHomeItemStaminaUse.Value); AddStaminaMultiplier(output, "sneak", doubled ? definition.DoubledSneakStaminaUse.Value : definition.PassiveSneakStaminaUse.Value); if (!string.IsNullOrWhiteSpace(text) && !string.Equals(text, "Normal", StringComparison.OrdinalIgnoreCase)) { output.AppendLine("Damage resistance: " + text + "."); } AddSkill(output, definition.GetSkillName(1, doubled), definition.GetSkillBonus(1, doubled)); AddSkill(output, definition.GetSkillName(2, doubled), definition.GetSkillBonus(2, doubled)); string raiseSkillName = definition.GetRaiseSkillName(doubled); if (!string.Equals(raiseSkillName, "None", StringComparison.OrdinalIgnoreCase)) { AddMultiplier(output, raiseSkillName + " skill gain", definition.GetRaiseSkillBonus(doubled)); } string[] damageTypes = TrinketDefinition.DamageTypes; foreach (string text2 in damageTypes) { AddPercent(output, text2 + " damage", definition.GetDamage(text2, doubled)); } if (doubled && definition.Duration.Value > 0.0001f) { output.AppendLine("Duration: " + Format(definition.Duration.Value) + " seconds."); } } private static void AddSkill(StringBuilder output, string skill, float value) { if (!string.Equals(skill, "None", StringComparison.OrdinalIgnoreCase) && Math.Abs(value) > 0.0001f) { output.AppendLine(((value >= 0f) ? "+" : string.Empty) + Format(value) + " " + skill + " skill."); } } private static void AddFlat(StringBuilder output, string label, float value) { if (Math.Abs(value) > 0.0001f) { output.AppendLine(((value >= 0f) ? "+" : string.Empty) + Format(value) + " " + label + "."); } } private static void AddValue(StringBuilder output, string label, float value) { if (Math.Abs(value) > 0.0001f) { output.AppendLine(label + ": " + Format(value) + "."); } } private static void AddPercent(StringBuilder output, string label, float value) { if (Math.Abs(value) > 0.0001f) { output.AppendLine(((value >= 0f) ? "+" : string.Empty) + Format(value * 100f) + "% " + label + "."); } } private static void AddMultiplier(StringBuilder output, string label, float value) { if (Math.Abs(value - 1f) > 0.0001f) { float num = (value - 1f) * 100f; output.AppendLine(((num >= 0f) ? "+" : string.Empty) + Format(num) + "% " + label + "."); } } private static void AddStaminaMultiplier(StringBuilder output, string action, float value) { if (Math.Abs(value - 1f) > 0.0001f) { float num = (value - 1f) * 100f; output.AppendLine(((num >= 0f) ? "+" : string.Empty) + Format(num) + "% " + action + " stamina cost."); } } private static string Format(float value) { return value.ToString("0.##", CultureInfo.InvariantCulture); } private static string ReplaceVanillaDescription(ItemData item, string tooltip, string configuredDescription) { string rawDescription = GetRawDescription(item); if (string.IsNullOrWhiteSpace(rawDescription)) { return tooltip + "\n\n" + configuredDescription; } string text = Localize(rawDescription); if (!string.IsNullOrEmpty(text) && tooltip.IndexOf(text, StringComparison.Ordinal) >= 0) { return tooltip.Replace(text, configuredDescription); } if (tooltip.IndexOf(rawDescription, StringComparison.Ordinal) >= 0) { return tooltip.Replace(rawDescription, configuredDescription); } return tooltip + "\n\n" + configuredDescription; } private static string GetRawDescription(ItemData item) { object shared = item.m_shared; if (shared == null) { return string.Empty; } return (shared.GetType().GetField("m_description", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(shared) as string) ?? string.Empty; } private static string Localize(string text) { Type type = typeof(Player).Assembly.GetType("Localization"); if (type == null) { return text; } object obj = type.GetField("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null); if (obj == null) { return text; } MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!string.Equals(methodInfo.Name, "Localize", StringComparison.Ordinal) || methodInfo.ReturnType != typeof(string)) { continue; } ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length == 1 && !(parameters[0].ParameterType != typeof(string))) { try { return (string)methodInfo.Invoke(obj, new object[1] { text }); } catch { return text; } } } return text; } } }