Decompiled source of Headhunter v0.6.2

Headhunter.dll

Decompiled 12 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using R2API;
using RoR2;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.Rendering;

[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("Headhunter")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+33ab65e1de3fca114d2aea8766c52bfbf4f51a36")]
[assembly: AssemblyProduct("Headhunter")]
[assembly: AssemblyTitle("Headhunter")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace Headshot;

public sealed class HeadshotArmPose : MonoBehaviour
{
	public Transform HandBone { get; set; }

	private void LateUpdate()
	{
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_003d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_005e: Unknown result type (might be due to invalid IL or missing references)
		//IL_005f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0064: 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_007e: Unknown result type (might be due to invalid IL or missing references)
		//IL_007f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0084: Unknown result type (might be due to invalid IL or missing references)
		//IL_009a: Unknown result type (might be due to invalid IL or missing references)
		//IL_009d: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)HandBone == (Object)null || (Object)(object)HandBone.parent == (Object)null)
		{
			return;
		}
		Vector3 val = HandBone.position - HandBone.parent.position;
		if (!(((Vector3)(ref val)).sqrMagnitude < 1E-06f))
		{
			((Vector3)(ref val)).Normalize();
			Vector3 val2 = Vector3.ProjectOnPlane(Vector3.up, val);
			if (((Vector3)(ref val2)).sqrMagnitude < 1E-06f)
			{
				val2 = Vector3.ProjectOnPlane(HandBone.right, val);
			}
			if (!(((Vector3)(ref val2)).sqrMagnitude < 1E-06f))
			{
				((Component)this).transform.rotation = Quaternion.LookRotation(val, ((Vector3)(ref val2)).normalized);
			}
		}
	}
}
public enum HeadDetectionMode
{
	Legacy,
	Strict,
	StrictOrWeakPoint
}
public static class HeadshotDetection
{
	public readonly struct HurtboxFacts
	{
		public string Name { get; }

		public IReadOnlyList<string> Ancestors { get; }

		public bool IsSniperTarget { get; }

		public bool IsMain { get; }

		public int GroupSize { get; }

		public HurtboxFacts(string name, IReadOnlyList<string> ancestorsNearestFirst, bool isSniperTarget, bool isMain, int groupSize)
		{
			Name = name;
			Ancestors = ancestorsNearestFirst ?? Array.Empty<string>();
			IsSniperTarget = isSniperTarget;
			IsMain = isMain;
			GroupSize = groupSize;
		}
	}

	public readonly struct HitFacts
	{
		public bool WeakPointDamageType { get; }

		public float MinSqrDistanceToSniperTarget { get; }

		public float SniperTargetRadius { get; }

		public HitFacts(bool weakPointDamageType, float minSqrDistanceToSniperTarget, float sniperTargetRadius = 1f)
		{
			WeakPointDamageType = weakPointDamageType;
			MinSqrDistanceToSniperTarget = minSqrDistanceToSniperTarget;
			SniperTargetRadius = sniperTargetRadius;
		}
	}

	public const int StrictMaxDepth = 2;

	public static readonly string[] DenyTokens = new string[3] { "neck", "base", "root" };

	public const float DefaultSniperTargetRadius = 1f;

	public const HeadDetectionMode DefaultMode = HeadDetectionMode.StrictOrWeakPoint;

	public static bool IsHead(HeadDetectionMode mode, in HurtboxFacts facts)
	{
		return mode switch
		{
			HeadDetectionMode.Strict => IsHeadStrict(in facts), 
			HeadDetectionMode.StrictOrWeakPoint => IsHeadStrict(in facts), 
			_ => IsHeadLegacy(in facts), 
		};
	}

	public static bool IsHeadLegacy(in HurtboxFacts facts)
	{
		foreach (string ancestor in facts.Ancestors)
		{
			if (!string.IsNullOrEmpty(ancestor) && ancestor.IndexOf("head", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return true;
			}
		}
		return false;
	}

	public static bool IsHeadStrict(in HurtboxFacts facts)
	{
		if (facts.GroupSize == 1)
		{
			return false;
		}
		IReadOnlyList<string> ancestors = facts.Ancestors;
		for (int i = 0; i < ancestors.Count; i++)
		{
			if (IsHeadBoneName(ancestors[i]) && (i < 2 || facts.IsSniperTarget))
			{
				return true;
			}
		}
		return false;
	}

	public static bool IsWeakPointHit(in HitFacts hit)
	{
		if (hit.WeakPointDamageType)
		{
			return true;
		}
		float minSqrDistanceToSniperTarget = hit.MinSqrDistanceToSniperTarget;
		float sniperTargetRadius = hit.SniperTargetRadius;
		if (!float.IsNaN(minSqrDistanceToSniperTarget) && minSqrDistanceToSniperTarget >= 0f && sniperTargetRadius > 0f)
		{
			return minSqrDistanceToSniperTarget <= sniperTargetRadius * sniperTargetRadius;
		}
		return false;
	}

	public static bool WeakPointsCount(HeadDetectionMode mode, bool bodyHasHead)
	{
		if (mode == HeadDetectionMode.StrictOrWeakPoint)
		{
			return !bodyHasHead;
		}
		return false;
	}

	public static bool IsHeadOrWeakPointHit(HeadDetectionMode mode, in HurtboxFacts facts, in HitFacts hit, bool bodyHasHead = false)
	{
		if (IsHead(mode, in facts))
		{
			return true;
		}
		if (WeakPointsCount(mode, bodyHasHead))
		{
			return IsWeakPointHit(in hit);
		}
		return false;
	}

	public static bool IsHeadBoneName(string name)
	{
		if (string.IsNullOrEmpty(name))
		{
			return false;
		}
		bool result = false;
		foreach (string item in Tokenize(name))
		{
			if (item == "head")
			{
				result = true;
			}
			else if (Array.IndexOf(DenyTokens, item) >= 0)
			{
				return false;
			}
		}
		return result;
	}

	public static List<string> Tokenize(string name)
	{
		List<string> list = new List<string>();
		StringBuilder stringBuilder = new StringBuilder();
		char c = '\0';
		foreach (char c2 in name)
		{
			bool num = char.IsLetter(c2);
			if ((!num || (char.IsUpper(c2) && char.IsLower(c))) && stringBuilder.Length > 0)
			{
				list.Add(stringBuilder.ToString());
				stringBuilder.Clear();
			}
			if (num)
			{
				stringBuilder.Append(char.ToLowerInvariant(c2));
			}
			c = c2;
		}
		if (stringBuilder.Length > 0)
		{
			list.Add(stringBuilder.ToString());
		}
		return list;
	}

	public static HeadDetectionMode ParseMode(string text)
	{
		if (!string.IsNullOrWhiteSpace(text) && Enum.TryParse<HeadDetectionMode>(text.Trim(), ignoreCase: true, out var result) && Enum.IsDefined(typeof(HeadDetectionMode), result))
		{
			return result;
		}
		return HeadDetectionMode.StrictOrWeakPoint;
	}
}
public static class HeadshotHandDisplay
{
	public struct Placement
	{
		public Vector3 Position;

		public Vector3 Angles;
	}

	private static readonly string[] PreferredBones = new string[3] { "HandR", "HandL", "Hand" };

	private static readonly Dictionary<string, Placement> Placements = new Dictionary<string, Placement>
	{
		{
			"HandR",
			new Placement
			{
				Position = new Vector3(0f, 0.15f, 0f),
				Angles = Vector3.zero
			}
		},
		{
			"HandL",
			new Placement
			{
				Position = new Vector3(0.1f, 0f, 0f),
				Angles = new Vector3(0f, 0f, 180f)
			}
		},
		{
			"Hand",
			new Placement
			{
				Position = new Vector3(-0.0689f, 0.1308f, 0.1029f),
				Angles = new Vector3(277.149f, 27.858f, 151.16f)
			}
		}
	};

	private static readonly HashSet<CharacterModel> Attached = new HashSet<CharacterModel>();

	private static readonly HashSet<string> ReportedBodies = new HashSet<string>();

	private static ItemIndex _deadeyeIndex = (ItemIndex)(-1);

	private static bool _deadeyeIndexResolved;

	private static ManualLogSource _logger;

	public static Vector3? PositionOverride { get; set; }

	public static Vector3? AnglesOverride { get; set; }

	public static void SetLogger(ManualLogSource logger)
	{
		_logger = logger;
	}

	public static CharacterBody Update(Inventory inventory)
	{
		if ((Object)(object)inventory == (Object)null)
		{
			return null;
		}
		CharacterBody val = FindBody(inventory);
		if ((Object)(object)val == (Object)null)
		{
			int num = DeadeyeCount(inventory);
			if (num > 0)
			{
				LogWarning($"display update: inventory has {num} Deadeye but no CharacterBody was found " + "(object '" + ((Object)inventory).name + "')");
			}
			return null;
		}
		CharacterMaster master = val.master;
		int num2 = DeadeyeCount(inventory);
		CharacterModel val2 = FindModel(val);
		if (num2 > 0 || ((Object)(object)val2 != (Object)null && Attached.Contains(val2)))
		{
			bool flag = (Object)(object)master != (Object)null && (Object)(object)master.playerCharacterMasterController != (Object)null;
			LogInfo($"display update: body '{((Object)val).name}', item count {num2}, " + $"player master {flag}, body flag {val.isPlayerControlled}, " + "model " + (((Object)(object)val2 != (Object)null) ? "ready" : "missing"));
		}
		Apply(val);
		return val;
	}

	public static bool Apply(CharacterBody body)
	{
		if ((Object)(object)body == (Object)null)
		{
			return false;
		}
		CharacterModel val = FindModel(body);
		if ((Object)(object)val == (Object)null)
		{
			return false;
		}
		CharacterMaster master = body.master;
		bool num = (Object)(object)master != (Object)null && (Object)(object)master.playerCharacterMasterController != (Object)null;
		bool flag = HeadshotLogic.SkipsHandDisplay(((Object)body).name, body.baseNameToken);
		bool flag2 = num && !flag && HoldsDeadeye(body);
		bool flag3 = Attached.Contains(val);
		if (flag2 && !flag3)
		{
			if (Attach(val))
			{
				Attached.Add(val);
				return true;
			}
			return false;
		}
		if (!flag2 && flag3)
		{
			Detach(val);
			Attached.Remove(val);
			return false;
		}
		return flag3;
	}

	public static void CleanUp()
	{
		if (Attached.Count == 0)
		{
			return;
		}
		List<CharacterModel> list = null;
		foreach (CharacterModel item in Attached)
		{
			if ((Object)(object)item == (Object)null)
			{
				if (list == null)
				{
					list = new List<CharacterModel>();
				}
				list.Add(item);
			}
		}
		if (list != null)
		{
			for (int i = 0; i < list.Count; i++)
			{
				Attached.Remove(list[i]);
			}
		}
	}

	private static CharacterBody FindBody(Inventory inventory)
	{
		try
		{
			CharacterMaster component = ((Component)inventory).GetComponent<CharacterMaster>();
			if ((Object)(object)component != (Object)null)
			{
				CharacterBody body = component.GetBody();
				if ((Object)(object)body != (Object)null)
				{
					return body;
				}
			}
			return ((Component)inventory).GetComponentInParent<CharacterBody>();
		}
		catch (Exception ex)
		{
			LogWarning("could not find the body of an inventory: " + ex.Message);
			return null;
		}
	}

	private static CharacterModel FindModel(CharacterBody body)
	{
		try
		{
			ModelLocator modelLocator = body.modelLocator;
			if ((Object)(object)modelLocator != (Object)null && (Object)(object)modelLocator.modelTransform != (Object)null)
			{
				CharacterModel component = ((Component)modelLocator.modelTransform).GetComponent<CharacterModel>();
				if ((Object)(object)component != (Object)null)
				{
					return component;
				}
			}
			return ((Component)body).GetComponentInChildren<CharacterModel>(true);
		}
		catch (Exception ex)
		{
			LogWarning("could not find the model of a body: " + ex.Message);
			return null;
		}
	}

	public static bool HoldsDeadeye(CharacterBody body)
	{
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_0013: Invalid comparison between Unknown and I4
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)body == (Object)null)
		{
			return false;
		}
		ItemIndex val = DeadeyeIndex();
		if ((int)val == -1)
		{
			return false;
		}
		CharacterMaster master = body.master;
		Inventory val2 = (((Object)(object)master != (Object)null) ? master.inventory : body.inventory);
		if ((Object)(object)val2 == (Object)null)
		{
			return false;
		}
		try
		{
			return val2.GetItemCountTotal(val) > 0;
		}
		catch (Exception ex)
		{
			LogWarning("could not read the item count: " + ex.Message);
			return false;
		}
	}

	private static int DeadeyeCount(Inventory inventory)
	{
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_0013: Invalid comparison between Unknown and I4
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)inventory == (Object)null)
		{
			return 0;
		}
		ItemIndex val = DeadeyeIndex();
		if ((int)val == -1)
		{
			return 0;
		}
		try
		{
			return inventory.GetItemCountTotal(val);
		}
		catch (Exception ex)
		{
			LogWarning("could not read the inventory count during display update: " + ex.Message);
			return 0;
		}
	}

	private static ItemIndex DeadeyeIndex()
	{
		//IL_0007: 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_0013: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Invalid comparison between Unknown and I4
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		if (_deadeyeIndexResolved)
		{
			return _deadeyeIndex;
		}
		try
		{
			ItemIndex val = ItemCatalog.FindItemIndex("Deadeye");
			if ((int)val != -1)
			{
				_deadeyeIndex = val;
				_deadeyeIndexResolved = true;
			}
		}
		catch (Exception ex)
		{
			LogWarning("could not look the item up: " + ex.Message);
		}
		return _deadeyeIndex;
	}

	private static bool Attach(CharacterModel model)
	{
		//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00be: Unknown result type (might be due to invalid IL or missing references)
		//IL_00db: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e0: 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_010a: 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_011a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0131: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ca: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
		GameObject handPrefab = HeadshotModel.HandPrefab;
		if ((Object)(object)handPrefab == (Object)null)
		{
			LogWarning("hand prefab is unavailable (not built or destroyed with its scene)");
			return false;
		}
		ChildLocator componentInChildren = ((Component)model).GetComponentInChildren<ChildLocator>(true);
		if ((Object)(object)componentInChildren == (Object)null)
		{
			ReportOnce(model, "no ChildLocator on the model");
			return false;
		}
		Transform val = null;
		string text = null;
		for (int i = 0; i < PreferredBones.Length; i++)
		{
			Transform val2 = componentInChildren.FindChild(PreferredBones[i]);
			if ((Object)(object)val2 != (Object)null)
			{
				val = val2;
				text = PreferredBones[i];
				break;
			}
		}
		if ((Object)(object)val == (Object)null)
		{
			ReportOnce(model, "no HandR / HandL / Hand bone");
			return false;
		}
		Placement placement = Placements[text];
		Vector3 val3 = (Vector3)(((??)PositionOverride) ?? placement.Position);
		Vector3 val4 = (Vector3)(((??)AnglesOverride) ?? placement.Angles);
		float x = HeadshotModel.HandLocalScale.x;
		GameObject val5 = Object.Instantiate<GameObject>(handPrefab, val);
		((Object)val5).name = "DeadeyeInHand";
		val5.transform.localPosition = val3;
		val5.transform.localRotation = Quaternion.Euler(val4);
		val5.transform.localScale = new Vector3(x, x, x);
		if (text == "HandR" && !AnglesOverride.HasValue)
		{
			val5.AddComponent<HeadshotArmPose>().HandBone = val;
		}
		val5.SetActive(true);
		if ((Object)(object)val5.GetComponent<ItemDisplay>() == (Object)null)
		{
			LogWarning("the hand model has no ItemDisplay component; not attaching it");
			Object.Destroy((Object)(object)val5);
			return false;
		}
		model.AddCustomDisplayPrefab(val5, handPrefab);
		LogInfo("pistol in hand: body '" + ((Object)model).name + "', bone '" + text + "', " + $"pos {val3}, angles {val4}, scale {x}, " + string.Format("arm-aligned pose {0}", text == "HandR" && !AnglesOverride.HasValue));
		return true;
	}

	private static void Detach(CharacterModel model)
	{
		GameObject handPrefab = HeadshotModel.HandPrefab;
		if (!((Object)(object)handPrefab == (Object)null) && !((Object)(object)model == (Object)null))
		{
			model.RemoveCustomDisplayPrefab(handPrefab);
			LogInfo("pistol removed from hand: body '" + ((Object)model).name + "'");
		}
	}

	private static void ReportOnce(CharacterModel model, string reason)
	{
		string item = ((Object)model).name + "|" + reason;
		if (ReportedBodies.Add(item))
		{
			LogWarning("no pistol for body '" + ((Object)model).name + "': " + reason);
		}
	}

	private static void LogInfo(string message)
	{
		if (_logger != null)
		{
			_logger.LogInfo((object)("[Headhunter] " + message));
		}
	}

	private static void LogWarning(string message)
	{
		if (_logger != null)
		{
			_logger.LogWarning((object)("[Headhunter] " + message));
		}
	}
}
public static class HeadshotLogic
{
	public enum HeadHitOutcome
	{
		NotEligible,
		Body,
		Forced,
		NaturalCritBonus,
		AlreadyApplied
	}

	public readonly struct HeadHitDecision
	{
		public HeadHitOutcome Outcome { get; }

		public bool SetCrit { get; }

		public int Multiplier { get; }

		public bool MarksApplied
		{
			get
			{
				if (Outcome != HeadHitOutcome.Forced)
				{
					return Outcome == HeadHitOutcome.NaturalCritBonus;
				}
				return true;
			}
		}

		public string LogLabel => Outcome switch
		{
			HeadHitOutcome.Forced => $"HEAD -> crit FORCED, damage x{Multiplier}", 
			HeadHitOutcome.NaturalCritBonus => $"HEAD -> natural crit + bonus, damage x{Multiplier}", 
			HeadHitOutcome.AlreadyApplied => "HEAD -> already applied to this DamageInfo, skipped", 
			HeadHitOutcome.Body => "BODY -> untouched", 
			_ => "not eligible", 
		};

		public HeadHitDecision(HeadHitOutcome outcome, bool setCrit, int multiplier)
		{
			Outcome = outcome;
			SetCrit = setCrit;
			Multiplier = multiplier;
		}
	}

	public sealed class AppliedOnceMarker<T> where T : class
	{
		private static readonly object Token = new object();

		private ConditionalWeakTable<T, object> _table = new ConditionalWeakTable<T, object>();

		public bool IsMarked(T item)
		{
			object value;
			if (item != null)
			{
				return _table.TryGetValue(item, out value);
			}
			return false;
		}

		public bool Mark(T item)
		{
			if (item == null || IsMarked(item))
			{
				return false;
			}
			_table.Add(item, Token);
			return true;
		}

		public void Clear()
		{
			_table = new ConditionalWeakTable<T, object>();
		}
	}

	public const int PercentPerStack = 100;

	public static readonly string[] Translations = new string[4] { "uk", "de", "fr", "ru" };

	public static string BuildDescriptionText(int stacks, string language = null)
	{
		int num = ExtraHeadshotDamagePercent(stacks);
		return language switch
		{
			"uk" => string.Format("Постріли в голову завдають додатково {0}% шкоди {1}", num, "\"<color=#FFFF00><b>V.F.</b></color>\""), 
			"de" => string.Format("Kopftreffer verursachen zusätzlich {0} % Schaden {1}", num, "\"<color=#FFFF00><b>V.F.</b></color>\""), 
			"fr" => string.Format("Les tirs à la tête infligent {0} % de dégâts supplémentaires {1}", num, "\"<color=#FFFF00><b>V.F.</b></color>\""), 
			"ru" => string.Format("Попадания в голову наносят дополнительно {0}% урона {1}", num, "\"<color=#FFFF00><b>V.F.</b></color>\""), 
			_ => string.Format("Headshots deal {0}% extra damage {1}", num, "\"<color=#FFFF00><b>V.F.</b></color>\""), 
		};
	}

	public static int ExtraHeadshotDamagePercent(int stacks)
	{
		return (2 * CriticalDamageMultiplier(Math.Max(1, stacks)) - 1) * 100;
	}

	public static string LoreText(string language = null)
	{
		return language switch
		{
			"uk" => "Одне око. Одна мета. Цілься в ту їхню частину, що думає.", 
			"de" => "Ein Auge. Ein Ziel. Ziele auf den Teil von ihnen, der denkt.", 
			"fr" => "Un œil. Un seul but. Vise la partie d'eux qui pense.", 
			"ru" => "Один глаз. Одна цель. Целься в ту их часть, что думает.", 
			_ => "One eye. One purpose. Aim for the part of them that thinks.", 
		};
	}

	public static int CriticalDamageMultiplier(int stacks)
	{
		if (stacks <= 1)
		{
			return 1;
		}
		return stacks;
	}

	public static HeadHitDecision DecideHeadHit(bool eligible, bool isHead, int stacks, bool alreadyCrit, bool alreadyApplied)
	{
		if (!eligible || stacks <= 0)
		{
			return new HeadHitDecision(HeadHitOutcome.NotEligible, setCrit: false, 1);
		}
		if (!isHead)
		{
			return new HeadHitDecision(HeadHitOutcome.Body, setCrit: false, 1);
		}
		if (alreadyApplied)
		{
			return new HeadHitDecision(HeadHitOutcome.AlreadyApplied, setCrit: false, 1);
		}
		int multiplier = CriticalDamageMultiplier(stacks);
		if (!alreadyCrit)
		{
			return new HeadHitDecision(HeadHitOutcome.Forced, setCrit: true, multiplier);
		}
		return new HeadHitDecision(HeadHitOutcome.NaturalCritBonus, setCrit: false, multiplier);
	}

	public static void ApplyDecision(in HeadHitDecision decision, ref bool crit, ref float damage)
	{
		if (decision.SetCrit)
		{
			crit = true;
		}
		if (decision.Multiplier > 1)
		{
			damage *= decision.Multiplier;
		}
	}

	public static byte[] InvertRedChannel(byte[] rgba)
	{
		if (rgba == null)
		{
			throw new ArgumentNullException("rgba");
		}
		if (rgba.Length % 4 != 0)
		{
			throw new ArgumentException("The pixel array must hold whole RGBA pixels (length divisible by 4).", "rgba");
		}
		byte[] array = new byte[rgba.Length];
		for (int i = 0; i < rgba.Length; i += 4)
		{
			array[i] = (byte)(255 - rgba[i]);
			array[i + 1] = rgba[i + 1];
			array[i + 2] = rgba[i + 2];
			array[i + 3] = rgba[i + 3];
		}
		return array;
	}

	public static bool SkipsHandDisplay(string bodyObjectName, string baseNameToken)
	{
		if (!string.IsNullOrEmpty(bodyObjectName) && bodyObjectName.StartsWith("ChamberBody", StringComparison.OrdinalIgnoreCase))
		{
			return true;
		}
		return string.Equals(baseNameToken, "CHAMBER_BODY_NAME", StringComparison.OrdinalIgnoreCase);
	}
}
public static class HeadshotModel
{
	public sealed class ObjMeshData
	{
		public Vector3[] Positions;

		public Vector2[] Uvs;

		public Vector3[] Normals;

		public int[] Triangles;
	}

	public static class ObjReader
	{
		public static ObjMeshData Read(string path)
		{
			//IL_00e9: 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_013a: Unknown result type (might be due to invalid IL or missing references)
			List<Vector3> list = new List<Vector3>();
			List<Vector2> list2 = new List<Vector2>();
			List<Vector3> list3 = new List<Vector3>();
			List<int> list4 = new List<int>();
			Dictionary<string, int> vertexMap = new Dictionary<string, int>();
			List<Vector3> list5 = new List<Vector3>();
			List<Vector2> list6 = new List<Vector2>();
			List<Vector3> list7 = new List<Vector3>();
			string[] array = File.ReadAllLines(path);
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i];
				if (text.Length < 2)
				{
					continue;
				}
				string[] array2 = text.Trim().Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
				if (array2.Length >= 2)
				{
					switch (array2[0])
					{
					case "v":
						list.Add(new Vector3(ParseFloat(array2[1], i), ParseFloat(array2[2], i), ParseFloat(array2[3], i)));
						break;
					case "vn":
						list3.Add(new Vector3(ParseFloat(array2[1], i), ParseFloat(array2[2], i), ParseFloat(array2[3], i)));
						break;
					case "vt":
						list2.Add(new Vector2(ParseFloat(array2[1], i), ParseFloat(array2[2], i)));
						break;
					case "f":
						AddFace(array2, list, list2, list3, vertexMap, list5, list6, list7, list4, i);
						break;
					}
				}
			}
			if (list4.Count == 0)
			{
				return null;
			}
			return new ObjMeshData
			{
				Positions = list5.ToArray(),
				Uvs = list6.ToArray(),
				Normals = list7.ToArray(),
				Triangles = list4.ToArray()
			};
		}

		private static void AddFace(string[] parts, List<Vector3> positions, List<Vector2> uvs, List<Vector3> normals, Dictionary<string, int> vertexMap, List<Vector3> outPositions, List<Vector2> outUvs, List<Vector3> outNormals, List<int> triangles, int lineNumber)
		{
			int num = parts.Length - 1;
			if (num >= 3)
			{
				int[] array = new int[num];
				for (int i = 0; i < num; i++)
				{
					array[i] = ResolveCorner(parts[i + 1], positions, uvs, normals, vertexMap, outPositions, outUvs, outNormals, lineNumber);
				}
				for (int j = 1; j + 1 < num; j++)
				{
					triangles.Add(array[0]);
					triangles.Add(array[j]);
					triangles.Add(array[j + 1]);
				}
			}
		}

		private static int ResolveCorner(string token, List<Vector3> positions, List<Vector2> uvs, List<Vector3> normals, Dictionary<string, int> vertexMap, List<Vector3> outPositions, List<Vector2> outUvs, List<Vector3> outNormals, int lineNumber)
		{
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			string[] array = token.Split('/');
			int num = ResolveIndex(array[0], positions.Count);
			int num2 = ((array.Length > 1) ? ResolveIndex(array[1], uvs.Count) : (-1));
			int num3 = ((array.Length > 2) ? ResolveIndex(array[2], normals.Count) : (-1));
			if (num < 0)
			{
				return 0;
			}
			string key = num + "|" + num2 + "|" + num3;
			if (vertexMap.TryGetValue(key, out var value))
			{
				return value;
			}
			int count = outPositions.Count;
			outPositions.Add(positions[num]);
			outUvs.Add((num2 >= 0) ? uvs[num2] : Vector2.zero);
			outNormals.Add((num3 >= 0) ? normals[num3] : Vector3.zero);
			vertexMap[key] = count;
			return count;
		}

		private static int ResolveIndex(string field, int count)
		{
			if (string.IsNullOrEmpty(field))
			{
				return -1;
			}
			if (!int.TryParse(field, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
			{
				return -1;
			}
			if (result > 0)
			{
				if (result - 1 >= count)
				{
					return -1;
				}
				return result - 1;
			}
			if (result < 0)
			{
				int num = count + result;
				if (num < 0 || num >= count)
				{
					return -1;
				}
				return num;
			}
			return -1;
		}

		private static float ParseFloat(string value, int lineNumber)
		{
			if (!float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				throw new FormatException($"Bad number '{value}' on OBJ line {lineNumber + 1}");
			}
			return result;
		}
	}

	public const string ModelFileName = "HeadhunterPistol.obj";

	public const string TextureFileName = "HeadhunterPistol.png";

	public const string NormalMapFileName = "HeadhunterPistolNormal.png";

	private const string TemplateMaterialAddress = "RoR2/Base/Crowbar/matCrowbar.mat";

	private static GameObject _prefab;

	private static GameObject _handPrefab;

	private const float ModelScale = 1f;

	private static Mesh _mesh;

	private static Mesh _handMesh;

	private static Material _material;

	public static Vector3 PickupEuler { get; set; } = new Vector3(0f, -90f, 0f);

	public static Vector3 HandEuler { get; set; } = new Vector3(0f, -90f, 0f);

	public static Vector3 HandLocalScale { get; set; } = new Vector3(1.05f, 1.05f, 1.05f);

	public static bool NormalMapRedFix { get; set; }

	public static GameObject Prefab => _prefab;

	public static GameObject HandPrefab => _handPrefab;

	public static string FileIn(string directory, string name)
	{
		string text = Path.Combine(directory, name);
		string text2 = Path.Combine(directory, name.Replace("Headhunter", "Deadeye"));
		if (!File.Exists(text) && File.Exists(text2))
		{
			return text2;
		}
		return text;
	}

	private static bool EnsureAssets(ManualLogSource logger)
	{
		//IL_0092: Unknown result type (might be due to invalid IL or missing references)
		//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
		//IL_014e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0153: Unknown result type (might be due to invalid IL or missing references)
		//IL_0157: Unknown result type (might be due to invalid IL or missing references)
		//IL_0161: Unknown result type (might be due to invalid IL or missing references)
		//IL_017d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0182: Unknown result type (might be due to invalid IL or missing references)
		//IL_0186: Unknown result type (might be due to invalid IL or missing references)
		//IL_0190: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)_mesh != (Object)null && (Object)(object)_handMesh != (Object)null && (Object)(object)_material != (Object)null)
		{
			return true;
		}
		try
		{
			string directoryName = Path.GetDirectoryName(typeof(HeadshotModel).Assembly.Location);
			string path = FileIn(directoryName, "HeadhunterPistol.obj");
			if (!File.Exists(path))
			{
				logger.LogWarning((object)"[Headhunter] HeadhunterPistol.obj not found next to Headhunter.dll; the pickup will have no 3D model.");
				return false;
			}
			ObjMeshData objMeshData = ObjReader.Read(path);
			if (objMeshData == null || objMeshData.Triangles.Length == 0)
			{
				logger.LogError((object)"[Headhunter] HeadhunterPistol.obj contained no usable geometry.");
				return false;
			}
			Mesh val = BuildMesh(objMeshData, PickupEuler);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			Material val2 = BuildMaterial(directoryName, logger);
			if ((Object)(object)val2 == (Object)null)
			{
				Object.Destroy((Object)(object)val);
				return false;
			}
			_mesh = val;
			_material = val2;
			_handMesh = BuildMesh(objMeshData, HandEuler);
			if ((Object)(object)_handMesh == (Object)null)
			{
				Object.Destroy((Object)(object)val);
				return false;
			}
			string[] obj = new string[7]
			{
				$"[Headhunter] model assets built: {objMeshData.Triangles.Length / 3} triangles, ",
				$"{objMeshData.Positions.Length / 3} vertices, ",
				null,
				null,
				null,
				null,
				null
			};
			Bounds bounds = val.bounds;
			obj[2] = $"pickup bounds {((Bounds)(ref bounds)).size} (euler {PickupEuler}), ";
			bounds = _handMesh.bounds;
			obj[3] = $"hand bounds {((Bounds)(ref bounds)).size} (euler {HandEuler}), ";
			obj[4] = "material '";
			obj[5] = ((Object)val2.shader).name;
			obj[6] = "'";
			logger.LogInfo((object)string.Concat(obj));
			return true;
		}
		catch (Exception arg)
		{
			logger.LogError((object)$"[Headhunter] model assets failed: {arg}");
			return false;
		}
	}

	public static GameObject Build(ManualLogSource logger)
	{
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_0029: Expected O, but got Unknown
		//IL_002e: 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_0045: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)_prefab != (Object)null)
		{
			return _prefab;
		}
		if (!EnsureAssets(logger))
		{
			return null;
		}
		try
		{
			GameObject val = new GameObject("DeadeyePickupModel");
			GameObject val2 = new GameObject("mdlDeadeyePistol");
			val2.transform.SetParent(val.transform, false);
			val2.AddComponent<MeshFilter>().sharedMesh = _mesh;
			((Renderer)val2.AddComponent<MeshRenderer>()).sharedMaterial = _material;
			_prefab = CreateHiddenPrefab(val, "HeadshotDeadeyePickupPrefab");
			logger.LogInfo((object)("[Headhunter] pickup model prefab ready: parent '" + ((Object)_prefab.transform.parent).name + "', " + $"activeSelf {_prefab.activeSelf}, activeInHierarchy {_prefab.activeInHierarchy}"));
			return _prefab;
		}
		catch (Exception arg)
		{
			logger.LogError((object)$"[Headhunter] pickup model build failed: {arg}");
			return null;
		}
	}

	public static GameObject BuildHandPrefab(ManualLogSource logger)
	{
		//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)
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		//IL_006f: Expected O, but got Unknown
		if ((Object)(object)_handPrefab != (Object)null)
		{
			return _handPrefab;
		}
		if (!EnsureAssets(logger))
		{
			return null;
		}
		try
		{
			GameObject val = new GameObject("DeadeyeHandModel");
			val.AddComponent<MeshFilter>().sharedMesh = _handMesh;
			((Renderer)val.AddComponent<MeshRenderer>()).sharedMaterial = _material;
			ItemDisplay obj = val.AddComponent<ItemDisplay>();
			obj.canBeEliteRecolored = false;
			obj.hideOnDeathObjects = Array.Empty<GameObject>();
			FillRendererInfos(obj);
			_handPrefab = CreateHiddenPrefab(val, "HeadshotDeadeyeHandPrefab");
			logger.LogInfo((object)("[Headhunter] hand display prefab ready: parent '" + ((Object)_handPrefab.transform.parent).name + "', " + $"activeSelf {_handPrefab.activeSelf}, activeInHierarchy {_handPrefab.activeInHierarchy}"));
			return _handPrefab;
		}
		catch (Exception arg)
		{
			logger.LogError((object)$"[Headhunter] hand display prefab build failed: {arg}");
			return null;
		}
	}

	private static GameObject CreateHiddenPrefab(GameObject source, string name)
	{
		try
		{
			return PrefabAPI.InstantiateClone(source, name, false);
		}
		finally
		{
			if ((Object)(object)source != (Object)null)
			{
				source.SetActive(false);
				Object.Destroy((Object)(object)source);
			}
		}
	}

	public static void FillRendererInfos(ItemDisplay itemDisplay)
	{
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Unknown result type (might be due to invalid IL or missing references)
		//IL_0062: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: Unknown result type (might be due to invalid IL or missing references)
		if (!((Object)(object)itemDisplay == (Object)null))
		{
			MeshRenderer[] componentsInChildren = ((Component)itemDisplay).GetComponentsInChildren<MeshRenderer>(true);
			RendererInfo[] array = (RendererInfo[])(object)new RendererInfo[componentsInChildren.Length];
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				array[i] = new RendererInfo
				{
					renderer = (Renderer)(object)componentsInChildren[i],
					defaultMaterial = ((Renderer)componentsInChildren[i]).sharedMaterial,
					defaultShadowCastingMode = (ShadowCastingMode)1,
					ignoreOverlays = false,
					hideOnDeath = true,
					ignoresMaterialOverrides = false
				};
			}
			itemDisplay.rendererInfos = array;
		}
	}

	private static Mesh BuildMesh(ObjMeshData data, Vector3 euler)
	{
		//IL_005e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0062: Unknown result type (might be due to invalid IL or missing references)
		//IL_0067: Unknown result type (might be due to invalid IL or missing references)
		//IL_006c: Unknown result type (might be due to invalid IL or missing references)
		//IL_006d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0071: 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_007b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0089: Unknown result type (might be due to invalid IL or missing references)
		//IL_008a: Unknown result type (might be due to invalid IL or missing references)
		//IL_008b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0095: Unknown result type (might be due to invalid IL or missing references)
		//IL_009a: Unknown result type (might be due to invalid IL or missing references)
		//IL_009c: Unknown result type (might be due to invalid IL or missing references)
		//IL_009d: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b6: 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_00c2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cc: 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_0119: Expected O, but got Unknown
		//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f6: 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_0100: Unknown result type (might be due to invalid IL or missing references)
		Vector3[] array = (Vector3[])data.Positions.Clone();
		Vector3[] array2 = ((data.Normals != null) ? ((Vector3[])data.Normals.Clone()) : null);
		Vector3 val = default(Vector3);
		((Vector3)(ref val))..ctor(float.MaxValue, float.MaxValue, float.MaxValue);
		Vector3 val2 = default(Vector3);
		((Vector3)(ref val2))..ctor(float.MinValue, float.MinValue, float.MinValue);
		for (int i = 0; i < array.Length; i++)
		{
			val = Vector3.Min(val, array[i]);
			val2 = Vector3.Max(val2, array[i]);
		}
		Vector3 val3 = (val + val2) * 0.5f;
		Quaternion val4 = Quaternion.Euler(euler);
		for (int j = 0; j < array.Length; j++)
		{
			array[j] = val4 * ((array[j] - val3) * 1f);
		}
		if (array2 != null && array2.Length == array.Length)
		{
			for (int k = 0; k < array2.Length; k++)
			{
				array2[k] = val4 * array2[k];
			}
		}
		Mesh val5 = new Mesh();
		((Object)val5).name = "mdlDeadeyePistol";
		val5.indexFormat = (IndexFormat)(data.Positions.Length / 3 > 65000);
		val5.vertices = array;
		val5.uv = data.Uvs;
		val5.triangles = data.Triangles;
		if (array2 != null && array2.Length == array.Length)
		{
			val5.normals = array2;
		}
		else
		{
			val5.RecalculateNormals();
		}
		try
		{
			val5.RecalculateTangents();
		}
		catch (Exception ex)
		{
			Debug.LogWarning((object)("[Headhunter] could not build tangents for the pistol mesh: " + ex.Message));
		}
		val5.RecalculateBounds();
		return val5;
	}

	private static Material BuildMaterial(string directory, ManualLogSource logger)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: 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_0147: Expected O, but got Unknown
		//IL_007c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0083: Expected O, but got Unknown
		Material val = null;
		try
		{
			val = Addressables.LoadAssetAsync<Material>((object)"RoR2/Base/Crowbar/matCrowbar.mat").WaitForCompletion();
		}
		catch (Exception ex)
		{
			logger.LogWarning((object)("[Headhunter] template material 'RoR2/Base/Crowbar/matCrowbar.mat' failed to load: " + ex.Message));
		}
		Material val2 = (Material)((!((Object)(object)val != (Object)null)) ? ((object)BuildFallbackMaterial(logger)) : ((object)new Material(val)));
		if ((Object)(object)val2 == (Object)null)
		{
			return null;
		}
		((Object)val2).name = "matDeadeyePistol";
		string path = FileIn(directory, "HeadhunterPistol.png");
		if (File.Exists(path))
		{
			try
			{
				Texture2D val3 = new Texture2D(2, 2, (TextureFormat)4, true);
				byte[] array = File.ReadAllBytes(path);
				if (ImageConversion.LoadImage(val3, array, false))
				{
					((Object)val3).name = "texDeadeyePistol";
					((Texture)val3).filterMode = (FilterMode)1;
					((Texture)val3).wrapMode = (TextureWrapMode)1;
					val3.Apply(true);
					val2.SetTexture("_MainTex", (Texture)(object)val3);
					logger.LogInfo((object)$"[Headhunter] pistol texture loaded ({((Texture)val3).width}x{((Texture)val3).height})");
				}
				else
				{
					logger.LogWarning((object)"[Headhunter] pistol texture could not be decoded.");
				}
			}
			catch (Exception ex2)
			{
				logger.LogWarning((object)("[Headhunter] pistol texture load failed: " + ex2.Message));
			}
		}
		else
		{
			logger.LogWarning((object)"[Headhunter] HeadhunterPistol.png not found; the pistol will use a flat material.");
		}
		string path2 = FileIn(directory, "HeadhunterPistolNormal.png");
		if (File.Exists(path2))
		{
			try
			{
				Texture2D val4 = new Texture2D(2, 2, (TextureFormat)4, true);
				byte[] array2 = File.ReadAllBytes(path2);
				if (ImageConversion.LoadImage(val4, array2, false))
				{
					((Object)val4).name = "texDeadeyePistolNormal";
					((Texture)val4).filterMode = (FilterMode)1;
					((Texture)val4).wrapMode = (TextureWrapMode)1;
					if (NormalMapRedFix)
					{
						InvertRedChannel(val4);
					}
					val4.Apply(true);
					if (val2.HasProperty("_NormalTex"))
					{
						val2.SetTexture("_NormalTex", (Texture)(object)val4);
						if (val2.HasProperty("_BumpScale"))
						{
							val2.SetFloat("_BumpScale", 1f);
						}
						if (val2.HasProperty("_NormalStrength"))
						{
							val2.SetFloat("_NormalStrength", 1f);
						}
						logger.LogInfo((object)$"[Headhunter] pistol normal map loaded ({((Texture)val4).width}x{((Texture)val4).height})");
					}
					else if (val2.HasProperty("_BumpMap"))
					{
						val2.SetTexture("_BumpMap", (Texture)(object)val4);
						logger.LogInfo((object)$"[Headhunter] pistol normal map loaded ({((Texture)val4).width}x{((Texture)val4).height})");
					}
					else
					{
						logger.LogWarning((object)"[Headhunter] the material has no normal map property; the normal map is ignored.");
					}
				}
				else
				{
					logger.LogWarning((object)"[Headhunter] pistol normal map could not be decoded.");
				}
			}
			catch (Exception ex3)
			{
				logger.LogWarning((object)("[Headhunter] pistol normal map load failed: " + ex3.Message));
			}
		}
		if (val2.HasProperty("_Metallic"))
		{
			val2.SetFloat("_Metallic", 0.65f);
		}
		if (val2.HasProperty("_Smoothness"))
		{
			val2.SetFloat("_Smoothness", 0.45f);
		}
		return val2;
	}

	private static void InvertRedChannel(Texture2D texture)
	{
		Color32[] pixels = texture.GetPixels32();
		if (pixels != null && pixels.Length != 0)
		{
			byte[] array = new byte[pixels.Length * 4];
			Buffer.BlockCopy(pixels, 0, array, 0, array.Length);
			Buffer.BlockCopy(HeadshotLogic.InvertRedChannel(array), 0, pixels, 0, array.Length);
			texture.SetPixels32(pixels);
		}
	}

	private static Material BuildFallbackMaterial(ManualLogSource logger)
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Expected O, but got Unknown
		try
		{
			Shader val = LegacyResourcesAPI.LoadAsync<Shader>("Shaders/Deferred/HGStandard").WaitForCompletion();
			if ((Object)(object)val == (Object)null)
			{
				logger.LogWarning((object)"[Headhunter] HGStandard shader could not be loaded.");
				return null;
			}
			return new Material(val);
		}
		catch (Exception ex)
		{
			logger.LogWarning((object)("[Headhunter] fallback material failed: " + ex.Message));
			return null;
		}
	}
}
[BepInPlugin("serge.headshot", "Headhunter", "0.6.2")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class HeadshotPlugin : BaseUnityPlugin
{
	private sealed class HeadVerdict
	{
		public HeadDetectionMode Mode;

		public bool IsHead;
	}

	private sealed class R2ApiOverlayHost : IOverlayHost
	{
		public IOverlayHandle AddOverlay(IDictionary<string, string> tokens, string language)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>(tokens);
			return new R2ApiOverlayHandle((language == null) ? LanguageAPI.AddOverlay(dictionary) : LanguageAPI.AddOverlay(dictionary, language));
		}
	}

	private sealed class R2ApiOverlayHandle : IOverlayHandle
	{
		private LanguageOverlay _overlay;

		public R2ApiOverlayHandle(LanguageOverlay overlay)
		{
			_overlay = overlay;
		}

		public void Remove()
		{
			if (_overlay != null)
			{
				_overlay.Remove();
				_overlay = null;
			}
		}
	}

	public const string PluginGuid = "serge.headshot";

	public const string PluginName = "Headhunter";

	public const string PluginVersion = "0.6.2";

	private const string HarmonyId = "serge.headshot";

	private const string ItemDefName = "Deadeye";

	private const string NameToken = "ITEM_DEADEYE_NAME";

	private const string DescriptionToken = "ITEM_DEADEYE_DESC";

	private const string LoreToken = "ITEM_DEADEYE_LORE";

	private const string PickupToken = "ITEM_DEADEYE_PICKUP";

	private const string NameText = "Headhunter";

	private static readonly string DescriptionText = HeadshotLogic.BuildDescriptionText(1);

	private static readonly TooltipTextBuilder[] _tooltipBuilders = BuildTooltipBuilders();

	private ConfigEntry<bool> _normalMapRedFix;

	private ConfigEntry<int> _pickupOrientation;

	private ConfigEntry<string> _pickupEuler;

	private ConfigEntry<string> _handEuler;

	private ConfigEntry<float> _handScale;

	private ConfigEntry<string> _handPosition;

	private ConfigEntry<string> _handAngles;

	private ConfigEntry<HeadDetectionMode> _detectionMode;

	private static HeadDetectionMode _headDetectionMode = HeadDetectionMode.StrictOrWeakPoint;

	private ConfigEntry<float> _weakPointRadius;

	private static float _weakPointRadiusOverride;

	private static readonly HeadshotLogic.AppliedOnceMarker<DamageInfo> _applied = new HeadshotLogic.AppliedOnceMarker<DamageInfo>();

	private static readonly ConditionalWeakTable<HurtBox, HeadVerdict> _headCache = new ConditionalWeakTable<HurtBox, HeadVerdict>();

	private const string PlaceholderIconAddress = "eccdfe4bd407fbd4782f1ed96f34ef7f";

	private const float IconPixelsPerUnit = 25f;

	private static bool _itemRegistered;

	private static Sprite _placeholderIcon;

	private static ManualLogSource _logger;

	private static ItemIndex _deadeyeIndex = (ItemIndex)(-1);

	private const int MaxHitLogLines = 30;

	private static int _hitLogLines;

	public const string ExcludedInflictorName = "ChamberTourDeForceRound";

	private static readonly ConditionalWeakTable<HurtBoxGroup, object> _hasHead = new ConditionalWeakTable<HurtBoxGroup, object>();

	private static TooltipTextBuilder[] BuildTooltipBuilders()
	{
		R2ApiOverlayHost host = new R2ApiOverlayHost();
		List<TooltipTextBuilder> list = new List<TooltipTextBuilder>
		{
			new TooltipTextBuilder(host, "ITEM_DEADEYE_DESC", "ITEM_DEADEYE_PICKUP")
		};
		string[] translations = HeadshotLogic.Translations;
		foreach (string language in translations)
		{
			list.Add(new TooltipTextBuilder(host, "ITEM_DEADEYE_DESC", "ITEM_DEADEYE_PICKUP", language));
		}
		return list.ToArray();
	}

	private void Awake()
	{
		_logger = ((BaseUnityPlugin)this).Logger;
		((BaseUnityPlugin)this).Logger.LogInfo((object)"[Headhunter] loading (v0.6.2)");
		BindConfig();
		RegisterLanguage();
		RegisterItem();
		PatchDamage();
		HeadshotHandDisplay.SetLogger(_logger);
		Inventory.onInventoryChangedGlobal += OnInventoryChangedGlobal;
		CharacterBody.onBodyStartGlobal += OnBodyStartGlobal;
		RefreshTooltip();
		((BaseUnityPlugin)this).Logger.LogInfo((object)"[Headhunter] loaded");
	}

	private void OnDestroy()
	{
		Inventory.onInventoryChangedGlobal -= OnInventoryChangedGlobal;
		CharacterBody.onBodyStartGlobal -= OnBodyStartGlobal;
		TooltipTextBuilder[] tooltipBuilders = _tooltipBuilders;
		for (int i = 0; i < tooltipBuilders.Length; i++)
		{
			tooltipBuilders[i].Dispose();
		}
	}

	private void BindConfig()
	{
		try
		{
			_normalMapRedFix = ((BaseUnityPlugin)this).Config.Bind<bool>("Model", "NormalMapRedFix", false, "Invert the red channel of the Deadeye normal map in memory. OFF = the map as extracted. ON = fixes a tangent-handedness bug (the bake basis is rotated 180 degrees around V). Needs a restart; the model is built once at load time.");
			HeadshotModel.NormalMapRedFix = _normalMapRedFix.Value;
			_pickupOrientation = ((BaseUnityPlugin)this).Config.Bind<int>("Model", "PickupOrientation", 0, "How the pistol lies on the ground. 0 = side-on (barrel horizontal, grip down; broad profile is vertical and faces sideways - current default). 1 = flat (the broad profile lies flat, facing up). 2 = upright (barrel pointing up, spinning about its own long axis). Needs a restart; the model is built once at load time.");
			_pickupEuler = ((BaseUnityPlugin)this).Config.Bind<string>("Model", "PickupEuler", "", "Overrides PickupOrientation with explicit Euler angles, 'x,y,z'. Empty = use PickupOrientation. Needs a restart.");
			_handEuler = ((BaseUnityPlugin)this).Config.Bind<string>("Model", "HandEuler", "", "Euler angles of the pistol in the survivor's hand, 'x,y,z'. Empty = 0,-90,0 (standing on its grip, barrel forward). Needs a restart.");
			_handScale = ((BaseUnityPlugin)this).Config.Bind<float>("Model", "HandScale", 1.05f, "How big the pistol is in the survivor's hand. Needs a restart.");
			_handPosition = ((BaseUnityPlugin)this).Config.Bind<string>("Model", "HandPosition", "", "Where the pistol sits in the hand bone, 'x,y,z'. Empty = the per-bone default. Needs a restart.");
			_handAngles = ((BaseUnityPlugin)this).Config.Bind<string>("Model", "HandAngles", "", "How the pistol is turned in the hand bone, 'x,y,z'. Empty = the per-bone default. Needs a restart.");
			_detectionMode = ((BaseUnityPlugin)this).Config.Bind<HeadDetectionMode>("Headshot", "DetectionMode", HeadDetectionMode.StrictOrWeakPoint, "Which hits count as a headshot. Legacy = any ancestor bone whose name contains 'head' (v0.6.0 rule). Strict = a 'head' bone among the two nearest bones (not neck/base/root), or an eye-type sniper target under the head; a one-hurtbox body is never a head. StrictOrWeakPoint (default) = Strict, plus any hit within the game's weak-point radius (1 m) of a sniper-target hurtbox, the way Railgunner's scoped shots judge it, or a hit the game itself marked as a weak-point hit. Needs a restart.");
			_headDetectionMode = _detectionMode.Value;
			_weakPointRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Headshot", "WeakPointRadius", 0f, "StrictOrWeakPoint only: how close (metres) a hit must land to a weak-point centre. 0 = the game's own radius (HurtBox.sniperTargetRadius, 1 m), as Railgunner uses it. A smaller value (e.g. 0.5) stops small whole-body weak points (Brass Contraption, Imp chest, Mini Mushrum) from counting on every hit. Needs a restart.");
			_weakPointRadiusOverride = _weakPointRadius.Value;
			ApplyModelSettings();
			((BaseUnityPlugin)this).Config.Save();
			((BaseUnityPlugin)this).Logger.LogInfo((object)(string.Format("[{0}] config: Model.NormalMapRedFix = {1}, ", "Headhunter", _normalMapRedFix.Value) + $"Headshot.DetectionMode = {_headDetectionMode}, " + $"Headshot.WeakPointRadius = {EffectiveWeakPointRadius():0.##} m"));
		}
		catch (Exception arg)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)string.Format("[{0}] could not read the config: {1}", "Headhunter", arg));
		}
	}

	private void ApplyModelSettings()
	{
		//IL_0025: 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_0062: Unknown result type (might be due to invalid IL or missing references)
		//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
		//IL_007d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0098: Unknown result type (might be due to invalid IL or missing references)
		//IL_017f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0196: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
		//IL_011a: Unknown result type (might be due to invalid IL or missing references)
		//IL_011f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0153: Unknown result type (might be due to invalid IL or missing references)
		//IL_0158: Unknown result type (might be due to invalid IL or missing references)
		if (_pickupEuler != null && !string.IsNullOrEmpty(_pickupEuler.Value))
		{
			HeadshotModel.PickupEuler = ParseEuler(_pickupEuler.Value, HeadshotModel.PickupEuler);
		}
		else if (_pickupOrientation != null)
		{
			switch (_pickupOrientation.Value)
			{
			case 0:
				HeadshotModel.PickupEuler = new Vector3(0f, -90f, 0f);
				break;
			case 2:
				HeadshotModel.PickupEuler = new Vector3(0f, -90f, 90f);
				break;
			default:
				HeadshotModel.PickupEuler = new Vector3(-90f, 0f, 180f);
				break;
			}
		}
		if (_handEuler != null && !string.IsNullOrEmpty(_handEuler.Value))
		{
			HeadshotModel.HandEuler = ParseEuler(_handEuler.Value, HeadshotModel.HandEuler);
		}
		if (_handScale != null)
		{
			float value = _handScale.Value;
			HeadshotModel.HandLocalScale = new Vector3(value, value, value);
		}
		if (_handPosition != null && !string.IsNullOrEmpty(_handPosition.Value))
		{
			HeadshotHandDisplay.PositionOverride = ParseEuler(_handPosition.Value, Vector3.zero);
		}
		if (_handAngles != null && !string.IsNullOrEmpty(_handAngles.Value))
		{
			HeadshotHandDisplay.AnglesOverride = ParseEuler(_handAngles.Value, Vector3.zero);
		}
		((BaseUnityPlugin)this).Logger.LogInfo((object)(string.Format("[{0}] model: pickup euler {1}, ", "Headhunter", HeadshotModel.PickupEuler) + $"hand euler {HeadshotModel.HandEuler}, " + $"hand scale {HeadshotModel.HandLocalScale.x}, " + $"hand pos override {HeadshotHandDisplay.PositionOverride}, " + $"hand angles override {HeadshotHandDisplay.AnglesOverride}"));
	}

	private static Vector3 ParseEuler(string text, Vector3 fallback)
	{
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		//IL_0083: Unknown result type (might be due to invalid IL or missing references)
		//IL_0093: Unknown result type (might be due to invalid IL or missing references)
		if (string.IsNullOrEmpty(text))
		{
			return fallback;
		}
		string[] array = text.Split(',');
		if (array.Length != 3)
		{
			ManualLogSource logger = _logger;
			if (logger != null)
			{
				logger.LogWarning((object)string.Format("[{0}] '{1}' is not 'x,y,z'; keeping {2}.", "Headhunter", text, fallback));
			}
			return fallback;
		}
		float[] array2 = new float[3];
		for (int i = 0; i < 3; i++)
		{
			if (!float.TryParse(array[i].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out array2[i]))
			{
				ManualLogSource logger2 = _logger;
				if (logger2 != null)
				{
					logger2.LogWarning((object)string.Format("[{0}] '{1}' is not 'x,y,z'; keeping {2}.", "Headhunter", text, fallback));
				}
				return fallback;
			}
		}
		return new Vector3(array2[0], array2[1], array2[2]);
	}

	private void RegisterLanguage()
	{
		try
		{
			LanguageAPI.Add("ITEM_DEADEYE_NAME", "Headhunter");
			LanguageAPI.Add("ITEM_DEADEYE_DESC", DescriptionText);
			LanguageAPI.Add("ITEM_DEADEYE_LORE", HeadshotLogic.LoreText());
			LanguageAPI.Add("ITEM_DEADEYE_PICKUP", DescriptionText);
			string[] translations = HeadshotLogic.Translations;
			foreach (string text in translations)
			{
				string text2 = HeadshotLogic.BuildDescriptionText(1, text);
				LanguageAPI.Add("ITEM_DEADEYE_NAME", "Headhunter", text);
				LanguageAPI.Add("ITEM_DEADEYE_DESC", text2, text);
				LanguageAPI.Add("ITEM_DEADEYE_PICKUP", text2, text);
				LanguageAPI.Add("ITEM_DEADEYE_LORE", HeadshotLogic.LoreText(text), text);
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[Headhunter] language tokens registered");
		}
		catch (Exception arg)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)string.Format("[{0}] language registration failed: {1}", "Headhunter", arg));
		}
	}

	private static void RefreshTooltip()
	{
		try
		{
			int localPlayerDeadeyeCount = GetLocalPlayerDeadeyeCount();
			string text = null;
			TooltipTextBuilder[] tooltipBuilders = _tooltipBuilders;
			for (int i = 0; i < tooltipBuilders.Length; i++)
			{
				string text2 = tooltipBuilders[i].Refresh(localPlayerDeadeyeCount);
				text = text ?? text2;
			}
			if (!string.IsNullOrEmpty(text))
			{
				ManualLogSource logger = _logger;
				if (logger != null)
				{
					logger.LogInfo((object)string.Format("[{0}] tooltip now reads '{1}' ({2} Deadeye)", "Headhunter", text, localPlayerDeadeyeCount));
				}
			}
		}
		catch (Exception arg)
		{
			ManualLogSource logger2 = _logger;
			if (logger2 != null)
			{
				logger2.LogError((object)string.Format("[{0}] tooltip refresh failed: {1}", "Headhunter", arg));
			}
		}
	}

	private static int GetLocalPlayerDeadeyeCount()
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0005: 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_0008: Invalid comparison between Unknown and I4
		//IL_005f: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			ItemIndex val = ResolveDeadeyeIndex();
			if ((int)val == -1)
			{
				return 0;
			}
			ReadOnlyCollection<NetworkUser> readOnlyLocalPlayersList = NetworkUser.readOnlyLocalPlayersList;
			if (readOnlyLocalPlayersList == null)
			{
				return 0;
			}
			foreach (NetworkUser item in readOnlyLocalPlayersList)
			{
				if (!((Object)(object)item == (Object)null))
				{
					CharacterMaster master = item.master;
					if (!((Object)(object)master == (Object)null) && !((Object)(object)master.inventory == (Object)null))
					{
						return master.inventory.GetItemCountEffective(val);
					}
				}
			}
		}
		catch (Exception)
		{
		}
		return 0;
	}

	private void OnInventoryChangedGlobal(Inventory inventory)
	{
		RefreshTooltip();
		try
		{
			CharacterBody val = HeadshotHandDisplay.Update(inventory);
			if ((Object)(object)val != (Object)null && HeadshotHandDisplay.HoldsDeadeye(val))
			{
				((MonoBehaviour)this).StartCoroutine(AttachPistolWhenReady(val));
			}
		}
		catch (Exception arg)
		{
			ManualLogSource logger = _logger;
			if (logger != null)
			{
				logger.LogError((object)string.Format("[{0}] hand display update failed: {1}", "Headhunter", arg));
			}
		}
	}

	private void OnBodyStartGlobal(CharacterBody body)
	{
		try
		{
			((MonoBehaviour)this).StartCoroutine(AttachPistolWhenReady(body));
		}
		catch (Exception arg)
		{
			ManualLogSource logger = _logger;
			if (logger != null)
			{
				logger.LogError((object)string.Format("[{0}] could not start the hand display coroutine: {1}", "Headhunter", arg));
			}
		}
	}

	private IEnumerator AttachPistolWhenReady(CharacterBody body)
	{
		for (int attempt = 0; attempt < 120; attempt++)
		{
			if ((Object)(object)body == (Object)null || !HeadshotHandDisplay.HoldsDeadeye(body) || HeadshotHandDisplay.Apply(body))
			{
				yield break;
			}
			yield return null;
		}
		ManualLogSource logger = _logger;
		if (logger != null)
		{
			logger.LogWarning((object)("[Headhunter] the pistol never made it into the hand of '" + ((Object)body).name + "'."));
		}
	}

	private void RegisterItem()
	{
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: Expected O, but got Unknown
		//IL_005c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: Expected O, but got Unknown
		//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			if (!_itemRegistered)
			{
				Sprite val = LoadPlaceholderIcon();
				GameObject val2 = HeadshotModel.Build(((BaseUnityPlugin)this).Logger);
				GameObject val3 = HeadshotModel.BuildHandPrefab(((BaseUnityPlugin)this).Logger);
				ItemDisplayRuleDict val4 = new ItemDisplayRuleDict(Array.Empty<ItemDisplayRule>());
				CustomItem val5 = new CustomItem("Deadeye", "ITEM_DEADEYE_NAME", "ITEM_DEADEYE_DESC", "ITEM_DEADEYE_LORE", "ITEM_DEADEYE_PICKUP", val, val2, (ItemTier)2, Array.Empty<ItemTag>(), true, false, (UnlockableDef)null, val4, (ItemTierDef)null);
				if (!ItemAPI.Add(val5))
				{
					((BaseUnityPlugin)this).Logger.LogError((object)"[Headhunter] ItemAPI.Add failed for Deadeye.");
					return;
				}
				_itemRegistered = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)(string.Format("[{0}] registered red item '{1}' (tier {2})", "Headhunter", "Deadeye", val5.ItemDef.tier) + (((Object)(object)val == (Object)null) ? " [NO ICON]" : "") + (((Object)(object)val2 == (Object)null) ? " [NO PICKUP MODEL]" : "") + (((Object)(object)val3 == (Object)null) ? " [NO HAND MODEL]" : "")));
			}
		}
		catch (Exception arg)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)string.Format("[{0}] item registration failed: {1}", "Headhunter", arg));
		}
	}

	private Sprite LoadPlaceholderIcon()
	{
		//IL_0125: Unknown result type (might be due to invalid IL or missing references)
		//IL_012a: Unknown result type (might be due to invalid IL or missing references)
		//IL_003d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0043: Expected O, but got Unknown
		//IL_0172: Unknown result type (might be due to invalid IL or missing references)
		//IL_0181: Unknown result type (might be due to invalid IL or missing references)
		//IL_0087: Unknown result type (might be due to invalid IL or missing references)
		//IL_0096: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)_placeholderIcon != (Object)null)
		{
			return _placeholderIcon;
		}
		try
		{
			string text = HeadshotModel.FileIn(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Headhunter.png");
			if (File.Exists(text))
			{
				Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
				if (ImageConversion.LoadImage(val, File.ReadAllBytes(text), false))
				{
					((Texture)val).filterMode = (FilterMode)0;
					((Texture)val).wrapMode = (TextureWrapMode)1;
					((Object)val).name = "texDeadeyeIcon";
					_placeholderIcon = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 25f);
					((Object)_placeholderIcon).name = "DeadeyeIcon";
					((BaseUnityPlugin)this).Logger.LogInfo((object)("[Headhunter] custom icon loaded from '" + text + "' " + $"({((Texture)val).width}x{((Texture)val).height})"));
					return _placeholderIcon;
				}
			}
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("[Headhunter] custom icon load failed: " + ex.Message));
		}
		try
		{
			Texture2D val2 = Addressables.LoadAssetAsync<Texture2D>((object)"eccdfe4bd407fbd4782f1ed96f34ef7f").WaitForCompletion();
			if ((Object)(object)val2 == (Object)null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"[Headhunter] placeholder icon 'eccdfe4bd407fbd4782f1ed96f34ef7f' could not be loaded. Headhunter will show without an icon; place a PNG named 'Headhunter.png' next to Headshot.dll to supply your own.");
				return null;
			}
			_placeholderIcon = Sprite.Create(val2, new Rect(0f, 0f, (float)((Texture)val2).width, (float)((Texture)val2).height), new Vector2(0.5f, 0.5f), 25f);
			((Object)_placeholderIcon).name = "DeadeyeIcon";
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[Headhunter] placeholder icon loaded ('eccdfe4bd407fbd4782f1ed96f34ef7f')");
			return _placeholderIcon;
		}
		catch (Exception ex2)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("[Headhunter] placeholder icon load failed: " + ex2.Message));
			return null;
		}
	}

	private void PatchDamage()
	{
		//IL_0049: Unknown result type (might be due to invalid IL or missing references)
		//IL_005f: Unknown result type (might be due to invalid IL or missing references)
		//IL_006d: Expected O, but got Unknown
		try
		{
			MethodInfo methodInfo = AccessTools.Method(typeof(HealthComponent), "TakeDamageProcess", new Type[1] { typeof(DamageInfo) }, (Type[])null);
			if (methodInfo == null)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"[Headhunter] HOOK TARGET MISSING: HealthComponent.TakeDamageProcess(DamageInfo)");
				return;
			}
			new Harmony("serge.headshot").Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(HeadshotPlugin), "PrefixTakeDamageProcess", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[Headhunter] Harmony patch installed on HealthComponent.TakeDamageProcess");
		}
		catch (Exception arg)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)string.Format("[{0}] could not install Harmony patch: {1}", "Headhunter", arg));
		}
	}

	private static void PrefixTakeDamageProcess(DamageInfo damageInfo)
	{
		//IL_0078: Unknown result type (might be due to invalid IL or missing references)
		//IL_007e: Invalid comparison between Unknown and I4
		try
		{
			if (damageInfo == null || damageInfo.delayedDamageSecondHalf)
			{
				return;
			}
			HurtBox inflictedHurtbox = damageInfo.inflictedHurtbox;
			if ((Object)(object)inflictedHurtbox == (Object)null || ((Object)(object)damageInfo.inflictor != (Object)null && ((Object)damageInfo.inflictor).name == "ChamberTourDeForceRound"))
			{
				return;
			}
			CharacterBody val = ResolveAttackerBody(damageInfo);
			TeamComponent val2 = (((Object)(object)val != (Object)null) ? val.teamComponent : null);
			bool flag = (Object)(object)val2 != (Object)null && (int)val2.teamIndex == 1;
			int num = (flag ? GetDeadeyeStackCount(val, damageInfo) : 0);
			if (num > 0)
			{
				bool flag2 = IsHeadHurtbox(inflictedHurtbox);
				bool flag3 = !flag2 && HeadshotDetection.WeakPointsCount(_headDetectionMode, BodyHasHead(inflictedHurtbox.hurtBoxGroup)) && IsWeakPointHit(damageInfo, inflictedHurtbox, val);
				HeadshotLogic.HeadHitDecision decision = HeadshotLogic.DecideHeadHit(flag, flag2 || flag3, num, damageInfo.crit, _applied.IsMarked(damageInfo));
				bool crit = damageInfo.crit;
				float damage = damageInfo.damage;
				HeadshotLogic.ApplyDecision(in decision, ref crit, ref damage);
				damageInfo.crit = crit;
				damageInfo.damage = damage;
				if (decision.MarksApplied)
				{
					_applied.Mark(damageInfo);
				}
				LogHitDecision(inflictedHurtbox, num, decision, flag3);
			}
		}
		catch (Exception arg)
		{
			ManualLogSource logger = _logger;
			if (logger != null)
			{
				logger.LogError((object)string.Format("[{0}] error in TakeDamageProcess prefix: {1}", "Headhunter", arg));
			}
		}
	}

	private static void LogHitDecision(HurtBox hurtbox, int stacks, HeadshotLogic.HeadHitDecision decision, bool viaWeakPoint)
	{
		if (_hitLogLines < 30)
		{
			_hitLogLines++;
			ManualLogSource logger = _logger;
			if (logger != null)
			{
				logger.LogInfo((object)(string.Format("[{0}] hit {1}: {2}", "Headhunter", _hitLogLines, decision.LogLabel) + (viaWeakPoint ? " (weak point)" : string.Empty) + $" hurtbox='{((Object)hurtbox).name}' mode={_headDetectionMode} ({stacks} Deadeye)"));
			}
		}
	}

	private static bool IsWeakPointHit(DamageInfo damageInfo, HurtBox hurtbox, CharacterBody attackerBody)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: 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_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_003e: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: 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_00ec: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fc: 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_0099: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
		//IL_00af: 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_00ba: 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_00b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
		bool weakPointDamageType = DamageTypeCombo.op_Implicit(damageInfo.damageType & DamageTypeCombo.op_Implicit((DamageType)16)) != 0;
		float num = float.PositiveInfinity;
		Vector3 val = (((Object)(object)attackerBody != (Object)null) ? (damageInfo.position - attackerBody.aimOrigin) : Vector3.zero);
		bool flag = ((Vector3)(ref val)).sqrMagnitude > 0.0001f;
		HurtBoxGroup hurtBoxGroup = hurtbox.hurtBoxGroup;
		HurtBox[] array = (((Object)(object)hurtBoxGroup != (Object)null) ? hurtBoxGroup.hurtBoxes : null);
		Vector3 val4;
		if (array != null)
		{
			HurtBox[] array2 = array;
			foreach (HurtBox val2 in array2)
			{
				if ((Object)(object)val2 != (Object)null && val2.isSniperTarget)
				{
					Vector3 val3 = damageInfo.position - ((Component)val2).transform.position;
					val4 = (flag ? Vector3.ProjectOnPlane(val3, val) : val3);
					float sqrMagnitude = ((Vector3)(ref val4)).sqrMagnitude;
					if (sqrMagnitude < num)
					{
						num = sqrMagnitude;
					}
				}
			}
		}
		else if (hurtbox.isSniperTarget)
		{
			val4 = damageInfo.position - ((Component)hurtbox).transform.position;
			num = ((Vector3)(ref val4)).sqrMagnitude;
		}
		return HeadshotDetection.IsWeakPointHit(new HeadshotDetection.HitFacts(weakPointDamageType, num, EffectiveWeakPointRadius()));
	}

	private static float EffectiveWeakPointRadius()
	{
		if (!(_weakPointRadiusOverride > 0f))
		{
			return HurtBox.sniperTargetRadius;
		}
		return _weakPointRadiusOverride;
	}

	private static bool BodyHasHead(HurtBoxGroup group)
	{
		if ((Object)(object)group == (Object)null || group.hurtBoxes == null)
		{
			return false;
		}
		if (_hasHead.TryGetValue(group, out var value))
		{
			return (bool)value;
		}
		bool flag = false;
		HurtBox[] hurtBoxes = group.hurtBoxes;
		foreach (HurtBox val in hurtBoxes)
		{
			flag |= (Object)(object)val != (Object)null && IsHeadHurtbox(val);
		}
		_hasHead.Add(group, flag);
		return flag;
	}

	private static bool IsHeadHurtbox(HurtBox hurtbox)
	{
		if (_headCache.TryGetValue(hurtbox, out var value) && value.Mode == _headDetectionMode)
		{
			return value.IsHead;
		}
		Transform transform = ((Component)hurtbox).transform;
		if ((Object)(object)transform == (Object)null)
		{
			return false;
		}
		List<string> list = new List<string>();
		Transform parent = transform.parent;
		while ((Object)(object)parent != (Object)null)
		{
			list.Add(((Object)parent).name);
			parent = parent.parent;
		}
		HurtBoxGroup hurtBoxGroup = hurtbox.hurtBoxGroup;
		int groupSize = (((Object)(object)hurtBoxGroup != (Object)null && hurtBoxGroup.hurtBoxes != null) ? hurtBoxGroup.hurtBoxes.Length : 0);
		bool isMain = (Object)(object)hurtBoxGroup != (Object)null && (Object)(object)hurtBoxGroup.mainHurtBox == (Object)(object)hurtbox;
		HeadshotDetection.HurtboxFacts facts = new HeadshotDetection.HurtboxFacts(((Object)hurtbox).name, list, hurtbox.isSniperTarget, isMain, groupSize);
		bool flag = HeadshotDetection.IsHead(_headDetectionMode, in facts);
		_headCache.Remove(hurtbox);
		_headCache.Add(hurtbox, new HeadVerdict
		{
			Mode = _headDetectionMode,
			IsHead = flag
		});
		return flag;
	}

	private static CharacterBody ResolveAttackerBody(DamageInfo damageInfo)
	{
		CharacterBody val = FindCharacterBodyInParents(((Object)(object)damageInfo.attacker != (Object)null) ? damageInfo.attacker.transform : null);
		if ((Object)(object)val != (Object)null)
		{
			return val;
		}
		val = FindCharacterBodyInParents(((Object)(object)damageInfo.inflictor != (Object)null) ? damageInfo.inflictor.transform : null);
		if ((Object)(object)val != (Object)null)
		{
			return val;
		}
		if ((Object)(object)damageInfo.attacker != (Object)null)
		{
			try
			{
				val = damageInfo.attacker.GetComponent<CharacterBody>();
				if ((Object)(object)val != (Object)null)
				{
					return val;
				}
			}
			catch (Exception)
			{
			}
			try
			{
				CharacterMaster component = damageInfo.attacker.GetComponent<CharacterMaster>();
				if ((Object)(object)component != (Object)null)
				{
					val = component.GetBody();
					if ((Object)(object)val != (Object)null)
					{
						return val;
					}
				}
			}
			catch (Exception)
			{
			}
		}
		return null;
	}

	private static CharacterMaster ResolveAttackerMaster(DamageInfo damageInfo)
	{
		if ((Object)(object)damageInfo.attacker == (Object)null)
		{
			return null;
		}
		try
		{
			CharacterMaster component = damageInfo.attacker.GetComponent<CharacterMaster>();
			if ((Object)(object)component != (Object)null)
			{
				return component;
			}
		}
		catch (Exception)
		{
		}
		try
		{
			CharacterBody component2 = damageInfo.attacker.GetComponent<CharacterBody>();
			if ((Object)(object)component2 != (Object)null && (Object)(object)component2.master != (Object)null)
			{
				return component2.master;
			}
		}
		catch (Exception)
		{
		}
		return null;
	}

	private static CharacterBody FindCharacterBodyInParents(Transform transform)
	{
		if ((Object)(object)transform == (Object)null)
		{
			return null;
		}
		Transform val = transform;
		while ((Object)(object)val != (Object)null)
		{
			CharacterBody component = ((Component)val).GetComponent<CharacterBody>();
			if ((Object)(object)component != (Object)null)
			{
				return component;
			}
			val = val.parent;
		}
		return null;
	}

	private static int GetDeadeyeStackCount(CharacterBody body, DamageInfo damageInfo)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0005: 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_0008: Invalid comparison between Unknown and I4
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_005e: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			ItemIndex val = ResolveDeadeyeIndex();
			if ((int)val == -1)
			{
				return 0;
			}
			CharacterMaster val2 = ResolveAttackerMaster(damageInfo);
			if ((Object)(object)val2 != (Object)null && (Object)(object)val2.inventory != (Object)null)
			{
				int itemCountEffective = val2.inventory.GetItemCountEffective(val);
				if (itemCountEffective > 0)
				{
					return itemCountEffective;
				}
			}
			if ((Object)(object)body != (Object)null && (Object)(object)body.inventory != (Object)null)
			{
				int itemCountEffective2 = body.inventory.GetItemCountEffective(val);
				if (itemCountEffective2 > 0)
				{
					return itemCountEffective2;
				}
			}
			return 0;
		}
		catch (Exception)
		{
			return 0;
		}
	}

	private static ItemIndex ResolveDeadeyeIndex()
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Invalid comparison between Unknown and I4
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//IL_004e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_001c: Invalid comparison between Unknown and I4
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		//IL_001f: 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)
		if ((int)_deadeyeIndex != -1)
		{
			return _deadeyeIndex;
		}
		try
		{
			ItemIndex val = ItemCatalog.FindItemIndex("Deadeye");
			if ((int)val != -1)
			{
				_deadeyeIndex = val;
				ManualLogSource logger = _logger;
				if (logger != null)
				{
					logger.LogInfo((object)string.Format("[{0}] Deadeye item index = {1}", "Headhunter", val));
				}
			}
		}
		catch (Exception)
		{
		}
		return _deadeyeIndex;
	}
}
public interface IOverlayHandle
{
	void Remove();
}
public interface IOverlayHost
{
	IOverlayHandle AddOverlay(IDictionary<string, string> tokens, string language);
}
public sealed class TooltipTextBuilder : IDisposable
{
	private readonly IOverlayHost _host;

	private readonly string _descriptionToken;

	private readonly string _pickupToken;

	private readonly string _language;

	private IOverlayHandle _overlay;

	private int _lastStacks = int.MinValue;

	private bool _lastCallFailed;

	private string _lastText;

	public string LastText => _lastText;

	public int LastStacks
	{
		get
		{
			if (_lastStacks != int.MinValue)
			{
				return _lastStacks;
			}
			return -1;
		}
	}

	public bool HasOverlay => _overlay != null;

	public TooltipTextBuilder(IOverlayHost host, string descriptionToken, string pickupToken, string language = null)
	{
		if (host == null)
		{
			throw new ArgumentNullException("host");
		}
		if (string.IsNullOrEmpty(descriptionToken))
		{
			throw new ArgumentNullException("descriptionToken");
		}
		if (string.IsNullOrEmpty(pickupToken))
		{
			throw new ArgumentNullException("pickupToken");
		}
		_host = host;
		_descriptionToken = descriptionToken;
		_pickupToken = pickupToken;
		_language = language;
	}

	public string Refresh(int stacks)
	{
		if (!_lastCallFailed && stacks == _lastStacks)
		{
			return null;
		}
		string text = HeadshotLogic.BuildDescriptionText(stacks, _language);
		Dictionary<string, string> tokens = new Dictionary<string, string>
		{
			{ _descriptionToken, text },
			{ _pickupToken, text }
		};
		try
		{
			if (_overlay != null)
			{
				_overlay.Remove();
				_overlay = null;
			}
			_overlay = _host.AddOverlay(tokens, _language);
			_lastStacks = stacks;
			_lastCallFailed = false;
			_lastText = text;
			return text;
		}
		catch (Exception)
		{
			_lastCallFailed = true;
			return null;
		}
	}

	public void Dispose()
	{
		if (_overlay != null)
		{
			try
			{
				_overlay.Remove();
			}
			catch (Exception)
			{
			}
			_overlay = null;
		}
	}
}