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 Even More Balanced Mortician v1.1.0
EvenMoreBalancedMortician.dll
Decompiled an hour agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using EvenMoreBalancedMortician.Networking; using EvenMoreBalancedMortician.Patches; using EvenMoreBalancedMortician.Presets; using Microsoft.CodeAnalysis; using Mono.Cecil; using Mono.Cecil.Cil; using MonoMod.Cil; using MonoMod.RuntimeDetour; using Morris; using Morris.Components; using Morris.Modules; using Morris.Modules.NPC; using Morris.Modules.Survivors; using R2API; using R2API.Networking; using R2API.Networking.Interfaces; using R2API.Utils; using RoR2; using RoR2.Networking; using RoR2.Orbs; using RoR2.Projectile; using RoR2.Skills; using SkillStates.Ghoul; using SkillStates.Morris; using SkillStates.SharedStates; using UnityEngine; using UnityEngine.Networking; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("Morris")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.0.0.0")] [module: UnverifiableCode] [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 EvenMoreBalancedMortician { [BepInPlugin("com.Delfofthebla.EvenMoreBalancedMortician", "EvenMoreBalancedMortician", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] [BepInIncompatibility("com.Bloonjitsu7.BalancedMortician")] public class EvenMoreBalancedMorticianPlugin : BaseUnityPlugin { public const string Guid = "com.Delfofthebla.EvenMoreBalancedMortician"; public const string Name = "EvenMoreBalancedMortician"; public const string Version = "1.0.0"; private const string BalancedMorticianGuid = "com.Bloonjitsu7.BalancedMortician"; private MorticianSettings _settings; internal static ManualLogSource Log { get; private set; } private void Awake() { Log = ((BaseUnityPlugin)this).Logger; _settings = new MorticianSettings(((BaseUnityPlugin)this).Config); ShovelDamageSourcePatch.Install(_settings.ShovelCountsAsPrimarySkill); GhoulEquipmentPatch.Install(); GhoulAspectPatch.Install(_settings.GhoulAspectInheritChance); ProcCoefficientPatch.Install(_settings); GhoulLimitPatch.Install(_settings.GhoulLimit); TombstoneDurationPatch.Install(_settings.TombstoneDuration); ApplySettings(); _settings.PresetSelector.SettingsChanged += ApplySettings; HostSettingsSync.Install(_settings, ApplySettings); } private void ApplySettings() { MorticianTuning.Apply(_settings); SkillDescriptions.Apply(_settings); } } internal sealed class MorticianSettings { private readonly ConfigFile _config; private readonly List<IPresetSetting> _presetSettings = new List<IPresetSetting>(); private const string GeneralSection = "General"; private const string BaseStatsSection = "Base Stats"; private const string PrimarySection = "Primary - Shovel Strike"; private const string SecondarySection = "Secondary - Raise Dead"; private const string UtilitySection = "Utility - Sacrifice"; private const string SpecialSection = "Special - Tombstone"; private const string GhoulDamageNote = "Scales with the ghoul's damage stat (Secondary - Raise Dead), not Mortician's. Uses Mortician's crit chance and on-hit items."; private const string LaunchDamageNote = "Scales with the launched ghoul's or tombstone's damage stat, not Mortician's. Uses Mortician's crit chance and on-hit items."; private const string SoulOrbDamageNote = "Scales with the tombstone's damage stat (Special - Tombstone), not Mortician's. Uses the tombstone's crit chance and items, which it copies from Mortician when placed."; private const string ProcCoefficientVanilla = "Most survivor attacks use 1.0. Rapid multi-hit attacks use less, such as MUL-T's nailgun at 0.6."; public PresetSelector PresetSelector { get; } public PresetSetting<float> BaseHealth { get; } public PresetSetting<float> HealthPerLevel { get; } public PresetSetting<float> BaseRegen { get; } public PresetSetting<float> RegenPerLevel { get; } public PresetSetting<float> BaseArmor { get; } public PresetSetting<float> ArmorPerLevel { get; } public PresetSetting<float> BaseDamage { get; } public PresetSetting<float> DamagePerLevel { get; } public PresetSetting<bool> ShovelCountsAsPrimarySkill { get; } public PresetSetting<float> ShovelDamagePercent { get; } public PresetSetting<float> LaunchDamagePercent { get; } public PresetSetting<float> LaunchProcCoefficient { get; } public PresetSetting<float> RaiseDeadCooldown { get; } public PresetSetting<int> GhoulLimit { get; } public PresetSetting<float> GhoulBaseDamage { get; } public PresetSetting<float> GhoulDamagePerLevel { get; } public PresetSetting<float> GhoulDegen { get; } public PresetSetting<float> GhoulDegenPerLevel { get; } public PresetSetting<float> GhoulBiteDamagePercent { get; } public PresetSetting<float> GhoulBiteProcCoefficient { get; } public PresetSetting<float> GhoulClingBiteProcCoefficient { get; } public PresetSetting<float> GhoulSpitDamagePercent { get; } public PresetSetting<float> GhoulSpitProcCoefficient { get; } public PresetSetting<float> GhoulAspectInheritChance { get; } public PresetSetting<float> SacrificeCooldown { get; } public PresetSetting<float> SacrificeDamagePercent { get; } public PresetSetting<float> SacrificeProcCoefficient { get; } public PresetSetting<float> SacrificeRadius { get; } public PresetSetting<float> SacrificeHealPercent { get; } public PresetSetting<float> TombstoneCooldown { get; } public PresetSetting<float> TombstoneDuration { get; } public PresetSetting<float> TombstoneBaseDamage { get; } public PresetSetting<float> TombstoneDamagePerLevel { get; } public PresetSetting<float> SoulOrbDamagePercent { get; } public PresetSetting<float> SoulOrbProcCoefficient { get; } public PresetSetting<float> TombstoneGhoulSpawnInterval { get; } public MorticianSettings(ConfigFile config) { _config = config; ConfigEntry<MorticianPreset> preset = config.Bind<MorticianPreset>("General", "Preset", MorticianPreset.EvenMoreBalanced, "Which preset the other settings follow. Choosing a preset overwrites every other setting with that preset's values; editing any setting afterwards switches this to Custom.\nEvenMoreBalanced: Delf's rebalance.\nBalancedMortician: Equivalent to Bloonjitsu7's BalancedMortician mod. \nOriginal: Mortician with no changes."); BaseHealth = Bind("Base Stats", "Base Health", "Mortician's maximum health at level 1.", Presets(200f, 200f, 170f), "Most survivors sit at 110. Loader and Acrid 160. MUL-T 200."); HealthPerLevel = Bind("Base Stats", "Health Per Level", "Maximum health Mortician gains per level.", Presets(66f, 66f, 51f), "Every survivor gains 30% of their base health, so 33 for a 110 health survivor."); BaseRegen = Bind("Base Stats", "Base Regen", "Health Mortician regenerates per second at level 1.", Presets(2.5f, 2.5f, 1f), "Most survivors 1. Loader and Acrid 2.5."); RegenPerLevel = Bind("Base Stats", "Regen Per Level", "Regen per second Mortician gains per level.", Presets(0.5f, 0.5f, 0.2f), "Most survivors 0.2. Loader and Acrid 0.5."); BaseArmor = Bind("Base Stats", "Base Armor", "Mortician's armor at level 1.", Presets(20f, 20f, 10f), "Most survivors 0. Mercenary, Loader, Acrid, REX, Seeker and Drifter 20. MUL-T 12."); ArmorPerLevel = Bind("Base Stats", "Armor Per Level", "Armor Mortician gains per level.", Presets(0f, 0f, 0f), "Every survivor 0."); BaseDamage = Bind("Base Stats", "Base Damage", "Mortician's damage at level 1. Only the shovel swing scales with it; ghoul and tombstone attacks use their own damage stats.", Presets(12f, 12f, 12f), "Most survivors 12. MUL-T 11. Engineer 14. Acrid 15."); DamagePerLevel = Bind("Base Stats", "Damage Per Level", "Damage Mortician gains per level.", Presets(2.4f, 2.4f, 2.4f), "Every survivor gains 20% of their base damage, so 2.4 for a 12 damage survivor."); ShovelCountsAsPrimarySkill = Bind("Primary - Shovel Strike", "Counts As Primary Skill Damage", "Whether shovel swings count as primary skill damage. Items that trigger on primary skill hits (such as Luminous Shot) require this.", Presets(original: false, balancedMortician: true, evenMoreBalanced: true)); ShovelDamagePercent = Bind("Primary - Shovel Strike", "Swing Damage Percent", "Shovel swing damage, as a percent of Mortician's damage stat.", Presets(800f, 360f, 280f)); LaunchDamagePercent = Bind("Primary - Shovel Strike", "Launch Damage Percent", "Damage a ghoul or tombstone deals when flung through enemies by your shovel, as a percent of their own damage stats.", Presets(350f, 600f, 360f), null, "Scales with the launched ghoul's or tombstone's damage stat, not Mortician's. Uses Mortician's crit chance and on-hit items."); LaunchProcCoefficient = Bind("Primary - Shovel Strike", "Launch Proc Coefficient", "How strongly each hit from a launched ghoul or tombstone triggers Mortician's on-hit items. 1.0 is full strength.", Presets(1f, 1f, 1f), "Most survivor attacks use 1.0. Rapid multi-hit attacks use less, such as MUL-T's nailgun at 0.6."); RaiseDeadCooldown = Bind("Secondary - Raise Dead", "Cooldown", "Seconds Raise Dead takes to recharge one of its 2 charges. Cooldown reduction items still apply.", Presets(7f, 7f, 7f)); GhoulLimit = Bind("Secondary - Raise Dead", "Ghoul Limit", "Maximum number of ghouls Mortician can have at once; raising another kills the oldest. 0 means no limit.", Presets(0, 0, 0), null, "Replaces the Ghoul limit setting in Mortician's own config."); GhoulBaseDamage = Bind("Secondary - Raise Dead", "Ghoul Base Damage", "Ghoul damage at level 1. Sets the damage of ghoul bites, spit, launched ghouls and Sacrifice explosions.", Presets(12f, 8f, 8f)); GhoulDamagePerLevel = Bind("Secondary - Raise Dead", "Ghoul Damage Per Level", "Damage ghouls gain per level.", Presets(2.4f, 1.6f, 1.6f)); GhoulDegen = Bind("Secondary - Raise Dead", "Ghoul Degen", "Health each ghoul loses per second at level 1. 0 means ghouls never decay.", Presets(10f, 10f, 10f), null, "Ghouls start with 150 health and gain 45 per level, so the original values give every ghoul about 15 seconds to live at any level."); GhoulDegenPerLevel = Bind("Secondary - Raise Dead", "Ghoul Degen Per Level", "Health lost per second that ghouls gain per level.", Presets(3f, 3f, 3f)); GhoulBiteDamagePercent = Bind("Secondary - Raise Dead", "Bite Damage Percent", "Ghoul bite damage as a percent of the ghoul's damage stat.", Presets(150f, 150f, 150f), null, "Scales with the ghoul's damage stat (Secondary - Raise Dead), not Mortician's. Uses Mortician's crit chance and on-hit items."); GhoulBiteProcCoefficient = Bind("Secondary - Raise Dead", "Bite Proc Coefficient", "How strongly each regular ghoul bite triggers Mortician's on-hit items. 1.0 is full strength.", Presets(1f, 1f, 1f), "Most survivor attacks use 1.0. Rapid multi-hit attacks use less, such as MUL-T's nailgun at 0.6."); GhoulClingBiteProcCoefficient = Bind("Secondary - Raise Dead", "Cling Bite Proc Coefficient", "How strongly each bite from a clinging ghoul triggers Mortician's on-hit items. 1.0 is full strength. A ghoul launched into a large enemy latches on and bites it every 0.7 seconds.", Presets(0.8f, 0.8f, 0.8f), "Most survivor attacks use 1.0. Rapid multi-hit attacks use less, such as MUL-T's nailgun at 0.6."); GhoulSpitDamagePercent = Bind("Secondary - Raise Dead", "Spit Damage Percent", "Ghoul bile spit damage as a percent of the ghoul's damage stat.", Presets(100f, 100f, 100f), null, "Scales with the ghoul's damage stat (Secondary - Raise Dead), not Mortician's. Uses Mortician's crit chance and on-hit items."); GhoulSpitProcCoefficient = Bind("Secondary - Raise Dead", "Spit Proc Coefficient", "How strongly each ghoul bile spit triggers Mortician's on-hit items. 1.0 is full strength.", Presets(1f, 1f, 0.7f), "Most survivor attacks use 1.0. Rapid multi-hit attacks use less, such as MUL-T's nailgun at 0.6."); GhoulAspectInheritChance = Bind("Secondary - Raise Dead", "Aspect Inherit Chance Percent", "Chance for a newly raised ghoul to copy each elite aspect Mortician holds. An aspect equipment rolls once; aspect items roll once per stack. 0 disables. Ghouls never copy any other items or equipment.", Presets(100f, 0f, 25f), null, "Aspect items are any item with \"Aspect\" in its name, such as ZetAspects' aspect items. The Original preset matches the base mod for aspect equipment, but the base mod never passed on aspect items."); SacrificeCooldown = Bind("Utility - Sacrifice", "Cooldown", "Seconds Sacrifice takes to recharge. Cooldown reduction items still apply.", Presets(6f, 6f, 6f)); SacrificeDamagePercent = Bind("Utility - Sacrifice", "Detonation Damage Percent", "Damage of a sacrificed ghoul's explosion, as a percent of the ghoul's damage stat.", Presets(700f, 925f, 880f), null, "Scales with the ghoul's damage stat (Secondary - Raise Dead), not Mortician's. Uses Mortician's crit chance and on-hit items."); SacrificeProcCoefficient = Bind("Utility - Sacrifice", "Detonation Proc Coefficient", "How strongly a sacrificed ghoul's explosion triggers Mortician's on-hit items, against every enemy it hits. 1.0 is full strength.", Presets(1f, 1f, 1f), "Most survivor attacks use 1.0. Rapid multi-hit attacks use less, such as MUL-T's nailgun at 0.6."); SacrificeRadius = Bind("Utility - Sacrifice", "Detonation Radius", "Radius of a sacrificed ghoul's explosion, in meters.", Presets(18f, 20f, 20f)); SacrificeHealPercent = Bind("Utility - Sacrifice", "Heal Percent", "Percent of Mortician's maximum health healed by each Sacrifice.", Presets(15f, 15f, 10f)); TombstoneCooldown = Bind("Special - Tombstone", "Cooldown", "Seconds Tombstone takes to recharge. Cooldown reduction items still apply.", Presets(30f, 30f, 30f), "Engineer's turrets 30.", "Tombstone Duration does not follow this; adjust it separately."); TombstoneDuration = Bind("Special - Tombstone", "Tombstone Duration", "Seconds a tombstone lasts before crumbling. 0 means it lasts until replaced or destroyed.", Presets(0f, 0f, 30f)); TombstoneBaseDamage = Bind("Special - Tombstone", "Tombstone Base Damage", "Tombstone damage stat at level 1. Sets the damage of vengeful souls and launched tombstones.", Presets(12f, 12f, 12f)); TombstoneDamagePerLevel = Bind("Special - Tombstone", "Tombstone Damage Per Level", "Damage stat tombstones gain per level.", Presets(2.4f, 2.4f, 2.4f)); SoulOrbDamagePercent = Bind("Special - Tombstone", "Soul Orb Damage Percent", "Vengeful soul explosion damage, as a percent of the tombstone's damage stat.", Presets(350f, 200f, 250f), null, "Scales with the tombstone's damage stat (Special - Tombstone), not Mortician's. Uses the tombstone's crit chance and items, which it copies from Mortician when placed."); SoulOrbProcCoefficient = Bind("Special - Tombstone", "Soul Orb Proc Coefficient", "How strongly each vengeful soul explosion triggers on-hit items. 1.0 is full strength.", Presets(0.2f, 0.2f, 0.2f), "Most survivor attacks use 1.0. Rapid multi-hit attacks use less, such as MUL-T's nailgun at 0.6.", "Triggers the tombstone's items, which it copies from Mortician when placed, not Mortician's own."); TombstoneGhoulSpawnInterval = Bind("Special - Tombstone", "Ghoul Spawn Interval", "Seconds between ghouls raised by the tombstone.", Presets(10f, 10f, 10f)); PresetSelector = new PresetSelector(config, preset, _presetSettings); } public string[] SerializeLocalValues() { return _presetSettings.Select((IPresetSetting setting) => setting.SerializedLocalValue).ToArray(); } public bool TryUseHostValues(IReadOnlyList<string> hostValues) { if (hostValues.Count != _presetSettings.Count) { return false; } for (int i = 0; i < hostValues.Count; i++) { _presetSettings[i].UseHostValue(hostValues[i]); } return true; } public void UseLocalValues() { foreach (IPresetSetting presetSetting in _presetSettings) { presetSetting.UseLocalValue(); } } private static PresetValues<T> Presets<T>(T original, T balancedMortician, T evenMoreBalanced) { return new PresetValues<T>(original, balancedMortician, evenMoreBalanced); } private PresetSetting<T> Bind<T>(string section, string key, string summary, PresetValues<T> presets, string vanilla = null, string note = null) { PresetSetting<T> presetSetting = new PresetSetting<T>(_config.Bind<T>(section, key, presets.EvenMoreBalanced, Describe(summary, presets, vanilla, note)), presets); _presetSettings.Add(presetSetting); return presetSetting; } private static string Describe<T>(string summary, PresetValues<T> presets, string vanilla, string note) { List<string> list = new List<string> { summary, "Presets:", "Original " + Format(presets.Original), "BalancedMortician " + Format(presets.BalancedMortician), "EvenMoreBalanced " + Format(presets.EvenMoreBalanced) }; if (vanilla != null) { list.Add("Vanilla: " + vanilla); } if (note != null) { list.Add("Note: " + note); } return string.Join("\n", list); } private static string Format<T>(T value) { if (!(value is bool)) { return Convert.ToString(value, CultureInfo.InvariantCulture); } object obj = value; if (!(bool)((obj is bool) ? obj : null)) { return "false"; } return "true"; } } internal static class MorticianTuning { public static void Apply(MorticianSettings settings) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) ApplyToBodies("Mortician", MorrisPlugin.MorrisBodyPrefab, MorrisPlugin.MorrisBodyIndex, delegate(CharacterBody body) { ApplyMorticianStats(body, settings); }); ApplyCooldowns(settings); SwingShovel.damageCoefficient = AsCoefficient(settings.ShovelDamagePercent); BaseLaunchedState.damageCoefficient = AsCoefficient(settings.LaunchDamagePercent); GhoulMelee.damageCoefficient = AsCoefficient(settings.GhoulBiteDamagePercent); ClingState.damageCoefficient = AsCoefficient(settings.GhoulBiteDamagePercent); BileSpit.damageCoefficient = AsCoefficient(settings.GhoulSpitDamagePercent); ApplySpitProcCoefficient(settings); ApplyToBodies("Ghoul", MorrisPlugin.GhoulBodyPrefab, MorrisPlugin.GhoulBodyIndex, delegate(CharacterBody body) { ApplyGhoulStats(body, settings); }); GhoulDeath.sacrificedDamageCoefficient = AsCoefficient(settings.SacrificeDamagePercent); GhoulDeath.sacrificedRadius = settings.SacrificeRadius.Value; Sacrifice.sacrificePercentHealAmount = AsCoefficient(settings.SacrificeHealPercent); ApplyToBodies("Tombstone", MorrisPlugin.TombstoneBodyPrefab, MorrisPlugin.TombstoneBodyIndex, delegate(CharacterBody body) { ApplyDamageStats(body, settings.TombstoneBaseDamage, settings.TombstoneDamagePerLevel); }); TombstoneController.soulOrbDamage = AsCoefficient(settings.SoulOrbDamagePercent); TombstoneController.spawnTime = settings.TombstoneGhoulSpawnInterval.Value; } private static void ApplyCooldowns(MorticianSettings settings) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Invalid comparison between Unknown and I4 //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) SkillLocator val = (Object.op_Implicit((Object)(object)MorrisPlugin.MorrisBodyPrefab) ? MorrisPlugin.MorrisBodyPrefab.GetComponent<SkillLocator>() : null); if (!Object.op_Implicit((Object)(object)val)) { EvenMoreBalancedMorticianPlugin.Log.LogError((object)"Mortician skill locator not found; cooldowns were not applied."); return; } SetCooldown(val.secondary, settings.RaiseDeadCooldown); SetCooldown(val.utility, settings.SacrificeCooldown); SetCooldown(val.special, settings.TombstoneCooldown); if ((int)MorrisPlugin.MorrisBodyIndex == -1) { return; } foreach (CharacterBody readOnlyInstances in CharacterBody.readOnlyInstancesList) { if (readOnlyInstances.bodyIndex == MorrisPlugin.MorrisBodyIndex) { RefreshCooldowns(readOnlyInstances.skillLocator); } } } private static void SetCooldown(GenericSkill skillSlot, PresetSetting<float> cooldown) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) Variant[] variants = skillSlot.skillFamily.variants; for (int i = 0; i < variants.Length; i++) { variants[i].skillDef.baseRechargeInterval = cooldown.Value; } } private static void RefreshCooldowns(SkillLocator skills) { skills.secondary.RecalculateValues(); skills.utility.RecalculateValues(); skills.special.RecalculateValues(); } private static void ApplySpitProcCoefficient(MorticianSettings settings) { ProjectileController val = (Object.op_Implicit((Object)(object)BileSpit.spitPrefab) ? BileSpit.spitPrefab.GetComponent<ProjectileController>() : null); if (!Object.op_Implicit((Object)(object)val)) { EvenMoreBalancedMorticianPlugin.Log.LogError((object)"Ghoul spit projectile not found; its proc coefficient was not applied."); } else { val.procCoefficient = settings.GhoulSpitProcCoefficient.Value; } } private static void ApplyMorticianStats(CharacterBody body, MorticianSettings settings) { body.baseMaxHealth = settings.BaseHealth.Value; body.levelMaxHealth = settings.HealthPerLevel.Value; body.baseRegen = settings.BaseRegen.Value; body.levelRegen = settings.RegenPerLevel.Value; body.baseArmor = settings.BaseArmor.Value; body.levelArmor = settings.ArmorPerLevel.Value; body.baseDamage = settings.BaseDamage.Value; body.levelDamage = settings.DamagePerLevel.Value; } private static void ApplyGhoulStats(CharacterBody body, MorticianSettings settings) { ApplyDamageStats(body, settings.GhoulBaseDamage, settings.GhoulDamagePerLevel); body.baseRegen = 0f - settings.GhoulDegen.Value; body.levelRegen = 0f - settings.GhoulDegenPerLevel.Value; } private static void ApplyDamageStats(CharacterBody body, PresetSetting<float> baseDamage, PresetSetting<float> damagePerLevel) { body.baseDamage = baseDamage.Value; body.levelDamage = damagePerLevel.Value; } private static void ApplyToBodies(string bodyName, GameObject bodyPrefab, BodyIndex bodyIndex, Action<CharacterBody> applyStats) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Invalid comparison between Unknown and I4 //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) CharacterBody val = (Object.op_Implicit((Object)(object)bodyPrefab) ? bodyPrefab.GetComponent<CharacterBody>() : null); if (!Object.op_Implicit((Object)(object)val)) { EvenMoreBalancedMorticianPlugin.Log.LogError((object)(bodyName + " body prefab not found; its stats were not applied.")); return; } applyStats(val); if ((int)bodyIndex == -1) { return; } foreach (CharacterBody readOnlyInstances in CharacterBody.readOnlyInstancesList) { if (readOnlyInstances.bodyIndex == bodyIndex) { applyStats(readOnlyInstances); readOnlyInstances.MarkAllStatsDirty(); } } } private static float AsCoefficient(PresetSetting<float> percent) { return percent.Value / 100f; } } internal static class SkillDescriptions { private const string TokenPrefix = "BOG_MORRIS_BODY_"; private static LanguageOverlay _overlay; public static void Apply(MorticianSettings settings) { LanguageOverlay overlay = _overlay; if (overlay != null) { overlay.Remove(); } _overlay = LanguageAPI.AddOverlay(BuildDescriptions(settings)); } private static Dictionary<string, string> BuildDescriptions(MorticianSettings settings) { Dictionary<string, string> dictionary = new Dictionary<string, string>(); dictionary["BOG_MORRIS_BODY_PRIMARY_SHOVEL_DESCRIPTION"] = "Swing your shovel for " + Damage(settings.ShovelDamagePercent.Value) + ". Hit ghouls and tombstones to <style=cIsUtility>launch</style> them for " + Damage(settings.LaunchDamagePercent.Value) + "."; dictionary["BOG_MORRIS_BODY_SECONDARY_GHOUL_DESCRIPTION"] = "<style=cIsUtility>Soulbound</style>. Spawn a ghoul on the ground in front of you. Ghouls bite for " + Damage(settings.GhoulBiteDamagePercent.Value) + ", and spit <style=cIsDamage>Blighted</style> bile for " + Damage(settings.GhoulSpitDamagePercent.Value) + "."; dictionary["BOG_MORRIS_BODY_UTILITY_LANTERN_DESCRIPTION"] = "<style=cIsHealth>Detonate</style> the target ghoul for " + Damage(settings.SacrificeDamagePercent.Value) + ", and <style=cIsHealing>heal " + Number(settings.SacrificeHealPercent.Value) + "% of your maximum health</style>."; dictionary["BOG_MORRIS_BODY_SPECIAL_TOMBSTONE_DESCRIPTION"] = "Erect a tombstone" + DescribeDuration(settings.TombstoneDuration.Value) + " that spawns a ghoul every <style=cIsUtility>" + Number(settings.TombstoneGhoulSpawnInterval.Value) + " seconds</style>. Whenever a ghoul is slain, the tombstone generates an <style=cIsDamage>explosive</style> <style=cIsUtility>vengeful soul</style> which it will fire at a nearby enemy for " + Damage(settings.SoulOrbDamagePercent.Value) + "."; return dictionary; } private static string DescribeDuration(float seconds) { if (!(seconds > 0f)) { return ""; } return " lasting <style=cIsUtility>" + Number(seconds) + " seconds</style>"; } private static string Damage(float percent) { return "<style=cIsDamage>" + Number(percent) + "% damage</style>"; } private static string Number(float value) { return value.ToString("0.##", CultureInfo.InvariantCulture); } } } namespace EvenMoreBalancedMortician.Presets { internal interface IPresetSetting { string SerializedLocalValue { get; } void ApplyPreset(MorticianPreset preset); bool Matches(MorticianPreset preset); void UseHostValue(string serializedValue); void UseLocalValue(); } internal enum MorticianPreset { Original, BalancedMortician, EvenMoreBalanced, Custom } internal sealed class PresetSelector { private readonly ConfigFile _config; private readonly ConfigEntry<MorticianPreset> _preset; private readonly IReadOnlyList<IPresetSetting> _settings; private bool _isRewritingSettings; public event Action SettingsChanged; public PresetSelector(ConfigFile config, ConfigEntry<MorticianPreset> preset, IReadOnlyList<IPresetSetting> settings) { _config = config; _preset = preset; _settings = settings; SelectCustomIfSettingsDiverge(); config.SettingChanged += OnSettingChanged; } private void OnSettingChanged(object sender, SettingChangedEventArgs args) { if (!_isRewritingSettings) { if ((object)args.ChangedSetting == _preset) { ApplySelectedPreset(); } else { SelectCustomIfSettingsDiverge(); } this.SettingsChanged?.Invoke(); } } private void ApplySelectedPreset() { if (_preset.Value == MorticianPreset.Custom) { return; } RewriteSettings(delegate { foreach (IPresetSetting setting in _settings) { setting.ApplyPreset(_preset.Value); } }); } private void SelectCustomIfSettingsDiverge() { if (_preset.Value != MorticianPreset.Custom && !_settings.All((IPresetSetting setting) => setting.Matches(_preset.Value))) { RewriteSettings(delegate { _preset.Value = MorticianPreset.Custom; }); } } private void RewriteSettings(Action rewrite) { bool saveOnConfigSet = _config.SaveOnConfigSet; _config.SaveOnConfigSet = false; _isRewritingSettings = true; try { rewrite(); } finally { _isRewritingSettings = false; _config.SaveOnConfigSet = saveOnConfigSet; _config.Save(); } } } internal sealed class PresetSetting<T> : IPresetSetting { private bool _hasHostValue; private T _hostValue; public ConfigEntry<T> Entry { get; } public T Value { get { if (!_hasHostValue) { return Entry.Value; } return _hostValue; } } public string SerializedLocalValue => TomlTypeConverter.ConvertToString((object)Entry.Value, typeof(T)); public PresetSetting(ConfigEntry<T> entry, PresetValues<T> presetValues) { <presetValues>P = presetValues; Entry = entry; base..ctor(); } public void ApplyPreset(MorticianPreset preset) { Entry.Value = <presetValues>P.For(preset); } public bool Matches(MorticianPreset preset) { return EqualityComparer<T>.Default.Equals(Entry.Value, <presetValues>P.For(preset)); } public void UseHostValue(string serializedValue) { _hostValue = TomlTypeConverter.ConvertToValue<T>(serializedValue); _hasHostValue = true; } public void UseLocalValue() { _hasHostValue = false; } } internal readonly struct PresetValues<T> { public T Original { get; } public T BalancedMortician { get; } public T EvenMoreBalanced { get; } public PresetValues(T original, T balancedMortician, T evenMoreBalanced) { Original = original; BalancedMortician = balancedMortician; EvenMoreBalanced = evenMoreBalanced; } public T For(MorticianPreset preset) { return preset switch { MorticianPreset.Original => Original, MorticianPreset.BalancedMortician => BalancedMortician, MorticianPreset.EvenMoreBalanced => EvenMoreBalanced, _ => throw new ArgumentOutOfRangeException("preset", preset, "Custom has no preset values."), }; } } } namespace EvenMoreBalancedMortician.Patches { internal static class GhoulAspectPatch { private const string AspectNameFragment = "Aspect"; private static readonly HashSet<string> _detectedAspects = new HashSet<string>(); private static Hook _minionStartHook; private static PresetSetting<float> _inheritChancePercent; public static void Install(PresetSetting<float> inheritChancePercent) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown _inheritChancePercent = inheritChancePercent; _minionStartHook = new Hook((MethodBase)typeof(MorrisMinionController).GetMethod("Start", BindingFlags.Instance | BindingFlags.NonPublic), (Delegate)new Action<Action<MorrisMinionController>, MorrisMinionController>(InheritAspects)); } private static void InheritAspects(Action<MorrisMinionController> orig, MorrisMinionController self) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Invalid comparison between Unknown and I4 //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) orig(self); if (!NetworkServer.active || (int)self.minionType != 1 || _inheritChancePercent.Value <= 0f) { return; } Inventory val = (Object.op_Implicit((Object)(object)self.characterBody) ? self.characterBody.inventory : null); Inventory val2 = (Object.op_Implicit((Object)(object)self.ownerBody) ? self.ownerBody.inventory : null); if (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)val2)) { return; } InheritAspectEquipment(val2, val); foreach (ItemIndex item in val2.itemAcquisitionOrder) { if (IsAspectItem(item)) { LogFirstDetection("item", ((Object)ItemCatalog.GetItemDef(item)).name); InheritAspectItemStacks(item, val2, val); } } } private static void InheritAspectEquipment(Inventory ownerInventory, Inventory ghoulInventory) { //IL_0001: 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_0007: 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_002e: Unknown result type (might be due to invalid IL or missing references) EquipmentIndex currentEquipmentIndex = ownerInventory.currentEquipmentIndex; if (IsAspectEquipment(currentEquipmentIndex)) { LogFirstDetection("equipment", ((Object)EquipmentCatalog.GetEquipmentDef(currentEquipmentIndex)).name); if (RollInheritChance()) { ghoulInventory.SetEquipmentIndex(currentEquipmentIndex, false); } } } private static void InheritAspectItemStacks(ItemIndex aspect, Inventory ownerInventory, Inventory ghoulInventory) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) int num = 0; int itemCountEffective = ownerInventory.GetItemCountEffective(aspect); for (int i = 0; i < itemCountEffective; i++) { if (RollInheritChance()) { num++; } } if (num > 0) { ghoulInventory.GiveItemPermanent(aspect, num); } } private static bool IsAspectEquipment(EquipmentIndex equipment) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) EquipmentDef equipmentDef = EquipmentCatalog.GetEquipmentDef(equipment); if (Object.op_Implicit((Object)(object)equipmentDef) && Object.op_Implicit((Object)(object)equipmentDef.passiveBuffDef)) { return equipmentDef.passiveBuffDef.isElite; } return false; } private static bool IsAspectItem(ItemIndex item) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return ((Object)ItemCatalog.GetItemDef(item)).name.IndexOf("Aspect", StringComparison.OrdinalIgnoreCase) >= 0; } private static bool RollInheritChance() { return Util.CheckRoll(_inheritChancePercent.Value, 0f, (CharacterMaster)null); } private static void LogFirstDetection(string aspectKind, string aspectName) { if (_detectedAspects.Add(aspectKind + ":" + aspectName)) { EvenMoreBalancedMorticianPlugin.Log.LogInfo((object)("Ghouls can inherit aspect " + aspectKind + ": " + aspectName)); } } } internal static class GhoulEquipmentPatch { [CompilerGenerated] private static class <>O { public static Manipulator <0>__RemoveEquipmentCopyCall; } private const BindingFlags AnyInstanceMethod = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static readonly List<ILHook> _hooks = new List<ILHook>(); public static void Install() { RemoveEquipmentCopy(typeof(SpawnGhoul).GetMethod("AttemptSpawnGhoul", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)); RemoveEquipmentCopy(typeof(TombstoneController).GetMethod("SpawnGhoulAtClosestNode", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)); } private static void RemoveEquipmentCopy(MethodInfo ghoulSpawner) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown List<ILHook> hooks = _hooks; object obj = <>O.<0>__RemoveEquipmentCopyCall; if (obj == null) { Manipulator val = RemoveEquipmentCopyCall; <>O.<0>__RemoveEquipmentCopyCall = val; obj = (object)val; } hooks.Add(new ILHook((MethodBase)ghoulSpawner, (Manipulator)obj)); } private static void RemoveEquipmentCopyCall(ILContext il) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) ILCursor val = new ILCursor(il); if (!val.TryGotoNext(new Func<Instruction, bool>[1] { (Instruction instruction) => ILPatternMatchingExt.MatchCallvirt<Inventory>(instruction, "CopyEquipmentFrom") })) { EvenMoreBalancedMorticianPlugin.Log.LogError((object)("Could not find the equipment copy in " + ((MemberReference)il.Method).Name + "; ghouls will still copy Mortician's equipment.")); return; } val.Remove(); val.Emit(OpCodes.Pop); val.Emit(OpCodes.Pop); } } internal static class GhoulLimitPatch { [CompilerGenerated] private static class <>O { public static GetDeployableSameSlotLimit <0>__GetGhoulLimit; public static Manipulator <1>__AlwaysTrackGhoulsAsDeployables; } private const int AnyPositiveLimit = 1; private static ILHook _minionStartHook; private static PresetSetting<int> _ghoulLimit; public static void Install(PresetSetting<int> limit) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown _ghoulLimit = limit; object obj = <>O.<0>__GetGhoulLimit; if (obj == null) { GetDeployableSameSlotLimit val = GetGhoulLimit; <>O.<0>__GetGhoulLimit = val; obj = (object)val; } Morris.ghoulSlot = DeployableAPI.RegisterDeployableSlot((GetDeployableSameSlotLimit)obj); EnsureGhoulsAreDeployable(); MethodInfo? method = typeof(MorrisMinionController).GetMethod("Start", BindingFlags.Instance | BindingFlags.NonPublic); object obj2 = <>O.<1>__AlwaysTrackGhoulsAsDeployables; if (obj2 == null) { Manipulator val2 = AlwaysTrackGhoulsAsDeployables; <>O.<1>__AlwaysTrackGhoulsAsDeployables = val2; obj2 = (object)val2; } _minionStartHook = new ILHook((MethodBase)method, (Manipulator)obj2); } private static void EnsureGhoulsAreDeployable() { GameObject ghoulMasterPrefab = GhoulMinion.ghoulMasterPrefab; if (!Object.op_Implicit((Object)(object)ghoulMasterPrefab)) { EvenMoreBalancedMorticianPlugin.Log.LogError((object)"Ghoul master prefab not found; the Ghoul Limit setting will not work."); } else if (!Object.op_Implicit((Object)(object)ghoulMasterPrefab.GetComponent<Deployable>())) { ghoulMasterPrefab.AddComponent<Deployable>(); } } private static int GetGhoulLimit(CharacterMaster master, int deployableCountMultiplier) { if (_ghoulLimit.Value <= 0) { return int.MaxValue; } return _ghoulLimit.Value; } private static void AlwaysTrackGhoulsAsDeployables(ILContext il) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown ILCursor val = new ILCursor(il); MethodReference val2 = default(MethodReference); if (!val.TryGotoNext((MoveType)2, new Func<Instruction, bool>[2] { (Instruction instruction) => ILPatternMatchingExt.MatchLdsfld(instruction, typeof(Config), "ghoulLimit"), (Instruction instruction) => ILPatternMatchingExt.MatchCallvirt(instruction, ref val2) && ((MemberReference)val2).Name == "get_Value" })) { EvenMoreBalancedMorticianPlugin.Log.LogError((object)"Could not find Mortician's ghoul limit check; the Ghoul Limit setting will not work."); return; } val.EmitDelegate<Func<int, int>>((Func<int, int>)((int _) => 1)); } } internal static class ProcCoefficientPatch { [CompilerGenerated] private static class <>O { public static Action<Action<GhoulMelee>, GhoulMelee> <0>__SetBiteProcCoefficient; public static Action<Action<BaseLaunchedState>, BaseLaunchedState> <1>__SetLaunchProcCoefficient; public static Manipulator <2>__ReplaceClingBiteProcCoefficient; public static Manipulator <3>__ReplaceDetonationProcCoefficient; public static Manipulator <4>__SetSoulOrbProcCoefficient; public static Func<float> <5>__ClingBiteProcCoefficient; public static Func<float> <6>__DetonationProcCoefficient; public static Func<Orb, Orb> <7>__WithSoulOrbProcCoefficient; } private const string ProcCoefficientField = "procCoefficient"; private static readonly List<Hook> _hooks = new List<Hook>(); private static readonly List<ILHook> _ilHooks = new List<ILHook>(); private static MorticianSettings _settings; public static void Install(MorticianSettings settings) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Expected O, but got Unknown //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Expected O, but got Unknown //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Expected O, but got Unknown //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Expected O, but got Unknown //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Expected O, but got Unknown //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Expected O, but got Unknown //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Expected O, but got Unknown _settings = settings; _hooks.Add(new Hook((MethodBase)typeof(GhoulMelee).GetMethod("OnEnter"), (Delegate)new Action<Action<GhoulMelee>, GhoulMelee>(SetBiteProcCoefficient))); _hooks.Add(new Hook((MethodBase)typeof(BaseLaunchedState).GetMethod("OnEnter"), (Delegate)new Action<Action<BaseLaunchedState>, BaseLaunchedState>(SetLaunchProcCoefficient))); List<ILHook> ilHooks = _ilHooks; MethodInfo? method = typeof(ClingState).GetMethod("Bite"); object obj = <>O.<2>__ReplaceClingBiteProcCoefficient; if (obj == null) { Manipulator val = ReplaceClingBiteProcCoefficient; <>O.<2>__ReplaceClingBiteProcCoefficient = val; obj = (object)val; } ilHooks.Add(new ILHook((MethodBase)method, (Manipulator)obj)); List<ILHook> ilHooks2 = _ilHooks; MethodInfo? method2 = typeof(GhoulDeath).GetMethod("Explode"); object obj2 = <>O.<3>__ReplaceDetonationProcCoefficient; if (obj2 == null) { Manipulator val2 = ReplaceDetonationProcCoefficient; <>O.<3>__ReplaceDetonationProcCoefficient = val2; obj2 = (object)val2; } ilHooks2.Add(new ILHook((MethodBase)method2, (Manipulator)obj2)); List<ILHook> ilHooks3 = _ilHooks; MethodInfo? method3 = typeof(TombstoneController).GetMethod("FireSoulOrb"); object obj3 = <>O.<4>__SetSoulOrbProcCoefficient; if (obj3 == null) { Manipulator val3 = SetSoulOrbProcCoefficient; <>O.<4>__SetSoulOrbProcCoefficient = val3; obj3 = (object)val3; } ilHooks3.Add(new ILHook((MethodBase)method3, (Manipulator)obj3)); } private static void SetBiteProcCoefficient(Action<GhoulMelee> orig, GhoulMelee self) { orig(self); self.attack.procCoefficient = _settings.GhoulBiteProcCoefficient.Value; } private static void SetLaunchProcCoefficient(Action<BaseLaunchedState> orig, BaseLaunchedState self) { orig(self); self.attack.procCoefficient = _settings.LaunchProcCoefficient.Value; } private static void ReplaceClingBiteProcCoefficient(ILContext il) { ReplaceProcCoefficientConstant<DamageInfo>(il, ClingBiteProcCoefficient); } private static void ReplaceDetonationProcCoefficient(ILContext il) { ReplaceProcCoefficientConstant<BlastAttack>(il, DetonationProcCoefficient); } private static void ReplaceProcCoefficientConstant<TAttack>(ILContext il, Func<float> procCoefficient) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown ILCursor val = new ILCursor(il); float num = default(float); if (!val.TryGotoNext(new Func<Instruction, bool>[2] { (Instruction instruction) => ILPatternMatchingExt.MatchLdcR4(instruction, ref num), (Instruction instruction) => ILPatternMatchingExt.MatchStfld<TAttack>(instruction, "procCoefficient") })) { EvenMoreBalancedMorticianPlugin.Log.LogError((object)("Could not find the proc coefficient in " + ((MemberReference)il.Method).Name + "; it keeps Mortician's value.")); return; } val.Remove(); val.EmitDelegate<Func<float>>(procCoefficient); } private static void SetSoulOrbProcCoefficient(ILContext il) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown ILCursor val = new ILCursor(il); if (!val.TryGotoNext(new Func<Instruction, bool>[1] { (Instruction instruction) => ILPatternMatchingExt.MatchCallvirt<OrbManager>(instruction, "AddOrb") })) { EvenMoreBalancedMorticianPlugin.Log.LogError((object)"Could not find where the tombstone fires its soul orb; it keeps Mortician's proc coefficient."); } else { val.EmitDelegate<Func<Orb, Orb>>((Func<Orb, Orb>)WithSoulOrbProcCoefficient); } } private static Orb WithSoulOrbProcCoefficient(Orb soulOrb) { GenericDamageOrb val = (GenericDamageOrb)(object)((soulOrb is GenericDamageOrb) ? soulOrb : null); if (val != null) { val.procCoefficient = _settings.SoulOrbProcCoefficient.Value; } return soulOrb; } private static float ClingBiteProcCoefficient() { return _settings.GhoulClingBiteProcCoefficient.Value; } private static float DetonationProcCoefficient() { return _settings.SacrificeProcCoefficient.Value; } } internal static class ShovelDamageSourcePatch { private static Hook _swingHook; private static PresetSetting<bool> _shovelCountsAsPrimarySkill; public static void Install(PresetSetting<bool> countsAsPrimarySkill) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown _shovelCountsAsPrimarySkill = countsAsPrimarySkill; _swingHook = new Hook((MethodBase)typeof(SwingShovel).GetMethod("OnEnter"), (Delegate)new Action<Action<SwingShovel>, SwingShovel>(TagSwingAsPrimaryIfEnabled)); } private static void TagSwingAsPrimaryIfEnabled(Action<SwingShovel> orig, SwingShovel self) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) orig(self); if (_shovelCountsAsPrimarySkill.Value) { self.attack.damageType.damageSource = (DamageSource)1; } } } internal static class TombstoneDurationPatch { private static Hook _tombstoneStartHook; private static PresetSetting<float> _tombstoneDuration; public static void Install(PresetSetting<float> duration) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown _tombstoneDuration = duration; _tombstoneStartHook = new Hook((MethodBase)typeof(TombstoneController).GetMethod("Start"), (Delegate)new Action<Action<TombstoneController>, TombstoneController>(StartExpiryTimer)); } private static void StartExpiryTimer(Action<TombstoneController> orig, TombstoneController self) { orig(self); if (NetworkServer.active && !(_tombstoneDuration.Value <= 0f)) { CharacterMaster master = ((Component)self).GetComponent<CharacterBody>().master; if (Object.op_Implicit((Object)(object)master)) { ((Component)master).gameObject.AddComponent<MasterSuicideOnTimer>().lifeTimer = _tombstoneDuration.Value; } } } } } namespace EvenMoreBalancedMortician.Networking { internal sealed class HostSettingsMessage : INetMessage, ISerializableObject { private string[] _hostValues = Array.Empty<string>(); public HostSettingsMessage() { } public HostSettingsMessage(string[] hostValues) { _hostValues = hostValues; } public void Serialize(NetworkWriter writer) { writer.Write(_hostValues.Length); string[] hostValues = _hostValues; foreach (string text in hostValues) { writer.Write(text); } } public void Deserialize(NetworkReader reader) { _hostValues = new string[reader.ReadInt32()]; for (int i = 0; i < _hostValues.Length; i++) { _hostValues[i] = reader.ReadString(); } } public void OnReceived() { HostSettingsSync.ReceiveHostValues(_hostValues); } } internal static class HostSettingsSync { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static NetworkUserGenericDelegate <>9__2_0; public static Action<Run> <>9__2_1; internal void <Install>b__2_0(NetworkUser _) { SendToClients(); } internal void <Install>b__2_1(Run _) { SendToClients(); } } private static MorticianSettings _settings; private static Action _applySettings; public static void Install(MorticianSettings settings, Action applySettings) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown _settings = settings; _applySettings = applySettings; NetworkingAPI.RegisterMessageType<HostSettingsMessage>(); object obj = <>c.<>9__2_0; if (obj == null) { NetworkUserGenericDelegate val = delegate { SendToClients(); }; <>c.<>9__2_0 = val; obj = (object)val; } NetworkUser.onPostNetworkUserStart += (NetworkUserGenericDelegate)obj; Run.onRunStartGlobal += delegate { SendToClients(); }; settings.PresetSelector.SettingsChanged += SendToClients; NetworkManagerSystem.onStopClientGlobal += RevertToLocalValues; } public static void ReceiveHostValues(string[] hostValues) { if (!NetworkServer.active) { if (!_settings.TryUseHostValues(hostValues)) { EvenMoreBalancedMorticianPlugin.Log.LogError((object)"The host's settings don't match this version of the mod; using local settings instead."); } else { _applySettings(); } } } private static void SendToClients() { if (NetworkServer.active) { NetMessageExtensions.Send((INetMessage)(object)new HostSettingsMessage(_settings.SerializeLocalValues()), (NetworkDestination)1); } } private static void RevertToLocalValues() { _settings.UseLocalValues(); _applySettings(); } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }