Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Headhunter v0.6.2
Headhunter.dll
Decompiled 12 hours agousing 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; } } }