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;
}
}