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.2.7
plugins/BalrondDualMastery.dll
Decompiled 2 days 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.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("1.0.5.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.5.0")] [module: UnverifiableCode] namespace Balrond.DualMastery { [BepInPlugin("balrond.astafaraios.BalrondDualMastery", "BalrondDualMastery", "0.2.7")] [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.2.7"; public static readonly JsonLoader jsonLoader = new JsonLoader(); private Harmony _harmony; internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; ModSettings.Bind(((BaseUnityPlugin)this).Config); jsonLoader.loadJson(); ContentPack.Load(); MasterySkill.Initialize(); _harmony = new Harmony("balrond.astafaraios.BalrondDualMastery"); _harmony.PatchAll(typeof(Plugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} {1} loaded.", "BalrondDualMastery", "0.2.7")); } 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) { if (string.IsNullOrWhiteSpace(value)) { return false; } string text = value.Trim().Replace("_", string.Empty).Replace("-", string.Empty) .Replace(" ", string.Empty); return text.Equals("DualMastery", StringComparison.OrdinalIgnoreCase); } } [HarmonyPatch] internal static class MasterySkillPatches { [HarmonyPatch(typeof(Skills), "IsSkillValid")] [HarmonyPostfix] private static void AcceptCustomSkill(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) { __result = true; } } [HarmonyPatch(typeof(Skills), "GetSkillDef")] [HarmonyPostfix] private static void SupplyDefinition(SkillType type, List<SkillDef> ___m_skills, 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) { SkillDef definition = MasterySkill.Definition; if (!___m_skills.Contains(definition)) { ___m_skills.Add(definition); } __result = definition; } } [HarmonyPatch(typeof(Skills), "CheatRaiseSkill")] [HarmonyPrefix] private static bool RaiseCustomSkill(Skills __instance, string name, float value, bool showMessage) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (!MasterySkill.MatchesCheatName(name)) { return true; } Skill skill = __instance.GetSkill(MasterySkill.Type); skill.m_level = Mathf.Clamp(skill.m_level + value, 0f, 100f); skill.m_accumulator = 0f; if (showMessage && (Object)(object)__instance.m_player != (Object)null) { ((Character)__instance.m_player).Message((MessageType)1, string.Format("{0}: {1:0}", "Dual Mastery", skill.m_level), 0, MasterySkill.Definition.m_icon, false); } return false; } [HarmonyPatch(typeof(Skills), "CheatResetSkill")] [HarmonyPrefix] private static bool ResetCustomSkill(Skills __instance, string name) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (!MasterySkill.MatchesCheatName(name)) { return true; } __instance.ResetSkill(MasterySkill.Type); return false; } } } namespace Balrond.DualMastery.Patches { [HarmonyPatch] internal static class CombatPatches { private struct StartQuote { internal bool Active; internal ResourceQuote Extra; } [HarmonyPatch(typeof(Humanoid), "StartAttack")] [HarmonyPrefix] private static bool CheckCombinedResources(Humanoid __instance, bool secondaryAttack, ref bool __result, out StartQuote __state) { __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; } Attack val2 = PairedLoadout.SelectAttack(loadout.MainHand, secondaryAttack); Attack val3 = PairedLoadout.SelectAttack(loadout.OffHand, secondaryAttack); if (val2 == null || val3 == null) { 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))) { __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) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (__result && __state.Active && !((Object)(object)val == (Object)null)) { 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); } } } [HarmonyPatch(typeof(Humanoid), "OnAttackTrigger")] [HarmonyPostfix] private static void AddIndependentOffhandStrike(Humanoid __instance) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (!((Object)(object)val == (Object)null) && NetworkAuthority.IsOwner(val) && PairedLoadout.TryGet(val, out var loadout)) { Attack currentAttack = ((Humanoid)val).m_currentAttack; if (currentAttack != null) { OffhandAttackEngine.Execute(loadout, currentAttack, ((Humanoid)val).m_currentAttackIsSecondary); } } } } [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 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 static int _equipDepth; private static int _handVisibilityDepth; private static bool _normalizing; [HarmonyPatch(typeof(Humanoid), "EquipItem")] [HarmonyPrefix] 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 || (Object)(object)val == (Object)null || !NetworkAuthority.IsOwner(val)) { return; } __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++; if (PairedLoadout.IsSupportedWeapon(item)) { ItemData val2 = ((Humanoid)val).m_rightItem ?? ((Humanoid)val).m_hiddenRightItem; if (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.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 void ConvertSuccessfulEquip(Player player, ItemData item, EquipCapture state) { if (((Humanoid)player).m_rightItem != item) { return; } ItemData previousMain = state.PreviousMain; if (!PairedLoadout.CanPair(previousMain, item)) { return; } _normalizing = true; try { ((Humanoid)player).m_hiddenRightItem = null; ((Humanoid)player).m_hiddenLeftItem = null; ((Humanoid)player).m_rightItem = previousMain; ((Humanoid)player).m_leftItem = item; previousMain.m_equipped = true; item.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; } } [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 && !((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) { 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), "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 && !((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 = false; offhand.m_equipped = false; 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 && !((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 = IsActiveHandReference(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; } 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 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)) { item.m_equipped = IsActiveHandReference(player, item); } } } [HarmonyPatch] internal static class VisualPatches { [HarmonyPatch(typeof(Player), "Awake")] [HarmonyPostfix] private static void InitializeVisualState(Player __instance) { if ((Object)(object)__instance != (Object)null) { AnimationControllerService.Refresh(__instance); } } [HarmonyPatch(typeof(Player), "Update")] [HarmonyPostfix] private static void RefreshVisualState(Player __instance) { AnimationControllerService.Refresh(__instance); AnimationPlaybackSpeed.Refresh(__instance); } [HarmonyPatch(typeof(Humanoid), "StartAttack")] [HarmonyPrefix] 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] private static void RefreshBeforeLocalAnimationTrigger(ZSyncAnimation __instance, string name) { RefreshBeforeTrigger(__instance, name); } [HarmonyPatch(typeof(ZSyncAnimation), "RPC_SetTrigger")] [HarmonyPrefix] 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(VisEquipment), "SetWeaponTrails")] [HarmonyPostfix] private static void MirrorTrailStateToOffhand(VisEquipment __instance, bool enabled) { GameObject val = (((Object)(object)__instance != (Object)null) ? __instance.m_leftItemInstance : null); if ((Object)(object)val == (Object)null) { return; } MeleeWeaponTrail[] componentsInChildren = val.GetComponentsInChildren<MeleeWeaponTrail>(true); MeleeWeaponTrail[] array = componentsInChildren; foreach (MeleeWeaponTrail val2 in array) { if ((Object)(object)val2 != (Object)null) { val2.Emit = enabled; } } } [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) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) return Mathf.Clamp01(((Character)player).GetSkillFactor(MasterySkill.Type)); } 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); return Mathf.Max(0f, num * num2); } 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 (!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; return (Object)(object)nview == (Object)null || !nview.IsValid() || nview.IsOwner(); } } internal static class OffhandAttackEngine { 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) { Plugin.Log.LogWarning((object)("Could not create off-hand resource probe: " + 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 void Execute(PairedLoadout loadout, Attack source, bool secondary) { //IL_0103: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)loadout.Owner == (Object)null || !NetworkAuthority.IsOwner(loadout.Owner) || loadout.OffHand == null || source == null) { return; } Attack val = PairedLoadout.SelectAttack(loadout.OffHand, secondary); if (val == null) { return; } 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; val2.StartWithoutAnimation((Humanoid)(object)loadout.Owner, ((Character)loadout.Owner).m_body, ((Humanoid)loadout.Owner).m_visEquipment, loadout.OffHand, num); float num4 = CombatPolicy.MasteryXp(secondary); if (num4 > 0f) { ((Character)loadout.Owner).RaiseSkill(MasterySkill.Type, num4); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not execute off-hand attack: " + ex.Message)); } } } 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_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Invalid comparison between Unknown and I4 //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Invalid comparison between Unknown and I4 if (item?.m_shared == null) { 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 static class ModSettings { private static readonly ConfigSync Sync = new ConfigSync("balrond.astafaraios.BalrondDualMastery") { DisplayName = "BalrondDualMastery", CurrentVersion = "0.2.7", MinimumRequiredVersion = "0.2.7", ModRequired = true }; 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<float> OffhandDamageAtZero { 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 void Bind(ConfigFile config) { LockConfiguration = config.Bind<bool>("1 - General", "Lock Configuration", true, "When enabled on a server, synchronized gameplay settings are controlled by the server."); Sync.AddLockingConfigEntry<bool>(LockConfiguration); Enabled = BindSynced(config, "1 - General", "Enabled", defaultValue: true, "Enable paired one-handed weapon behavior."); AllowMixedWeaponSkills = BindSynced(config, "2 - Equipment", "Allow Mixed Weapon Skills", defaultValue: true, "Allow the two equipped weapons to use different weapon skill categories."); 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."); OffhandDamageAtZero = BindSynced(config, "3 - Combat", "Offhand Damage At Skill 0", 0.55f, "Off-hand damage multiplier at 0 Dual Mastery."); OffhandDamageAtMax = BindSynced(config, "3 - Combat", "Offhand Damage At Skill 100", 0.8f, "Off-hand damage multiplier at 100 Dual Mastery."); SecondaryDamageMultiplier = BindSynced(config, "3 - Combat", "Secondary Damage Multiplier", 1.08f, "Additional multiplier applied to the off-hand strike during a secondary attack."); OffhandForceAtZero = BindSynced(config, "3 - Combat", "Offhand Force At Skill 0", 0.7f, "Off-hand force/stagger multiplier at 0 Dual Mastery."); OffhandForceAtMax = BindSynced(config, "3 - Combat", "Offhand Force At Skill 100", 0.95f, "Off-hand force/stagger multiplier at 100 Dual Mastery."); PrimaryExtraResourceRatio = BindSynced(config, "3 - Combat", "Primary Extra Resource Ratio", 0.6f, "Fraction of the off-hand attack resource cost added to a primary paired attack."); SecondaryExtraResourceRatio = BindSynced(config, "3 - Combat", "Secondary Extra Resource Ratio", 0.75f, "Fraction of the off-hand attack resource cost added to a secondary paired attack."); ResourceDiscountAtMaxMastery = BindSynced(config, "3 - Combat", "Resource Discount At Skill 100", 0.2f, "Maximum reduction of the off-hand resource surcharge at 100 Dual Mastery."); MasteryProgressionEnabled = BindSynced(config, "4 - Skill", "Mastery Progression Enabled", defaultValue: true, "Enable gaining Dual Mastery experience from paired attacks."); PrimaryMasteryXp = BindSynced(config, "4 - Skill", "Primary Attack XP", 0.12f, "Base Dual Mastery experience granted per primary paired attack trigger."); SecondaryMasteryXp = BindSynced(config, "4 - Skill", "Secondary Attack XP", 0.18f, "Base Dual Mastery experience granted per secondary paired attack trigger."); MasteryXpMultiplier = BindSynced(config, "4 - Skill", "Mastery XP Multiplier", 1f, "Global multiplier applied to Dual Mastery experience gain."); PrimaryAnimationSpeed = BindSynced(config, "5 - Animation", "Primary Animation Speed", 1f, "Playback speed multiplier for primary paired-weapon attack animations."); SecondaryAnimationSpeed = BindSynced(config, "5 - Animation", "Secondary Animation Speed", 1f, "Playback speed multiplier for secondary paired-weapon attack animations."); } private static ConfigEntry<T> BindSynced<T>(ConfigFile config, string section, string key, T defaultValue, string description) { ConfigEntry<T> val = config.Bind<T>(section, key, defaultValue, description); Sync.AddConfigEntry<T>(val).SynchronizedConfig = true; return val; } private static ConfigEntry<T> BindLocal<T>(ConfigFile config, string section, string key, T defaultValue, string description) { ConfigEntry<T> val = config.Bind<T>(section, key, defaultValue, description); Sync.AddConfigEntry<T>(val).SynchronizedConfig = false; return val; } } } namespace Balrond.DualMastery.Compatibility { internal static class BetterArcheryCompatibility { private const string BetterArcheryGuid = "ishid4.mods.betterarchery"; private const string OverlayTypeName = "BetterArchery.CrouchBowDrawOverlay"; private const string MainTypeName = "BetterArchery.BetterArchery"; private const string OverrideControllerFieldName = "_overrideController"; private const string EnabledConfigFieldName = "ConfigCrouchBowDrawEnabled"; private static bool _reflectionInitialized; private static bool _installed; private static bool _unsupported; private static bool _activationLogged; private static bool _deferredLogged; private static bool _mappingWarningLogged; private static FieldInfo _overrideControllerField; private static FieldInfo _enabledConfigField; private static AnimatorOverrideController _activeController; private static readonly Dictionary<AnimationClip, AnimationClip> BaselineOverrides = new Dictionary<AnimationClip, AnimationClip>(); private static bool _pairOverridesApplied; internal static bool TryHandleLocalPlayer(Player player, bool shouldUsePairSet) { EnsureReflection(); if (!_installed || (Object)(object)player == (Object)null) { return false; } bool flag = (Object)(object)player == (Object)(object)Player.m_localPlayer; if (!flag && (Object)(object)Player.m_localPlayer == (Object)null) { ZNetView nview = ((Character)player).m_nview; flag = (Object)(object)nview != (Object)null && nview.IsValid() && nview.IsOwner(); } if (!flag) { return false; } if (_unsupported) { return true; } AnimatorOverrideController val = null; try { object? value = _overrideControllerField.GetValue(null); val = (AnimatorOverrideController)((value is AnimatorOverrideController) ? value : null); } catch (Exception ex) { MarkUnsupported("Could not read BetterArchery animation controller: " + ex.Message); return true; } if ((Object)(object)val == (Object)null) { if (IsCrouchBowOverlayEnabled()) { if (!_deferredLogged) { Plugin.Log.LogDebug((object)"BetterArchery detected; waiting for its AnimatorOverrideController before applying Dual Mastery animation overrides."); _deferredLogged = true; } return true; } ReleaseControllerState(); return false; } _deferredLogged = false; if (_activeController != val) { RestoreCurrentController(); _activeController = val; CaptureBaselineOverrides(_activeController); } ApplyPairState(_activeController, shouldUsePairSet); if ((Object)(object)((Character)player).m_animator != (Object)null && (object)((Character)player).m_animator.runtimeAnimatorController != _activeController) { ((Character)player).m_animator.runtimeAnimatorController = (RuntimeAnimatorController)(object)_activeController; } if (!_activationLogged) { string text = "unknown"; try { text = Chainloader.PluginInfos["ishid4.mods.betterarchery"].Metadata.Version.ToString(); } catch { } Plugin.Log.LogInfo((object)("BetterArchery " + text + " compatibility active: sharing its AnimatorOverrideController instead of replacing it.")); _activationLogged = true; } return true; } private static void EnsureReflection() { if (_reflectionInitialized) { return; } _reflectionInitialized = true; if (!Chainloader.PluginInfos.ContainsKey("ishid4.mods.betterarchery")) { return; } _installed = true; try { Assembly assembly = ((object)Chainloader.PluginInfos["ishid4.mods.betterarchery"].Instance).GetType().Assembly; Type type = assembly.GetType("BetterArchery.CrouchBowDrawOverlay", throwOnError: false); Type type2 = assembly.GetType("BetterArchery.BetterArchery", throwOnError: false); if (type == null) { MarkUnsupported("BetterArchery type 'BetterArchery.CrouchBowDrawOverlay' was not found."); return; } _overrideControllerField = type.GetField("_overrideController", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (type2 != null) { _enabledConfigField = type2.GetField("ConfigCrouchBowDrawEnabled", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } if (_overrideControllerField == null) { MarkUnsupported("BetterArchery field 'BetterArchery.CrouchBowDrawOverlay._overrideController' was not found."); } } catch (Exception ex) { MarkUnsupported("BetterArchery compatibility initialization failed: " + ex.Message); } } private static bool IsCrouchBowOverlayEnabled() { if (_enabledConfigField == null) { return true; } try { object value = _enabledConfigField.GetValue(null); if (value == null) { return true; } PropertyInfo property = value.GetType().GetProperty("Value", BindingFlags.Instance | BindingFlags.Public); object obj = ((property != null) ? property.GetValue(value, null) : null); return !(obj is bool) || (bool)obj; } catch { return true; } } private static void CaptureBaselineOverrides(AnimatorOverrideController controller) { BaselineOverrides.Clear(); _pairOverridesApplied = false; if ((Object)(object)controller == (Object)null) { return; } List<KeyValuePair<AnimationClip, AnimationClip>> list = new List<KeyValuePair<AnimationClip, AnimationClip>>(controller.overridesCount); controller.GetOverrides(list); for (int i = 0; i < list.Count; i++) { AnimationClip key = list[i].Key; if ((Object)(object)key != (Object)null && AnimationControllerService.TryClassifyGameClip(((Object)key).name, out var _)) { BaselineOverrides[key] = list[i].Value; } } if (BaselineOverrides.Count == 0 && !_mappingWarningLogged) { Plugin.Log.LogWarning((object)"BetterArchery compatibility found its AnimatorOverrideController, but no Dual Mastery melee animation slots matched. Dual-wield gameplay will remain active, but local custom animations may be unavailable."); _mappingWarningLogged = true; } } private static void ApplyPairState(AnimatorOverrideController controller, bool shouldUsePairSet) { if ((Object)(object)controller == (Object)null || BaselineOverrides.Count == 0 || shouldUsePairSet == _pairOverridesApplied) { return; } List<KeyValuePair<AnimationClip, AnimationClip>> list = new List<KeyValuePair<AnimationClip, AnimationClip>>(controller.overridesCount); controller.GetOverrides(list); bool flag = false; for (int i = 0; i < list.Count; i++) { AnimationClip key = list[i].Key; if ((Object)(object)key == (Object)null || !BaselineOverrides.ContainsKey(key)) { continue; } AnimationClip val = BaselineOverrides[key]; if (shouldUsePairSet) { if (!AnimationControllerService.TryClassifyGameClip(((Object)key).name, out var slot) || !ContentPack.TryGetMotion(slot, out var clip) || (Object)(object)clip == (Object)null) { continue; } val = clip; } if (list[i].Value != val) { list[i] = new KeyValuePair<AnimationClip, AnimationClip>(key, val); flag = true; } } if (flag) { controller.ApplyOverrides((IList<KeyValuePair<AnimationClip, AnimationClip>>)list); } _pairOverridesApplied = shouldUsePairSet; } private static void RestoreCurrentController() { if ((Object)(object)_activeController == (Object)null || !_pairOverridesApplied || BaselineOverrides.Count == 0) { return; } try { List<KeyValuePair<AnimationClip, AnimationClip>> list = new List<KeyValuePair<AnimationClip, AnimationClip>>(_activeController.overridesCount); _activeController.GetOverrides(list); bool flag = false; for (int i = 0; i < list.Count; i++) { AnimationClip key = list[i].Key; if ((Object)(object)key != (Object)null && BaselineOverrides.TryGetValue(key, out var value) && list[i].Value != value) { list[i] = new KeyValuePair<AnimationClip, AnimationClip>(key, value); flag = true; } } if (flag) { _activeController.ApplyOverrides((IList<KeyValuePair<AnimationClip, AnimationClip>>)list); } } catch (Exception ex) { Plugin.Log.LogDebug((object)("Could not restore previous BetterArchery override baseline: " + ex.Message)); } } private static void ReleaseControllerState() { RestoreCurrentController(); _activeController = null; BaselineOverrides.Clear(); _pairOverridesApplied = false; } private static void MarkUnsupported(string reason) { _unsupported = true; Plugin.Log.LogWarning((object)("BetterArchery is installed, but safe animation compatibility could not be initialized. Dual Mastery will not replace the local player's animator controller to avoid T-pose/controller conflicts. Reason: " + reason)); } } } namespace Balrond.DualMastery.Assets { internal sealed class AnimatorLease : MonoBehaviour { internal RuntimeAnimatorController SourceController; internal AnimatorOverrideController SharedController; internal readonly Dictionary<AnimationClip, AnimationClip> BaselineOverrides = new Dictionary<AnimationClip, AnimationClip>(); internal bool PairOverridesApplied; } internal static class AnimationControllerService { internal static void Refresh(Player player) { if ((Object)(object)player == (Object)null || (Object)(object)((Character)player).m_animator == (Object)null) { return; } Animator animator = ((Character)player).m_animator; AnimatorLease animatorLease = ((Component)player).GetComponent<AnimatorLease>() ?? ((Component)player).gameObject.AddComponent<AnimatorLease>(); bool shouldUsePairSet = ContentPack.HasMotionSet && PairedAnimationState.Resolve(player); if (BetterArcheryCompatibility.TryHandleLocalPlayer(player, shouldUsePairSet)) { if ((Object)(object)animatorLease.SourceController != (Object)null && (Object)(object)animatorLease.SharedController != (Object)null && (object)animator.runtimeAnimatorController == animatorLease.SharedController) { animator.runtimeAnimatorController = animatorLease.SourceController; } ResetLeaseToCurrentController(animatorLease, animator.runtimeAnimatorController); return; } RuntimeAnimatorController runtimeAnimatorController = animator.runtimeAnimatorController; if ((Object)(object)runtimeAnimatorController == (Object)null) { return; } if ((object)runtimeAnimatorController != animatorLease.SharedController && runtimeAnimatorController != animatorLease.SourceController) { ResetLeaseToCurrentController(animatorLease, runtimeAnimatorController); } else if ((Object)(object)animatorLease.SourceController == (Object)null && (object)runtimeAnimatorController != animatorLease.SharedController) { ResetLeaseToCurrentController(animatorLease, runtimeAnimatorController); } if ((Object)(object)animatorLease.SourceController == (Object)null) { return; } if (!ContentPack.HasMotionSet) { if ((Object)(object)animatorLease.SharedController != (Object)null && (object)animator.runtimeAnimatorController == animatorLease.SharedController) { animator.runtimeAnimatorController = animatorLease.SourceController; } animatorLease.SharedController = null; animatorLease.BaselineOverrides.Clear(); animatorLease.PairOverridesApplied = false; return; } if ((Object)(object)animatorLease.SharedController == (Object)null) { animatorLease.SharedController = BuildSharedOverride(animatorLease.SourceController, animatorLease.BaselineOverrides); animatorLease.PairOverridesApplied = false; } if (!((Object)(object)animatorLease.SharedController == (Object)null)) { ApplyPairState(animatorLease, shouldUsePairSet); if ((object)animator.runtimeAnimatorController != animatorLease.SharedController) { animator.runtimeAnimatorController = (RuntimeAnimatorController)(object)animatorLease.SharedController; } } } private static void ResetLeaseToCurrentController(AnimatorLease lease, RuntimeAnimatorController source) { lease.SourceController = source; lease.SharedController = null; lease.BaselineOverrides.Clear(); lease.PairOverridesApplied = false; } private static AnimatorOverrideController BuildSharedOverride(RuntimeAnimatorController source, Dictionary<AnimationClip, AnimationClip> baselineOverrides) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown if ((Object)(object)source == (Object)null) { return null; } AnimatorOverrideController val = new AnimatorOverrideController(source); List<KeyValuePair<AnimationClip, AnimationClip>> list = new List<KeyValuePair<AnimationClip, AnimationClip>>(val.overridesCount); val.GetOverrides(list); baselineOverrides.Clear(); for (int i = 0; i < list.Count; i++) { AnimationClip key = list[i].Key; if ((Object)(object)key != (Object)null && TryClassifyGameClip(((Object)key).name, out var _)) { baselineOverrides[key] = list[i].Value; } } ((Object)val).name = "BalrondDualMastery_SharedController"; if (baselineOverrides.Count == 0) { Plugin.Log.LogWarning((object)("Dual Mastery shared controller was created, but no source animation clips matched the known attack/guard slots. Source controller: " + (((Object)(object)source != (Object)null) ? ((Object)source).name : "<null>") + ".")); } else { Plugin.Log.LogDebug((object)("Dual Mastery shared controller captured " + baselineOverrides.Count + " melee animation slots from source controller '" + (((Object)(object)source != (Object)null) ? ((Object)source).name : "<null>") + "'.")); } return val; } private static void ApplyPairState(AnimatorLease lease, bool shouldUsePairSet) { AnimatorOverrideController sharedController = lease.SharedController; if ((Object)(object)sharedController == (Object)null || lease.BaselineOverrides.Count == 0) { lease.PairOverridesApplied = false; } else { if (lease.PairOverridesApplied == shouldUsePairSet) { return; } List<KeyValuePair<AnimationClip, AnimationClip>> list = new List<KeyValuePair<AnimationClip, AnimationClip>>(sharedController.overridesCount); sharedController.GetOverrides(list); bool flag = false; for (int i = 0; i < list.Count; i++) { AnimationClip key = list[i].Key; if (!((Object)(object)key == (Object)null) && lease.BaselineOverrides.TryGetValue(key, out var value)) { AnimationClip val = value; if (shouldUsePairSet && TryClassifyGameClip(((Object)key).name, out var slot) && ContentPack.TryGetMotion(slot, out var clip) && (Object)(object)clip != (Object)null) { val = clip; } if (list[i].Value != val) { list[i] = new KeyValuePair<AnimationClip, AnimationClip>(key, val); flag = true; } } } if (flag) { sharedController.ApplyOverrides((IList<KeyValuePair<AnimationClip, AnimationClip>>)list); } lease.PairOverridesApplied = shouldUsePairSet; } } internal static bool TryClassifyGameClip(string clipName, out MotionSlot slot) { slot = MotionSlot.Guard; if (string.IsNullOrWhiteSpace(clipName)) { return false; } string text = clipName.Trim().ToLowerInvariant(); if (text == "fight idle" || text == "block idle") { slot = MotionSlot.Guard; return true; } if (text.Contains("jumpattack") || text.Contains("altattack") || text.Contains("secondary") || text.Contains("special") || text.Contains("attack-r4")) { slot = MotionSlot.Special; return true; } if (text == "attack3" || text.EndsWith("slash2", StringComparison.Ordinal) || text.Contains("combo 3")) { slot = MotionSlot.PrimaryThree; return true; } if (text == "attack2" || text.EndsWith("slash1", StringComparison.Ordinal) || text.Contains("combo 2")) { slot = MotionSlot.PrimaryTwo; return true; } if (text == "attack1" || text.EndsWith("slash0", StringComparison.Ordinal) || text == "axe_swing") { slot = MotionSlot.PrimaryOne; return true; } return false; } } internal sealed class AnimationSpeedLease : MonoBehaviour { private bool _active; private float _baseSpeed = 1f; private float _appliedSpeed = 1f; internal void Refresh(Player player) { if ((Object)(object)player == (Object)null || (Object)(object)((Character)player).m_animator == (Object)null) { return; } Animator animator = ((Character)player).m_animator; if (!PairedLoadout.TryGet(player, out var _) || ((Humanoid)player).m_currentAttack == null) { if (_active && Mathf.Approximately(animator.speed, _appliedSpeed)) { animator.speed = _baseSpeed; } _active = false; return; } float speed = animator.speed; if (!_active || !Mathf.Approximately(speed, _appliedSpeed)) { _baseSpeed = speed; } float num = CombatPolicy.AnimationSpeed(((Humanoid)player).m_currentAttackIsSecondary); _appliedSpeed = _baseSpeed * num; animator.speed = _appliedSpeed; _active = true; } private void OnDisable() { Animator val = (((Object)(object)((Component)this).GetComponent<Player>() != (Object)null) ? ((Character)((Component)this).GetComponent<Player>()).m_animator : null); if (_active && (Object)(object)val != (Object)null && Mathf.Approximately(val.speed, _appliedSpeed)) { val.speed = _baseSpeed; } _active = false; } } internal static class AnimationPlaybackSpeed { internal static void Refresh(Player player) { if (!((Object)(object)player == (Object)null)) { AnimationSpeedLease animationSpeedLease = ((Component)player).GetComponent<AnimationSpeedLease>(); if ((Object)(object)animationSpeedLease == (Object)null) { animationSpeedLease = ((Component)player).gameObject.AddComponent<AnimationSpeedLease>(); } animationSpeedLease.Refresh(player); } } } internal enum MotionSlot { Guard, PrimaryOne, PrimaryTwo, PrimaryThree, Special } internal static class ContentPack { private const string BundleResourceName = "BalrondDualMastery.Resources.balronddualmastery"; private const string IconResourceName = "BalrondDualMastery.Resources.dualwield.png"; private const string GuardAsset = "assets/custom/balronddualmastery/animation/bdm_pair_guard.anim"; private const string PrimaryOneAsset = "assets/custom/balronddualmastery/animation/bdm_pair_combo_a.anim"; private const string PrimaryTwoAsset = "assets/custom/balronddualmastery/animation/bdm_pair_combo_b.anim"; private const string PrimaryThreeAsset = "assets/custom/balronddualmastery/animation/bdm_pair_combo_c.anim"; private const string SpecialAsset = "assets/custom/balronddualmastery/animation/bdm_pair_power.anim"; private static readonly Dictionary<MotionSlot, AnimationClip> Motions = new Dictionary<MotionSlot, AnimationClip>(); private static Sprite _skillIcon; internal static Sprite SkillIcon => _skillIcon ?? FallbackIconFactory.Get(); internal static bool HasMotionSet => Motions.Count == 5; internal static void Load() { Motions.Clear(); _skillIcon = null; LoadEmbeddedMotionBundle(); LoadEmbeddedIcon(); } internal static bool TryGetMotion(MotionSlot slot, out AnimationClip clip) { return Motions.TryGetValue(slot, out clip); } private static void LoadEmbeddedMotionBundle() { byte[] array = ReadEmbeddedResource("BalrondDualMastery.Resources.balronddualmastery"); if (array == null || array.Length == 0) { Plugin.Log.LogError((object)"Embedded animation bundle is missing: BalrondDualMastery.Resources.balronddualmastery"); return; } AssetBundle val = null; try { val = AssetBundle.LoadFromMemory(array); if ((Object)(object)val == (Object)null) { Plugin.Log.LogError((object)"Unity could not open the embedded BalrondDualMastery animation bundle."); return; } LoadMotion(val, MotionSlot.Guard, "assets/custom/balronddualmastery/animation/bdm_pair_guard.anim"); LoadMotion(val, MotionSlot.PrimaryOne, "assets/custom/balronddualmastery/animation/bdm_pair_combo_a.anim"); LoadMotion(val, MotionSlot.PrimaryTwo, "assets/custom/balronddualmastery/animation/bdm_pair_combo_b.anim"); LoadMotion(val, MotionSlot.PrimaryThree, "assets/custom/balronddualmastery/animation/bdm_pair_combo_c.anim"); LoadMotion(val, MotionSlot.Special, "assets/custom/balronddualmastery/animation/bdm_pair_power.anim"); if (HasMotionSet) { Plugin.Log.LogInfo((object)"Loaded all 5 embedded Dual Mastery animation clips."); } else { Plugin.Log.LogError((object)("Embedded animation bundle is incomplete. Loaded " + Motions.Count + "/5 clips. Available clips: " + DescribeAvailableAnimationClips(val))); } } catch (Exception ex) { Motions.Clear(); Plugin.Log.LogError((object)("Embedded animation bundle load failed: " + ex.Message)); } finally { if ((Object)(object)val != (Object)null) { val.Unload(false); } } } private static void LoadMotion(AssetBundle bundle, MotionSlot slot, string assetPath) { AnimationClip val = bundle.LoadAsset<AnimationClip>(assetPath); if ((Object)(object)val == (Object)null) { string baseName = Path.GetFileNameWithoutExtension(assetPath); AnimationClip[] source = bundle.LoadAllAssets<AnimationClip>(); val = ((IEnumerable<AnimationClip>)source).FirstOrDefault((Func<AnimationClip, bool>)((AnimationClip candidate) => (Object)(object)candidate != (Object)null && string.Equals(((Object)candidate).name, baseName, StringComparison.OrdinalIgnoreCase))); } if ((Object)(object)val == (Object)null) { Plugin.Log.LogError((object)("Missing embedded AnimationClip for slot '" + slot.ToString() + "': " + assetPath)); return; } Motions[slot] = val; Plugin.Log.LogInfo((object)("Dual Mastery animation '" + slot.ToString() + "' -> '" + ((Object)val).name + "'.")); } private static string DescribeAvailableAnimationClips(AssetBundle bundle) { try { string[] array = (from clip in bundle.LoadAllAssets<AnimationClip>() where (Object)(object)clip != (Object)null select ((Object)clip).name into name where !string.IsNullOrWhiteSpace(name) select name).Distinct<string>(StringComparer.OrdinalIgnoreCase).OrderBy<string, string>((string name) => name, StringComparer.OrdinalIgnoreCase).ToArray(); return (array.Length == 0) ? "<none>" : string.Join(", ", array); } catch { return "<unable to enumerate>"; } } private static void LoadEmbeddedIcon() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) byte[] array = ReadEmbeddedResource("BalrondDualMastery.Resources.dualwield.png"); if (array == null || array.Length == 0) { Plugin.Log.LogError((object)"Embedded skill icon is missing: BalrondDualMastery.Resources.dualwield.png"); return; } Texture2D val = null; try { val = new Texture2D(2, 2, (TextureFormat)4, false); ((Object)val).name = "BalrondDualMastery_SkillIconTexture"; ((Texture)val).filterMode = (FilterMode)1; ((Texture)val).wrapMode = (TextureWrapMode)1; if (!LoadPngWithoutCompileTimeImageConversionReference(val, array)) { Object.Destroy((Object)(object)val); Plugin.Log.LogError((object)"Embedded dualwield.png could not be decoded by Unity ImageConversion."); } else { _skillIcon = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), (float)Mathf.Max(((Texture)val).width, ((Texture)val).height)); ((Object)_skillIcon).name = "BalrondDualMastery_SkillIcon"; Plugin.Log.LogInfo((object)"Loaded embedded Dual Mastery skill icon."); } } catch (Exception ex) { if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } Plugin.Log.LogError((object)("Embedded skill icon load failed: " + ex.Message)); } } private static bool LoadPngWithoutCompileTimeImageConversionReference(Texture2D texture, byte[] bytes) { try { Type type = Type.GetType("UnityEngine.ImageConversion, UnityEngine.ImageConversionModule", throwOnError: false); if (type == null) { Assembly assembly = Assembly.Load("UnityEngine.ImageConversionModule"); type = assembly.GetType("UnityEngine.ImageConversion", throwOnError: false); } if (type == null) { return false; } MethodInfo method = type.GetMethod("LoadImage", BindingFlags.Static | BindingFlags.Public, null, new Type[3] { typeof(Texture2D), typeof(byte[]), typeof(bool) }, null); if (method == null) { return false; } object obj = method.Invoke(null, new object[3] { texture, bytes, false }); return obj is bool && (bool)obj; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Unity ImageConversion reflection call failed: " + ex.Message)); return false; } } private static byte[] ReadEmbeddedResource(string resourceName) { Assembly assembly = typeof(Plugin).Assembly; using Stream stream = assembly.GetManifestResourceStream(resourceName); if (stream == null) { return null; } if (stream.Length > int.MaxValue) { return null; } byte[] array = new byte[(int)stream.Length]; int i; int num; for (i = 0; i < array.Length; i += num) { num = stream.Read(array, i, array.Length - i); if (num <= 0) { break; } } return (i == array.Length) ? array : null; } } internal static class FallbackIconFactory { private static Sprite _cached; internal static Sprite Get() { //IL_0022: 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: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_cached != (Object)null) { return _cached; } Texture2D val = new Texture2D(32, 32, (TextureFormat)4, false) { name = "BalrondDualMastery_DevelopmentFallback", filterMode = (FilterMode)0, wrapMode = (TextureWrapMode)1, hideFlags = (HideFlags)61 }; Color32[] array = (Color32[])(object)new Color32[1024]; for (int i = 0; i < 32; i++) { for (int j = 0; j < 32; j++) { bool flag = Math.Abs(j - i) <= 1; bool flag2 = Math.Abs(31 - j - i) <= 1; array[i * 32 + j] = ((flag || flag2) ? new Color32((byte)230, (byte)230, (byte)230, byte.MaxValue) : new Color32((byte)0, (byte)0, (byte)0, (byte)0)); } } val.SetPixels32(array); val.Apply(false, true); _cached = Sprite.Create(val, new Rect(0f, 0f, 32f, 32f), new Vector2(0.5f, 0.5f), 32f); ((Object)_cached).name = "BalrondDualMastery_DevelopmentFallback"; ((Object)_cached).hideFlags = (HideFlags)61; return _cached; } } internal static class PairedAnimationState { private const string ZdoKey = "BalrondDualMastery_PairedAnimation"; internal static bool Resolve(Player player) { if ((Object)(object)player == (Object)null) { return false; } bool flag = PairedLoadout.HasAnimationIntent(player); ZNetView nview = ((Character)player).m_nview; if ((Object)(object)nview == (Object)null || !nview.IsValid()) { return flag; } ZDO zDO = nview.GetZDO(); if (zDO == null) { return flag; } if (nview.IsOwner()) { if (zDO.GetBool("BalrondDualMastery_PairedAnimation", false) != flag) { zDO.Set("BalrondDualMastery_PairedAnimation", flag); } return flag; } return zDO.GetBool("BalrondDualMastery_PairedAnimation", flag); } } } namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ServerSync { [PublicAPI] internal abstract class OwnConfigEntryBase { public object? LocalBaseValue; public bool SynchronizedConfig = true; public abstract ConfigEntryBase BaseConfig { get; } } [PublicAPI] internal class SyncedConfigEntry<T>(ConfigEntry<T> sourceConfig) : OwnConfigEntryBase() { public readonly ConfigEntry<T> SourceConfig = sourceConfig; public override ConfigEntryBase BaseConfig => (ConfigEntryBase)(object)SourceConfig; public T Value { get { return SourceConfig.Value; } set { SourceConfig.Value = value; } } public void AssignLocalValue(T value) { if (LocalBaseValue == null) { Value = value; } else { LocalBaseValue = value; } } } internal abstract class CustomSyncedValueBase { public object? LocalBaseValue; public readonly string Identifier; public readonly Type Type; private object? boxedValue; protected bool localIsOwner; public readonly int Priority; public object? BoxedValue { get { return boxedValue; } set { boxedValue = value; this.ValueChanged?.Invoke(); } } public event Action? ValueChanged; protected CustomSyncedValueBase(ConfigSync configSync, string identifier, Type type, int priority) { Priority = priority; Identifier = identifier; Type = type; configSync.AddCustomValue(this); localIsOwner = configSync.IsSourceOfTruth; configSync.SourceOfTruthChanged += delegate(bool truth) { localIsOwner = truth; }; } } [PublicAPI] internal sealed class CustomSyncedValue<T> : CustomSyncedValueBase { public T Value { get { return (T)base.BoxedValue; } set { base.BoxedValue = value; } } public CustomSyncedValue(ConfigSync configSync, string identifier, T value = default(T), int priority = 0) : base(configSync, identifier, typeof(T), priority) { Value = value; } public void AssignLocalValue(T value) { if (localIsOwner) { Value = value; } else { LocalBaseValue = value; } } } internal class ConfigurationManagerAttributes { [UsedImplicitly] public bool? ReadOnly = false; } [PublicAPI] internal class ConfigSync { [HarmonyPatch(typeof(ZRpc), "HandlePackage")] private static class SnatchCurrentlyHandlingRPC { public static ZRpc? currentRpc; [HarmonyPrefix] private static void Prefix(ZRpc __instance) { currentRpc = __instance; } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class RegisterRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance) { isServer = __instance.IsServer(); foreach (ConfigSync configSync2 in configSyncs) { ZRoutedRpc.instance.Register<ZPackage>(configSync2.Name + " ConfigSync", (Action<long, ZPackage>)configSync2.RPC_FromOtherClientConfigSync); if (isServer) { configSync2.InitialSyncDone = true; Debug.Log((object)("Registered '" + configSync2.Name + " ConfigSync' RPC - waiting for incoming connections")); } } if (isServer) { ((MonoBehaviour)__instance).StartCoroutine(WatchAdminListChanges()); } static void SendAdmin(List<ZNetPeer> peers, bool isAdmin) { ZPackage package = ConfigsToPackage(null, null, new PackageEntry[1] { new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = isAdmin } }); ConfigSync configSync = configSyncs.First(); if (configSync != null) { ((MonoBehaviour)ZNet.instance).StartCoroutine(configSync.sendZPackage(peers, package)); } } static IEnumerator WatchAdminListChanges() { MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); List<string> CurrentList = new List<string>(adminList.GetList()); while (true) { yield return (object)new WaitForSeconds(30f); if (!adminList.GetList().SequenceEqual(CurrentList)) { CurrentList = new List<string>(adminList.GetList()); List<ZNetPeer> adminPeer = ZNet.instance.GetPeers().Where(delegate(ZNetPeer p) { string hostName = p.m_rpc.GetSocket().GetHostName(); return ((object)listContainsId == null) ? adminList.Contains(hostName) : ((bool)listContainsId.Invoke(ZNet.instance, new object[2] { adminList, hostName })); }).ToList(); List<ZNetPeer> nonAdminPeer = ZNet.instance.GetPeers().Except(adminPeer).ToList(); SendAdmin(nonAdminPeer, isAdmin: false); SendAdmin(adminPeer, isAdmin: true); } } } } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class RegisterClientRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance, ZNetPeer peer) { if (__instance.IsServer()) { return; } foreach (ConfigSync configSync in configSyncs) { peer.m_rpc.Register<ZPackage>(configSync.Name + " ConfigSync", (Action<ZRpc, ZPackage>)configSync.RPC_FromServerConfigSync); } } } private class ParsedConfigs { public readonly Dictionary<OwnConfigEntryBase, object?> configValues = new Dictionary<OwnConfigEntryBase, object>(); public readonly Dictionary<CustomSyncedValueBase, object?> customValues = new Dictionary<CustomSyncedValueBase, object>(); } [HarmonyPatch(typeof(ZNet), "Shutdown")] private class ResetConfigsOnShutdown { [HarmonyPostfix] private static void Postfix() { ProcessingServerUpdate = true; foreach (ConfigSync configSync in configSyncs) { configSync.resetConfigsFromServer(); configSync.IsSourceOfTruth = true; configSync.InitialSyncDone = false; } ProcessingServerUpdate = false; } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private class SendConfigsAfterLogin { private class BufferingSocket : ZPlayFabSocket, ISocket { public volatile bool finished = false; public volatile int versionMatchQueued = -1; public readonly List<ZPackage> Package = new List<ZPackage>(); public readonly ISocket Original; public BufferingSocket(ISocket original) { Original = original; ((ZPlayFabSocket)this)..ctor(); } public bool IsConnected() { return Original.IsConnected(); } public ZPackage Recv() { return Original.Recv(); } public int GetSendQueueSize() { return Original.GetSendQueueSize(); } public int GetCurrentSendRate() { return Original.GetCurrentSendRate(); } public bool IsHost() { return Original.IsHost(); } public void Dispose() { Original.Dispose(); } public bool GotNewData() { return Original.GotNewData(); } public void Close() { Original.Close(); } public string GetEndPointString() { return Original.GetEndPointString(); } public void GetAndResetStats(out int totalSent, out int totalRecv) { Original.GetAndResetStats(ref totalSent, ref totalRecv); } public void GetConnectionQuality(out float localQuality, out float remoteQuality, out int ping, out float outByteSec, out float inByteSec) { Original.GetConnectionQuality(ref localQuality, ref remoteQuality, ref ping, ref outByteSec, ref inByteSec); } public ISocket Accept() { return Original.Accept(); } public int GetHostPort() { return Original.GetHostPort(); } public bool Flush() { return Original.Flush(); } public string GetHostName() { return Original.GetHostName(); } public void VersionMatch() { if (finished) { Original.VersionMatch(); } else { versionMatchQueued = Package.Count; } } public void Send(ZPackage pkg) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown int pos = pkg.GetPos(); pkg.SetPos(0); int num = pkg.ReadInt(); if ((num == StringExtensionMethods.GetStableHashCode("PeerInfo") || num == StringExtensionMethods.GetStableHashCode("RoutedRPC") || num == StringExtensionMethods.GetStableHashCode("ZDOData")) && !finished) { ZPackage val = new ZPackage(pkg.GetArray()); val.SetPos(pos); Package.Add(val); } else { pkg.SetPos(pos); Original.Send(pkg); } } } [HarmonyPriority(800)] [HarmonyPrefix] private static void Prefix(ref Dictionary<Assembly, BufferingSocket>? __state, ZNet __instance, ZRpc rpc) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Invalid comparison between Unknown and I4 if (!__instance.IsServer()) { return; } BufferingSocket bufferingSocket = new BufferingSocket(rpc.GetSocket()); AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket); object? obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (val != null && (int)ZNet.m_onlineBackend > 0) { FieldInfo fieldInfo = AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket"); object? value = fieldInfo.GetValue(val); ZPlayFabSocket val2 = (ZPlayFabSocket)((value is ZPlayFabSocket) ? value : null); if (val2 != null) { typeof(ZPlayFabSocket).GetField("m_remotePlayerId").SetValue(bufferingSocket, val2.m_remotePlayerId); } fieldInfo.SetValue(val, bufferingSocket); } if (__state == null) { __state = new Dictionary<Assembly, BufferingSocket>(); } __state[Assembly.GetExecutingAssembly()] = bufferingSocket; } [HarmonyPostfix] private static void Postfix(Dictionary<Assembly, BufferingSocket> __state, ZNet __instance, ZRpc rpc) { ZNetPeer peer; if (__instance.IsServer()) { object obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); peer = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (peer == null) { SendBufferedData(); } else { ((MonoBehaviour)__instance).StartCoroutine(sendAsync()); } } void SendBufferedData() { if (rpc.GetSocket() is BufferingSocket bufferingSocket) { AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket.Original); object? obj2 = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj2 is ZNetPeer) ? obj2 : null); if (val != null) { AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket").SetValue(val, bufferingSocket.Original); } } BufferingSocket bufferingSocket2 = __state[Assembly.GetExecutingAssembly()]; bufferingSocket2.finished = true; for (int i = 0; i < bufferingSocket2.Package.Count; i++) { if (i == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } bufferingSocket2.Original.Send(bufferingSocket2.Package[i]); } if (bufferingSocket2.Package.Count == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } } IEnumerator sendAsync() { foreach (ConfigSync configSync in configSyncs) { List<PackageEntry> entries = new List<PackageEntry>(); if (configSync.CurrentVersion != null) { entries.Add(new PackageEntry { section = "Internal", key = "serverversion", type = typeof(string), value = configSync.CurrentVersion }); } MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); entries.Add(new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = (((object)listContainsId == null) ? ((object)adminList.Contains(rpc.GetSocket().GetHostName())) : listContainsId.Invoke(ZNet.instance, new object[2] { adminList, rpc.GetSocket().GetHostName() })) }); ZPackage package = ConfigsToPackage(configSync.allConfigs.Select((OwnConfigEntryBase c) => c.BaseConfig), configSync.allCustomValues, entries, partial: false); yield return ((MonoBehaviour)__instance).StartCoroutine(configSync.sendZPackage(new List<ZNetPeer> { peer }, package)); } SendBufferedData(); } } } private class PackageEntry { public string section = null; public string key = null; public Type type = null; public object? value; } [HarmonyPatch(typeof(ConfigEntryBase), "GetSerializedValue")] private static class PreventSavingServerInfo { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, ref string __result) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || isWritableConfig(ownConfigEntryBase)) { return true; } __result = TomlTypeConverter.ConvertToString(ownConfigEntryBase.LocalBaseValue, __instance.SettingType); return false; } } [HarmonyPatch(typeof(ConfigEntryBase), "SetSerializedValue")] private static class PreventConfigRereadChangingValues { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, string value) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || ownConfigEntryBase.LocalBaseValue == null) { return true; } try { ownConfigEntryBase.LocalBaseValue = TomlTypeConverter.ConvertToValue(value, __instance.SettingType); } catch (Exception ex) { Debug.LogWarning((object)$"Config value of setting \"{__instance.Definition}\" could not be parsed and will be ignored. Reason: {ex.Message}; Value: {value}"); } return false; } } private class InvalidDeserializationTypeException : Exception { public string expected = null; public string received = null; public string field = ""; } public static bool ProcessingServerUpdate; public readonly string Name; public string? DisplayName; public string? CurrentVersion; public string? MinimumRequiredVersion; public bool ModRequired = false; private bool? forceConfigLocking; private bool isSourceOfTruth = true; private static readonly HashSet<ConfigSync> configSyncs; private readonly HashSet<OwnConfigEntryBase> allConfigs = new HashSet<OwnConfigEntryBase>(); private HashSet<CustomSyncedValueBase> allCustomValues = new HashSet<CustomSyncedValueBase>(); private static bool isServer; private static bool lockExempt; private OwnConfigEntryBase? lockedConfig = null; private const byte PARTIAL_CONFIGS = 1; private const byte FRAGMENTED_CONFIG = 2; private const byte COMPRESSED_CONFIG = 4; private readonly Dictionary<string, SortedDictionary<int, byte[]>> configValueCache = new Dictionary<string, SortedDictionary<int, byte[]>>(); private readonly List<KeyValuePair<long, string>> cacheExpirations = new List<KeyValuePair<long, string>>(); private static long packageCounter; public bool IsLocked { get { bool? flag = forceConfigLocking; bool num; if (!flag.HasValue) { if (lockedConfig == null) { goto IL_0052; } num = ((IConvertible)lockedConfig.BaseConfig.BoxedValue).ToInt32(CultureInfo.InvariantCulture) != 0; } else { num = flag == true; } if (!num) { goto IL_0052; } int result = ((!lockExempt) ? 1 : 0); goto IL_0053; IL_0052: result = 0; goto IL_0053; IL_0053: return (byte)result != 0; } set { forceConfigLocking = value; } } public bool IsAdmin => lockExempt || isSourceOfTruth; public bool IsSourceOfTruth { get { return isSourceOfTruth; } private set { if (value != isSourceOfTruth) { isSourceOfTruth = value; this.SourceOfTruthChanged?.Invoke(value); } } } public bool InitialSyncDone { get; private set; } = false; public event Action<bool>? SourceOfTruthChanged; private event Action? lockedConfigChanged; static ConfigSync() { ProcessingServerUpdate = false; configSyncs = new HashSet<ConfigSync>(); lockExempt = false; packageCounter = 0L; RuntimeHelpers.RunClassConstructor(typeof(VersionCheck).TypeHandle); } public ConfigSync(string name) { Name = name; configSyncs.Add(this); new VersionCheck(this); } public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigEntry<T> configEntry) { OwnConfigEntryBase ownConfigEntryBase = configData((ConfigEntryBase)(object)configEntry); SyncedConfigEntry<T> syncedEntry = ownConfigEntryBase as SyncedConfigEntry<T>; if (syncedEntry == null) { syncedEntry = new SyncedConfigEntry<T>(configEntry); AccessTools.DeclaredField(typeof(ConfigDescription), "<Tags>k__BackingField").SetValue(((ConfigEntryBase)configEntry).Description, new object[1] { new ConfigurationManagerAttributes() }.Concat(((ConfigEntryBase)configEntry).Description.Tags ?? Array.Empty<object>()).Concat(new SyncedConfigEntry<T>[1] { syncedEntry }).ToArray()); configEntry.SettingChanged += delegate { if (!ProcessingServerUpdate && syncedEntry.SynchronizedConfig) { Broadcast(0L, (ConfigEntryBase)configEntry); } }; allConfigs.Add(syncedEntry); } return syncedEntry; } public SyncedConfigEntry<T> AddLockingConfigEntry<T>(ConfigEntry<T> lockingConfig) where T : IConvertible { if (lockedConfig != null) { throw new Exception("Cannot initialize locking ConfigEntry twice"); } lockedConfig = AddConfigEntry<T>(lockingConfig); lockingConfig.SettingChanged += delegate { this.lockedConfigChanged?.Invoke(); }; return (SyncedConfigEntry<T>)lockedConfig; } internal void AddCustomValue(CustomSyncedValueBase customValue) { if (allCustomValues.Select((CustomSyncedValueBase v) => v.Identifier).Concat(new string[1] { "serverversion" }).Contains(customValue.Identifier)) { throw new Exception("Cannot have multiple settings with the same name or with a reserved name (serverversion)"); } allCustomValues.Add(customValue); allCustomValues = new HashSet<CustomSyncedValueBase>(allCustomValues.OrderByDescending((CustomSyncedValueBase v) => v.Priority)); customValue.ValueChanged += delegate { if (!ProcessingServerUpdate) { Broadcast(0L, customValue); } }; } private void RPC_FromServerConfigSync(ZRpc rpc, ZPackage package) { lockedConfigChanged += serverLockedSettingChanged; IsSourceOfTruth = false; if (HandleConfigSyncRPC(0L, package, clientUpdate: false)) { InitialSyncDone = true; } } private void RPC_FromOtherClientConfigSync(long sender, ZPackage package) { HandleConfigSyncRPC(sender, package, clientUpdate: true); } private bool HandleConfigSyncRPC(long sender, ZPackage package, bool clientUpdate) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Expected O, but got Unknown //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Expected O, but got Unknown try { if (isServer && IsLocked) { ZRpc? currentRpc = SnatchCurrentlyHandlingRPC.currentRpc; object obj; if (currentRpc == null) { obj = null; } else { ISocket socket = currentRpc.GetSocket(); obj = ((socket != null) ? socket.GetHostName() : null); } string text = (string)obj; if (text != null) { MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList val = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); if (!(((object)methodInfo == null) ? val.Contains(text) : ((bool)methodInfo.Invoke(ZNet.instance, new object[2] { val, text })))) { return false; } } } cacheExpirations.RemoveAll(delegate(KeyValuePair<long, string> kv) { if (kv.Key < DateTimeOffset.Now.Ticks) { configValueCache.Remove(kv.Value); return true; } return false; }); byte b = package.ReadByte(); if ((b & 2) != 0) { long num = package.ReadLong(); string text2 = sender.ToString() + num; if (!configValueCache.TryGetValue(text2, out SortedDictionary<int, byte[]> value)) { value = new SortedDictionary<int, byte[]>(); configValueCache[text2] = value; cacheExpirations.Add(new KeyValuePair<long, string>(DateTimeOffset.Now.AddSeconds(60.0).Ticks, text2)); } int key = package.ReadInt(); int num2 = package.ReadInt(); value.Add(key, package.ReadByteArray()); if (value.Count < num2) { return false; } configValueCache.Remove(text2); package = new ZPackage(value.Values.SelectMany((byte[] a) => a).ToArray()); b = package.ReadByte(); } ProcessingServerUpdate = true; if ((b & 4) != 0) { byte[] buffer = package.ReadByteArray(); MemoryStream stream = new MemoryStream(buffer); MemoryStream memoryStream = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress)) { deflateStream.CopyTo(memoryStream); } package = new ZPackage(memoryStream.ToArray()); b = package.ReadByte(); } if ((b & 1) == 0) { resetConfigsFromServer(); } ParsedConfigs parsedConfigs = ReadConfigsFromPackage(package); ConfigFile val2 = null; bool saveOnConfigSet = false; foreach (KeyValuePair<OwnConfigEntryBase, object> configValue in parsedConfigs.configValues) { if (!isServer && configValue.Key.LocalBaseValue == null) { configValue.Key.LocalBaseValue = configValue.Key.BaseConfig.BoxedValue; } if (val2 == null) { val2 = configValue.Key.BaseConfig.ConfigFile; saveOnConfigSet = val2.SaveOnConfigSet; val2.SaveOnConfigSet = false; } configValue.Key.BaseConfig.BoxedValue = configValue.Value; } if (val2 != null) { val2.SaveOnConfigSet = saveOnConfigSet; val2.Save(); } foreach (KeyValuePair<CustomSyncedValueBase, object> customValue in parsedConfigs.customValues) { if (!isServer) { CustomSyncedValueBase key2 = customValue.Key; if (key2.LocalBaseValue == null) { key2.LocalBaseValue = customValue.Key.BoxedValue; } } customValue.Key.BoxedValue = customValue.Value; } Debug.Log((object)string.Format("Received {0} configs and {1} custom values from {2} for mod {3}", parsedConfigs.configValues.Count, parsedConfigs.customValues.Count, (isServer || clientUpdate) ? $"client {sender}" : "the server", DisplayName ?? Name)); if (!isServer) { serverLockedSettingChanged(); } return true; } finally { ProcessingServerUpdate = false; } } private ParsedConfigs ReadConfigsFromPackage(ZPackage package) { ParsedConfigs parsedConfigs = new ParsedConfigs(); Dictionary<string, OwnConfigEntryBase> dictionary = allConfigs.Where((OwnConfigEntryBase c) => c.SynchronizedConfig).ToDictionary((OwnConfigEntryBase c) => c.BaseConfig.Definition.Section + "_" + c.BaseConfig.Definition.Key, (OwnConfigEntryBase c) => c); Dictionary<string, CustomSyncedValueBase> dictionary2 = allCustomValues.ToDictionary((CustomSyncedValueBase c) => c.Identifier, (CustomSyncedValueBase c) => c); int num = package.ReadInt(); for (int num2 = 0; num2 < num; num2++) { string text = package.ReadString(); string text2 = package.ReadString(); string text3 = package.ReadString(); Type type = Type.GetType(text3); if (text3 == "" || type != null) { object obj; try { obj = ((text3 == "") ? null : ReadValueWithTypeFromZPackage(package, type)); } catch (InvalidDeserializationTypeException ex) { Debug.LogWarning((object)("Got unexpected struct internal type " + ex.received + " for field " + ex.field + " struct " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + ex.expected)); continue; } OwnConfigEntryBase value2; if (text == "Internal") { CustomSyncedValueBase value; if (text2 == "serverversion") { if (obj?.ToString(