Decompiled source of PerfectParry v1.3.1

PerfectParry.dll

Decompiled 7 hours ago
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;

[assembly: AssemblyFileVersion("1.3.1.0")]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: CompilationRelaxations(8)]
[assembly: AssemblyTitle("PerfectParry")]
[assembly: AssemblyProduct("PerfectParry")]
[assembly: AssemblyCopyright("Perfect window rewrite and imperfect-parry damage by supidowagon. Original Perfect Parry mod by nozwock.")]
[assembly: AssemblyVersion("1.3.1.0")]
namespace PerfectParry;

public static class PluginInfo
{
	public const string PLUGIN_GUID = "supidowagon.PerfectParry";

	public const string PLUGIN_NAME = "Perfect Parry";

	public const string PLUGIN_VERSION = "1.3.1";
}
[BepInPlugin("supidowagon.PerfectParry", "Perfect Parry", "1.3.1")]
public class Plugin : BaseUnityPlugin
{
	[HarmonyPatch(typeof(PlayerParryState), "Parried")]
	private static class Patch_PlayerParryState_Parried
	{
		private static readonly FieldInfo SpamDatasField = AccessTools.Field(typeof(PlayerParryState), "spamDatas");

		private static readonly ConditionalWeakTable<object, float[]> Baselines = new ConditionalWeakTable<object, float[]>();

		private static string lastReported;

		private static void Prefix(PlayerParryState __instance)
		{
			if (SpamDatasField == null)
			{
				return;
			}
			float[] windows = SpamDatasField.GetValue(__instance) as float[];
			if (windows == null || windows.Length == 0)
			{
				return;
			}
			float[] value = Baselines.GetValue(__instance, (object _) => (float[])windows.Clone());
			if (value.Length != windows.Length)
			{
				return;
			}
			bool flag = Enabled == null || Enabled.Value;
			float num = WindowSeconds();
			for (int num2 = 0; num2 < windows.Length; num2++)
			{
				windows[num2] = ((!flag || value[num2] <= 0f) ? value[num2] : num);
			}
			if (Logger != null)
			{
				string text = (flag ? (Describe(value) + " -> " + Describe(windows) + "  (" + PerfectParryFrames.Value + " frames @ " + FramesPerSecond.Value + "fps)") : ("disabled, stock windows " + Describe(value)));
				if (text != lastReported)
				{
					lastReported = text;
					Logger.LogInfo((object)("Perfect parry window: " + text));
				}
			}
		}

		private static string Describe(float[] windows)
		{
			StringBuilder stringBuilder = new StringBuilder("[");
			for (int i = 0; i < windows.Length; i++)
			{
				if (i > 0)
				{
					stringBuilder.Append(", ");
				}
				stringBuilder.Append(windows[i].ToString("0.####"));
			}
			return stringBuilder.Append("]").ToString();
		}
	}

	[HarmonyPatch(typeof(PlayerParryState), "NonAccurateParry")]
	private static class Patch_PlayerParryState_NonAccurateParry
	{
		[HarmonyPrefix]
		private static void Prefix()
		{
			EnterNonAccurateParry();
		}

		[HarmonyPostfix]
		private static void Postfix()
		{
			LeaveNonAccurateParry();
		}
	}

	[HarmonyPatch(typeof(PlayerParryState), "AccurateParry")]
	private static class Patch_PlayerParryState_AccurateParry
	{
		[HarmonyPrefix]
		private static void Prefix()
		{
			LeaveNonAccurateParry();
		}
	}

	[HarmonyPatch(typeof(PlayerParryState), "TakeInternalInjury")]
	private static class Patch_PlayerParryState_TakeInternalInjury
	{
		[HarmonyPrefix]
		private static bool Prefix(PlayerParryState __instance, float oriDamage)
		{
			return ConvertImperfectParryDamage(__instance, oriDamage);
		}
	}

	private const float VanillaFrames = 8f;

	internal static ManualLogSource Logger;

	internal static ConfigEntry<bool> Enabled;

	internal static ConfigEntry<float> PerfectParryFrames;

	internal static ConfigEntry<float> FramesPerSecond;

	internal static ConfigEntry<bool> ImperfectParryTakesRealDamage;

	internal static ConfigEntry<float> ImperfectParryDamageScale;

	internal static ConfigEntry<bool> IgnoreChipDamageRatio;

	private static bool inNonAccurateParry;

	private static FieldInfo chipRatioField;

	private static FieldInfo statePlayerField;

	private static bool injuryLookupTried;

	private static FieldInfo healthField;

	private static MethodInfo receiveDotDamage;

	private static PropertyInfo statValueProp;

	private void Awake()
	{
		Logger = ((BaseUnityPlugin)this).Logger;
		Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Turn off to restore the stock windows exactly, without removing the mod.");
		PerfectParryFrames = ((BaseUnityPlugin)this).Config.Bind<float>("General", "PerfectParryFrames", 12f, "How long after pressing parry a hit still counts as a perfect parry, in frames. Vanilla is 8 (0.1333s). Fractions are allowed. Lower values make perfect parries harder than stock.");
		FramesPerSecond = ((BaseUnityPlugin)this).Config.Bind<float>("General", "FramesPerSecond", 60f, "Frames-per-second used to turn the setting above into seconds. The game's own check is time-based, so this is purely the unit for PerfectParryFrames.");
		ImperfectParryTakesRealDamage = ((BaseUnityPlugin)this).Config.Bind<bool>("Imperfect parry", "ImperfectParryTakesRealDamage", false, "Take real health damage on an imperfect parry instead of internal injury. Internal injury heals back by attacking; real health needs a potion, so this makes the game harder. The parry still happens - no knockback, no hurt animation, no invincibility frames - it just costs you health you cannot regenerate.");
		ImperfectParryDamageScale = ((BaseUnityPlugin)this).Config.Bind<float>("Imperfect parry", "ImperfectParryDamageScale", 1f, "Multiplies the damage an imperfect parry deals. 1 is the same number the game would have applied as internal injury. Only used when ImperfectParryTakesRealDamage is on.");
		IgnoreChipDamageRatio = ((BaseUnityPlugin)this).Config.Bind<bool>("Imperfect parry", "IgnoreChipDamageRatio", false, "Take the attack's full damage rather than the chip-damage slice of it. The game multiplies the incoming hit by ChipDamageRatio before applying it as internal injury; turning this on skips that, so an imperfect parry hurts like a clean hit. Only used when ImperfectParryTakesRealDamage is on.");
		Logger.LogInfo((object)"Plugin supidowagon.PerfectParry is loaded!");
		Harmony val = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), "supidowagon.PerfectParry");
		foreach (MethodBase patchedMethod in val.GetPatchedMethods())
		{
			Logger.LogInfo((object)("Patched method: " + patchedMethod.DeclaringType.FullName + "." + patchedMethod.Name));
		}
		if (val.GetPatchedMethods().Count() == 0)
		{
			Logger.LogError((object)"Failed to apply Harmony patches.");
		}
	}

	internal static float WindowSeconds()
	{
		float num = ((FramesPerSecond == null) ? 60f : FramesPerSecond.Value);
		if (num <= 0f)
		{
			num = 60f;
		}
		float num2 = ((PerfectParryFrames == null) ? 12f : PerfectParryFrames.Value);
		if (num2 < 0f)
		{
			num2 = 0f;
		}
		return num2 / num;
	}

	private static void EnsureInjuryLookups()
	{
		if (!injuryLookupTried)
		{
			injuryLookupTried = true;
			chipRatioField = AccessTools.Field(typeof(PlayerParryState), "ChipDamageRatio");
			statePlayerField = AccessTools.Field(typeof(PlayerParryState), "player");
			if (statePlayerField == null)
			{
				Logger.LogError((object)"Could not find PlayerBaseState.player; imperfect parries keep taking internal injury.");
			}
			if (chipRatioField == null)
			{
				Logger.LogWarning((object)"Could not find PlayerParryState.ChipDamageRatio; imperfect parry damage will use the attack's full value.");
			}
		}
	}

	private static float ChipRatio(PlayerParryState state)
	{
		if (chipRatioField == null)
		{
			return 1f;
		}
		try
		{
			object value = chipRatioField.GetValue(state);
			if (value == null)
			{
				return 1f;
			}
			if (statValueProp == null)
			{
				statValueProp = AccessTools.Property(value.GetType(), "Value");
				if (statValueProp == null)
				{
					return 1f;
				}
			}
			return (float)statValueProp.GetValue(value, null);
		}
		catch
		{
			return 1f;
		}
	}

	private static bool DealRealDamage(PlayerParryState state, float amount)
	{
		if (statePlayerField == null)
		{
			return false;
		}
		try
		{
			object value = statePlayerField.GetValue(state);
			if (value == null)
			{
				return false;
			}
			if (healthField == null)
			{
				healthField = AccessTools.Field(value.GetType(), "health");
				if (healthField == null)
				{
					Logger.LogError((object)"Could not find Player.health; imperfect parries keep taking internal injury.");
					return false;
				}
			}
			object value2 = healthField.GetValue(value);
			if (value2 == null)
			{
				return false;
			}
			if (receiveDotDamage == null)
			{
				receiveDotDamage = AccessTools.Method(value2.GetType(), "ReceiveDOT_Damage", (Type[])null, (Type[])null);
				if (receiveDotDamage == null)
				{
					Logger.LogError((object)"Could not find PlayerHealth.ReceiveDOT_Damage; imperfect parries keep taking internal injury.");
					return false;
				}
			}
			receiveDotDamage.Invoke(value2, new object[1] { amount });
			return true;
		}
		catch (Exception ex)
		{
			Logger.LogError((object)("Could not deal imperfect parry damage: " + ex.Message));
			return false;
		}
	}

	internal static bool ConvertImperfectParryDamage(PlayerParryState __instance, float oriDamage)
	{
		if (!inNonAccurateParry)
		{
			return true;
		}
		inNonAccurateParry = false;
		if (ImperfectParryTakesRealDamage == null || !ImperfectParryTakesRealDamage.Value)
		{
			return true;
		}
		EnsureInjuryLookups();
		float num = oriDamage;
		if (IgnoreChipDamageRatio == null || !IgnoreChipDamageRatio.Value)
		{
			num *= ChipRatio(__instance);
		}
		float num2 = ((ImperfectParryDamageScale == null) ? 1f : ImperfectParryDamageScale.Value);
		if (num2 < 0f)
		{
			num2 = 0f;
		}
		num *= num2;
		if (!DealRealDamage(__instance, num))
		{
			return true;
		}
		return false;
	}

	internal static void EnterNonAccurateParry()
	{
		inNonAccurateParry = true;
	}

	internal static void LeaveNonAccurateParry()
	{
		inNonAccurateParry = false;
	}
}