Decompiled source of ErenshorSwingSync v1.1.1

ErenshorSwingSync.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("ErenshorSwingSync")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.1.0")]
[assembly: AssemblyInformationalVersion("1.1.1+87ef27191ae2bf2e2fa173e5fd4bc4fc83c4ff1c")]
[assembly: AssemblyProduct("ErenshorSwingSync")]
[assembly: AssemblyTitle("ErenshorSwingSync")]
[assembly: AssemblyVersion("1.1.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ErenshorSwingSync
{
	[BepInPlugin("com.blackhorse311.erenshor.swingsync", "Erenshor SwingSync", "1.1.1")]
	public class SwingSyncPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.blackhorse311.erenshor.swingsync";

		public const string PluginName = "Erenshor SwingSync";

		public const string PluginVersion = "1.1.1";

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<float> SwingDelaySeconds;

		internal static ConfigEntry<bool> NpcEnabled;

		internal static ConfigEntry<float> NpcSwingDelaySeconds;

		internal static bool Replaying;

		internal static Animator SuppressAnim;

		internal static FieldRef<PlayerCombat, Character> MyselfRef;

		private static Action<PlayerCombat, Character, int, bool> _performAttacks;

		private static Action<NPC, int, bool> _performMeleeHit;

		private static FieldRef<NPC, Stats> _npcStatsRef;

		private static FieldRef<Stats, float> _mhAtkDelayRef;

		private static FieldRef<Stats, float> _ohAtkDelayRef;

		internal static bool PlayerReady => _performAttacks != null;

		internal static bool NpcReady => _performMeleeHit != null;

		private void Awake()
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Expected O, but got Unknown
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Expected O, but got Unknown
			//IL_02c1: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Master toggle for player swings. When on, your melee swing animation starts immediately and the damage (numbers, combat log, sounds, target flinch) lands after the windup delay, so the hit connects when the blade does instead of half a second before it. Turn off to restore the vanilla 1999-authentic feel.");
			SwingDelaySeconds = ((BaseUnityPlugin)this).Config.Bind<float>("General", "SwingDelaySeconds", 0.45f, new ConfigDescription("Seconds between your swing starting and the damage landing. Tune until impacts line up with your weapon animation. Applies live.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1.5f), Array.Empty<object>()));
			NpcEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("NPCs", "Enabled", true, "Sync NPC and SimPlayer melee swings the same way: their damage lands when their animation connects instead of at the start of the windup. Attack round cadence (their DPS) is unchanged.");
			NpcSwingDelaySeconds = ((BaseUnityPlugin)this).Config.Bind<float>("NPCs", "SwingDelaySeconds", 0.45f, new ConfigDescription("Seconds between an NPC/SimPlayer swing starting and their damage landing. Applies live.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1.5f), Array.Empty<object>()));
			MethodInfo methodInfo = AccessTools.Method(typeof(PlayerCombat), "PerformAttacks", new Type[3]
			{
				typeof(Character),
				typeof(int),
				typeof(bool)
			}, (Type[])null);
			FieldInfo fieldInfo = AccessTools.Field(typeof(PlayerCombat), "Myself");
			if (methodInfo != null && fieldInfo != null)
			{
				_performAttacks = AccessTools.MethodDelegate<Action<PlayerCombat, Character, int, bool>>(methodInfo, (object)null, false);
				MyselfRef = AccessTools.FieldRefAccess<PlayerCombat, Character>(fieldInfo);
			}
			else
			{
				Log.LogError((object)($"Could not resolve PlayerCombat members (PerformAttacks found: {methodInfo != null}, " + $"Myself found: {fieldInfo != null}). Probably a game update - check for a mod update. " + "Player swing sync disabled, game untouched."));
			}
			MethodInfo methodInfo2 = AccessTools.Method(typeof(NPC), "PerformMeleeHit", new Type[2]
			{
				typeof(int),
				typeof(bool)
			}, (Type[])null);
			FieldInfo fieldInfo2 = AccessTools.Field(typeof(NPC), "MyStats");
			FieldInfo fieldInfo3 = AccessTools.Field(typeof(Stats), "ActualMHAtkDelay");
			FieldInfo fieldInfo4 = AccessTools.Field(typeof(Stats), "ActualOHAtkDelay");
			if (methodInfo2 != null && fieldInfo2 != null && fieldInfo3 != null && fieldInfo4 != null)
			{
				_performMeleeHit = AccessTools.MethodDelegate<Action<NPC, int, bool>>(methodInfo2, (object)null, false);
				_npcStatsRef = AccessTools.FieldRefAccess<NPC, Stats>(fieldInfo2);
				_mhAtkDelayRef = AccessTools.FieldRefAccess<Stats, float>(fieldInfo3);
				_ohAtkDelayRef = AccessTools.FieldRefAccess<Stats, float>(fieldInfo4);
			}
			else
			{
				Log.LogError((object)($"Could not resolve NPC/Stats members (PerformMeleeHit found: {methodInfo2 != null}, " + $"NPC.MyStats found: {fieldInfo2 != null}, ActualMHAtkDelay found: {fieldInfo3 != null}, " + $"ActualOHAtkDelay found: {fieldInfo4 != null}). " + "Probably a game update - check for a mod update. NPC swing sync disabled, game untouched."));
			}
			new Harmony("com.blackhorse311.erenshor.swingsync").PatchAll(typeof(SwingSyncPlugin).Assembly);
			Log.LogInfo((object)(string.Format("{0} {1} loaded. Player windup: {2:F2}s, ", "Erenshor SwingSync", "1.1.1", SwingDelaySeconds.Value) + $"NPC windup: {NpcSwingDelaySeconds.Value:F2}s"));
		}

		internal static void ScheduleImpact(PlayerCombat pc, Character target, int attackCount, bool isMainHand, Animator anim)
		{
			((MonoBehaviour)pc).StartCoroutine(Impact(pc, target, attackCount, isMainHand, anim));
		}

		private static IEnumerator Impact(PlayerCombat pc, Character target, int attackCount, bool isMainHand, Animator anim)
		{
			yield return (object)new WaitForSeconds(SwingDelaySeconds.Value);
			if ((Object)(object)pc == (Object)null || (Object)(object)target == (Object)null || !target.Alive)
			{
				yield break;
			}
			try
			{
				Replaying = true;
				SuppressAnim = anim;
				_performAttacks(pc, target, attackCount, isMainHand);
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"Delayed player attack resolution failed: {arg}");
			}
			finally
			{
				Replaying = false;
				SuppressAnim = null;
			}
		}

		internal static void ScheduleNpcImpact(NPC npc, int baseDamage, bool isOffhand)
		{
			Stats val = _npcStatsRef.Invoke(npc);
			if ((Object)(object)val != (Object)null)
			{
				if (isOffhand)
				{
					_ohAtkDelayRef.Invoke(val) = Mathf.Max(val.CurrentOHAtkDelay, 1f);
				}
				else
				{
					_mhAtkDelayRef.Invoke(val) = Mathf.Max(val.CurrentMHAtkDelay, 1f);
				}
			}
			((MonoBehaviour)npc).StartCoroutine(NpcImpact(npc, baseDamage, isOffhand));
		}

		private static IEnumerator NpcImpact(NPC npc, int baseDamage, bool isOffhand)
		{
			float delaySeconds = NpcSwingDelaySeconds.Value;
			yield return (object)new WaitForSeconds(delaySeconds);
			if ((Object)(object)npc == (Object)null)
			{
				yield break;
			}
			Stats val = _npcStatsRef.Invoke(npc);
			if ((Object)(object)val == (Object)null || (Object)(object)val.Myself == (Object)null || !val.Myself.Alive || (Object)(object)npc.CurrentAggroTarget == (Object)null || !npc.CurrentAggroTarget.Alive)
			{
				yield break;
			}
			float num = _mhAtkDelayRef.Invoke(val);
			float num2 = _ohAtkDelayRef.Invoke(val);
			try
			{
				Replaying = true;
				_performMeleeHit(npc, baseDamage, isOffhand);
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"Delayed NPC attack resolution failed: {arg}");
			}
			finally
			{
				Replaying = false;
			}
			float num3 = delaySeconds * 60f;
			if (!Mathf.Approximately(_mhAtkDelayRef.Invoke(val), num))
			{
				_mhAtkDelayRef.Invoke(val) = Mathf.Max(_mhAtkDelayRef.Invoke(val) - num3, 0f);
			}
			if (!Mathf.Approximately(_ohAtkDelayRef.Invoke(val), num2))
			{
				_ohAtkDelayRef.Invoke(val) = Mathf.Max(_ohAtkDelayRef.Invoke(val) - num3, 0f);
			}
		}
	}
	[HarmonyPatch(typeof(PlayerCombat), "PerformAttacks")]
	internal static class PlayerCombat_PerformAttacks_Patch
	{
		private static bool Prefix(PlayerCombat __instance, Character target, int attackCount, bool isMainHand)
		{
			if (!SwingSyncPlugin.PlayerReady || !SwingSyncPlugin.Enabled.Value || SwingSyncPlugin.Replaying)
			{
				return true;
			}
			try
			{
				int num;
				if (!isMainHand)
				{
					Inventory playerInv = GameData.PlayerInv;
					num = ((playerInv != null && playerInv.OH?.MyItem?.IsWand == true) ? 1 : 0);
				}
				else
				{
					Inventory playerInv2 = GameData.PlayerInv;
					num = ((playerInv2 != null && playerInv2.MH?.MyItem?.IsWand == true) ? 1 : 0);
				}
				int num2;
				if (!isMainHand)
				{
					Inventory playerInv3 = GameData.PlayerInv;
					num2 = ((playerInv3 != null && playerInv3.OH?.MyItem?.IsBow == true) ? 1 : 0);
				}
				else
				{
					Inventory playerInv4 = GameData.PlayerInv;
					num2 = ((playerInv4 != null && playerInv4.MH?.MyItem?.IsBow == true) ? 1 : 0);
				}
				bool flag = (byte)num2 != 0;
				if (((uint)num | (flag ? 1u : 0u)) != 0 || (Object)(object)target == (Object)null)
				{
					return true;
				}
				if ((Object)(object)__instance.MyMeleeRange == (Object)null || !__instance.MyMeleeRange.GetNPCsInRange().Contains(target))
				{
					return true;
				}
				Character val = SwingSyncPlugin.MyselfRef.Invoke(__instance);
				if ((Object)(object)val == (Object)null)
				{
					return true;
				}
				Animator myAnim = val.GetMyAnim();
				if ((Object)(object)myAnim == (Object)null)
				{
					return true;
				}
				myAnim.SetTrigger(isMainHand ? "MeleeSwing" : "DualWield");
				if (attackCount >= 1)
				{
					myAnim.SetBool(isMainHand ? "DoubleAttack" : "OHDoubleAttack", true);
				}
				if (attackCount >= 2)
				{
					myAnim.SetTrigger("MeleeSwing");
				}
				SwingSyncPlugin.ScheduleImpact(__instance, target, attackCount, isMainHand, myAnim);
				return false;
			}
			catch (Exception arg)
			{
				SwingSyncPlugin.Log.LogError((object)$"SwingSync player prefix failed, vanilla behavior this swing: {arg}");
				return true;
			}
		}
	}
	[HarmonyPatch(typeof(NPC), "PerformMeleeHit")]
	internal static class NPC_PerformMeleeHit_Patch
	{
		private static bool Prefix(NPC __instance, int baseDamage, bool isOffhand)
		{
			if (!SwingSyncPlugin.NpcReady || !SwingSyncPlugin.NpcEnabled.Value || SwingSyncPlugin.Replaying)
			{
				return true;
			}
			try
			{
				if ((Object)(object)__instance.CurrentAggroTarget == (Object)null)
				{
					return true;
				}
				SwingSyncPlugin.ScheduleNpcImpact(__instance, baseDamage, isOffhand);
				return false;
			}
			catch (Exception arg)
			{
				SwingSyncPlugin.Log.LogError((object)$"SwingSync NPC prefix failed, vanilla behavior this swing: {arg}");
				return true;
			}
		}
	}
	[HarmonyPatch(typeof(Animator), "SetTrigger", new Type[] { typeof(string) })]
	internal static class Animator_SetTrigger_Patch
	{
		private static bool Prefix(Animator __instance)
		{
			if (SwingSyncPlugin.Replaying)
			{
				return (Object)(object)__instance != (Object)(object)SwingSyncPlugin.SuppressAnim;
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(Animator), "SetBool", new Type[]
	{
		typeof(string),
		typeof(bool)
	})]
	internal static class Animator_SetBool_Patch
	{
		private static bool Prefix(Animator __instance)
		{
			if (SwingSyncPlugin.Replaying)
			{
				return (Object)(object)__instance != (Object)(object)SwingSyncPlugin.SuppressAnim;
			}
			return true;
		}
	}
}