Decompiled source of HarnessForceJade v1.1.1

HarnessForceJade.dll

Decompiled 4 hours ago
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: AssemblyFileVersion("1.1.1.0")]
[assembly: CompilationRelaxations(8)]
[assembly: AssemblyProduct("HarnessForceJade")]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyTitle("HarnessForceJade")]
[assembly: AssemblyVersion("1.1.1.0")]
namespace HarnessForceJade;

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

	public const string PLUGIN_NAME = "Harness Force Jade";

	public const string PLUGIN_VERSION = "1.1.1";
}
[BepInPlugin("supidowagon.HarnessForceJade", "Harness Force Jade", "1.1.1")]
public class Plugin : BaseUnityPlugin
{
	[HarmonyPatch]
	private static class Patch_PlayerParryState_AccurateParry
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			EnsureLookups();
			Type stateType = AccessTools.TypeByName("PlayerParryState");
			if (stateType == null)
			{
				Logger.LogError((object)"Could not find PlayerParryState; the jade keeps its stock behaviour.");
				yield break;
			}
			MethodBase target = AccessTools.Method(stateType, "AccurateParry", (Type[])null, (Type[])null);
			if (target == null)
			{
				Logger.LogError((object)"Could not find PlayerParryState.AccurateParry; the jade keeps its stock behaviour.");
			}
			else
			{
				yield return target;
			}
		}

		[HarmonyPrefix]
		private static void Prefix()
		{
			GrantFullCharge();
		}
	}

	[HarmonyPatch]
	private static class Patch_Player_isReleasingChargeAttack
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			Type playerType = AccessTools.TypeByName("Player");
			if (!(playerType == null))
			{
				MethodBase target = AccessTools.Method(playerType, "get_isReleasingChargeAttack", (Type[])null, (Type[])null);
				if (target == null)
				{
					Logger.LogError((object)"Could not find Player.isReleasingChargeAttack; a banked charge would fire on its own straight out of the parry. Disable this mod.");
				}
				else
				{
					yield return target;
				}
			}
		}

		[HarmonyPrefix]
		private static bool Prefix(object __instance, ref bool __result)
		{
			return HoldBankedCharge(__instance, ref __result);
		}
	}

	private const float FullChargeTimerBump = 999f;

	internal static ManualLogSource Logger;

	internal static ConfigEntry<bool> Enabled;

	internal static ConfigEntry<bool> RequireJadeEquipped;

	internal static ConfigEntry<bool> FireOnAttackPress;

	internal static ConfigEntry<float> BankSeconds;

	internal static ConfigEntry<bool> LogChargeBank;

	private static bool banked;

	private static float bankedAt;

	private static bool lookupTried;

	private static MethodInfo playerInstanceGetter;

	private static FieldInfo isChargedField;

	private static FieldInfo chargedTimerField;

	private static MethodInfo playChargedEffect;

	private static MethodInfo chargingReleasedOrCanceled;

	private static PropertyInfo mainAbilitiesProp;

	private static FieldInfo jadeField;

	private static PropertyInfo abilityIsActivatedProp;

	private static PropertyInfo actionsProp;

	private static FieldInfo attackActionField;

	private static PropertyInfo isPressedProp;

	private static PropertyInfo wasPressedProp;

	private static bool inputLookupTried;

	private void Awake()
	{
		Logger = ((BaseUnityPlugin)this).Logger;
		Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Turn off to restore the jade's stock behaviour without removing the mod.");
		RequireJadeEquipped = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "RequireJadeEquipped", true, "Only bank a charge when the jade is actually equipped, so unequipping it gives you vanilla. Turn off to get the effect for free, without spending the slot.");
		FireOnAttackPress = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "FireOnAttackPress", true, "When on, the banked charge comes out the moment you press attack. Turn off and pressing attack does a normal swing instead, with the charged attack coming out when you release - the way charging normally works.");
		BankSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("General", "BankSeconds", 0f, "How long a banked charge waits for you, in seconds. 0 or less means it waits until you use it. When it does expire the charge is dropped rather than fired.");
		LogChargeBank = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "LogChargeBank", false, "Log every charge banked and released. Off by default because it is noisy; turn it on only when investigating.");
		Logger.LogInfo((object)"Plugin supidowagon.HarnessForceJade is loaded!");
		Harmony val = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), "supidowagon.HarnessForceJade");
		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.");
		}
	}

	private static MethodInfo FindMethod(Type type, string name)
	{
		Type type2 = type;
		while (type2 != null)
		{
			try
			{
				MethodInfo method = type2.GetMethod(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				if (method != null)
				{
					return method;
				}
			}
			catch (AmbiguousMatchException)
			{
				return null;
			}
			type2 = type2.BaseType;
		}
		return null;
	}

	private static FieldInfo FindField(Type type, string name)
	{
		Type type2 = type;
		while (type2 != null)
		{
			FieldInfo field = type2.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (field != null)
			{
				return field;
			}
			type2 = type2.BaseType;
		}
		return null;
	}

	private static PropertyInfo FindProperty(Type type, string name)
	{
		Type type2 = type;
		while (type2 != null)
		{
			try
			{
				PropertyInfo property = type2.GetProperty(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				if (property != null)
				{
					return property;
				}
			}
			catch (AmbiguousMatchException)
			{
				return null;
			}
			type2 = type2.BaseType;
		}
		return null;
	}

	private static void EnsureLookups()
	{
		if (lookupTried)
		{
			return;
		}
		lookupTried = true;
		Type type = AccessTools.TypeByName("Player");
		if (type == null)
		{
			Logger.LogError((object)"Could not find the Player type; the jade keeps its stock behaviour.");
			return;
		}
		playerInstanceGetter = FindMethod(type, "get_i");
		isChargedField = FindField(type, "isCharged");
		chargedTimerField = FindField(type, "attackChargedTimer");
		playChargedEffect = FindMethod(type, "PlayChargedEffect");
		chargingReleasedOrCanceled = FindMethod(type, "ChargingReleasedOrCanceled");
		mainAbilitiesProp = FindProperty(type, "mainAbilities");
		actionsProp = FindProperty(type, "actions");
		Type type2 = AccessTools.TypeByName("PlayerMainAbilityCollection");
		if (type2 != null)
		{
			jadeField = FindField(type2, "AttackChargedWhenParryJade");
		}
		Type type3 = AccessTools.TypeByName("PlayerAbilityData");
		if (type3 != null)
		{
			abilityIsActivatedProp = FindProperty(type3, "IsActivated");
		}
		Type type4 = AccessTools.TypeByName("PlayerGamePlayActionSet");
		if (type4 != null)
		{
			attackActionField = FindField(type4, "Attack");
		}
		List<string> list = new List<string>();
		if (playerInstanceGetter == null)
		{
			list.Add("Player.i");
		}
		if (isChargedField == null)
		{
			list.Add("Player.isCharged");
		}
		if (chargedTimerField == null)
		{
			list.Add("Player.attackChargedTimer");
		}
		if (actionsProp == null)
		{
			list.Add("Player.actions");
		}
		if (attackActionField == null)
		{
			list.Add("PlayerGamePlayActionSet.Attack");
		}
		if (mainAbilitiesProp == null)
		{
			list.Add("Player.mainAbilities");
		}
		if (jadeField == null)
		{
			list.Add("PlayerMainAbilityCollection.AttackChargedWhenParryJade");
		}
		if (abilityIsActivatedProp == null)
		{
			list.Add("PlayerAbilityData.IsActivated");
		}
		if (list.Count > 0)
		{
			Logger.LogError((object)("Could not resolve " + string.Join(", ", list.ToArray()) + "; the jade keeps its stock behaviour. This usually means a game update renamed something - disable this mod until it is rebuilt."));
		}
		if (playChargedEffect == null)
		{
			Logger.LogWarning((object)"Could not find Player.PlayChargedEffect; a banked charge will work but will not show the charged glow.");
		}
		if (chargingReleasedOrCanceled == null)
		{
			Logger.LogWarning((object)"Could not find Player.ChargingReleasedOrCanceled; BankSeconds cannot drop an expired charge and will be ignored.");
		}
	}

	private static object PlayerInstance()
	{
		EnsureLookups();
		if (playerInstanceGetter == null)
		{
			return null;
		}
		try
		{
			return playerInstanceGetter.Invoke(null, null);
		}
		catch
		{
			return null;
		}
	}

	private static object AttackAction(object player)
	{
		if (player == null || actionsProp == null || attackActionField == null)
		{
			return null;
		}
		try
		{
			object value = actionsProp.GetValue(player, null);
			return (value == null) ? null : attackActionField.GetValue(value);
		}
		catch
		{
			return null;
		}
	}

	private static bool ReadInputFlag(object player, bool wasPressed)
	{
		object obj = AttackAction(player);
		if (obj == null)
		{
			return false;
		}
		if (!inputLookupTried)
		{
			inputLookupTried = true;
			isPressedProp = FindProperty(obj.GetType(), "IsPressed");
			wasPressedProp = FindProperty(obj.GetType(), "WasPressed");
			if (isPressedProp == null)
			{
				Logger.LogError((object)("Could not find IsPressed on " + obj.GetType().FullName + "; the jade keeps its stock behaviour."));
			}
		}
		PropertyInfo propertyInfo = (wasPressed ? wasPressedProp : isPressedProp);
		if (propertyInfo == null)
		{
			return false;
		}
		try
		{
			return (bool)propertyInfo.GetValue(obj, null);
		}
		catch
		{
			return false;
		}
	}

	private static bool AttackHeld(object player)
	{
		return ReadInputFlag(player, wasPressed: false);
	}

	private static bool AttackJustPressed(object player)
	{
		return ReadInputFlag(player, wasPressed: true);
	}

	private static bool JadeEquipped(object player)
	{
		if (player == null || mainAbilitiesProp == null || jadeField == null || abilityIsActivatedProp == null)
		{
			return false;
		}
		try
		{
			object value = mainAbilitiesProp.GetValue(player, null);
			if (value == null)
			{
				return false;
			}
			object value2 = jadeField.GetValue(value);
			if (value2 == null)
			{
				return false;
			}
			return (bool)abilityIsActivatedProp.GetValue(value2, null);
		}
		catch
		{
			return false;
		}
	}

	private static bool IsCharged(object player)
	{
		if (player == null || isChargedField == null)
		{
			return false;
		}
		try
		{
			return (bool)isChargedField.GetValue(player);
		}
		catch
		{
			return false;
		}
	}

	private static void CancelCharge(object player)
	{
		if (chargingReleasedOrCanceled == null)
		{
			return;
		}
		try
		{
			MethodInfo methodInfo = chargingReleasedOrCanceled;
			object[] parameters = new object[1];
			methodInfo.Invoke(player, parameters);
		}
		catch (Exception ex)
		{
			Logger.LogWarning((object)("Could not drop the expired charge: " + ex.Message));
		}
	}

	internal static void GrantFullCharge()
	{
		if (Enabled != null && !Enabled.Value)
		{
			return;
		}
		object obj = PlayerInstance();
		if (obj == null || isChargedField == null || chargedTimerField == null || ((RequireJadeEquipped == null || RequireJadeEquipped.Value) && !JadeEquipped(obj)) || IsCharged(obj))
		{
			return;
		}
		bool flag = AttackHeld(obj);
		try
		{
			isChargedField.SetValue(obj, true);
			chargedTimerField.SetValue(obj, (float)chargedTimerField.GetValue(obj) + 999f);
		}
		catch (Exception ex)
		{
			Logger.LogError((object)("Could not grant the charge: " + ex.Message));
			return;
		}
		if (playChargedEffect != null)
		{
			try
			{
				MethodInfo methodInfo = playChargedEffect;
				object[] parameters = new object[1];
				methodInfo.Invoke(obj, parameters);
			}
			catch (Exception ex2)
			{
				Logger.LogWarning((object)("Could not play the charged effect: " + ex2.Message));
			}
		}
		banked = !flag;
		bankedAt = Time.time;
		if (LogChargeBank != null && LogChargeBank.Value)
		{
			Logger.LogInfo((object)(flag ? "Perfect parry: full charge granted while attack was held (stock path)." : "Perfect parry: full charge banked, waiting for your next attack."));
		}
	}

	internal static bool HoldBankedCharge(object player, ref bool result)
	{
		if (!banked)
		{
			return true;
		}
		if (Enabled != null && !Enabled.Value)
		{
			banked = false;
			return true;
		}
		if (!IsCharged(player))
		{
			banked = false;
			return true;
		}
		float num = ((BankSeconds == null) ? 0f : BankSeconds.Value);
		if (num > 0f && Time.time - bankedAt > num)
		{
			banked = false;
			CancelCharge(player);
			if (LogChargeBank != null && LogChargeBank.Value)
			{
				Logger.LogInfo((object)("Banked charge expired after " + num + "s and was dropped."));
			}
			result = false;
			return false;
		}
		if (!AttackHeld(player) && !AttackJustPressed(player))
		{
			result = false;
			return false;
		}
		banked = false;
		if (FireOnAttackPress == null || FireOnAttackPress.Value)
		{
			if (LogChargeBank != null && LogChargeBank.Value)
			{
				Logger.LogInfo((object)"Banked charge released on attack press.");
			}
			result = true;
			return false;
		}
		if (LogChargeBank != null && LogChargeBank.Value)
		{
			Logger.LogInfo((object)"Banked charge handed back to the stock release rules.");
		}
		return true;
	}
}