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 balrond DualMastery v0.3.14
plugins/BalrondDualMastery.dll
Decompiled a month ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using Balrond.DualMastery.Assets; using Balrond.DualMastery.Compatibility; using Balrond.DualMastery.Configuration; using Balrond.DualMastery.Core; using Balrond.DualMastery.Patches; using Balrond.DualMastery.Progression; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using LitJson2; using Microsoft.CodeAnalysis; using ServerSync; using TMPro; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("BalrondDualMastery")] [assembly: AssemblyDescription("Independent paired-weapon combat implementation for Valheim")] [assembly: AssemblyCompany("Balrond")] [assembly: AssemblyProduct("BalrondDualMastery")] [assembly: ComVisible(false)] [assembly: Guid("A8C95E47-F3B3-4AF1-B4BD-544392311DA9")] [assembly: AssemblyFileVersion("0.3.14.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.3.14.0")] [module: UnverifiableCode] namespace Balrond.DualMastery { [BepInPlugin("balrond.astafaraios.BalrondDualMastery", "BalrondDualMastery", "0.3.14")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "balrond.astafaraios.BalrondDualMastery"; public const string PluginName = "BalrondDualMastery"; public const string PluginVersion = "0.3.14"; public static readonly JsonLoader jsonLoader = new JsonLoader(); private Harmony _harmony; internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; SkillsReworkedCompatibility.InitializePresence(); ModSettings.Bind(((BaseUnityPlugin)this).Config); jsonLoader.loadJson(); ContentPack.Load(); MasterySkill.Initialize(); _harmony = new Harmony("balrond.astafaraios.BalrondDualMastery"); _harmony.PatchAll(typeof(Plugin).Assembly); RuntimePatchInstaller.Install(_harmony); SkillsReworkedSkillNameResolverBridge.Install(_harmony); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} {1} loaded.", "BalrondDualMastery", "0.3.14")); } private void OnDestroy() { if (_harmony != null) { _harmony.UnpatchSelf(); } } } public class BalrondTranslator { public static Dictionary<string, Dictionary<string, string>> translations = new Dictionary<string, Dictionary<string, string>>(); public static Dictionary<string, string> getLanguage(string language) { if (string.IsNullOrEmpty(language)) { return null; } if (translations.TryGetValue(language, out var value)) { return value; } return null; } } public class JsonLoader { public string defaultPath = string.Empty; public void loadJson() { LoadTranslations(); justDefaultPath(); } public void justDefaultPath() { string configPath = Paths.ConfigPath; string text = Path.Combine(configPath, "BalrondDualMastery-translation/"); defaultPath = text; } public void createDefaultPath() { string configPath = Paths.ConfigPath; string text = Path.Combine(configPath, "BalrondDualMastery-translation/"); if (!Directory.Exists(text)) { CreateFolder(text); } else { Debug.Log((object)("BalrondDualMastery: Folder already exists: " + text)); } defaultPath = text; } private string[] jsonFilePath(string folderName, string extension) { string configPath = Paths.ConfigPath; string text = Path.Combine(configPath, "BalrondDualMastery-translation/"); if (!Directory.Exists(text)) { CreateFolder(text); } else { Debug.Log((object)("BalrondDualMastery: Folder already exists: " + text)); } string[] files = Directory.GetFiles(text, extension); Debug.Log((object)("BalrondDualMastery:" + folderName + " Json Files Found: " + files.Length)); return files; } private static void CreateFolder(string path) { try { Directory.CreateDirectory(path); Debug.Log((object)"BalrondDualMastery: Folder created successfully."); } catch (Exception ex) { Debug.Log((object)("BalrondDualMastery: Error creating folder: " + ex.Message)); } } private void LoadTranslations() { int num = 0; string[] array = jsonFilePath("Translation", "*.json"); foreach (string text in array) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text); string json = File.ReadAllText(text); JsonData jsonData = JsonMapper.ToObject(json); Dictionary<string, string> dictionary = new Dictionary<string, string>(); foreach (string key in jsonData.Keys) { dictionary[key] = jsonData[key].ToString(); } if (dictionary != null) { BalrondTranslator.translations.Add(fileNameWithoutExtension, dictionary); Debug.Log((object)("BalrondDualMastery: Json Files Language: " + fileNameWithoutExtension)); num++; } else { Debug.LogError((object)("BalrondDualMastery: Loading FAILED file: " + text)); } } Debug.Log((object)("BalrondDualMastery: Translation JsonFiles Loaded: " + num)); } } [HarmonyPatch] internal static class TranslationPatches { [HarmonyPatch(typeof(FejdStartup), "SetupGui")] private class FejdStartup_SetupGUI { private static void Postfix() { string selectedLanguage = Localization.instance.GetSelectedLanguage(); Dictionary<string, string> translations = GetTranslations(selectedLanguage); AddTranslations(translations); } } [HarmonyPriority(800)] [HarmonyPatch(typeof(Localization), "SetupLanguage")] private class Translation_SetupLanguage { private static void Prefix(Localization __instance, string language) { Dictionary<string, string> translations = GetTranslations(language); AddTranslations(translations, __instance); } } [HarmonyPriority(800)] [HarmonyPatch(typeof(Localization), "LoadCSV")] private class Translation_LoadCSV { private static void Prefix(Localization __instance, string language) { Dictionary<string, string> translations = GetTranslations(language); AddTranslations(translations, __instance); } } private static Dictionary<string, string> GetTranslations(string language) { Dictionary<string, string> result = BalrondTranslator.getLanguage("English"); if (!string.Equals(language, "English", StringComparison.OrdinalIgnoreCase)) { Dictionary<string, string> language2 = BalrondTranslator.getLanguage(language); if (language2 != null) { result = language2; } else { Debug.Log((object)("BalrondDualMastery: Did not find translation file for '" + language + "', loading English")); } } return result; } private static void AddTranslations(Dictionary<string, string> translations, Localization localizationInstance = null) { if (translations == null) { Debug.LogWarning((object)"BalrondDualMastery: No translation file found!"); return; } if (localizationInstance != null) { foreach (KeyValuePair<string, string> translation in translations) { localizationInstance.AddWord(translation.Key, translation.Value); } return; } foreach (KeyValuePair<string, string> translation2 in translations) { Localization.instance.AddWord(translation2.Key, translation2.Value); } } } } namespace Balrond.DualMastery.Runtime { internal sealed class HolsterBaseline : MonoBehaviour { private bool _captured; private Vector3 _localPosition; private Quaternion _localRotation; internal void Capture() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: 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_002b: Unknown result type (might be due to invalid IL or missing references) if (!_captured) { _localPosition = ((Component)this).transform.localPosition; _localRotation = ((Component)this).transform.localRotation; _captured = true; } } internal void ApplySeparation() { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) Capture(); ((Component)this).transform.localPosition = _localPosition + new Vector3(-0.022f, 0.01f, 0.016f); ((Component)this).transform.localRotation = _localRotation * Quaternion.Euler(-5f, 56f, -7f); } internal void Restore() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) if (_captured) { ((Component)this).transform.localPosition = _localPosition; ((Component)this).transform.localRotation = _localRotation; } } } } namespace Balrond.DualMastery.Progression { internal static class MasterySkill { internal const int NumericId = 874298; internal static readonly SkillType Type = (SkillType)874298; internal const string DisplayName = "Dual Mastery"; internal const string DescriptionKey = "$balrond_dualmastery_tooltip"; private static SkillDef _definition; internal static SkillDef Definition { get { if (_definition == null) { _definition = CreateDefinition(); } return _definition; } } internal static void Initialize() { if (_definition == null) { _definition = CreateDefinition(); } } private static SkillDef CreateDefinition() { //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_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown return new SkillDef { m_skill = Type, m_icon = ContentPack.SkillIcon, m_description = "$balrond_dualmastery_tooltip", m_increseStep = 1f }; } internal static bool MatchesCheatName(string value) { return MatchesExternalName(value); } internal static bool MatchesExternalName(string value) { if (string.IsNullOrWhiteSpace(value)) { return false; } string text = value.Trim().Replace("$", string.Empty).Replace("_", string.Empty) .Replace("-", string.Empty) .Replace(" ", string.Empty); return text.Equals("DualMastery", StringComparison.OrdinalIgnoreCase) || text.Equals("DualWield", StringComparison.OrdinalIgnoreCase) || text.Equals("BalrondDualMastery", StringComparison.OrdinalIgnoreCase) || text.Equals("skill874298", StringComparison.OrdinalIgnoreCase) || text.Equals(874298.ToString(), StringComparison.OrdinalIgnoreCase); } } [HarmonyPatch] internal static class MasterySkillPatches { private static bool _collisionLogged; private static bool _idCollision; internal static bool HasIdCollision => _idCollision; private static bool IsOurDefinition(SkillDef definition) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) if (definition == null || definition.m_skill != MasterySkill.Type) { return false; } if (definition == MasterySkill.Definition) { return true; } return string.Equals(definition.m_description, "$balrond_dualmastery_tooltip", StringComparison.Ordinal); } private static bool EnsureDefinitionInList(List<SkillDef> definitions) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (definitions == null) { return false; } SkillDef definition = MasterySkill.Definition; bool flag = false; bool flag2 = false; for (int i = 0; i < definitions.Count; i++) { SkillDef val = definitions[i]; if (val == null || val.m_skill != MasterySkill.Type) { continue; } if (IsOurDefinition(val)) { flag = true; if (val != definition) { definitions[i] = definition; } } else { flag2 = true; } } _idCollision = flag2; if (flag2) { if (!_collisionLogged && Plugin.Log != null) { _collisionLogged = true; Plugin.Log.LogError((object)("Dual Mastery skill ID collision: " + 874298 + " is already registered by another SkillDef. Dual Mastery will keep gameplay active at skill-0 scaling, but it will not overwrite, raise, reset, or resolve that foreign skill as Dual Mastery.")); } return false; } if (!flag) { definitions.Add(definition); } return true; } internal static bool EnsureRuntimeDefinition(Skills skills) { return (Object)(object)skills != (Object)null && EnsureDefinitionInList(skills.m_skills); } private static void Seed(Skills skills) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)skills == (Object)null || !EnsureRuntimeDefinition(skills)) { return; } try { skills.GetSkill(MasterySkill.Type); } catch (Exception ex) { if (Plugin.Log != null) { Plugin.Log.LogDebug((object)("Dual Mastery custom-skill seed failed safely: " + ex.GetBaseException().Message)); } } } [HarmonyPatch(typeof(Skills), "Awake")] [HarmonyPrefix] [HarmonyPriority(800)] [HarmonyBefore(new string[] { "M2Valheim.SkillsReworked" })] private static void SkillsAwakePrefix(Skills __instance) { EnsureRuntimeDefinition(__instance); } [HarmonyPatch(typeof(Skills), "Awake")] [HarmonyPostfix] [HarmonyPriority(0)] [HarmonyAfter(new string[] { "M2Valheim.SkillsReworked" })] private static void SkillsAwakePostfix(Skills __instance) { if (!SkillsReworkedCompatibility.IsActive) { Seed(__instance); } Player val = (((Object)(object)__instance != (Object)null) ? __instance.m_player : null); if ((Object)(object)val != (Object)null) { SkillsReworkedCompatibility.RefreshNameMapping(val); } } [HarmonyPatch(typeof(Player), "Load")] [HarmonyPostfix] [HarmonyPriority(0)] [HarmonyAfter(new string[] { "M2Valheim.SkillsReworked" })] private static void PlayerLoadPostfix(Player __instance) { if (!((Object)(object)__instance == (Object)null)) { Skills skills = ((Character)__instance).GetSkills(); EnsureRuntimeDefinition(skills); if (!SkillsReworkedCompatibility.IsActive) { Seed(skills); } MasterySkillPersistence.ReconcileAfterLoad(__instance); SkillsReworkedCompatibility.RefreshNameMapping(__instance); } } [HarmonyPatch(typeof(Skills), "GetSkillList")] [HarmonyPrefix] private static void SeedBeforeSkillList(Skills __instance) { EnsureRuntimeDefinition(__instance); } [HarmonyPatch(typeof(Skills), "IsSkillValid")] [HarmonyPostfix] private static void AcceptCustomSkill(Skills __instance, SkillType type, ref bool __result) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) if (type == MasterySkill.Type && EnsureRuntimeDefinition(__instance)) { __result = true; } } [HarmonyPatch(typeof(Skills), "GetSkillDef")] [HarmonyPostfix] private static void SupplyDefinition(Skills __instance, SkillType type, ref SkillDef __result) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) if (type == MasterySkill.Type && EnsureRuntimeDefinition(__instance)) { __result = MasterySkill.Definition; } } [HarmonyPatch(typeof(Skills), "CheatRaiseSkill")] [HarmonyPrefix] [HarmonyPriority(800)] [HarmonyBefore(new string[] { "M2Valheim.SkillsReworked" })] private static bool RaiseCustomSkill(Skills __instance, string name, float value, bool showMessage) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) if (!MasterySkill.MatchesCheatName(name)) { return true; } if ((Object)(object)__instance != (Object)null) { EnsureRuntimeDefinition(__instance); } if (HasIdCollision) { if (showMessage && (Object)(object)Console.instance != (Object)null) { Console.instance.Print("Dual Mastery skill ID 874298 is occupied by another mod; command ignored."); } return false; } if ((Object)(object)__instance == (Object)null) { return false; } __instance.RaiseSkill(MasterySkill.Type, value); MasterySkillPersistence.Capture(__instance); if (showMessage && (Object)(object)Console.instance != (Object)null) { Console.instance.Print("Dual Mastery raised by " + value); } return false; } [HarmonyPatch(typeof(Skills), "CheatResetSkill")] [HarmonyPrefix] [HarmonyPriority(800)] [HarmonyBefore(new string[] { "M2Valheim.SkillsReworked" })] private static bool ResetCustomSkill(Skills __instance, string name) { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) if (!MasterySkill.MatchesCheatName(name)) { return true; } if ((Object)(object)__instance != (Object)null) { EnsureRuntimeDefinition(__instance); } if (HasIdCollision) { if ((Object)(object)Console.instance != (Object)null) { Console.instance.Print("Dual Mastery skill ID 874298 is occupied by another mod; reset ignored."); } return false; } if ((Object)(object)__instance != (Object)null) { __instance.ResetSkill(MasterySkill.Type); if (!SkillsReworkedCompatibility.IsActive) { __instance.GetSkill(MasterySkill.Type); } MasterySkillPersistence.Capture(__instance); } return false; } [HarmonyPatch(typeof(Skills), "RaiseSkill")] [HarmonyPrefix] private static void EnsureBeforeRaise(Skills __instance, SkillType skillType) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) if (skillType == MasterySkill.Type) { EnsureRuntimeDefinition(__instance); } } [HarmonyPatch(typeof(Skills), "RaiseSkill")] [HarmonyPostfix] [HarmonyPriority(0)] [HarmonyAfter(new string[] { "M2Valheim.SkillsReworked" })] private static void PersistAfterRaise(Skills __instance, SkillType skillType) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) if (skillType == MasterySkill.Type) { MasterySkillPersistence.Capture(__instance); } } [HarmonyPatch(typeof(Skills), "ResetSkill")] [HarmonyPostfix] [HarmonyPriority(0)] [HarmonyAfter(new string[] { "M2Valheim.SkillsReworked" })] private static void PersistAfterReset(Skills __instance, SkillType skillType) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) if (skillType == MasterySkill.Type && !((Object)(object)__instance == (Object)null) && !HasIdCollision) { if (!SkillsReworkedCompatibility.IsActive) { __instance.GetSkill(MasterySkill.Type); } MasterySkillPersistence.Capture(__instance); } } [HarmonyPatch(typeof(Skills), "OnDeath")] [HarmonyPostfix] [HarmonyPriority(0)] [HarmonyAfter(new string[] { "M2Valheim.SkillsReworked" })] private static void PersistAfterDeath(Skills __instance) { MasterySkillPersistence.Capture(__instance); } } internal static class MasterySkillPersistence { internal const string CustomDataKey = "balrond.dualmastery.skill.v1"; internal const string BackendMarkerKey = "balrond.dualmastery.skill.backend.v1"; private const string NativeBackendMarker = "native"; private const string SkillsReworkedBackendMarker = "skillsreworked"; private const char Separator = '|'; internal static void Capture(Player player) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || MasterySkillPatches.HasIdCollision) { return; } Skills skills = ((Character)player).GetSkills(); if ((Object)(object)skills == (Object)null || skills.m_skillData == null) { return; } if (!skills.m_skillData.TryGetValue(MasterySkill.Type, out var value) || value == null) { if (SkillsReworkedCompatibility.IsActive && IsBackend(player, "skillsreworked")) { WriteValues(player, 0f, 0f); } } else { Write(player, value); MarkCurrentBackend(player); } } internal static void Capture(Skills skills) { Capture(((Object)(object)skills != (Object)null) ? skills.m_player : null); } internal static void ReconcileAfterLoad(Player player) { //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || MasterySkillPatches.HasIdCollision) { return; } Skills skills = ((Character)player).GetSkills(); if ((Object)(object)skills == (Object)null) { return; } float level2; float accumulator2; Skill value2; if (SkillsReworkedCompatibility.IsActive) { if (skills.m_skillData != null && skills.m_skillData.TryGetValue(MasterySkill.Type, out var value) && value != null) { Write(player, value); MarkBackend(player, "skillsreworked"); return; } if (!IsBackend(player, "skillsreworked")) { if (TryRead(player, out var level, out var accumulator)) { Skill skill = skills.GetSkill(MasterySkill.Type); if (skill != null) { skill.m_level = level; skill.m_accumulator = accumulator; Write(player, skill); MarkBackend(player, "skillsreworked"); if (Plugin.Log != null) { Plugin.Log.LogInfo((object)("Migrated fallback Dual Mastery state into SkillsReworked once: level " + level.ToString("0.###", CultureInfo.InvariantCulture) + ".")); } return; } } MarkBackend(player, "skillsreworked"); } WriteValues(player, 0f, 0f); } else if (TryRead(player, out level2, out accumulator2)) { Skill skill2 = skills.GetSkill(MasterySkill.Type); if (skill2 != null) { float level3 = skill2.m_level; float accumulator3 = skill2.m_accumulator; skill2.m_level = level2; skill2.m_accumulator = accumulator2; MarkBackend(player, "native"); if (Plugin.Log != null && (!Approximately(level3, level2) || !Approximately(accumulator3, accumulator2))) { Plugin.Log.LogInfo((object)("Restored native Dual Mastery state after character load: level " + level2.ToString("0.###", CultureInfo.InvariantCulture) + ".")); } } } else if (skills.m_skillData != null && skills.m_skillData.TryGetValue(MasterySkill.Type, out value2) && value2 != null) { Write(player, value2); MarkBackend(player, "native"); } else { Skill skill3 = skills.GetSkill(MasterySkill.Type); if (skill3 != null) { Write(player, skill3); MarkBackend(player, "native"); } } } internal static float GetBackingLevel(Player player) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || MasterySkillPatches.HasIdCollision) { return 0f; } Skills skills = ((Character)player).GetSkills(); if ((Object)(object)skills == (Object)null || skills.m_skillData == null) { return 0f; } if (!skills.m_skillData.TryGetValue(MasterySkill.Type, out var value) || value == null) { return 0f; } return SanitizeLevel(value.m_level); } private static bool TryRead(Player player, out float level, out float accumulator) { level = 0f; accumulator = 0f; if ((Object)(object)player == (Object)null || player.m_customData == null) { return false; } if (!player.m_customData.TryGetValue("balrond.dualmastery.skill.v1", out var value) || string.IsNullOrEmpty(value)) { return false; } string[] array = value.Split(new char[1] { '|' }); if (array.Length != 2) { return false; } if (!float.TryParse(array[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || !float.TryParse(array[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result2) || float.IsNaN(result) || float.IsInfinity(result) || float.IsNaN(result2) || float.IsInfinity(result2)) { return false; } level = SanitizeLevel(result); accumulator = SanitizeAccumulator(result2); return true; } private static void Write(Player player, Skill skill) { if (!((Object)(object)player == (Object)null) && player.m_customData != null && skill != null) { WriteValues(player, skill.m_level, skill.m_accumulator); } } private static void WriteValues(Player player, float level, float accumulator) { if (!((Object)(object)player == (Object)null) && player.m_customData != null) { level = SanitizeLevel(level); accumulator = SanitizeAccumulator(accumulator); player.m_customData["balrond.dualmastery.skill.v1"] = level.ToString("R", CultureInfo.InvariantCulture) + "|" + accumulator.ToString("R", CultureInfo.InvariantCulture); } } private static void MarkCurrentBackend(Player player) { MarkBackend(player, SkillsReworkedCompatibility.IsActive ? "skillsreworked" : "native"); } private static void MarkBackend(Player player, string marker) { if (!((Object)(object)player == (Object)null) && player.m_customData != null && !string.IsNullOrEmpty(marker)) { player.m_customData["balrond.dualmastery.skill.backend.v1"] = marker; } } private static bool IsBackend(Player player, string marker) { if ((Object)(object)player == (Object)null || player.m_customData == null || string.IsNullOrEmpty(marker)) { return false; } string value; return player.m_customData.TryGetValue("balrond.dualmastery.skill.backend.v1", out value) && string.Equals(value, marker, StringComparison.Ordinal); } private static float SanitizeLevel(float value) { if (float.IsNaN(value) || float.IsInfinity(value)) { return 0f; } return Math.Max(0f, value); } private static float SanitizeAccumulator(float value) { if (float.IsNaN(value) || float.IsInfinity(value)) { return 0f; } return Math.Max(0f, value); } private static bool Approximately(float a, float b) { return Math.Abs(a - b) <= 0.0001f; } } } namespace Balrond.DualMastery.Patches { [HarmonyPatch] internal static class CombatPatches { private struct StartQuote { internal bool Active; internal ResourceQuote Extra; internal Player Player; internal bool Secondary; internal bool FacingChanged; internal Quaternion PreviousRotation; } private struct TriggerState { internal Player Player; internal bool Changed; internal Quaternion PreviousRotation; internal bool Paired; internal bool Secondary; internal bool PrimaryMainWindow; internal bool ExternalSingleWindowPrimary; } [HarmonyPatch(typeof(Humanoid), "StartAttack")] [HarmonyPrefix] private static bool CheckCombinedResources(Humanoid __instance, bool secondaryAttack, ref bool __result, out StartQuote __state) { //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) __state = default(StartQuote); Player val = (Player)(object)((__instance is Player) ? __instance : null); if ((Object)(object)val == (Object)null || !NetworkAuthority.IsOwner(val) || !PairedLoadout.TryGet(val, out var loadout)) { return true; } __state.Player = val; __state.Secondary = secondaryAttack; __state.FacingChanged = PairedAttackRuntime.TryFaceCursor(val, out __state.PreviousRotation); Attack val2 = PairedLoadout.SelectAttack(loadout.MainHand, secondaryAttack); Attack val3 = PairedLoadout.SelectAttack(loadout.OffHand, secondaryAttack); if (val2 == null || val3 == null) { PairedAttackRuntime.RestoreFacing(val, __state.FacingChanged, __state.PreviousRotation); __state.FacingChanged = false; return true; } ResourceQuote resourceQuote = OffhandAttackEngine.Quote(val, loadout.MainHand, val2); ResourceQuote resourceQuote2 = OffhandAttackEngine.Quote(val, loadout.OffHand, val3).Scale(CombatPolicy.ExtraResourceFactor(val, secondaryAttack)); ResourceQuote resourceQuote3 = resourceQuote.Add(resourceQuote2); if ((resourceQuote3.Stamina > 0f && !((Character)val).HaveStamina(resourceQuote3.Stamina)) || (resourceQuote3.Eitr > 0f && !((Character)val).HaveEitr(resourceQuote3.Eitr)) || (resourceQuote3.Health > 0f && !((Character)val).HaveHealth(resourceQuote3.Health))) { PairedAttackRuntime.RestoreFacing(val, __state.FacingChanged, __state.PreviousRotation); __state.FacingChanged = false; __result = false; return false; } __state.Active = true; __state.Extra = resourceQuote2; return true; } [HarmonyPatch(typeof(Humanoid), "StartAttack")] [HarmonyPostfix] private static void PayOffhandSurcharge(bool __result, StartQuote __state, Humanoid __instance) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) Player val = (Player)(object)((__instance is Player) ? __instance : null); if (!__result || !__state.Active || (Object)(object)val == (Object)null) { PairedAttackRuntime.RestoreFacing(__state.Player, __state.FacingChanged, __state.PreviousRotation); return; } if (__state.Extra.Stamina > 0f) { ((Character)val).UseStamina(__state.Extra.Stamina); } if (__state.Extra.Eitr > 0f) { ((Character)val).UseEitr(__state.Extra.Eitr); } if (__state.Extra.Health > 0f) { ((Character)val).UseHealth(__state.Extra.Health); } Attack currentAttack = ((Humanoid)val).m_currentAttack; if (currentAttack != null) { PairedAttackRuntime.Begin(val, currentAttack, __state.Secondary); } } [HarmonyPatch(typeof(Humanoid), "OnAttackTrigger")] [HarmonyPrefix] private static bool RoutePairedAttackTrigger(Humanoid __instance, out TriggerState __state) { __state = default(TriggerState); Player val = (Player)(object)((__instance is Player) ? __instance : null); if ((Object)(object)val == (Object)null || !NetworkAuthority.IsOwner(val) || ((Humanoid)val).m_currentAttack == null) { return true; } if (!PairedLoadout.TryGet(val, out var loadout)) { return true; } __state.Player = val; __state.Paired = true; __state.Secondary = ((Humanoid)val).m_currentAttackIsSecondary; __state.Changed = PairedAttackRuntime.TryFaceCursor(val, out __state.PreviousRotation); Attack currentAttack = ((Humanoid)val).m_currentAttack; PairedAttackRuntime.NormalizePrimaryAttackType(val, currentAttack, __state.Secondary); if (__state.Secondary) { return true; } if (ExternalAnimationCompatibility.PreserveExternalPrimaryAttackSemantics(val)) { if (!PairedAttackRuntime.ConsumeExternalSingleWindowPrimary(val, currentAttack)) { return false; } __state.ExternalSingleWindowPrimary = true; return true; } switch (PairedAttackRuntime.ConsumePrimaryTrigger(val, currentAttack)) { case PrimaryTriggerRole.OffhandFirst: OffhandAttackEngine.Execute(loadout, currentAttack, secondary: false); return false; case PrimaryTriggerRole.MainSecond: __state.PrimaryMainWindow = true; return true; default: return false; } } [HarmonyPatch(typeof(Humanoid), "OnAttackTrigger")] [HarmonyPostfix] private static void FinishPairedAttackTrigger(Humanoid __instance, TriggerState __state) { //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) try { if (!__state.Paired) { return; } Player val = (Player)(object)((__instance is Player) ? __instance : null); if ((Object)(object)val == (Object)null || !NetworkAuthority.IsOwner(val) || !PairedLoadout.TryGet(val, out var loadout)) { return; } Attack currentAttack = ((Humanoid)val).m_currentAttack; if (currentAttack == null) { return; } bool flag = false; if (__state.Secondary) { PairedAttackRuntime.NormalizePrimaryAttackType(val, currentAttack, secondary: true); if (PairedAttackRuntime.TryConsumeSecondaryOffhand(val, currentAttack) && OffhandAttackEngine.Execute(loadout, currentAttack, secondary: true)) { flag = true; } } else if (__state.ExternalSingleWindowPrimary) { if (PairedAttackRuntime.TryConsumeSameWindowOffhand(val, currentAttack) && OffhandAttackEngine.Execute(loadout, currentAttack, secondary: false)) { flag = true; } } else if (__state.PrimaryMainWindow) { flag = true; } if (flag && PairedAttackRuntime.TryAwardMastery(val, currentAttack)) { float num = CombatPolicy.MasteryXp(__state.Secondary); if (num > 0f) { ((Character)val).RaiseSkill(MasterySkill.Type, num); } } } finally { PairedAttackRuntime.RestoreFacing(__state.Player, __state.Changed, __state.PreviousRotation); } } } [HarmonyPatch] internal static class EquipmentPatches { private struct EquipCapture { internal Player Player; internal ItemData RequestedItem; internal ItemData PreviousMain; internal ItemData PreviousOffhand; internal ItemData PreviousHiddenMain; internal ItemData PreviousHiddenOffhand; internal bool ConvertToOffhand; internal bool ForceOffhandReplacement; internal bool EnteredDepth; } private struct UnequipCapture { internal Player Player; internal ItemData Item; internal ItemData PreviousMain; internal ItemData PreviousOffhand; internal ItemData PreviousHiddenMain; internal ItemData PreviousHiddenOffhand; internal bool WasHandReference; } private struct HandVisibilityCapture { internal Player Player; internal ItemData Main; internal ItemData Offhand; internal bool Pair; internal bool EnteredDepth; } private struct DeathUnequipCapture { internal Player Player; internal bool EnteredDepth; } private static int _equipDepth; private static int _handVisibilityDepth; private static int _bulkUnequipDepth; private static int _deathUnequipDepth; private static bool _normalizing; private static readonly HashSet<ItemData> GridRepairLogged = new HashSet<ItemData>(); private static readonly HashSet<ItemData> GridRepairFailureLogged = new HashSet<ItemData>(); [HarmonyPatch(typeof(Humanoid), "EquipItem")] [HarmonyPrefix] [HarmonyBefore(new string[] { "windstride.bowanimations", "ishid4.mods.betterarchery" })] [HarmonyPriority(800)] private static void CaptureEquip(Humanoid __instance, ItemData item, out EquipCapture __state) { __state = default(EquipCapture); Player val = (Player)(object)((__instance is Player) ? __instance : null); if (!_normalizing && _handVisibilityDepth <= 0 && _bulkUnequipDepth <= 0 && _deathUnequipDepth <= 0 && !((Object)(object)val == (Object)null) && NetworkAuthority.IsOwner(val) && ModSettings.Enabled.Value) { RepairRelevantInventoryGrid(val, "equip-prefix"); EnsureInventoryGridSlot(val, ((Humanoid)val).GetInventory(), item, "equip-request"); if (!PairedLoadout.IsSupportedWeapon(item)) { AnimationControllerService.PrepareForNonPairedHandTransition(val); } __state.Player = val; __state.RequestedItem = item; __state.PreviousMain = ((Humanoid)val).m_rightItem; __state.PreviousOffhand = ((Humanoid)val).m_leftItem; __state.PreviousHiddenMain = ((Humanoid)val).m_hiddenRightItem; __state.PreviousHiddenOffhand = ((Humanoid)val).m_hiddenLeftItem; __state.EnteredDepth = true; _equipDepth++; ItemData val2 = ((Humanoid)val).m_rightItem ?? ((Humanoid)val).m_hiddenRightItem; ItemData val3 = ((Humanoid)val).m_leftItem ?? ((Humanoid)val).m_hiddenLeftItem; if (val == Player.m_localPlayer && IsOffhandSwapModifierHeld() && val2 != null && val3 != null && IsReplaceableLeftItem(val3) && IsOffhandTarget(item) && CanPlaceInOffhand(val2, item)) { __state.PreviousMain = val2; __state.PreviousOffhand = val3; __state.ForceOffhandReplacement = true; } else if (PairedLoadout.IsSupportedWeapon(item) && val2 != null && val2 != item && PairedLoadout.CanPair(val2, item)) { __state.PreviousMain = val2; __state.ConvertToOffhand = true; } } } [HarmonyPatch(typeof(Humanoid), "EquipItem")] [HarmonyPostfix] private static void NormalizeAfterEquip(ItemData item, bool __result, EquipCapture __state) { Player player = __state.Player; if (!((Object)(object)player == (Object)null) && !_normalizing) { if (__result && __state.ForceOffhandReplacement && __state.PreviousMain != null) { ConvertSuccessfulOffhandReplacement(player, item, __state); } else if (__result && __state.ConvertToOffhand && __state.PreviousMain != null) { ConvertSuccessfulEquip(player, item, __state); } bool flag = __result && NormalizeHandInvariant(player); ReconcileCapturedFlags(player, __state); if (flag) { RunSetupEquipment(player); ReconcileCapturedFlags(player, __state); } } } [HarmonyPatch(typeof(Humanoid), "EquipItem")] [HarmonyFinalizer] private static Exception EndEquipTransaction(Exception __exception, EquipCapture __state) { if (__state.EnteredDepth && _equipDepth > 0) { _equipDepth--; } return __exception; } private static bool ConvertSuccessfulEquip(Player player, ItemData item, EquipCapture state) { if (((Humanoid)player).m_rightItem != item) { return false; } ItemData previousMain = state.PreviousMain; if (!PairedLoadout.CanPair(previousMain, item)) { return false; } Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null || !inventory.ContainsItem(previousMain) || !inventory.ContainsItem(item)) { return false; } if (!EnsureInventoryGridSlot(player, inventory, previousMain, "pair-main") || !EnsureInventoryGridSlot(player, inventory, item, "pair-selected")) { return false; } _normalizing = true; try { ((Humanoid)player).m_hiddenRightItem = null; ((Humanoid)player).m_hiddenLeftItem = null; ((Humanoid)player).m_rightItem = item; ((Humanoid)player).m_leftItem = previousMain; item.m_equipped = true; previousMain.m_equipped = true; ItemData val = state.PreviousOffhand ?? state.PreviousHiddenOffhand; if (val != null && val != previousMain && val != item) { val.m_equipped = false; } AnimationControllerService.Refresh(player); ((Humanoid)player).SetupEquipment(); } finally { _normalizing = false; } return true; } private static void ConvertSuccessfulOffhandReplacement(Player player, ItemData item, EquipCapture state) { if ((Object)(object)player == (Object)null || item == null || state.PreviousMain == null || (((Humanoid)player).m_rightItem != item && ((Humanoid)player).m_leftItem != item && !item.m_equipped) || !CanPlaceInOffhand(state.PreviousMain, item)) { return; } Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null || !inventory.ContainsItem(state.PreviousMain) || !inventory.ContainsItem(item) || !EnsureInventoryGridSlot(player, inventory, state.PreviousMain, "offhand-replace-main") || !EnsureInventoryGridSlot(player, inventory, item, "offhand-replace-selected")) { return; } _normalizing = true; try { ItemData val = state.PreviousOffhand ?? state.PreviousHiddenOffhand; ItemData rightItem = ((Humanoid)player).m_rightItem; ItemData leftItem = ((Humanoid)player).m_leftItem; ((Humanoid)player).m_hiddenRightItem = null; ((Humanoid)player).m_hiddenLeftItem = null; ((Humanoid)player).m_rightItem = state.PreviousMain; ((Humanoid)player).m_leftItem = item; state.PreviousMain.m_equipped = true; item.m_equipped = true; if (val != null && val != state.PreviousMain && val != item) { val.m_equipped = false; } if (rightItem != null && rightItem != state.PreviousMain && rightItem != item) { rightItem.m_equipped = false; } if (leftItem != null && leftItem != state.PreviousMain && leftItem != item) { leftItem.m_equipped = false; } AnimationControllerService.Refresh(player); ((Humanoid)player).SetupEquipment(); } finally { _normalizing = false; } } [HarmonyPatch(typeof(Humanoid), "UnequipItem")] [HarmonyPrefix] private static void CaptureUnequip(Humanoid __instance, ItemData item, out UnequipCapture __state) { __state = default(UnequipCapture); Player val = (Player)(object)((__instance is Player) ? __instance : null); if (!_normalizing && _equipDepth <= 0 && _handVisibilityDepth <= 0 && _bulkUnequipDepth <= 0 && _deathUnequipDepth <= 0 && !((Object)(object)val == (Object)null) && NetworkAuthority.IsOwner(val)) { __state.Player = val; __state.Item = item; __state.PreviousMain = ((Humanoid)val).m_rightItem; __state.PreviousOffhand = ((Humanoid)val).m_leftItem; __state.PreviousHiddenMain = ((Humanoid)val).m_hiddenRightItem; __state.PreviousHiddenOffhand = ((Humanoid)val).m_hiddenLeftItem; __state.WasHandReference = IsHandReference(val, item); } } [HarmonyPatch(typeof(Humanoid), "UnequipItem")] [HarmonyPostfix] private static void NormalizeAfterUnequip(UnequipCapture __state) { Player player = __state.Player; if (!((Object)(object)player == (Object)null) && !_normalizing && _equipDepth <= 0 && _bulkUnequipDepth <= 0 && _deathUnequipDepth <= 0) { bool flag = NormalizeHandInvariant(player); if (__state.WasHandReference) { ReconcileItemFlag(player, __state.Item); } ReconcileItemFlag(player, __state.PreviousMain); ReconcileItemFlag(player, __state.PreviousOffhand); ReconcileItemFlag(player, __state.PreviousHiddenMain); ReconcileItemFlag(player, __state.PreviousHiddenOffhand); if (flag) { RunSetupEquipment(player); ReconcileItemFlag(player, __state.Item); ReconcileItemFlag(player, __state.PreviousMain); ReconcileItemFlag(player, __state.PreviousOffhand); ReconcileItemFlag(player, __state.PreviousHiddenMain); ReconcileItemFlag(player, __state.PreviousHiddenOffhand); } } } [HarmonyPatch(typeof(Humanoid), "UnequipAllItems")] [HarmonyPrefix] private static void BeginBulkUnequip(Humanoid __instance, out bool __state) { Player val = (Player)(object)((__instance is Player) ? __instance : null); __state = (Object)(object)val != (Object)null && NetworkAuthority.IsOwner(val); if (__state) { _bulkUnequipDepth++; } } [HarmonyPatch(typeof(Humanoid), "UnequipAllItems")] [HarmonyFinalizer] private static Exception EndBulkUnequip(Exception __exception, bool __state) { if (__state && _bulkUnequipDepth > 0) { _bulkUnequipDepth--; } return __exception; } [HarmonyPatch(typeof(Player), "UnequipDeathDropItems")] [HarmonyPrefix] private static void BeginDeathUnequip(Player __instance, out DeathUnequipCapture __state) { __state = default(DeathUnequipCapture); if (!((Object)(object)__instance == (Object)null) && NetworkAuthority.IsOwner(__instance)) { if (ModSettings.Enabled.Value) { RepairRelevantInventoryGrid(__instance, "death-prefix"); } __state.Player = __instance; __state.EnteredDepth = true; _deathUnequipDepth++; } } [HarmonyPatch(typeof(Player), "UnequipDeathDropItems")] [HarmonyFinalizer] private static Exception EndDeathUnequip(Exception __exception, DeathUnequipCapture __state) { try { if (__state.EnteredDepth && (Object)(object)__state.Player != (Object)null) { CleanupDeathHandReferences(__state.Player); } } finally { if (__state.EnteredDepth && _deathUnequipDepth > 0) { _deathUnequipDepth--; } } return __exception; } private static void CleanupDeathHandReferences(Player player) { if (!((Object)(object)player == (Object)null)) { Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory != null) { CleanupDeathHandReference(player, inventory, ((Humanoid)player).m_leftItem); CleanupDeathHandReference(player, inventory, ((Humanoid)player).m_hiddenLeftItem); } } } private static void CleanupDeathHandReference(Player player, Inventory inventory, ItemData item) { if (!((Object)(object)player == (Object)null) && inventory != null && item != null && PairedLoadout.IsSupportedWeapon(item) && (!inventory.ContainsItem(item) || !item.m_equipped)) { RemoveHandReferences(player, item); if (!inventory.ContainsItem(item)) { item.m_equipped = false; } } } [HarmonyPatch(typeof(Humanoid), "HideHandItems", new Type[] { typeof(bool), typeof(bool) })] [HarmonyPrefix] private static void CaptureHideHands(Humanoid __instance, bool __0, out HandVisibilityCapture __state) { __state = default(HandVisibilityCapture); if (!__0) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (!_normalizing && _bulkUnequipDepth <= 0 && _deathUnequipDepth <= 0 && !((Object)(object)val == (Object)null) && NetworkAuthority.IsOwner(val) && PairedLoadout.CanPair(((Humanoid)val).m_rightItem, ((Humanoid)val).m_leftItem)) { __state.Player = val; __state.Main = ((Humanoid)val).m_rightItem; __state.Offhand = ((Humanoid)val).m_leftItem; __state.Pair = true; __state.EnteredDepth = true; _handVisibilityDepth++; } } } [HarmonyPatch(typeof(Humanoid), "HideHandItems", new Type[] { typeof(bool), typeof(bool) })] [HarmonyPostfix] private static void RestoreHiddenPairAfterHide(HandVisibilityCapture __state) { Player player = __state.Player; if (__state.Pair && !((Object)(object)player == (Object)null) && !_normalizing) { ItemData main = __state.Main; ItemData offhand = __state.Offhand; if (PairedLoadout.CanPair(main, offhand) && (((Humanoid)player).m_rightItem != main || ((Humanoid)player).m_leftItem != offhand)) { ((Humanoid)player).m_rightItem = null; ((Humanoid)player).m_leftItem = null; ((Humanoid)player).m_hiddenRightItem = main; ((Humanoid)player).m_hiddenLeftItem = offhand; main.m_equipped = true; offhand.m_equipped = true; RunSetupEquipment(player); ReconcileItemFlag(player, main); ReconcileItemFlag(player, offhand); } } } [HarmonyPatch(typeof(Humanoid), "HideHandItems", new Type[] { typeof(bool), typeof(bool) })] [HarmonyFinalizer] private static Exception EndHideHands(Exception __exception, HandVisibilityCapture __state) { EndHandVisibilityDepth(__state); return __exception; } [HarmonyPatch(typeof(Humanoid), "ShowHandItems", new Type[] { typeof(bool), typeof(bool) })] [HarmonyPrefix] private static void CaptureShowHands(Humanoid __instance, bool __0, out HandVisibilityCapture __state) { __state = default(HandVisibilityCapture); if (!__0) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (!_normalizing && _bulkUnequipDepth <= 0 && _deathUnequipDepth <= 0 && !((Object)(object)val == (Object)null) && NetworkAuthority.IsOwner(val) && PairedLoadout.CanPair(((Humanoid)val).m_hiddenRightItem, ((Humanoid)val).m_hiddenLeftItem)) { __state.Player = val; __state.Main = ((Humanoid)val).m_hiddenRightItem; __state.Offhand = ((Humanoid)val).m_hiddenLeftItem; __state.Pair = true; __state.EnteredDepth = true; _handVisibilityDepth++; } } } [HarmonyPatch(typeof(Humanoid), "ShowHandItems", new Type[] { typeof(bool), typeof(bool) })] [HarmonyPostfix] private static void RestoreDrawnPairAfterShow(HandVisibilityCapture __state) { Player player = __state.Player; if (__state.Pair && !((Object)(object)player == (Object)null) && !_normalizing) { ItemData main = __state.Main; ItemData offhand = __state.Offhand; if (PairedLoadout.CanPair(main, offhand) && (((Humanoid)player).m_hiddenRightItem != main || ((Humanoid)player).m_hiddenLeftItem != offhand || ((Humanoid)player).m_rightItem != null || ((Humanoid)player).m_leftItem != null)) { ((Humanoid)player).m_hiddenRightItem = null; ((Humanoid)player).m_hiddenLeftItem = null; ((Humanoid)player).m_rightItem = main; ((Humanoid)player).m_leftItem = offhand; main.m_equipped = true; offhand.m_equipped = true; RunSetupEquipment(player); ReconcileItemFlag(player, main); ReconcileItemFlag(player, offhand); } } } [HarmonyPatch(typeof(Humanoid), "ShowHandItems", new Type[] { typeof(bool), typeof(bool) })] [HarmonyFinalizer] private static Exception EndShowHands(Exception __exception, HandVisibilityCapture __state) { EndHandVisibilityDepth(__state); return __exception; } private static void EndHandVisibilityDepth(HandVisibilityCapture state) { if (state.EnteredDepth && _handVisibilityDepth > 0) { _handVisibilityDepth--; } } private static bool NormalizeHandInvariant(Player player) { if ((Object)(object)player == (Object)null) { return false; } bool result = RemoveDuplicateHandReferences(player); ItemData val = ((Humanoid)player).m_rightItem ?? ((Humanoid)player).m_hiddenRightItem; ItemData val2 = ((Humanoid)player).m_leftItem ?? ((Humanoid)player).m_hiddenLeftItem; if (val == null && PairedLoadout.IsSupportedWeapon(val2)) { if (((Humanoid)player).m_leftItem == val2) { ((Humanoid)player).m_leftItem = null; ((Humanoid)player).m_rightItem = val2; } else { ((Humanoid)player).m_hiddenLeftItem = null; ((Humanoid)player).m_hiddenRightItem = val2; } val2.m_equipped = IsHandReference(player, val2); return true; } if (PairedLoadout.IsSupportedWeapon(val2) && (val == null || !PairedLoadout.CanPair(val, val2))) { if (((Humanoid)player).m_leftItem == val2) { ((Humanoid)player).m_leftItem = null; } if (((Humanoid)player).m_hiddenLeftItem == val2) { ((Humanoid)player).m_hiddenLeftItem = null; } val2.m_equipped = false; result = true; } return result; } private static bool RemoveDuplicateHandReferences(Player player) { bool result = false; if (((Humanoid)player).m_rightItem != null && ((Humanoid)player).m_rightItem == ((Humanoid)player).m_hiddenRightItem) { ((Humanoid)player).m_hiddenRightItem = null; result = true; } if (((Humanoid)player).m_leftItem != null && ((Humanoid)player).m_leftItem == ((Humanoid)player).m_hiddenLeftItem) { ((Humanoid)player).m_hiddenLeftItem = null; result = true; } ItemData val = ((Humanoid)player).m_rightItem ?? ((Humanoid)player).m_hiddenRightItem; ItemData val2 = ((Humanoid)player).m_leftItem ?? ((Humanoid)player).m_hiddenLeftItem; if (val != null && val == val2) { if (((Humanoid)player).m_leftItem != null) { ((Humanoid)player).m_leftItem = null; } if (((Humanoid)player).m_hiddenLeftItem != null) { ((Humanoid)player).m_hiddenLeftItem = null; } result = true; } return result; } [HarmonyPatch(typeof(Player), "EquipInventoryItems")] [HarmonyPostfix] [HarmonyPriority(0)] private static void SanitizeAfterInventoryEquipmentRestore(Player __instance) { SanitizeRuntimeState(__instance); } internal static void SanitizeRuntimeState(Player player) { if ((Object)(object)player == (Object)null || _normalizing || _equipDepth > 0 || _handVisibilityDepth > 0 || _bulkUnequipDepth > 0 || _deathUnequipDepth > 0 || !NetworkAuthority.IsOwner(player)) { return; } Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory != null) { if (ModSettings.Enabled.Value) { RepairRelevantInventoryGrid(player, "sanitize"); } bool flag = false; flag |= ClearOrphanedHandReference(player, inventory, ((Humanoid)player).m_leftItem); flag |= ClearOrphanedHandReference(player, inventory, ((Humanoid)player).m_hiddenLeftItem); if (ModSettings.Enabled.Value) { flag |= NormalizeHandInvariant(player); } if (flag) { RunSetupEquipment(player); } } } private static void RepairRelevantInventoryGrid(Player player, string reason) { if ((Object)(object)player == (Object)null || !NetworkAuthority.IsOwner(player)) { return; } Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null) { return; } List<ItemData> allItems = inventory.GetAllItems(); if (allItems == null) { return; } for (int i = 0; i < allItems.Count; i++) { ItemData val = allItems[i]; if (val != null && PairedLoadout.IsSupportedWeapon(val) && (val.m_equipped || IsHandReference(player, val))) { EnsureInventoryGridSlot(player, inventory, val, reason); } } } private static bool EnsureInventoryGridSlot(Player player, Inventory inventory, ItemData item, string reason) { //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || inventory == null || item == null) { return false; } if (!PairedLoadout.IsSupportedWeapon(item)) { return true; } if (!inventory.ContainsItem(item)) { return false; } int width = inventory.GetWidth(); int height = inventory.GetHeight(); int x = item.m_gridPos.x; int y = item.m_gridPos.y; bool flag = x >= 0 && y >= 0 && x < width && y < height; bool flag2 = false; if (flag) { List<ItemData> allItems = inventory.GetAllItems(); if (allItems != null) { for (int i = 0; i < allItems.Count; i++) { ItemData val = allItems[i]; if (val != null && val != item && val.m_gridPos.x == x && val.m_gridPos.y == y) { flag2 = true; break; } } } } if (flag && !flag2) { return true; } Vector2i val2 = inventory.FindEmptySlot(true); if (val2.x < 0 || val2.y < 0 || val2.x >= width || val2.y >= height) { if (GridRepairFailureLogged.Add(item) && Plugin.Log != null) { Plugin.Log.LogError((object)("Dual Mastery detected an invalid inventory position for " + DescribeItem(item) + " at (" + x + "," + y + ") during " + reason + ", but the normal inventory has no free cell to repair it. No item was added, duplicated or reserved; this diagnostic is emitted once for this ItemData.")); } return false; } item.m_gridPos = val2; inventory.Changed(false, false); GridRepairFailureLogged.Remove(item); if (GridRepairLogged.Add(item) && Plugin.Log != null) { Plugin.Log.LogWarning((object)("Repaired Dual Mastery inventory grid position for " + DescribeItem(item) + ": (" + x + "," + y + ") -> (" + val2.x + "," + val2.y + ") during " + reason + ". No extra slot was created or reserved.")); } return true; } private static string DescribeItem(ItemData item) { if (item == null) { return "<null>"; } if ((Object)(object)item.m_dropPrefab != (Object)null && !string.IsNullOrEmpty(((Object)item.m_dropPrefab).name)) { return ((Object)item.m_dropPrefab).name; } if (item.m_shared != null && !string.IsNullOrEmpty(item.m_shared.m_name)) { return item.m_shared.m_name; } return "<unnamed weapon>"; } internal static void RepairSentinelCollision(Inventory inventory) { if (!ModSettings.Enabled.Value || inventory == null) { return; } Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && NetworkAuthority.IsOwner(localPlayer) && ((Humanoid)localPlayer).GetInventory() == inventory) { ItemData itemAt = inventory.GetItemAt(-1, -1); if (itemAt != null && PairedLoadout.IsSupportedWeapon(itemAt) && (itemAt.m_equipped || IsHandReference(localPlayer, itemAt))) { EnsureInventoryGridSlot(localPlayer, inventory, itemAt, "Inventory.AddItem sentinel guard"); } } } internal static void RepairBeforePlayerSave(Player player) { if (ModSettings.Enabled.Value) { RepairRelevantInventoryGrid(player, "player-save"); } } private static bool ClearOrphanedHandReference(Player player, Inventory inventory, ItemData item) { if ((Object)(object)player == (Object)null || inventory == null || item == null || !PairedLoadout.IsSupportedWeapon(item)) { return false; } if (inventory.ContainsItem(item)) { return false; } bool flag = RemoveHandReferences(player, item); if (flag) { item.m_equipped = false; } return flag; } private static bool RemoveHandReferences(Player player, ItemData item) { if ((Object)(object)player == (Object)null || item == null) { return false; } bool result = false; if (((Humanoid)player).m_rightItem == item) { ((Humanoid)player).m_rightItem = null; result = true; } if (((Humanoid)player).m_leftItem == item) { ((Humanoid)player).m_leftItem = null; result = true; } if (((Humanoid)player).m_hiddenRightItem == item) { ((Humanoid)player).m_hiddenRightItem = null; result = true; } if (((Humanoid)player).m_hiddenLeftItem == item) { ((Humanoid)player).m_hiddenLeftItem = null; result = true; } return result; } private static void RunSetupEquipment(Player player) { if ((Object)(object)player == (Object)null || _normalizing) { return; } _normalizing = true; try { AnimationControllerService.Refresh(player); ((Humanoid)player).SetupEquipment(); } finally { _normalizing = false; } } private static bool IsHandReference(Player player, ItemData item) { if ((Object)(object)player == (Object)null || item == null) { return false; } return ((Humanoid)player).m_rightItem == item || ((Humanoid)player).m_leftItem == item || ((Humanoid)player).m_hiddenRightItem == item || ((Humanoid)player).m_hiddenLeftItem == item; } private static bool IsActiveHandReference(Player player, ItemData item) { if ((Object)(object)player == (Object)null || item == null) { return false; } return ((Humanoid)player).m_rightItem == item || ((Humanoid)player).m_leftItem == item; } private static bool IsOffhandSwapModifierHeld() { //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) ConfigEntry<KeyboardShortcut> offhandSwapShortcut = ModSettings.OffhandSwapShortcut; if (offhandSwapShortcut == null) { return false; } try { KeyboardShortcut value = offhandSwapShortcut.Value; return ((KeyboardShortcut)(ref value)).IsPressed(); } catch { return false; } } private static bool IsOffhandTarget(ItemData item) { return PairedLoadout.IsSupportedWeapon(item) || IsShieldOrTorch(item); } private static bool IsReplaceableLeftItem(ItemData item) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Invalid comparison between Unknown and I4 if (item == null || item.m_shared == null) { return false; } return PairedLoadout.IsSupportedWeapon(item) || (int)item.m_shared.m_itemType == 3 || IsShieldOrTorch(item); } private static bool CanPlaceInOffhand(ItemData main, ItemData target) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Invalid comparison between Unknown and I4 if (main == null || main.m_shared == null || target == null || target.m_shared == null) { return false; } if (PairedLoadout.IsSupportedWeapon(target)) { return PairedLoadout.CanPair(main, target); } if (!IsShieldOrTorch(target)) { return false; } return (int)main.m_shared.m_itemType == 3 && (Object)(object)main.m_shared.m_buildPieces == (Object)null; } private static bool IsShieldOrTorch(ItemData item) { if (item == null || item.m_shared == null) { return false; } string a = ((object)Unsafe.As<ItemType, ItemType>(ref item.m_shared.m_itemType)/*cast due to .constrained prefix*/).ToString(); return string.Equals(a, "Shield", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "Torch", StringComparison.OrdinalIgnoreCase); } internal static void ForgetPlayer(Player player) { if (!((Object)(object)player == (Object)null)) { GridRepairLogged.Clear(); GridRepairFailureLogged.Clear(); } } internal static void HandleEnabledChanged(Player player, bool enabled) { if ((Object)(object)player == (Object)null || !NetworkAuthority.IsOwner(player)) { return; } if (enabled) { SanitizeRuntimeState(player); return; } RepairRelevantInventoryGrid(player, "feature-disable"); ItemData val = ((Humanoid)player).m_leftItem ?? ((Humanoid)player).m_hiddenLeftItem; if (PairedLoadout.IsSupportedWeapon(val)) { bool flag = false; if (((Humanoid)player).m_leftItem == val) { ((Humanoid)player).m_leftItem = null; flag = true; } if (((Humanoid)player).m_hiddenLeftItem == val) { ((Humanoid)player).m_hiddenLeftItem = null; flag = true; } if (flag) { val.m_equipped = false; RunSetupEquipment(player); } } } private static void ReconcileCapturedFlags(Player player, EquipCapture state) { ReconcileItemFlag(player, state.RequestedItem); ReconcileItemFlag(player, state.PreviousMain); ReconcileItemFlag(player, state.PreviousOffhand); ReconcileItemFlag(player, state.PreviousHiddenMain); ReconcileItemFlag(player, state.PreviousHiddenOffhand); } private static void ReconcileItemFlag(Player player, ItemData item) { if (!((Object)(object)player == (Object)null) && item != null && PairedLoadout.IsSupportedWeapon(item)) { Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null || !inventory.ContainsItem(item)) { RemoveHandReferences(player, item); item.m_equipped = false; } else { item.m_equipped = IsHandReference(player, item); } } } } internal static class RuntimePatchInstaller { private static bool _installed; internal static void Install(Harmony harmony) { if (!_installed && harmony != null) { _installed = true; InstallInventorySentinelGuard(harmony); InstallPlayerSaveCheckpoint(harmony); } } private static void InstallInventorySentinelGuard(Harmony harmony) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown MethodInfo method = typeof(RuntimePatchInstaller).GetMethod("InventoryAddItemPrefix", BindingFlags.Static | BindingFlags.NonPublic); if (method == null) { return; } int num = 0; MethodInfo[] array; try { array = typeof(Inventory).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } catch { array = new MethodInfo[0]; } foreach (MethodInfo methodInfo in array) { if (!IsSupportedInventoryAddItem(methodInfo)) { continue; } try { HarmonyMethod val = new HarmonyMethod(method) { priority = 800 }; harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } catch (Exception ex) { if (Plugin.Log != null) { Plugin.Log.LogDebug((object)("Inventory sentinel guard skipped safely for " + methodInfo?.ToString() + ": " + ex.GetBaseException().Message)); } } } if (Plugin.Log != null) { Plugin.Log.LogDebug((object)("Inventory sentinel guard installed on " + num + " Inventory.AddItem overload(s).")); } } private static bool IsSupportedInventoryAddItem(MethodInfo method) { if (method == null || !string.Equals(method.Name, "AddItem", StringComparison.Ordinal)) { return false; } ParameterInfo[] parameters = method.GetParameters(); if (parameters.Length < 4 || parameters.Length > 5) { return false; } return parameters[0].ParameterType == typeof(ItemData) && parameters[1].ParameterType == typeof(int) && parameters[2].ParameterType == typeof(int) && parameters[3].ParameterType == typeof(int); } private static void InventoryAddItemPrefix(Inventory __instance, ItemData __0, int __1, int __2, int __3) { if (__instance != null && __2 == -1 && __3 == -1) { EquipmentPatches.RepairSentinelCollision(__instance); } } private static void InstallPlayerSaveCheckpoint(Harmony harmony) { //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Expected O, but got Unknown MethodInfo method = typeof(RuntimePatchInstaller).GetMethod("PlayerSavePrefix", BindingFlags.Static | BindingFlags.NonPublic); if (method == null) { return; } List<MethodInfo> list = new List<MethodInfo>(); MethodInfo[] array; try { array = typeof(Player).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } catch { array = new MethodInfo[0]; } foreach (MethodInfo methodInfo in array) { if (methodInfo != null && string.Equals(methodInfo.Name, "Save", StringComparison.Ordinal)) { list.Add(methodInfo); } } int num = 0; for (int j = 0; j < list.Count; j++) { try { HarmonyMethod val = new HarmonyMethod(method); val.priority = 0; val.after = new string[1] { "M2Valheim.SkillsReworked" }; HarmonyMethod val2 = val; harmony.Patch((MethodBase)list[j], val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } catch (Exception ex) { if (Plugin.Log != null) { Plugin.Log.LogDebug((object)("Player.Save checkpoint skipped safely for " + list[j]?.ToString() + ": " + ex.GetBaseException().Message)); } } } if (Plugin.Log != null) { Plugin.Log.LogDebug((object)("Dual Mastery save checkpoint installed on " + num + " Player.Save overload(s).")); } } private static void PlayerSavePrefix(Player __instance) { if (!((Object)(object)__instance == (Object)null)) { EquipmentPatches.RepairBeforePlayerSave(__instance); MasterySkillPersistence.Capture(__instance); } } } [HarmonyPatch] internal static class VisualPatches { private sealed class TrailCacheEntry { internal GameObject Visual; internal MeleeWeaponTrail[] Trails; } private static readonly Dictionary<int, TrailCacheEntry> OffhandTrailCache = new Dictionary<int, TrailCacheEntry>(); [HarmonyPatch(typeof(Player), "Awake")] [HarmonyPostfix] [HarmonyAfter(new string[] { "ishid4.mods.betterarchery", "Raelaziel.OdinHorse", "windstride.bowanimations" })] [HarmonyPriority(0)] private static void InitializeVisualState(Player __instance) { if ((Object)(object)__instance != (Object)null) { AnimationControllerService.Refresh(__instance); AnimationPlaybackSpeed.Refresh(__instance); MountedDualWieldPose.Ensure(__instance); } } [HarmonyPatch(typeof(Player), "Update")] [HarmonyPostfix] [HarmonyAfter(new string[] { "ishid4.mods.betterarchery", "Raelaziel.OdinHorse", "windstride.bowanimations" })] [HarmonyPriority(0)] private static void RefreshVisualState(Player __instance) { AnimationControllerService.Refresh(__instance); AnimationPlaybackSpeed.Refresh(__instance); } [HarmonyPatch(typeof(Humanoid), "StartAttack")] [HarmonyPrefix] [HarmonyAfter(new string[] { "ishid4.mods.betterarchery", "Raelaziel.OdinHorse", "windstride.bowanimations" })] [HarmonyPriority(0)] private static void RefreshBeforeLocalAttack(Humanoid __instance) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if ((Object)(object)val != (Object)null) { AnimationControllerService.Refresh(val); } } [HarmonyPatch(typeof(ZSyncAnimation), "SetTrigger")] [HarmonyPrefix] [HarmonyAfter(new string[] { "ishid4.mods.betterarchery", "Raelaziel.OdinHorse", "windstride.bowanimations" })] [HarmonyPriority(0)] private static void RefreshBeforeLocalAnimationTrigger(ZSyncAnimation __instance, string name) { RefreshBeforeTrigger(__instance, name); } [HarmonyPatch(typeof(ZSyncAnimation), "RPC_SetTrigger")] [HarmonyPrefix] [HarmonyAfter(new string[] { "ishid4.mods.betterarchery", "Raelaziel.OdinHorse", "windstride.bowanimations" })] [HarmonyPriority(0)] private static void RefreshBeforeRemoteAnimationTrigger(ZSyncAnimation __instance, string name) { RefreshBeforeTrigger(__instance, name); } private static void RefreshBeforeTrigger(ZSyncAnimation syncAnimation, string name) { if (!string.Equals(name, "equip_hip", StringComparison.Ordinal)) { Player val = (((Object)(object)syncAnimation != (Object)null) ? ((Component)syncAnimation).GetComponent<Player>() : null); if ((Object)(object)val != (Object)null) { AnimationControllerService.Refresh(val); } } } [HarmonyPatch(typeof(Player), "OnDestroy")] [HarmonyPostfix] private static void CleanupPlayerState(Player __instance) { if (!((Object)(object)__instance == (Object)null)) { EquipmentPatches.ForgetPlayer(__instance); PairedAttackRuntime.ForgetPlayer(__instance); WindstrideCompatibility.ForgetPlayer(__instance); BetterArcheryCompatibility.ForgetTrackingForExternalOwner(__instance); ForgetTrailCache(((Humanoid)__instance).m_visEquipment); } } [HarmonyPatch(typeof(VisEquipment), "SetWeaponTrails")] [HarmonyPostfix] private static void MirrorTrailStateToOffhand(VisEquipment __instance, bool enabled) { if ((Object)(object)__instance == (Object)null) { return; } int instanceID = ((Object)__instance).GetInstanceID(); GameObject leftItemInstance = __instance.m_leftItemInstance; if ((Object)(object)leftItemInstance == (Object)null) { OffhandTrailCache.Remove(instanceID); return; } if (!OffhandTrailCache.TryGetValue(instanceID, out var value) || value == null || value.Visual != leftItemInstance) { value = new TrailCacheEntry { Visual = leftItemInstance, Trails = leftItemInstance.GetComponentsInChildren<MeleeWeaponTrail>(true) }; OffhandTrailCache[instanceID] = value; } MeleeWeaponTrail[] trails = value.Trails; if (trails == null) { return; } foreach (MeleeWeaponTrail val in trails) { if ((Object)(object)val != (Object)null) { val.Emit = enabled; } } } private static void ForgetTrailCache(VisEquipment visuals) { if ((Object)(object)visuals != (Object)null) { OffhandTrailCache.Remove(((Object)visuals).GetInstanceID()); } } [HarmonyPatch(typeof(VisEquipment), "AttachItem")] [HarmonyPostfix] private static void OffsetSecondBackMelee(VisEquipment __instance, Transform joint, bool backAttach, GameObject __result) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) if (ModSettings.SeparateBackWeapons.Value && !((Object)(object)__instance == (Object)null) && !((Object)(object)joint == (Object)null) && !((Object)(object)__result == (Object)null) && backAttach && !((Object)(object)joint != (Object)(object)__instance.m_backMelee)) { Transform transform = __result.transform; if (!((Object)(object)transform.parent != (Object)(object)joint) && transform.GetSiblingIndex() > 0) { transform.localPosition = new Vector3(-0.003f, 0f, 0.003f); transform.localEulerAngles = new Vector3(0f, 80f, 0f); } } } } } namespace Balrond.DualMastery.Core { internal struct ResourceQuote { internal float Stamina { get; private set; } internal float Eitr { get; private set; } internal float Health { get; private set; } internal ResourceQuote(float stamina, float eitr, float health) { Stamina = Mathf.Max(0f, stamina); Eitr = Mathf.Max(0f, eitr); Health = Mathf.Max(0f, health); } internal ResourceQuote Scale(float factor) { return new ResourceQuote(Stamina * factor, Eitr * factor, Health * factor); } internal ResourceQuote Add(ResourceQuote other) { return new ResourceQuote(Stamina + other.Stamina, Eitr + other.Eitr, Health + other.Health); } } internal static class CombatPolicy { internal static float Mastery(Player player) { if (MasterySkillPatches.HasIdCollision) { return 0f; } return Mathf.Clamp01(MasterySkillPersistence.GetBackingLevel(player) / 100f); } internal static float DamageFactor(Player player, bool secondary) { float num = Mathf.Lerp(ModSettings.OffhandDamageAtZero.Value, ModSettings.OffhandDamageAtMax.Value, Mastery(player)); float num2 = (secondary ? Mathf.Max(0f, ModSettings.SecondaryDamageMultiplier.Value) : 1f); float num3 = Mathf.Max(0f, ModSettings.PairedDamageScale.Value); return Mathf.Max(0f, num * num2 * num3); } internal static float ForceFactor(Player player) { return Mathf.Max(0f, Mathf.Lerp(ModSettings.OffhandForceAtZero.Value, ModSettings.OffhandForceAtMax.Value, Mastery(player))); } internal static float ExtraResourceFactor(Player player, bool secondary) { float num = (secondary ? ModSettings.SecondaryExtraResourceRatio.Value : ModSettings.PrimaryExtraResourceRatio.Value); float num2 = Mathf.Clamp01(ModSettings.ResourceDiscountAtMaxMastery.Value) * Mastery(player); return Mathf.Max(0f, num * (1f - num2)); } internal static float MasteryXp(bool secondary) { if (MasterySkillPatches.HasIdCollision || !ModSettings.MasteryProgressionEnabled.Value) { return 0f; } float num = (secondary ? ModSettings.SecondaryMasteryXp.Value : ModSettings.PrimaryMasteryXp.Value); return Mathf.Max(0f, num * Mathf.Max(0f, ModSettings.MasteryXpMultiplier.Value)); } internal static float AnimationSpeed(bool secondary) { float num = (secondary ? ModSettings.SecondaryAnimationSpeed.Value : ModSettings.PrimaryAnimationSpeed.Value); return Mathf.Clamp(num, 0.05f, 5f); } } internal static class NetworkAuthority { internal static bool IsOwner(Player player) { if ((Object)(object)player == (Object)null) { return false; } ZNetView nview = ((Character)player).m_nview; if ((Object)(object)nview != (Object)null && nview.IsValid()) { return nview.IsOwner(); } return player == Player.m_localPlayer; } } internal static class OffhandAttackEngine { private static bool _costProbeWarningLogged; private static bool _executeWarningLogged; private static bool TryCreateCostProbe(Player player, ItemData weapon, Attack template, out Attack probe) { probe = null; if ((Object)(object)player == (Object)null || weapon == null || template == null) { return false; } try { Attack val = template.Clone(); val.m_character = (Humanoid)(object)player; val.m_weapon = weapon; val.m_attackDrawPercentage = 1f; probe = val; return true; } catch (Exception ex) { if (!_costProbeWarningLogged && Plugin.Log != null) { _costProbeWarningLogged = true; Plugin.Log.LogWarning((object)("Could not create off-hand resource probe; further identical warnings are suppressed: " + ex.Message)); } return false; } } internal static ResourceQuote Quote(Player player, ItemData weapon, Attack template) { if (!TryCreateCostProbe(player, weapon, template, out var probe)) { return default(ResourceQuote); } return new ResourceQuote(probe.GetAttackStamina(), probe.GetAttackEitr(), probe.GetAttackHealth()); } internal static bool Execute(PairedLoadout loadout, Attack source, bool secondary) { if ((Object)(object)loadout.Owner == (Object)null || !NetworkAuthority.IsOwner(loadout.Owner) || loadout.OffHand == null || source == null) { return false; } Attack val = PairedLoadout.SelectAttack(loadout.OffHand, secondary); if (val == null) { return false; } float num = Mathf.Clamp01(source.m_attackDrawPercentage); float num2 = CombatPolicy.DamageFactor(loadout.Owner, secondary); float num3 = CombatPolicy.ForceFactor(loadout.Owner); try { Attack val2 = val.Clone(); val2.m_damageMultiplier *= num2; val2.m_forceMultiplier *= num3; val2.m_staggerMultiplier *= num3; PairedAttackRuntime.NormalizeAttackTypeForPlayer(loadout.Owner, val2, secondary); val2.StartWithoutAnimation((Humanoid)(object)loadout.Owner, ((Character)loadout.Owner).m_body, ((Humanoid)loadout.Owner).m_visEquipment, loadout.OffHand, num); return true; } catch (Exception ex) { if (!_executeWarningLogged && Plugin.Log != null) { _executeWarningLogged = true; Plugin.Log.LogWarning((object)("Could not execute off-hand attack; further identical warnings are suppressed: " + ex.Message)); } return false; } } } internal enum PrimaryTriggerRole { OffhandFirst, MainSecond, Ignore } internal static class PairedAttackRuntime { private sealed class State { internal Attack Attack; internal bool Secondary; internal int PrimaryTriggerIndex; internal bool OffhandConsumed; internal bool MasteryAwarded; } private static readonly Dictionary<int, State> States = new Dictionary<int, State>(); private static readonly FieldInfo AttackTypeField = typeof(Attack).GetField("m_attackType", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); internal static void ForgetPlayer(Player player) { if ((Object)(object)player != (Object)null) { States.Remove(((Object)player).GetInstanceID()); } } internal static void Begin(Player player, Attack attack, bool secondary) { if (!((Object)(object)player == (Object)null) && attack != null) { int instanceID = ((Object)player).GetInstanceID(); if (!States.TryGetValue(instanceID, out var value)) { value = new State(); States[instanceID] = value; } value.Attack = attack; value.Secondary = secondary; value.PrimaryTriggerIndex = 0; value.OffhandConsumed = false; value.MasteryAwarded = false; NormalizePrimaryAttackType(player, attack, secondary); } } internal static PrimaryTriggerRole ConsumePrimaryTrigger(Player player, Attack attack) { State orCreateState = GetOrCreateState(player, attack, secondaryFallback: false); if (orCreateState == null || orCreateState.Secondary) { return PrimaryTriggerRole.Ignore; } switch (orCreateState.PrimaryTriggerIndex++) { case 0: orCreateState.OffhandConsumed = true; return PrimaryTriggerRole.OffhandFirst; case 1: return PrimaryTriggerRole.MainSecond; default: return PrimaryTriggerRole.Ignore; } } internal static bool ConsumeExternalSingleWindowPrimary(Player player, Attack attack) { State orCreateState = GetOrCreateState(player, attack, secondaryFallback: false); if (orCreateState == null || orCreateState.Secondary) { return false; } return orCreateState.PrimaryTriggerIndex++ == 0; } internal static bool TryConsumeSameWindowOffhand(Player player, Attack attack) { State orCreateState = GetOrCreateState(player, attack, secondaryFallback: false); if (orCreateState == null || orCreateState.OffhandConsumed) { return false; } orCreateState.OffhandConsumed = true; return true; } internal static bool TryConsumeSecondaryOffhand(Player player, Attack attack) { State orCreateState = GetOrCreateState(player, attack, secondaryFallback: true); if (orCreateState == null || !orCreateState.Secondary) { return false; } return TryConsumeSameWindowOffhand(player, attack); } internal static bool TryAwardMastery(Player player, Attack attack) { State orCreateState = GetOrCreateState(player, attack, secondaryFallback: false); if (orCreateState == null || orCreateState.MasteryAwarded) { return false; } orCreateState.MasteryAwarded = true; return true; } private static State GetOrCreateState(Player player, Attack attack, bool secondaryFallback) { if ((Object)(object)player == (Object)null || attack == null) { return null; } int instanceID = ((Object)player).GetInstanceID(); if (!States.TryGetValue(instanceID, out var value) || value == null || value.Attack != attack) { value = new State { Attack = attack, Secondary = secondaryFallback, PrimaryTriggerIndex = 0, OffhandConsumed = false, MasteryAwarded = false }; States[instanceID] = value; } return value; } internal static void NormalizePrimaryAttackType(Player player, Attack attack, bool secondary) { NormalizeAttackTypeForPlayer(player, attack, secondary); } internal static void NormalizeAttackTypeForPlayer(Player player, Attack attack, bool secondary) { if (!ExternalAnimationCompatibility.PreserveExternalPrimaryAttackSemantics(player)) { NormalizeAttackType(attack, secondary); } } internal static void NormalizeAttackType(Attack attack, bool secondary) { if (attack == null || AttackTypeField == null || !AttackTypeField.FieldType.IsEnum) { return; } try { string value = (secondary ? "Vertical" : "Horizontal"); object value2 = Enum.Parse(AttackTypeField.FieldType, value, ignoreCase: true); AttackTypeField.SetValue(attack, value2); } catch (Exception ex) { Plugin.Log.LogDebug((object)("Could not normalize paired attack type: " + ex.Message)); } } internal static bool TryFaceCursor(Player player, out Quaternion previousRotation) { //IL_0002: 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_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) previousRotation = Quaternion.identity; if ((Object)(object)player == (Object)null || player != Player.m_localPlayer || ExternalAnimationCompatibility.PreserveExternalPrimaryAttackSemantics(player)) { return false; } Vector3 val = Vector3.zero; if ((Object)(object)GameCamera.instance != (Object)null) { val = ((Component)GameCamera.instance).transform.forward; } val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.0001f) { return false; } ((Vector3)(ref val)).Normalize(); previousRotation = ((Component)player).transform.rotation; ((Component)player).transform.rotation = Quaternion.LookRotation(val, Vector3.up); return true; } internal static void RestoreFacing(Player player, bool changed, Quaternion previousRotation) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) if (changed && (Object)(object)player != (Object)null) { ((Component)player).transform.rotation = previousRotation; } } } internal readonly struct PairedLoadout { internal Player Owner { get; } internal ItemData MainHand { get; } internal ItemData OffHand { get; } private PairedLoadout(Player owner, ItemData mainHand, ItemData offHand) { Owner = owner; MainHand = mainHand; OffHand = offHand; } internal static bool TryGet(Player player, out PairedLoadout loadout) { loadout = default(PairedLoadout); if ((Object)(object)player == (Object)null || !ModSettings.Enabled.Value) { return false; } ItemData rightItem = ((Humanoid)player).m_rightItem; ItemData leftItem = ((Humanoid)player).m_leftItem; if (!CanPair(rightItem, leftItem)) { return false; } loadout = new PairedLoadout(player, rightItem, leftItem); return true; } internal static bool HasAnimationIntent(Player player) { if ((Object)(object)player == (Object)null || !ModSettings.Enabled.Value) { return false; } ItemData main = ((Humanoid)player).m_rightItem ?? ((Humanoid)player).m_hiddenRightItem; ItemData off = ((Humanoid)player).m_leftItem ?? ((Humanoid)player).m_hiddenLeftItem; return CanPair(main, off); } internal static bool CanPair(ItemData main, ItemData off) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) if (main == off || !IsSupportedWeapon(main) || !IsSupportedWeapon(off)) { return false; } return ModSettings.AllowMixedWeaponSkills.Value || main.m_shared.m_skillType == off.m_shared.m_skillType; } internal static bool IsSupportedWeapon(ItemData item) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Invalid comparison between Unknown and I4 //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Invalid comparison between Unknown and I4 if (item?.m_shared == null) { return false; } if (OdinHorseCompatibility.IsReservedPairWeapon(item)) { return false; } return (int)item.m_shared.m_itemType == 3 && (Object)(object)item.m_shared.m_buildPieces == (Object)null && item.m_shared.m_attack != null && (int)item.m_shared.m_skillType != 5; } internal static Attack SelectAttack(ItemData weapon, bool secondary) { if (weapon?.m_shared == null) { return null; } if (secondary && weapon.m_shared.m_secondaryAttack != null) { return weapon.m_shared.m_secondaryAttack; } return weapon.m_shared.m_attack; } } } namespace Balrond.DualMastery.Configuration { internal sealed class ConfigurationManagerAttributes { public int? Order = null; public bool? Browsable = null; public string Category = null; public string DispName = null; public bool? IsAdvanced = null; public bool? ReadOnly = null; public bool? HideDefaultButton = null; public bool? ShowRangeAsPercent = null; public object DefaultValue = null; } internal static class ModSettings { private static readonly ConfigSync Sync = new ConfigSync("balrond.astafaraios.BalrondDualMastery") { DisplayName = "BalrondDualMastery", CurrentVersion = "0.3.14", MinimumRequiredVersion = "0.3.14", ModRequired = true }; private static readonly MethodInfo VisEquipmentUpdateVisuals = typeof(VisEquipment).GetMethod("UpdateVisuals", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); internal static ConfigEntry<bool> LockConfiguration { get; private set; } internal static ConfigEntry<bool> Enabled { get; private set; } internal static ConfigEntry<bool> AllowMixedWeaponSkills { get; private set; } internal static ConfigEntry<bool> SeparateBackWeapons { get; private set; } internal static ConfigEntry<KeyboardShortcut> OffhandSwapShortcut { get; private set; } internal static ConfigEntry<float> OffhandDamageAtZero { get; private set; } internal static ConfigEntry<float> PairedDamageScale { get; private set; } internal static ConfigEntry<float> OffhandDamageAtMax { get; private set; } internal static ConfigEntry<float> SecondaryDamageMultiplier { get; private set; } internal static ConfigEntry<float> OffhandForceAtZero { get; private set; } internal static ConfigEntry<float> OffhandForceAtMax { get; private set; } internal static ConfigEntry<float> PrimaryExtraResourceRatio { get; private set; } internal static ConfigEntry<float> SecondaryExtraResourceRatio { get; private set; } internal static ConfigEntry<float> ResourceDiscountAtMaxMastery { get; private set; } internal static ConfigEntry<bool> MasteryProgressionEnabled { get; private set; } internal static ConfigEntry<float> PrimaryMasteryXp { get; private set; } internal static ConfigEntry<float> SecondaryMasteryXp { get; private set; } internal static ConfigEntry<float> MasteryXpMultiplier { get; private set; } internal static ConfigEntry<float> PrimaryAnimationSpeed { get; private set; } internal static ConfigEntry<float> SecondaryAnimationSpeed { get; private set; } internal static ConfigEntry<bool> ExtraPrimarySwingAudio { get; private set; } internal static void Bind(ConfigFile config) { //IL_00e3: Unknown result type (might be due to invalid IL or missing references) LockConfiguration = BindRaw(config, "1 - General", "Lock Configuration", defaultValue: true, "When enabled on a server, synchronized gameplay settings are controlled by the server.", -100); Sync.AddLockingConfigEntry<bool>(LockConfiguration); Enabled = BindSynced(config, "1 - General", "Enabled", defaultValue: true, "Enable paired one-handed weapon behavior.", -90); Enabled.SettingChanged += OnEnabledChanged; AllowMixedWeaponSkills = BindSynced(config, "2 - Equipment", "Allow Mixed Weapon Skills", defaultValue: true, "Allow the two equipped weapons to use different weapon skill categories.", -100); AllowMixedWeaponSkills.SettingChanged += OnEquipmentPolicyChanged; SeparateBackWeapons = BindLocal(config, "2 - Equipment", "Separate Back Weapons", defaultValue: true, "Offset the second holstered weapon so paired weapons do not overlap. Client-side visual setting.", -90); SeparateBackWeapons.SettingChanged += OnSeparateBackWeaponsChanged; OffhandSwapShortcut = BindLocal<KeyboardShortcut>(config, "2 - Equipment", "Replace Offhand Modifier", new KeyboardShortcut((KeyCode)308, Array.Empty<KeyCode>()), "Local modifier for replacing only the left-hand item. Without the modifier, selecting a supported one-handed weapon immediately makes it the main hand and moves the previous main weapon to the off-hand. Selecting an incompatible/2H/tool item follows normal vanilla equip behavior.", -80); OffhandDamageAtZero = BindSyncedRange(config, "3 - Combat", "Offhand Damage At Skill 0", 0.55f, 0f, 10f, "Off-hand damage multiplier at 0 Dual Mastery.", -100); OffhandDamageAtMax = BindSyncedRange(config, "3 - Combat", "Offhand Damage At Skill 100", 0.8f, 0f, 10f, "Off-hand damage multiplier at 100 Dual Mastery.", -90); PairedDamageScale = BindSyncedRange(config, "3 - Combat", "Dual Wield Damage Scale", 0.75f, 0f, 10f, "Global balance scale applied to the off-hand strike after mastery and secondary multipliers. 1.0 restores legacy 0.2.7 damage.", -80); SecondaryDamageMultiplier = BindSyncedRange(config, "3 - Combat", "Secondary Damage Multiplier", 1.08f, 0f, 10f, "Additional multiplier applied to the off-hand strike during a secondary attack.", -70); OffhandForceAtZero = BindSyncedRange(config, "3 - Combat", "Offhand Force At Skill 0", 0.7f, 0f, 10f, "Off-hand force/stagger multiplier at 0 Dual Mastery.", -60); OffhandForceAtMax = BindSyncedRange(config, "3 - Combat", "Offhand Force At Skill 100", 0.95f, 0f, 10f, "Off-hand force/stagger multiplier at 100 Dual Mastery.", -50); PrimaryExtraResourceRatio = BindSyncedRange(config, "3 - Combat", "Primary Extra Resource Ratio", 0.6f, 0f, 10f, "Fraction of the off-hand attack resource cost added to a primary paired attack.", -40); SecondaryExtraResourceRatio = BindSyncedRange(config, "3 - Combat", "Secondary Extra Resource Ratio", 0.75f, 0f, 10f, "Fraction of the off-hand attack resource cost added to a secondary paired attack.", -30); ResourceDiscountAtMaxMastery = BindSyncedRange(config, "3 - Combat", "Resource Discount At Skill 100", 0.2f, 0f, 1f, "Maximum reduction of the off-hand resource surcharge at 100 Dual Mastery.", -20, showRangeAsPercent: true); MasteryProgressionEnabled = BindSynced(config, "4 - Skill", "Mastery Progression Enabled", defaultValue: true, "Grant Dual Mastery XP after successful paired attacks. If SkillsReworked is installed it owns the live progression/death/load rules; otherwise Dual Mastery uses its native persistent backend. A local mirror is maintained in both modes for fallback/migration.", -100); PrimaryMasteryXp = BindSyncedRange(config, "4 - Skill", "Primary Attack XP", 0.12f, 0f, 100f, "RaiseSkill factor granted to the persistent Dual Mastery skill after a successful primary paired attack.", -90); SecondaryMasteryXp = BindSyncedRange(config, "4 - Skill", "Secondary Attack XP", 0.18f, 0f, 100f, "RaiseSkill factor granted to the persistent Dual Mastery skill after a successful secondary paired attack.", -80); MasteryXpMultiplier = BindSyncedRange(config, "4 - Skill", "Mastery XP Multiplier", 1f, 0f, 100f, "Global multiplier applied to the Dual Mastery RaiseSkill factor.", -70); PrimaryAnimationSpeed = BindSyncedRange(config, "5 - Animation", "Primary Animation Speed", 2f, 0.05f, 5f, "Playback multiplier for primary paired attacks. Primary clips ignore their legacy authored Speed=0.7 marker, so 2.0 means an actual 2x gameplay cadence. Mounted OdinHorse combat keeps OdinHorse's own cadence.", -100); SecondaryAnimationSpeed = BindSyncedRange(config, "5 - Animation", "Secondary Animation Speed", 1f, 0.05f, 5f, "Additional playback multiplier for secondary paired attacks. The authored SPEED AnimationEvent inside the active Dual Mastery clip is applied first; this setting is a server-controlled tuning multiplier on top of it. Mounted OdinHorse combat keeps OdinHorse's own cadence.", -90); ExtraPrimarySwingAudio = BindLocal(config, "5 - Animation", "Extra Primary Swing Audio", defaultValue: true, "Play the extra non-damaging swing sounds authored for the paired primary animations. This is client-side only. Disable it as a diagnostic/fallback if a third-party weapon uses unusually heavy custom start-effect audio prefabs.", -80); } private static void OnEnabledChanged(object sender, EventArgs args) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && Enabled != null) { EquipmentPatches.HandleEnabledChanged(localPlayer, Enabled.Value); } } private static void OnEquipmentPolicyChanged(object sender, EventArgs args) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { EquipmentPatches.SanitizeRuntimeState(localPlayer); } } private static void OnSeparateBackWeaponsChanged(object sender, EventArgs args) { Player localPlayer = Player.m_localPlayer; VisEquipment val = (((Object)(object)localPlayer != (Object)null) ? ((Humanoid)localPlayer).m_visEquipment : null); if ((Object)(object)val == (Object)null || VisEquipmentUpdateVisuals == null) { return; } try { VisEquipmentUpdateVisuals.Invoke(val, null); } catch (Exception ex) { if (Plugin.Log != null) { Plugin.Log.LogDebug((object)("Could not refresh back-weapon visuals after config change: " + ex.Message)); } } } private static ConfigEntry<T> BindRaw<T>(ConfigFile config, string section, string key, T defaultValue, string description, int order) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown return config.Bind<T>(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { Order = order } })); } private static ConfigEntry<T> BindSynced<T>(ConfigFile config, string section, string key, T defaultValue, string description, int order) { ConfigEntry<T> val = BindRaw(config, section, key, defaultValue, description, order); Sync.AddConfigEntry<T>(val).SynchronizedConfig = true; return val; } private static ConfigEntry<float> BindSyncedRange(ConfigFile config, string section, string key, float defaultValue, float min, float max, string description, int order, bool showRangeAsPercent = false) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown ConfigEntry<float> val = config.Bind<float>(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(min, max), new object[1] { new ConfigurationManagerAttributes { Order = order, ShowRangeAsPercent = (showRangeAsPercent ? new bool?(true) : ((bool?)null)) } })); Sync.AddConfigEntry<float>(val).SynchronizedConfig = true; return val; } private static ConfigEntry<T> BindLocal<T>(ConfigFile config, string section, string key, T defaultValue, string description, int order) { ConfigEntry<T> val = BindRaw(config, section, key, defaultValue, description, order); Sync.AddConfigEntry<T>(val).SynchronizedConfig = false; return val; } private static ConfigEntry<float> BindLocalRange(ConfigFile config, string section, string key, float defaultValue, float min, float max, string description, int order) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown ConfigEntry<float> val = config.Bind<float>(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(min, max), new object[1] { new ConfigurationManagerAttributes { Order = order } })); Sync.AddConfigEntry<float>(val).SynchronizedConfig = false; return val; } } } namespace Balrond.DualMastery.Compatibility { internal static class BetterArcheryCompatibility { private const