Decompiled source of ComboSpear v1.0.0

BepInEx\plugins\ComboSpear\ComboSpear.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
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(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ComboSpear")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("ComboSpear")]
[assembly: AssemblyTitle("ComboSpear")]
[assembly: AssemblyVersion("1.0.0.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 SpearCombo
{
	[BepInPlugin("andythedemon.combospear", "Combo Spear", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		internal readonly struct Setting<T>
		{
			private readonly T value;

			internal T Value => value;

			internal Setting(T v)
			{
				value = v;
			}
		}

		public const string Guid = "andythedemon.combospear";

		public const string PluginName = "Combo Spear";

		public const string Version = "1.0.0";

		internal const string ComboTrigger = "spearcombo";

		internal const string VanillaPoke = "spear_poke";

		internal const string ThrowTrigger = "spear_throw";

		internal const string GuardTrigger = "spearguard";

		internal const string IgnoredTrigger = "spear_dup_ignored";

		internal const float LungeHitSeconds = 0.667f;

		internal const float ThrowWindowSeconds = 1.6f;

		internal const string KnifeLunge = "knife-lunge";

		internal static ManualLogSource Log;

		internal static ConfigEntry<float> NormalHitDamage;

		internal static ConfigEntry<float> BlockHitDamage;

		internal static ConfigEntry<float> ThrowDamage;

		internal static readonly Setting<string>[] Hit = new Setting<string>[3]
		{
			new Setting<string>("knife_stab0"),
			new Setting<string>("knife_stab1"),
			new Setting<string>("knife_stab2")
		};

		internal static readonly Setting<float>[] HitSpeed = new Setting<float>[3]
		{
			new Setting<float>(1f),
			new Setting<float>(1f),
			new Setting<float>(1f)
		};

		internal static readonly Setting<Vector3>[] HitRotation = new Setting<Vector3>[3]
		{
			new Setting<Vector3>(Vector3.zero),
			new Setting<Vector3>(Vector3.zero),
			new Setting<Vector3>(Vector3.zero)
		};

		internal static readonly Setting<Vector3>[] HitOffset = new Setting<Vector3>[3]
		{
			new Setting<Vector3>(Vector3.zero),
			new Setting<Vector3>(Vector3.zero),
			new Setting<Vector3>(Vector3.zero)
		};

		internal static readonly Setting<float> LastHitDamage = new Setting<float>(2f);

		internal static readonly Setting<float> Range = new Setting<float>(3.2f);

		internal static readonly Setting<Vector3> ComboGrip = new Setting<Vector3>(new Vector3(-8f, 23f, 12f));

		internal static readonly Setting<float> ComboGripBlend = new Setting<float>(0.12f);

		internal static readonly Setting<bool> JumpBackThrow = new Setting<bool>(v: true);

		internal static readonly Setting<bool> DefenceCombo = new Setting<bool>(v: true);

		internal static readonly Setting<float> GuardReset = new Setting<float>(1f);

		internal static readonly Setting<float> GuardHold = new Setting<float>(0.3f);

		internal static readonly Setting<string>[] GuardClip = new Setting<string>[3]
		{
			new Setting<string>("SpearGuard1"),
			new Setting<string>("SpearGuard2"),
			new Setting<string>("SpearGuard3")
		};

		internal static readonly Setting<float>[] GuardDamage = new Setting<float>[3]
		{
			new Setting<float>(0.5f),
			new Setting<float>(0.7f),
			new Setting<float>(1f)
		};

		internal static readonly Setting<float> JumpBackHeight = new Setting<float>(1f);

		internal static readonly Setting<float> JumpBackDistance = new Setting<float>(2f);

		internal static readonly Setting<float> JumpBackDelay = new Setting<float>(0.2f);

		internal static readonly Setting<string> ThrowCustomClip = new Setting<string>("SpearThrowBack");

		internal static readonly Setting<string> ThrowCustomClipFree = new Setting<string>("SpearThrowBack_Free");

		internal static readonly Setting<string> ThrowReplacesClip = new Setting<string>("throw_spear");

		internal static readonly Setting<bool> Flip = new Setting<bool>(v: true);

		internal static readonly Setting<bool> Dump = new Setting<bool>(v: false);

		internal static readonly Setting<string> CustomBundle = new Setting<string>("spearcombo.bundle");

		internal static readonly Setting<string>[] HitCustomClip = new Setting<string>[3]
		{
			new Setting<string>("SpearJab1"),
			new Setting<string>("SpearJab2"),
			new Setting<string>("SpearLunge")
		};

		internal static readonly Setting<string>[] HitCustomClipFree = new Setting<string>[3]
		{
			new Setting<string>("SpearJab1_Free"),
			new Setting<string>("SpearJab2_Free"),
			new Setting<string>("SpearLunge_Free")
		};

		internal static readonly Setting<string>[] HitReplaceClip = new Setting<string>[3]
		{
			new Setting<string>("knife_slash0"),
			new Setting<string>("knife_slash1"),
			new Setting<string>("knife_slash2")
		};

		internal static string PluginFolder;

		private bool dumped;

		private void Awake()
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Expected O, but got Unknown
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Expected O, but got Unknown
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Expected O, but got Unknown
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Expected O, but got Unknown
			//IL_036e: Unknown result type (might be due to invalid IL or missing references)
			//IL_037b: Expected O, but got Unknown
			//IL_02e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f4: Expected O, but got Unknown
			//IL_01f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0202: Expected O, but got Unknown
			//IL_021b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0238: Unknown result type (might be due to invalid IL or missing references)
			//IL_0243: Expected O, but got Unknown
			//IL_0243: Expected O, but got Unknown
			//IL_046f: Unknown result type (might be due to invalid IL or missing references)
			//IL_048c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0497: Expected O, but got Unknown
			//IL_0497: Expected O, but got Unknown
			//IL_04b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_04ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_04d9: Expected O, but got Unknown
			//IL_04d9: Expected O, but got Unknown
			//IL_04f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0501: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			NormalHitDamage = ((BaseUnityPlugin)this).Config.Bind<float>("1 - Damage", "NormalHitsDamageMultiplier", 1f, new ConfigDescription("Damage of the three normal combo hits relative to vanilla. 1 = as designed, 0.5 = half, 2 = double.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>()));
			BlockHitDamage = ((BaseUnityPlugin)this).Config.Bind<float>("1 - Damage", "BlockHitsDamageMultiplier", 1f, new ConfigDescription("Damage of the three block-combo hits. Their designed values are 50% / 70% / 100% of a normal hit; this scales that.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>()));
			ThrowDamage = ((BaseUnityPlugin)this).Config.Bind<float>("1 - Damage", "SpearThrowDamageMultiplier", 0.5f, new ConfigDescription("Damage of the jump-back finishing throw relative to a normal spear throw. The plain middle-mouse throw is not affected.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>()));
			Log.LogInfo((object)$"Damage: normal hits x{NormalHitDamage.Value:0.##}, block hits x{BlockHitDamage.Value:0.##}, jump-back throw x{ThrowDamage.Value:0.##}.");
			PluginFolder = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location);
			Refs.Init();
			if (Chainloader.PluginInfos.ContainsKey("goldenrevolver.ProperSpears"))
			{
				Log.LogWarning((object)"Proper Spears is installed too - both change spears; disable one of them.");
			}
			Harmony val = new Harmony("andythedemon.combospear");
			val.PatchAll(typeof(Plugin).Assembly);
			try
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(Humanoid), "StartAttack", new Type[2]
				{
					typeof(Character),
					typeof(bool)
				}, (Type[])null);
				MethodInfo methodInfo2 = AccessTools.Method(typeof(Player), "InAttack", (Type[])null, (Type[])null);
				if (methodInfo != null && methodInfo2 != null && Refs.CurrentAttack != null && Refs.Zanim != null)
				{
					val.Patch((MethodBase)methodInfo2, new HarmonyMethod(AccessTools.Method(typeof(LungeCancel), "InAttackPrefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					val.Patch((MethodBase)methodInfo, new HarmonyMethod(AccessTools.Method(typeof(LungeCancel), "Prefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(LungeCancel), "Finalizer", (Type[])null, (Type[])null)), (HarmonyMethod)null);
				}
				else
				{
					Log.LogError((object)"StartAttack / Player.InAttack / m_currentAttack / m_zanim not found; cancelling the lunge is off.");
				}
			}
			catch (Exception ex)
			{
				Log.LogError((object)("Could not patch the lunge cancel (" + ex.GetType().Name + "); cancelling the lunge is off."));
			}
			try
			{
				MethodInfo methodInfo3 = AccessTools.Method(typeof(Humanoid), "StartAttack", new Type[2]
				{
					typeof(Character),
					typeof(bool)
				}, (Type[])null);
				if (methodInfo3 != null && Refs.CurrentAttack != null)
				{
					val.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(SoftThrow), "Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
				else
				{
					Log.LogError((object)"Humanoid.StartAttack / m_currentAttack not found; the jump-back throw hits at full damage.");
				}
			}
			catch (Exception ex2)
			{
				Log.LogError((object)("Could not patch the jump-back throw damage (" + ex2.GetType().Name + "); it hits at full damage."));
			}
			try
			{
				MethodInfo methodInfo4 = AccessTools.Method(typeof(Attack), "FireProjectileBurst", (Type[])null, (Type[])null);
				if (methodInfo4 != null)
				{
					val.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(GameThrowWatch), "BurstPostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
			}
			catch (Exception ex3)
			{
				Log.LogError((object)("Could not watch projectile bursts (" + ex3.GetType().Name + "); the throw fallback may double-fire."));
			}
			try
			{
				MethodInfo methodInfo5 = AccessTools.Method(typeof(Humanoid), "StartAttack", new Type[2]
				{
					typeof(Character),
					typeof(bool)
				}, (Type[])null);
				MethodInfo methodInfo6 = AccessTools.Method(typeof(Character), "RPC_Damage", (Type[])null, (Type[])null);
				MethodInfo methodInfo7 = AccessTools.Method(typeof(Humanoid), "IsBlocking", (Type[])null, (Type[])null);
				if (methodInfo5 != null && methodInfo6 != null && methodInfo7 != null && Refs.Blocking != null && Refs.CurrentAttack != null)
				{
					val.Patch((MethodBase)methodInfo5, new HarmonyMethod(AccessTools.Method(typeof(GuardCombo), "Prefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(GuardCombo), "Finalizer", (Type[])null, (Type[])null)), (HarmonyMethod)null);
					val.Patch((MethodBase)methodInfo6, new HarmonyMethod(AccessTools.Method(typeof(GuardCombo), "DamagePrefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(GuardCombo), "DamageFinalizer", (Type[])null, (Type[])null)), (HarmonyMethod)null);
					val.Patch((MethodBase)methodInfo7, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(GuardCombo), "IsBlockingPostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
				else
				{
					Log.LogError((object)"StartAttack / RPC_Damage / IsBlocking / m_blocking not found; the defence combo is off.");
				}
			}
			catch (Exception ex4)
			{
				Log.LogError((object)("Could not patch the defence combo (" + ex4.GetType().Name + "); it is off."));
			}
			Log.LogInfo((object)"Combo Spear 1.0.0 loaded.");
		}

		private void Update()
		{
			WiresShim.Ensure();
			if (dumped || !Dump.Value || (Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)ObjectDB.instance == (Object)null)
			{
				return;
			}
			dumped = true;
			try
			{
				AnimationDump.Write(Player.m_localPlayer);
			}
			catch (Exception ex)
			{
				Log.LogError((object)("Animation dump failed: " + ex));
			}
		}
	}
	internal sealed class ConfigurationManagerAttributes
	{
		public Action<ConfigEntryBase> CustomDrawer;

		public int? Order;
	}
	internal static class Refs
	{
		internal static FieldRef<Attack, int> ChainLevels;

		internal static FieldRef<Attack, float> LastChainDamage;

		internal static FieldRef<Attack, int> ChainLevel;

		internal static FieldRef<ZSyncAnimation, Animator> AnimatorOf;

		internal static FieldRef<VisEquipment, GameObject> RightItem;

		internal static FieldRef<VisEquipment, int> LeftItemHash;

		internal static FieldRef<VisEquipment, int> RightItemHash;

		internal static FieldInfo SecondaryAttack;

		internal static FieldInfo CurrentAttack;

		internal static FieldInfo Zanim;

		internal static FieldInfo Blocking;

		internal static FieldRef<Humanoid, bool> InternalBlocking;

		internal static FieldRef<Player, float> QueuedAttack;

		internal static FieldRef<Humanoid, float> TimeSinceLastAttack;

		internal static FieldInfo BlockingAnimHash;

		internal static MethodInfo FireProjectileBurst;

		internal static void Init()
		{
			ChainLevels = Get<Attack, int>("m_attackChainLevels");
			LastChainDamage = Get<Attack, float>("m_lastChainDamageMultiplier");
			ChainLevel = Get<Attack, int>("m_currentAttackCainLevel");
			AnimatorOf = Get<ZSyncAnimation, Animator>("m_animator");
			RightItem = Get<VisEquipment, GameObject>("m_rightItemInstance");
			LeftItemHash = Get<VisEquipment, int>("m_currentLeftItemHash");
			RightItemHash = Get<VisEquipment, int>("m_currentRightItemHash");
			SecondaryAttack = AccessTools.Field(typeof(SharedData), "m_secondaryAttack");
			CurrentAttack = AccessTools.Field(typeof(Humanoid), "m_currentAttack");
			Zanim = AccessTools.Field(typeof(Character), "m_zanim");
			Blocking = AccessTools.Field(typeof(Character), "m_blocking");
			InternalBlocking = Get<Humanoid, bool>("m_internalBlockingState");
			QueuedAttack = Get<Player, float>("m_queuedAttackTimer");
			TimeSinceLastAttack = Get<Humanoid, float>("m_timeSinceLastAttack");
			BlockingAnimHash = AccessTools.Field(typeof(Humanoid), "s_blocking");
			FireProjectileBurst = AccessTools.Method(typeof(Attack), "FireProjectileBurst", (Type[])null, (Type[])null);
			if (CurrentAttack == null || Zanim == null)
			{
				Plugin.Log.LogError((object)"m_currentAttack / m_zanim not found; cancelling the lunge is off.");
			}
		}

		private static FieldRef<T, F> Get<T, F>(string field) where T : class
		{
			try
			{
				return AccessTools.FieldRefAccess<T, F>(field);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Field " + typeof(T).Name + "." + field + " not found (" + ex.GetType().Name + "); that feature is off."));
				return null;
			}
		}

		internal static int Levels(Attack a)
		{
			if (ChainLevels == null)
			{
				return 1;
			}
			return ChainLevels.Invoke(a);
		}
	}
	internal static class Combo
	{
		private static string resolvedLunge;

		internal static bool IsComboSpear(SharedData s)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Invalid comparison between Unknown and I4
			if (s != null && (int)s.m_skillType == 5 && s.m_attack != null)
			{
				if (!(s.m_attack.m_attackAnimation == "spear_poke"))
				{
					return s.m_attack.m_attackAnimation == "spearcombo";
				}
				return true;
			}
			return false;
		}

		internal static SharedData SharedOf(GameObject go)
		{
			ItemDrop val = (Object.op_Implicit((Object)(object)go) ? go.GetComponent<ItemDrop>() : null);
			if (!((Object)(object)val != (Object)null))
			{
				return null;
			}
			return val.m_itemData?.m_shared;
		}

		internal static SharedData SharedFromHash(int hash)
		{
			if (hash != 0 && !((Object)(object)ObjectDB.instance == (Object)null))
			{
				return SharedOf(ObjectDB.instance.GetItemPrefab(hash));
			}
			return null;
		}

		internal static void Apply(SharedData s)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Invalid comparison between Unknown and I4
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			if (s == null || (int)s.m_skillType != 5 || s.m_attack == null)
			{
				return;
			}
			Attack attack = s.m_attack;
			if (attack.m_attackAnimation != "spear_poke" || Refs.ChainLevels == null)
			{
				return;
			}
			attack.m_attackAnimation = "spearcombo";
			Refs.ChainLevels.Invoke(attack) = 3;
			DestructibleType resetChainIfHit = attack.m_resetChainIfHit;
			attack.m_resetChainIfHit = (DestructibleType)0;
			if (Refs.LastChainDamage != null)
			{
				Refs.LastChainDamage.Invoke(attack) = Plugin.LastHitDamage.Value;
			}
			attack.m_attackRange = Plugin.Range.Value;
			attack.m_attackHeight = 1f;
			attack.m_attackAngle = 30f;
			attack.m_attackRayWidth = 0.4f;
			if (Plugin.JumpBackThrow.Value)
			{
				object? obj = Refs.SecondaryAttack?.GetValue(s);
				Attack val = (Attack)((obj is Attack) ? obj : null);
				if (val != null)
				{
					val.m_damageMultiplier = 1f;
				}
			}
			Plugin.Log.LogInfo((object)$"Combo applied to {s.m_name} (chain reset-if-hit {resetChainIfHit} -> None).");
		}

		internal static string MapTrigger(string name, out int level)
		{
			return MapTrigger(name, "spearcombo", out level);
		}

		internal static string MapTrigger(string name, string prefix, out int level)
		{
			level = 0;
			if (name.Length > prefix.Length)
			{
				int.TryParse(name.Substring(prefix.Length), out level);
			}
			level = Mathf.Clamp(level, 0, 2);
			return Resolve(Plugin.Hit[level].Value);
		}

		internal static string Resolve(string configured)
		{
			if (!(configured == "knife-lunge"))
			{
				return configured;
			}
			return LungeTrigger();
		}

		private static string LungeTrigger()
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Invalid comparison between Unknown and I4
			if (resolvedLunge != null)
			{
				return resolvedLunge;
			}
			if ((Object)(object)ObjectDB.instance != (Object)null && Refs.ChainLevels != null)
			{
				foreach (GameObject item in ObjectDB.instance.m_items)
				{
					SharedData val = SharedOf(item);
					if (val != null && (int)val.m_skillType == 2 && val.m_attack != null)
					{
						int num = Refs.Levels(val.m_attack);
						if (num >= 3 && !string.IsNullOrEmpty(val.m_attack.m_attackAnimation))
						{
							resolvedLunge = val.m_attack.m_attackAnimation + (num - 1);
							Plugin.Log.LogInfo((object)("knife-lunge = '" + resolvedLunge + "' (from " + ((Object)item).name + ")."));
							return resolvedLunge;
						}
					}
				}
			}
			resolvedLunge = "knife_stab2";
			Plugin.Log.LogWarning((object)("Could not read the knife combo; guessing '" + resolvedLunge + "'. Pick the lunge from the list if it does nothing."));
			return resolvedLunge;
		}
	}
	internal class AnimOption
	{
		public string Trigger;

		public string Label;
	}
	internal static class AnimationCatalog
	{
		private static readonly HashSet<string> PlayerItemTypes = new HashSet<string> { "OneHandedWeapon", "TwoHandedWeapon", "TwoHandedWeaponLeft", "Bow", "Torch", "Tool" };

		private static List<AnimOption> cache;

		internal static List<AnimOption> Get()
		{
			if (cache != null)
			{
				return cache;
			}
			if ((Object)(object)ObjectDB.instance == (Object)null || ObjectDB.instance.m_recipes == null || ObjectDB.instance.m_recipes.Count == 0)
			{
				return null;
			}
			HashSet<SharedData> hashSet = new HashSet<SharedData>();
			foreach (Recipe recipe in ObjectDB.instance.m_recipes)
			{
				SharedData val = ((!((Object)(object)recipe != (Object)null) || !((Object)(object)recipe.m_item != (Object)null)) ? null : recipe.m_item.m_itemData?.m_shared);
				if (val != null)
				{
					hashSet.Add(val);
				}
			}
			HashSet<string> seen = new HashSet<string> { "knife-lunge" };
			List<AnimOption> list = new List<AnimOption>();
			foreach (GameObject item in ObjectDB.instance.m_items)
			{
				SharedData val2 = Combo.SharedOf(item);
				if (val2 != null && hashSet.Contains(val2) && PlayerItemTypes.Contains(((object)Unsafe.As<ItemType, ItemType>(ref val2.m_itemType)/*cast due to .constrained prefix*/).ToString()))
				{
					string weapon = Localize(val2.m_name);
					AddAttack(list, seen, weapon, "primary", val2.m_attack);
					object? obj = Refs.SecondaryAttack?.GetValue(val2);
					AddAttack(list, seen, weapon, "secondary", (Attack)((obj is Attack) ? obj : null));
				}
			}
			list.Sort((AnimOption a, AnimOption b) => string.CompareOrdinal(a.Label, b.Label));
			List<AnimOption> list2 = new List<AnimOption>();
			list2.Add(new AnimOption
			{
				Trigger = "knife-lunge",
				Label = "knife-lunge   (knife combo finisher, auto-detected)"
			});
			list2.AddRange(list);
			cache = list2;
			return cache;
		}

		private static string Localize(string token)
		{
			try
			{
				return (Localization.instance != null) ? Localization.instance.Localize(token) : token;
			}
			catch
			{
				return token;
			}
		}

		private static void AddAttack(List<AnimOption> list, HashSet<string> seen, string weapon, string kind, Attack a)
		{
			if (a == null || string.IsNullOrEmpty(a.m_attackAnimation))
			{
				return;
			}
			bool num = a.m_attackAnimation == "spearcombo";
			string text = (num ? "spear_poke" : a.m_attackAnimation);
			int num2 = (num ? 1 : Refs.Levels(a));
			if (num2 <= 1)
			{
				if (seen.Add(text))
				{
					list.Add(new AnimOption
					{
						Trigger = text,
						Label = text + "   (" + weapon + ", " + kind + ")"
					});
				}
				return;
			}
			for (int i = 0; i < num2; i++)
			{
				string text2 = text + i;
				if (seen.Add(text2))
				{
					list.Add(new AnimOption
					{
						Trigger = text2,
						Label = $"{text2}   ({weapon}, {kind} hit {i + 1}/{num2})"
					});
				}
			}
		}
	}
	internal static class AnimPicker
	{
		private static readonly Dictionary<ConfigEntryBase, bool> open = new Dictionary<ConfigEntryBase, bool>();

		private static readonly Dictionary<ConfigEntryBase, Vector2> scroll = new Dictionary<ConfigEntryBase, Vector2>();

		internal static void Draw(ConfigEntryBase entryBase)
		{
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			ConfigEntry<string> val = (ConfigEntry<string>)(object)entryBase;
			List<AnimOption> list = AnimationCatalog.Get();
			open.TryGetValue((ConfigEntryBase)(object)val, out var value);
			GUILayout.BeginVertical(Array.Empty<GUILayoutOption>());
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			if (GUILayout.Button("<", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(28f) }) && list != null)
			{
				Step(val, list, -1);
			}
			string text = GUILayout.TextField(val.Value, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
			if (text != val.Value)
			{
				val.Value = text;
			}
			if (GUILayout.Button(">", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(28f) }) && list != null)
			{
				Step(val, list, 1);
			}
			if (GUILayout.Button(value ? "Close" : "List", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(55f) }))
			{
				value = (open[(ConfigEntryBase)(object)val] = !value);
			}
			GUILayout.EndHorizontal();
			if (value)
			{
				if (list == null)
				{
					GUILayout.Label("Enter a world first - the list is read from the game.", Array.Empty<GUILayoutOption>());
				}
				else
				{
					scroll.TryGetValue((ConfigEntryBase)(object)val, out var value2);
					scroll[(ConfigEntryBase)(object)val] = GUILayout.BeginScrollView(value2, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(260f) });
					foreach (AnimOption item in list)
					{
						if (GUILayout.Button((item.Trigger == val.Value) ? ("> " + item.Label) : item.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }))
						{
							val.Value = item.Trigger;
							open[(ConfigEntryBase)(object)val] = false;
						}
					}
					GUILayout.EndScrollView();
				}
			}
			GUILayout.EndVertical();
		}

		private static void Step(ConfigEntry<string> entry, List<AnimOption> options, int dir)
		{
			int num = options.FindIndex((AnimOption o) => o.Trigger == entry.Value);
			num = ((num >= 0) ? ((num + dir + options.Count) % options.Count) : 0);
			entry.Value = options[num].Trigger;
		}
	}
	internal static class AnimationDump
	{
		internal static void Write(Player player)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine("Valheim attack animations (written by Spear Combo). Use these names for Hit1..Hit3.");
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("== Attacks of player weapons (craftable) ==");
			List<AnimOption> list = AnimationCatalog.Get();
			if (list != null)
			{
				foreach (AnimOption item in list)
				{
					stringBuilder.AppendLine(item.Label);
				}
			}
			Animator val = ((Refs.AnimatorOf != null) ? Refs.AnimatorOf.Invoke(((Component)player).GetComponent<ZSyncAnimation>()) : null);
			if ((Object)(object)val != (Object)null && (Object)(object)val.runtimeAnimatorController != (Object)null)
			{
				stringBuilder.AppendLine();
				stringBuilder.AppendLine("== Animation clips in the player controller (for custom-clip replacement later) ==");
				SortedSet<string> sortedSet = new SortedSet<string>(StringComparer.Ordinal);
				AnimationClip[] animationClips = val.runtimeAnimatorController.animationClips;
				foreach (AnimationClip val2 in animationClips)
				{
					if ((Object)(object)val2 != (Object)null)
					{
						sortedSet.Add($"{((Object)val2).name}  ({val2.length:0.00}s)");
					}
				}
				foreach (string item2 in sortedSet)
				{
					stringBuilder.AppendLine(item2);
				}
			}
			string text = Path.Combine(Paths.BepInExRootPath, "SpearCombo-animations.txt");
			File.WriteAllText(text, stringBuilder.ToString());
			Plugin.Log.LogInfo((object)("Animation list written to " + text));
		}
	}
	internal class FlipMark : MonoBehaviour
	{
		public bool Flipped;

		public bool Fang;

		public bool PoseActive;

		public float ReflipAt;

		public float GripW;

		public float RegripW;

		public int Level = -1;

		private ComboState state;

		private void LateUpdate()
		{
			if (!Flipped)
			{
				GripW = 0f;
				return;
			}
			if ((Object)(object)state == (Object)null)
			{
				state = ((Component)this).GetComponentInParent<ComboState>();
			}
			float num = (((Object)(object)state != (Object)null && (state.Level >= 0 || Time.time < state.LastActive + 0.15f)) ? 1f : 0f);
			if (GripW != num || GripW != 0f)
			{
				float num2 = Time.deltaTime / ((num > GripW) ? Mathf.Max(0.01f, Plugin.ComboGripBlend.Value) : 0.5f);
				GripW = Mathf.MoveTowards(GripW, num, num2);
				RegripW = (((Object)(object)state != (Object)null && state.ThrowAt > 0f) ? SpearFlip.Regrip(Time.time - state.ThrowAt) : 0f);
				SpearFlip.ApplyPose(((Component)this).gameObject, this, ((Object)(object)state != (Object)null) ? state.Level : (-1));
			}
		}
	}
	internal static class SpearFlip
	{
		private static readonly Vector3 PalmOffset = new Vector3(-0.0171f, -0.0045f, 0f);

		private const float TossStart = 0.22f;

		private const float TossEnd = 0.53f;

		private const float TossLift = 0.15f;

		internal static Quaternion RegripRot(Quaternion r1, float w)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//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_0029: 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_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: 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)
			Quaternion val = Quaternion.Inverse(r1);
			float num = default(float);
			Vector3 val2 = default(Vector3);
			((Quaternion)(ref val)).ToAngleAxis(ref num, ref val2);
			if (num > 180f)
			{
				num = 360f - num;
				val2 = -val2;
			}
			return r1 * Quaternion.AngleAxis((num - 360f) * w, val2);
		}

		internal static float Regrip(float t)
		{
			if (t < 0f || t > 2f)
			{
				return 0f;
			}
			float num = Mathf.Clamp01((t - 0.22f) / 0.30999997f);
			return num * num * (3f - 2f * num);
		}

		internal static void Flip(GameObject go, bool fang)
		{
			FlipMark flipMark = go.GetComponent<FlipMark>() ?? go.AddComponent<FlipMark>();
			flipMark.Fang = fang;
			flipMark.Flipped = true;
			ApplyPose(go, flipMark, -1);
		}

		internal static void ApplyPose(GameObject go, FlipMark m, int hitLevel)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: 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_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: 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_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_0166: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			Transform transform = go.transform;
			transform.localRotation = Quaternion.identity;
			transform.localPosition = Vector3.zero;
			m.PoseActive = false;
			if (m.Flipped)
			{
				transform.Rotate(180f, 25f, 0f);
				transform.Translate(Vector3.forward * (m.Fang ? (-0.72f) : (-0.5f)) + PalmOffset, (Space)1);
				if (m.GripW > 0f)
				{
					float num = m.GripW * m.GripW * (3f - 2f * m.GripW);
					Quaternion val = Quaternion.Slerp(Quaternion.identity, Quaternion.Euler(Plugin.ComboGrip.Value), num);
					transform.localRotation = val * transform.localRotation;
					transform.localPosition = val * transform.localPosition;
					m.PoseActive = true;
				}
				if (m.RegripW > 0f)
				{
					float regripW = m.RegripW;
					transform.localRotation = RegripRot(transform.localRotation, regripW);
					transform.localPosition = Vector3.Lerp(transform.localPosition, Vector3.zero, regripW);
					transform.position += Vector3.up * (0.15f * Mathf.Sin(MathF.PI * regripW));
					m.PoseActive = true;
				}
				if (hitLevel >= 0)
				{
					transform.Rotate(Plugin.HitRotation[hitLevel].Value);
					transform.Translate(Plugin.HitOffset[hitLevel].Value);
					m.PoseActive = true;
				}
			}
		}

		internal static GameObject RightItemOf(Component c)
		{
			Humanoid component = c.GetComponent<Humanoid>();
			VisEquipment val = (((Object)(object)component != (Object)null) ? component.GetVisEquipment() : null);
			if (!((Object)(object)val != (Object)null) || Refs.RightItem == null)
			{
				return null;
			}
			return Refs.RightItem.Invoke(val);
		}
	}
	internal class JumpBack : MonoBehaviour
	{
		private float fireAt = -1f;

		internal void Arm(float delay)
		{
			fireAt = Time.time + delay;
			((Behaviour)this).enabled = true;
		}

		private void Update()
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: 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_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			if (!(fireAt < 0f) && !(Time.time < fireAt))
			{
				fireAt = -1f;
				((Behaviour)this).enabled = false;
				Character component = ((Component)this).GetComponent<Character>();
				if (!((Object)(object)component == (Object)null) && !component.IsDead() && component.IsOnGround())
				{
					float num = Mathf.Max(0.1f, 0f - Physics.gravity.y);
					float num2 = Mathf.Sqrt(2f * num * Mathf.Max(0f, Plugin.JumpBackHeight.Value));
					float num3 = 2f * num2 / num;
					float num4 = ((num3 > 0.01f) ? (Plugin.JumpBackDistance.Value / num3) : 0f);
					Vector3 val = -((Component)component).transform.forward;
					val.y = 0f;
					((Vector3)(ref val)).Normalize();
					component.ForceJump(val * num4 + Vector3.up * num2, false);
				}
			}
		}
	}
	internal static class GuardCombo
	{
		internal static bool SwallowedSinceStart;

		private static float pendingGap;

		private static bool pendingContinuing;

		private static float guardStartedAt = -10f;

		private static float lastTraceAt = -10f;

		private static Character damaged;

		private static void Trace(string text)
		{
			if (!(Time.time - lastTraceAt < 0.25f))
			{
				lastTraceAt = Time.time;
				Plugin.Log.LogDebug((object)("[guard] " + text));
			}
		}

		private static bool BlockButtonHeld()
		{
			try
			{
				if (Hud.InRadial())
				{
					return false;
				}
				return ZInput.GetButton("Block") || ZInput.GetButton("JoyBlock");
			}
			catch
			{
				return false;
			}
		}

		internal static bool Prefix(Humanoid __instance, bool secondaryAttack, out bool __state, ref bool __result)
		{
			__state = false;
			if (secondaryAttack || !Plugin.DefenceCombo.Value || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || Refs.Blocking == null)
			{
				return true;
			}
			object value = Refs.Blocking.GetValue(__instance);
			bool flag = default(bool);
			int num;
			if (value is bool)
			{
				flag = (bool)value;
				num = 1;
			}
			else
			{
				num = 0;
			}
			bool flag2 = (byte)((uint)num & (flag ? 1u : 0u)) != 0;
			bool flag3 = BlockButtonHeld();
			bool flag4 = flag2 || flag3;
			if (!flag4)
			{
				ComboState component = ((Component)__instance).GetComponent<ComboState>();
				if ((Object)(object)component == (Object)null || !component.Guard || Time.time > component.LastActive + 0.5f)
				{
					Trace("skip: block neither held nor recent -> game starts a NORMAL combo hit");
					return true;
				}
			}
			SharedData val = __instance.GetCurrentWeapon()?.m_shared;
			if (val == null || val.m_attack == null)
			{
				if (flag4)
				{
					Trace("skip: no weapon/attack");
				}
				return true;
			}
			string attackAnimation = val.m_attack.m_attackAnimation;
			if (attackAnimation != "spearcombo" && attackAnimation != "spearguard")
			{
				if (flag4)
				{
					Trace("skip: attack name '" + attackAnimation + "' (block held) -> game starts a NORMAL hit");
				}
				return true;
			}
			object? obj = Refs.CurrentAttack?.GetValue(__instance);
			Attack val2 = (Attack)((obj is Attack) ? obj : null);
			ComboState component2 = ((Component)__instance).GetComponent<ComboState>();
			bool flag5 = (Object)(object)component2 != (Object)null && component2.GuardHolding;
			bool flag6 = val2 != null && val2.m_attackAnimation == "spearguard" && !val2.IsDone() && !flag5 && Time.time - guardStartedAt < 1.5f;
			Trace(string.Format("block(field={0},button={1}) running='{2}' ", flag2, flag3, (val2 != null) ? val2.m_attackAnimation : "<none>") + string.Format("done={0} held={1} sinceStart={2:0.00} ", (val2 != null) ? val2.IsDone().ToString() : "-", flag5, Time.time - guardStartedAt) + "-> " + (flag6 ? "REFUSE (hit still playing)" : "allow"));
			if (flag6)
			{
				__state = false;
				__result = false;
				return false;
			}
			val.m_attack.m_attackAnimation = "spearguard";
			if (Refs.LastChainDamage != null)
			{
				Refs.LastChainDamage.Invoke(val.m_attack) = 1f;
			}
			ComboState component3 = ((Component)__instance).GetComponent<ComboState>();
			pendingGap = (((Object)(object)component3 != (Object)null) ? (Time.time - component3.LastActive) : 999f);
			pendingContinuing = (Object)(object)component3 != (Object)null && component3.Guard && pendingGap <= Plugin.GuardReset.Value;
			if (pendingContinuing && Refs.TimeSinceLastAttack != null)
			{
				Refs.TimeSinceLastAttack.Invoke(__instance) = 0f;
			}
			object? obj2 = Refs.Zanim?.GetValue(__instance);
			ZSyncAnimation val3 = (ZSyncAnimation)((obj2 is ZSyncAnimation) ? obj2 : null);
			if (val3 != null)
			{
				if (Refs.BlockingAnimHash != null)
				{
					ClearBlockPose(__instance, val3);
				}
				if (Refs.AnimatorOf != null)
				{
					Animator val4 = Refs.AnimatorOf.Invoke(val3);
					if ((Object)(object)val4 != (Object)null && val4.speed < 0.01f)
					{
						val4.speed = 1f;
					}
				}
			}
			guardStartedAt = Time.time;
			__state = true;
			return true;
		}

		internal static void Finalizer(Humanoid __instance, bool __state, bool __result)
		{
			if (!__state)
			{
				return;
			}
			SharedData val = __instance.GetCurrentWeapon()?.m_shared;
			if (val == null || val.m_attack == null)
			{
				return;
			}
			val.m_attack.m_attackAnimation = "spearcombo";
			if (Refs.LastChainDamage != null)
			{
				Refs.LastChainDamage.Invoke(val.m_attack) = Plugin.LastHitDamage.Value;
			}
			if (!__result)
			{
				return;
			}
			object? obj = Refs.CurrentAttack?.GetValue(__instance);
			Attack val2 = (Attack)((obj is Attack) ? obj : null);
			if (val2 != null && val2.m_attackAnimation == "spearguard" && Refs.ChainLevel != null)
			{
				if (SwallowedSinceStart)
				{
					SwallowedSinceStart = false;
					return;
				}
				int num = Mathf.Clamp(Refs.ChainLevel.Invoke(val2), 0, 2);
				val2.m_damageMultiplier = Plugin.GuardDamage[num].Value;
				Plugin.Log.LogDebug((object)($"Defence hit {num + 1} of 3: damage x{val2.m_damageMultiplier:0.##} (" + string.Format("{0}, {1:0.00}s since the last defence hit).", pendingContinuing ? "continuing the combo" : "fresh chain", pendingGap)));
			}
		}

		internal static void DamagePrefix(Character __instance)
		{
			damaged = __instance;
		}

		internal static void DamageFinalizer()
		{
			damaged = null;
		}

		internal static void IsBlockingPostfix(Humanoid __instance, ref bool __result)
		{
			if ((Object)(object)damaged != (Object)(object)__instance)
			{
				if (__result && (Object)(object)__instance == (Object)(object)Player.m_localPlayer)
				{
					ComboState component = ((Component)__instance).GetComponent<ComboState>();
					if (component != null && component.Guard && (component.Level >= 0 || Time.time < component.LastActive + 0.35f))
					{
						__result = false;
					}
				}
			}
			else
			{
				if (__result || !(__instance is Player) || Refs.Blocking == null || Refs.CurrentAttack == null)
				{
					return;
				}
				object value = Refs.Blocking.GetValue(__instance);
				if (value is bool && (bool)value)
				{
					object? value2 = Refs.CurrentAttack.GetValue(__instance);
					Attack val = (Attack)((value2 is Attack) ? value2 : null);
					if (val != null && !(val.m_attackAnimation != "spearguard") && !((Character)__instance).IsStaggering() && !((Character)__instance).InDodge())
					{
						__result = true;
					}
				}
			}
		}

		internal static void KeepQueuedClick(ComboState st, ZSyncAnimation zanim)
		{
			if ((Object)(object)st == (Object)null || !st.Guard || st.Level < 0 || Refs.QueuedAttack == null)
			{
				return;
			}
			Humanoid component = ((Component)zanim).GetComponent<Humanoid>();
			if (!((Object)(object)component == (Object)null) && !((Object)(object)component != (Object)(object)Player.m_localPlayer))
			{
				Player val = (Player)(object)((component is Player) ? component : null);
				if (!((Object)(object)val == (Object)null) && !(Refs.QueuedAttack.Invoke(val) <= 0f))
				{
					Refs.QueuedAttack.Invoke(val) = 0.5f;
				}
			}
		}

		private static void ClearBlockPose(Humanoid h, ZSyncAnimation zanim)
		{
			if (Refs.BlockingAnimHash != null)
			{
				zanim.SetBool((int)Refs.BlockingAnimHash.GetValue(null), false);
			}
			if ((Object)(object)h != (Object)null && Refs.InternalBlocking != null)
			{
				Refs.InternalBlocking.Invoke(h) = false;
			}
		}

		internal static void SuppressBlockPose(ZSyncAnimation zanim, ComboState st)
		{
			if ((Object)(object)st == (Object)null || !Plugin.DefenceCombo.Value || Refs.BlockingAnimHash == null || !st.Guard || Time.time > st.LastActive + Plugin.GuardReset.Value)
			{
				return;
			}
			Humanoid component = ((Component)zanim).GetComponent<Humanoid>();
			if ((Object)(object)component == (Object)null || (Object)(object)component != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			bool flag = default(bool);
			int num;
			if (Refs.Blocking != null)
			{
				object value = Refs.Blocking.GetValue(component);
				if (value is bool)
				{
					flag = (bool)value;
					num = 1;
				}
				else
				{
					num = 0;
				}
			}
			else
			{
				num = 0;
			}
			if (((uint)num & (flag ? 1u : 0u)) != 0 || BlockButtonHeld())
			{
				ClearBlockPose(component, zanim);
			}
		}
	}
	internal static class WiresShim
	{
		private static bool tried;

		private static float lastLogAt = -10f;

		internal static void Ensure()
		{
			//IL_00a1: 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_00cb: Expected O, but got Unknown
			if (tried)
			{
				return;
			}
			tried = true;
			try
			{
				Assembly assembly = null;
				Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
				foreach (Assembly assembly2 in assemblies)
				{
					if (assembly2.GetName().Name == "WiresCombatFlow")
					{
						assembly = assembly2;
						break;
					}
				}
				Type type = ((assembly != null) ? assembly.GetType("WiresCombatFlow.CombatFlow") : null);
				MethodInfo methodInfo = ((type != null) ? AccessTools.Method(type, "TryCancel", (Type[])null, (Type[])null) : null);
				if (methodInfo == null)
				{
					Plugin.Log.LogInfo((object)"Wires Combat Flow not detected - its recovery-cancel cannot touch the defence hits.");
					return;
				}
				new Harmony("andythedemon.combospear.wires").Patch((MethodBase)methodInfo, new HarmonyMethod(AccessTools.Method(typeof(WiresShim), "TryCancelPrefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Plugin.Log.LogInfo((object)"Wires Combat Flow detected - its recovery-cancel is now skipped while a defence hit plays.");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not bind the Wires cancel immunity (" + ex.GetType().Name + "); defence hits stay cancellable by it."));
			}
		}

		internal static bool TryCancelPrefix(Player player, int cancel)
		{
			if ((Object)(object)player != (Object)(object)Player.m_localPlayer || Refs.CurrentAttack == null)
			{
				return true;
			}
			object? value = Refs.CurrentAttack.GetValue(player);
			Attack val = (Attack)((value is Attack) ? value : null);
			if (val == null)
			{
				return true;
			}
			if (val.m_attackAnimation != "spearguard")
			{
				return true;
			}
			if (Time.time - lastLogAt > 2f)
			{
				lastLogAt = Time.time;
				Plugin.Log.LogDebug((object)"Wires recovery-cancel suppressed during a defence hit (its Stop/reject/crossfade skipped).");
			}
			return false;
		}
	}
	internal static class SoftThrow
	{
		internal static void Postfix(Humanoid __instance, bool secondaryAttack, bool __result)
		{
			if (!__result || (Object)(object)__instance != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			ComboState component = ((Component)__instance).GetComponent<ComboState>();
			if (!secondaryAttack)
			{
				object? obj = Refs.CurrentAttack?.GetValue(__instance);
				Attack val = (Attack)((obj is Attack) ? obj : null);
				if (val != null && val.m_attackAnimation == "spearcombo")
				{
					float value = Plugin.NormalHitDamage.Value;
					if (!Mathf.Approximately(value, 1f))
					{
						val.m_damageMultiplier = value;
					}
				}
			}
			else if (!((Object)(object)component == (Object)null) && component.SoftThrowPending)
			{
				component.SoftThrowPending = false;
				object? obj2 = Refs.CurrentAttack?.GetValue(__instance);
				Attack val2 = (Attack)((obj2 is Attack) ? obj2 : null);
				if (val2 != null)
				{
					val2.m_damageMultiplier = Plugin.ThrowDamage.Value;
					Plugin.Log.LogInfo((object)$"Jump-back throw damage x{Plugin.ThrowDamage.Value:0.##} (this throw only).");
				}
			}
		}
	}
	internal static class GameThrowWatch
	{
		private struct Burst
		{
			public Attack attack;

			public float at;
		}

		private static readonly List<Burst> bursts = new List<Burst>(16);

		internal static void BurstPostfix(Attack __instance)
		{
			if (__instance != null)
			{
				bursts.Add(new Burst
				{
					attack = __instance,
					at = Time.time
				});
				if (bursts.Count > 16)
				{
					bursts.RemoveAt(0);
				}
			}
		}

		internal static bool FiredSince(Attack attack, float sinceAt)
		{
			for (int num = bursts.Count - 1; num >= 0; num--)
			{
				if (bursts[num].attack == attack && bursts[num].at >= sinceAt)
				{
					return true;
				}
			}
			return false;
		}
	}
	internal static class LungeCancel
	{
		private static bool bypass;

		internal static bool InAttackPrefix(Player __instance, ref bool __result)
		{
			if (!bypass || (Object)(object)__instance != (Object)(object)Player.m_localPlayer)
			{
				return true;
			}
			__result = false;
			return false;
		}

		internal static void Prefix(Humanoid __instance, bool secondaryAttack, out bool __state)
		{
			__state = false;
			if (!secondaryAttack || !Plugin.JumpBackThrow.Value || (Object)(object)__instance != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			object? value = Refs.CurrentAttack.GetValue(__instance);
			Attack val = (Attack)((value is Attack) ? value : null);
			if (val != null && !(val.m_attackAnimation != "spearcombo"))
			{
				object? value2 = Refs.Zanim.GetValue(__instance);
				ZSyncAnimation val2 = (ZSyncAnimation)((value2 is ZSyncAnimation) ? value2 : null);
				if (val2 != null && ComboState.LungeHitLanded(val2) && ((Character)__instance).IsOnGround())
				{
					bypass = true;
					__state = true;
				}
			}
		}

		internal static void Finalizer(Humanoid __instance, bool __state, bool __result)
		{
			if (!__state)
			{
				return;
			}
			bypass = false;
			if (__result)
			{
				Plugin.Log.LogInfo((object)"Special during the lunge: lunge cut, jump-back throw started.");
				return;
			}
			string text;
			try
			{
				ItemData currentWeapon = __instance.GetCurrentWeapon();
				Attack val = currentWeapon?.m_shared?.m_secondaryAttack;
				text = (((Character)__instance).IsStaggering() ? "the character is staggering (took a hit)" : (((Character)__instance).InDodge() ? "the character is dodging" : ((!((Character)__instance).CanMove()) ? "the character cannot move" : (((Character)__instance).InMinorAction() ? "a minor action is playing" : ((currentWeapon == null) ? "nothing in hand" : ((!currentWeapon.HaveSecondaryAttack()) ? "the weapon has no secondary attack" : ((val != null && !string.IsNullOrEmpty(val.m_attackAnimation)) ? ((val.m_cantUseInDungeon && ((Character)__instance).InInterior()) ? "the throw is blocked indoors" : ((!val.m_requiresReload || __instance.IsWeaponLoaded()) ? "vanilla refused it, no blocking state detected" : "the weapon is not loaded")) : "the secondary attack has no animation")))))));
			}
			catch (Exception ex)
			{
				text = "state read failed (" + ex.GetType().Name + ")";
			}
			Plugin.Log.LogInfo((object)("Special during the lunge: the throw could not start - " + text + "."));
		}
	}
	internal class ComboState : MonoBehaviour
	{
		public int Level = -1;

		public string Clip;

		public float CaptureAt;

		public float ExpireAt;

		public float LastActive = -10f;

		public float ThrowAt = -10f;

		public bool ThrowFired;

		public Attack PendingThrow;

		public int PreloadedSet = -1;

		public bool Guard;

		public bool GuardHolding;

		public bool GuardHoldDone;

		public float GuardHoldFrom = -10f;

		public float HitAt = -1f;

		public float LungeUntil = -10f;

		public bool SoftThrowPending;

		internal static void Begin(ZSyncAnimation zanim, int level, bool guard = false)
		{
			ComboState comboState = ((Component)zanim).GetComponent<ComboState>() ?? ((Component)zanim).gameObject.AddComponent<ComboState>();
			comboState.Level = level;
			comboState.Guard = guard;
			comboState.GuardHolding = false;
			comboState.GuardHoldDone = false;
			comboState.Clip = null;
			comboState.CaptureAt = Time.time + 0.05f;
			comboState.LastActive = Time.time;
			comboState.ExpireAt = Time.time + 4f;
			if (guard)
			{
				comboState.HitAt = -1f;
				comboState.LungeUntil = -10f;
			}
			else if (level >= 2)
			{
				comboState.HitAt = Time.time + 0.667f;
				comboState.LungeUntil = comboState.HitAt + 1.6f;
			}
		}

		internal static bool InThrowWindow(ZSyncAnimation zanim)
		{
			ComboState comboState = (((Object)(object)zanim != (Object)null) ? ((Component)zanim).GetComponent<ComboState>() : null);
			if ((Object)(object)comboState != (Object)null && comboState.HitAt > 0f)
			{
				return Time.time <= comboState.LungeUntil;
			}
			return false;
		}

		internal static bool LungeHitLanded(ZSyncAnimation zanim)
		{
			ComboState comboState = (((Object)(object)zanim != (Object)null) ? ((Component)zanim).GetComponent<ComboState>() : null);
			if ((Object)(object)comboState != (Object)null && comboState.Level >= 2 && comboState.HitAt > 0f && Time.time >= comboState.HitAt)
			{
				return Time.time <= comboState.LungeUntil;
			}
			return false;
		}

		internal static void Spend(ZSyncAnimation zanim)
		{
			ComboState comboState = (((Object)(object)zanim != (Object)null) ? ((Component)zanim).GetComponent<ComboState>() : null);
			if (!((Object)(object)comboState == (Object)null))
			{
				comboState.LungeUntil = -10f;
				comboState.Level = -1;
			}
		}

		internal static void MarkSoftThrow(ZSyncAnimation zanim)
		{
			(((Component)zanim).GetComponent<ComboState>() ?? ((Component)zanim).gameObject.AddComponent<ComboState>()).SoftThrowPending = true;
		}

		internal static void Throw(ZSyncAnimation zanim)
		{
			ComboState component = ((Component)zanim).GetComponent<ComboState>();
			if ((Object)(object)component != (Object)null)
			{
				component.PreloadedSet = -1;
			}
			ComboState obj = ((Component)zanim).GetComponent<ComboState>() ?? ((Component)zanim).gameObject.AddComponent<ComboState>();
			obj.Level = -1;
			obj.LastActive = Time.time + 1f;
			obj.ThrowAt = Time.time;
			obj.ThrowFired = false;
			obj.PendingThrow = null;
		}

		internal static void End(ZSyncAnimation zanim)
		{
			ComboState component = ((Component)zanim).GetComponent<ComboState>();
			if ((Object)(object)component != (Object)null)
			{
				component.Level = -1;
			}
		}
	}
	internal static class CustomClips
	{
		private class Slot
		{
			public AnimatorOverrideController Controller;

			public RuntimeAnimatorController Base;

			public string AppliedSignature = "";
		}

		private static AssetBundle bundle;

		private static bool bundleTried;

		private static readonly Dictionary<string, AnimationClip> clips = new Dictionary<string, AnimationClip>();

		private static readonly ConditionalWeakTable<Animator, Slot> slots = new ConditionalWeakTable<Animator, Slot>();

		internal const string EmbeddedBundleName = "spearcombo.bundle";

		private static Stream embeddedBundleStream;

		internal const string TorchSuffix = "_Torch";

		private static AssetBundle LoadEmbeddedBundle()
		{
			try
			{
				Assembly executingAssembly = Assembly.GetExecutingAssembly();
				string text = null;
				string[] manifestResourceNames = executingAssembly.GetManifestResourceNames();
				foreach (string text2 in manifestResourceNames)
				{
					if (text2.EndsWith("spearcombo.bundle", StringComparison.OrdinalIgnoreCase))
					{
						text = text2;
						break;
					}
				}
				if (text == null)
				{
					return null;
				}
				using Stream stream = executingAssembly.GetManifestResourceStream(text);
				if (stream == null)
				{
					return null;
				}
				MemoryStream memoryStream = new MemoryStream();
				stream.CopyTo(memoryStream);
				memoryStream.Position = 0L;
				embeddedBundleStream = memoryStream;
				return AssetBundle.LoadFromStream((Stream)memoryStream);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not load the embedded bundle (" + ex.GetType().Name + ": " + ex.Message + ")."));
				return null;
			}
		}

		internal static AnimationClip Clip(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return null;
			}
			if (clips.TryGetValue(name, out var value))
			{
				return value;
			}
			if (!bundleTried)
			{
				bundleTried = true;
				string text = Path.Combine(Plugin.PluginFolder, Plugin.CustomBundle.Value);
				if (File.Exists(text))
				{
					bundle = AssetBundle.LoadFromFile(text);
					Plugin.Log.Log((LogLevel)(Object.op_Implicit((Object)(object)bundle) ? 16 : 4), (object)(Object.op_Implicit((Object)(object)bundle) ? ("Loaded bundle " + text) : ("Bundle found but could not be read: " + text)));
				}
				else
				{
					bundle = LoadEmbeddedBundle();
					Plugin.Log.Log((LogLevel)(Object.op_Implicit((Object)(object)bundle) ? 16 : 4), (object)(Object.op_Implicit((Object)(object)bundle) ? "No bundle file next to the DLL - loaded the embedded copy from this assembly (release build)." : ("Bundle not found: " + text + ", and this build has no embedded bundle.")));
				}
			}
			value = (Object.op_Implicit((Object)(object)bundle) ? bundle.LoadAsset<AnimationClip>(name) : null);
			if ((Object)(object)value == (Object)null)
			{
				Plugin.Log.LogWarning((object)("Custom clip '" + name + "' not found in the bundle."));
			}
			else if (!Array.Exists(value.events, (AnimationEvent e) => e.functionName == "OnAttackTrigger"))
			{
				Plugin.Log.LogWarning((object)("Custom clip '" + name + "' has no OnAttackTrigger event - it would never hit. Ignoring it."));
				value = null;
			}
			else
			{
				Plugin.Log.LogInfo((object)$"Custom clip '{name}' loaded ({value.length:0.00}s).");
			}
			clips[name] = value;
			return value;
		}

		internal static void Apply(Animator animator, bool on, bool freeHand = false, bool throwing = false, bool torch = false, bool guard = false)
		{
			if ((Object)(object)animator == (Object)null)
			{
				return;
			}
			List<KeyValuePair<string, AnimationClip>> pairs = new List<KeyValuePair<string, AnimationClip>>();
			if (throwing)
			{
				string name = ((freeHand && !string.IsNullOrEmpty(Plugin.ThrowCustomClipFree.Value)) ? Plugin.ThrowCustomClipFree.Value : Plugin.ThrowCustomClip.Value);
				if (torch && (Object)(object)Clip(Plugin.ThrowCustomClip.Value + "_Torch") != (Object)null)
				{
					name = Plugin.ThrowCustomClip.Value + "_Torch";
				}
				AnimationClip val = Clip(name);
				if ((Object)(object)val != (Object)null)
				{
					pairs.Add(new KeyValuePair<string, AnimationClip>(Plugin.ThrowReplacesClip.Value, val));
				}
			}
			else if (on)
			{
				for (int i = 0; i < 3; i++)
				{
					string value = Plugin.HitReplaceClip[i].Value;
					string name2 = ((freeHand && !string.IsNullOrEmpty(Plugin.HitCustomClipFree[i].Value)) ? Plugin.HitCustomClipFree[i].Value : Plugin.HitCustomClip[i].Value);
					if (guard)
					{
						string value2 = Plugin.GuardClip[i].Value;
						name2 = ((freeHand && (Object)(object)Clip(value2 + "_Free") != (Object)null) ? (value2 + "_Free") : ((torch && (Object)(object)Clip(value2 + "_Torch") != (Object)null) ? (value2 + "_Torch") : value2));
					}
					else if (torch && (Object)(object)Clip(Plugin.HitCustomClip[i].Value + "_Torch") != (Object)null)
					{
						name2 = Plugin.HitCustomClip[i].Value + "_Torch";
					}
					AnimationClip val2 = (string.IsNullOrEmpty(value) ? null : Clip(name2));
					if ((Object)(object)val2 != (Object)null)
					{
						pairs.Add(new KeyValuePair<string, AnimationClip>(value, val2));
					}
				}
			}
			string text = string.Join("|", pairs.ConvertAll((KeyValuePair<string, AnimationClip> p) => p.Key + ">" + ((Object)p.Value).name));
			slots.TryGetValue(animator, out var slot);
			if (slot != null && (Object)(object)animator.runtimeAnimatorController != (Object)(object)slot.Controller)
			{
				slots.Remove(animator);
				slot = null;
			}
			if (slot == null)
			{
				if (pairs.Count == 0)
				{
					return;
				}
				slot = Wrap(animator);
				if (slot == null)
				{
					return;
				}
			}
			if (slot.AppliedSignature == text)
			{
				return;
			}
			Preserving(animator, delegate
			{
				List<KeyValuePair<AnimationClip, AnimationClip>> list = new List<KeyValuePair<AnimationClip, AnimationClip>>();
				slot.Controller.GetOverrides(list);
				for (int j = 0; j < list.Count; j++)
				{
					AnimationClip key = list[j].Key;
					AnimationClip value3 = null;
					foreach (KeyValuePair<string, AnimationClip> item in pairs)
					{
						if ((Object)(object)key != (Object)null && ((Object)key).name == item.Key)
						{
							value3 = item.Value;
						}
					}
					list[j] = new KeyValuePair<AnimationClip, AnimationClip>(key, value3);
				}
				slot.Controller.ApplyOverrides((IList<KeyValuePair<AnimationClip, AnimationClip>>)list);
			});
			slot.AppliedSignature = text;
		}

		private static Slot Wrap(Animator animator)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Expected O, but got Unknown
			RuntimeAnimatorController runtimeAnimatorController = animator.runtimeAnimatorController;
			if ((Object)(object)runtimeAnimatorController == (Object)null)
			{
				return null;
			}
			if (runtimeAnimatorController is AnimatorOverrideController)
			{
				Plugin.Log.LogWarning((object)"Player animator is already overridden by another mod; custom clips are off to avoid a conflict.");
				return null;
			}
			AnimatorOverrideController controller = new AnimatorOverrideController(runtimeAnimatorController)
			{
				name = ((Object)runtimeAnimatorController).name + " (SpearCombo)"
			};
			Preserving(animator, delegate
			{
				animator.runtimeAnimatorController = (RuntimeAnimatorController)(object)controller;
			});
			Slot slot = new Slot
			{
				Controller = controller,
				Base = runtimeAnimatorController
			};
			slots.Add(animator, slot);
			return slot;
		}

		private static void Preserving(Animator animator, Action change)
		{
			//IL_0034: 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)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Expected I4, but got Unknown
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Expected I4, but got Unknown
			//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
			int layerCount = animator.layerCount;
			AnimatorStateInfo[] array = (AnimatorStateInfo[])(object)new AnimatorStateInfo[layerCount];
			float[] array2 = new float[layerCount];
			for (int i = 0; i < layerCount; i++)
			{
				array[i] = (animator.IsInTransition(i) ? animator.GetNextAnimatorStateInfo(i) : animator.GetCurrentAnimatorStateInfo(i));
				array2[i] = animator.GetLayerWeight(i);
			}
			AnimatorControllerParameter[] parameters = animator.parameters;
			float[] array3 = new float[parameters.Length];
			for (int j = 0; j < parameters.Length; j++)
			{
				AnimatorControllerParameterType type = parameters[j].type;
				switch (type - 1)
				{
				case 0:
					array3[j] = animator.GetFloat(parameters[j].nameHash);
					break;
				case 2:
					array3[j] = animator.GetInteger(parameters[j].nameHash);
					break;
				case 3:
					array3[j] = (animator.GetBool(parameters[j].nameHash) ? 1f : 0f);
					break;
				}
			}
			float speed = animator.speed;
			change();
			animator.Update(0f);
			for (int k = 0; k < parameters.Length; k++)
			{
				AnimatorControllerParameterType type = parameters[k].type;
				switch (type - 1)
				{
				case 0:
					animator.SetFloat(parameters[k].nameHash, array3[k]);
					break;
				case 2:
					animator.SetInteger(parameters[k].nameHash, (int)array3[k]);
					break;
				case 3:
					animator.SetBool(parameters[k].nameHash, array3[k] != 0f);
					break;
				}
			}
			for (int l = 0; l < layerCount && l < animator.layerCount; l++)
			{
				animator.SetLayerWeight(l, array2[l]);
				AnimatorStateInfo currentAnimatorStateInfo = animator.GetCurrentAnimatorStateInfo(l);
				if (((AnimatorStateInfo)(ref currentAnimatorStateInfo)).fullPathHash != ((AnimatorStateInfo)(ref array[l])).fullPathHash)
				{
					animator.Play(((AnimatorStateInfo)(ref array[l])).fullPathHash, l, ((AnimatorStateInfo)(ref array[l])).normalizedTime);
				}
			}
			animator.speed = speed;
		}
	}
	[HarmonyPatch(typeof(Humanoid), "EquipItem")]
	internal static class EquipPatch
	{
		private static void Prefix(ItemData item)
		{
			Combo.Apply(item?.m_shared);
		}
	}
	[HarmonyPatch(typeof(VisEquipment), "AttachItem")]
	internal static class AttachPatch
	{
		private static void Postfix(int itemHash, GameObject __result)
		{
			if (Plugin.Flip.Value && !((Object)(object)__result == (Object)null))
			{
				SharedData val = Combo.SharedFromHash(itemHash);
				if (Combo.IsComboSpear(val))
				{
					SpearFlip.Flip(__result, val.m_name == "$item_spear_wolffang");
				}
			}
		}
	}
	[HarmonyPatch(typeof(ZSyncAnimation), "RPC_SetTrigger")]
	internal static class TriggerPatch
	{
		private static string lastGuardName;

		private static float lastGuardAt = -10f;

		private static string lastComboName;

		private static float lastComboAt = -10f;

		private static void Prefix(ZSyncAnimation __instance, ref string name)
		{
			if (name == null || name.StartsWith("spear_dup_ignored", StringComparison.Ordinal))
			{
				return;
			}
			if (name.StartsWith("spearguard", StringComparison.Ordinal))
			{
				ComboState component = ((Component)__instance).GetComponent<ComboState>();
				bool flag = (Object)(object)component != (Object)null && component.Level >= 0 && Time.time < component.LastActive + 0.05f;
				if (name == lastGuardName && (flag || Time.time - lastGuardAt < 0.12f))
				{
					Plugin.Log.LogDebug((object)("Duplicate guard trigger '" + name + "' swallowed (would have restarted the hit)."));
					GuardCombo.SwallowedSinceStart = true;
					name = "spear_dup_ignored";
					return;
				}
				lastGuardName = name;
				lastGuardAt = Time.time;
				name = Combo.MapTrigger(name, "spearguard", out var level);
				ComboState.Begin(__instance, level, guard: true);
				if (Refs.AnimatorOf != null)
				{
					CustomClips.Apply(Refs.AnimatorOf.Invoke(__instance), on: true, LeftHandEmpty((Component)(object)__instance), throwing: false, LeftHandTorch((Component)(object)__instance), guard: true);
				}
				return;
			}
			if (name.StartsWith("spearcombo", StringComparison.Ordinal))
			{
				ComboState component2 = ((Component)__instance).GetComponent<ComboState>();
				bool flag2 = (Object)(object)component2 != (Object)null && component2.Level >= 0 && Time.time < component2.LastActive + 0.05f;
				if (name == lastComboName && (flag2 || Time.time - lastComboAt < 0.12f))
				{
					Plugin.Log.LogDebug((object)("Duplicate combo trigger '" + name + "' swallowed (would have restarted the hit)."));
					name = "spear_dup_ignored";
					return;
				}
				lastComboName = name;
				lastComboAt = Time.time;
				name = Combo.MapTrigger(name, out var level2);
				ComboState.Begin(__instance, level2);
				if (Refs.AnimatorOf != null)
				{
					CustomClips.Apply(Refs.AnimatorOf.Invoke(__instance), on: true, LeftHandEmpty((Component)(object)__instance), throwing: false, LeftHandTorch((Component)(object)__instance));
				}
				return;
			}
			if (name == "spear_throw" && Plugin.JumpBackThrow.Value && Combo.IsComboSpear(RightWeapon((Component)(object)__instance)) && ComboState.InThrowWindow(__instance) && Refs.AnimatorOf != null && (Object)(object)CustomClips.Clip(LeftHandEmpty((Component)(object)__instance) ? Plugin.ThrowCustomClipFree.Value : Plugin.ThrowCustomClip.Value) != (Object)null)
			{
				CustomClips.Apply(Refs.AnimatorOf.Invoke(__instance), on: true, LeftHandEmpty((Component)(object)__instance), throwing: true, LeftHandTorch((Component)(object)__instance));
				ComboState.Throw(__instance);
				ComboState.MarkSoftThrow(__instance);
				ComboState.Spend(__instance);
				Character component3 = ((Component)__instance).GetComponent<Character>();
				if ((Object)(object)component3 != (Object)null && (Object)(object)component3 == (Object)(object)Player.m_localPlayer)
				{
					(((Component)component3).GetComponent<JumpBack>() ?? ((Component)component3).gameObject.AddComponent<JumpBack>()).Arm(Plugin.JumpBackDelay.Value);
				}
				return;
			}
			if (Refs.AnimatorOf != null)
			{
				CustomClips.Apply(Refs.AnimatorOf.Invoke(__instance), on: false);
			}
			if (name == "spear_throw")
			{
				ComboState.End(__instance);
				GameObject val = SpearFlip.RightItemOf((Component)(object)__instance);
				FlipMark flipMark = (((Object)(object)val != (Object)null) ? val.GetComponent<FlipMark>() : null);
				if ((Object)(object)flipMark != (Object)null)
				{
					flipMark.Flipped = false;
					flipMark.ReflipAt = Time.time + 1.5f;
					SpearFlip.ApplyPose(val, flipMark, -1);
				}
			}
		}

		internal static SharedData RightWeapon(Component c)
		{
			Humanoid component = c.GetComponent<Humanoid>();
			VisEquipment val = (((Object)(object)component != (Object)null) ? component.GetVisEquipment() : null);
			if (!((Object)(object)val != (Object)null) || Refs.RightItemHash == null)
			{
				return null;
			}
			return Combo.SharedFromHash(Refs.RightItemHash.Invoke(val));
		}

		internal static bool LeftHandEmpty(Component c)
		{
			Humanoid component = c.GetComponent<Humanoid>();
			VisEquipment val = (((Object)(object)component != (Object)null) ? component.GetVisEquipment() : null);
			if ((Object)(object)val != (Object)null && Refs.LeftItemHash != null)
			{
				return Refs.LeftItemHash.Invoke(val) == 0;
			}
			return false;
		}

		internal static bool LeftHandTorch(Component c)
		{
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Invalid comparison between Unknown and I4
			Humanoid component = c.GetComponent<Humanoid>();
			VisEquipment val = (((Object)(object)component != (Object)null) ? component.GetVisEquipment() : null);
			if ((Object)(object)val == (Object)null || Refs.LeftItemHash == null)
			{
				return false;
			}
			SharedData val2 = Combo.SharedFromHash(Refs.LeftItemHash.Invoke(val));
			if (val2 != null)
			{
				return (int)val2.m_itemType == 15;
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(ZSyncAnimation), "SyncParameters")]
	internal static class ComboUpdatePatch
	{
		private const float ThrowFallbackSeconds = 1.3f;

		private static void Postfix(ZSyncAnimation __instance)
		{
			GameObject val = SpearFlip.RightItemOf((Component)(object)__instance);
			FlipMark flipMark = (((Object)(object)val != (Object)null) ? val.GetComponent<FlipMark>() : null);
			ComboState component = ((Component)__instance).GetComponent<ComboState>();
			int num = Track(__instance, component);
			GuardCombo.KeepQueuedClick(component, __instance);
			GuardCombo.SuppressBlockPose(__instance, component);
			FireThrowIfDue(__instance, component);
			if (num < 0)
			{
				PreloadClips(__instance, component);
			}
			if ((Object)(object)flipMark != (Object)null && Plugin.Flip.Value)
			{
				if (!flipMark.Flipped && num < 0 && Time.time > flipMark.ReflipAt)
				{
					flipMark.Flipped = true;
					SpearFlip.ApplyPose(val, flipMark, -1);
				}
				else if (num >= 0 || flipMark.PoseActive)
				{
					SpearFlip.ApplyPose(val, flipMark, num);
				}
			}
		}

		private static void PreloadClips(ZSyncAnimation zanim, ComboState st)
		{
			if (Refs.AnimatorOf == null || !Combo.IsComboSpear(TriggerPatch.RightWeapon((Component)(object)zanim)))
			{
				return;
			}
			Humanoid component = ((Component)zanim).GetComponent<Humanoid>();
			if ((Object)(object)component == (Object)null)
			{
				return;
			}
			int num;
			if (Plugin.DefenceCombo.Value)
			{
				if (!((Object)(object)component == (Object)(object)Player.m_localPlayer) || !(Refs.Blocking != null))
				{
					num = (((Character)component).IsBlocking() ? 1 : 0);
				}
				else
				{
					object value = Refs.Blocking.GetValue(component);
					bool flag = default(bool);
					int num2;
					if (value is bool)
					{
						flag = (bool)value;
						num2 = 1;
					}
					else
					{
						num2 = 0;
					}
					num = num2 & (flag ? 1 : 0);
				}
			}
			else
			{
				num = 0;
			}
			bool flag2 = (byte)num != 0;
			bool flag3 = TriggerPatch.LeftHandEmpty((Component)(object)zanim);
			bool flag4 = TriggerPatch.LeftHandTorch((Component)(object)zanim);
			int num3 = (int)((flag2 ? 1u : 0u) | (uint)(flag3 ? 2 : 0)) | (flag4 ? 4 : 0);
			if ((Object)(object)st == (Object)null)
			{
				st = ((Component)zanim).gameObject.AddComponent<ComboState>();
			}
			if (st.PreloadedSet != num3 && !(Time.time < st.LastActive + 0.3f))
			{
				st.PreloadedSet = num3;
				CustomClips.Apply(Refs.AnimatorOf.Invoke(zanim), on: true, flag3, throwing: false, flag4, flag2);
			}
		}

		private static void FireThrowIfDue(ZSyncAnimation zanim, ComboState st)
		{
			if ((Object)(object)st == (Object)null || st.ThrowAt <= 0f || st.ThrowFired)
			{
				return;
			}
			if (st.PendingThrow == null && Refs.CurrentAttack != null)
			{
				Humanoid component = ((Component)zanim).GetComponent<Humanoid>();
				if ((Object)(object)component != (Object)null)
				{
					object? value = Refs.CurrentAttack.GetValue(component);
					Attack val = (Attack)((value is Attack) ? value : null);
					if (val != null && val.m_attackAnimation == "spear_throw")
					{
						st.PendingThrow = val;
					}
				}
			}
			if (Time.time < st.ThrowAt + 1.3f)
			{
				return;
			}
			st.ThrowFired = true;
			Attack pendingThrow = st.PendingThrow;
			st.PendingThrow = null;
			if (pendingThrow == null || Refs.FireProjectileBurst == null)
			{
				Plugin.Log.LogError((object)"Jump-back throw: no attack captured, spear not released.");
				return;
			}
			if (GameThrowWatch.FiredSince(pendingThrow, st.ThrowAt))
			{
				Plugin.Log.LogInfo((object)"Jump-back throw: spear released by its own trigger at the throwing motion (no duplicate).");
				return;
			}
			try
			{
				Refs.FireProjectileBurst.Invoke(pendingThrow, null);
				Plugin.Log.LogWarning((object)("Jump-back throw: its trigger never arrived - fallback release from '" + pendingThrow.m_attackAnimation + "'."));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not fire the throw projectile: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private static float HoldGuardPose(ComboState st, Animator anim, float playSpeed)
		{
			//IL_0036: 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)
			float value = Plugin.GuardHold.Value;
			if (!st.Guard || value <= 0f)
			{
				st.GuardHolding = false;
				return playSpeed;
			}
			if (!st.GuardHolding && !st.GuardHoldDone)
			{
				AnimatorStateInfo currentAnimatorStateInfo = anim.GetCurrentAnimatorStateInfo(0);
				if (((AnimatorStateInfo)(ref currentAnimatorStateInfo)).normalizedTime >= 0.97f)
				{
					st.GuardHolding = true;
					st.GuardHoldFrom = Time.time;
				}
			}
			if (st.GuardHolding)
			{
				if (Time.time - st.GuardHoldFrom < value)
				{
					return 0.0001f;
				}
				st.GuardHolding = false;
				st.GuardHoldDone = true;
			}
			return playSpeed;
		}

		private static int Track(ZSyncAnimation zanim, ComboState st)
		{
			if ((Object)(object)st == (Object)null || st.Level < 0)
			{
				RestoreAnimSpeed(zanim);
				return -1;
			}
			if (Time.time > st.ExpireAt)
			{
				st.Level = -1;
				RestoreAnimSpeed(zanim);
				return -1;
			}
			if (Time.time < st.CaptureAt || Refs.AnimatorOf == null)
			{
				return st.Level;
			}
			Animator val = Refs.AnimatorOf.Invoke(zanim);
			if ((Object)(object)val == (Object)null)
			{
				return st.Level;
			}
			string text = TopClip(val.GetCurrentAnimatorClipInfo(0));
			string text2 = (val.IsInTransition(0) ? TopClip(val.GetNextAnimatorClipInfo(0)) : null);
			if (st.Clip == null)
			{
				st.Clip = text2 ?? text;
				if (st.Clip == null)
				{
					return st.Level;
				}
			}
			if (text == st.Clip || text2 == st.Clip)
			{
				val.speed = HoldGuardPose(st, val, Plugin.HitSpeed[st.Level].Value);
				st.LastActive = Time.time;
				return st.Level;
			}
			st.Level = -1;
			RestoreAnimSpeed(zanim);
			return -1;
		}

		private static void RestoreAnimSpeed(ZSyncAnimation zanim)
		{
			if (Refs.AnimatorOf != null)
			{
				Animator val = Refs.AnimatorOf.Invoke(zanim);
				if ((Object)(object)val != (Object)null && !Mathf.Approximately(val.speed, 1f))
				{
					val.speed = 1f;
				}
			}
		}

		private static string TopClip(AnimatorClipInfo[] infos)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			string result = null;
			float num = -1f;
			for (int i = 0; i < infos.Length; i++)
			{
				AnimatorClipInfo val = infos[i];
				if ((Object)(object)((AnimatorClipInfo)(ref val)).clip != (Object)null && ((AnimatorClipInfo)(ref val)).weight > num)
				{
					num = ((AnimatorClipInfo)(ref val)).weight;
					result = ((Object)((AnimatorClipInfo)(ref val)).clip).name;
				}
			}
			return result;
		}
	}
}