Decompiled source of RepoDeathCounter v1.3.2

Matsu.RepoDeathCounter.dll

Decompiled a month ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("Matsu.RepoDeathCounter")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0")]
[assembly: AssemblyProduct("Matsu.RepoDeathCounter")]
[assembly: AssemblyTitle("Matsu.RepoDeathCounter")]
[assembly: AssemblyVersion("1.1.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Matsu.RepoDeathCounter
{
	internal sealed class DeathSyncBehaviour : MonoBehaviour, IInRoomCallbacks, IOnEventCallback
	{
		internal const byte DeathSyncEventCode = 77;

		private static MethodInfo? isMasterOrSpMethod;

		private static bool isMasterResolved;

		private static FieldInfo? statsInstanceField;

		private static FieldInfo? saveFileCurrentField;

		private static bool statsResolved;

		private string? loadedSaveFile;

		private float pollTimer;

		private bool callbacksRegistered;

		private static string SaveDir => Path.Combine(Application.persistentDataPath, "MatsuDeathCounter");

		private void OnDestroy()
		{
			if (callbacksRegistered)
			{
				try
				{
					PhotonNetwork.RemoveCallbackTarget((object)this);
				}
				catch
				{
				}
				callbacksRegistered = false;
			}
		}

		private void Update()
		{
			try
			{
				bool flag = false;
				try
				{
					flag = PhotonNetwork.InRoom && !PhotonNetwork.OfflineMode;
				}
				catch
				{
				}
				if (flag && !callbacksRegistered)
				{
					try
					{
						PhotonNetwork.AddCallbackTarget((object)this);
						callbacksRegistered = true;
					}
					catch (Exception ex)
					{
						Plugin.TrackerErr("[DeathCounter] AddCallbackTarget failed: " + ex.Message);
					}
				}
				else if (!flag && callbacksRegistered)
				{
					try
					{
						PhotonNetwork.RemoveCallbackTarget((object)this);
					}
					catch
					{
					}
					callbacksRegistered = false;
				}
				if (!IsHost())
				{
					return;
				}
				pollTimer += Time.deltaTime;
				if (pollTimer < 1f)
				{
					return;
				}
				pollTimer = 0f;
				string saveFileCurrent = GetSaveFileCurrent();
				if (!(saveFileCurrent == loadedSaveFile))
				{
					loadedSaveFile = saveFileCurrent;
					Plugin.DeathCountsBySteamId.Clear();
					Plugin.PlayerNamesBySteamId.Clear();
					Plugin.DeathCausesBySteamId.Clear();
					Plugin.LastEnemyContact.Clear();
					Plugin.LastPlayerContact.Clear();
					Plugin.LastFallDamage.Clear();
					if (!string.IsNullOrEmpty(saveFileCurrent))
					{
						LoadFromDisk(saveFileCurrent);
						BroadcastDeaths();
					}
				}
			}
			catch (Exception ex2)
			{
				Plugin.TrackerErr("[DeathCounter] Update error: " + ex2.Message);
			}
		}

		public void OnEvent(EventData photonEvent)
		{
			if (photonEvent.Code != 77 || IsHost())
			{
				return;
			}
			try
			{
				if (!(photonEvent.CustomData is object[] array) || array.Length < 3)
				{
					return;
				}
				string[] array2 = array[0] as string[];
				int[] array3 = array[1] as int[];
				string[] array4 = array[2] as string[];
				if (array2 == null || array3 == null || array4 == null)
				{
					return;
				}
				Plugin.DeathCountsBySteamId.Clear();
				Plugin.PlayerNamesBySteamId.Clear();
				Plugin.DeathCausesBySteamId.Clear();
				for (int i = 0; i < array2.Length && i < array3.Length; i++)
				{
					if (!string.IsNullOrEmpty(array2[i]))
					{
						Plugin.DeathCountsBySteamId[array2[i]] = array3[i];
						if (i < array4.Length && !string.IsNullOrEmpty(array4[i]))
						{
							Plugin.PlayerNamesBySteamId[array2[i]] = array4[i];
						}
					}
				}
				if (array.Length < 4 || !(array[3] is string[] array5))
				{
					return;
				}
				string[] array6 = array5;
				foreach (string text in array6)
				{
					if (string.IsNullOrEmpty(text))
					{
						continue;
					}
					string[] array7 = text.Split(new char[1] { '\t' });
					if (array7.Length >= 3 && int.TryParse(array7[2], out var result))
					{
						if (!Plugin.DeathCausesBySteamId.TryGetValue(array7[0], out Dictionary<string, int> value))
						{
							value = (Plugin.DeathCausesBySteamId[array7[0]] = new Dictionary<string, int>());
						}
						value[array7[1]] = result;
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.TrackerErr("[DeathCounter] apply snapshot failed: " + ex.Message);
			}
		}

		public void OnPlayerEnteredRoom(Player newPlayer)
		{
			if (IsHost())
			{
				BroadcastDeaths();
			}
		}

		public void OnPlayerLeftRoom(Player otherPlayer)
		{
		}

		public void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged)
		{
		}

		public void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps)
		{
		}

		public void OnMasterClientSwitched(Player newMasterClient)
		{
		}

		internal static void OnHostDeath(string steamId, string name, string cause)
		{
			string text = ((!string.IsNullOrEmpty(steamId)) ? steamId : name);
			if (string.IsNullOrEmpty(text))
			{
				return;
			}
			Plugin.DeathCountsBySteamId.TryGetValue(text, out var value);
			Plugin.DeathCountsBySteamId[text] = value + 1;
			if (!string.IsNullOrEmpty(name))
			{
				Plugin.PlayerNamesBySteamId[text] = name;
			}
			if (!string.IsNullOrEmpty(cause))
			{
				if (!Plugin.DeathCausesBySteamId.TryGetValue(text, out Dictionary<string, int> value2))
				{
					value2 = (Plugin.DeathCausesBySteamId[text] = new Dictionary<string, int>());
				}
				value2.TryGetValue(cause, out var value3);
				value2[cause] = value3 + 1;
			}
			Plugin.TrackerLog($"[DeathCounter] {(string.IsNullOrEmpty(name) ? text : name)} died (cause: {cause}). Total: {Plugin.DeathCountsBySteamId[text]}");
			SaveToDisk();
			BroadcastDeaths();
		}

		internal static void BroadcastDeathsPublic()
		{
			BroadcastDeaths();
		}

		private static void BroadcastDeaths()
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: 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)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			try
			{
				if (PhotonNetwork.InRoom && !PhotonNetwork.OfflineMode)
				{
					PhotonNetwork.RaiseEvent((byte)77, (object)Pack(), new RaiseEventOptions
					{
						Receivers = (ReceiverGroup)0
					}, SendOptions.SendReliable);
				}
			}
			catch (Exception ex)
			{
				Plugin.TrackerErr("[DeathCounter] broadcast failed: " + ex.Message);
			}
		}

		private static object[] Pack()
		{
			int count = Plugin.DeathCountsBySteamId.Count;
			string[] array = new string[count];
			int[] array2 = new int[count];
			string[] array3 = new string[count];
			int num = 0;
			foreach (KeyValuePair<string, int> item in Plugin.DeathCountsBySteamId)
			{
				array[num] = item.Key;
				array2[num] = item.Value;
				array3[num] = (Plugin.PlayerNamesBySteamId.TryGetValue(item.Key, out string value) ? value : "");
				num++;
			}
			List<string> list = new List<string>();
			foreach (KeyValuePair<string, Dictionary<string, int>> item2 in Plugin.DeathCausesBySteamId)
			{
				foreach (KeyValuePair<string, int> item3 in item2.Value)
				{
					list.Add(item2.Key + "\t" + item3.Key + "\t" + item3.Value);
				}
			}
			return new object[4]
			{
				array,
				array2,
				array3,
				list.ToArray()
			};
		}

		private static string SavePathFor(string saveFile)
		{
			return Path.Combine(SaveDir, Sanitize(saveFile) + ".tsv");
		}

		private static string Sanitize(string s)
		{
			char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
			foreach (char oldChar in invalidFileNameChars)
			{
				s = s.Replace(oldChar, '_');
			}
			return s;
		}

		internal static void SaveToDisk()
		{
			string saveFileCurrent = GetSaveFileCurrent();
			if (string.IsNullOrEmpty(saveFileCurrent))
			{
				return;
			}
			try
			{
				Directory.CreateDirectory(SaveDir);
				StringBuilder stringBuilder = new StringBuilder();
				foreach (KeyValuePair<string, int> item in Plugin.DeathCountsBySteamId)
				{
					string text = (Plugin.PlayerNamesBySteamId.TryGetValue(item.Key, out string value) ? value : "");
					text = text.Replace('\t', ' ').Replace('\n', ' ').Replace('\r', ' ');
					stringBuilder.Append(item.Key).Append('\t').Append(item.Value)
						.Append('\t')
						.Append(text);
					if (Plugin.DeathCausesBySteamId.TryGetValue(item.Key, out Dictionary<string, int> value2))
					{
						foreach (KeyValuePair<string, int> item2 in value2)
						{
							string value3 = item2.Key.Replace('\t', ' ').Replace('\n', ' ').Replace('\r', ' ');
							stringBuilder.Append('\t').Append(value3).Append('\t')
								.Append(item2.Value);
						}
					}
					stringBuilder.Append('\n');
				}
				File.WriteAllText(SavePathFor(saveFileCurrent), stringBuilder.ToString());
			}
			catch (Exception ex)
			{
				Plugin.TrackerErr("[DeathCounter] save failed: " + ex.Message);
			}
		}

		private static void LoadFromDisk(string saveFile)
		{
			try
			{
				string path = SavePathFor(saveFile);
				if (!File.Exists(path))
				{
					return;
				}
				string[] array = File.ReadAllLines(path);
				foreach (string text in array)
				{
					if (string.IsNullOrWhiteSpace(text))
					{
						continue;
					}
					string[] array2 = text.Split(new char[1] { '\t' });
					if (array2.Length < 2)
					{
						continue;
					}
					string text2 = array2[0];
					if (string.IsNullOrEmpty(text2) || !int.TryParse(array2[1], out var result))
					{
						continue;
					}
					Plugin.DeathCountsBySteamId[text2] = result;
					if (array2.Length >= 3 && !string.IsNullOrEmpty(array2[2]))
					{
						Plugin.PlayerNamesBySteamId[text2] = array2[2];
					}
					for (int j = 3; j + 1 < array2.Length; j += 2)
					{
						string text3 = array2[j];
						if (!string.IsNullOrEmpty(text3) && int.TryParse(array2[j + 1], out var result2))
						{
							if (!Plugin.DeathCausesBySteamId.TryGetValue(text2, out Dictionary<string, int> value))
							{
								value = (Plugin.DeathCausesBySteamId[text2] = new Dictionary<string, int>());
							}
							value[text3] = result2;
						}
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.TrackerErr("[DeathCounter] load failed: " + ex.Message);
			}
		}

		internal static bool IsHost()
		{
			if (!isMasterResolved)
			{
				Type type = AccessTools.TypeByName("SemiFunc");
				isMasterOrSpMethod = ((type == null) ? null : AccessTools.Method(type, "IsMasterClientOrSingleplayer", (Type[])null, (Type[])null));
				isMasterResolved = true;
			}
			if (isMasterOrSpMethod != null)
			{
				try
				{
					return (bool)isMasterOrSpMethod.Invoke(null, null);
				}
				catch
				{
				}
			}
			try
			{
				return PhotonNetwork.OfflineMode || !PhotonNetwork.InRoom || PhotonNetwork.IsMasterClient;
			}
			catch
			{
				return true;
			}
		}

		private static string GetSaveFileCurrent()
		{
			if (!statsResolved)
			{
				Type type = AccessTools.TypeByName("StatsManager");
				statsInstanceField = ((type == null) ? null : AccessTools.Field(type, "instance"));
				saveFileCurrentField = ((type == null) ? null : AccessTools.Field(type, "saveFileCurrent"));
				statsResolved = true;
			}
			try
			{
				object obj = statsInstanceField?.GetValue(null);
				if (obj == null)
				{
					return "";
				}
				return (saveFileCurrentField?.GetValue(obj) as string) ?? "";
			}
			catch
			{
				return "";
			}
		}
	}
	public enum OverlayCorner
	{
		TopRight,
		TopLeft,
		BottomRight,
		BottomLeft
	}
	public enum SortMode
	{
		DeathsAscending,
		DeathsDescending,
		NameAscending,
		NameDescending
	}
	[BepInPlugin("matsu.repo.deathcounter", "REPO Death Counter", "1.3.2")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "matsu.repo.deathcounter";

		public const string PluginName = "REPO Death Counter";

		public const string PluginVersion = "1.3.2";

		internal static ManualLogSource? Log;

		public static Dictionary<string, int> DeathCountsBySteamId = new Dictionary<string, int>();

		public static Dictionary<string, string> PlayerNamesBySteamId = new Dictionary<string, string>();

		public static Dictionary<string, Dictionary<string, int>> DeathCausesBySteamId = new Dictionary<string, Dictionary<string, int>>();

		internal static readonly Dictionary<string, (int enemyIndex, string enemyName, float time)> LastEnemyContact = new Dictionary<string, (int, string, float)>();

		internal static readonly Dictionary<string, float> LastPlayerContact = new Dictionary<string, float>();

		internal static readonly Dictionary<string, float> LastFallDamage = new Dictionary<string, float>();

		internal const float ContactWindow = 8f;

		private static MethodInfo? enemyGetFromIndexMethod;

		private static bool enemyGetFromIndexResolved;

		private static MethodInfo? semiFuncEnemyGetIndexMethod;

		private static ConfigEntry<bool> showOverlay = null;

		private static ConfigEntry<OverlayCorner> overlayCorner = null;

		private static ConfigEntry<int> titleFontSize = null;

		private static ConfigEntry<int> itemFontSize = null;

		private static ConfigEntry<string> titleColorHex = null;

		private static ConfigEntry<string> itemColorHex = null;

		private static ConfigEntry<SortMode> sortMode = null;

		private static ConfigEntry<bool> showTotal = null;

		private static ConfigEntry<bool> highlightSelf = null;

		private static ConfigEntry<string> selfColorHex = null;

		private static ConfigEntry<bool> showDeathCause = null;

		private static ConfigEntry<bool> ignoreShopDeaths = null;

		private static bool runManagerReflectionResolved;

		private static FieldInfo? runManagerInstanceField;

		private static FieldInfo? runManagerLevelCurrentField;

		private static FieldInfo? runManagerLevelLobbyField;

		private static bool localPlayerReflectionResolved;

		private static FieldInfo? playerAvatarInstanceField;

		private static FieldInfo? playerAvatarSteamIdField;

		private static bool runIsShopResolved;

		private static MethodInfo? runIsShopMethod;

		private static FieldInfo? shopManagerInstanceField;

		private static FieldInfo? playerIsLocalField;

		private static FieldInfo? playerSteamIdFieldCached;

		private static FieldInfo? playerPlayerNameField;

		private static FieldInfo? hcEnemyHostField;

		private static FieldInfo? hcPlayerCausingHurtField;

		private static FieldInfo? enemyEnemyParentField;

		private static PropertyInfo? enemyEnemyParentProperty;

		private static FieldInfo? enemyParentEnemyNameField;

		private static FieldInfo? ptPlayerAvatarField;

		private static FieldInfo? phPlayerAvatarField;

		private static FieldInfo? edInstanceField;

		private static FieldInfo? edEnemiesSpawnedField;

		private static FieldInfo? epSpawnedField;

		private static FieldInfo? epEnableObjectField;

		private static Type? enemyTypeCached;

		private Harmony harmony;

		private void Awake()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			//IL_01f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0202: Expected O, but got Unknown
			//IL_028d: Unknown result type (might be due to invalid IL or missing references)
			//IL_029a: Expected O, but got Unknown
			//IL_032a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0337: Expected O, but got Unknown
			//IL_03c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d4: Expected O, but got Unknown
			//IL_0463: Unknown result type (might be due to invalid IL or missing references)
			//IL_0471: Expected O, but got Unknown
			//IL_0501: Unknown result type (might be due to invalid IL or missing references)
			//IL_050e: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			harmony = new Harmony("matsu.repo.deathcounter");
			showOverlay = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowOverlay", true, "Show the death-count overlay in the truck lobby.");
			overlayCorner = ((BaseUnityPlugin)this).Config.Bind<OverlayCorner>("Display", "Corner", OverlayCorner.TopRight, "Which screen corner the overlay is anchored to.");
			titleFontSize = ((BaseUnityPlugin)this).Config.Bind<int>("Display", "TitleFontSize", 24, "Font size of the header.");
			itemFontSize = ((BaseUnityPlugin)this).Config.Bind<int>("Display", "FontSize", 20, "Font size of each player line.");
			titleColorHex = ((BaseUnityPlugin)this).Config.Bind<string>("Display", "TitleColorHex", "FF3333", "Header text color, hex RGB (e.g. FF3333). '#' optional.");
			itemColorHex = ((BaseUnityPlugin)this).Config.Bind<string>("Display", "TextColorHex", "FFFFFF", "Player-line text color, hex RGB (e.g. FFFFFF). '#' optional.");
			sortMode = ((BaseUnityPlugin)this).Config.Bind<SortMode>("Display", "SortMode", SortMode.DeathsAscending, "List order. DeathsAscending = fewest deaths on top.");
			showTotal = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowTotal", true, "Show a 'Total' line summing every player's deaths at the bottom of the list.");
			highlightSelf = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "HighlightSelf", true, "Draw your own row in a different color so it's easy to pick out at a glance.");
			selfColorHex = ((BaseUnityPlugin)this).Config.Bind<string>("Display", "SelfColorHex", "FFD700", "Your own row's text color, hex RGB (e.g. FFD700 = gold). Only used when HighlightSelf is on.");
			showDeathCause = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowNemesis", true, "After each player's death count, show their 'nemesis' — the enemy that has killed them the most.");
			ignoreShopDeaths = ((BaseUnityPlugin)this).Config.Bind<bool>("Counting", "IgnoreShopDeaths", true, "If true, deaths that happen while in the shop are not counted (default on).");
			try
			{
				Type type = AccessTools.TypeByName("PlayerAvatar");
				MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "PlayerDeathRPC", (Type[])null, (Type[])null));
				if (methodInfo != null)
				{
					harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(AccessTools.Method(typeof(Plugin), "PlayerDeathRpcPrefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					Log.LogInfo((object)"Patched PlayerAvatar.PlayerDeathRPC for the death counter.");
				}
				else
				{
					Log.LogWarning((object)"PlayerAvatar.PlayerDeathRPC not found; the death counter is disabled for this game version.");
				}
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"Failed to patch PlayerDeathRPC: {arg}");
			}
			try
			{
				Type type2 = AccessTools.TypeByName("HurtCollider");
				MethodInfo methodInfo2 = ((type2 == null) ? null : AccessTools.Method(type2, "PlayerHurt", (Type[])null, (Type[])null));
				if (methodInfo2 != null)
				{
					harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(Plugin), "HurtCollider_PlayerHurt_Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					Log.LogInfo((object)"Patched HurtCollider.PlayerHurt for death-cause contact tracking.");
				}
				else
				{
					Log.LogWarning((object)"HurtCollider.PlayerHurt NOT FOUND — enemy-contact death causes will fall back to proximity.");
				}
			}
			catch (Exception arg2)
			{
				Log.LogError((object)$"Failed to patch HurtCollider.PlayerHurt: {arg2}");
			}
			try
			{
				Type type3 = AccessTools.TypeByName("PlayerHealth");
				MethodInfo methodInfo3 = ((type3 == null) ? null : AccessTools.Method(type3, "HurtOtherRPC", (Type[])null, (Type[])null));
				if (methodInfo3 != null)
				{
					harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(Plugin), "PlayerHealth_HurtOtherRPC_Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					Log.LogInfo((object)"Patched PlayerHealth.HurtOtherRPC for death-cause contact tracking.");
				}
				else
				{
					Log.LogWarning((object)"PlayerHealth.HurtOtherRPC NOT FOUND — networked-hit death causes will fall back to proximity.");
				}
			}
			catch (Exception arg3)
			{
				Log.LogError((object)$"Failed to patch PlayerHealth.HurtOtherRPC: {arg3}");
			}
			try
			{
				Type type4 = AccessTools.TypeByName("PlayerTumble");
				MethodInfo methodInfo4 = ((type4 == null) ? null : AccessTools.Method(type4, "ImpactHurtSet", (Type[])null, (Type[])null));
				if (methodInfo4 != null)
				{
					harmony.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(Plugin), "PlayerTumble_ImpactHurtSet_Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					Log.LogInfo((object)"Patched PlayerTumble.ImpactHurtSet for fall-death detection.");
				}
				else
				{
					Log.LogWarning((object)"PlayerTumble.ImpactHurtSet NOT FOUND — fall deaths may show as Environment.");
				}
			}
			catch (Exception arg4)
			{
				Log.LogError((object)$"Failed to patch PlayerTumble.ImpactHurtSet: {arg4}");
			}
			try
			{
				Type type5 = AccessTools.TypeByName("PlayerAvatar");
				MethodInfo methodInfo5 = ((type5 == null) ? null : AccessTools.Method(type5, "PlayerDeath", (Type[])null, (Type[])null));
				if (methodInfo5 != null)
				{
					harmony.Patch((MethodBase)methodInfo5, new HarmonyMethod(AccessTools.Method(typeof(Plugin), "PlayerAvatar_PlayerDeath_Prefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					Log.LogInfo((object)"Patched PlayerAvatar.PlayerDeath for MP death-cause reporting.");
				}
				else
				{
					Log.LogWarning((object)"PlayerAvatar.PlayerDeath NOT FOUND — death-cause encoding disabled; host proximity fallback only.");
				}
			}
			catch (Exception arg5)
			{
				Log.LogError((object)$"Failed to patch PlayerAvatar.PlayerDeath: {arg5}");
			}
			try
			{
				Type type6 = AccessTools.TypeByName("RunManager");
				MethodInfo methodInfo6 = ((type6 == null) ? null : AccessTools.Method(type6, "ResetProgress", (Type[])null, (Type[])null));
				if (methodInfo6 != null)
				{
					harmony.Patch((MethodBase)methodInfo6, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(Plugin), "RunManager_ResetProgress_Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					Log.LogInfo((object)"Patched RunManager.ResetProgress — death counts now reset on a save wipe / new campaign.");
				}
				else
				{
					Log.LogWarning((object)"RunManager.ResetProgress NOT FOUND — death counts may survive a save wipe.");
				}
			}
			catch (Exception arg6)
			{
				Log.LogError((object)$"Failed to patch RunManager.ResetProgress: {arg6}");
			}
			((Component)this).gameObject.AddComponent<DeathSyncBehaviour>();
			Log.LogInfo((object)"REPO Death Counter 1.3.2 loaded.");
		}

		private void OnGUI()
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: 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_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0150: 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_015e: Unknown result type (might be due to invalid IL or missing references)
			//IL_015f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0168: Expected O, but got Unknown
			//IL_016f: 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)
			//IL_0195: Unknown result type (might be due to invalid IL or missing references)
			//IL_019c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ad: Expected O, but got Unknown
			//IL_01b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ca: Expected O, but got Unknown
			//IL_01db: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
			if (!showOverlay.Value || !IsInTruckLobby())
			{
				return;
			}
			int num = Mathf.Clamp(titleFontSize.Value, 6, 100);
			int num2 = Mathf.Clamp(itemFontSize.Value, 6, 100);
			Color textColor = ParseColor(titleColorHex.Value, Color.red);
			Color val = ParseColor(itemColorHex.Value, Color.white);
			OverlayCorner value = overlayCorner.Value;
			bool num3 = value == OverlayCorner.TopRight || value == OverlayCorner.BottomRight;
			bool flag = value == OverlayCorner.BottomLeft || value == OverlayCorner.BottomRight;
			TextAnchor alignment = (TextAnchor)(num3 ? 2 : 0);
			List<(string, string, int)> list = BuildSortedLines();
			int num4 = Mathf.Max(1, list.Count) + ((showTotal.Value && list.Count > 0) ? 1 : 0);
			float num5 = (float)num + 10f + (float)num4 * ((float)num2 + 6f) + 20f;
			float num6 = (num3 ? ((float)Screen.width - 440f - 20f) : 20f);
			float num7 = (flag ? Mathf.Max(20f, (float)Screen.height - num5 - 20f) : 20f);
			GUILayout.BeginArea(new Rect(num6, num7, 440f, num5));
			GUI.color = Color.white;
			GUIStyle val2 = new GUIStyle(GUI.skin.label)
			{
				fontSize = num,
				fontStyle = (FontStyle)1,
				alignment = alignment
			};
			val2.normal.textColor = textColor;
			GUILayout.Label("--- Player Deaths ---", val2, Array.Empty<GUILayoutOption>());
			GUIStyle val3 = new GUIStyle(GUI.skin.label)
			{
				fontSize = num2,
				fontStyle = (FontStyle)0,
				alignment = alignment
			};
			val3.normal.textColor = val;
			GUIStyle val4 = new GUIStyle(val3)
			{
				fontStyle = (FontStyle)1
			};
			val4.normal.textColor = ParseColor(selfColorHex.Value, val);
			string text = (highlightSelf.Value ? GetLocalSteamId() : null);
			if (list.Count == 0)
			{
				GUILayout.Label("No deaths recorded yet.", val3, Array.Empty<GUILayoutOption>());
			}
			else
			{
				int num8 = 0;
				foreach (var item4 in list)
				{
					string item = item4.Item1;
					string item2 = item4.Item2;
					int item3 = item4.Item3;
					bool flag2 = highlightSelf.Value && !string.IsNullOrEmpty(text) && item == text;
					GUILayout.Label(item2, flag2 ? val4 : val3, Array.Empty<GUILayoutOption>());
					num8 += item3;
				}
				if (showTotal.Value)
				{
					GUILayout.Label($"Total: {num8}", val3, Array.Empty<GUILayoutOption>());
				}
			}
			GUILayout.EndArea();
		}

		private static List<(string SteamId, string Text, int Deaths)> BuildSortedLines()
		{
			List<KeyValuePair<string, int>> list = new List<KeyValuePair<string, int>>(DeathCountsBySteamId);
			switch (sortMode.Value)
			{
			case SortMode.DeathsAscending:
				list.Sort((KeyValuePair<string, int> a, KeyValuePair<string, int> b) => a.Value.CompareTo(b.Value));
				break;
			case SortMode.DeathsDescending:
				list.Sort((KeyValuePair<string, int> a, KeyValuePair<string, int> b) => b.Value.CompareTo(a.Value));
				break;
			case SortMode.NameAscending:
				list.Sort((KeyValuePair<string, int> a, KeyValuePair<string, int> b) => string.Compare(NameOf(a.Key), NameOf(b.Key), StringComparison.OrdinalIgnoreCase));
				break;
			case SortMode.NameDescending:
				list.Sort((KeyValuePair<string, int> a, KeyValuePair<string, int> b) => string.Compare(NameOf(b.Key), NameOf(a.Key), StringComparison.OrdinalIgnoreCase));
				break;
			}
			List<(string, string, int)> list2 = new List<(string, string, int)>(list.Count);
			foreach (KeyValuePair<string, int> item in list)
			{
				string text = $"{NameOf(item.Key)}: {item.Value}";
				if (showDeathCause.Value)
				{
					string text2 = NemesisOf(item.Key);
					if (!string.IsNullOrEmpty(text2))
					{
						text = text + "   ☠ " + text2;
					}
				}
				list2.Add((item.Key, text, item.Value));
			}
			return list2;
		}

		private static string? GetLocalSteamId()
		{
			try
			{
				if (!localPlayerReflectionResolved)
				{
					Type type = AccessTools.TypeByName("PlayerAvatar");
					if (type != null)
					{
						playerAvatarInstanceField = AccessTools.Field(type, "instance");
						playerAvatarSteamIdField = AccessTools.Field(type, "steamID");
					}
					localPlayerReflectionResolved = true;
				}
				object obj = playerAvatarInstanceField?.GetValue(null);
				if (obj == null)
				{
					return null;
				}
				return playerAvatarSteamIdField?.GetValue(obj) as string;
			}
			catch
			{
				return null;
			}
		}

		private static string NameOf(string steamId)
		{
			if (!PlayerNamesBySteamId.TryGetValue(steamId, out string value) || string.IsNullOrEmpty(value))
			{
				return steamId;
			}
			return value;
		}

		private static Color ParseColor(string hex, Color 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_002e: Unknown result type (might be due to invalid IL or missing references)
			if (string.IsNullOrWhiteSpace(hex))
			{
				return fallback;
			}
			if (!hex.StartsWith("#"))
			{
				hex = "#" + hex;
			}
			Color result = default(Color);
			if (!ColorUtility.TryParseHtmlString(hex, ref result))
			{
				return fallback;
			}
			return result;
		}

		private static bool IsInTruckLobby()
		{
			try
			{
				if (!runManagerReflectionResolved)
				{
					Type type = AccessTools.TypeByName("RunManager");
					if (type != null)
					{
						runManagerInstanceField = AccessTools.Field(type, "instance");
						runManagerLevelCurrentField = AccessTools.Field(type, "levelCurrent");
						runManagerLevelLobbyField = AccessTools.Field(type, "levelLobby");
					}
					runManagerReflectionResolved = true;
				}
				if (runManagerInstanceField == null || runManagerLevelCurrentField == null || runManagerLevelLobbyField == null)
				{
					return false;
				}
				object value = runManagerInstanceField.GetValue(null);
				if (value == null)
				{
					return false;
				}
				object value2 = runManagerLevelCurrentField.GetValue(value);
				object value3 = runManagerLevelLobbyField.GetValue(value);
				return value2 != null && value3 != null && value2 == value3;
			}
			catch
			{
				return false;
			}
		}

		private static void PlayerDeathRpcPrefix(object __instance, int __0)
		{
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (__instance == null)
				{
					return;
				}
				Type type = __instance.GetType();
				Type type2 = AccessTools.TypeByName("HurtCollider");
				Type type3 = AccessTools.TypeByName("Enemy");
				if (type2 != null && type3 != null)
				{
					ResolveReflectionFields(type, type2, type3);
				}
				if ((ignoreShopDeaths.Value && IsInShop()) || !DeathSyncBehaviour.IsHost())
				{
					return;
				}
				string text = (playerSteamIdFieldCached?.GetValue(__instance) as string) ?? "";
				string text2 = (playerPlayerNameField?.GetValue(__instance) as string) ?? "";
				string text3 = DecodeKiller(__0);
				if (text3 == "Environment")
				{
					Component val = (Component)((__instance is Component) ? __instance : null);
					if (val != null && TryNearestEnemy(val.transform.position, 6f, out int _, out string enemyName))
					{
						text3 = enemyName;
						ManualLogSource? log = Log;
						if (log != null)
						{
							log.LogInfo((object)("[DeathCounter] Environment death re-attributed by proximity → " + enemyName));
						}
					}
				}
				ManualLogSource? log2 = Log;
				if (log2 != null)
				{
					log2.LogInfo((object)$"[DeathCounter] RPC: steamId={text}, name={text2}, encodedIdx={__0}, cause={text3}");
				}
				DeathSyncBehaviour.OnHostDeath(text, text2, text3);
			}
			catch (Exception arg)
			{
				ManualLogSource? log3 = Log;
				if (log3 != null)
				{
					log3.LogError((object)$"[DeathCounter] PlayerDeathRpcPrefix error: {arg}");
				}
			}
		}

		internal static string DecodeKiller(int encodedIndex)
		{
			if (encodedIndex >= 0)
			{
				return ResolveEnemyName(encodedIndex);
			}
			if (encodedIndex == -2)
			{
				return "Fall";
			}
			if (encodedIndex == -3)
			{
				return "Player";
			}
			if (encodedIndex == -4)
			{
				return "EnemyImpact";
			}
			if (encodedIndex <= -100)
			{
				return ResolveEnemyName(-(encodedIndex + 100));
			}
			return "Environment";
		}

		private static void ResolveReflectionFields(Type playerType, Type hcType, Type enemyType)
		{
			if ((object)playerIsLocalField == null)
			{
				playerIsLocalField = AccessTools.Field(playerType, "isLocal");
			}
			if ((object)playerSteamIdFieldCached == null)
			{
				playerSteamIdFieldCached = AccessTools.Field(playerType, "steamID");
			}
			if ((object)playerPlayerNameField == null)
			{
				playerPlayerNameField = AccessTools.Field(playerType, "playerName");
			}
			if ((object)hcEnemyHostField == null)
			{
				hcEnemyHostField = AccessTools.Field(hcType, "enemyHost");
			}
			if ((object)hcPlayerCausingHurtField == null)
			{
				hcPlayerCausingHurtField = AccessTools.Field(hcType, "playerCausingHurt");
			}
			if (enemyEnemyParentField == null && enemyEnemyParentProperty == null)
			{
				enemyEnemyParentField = AccessTools.Field(enemyType, "EnemyParent");
				if (enemyEnemyParentField == null)
				{
					enemyEnemyParentProperty = AccessTools.Property(enemyType, "EnemyParent");
				}
			}
			if (enemyParentEnemyNameField == null)
			{
				Type type = AccessTools.TypeByName("EnemyParent");
				if (type != null)
				{
					enemyParentEnemyNameField = AccessTools.Field(type, "enemyName");
				}
			}
			if (ptPlayerAvatarField == null)
			{
				Type type2 = AccessTools.TypeByName("PlayerTumble");
				if (type2 != null)
				{
					ptPlayerAvatarField = AccessTools.Field(type2, "playerAvatar");
				}
			}
			if (semiFuncEnemyGetIndexMethod == null)
			{
				Type type3 = AccessTools.TypeByName("SemiFunc");
				if (type3 != null)
				{
					semiFuncEnemyGetIndexMethod = AccessTools.Method(type3, "EnemyGetIndex", (Type[])null, (Type[])null);
				}
			}
		}

		private static string ResolveEnemyName(int enemyIndex)
		{
			try
			{
				if (!enemyGetFromIndexResolved)
				{
					Type type = AccessTools.TypeByName("SemiFunc");
					enemyGetFromIndexMethod = ((type == null) ? null : AccessTools.Method(type, "EnemyGetFromIndex", (Type[])null, (Type[])null));
					enemyGetFromIndexResolved = true;
				}
				object obj = enemyGetFromIndexMethod?.Invoke(null, new object[1] { enemyIndex });
				if (obj == null)
				{
					return "Unknown";
				}
				return EnemyNameFromObject(obj);
			}
			catch
			{
				return "Unknown";
			}
		}

		internal static string EnemyNameFromObject(object enemy)
		{
			try
			{
				object obj = enemyEnemyParentField?.GetValue(enemy) ?? enemyEnemyParentProperty?.GetValue(enemy);
				if (obj == null)
				{
					return "Unknown";
				}
				string text = enemyParentEnemyNameField?.GetValue(obj) as string;
				return string.IsNullOrEmpty(text) ? "Unknown" : text;
			}
			catch
			{
				return "Unknown";
			}
		}

		private static string? NemesisOf(string steamId)
		{
			if (!DeathCausesBySteamId.TryGetValue(steamId, out Dictionary<string, int> value) || value.Count == 0)
			{
				return null;
			}
			string result = null;
			int num = 0;
			foreach (KeyValuePair<string, int> item in value)
			{
				if (item.Value > num)
				{
					num = item.Value;
					result = item.Key;
				}
			}
			return result;
		}

		private static bool IsInShop()
		{
			try
			{
				if (!runIsShopResolved)
				{
					Type type = AccessTools.TypeByName("SemiFunc");
					runIsShopMethod = ((type == null) ? null : AccessTools.Method(type, "RunIsShop", (Type[])null, (Type[])null));
					Type type2 = AccessTools.TypeByName("ShopManager");
					shopManagerInstanceField = ((type2 == null) ? null : AccessTools.Field(type2, "instance"));
					runIsShopResolved = true;
				}
				if (runIsShopMethod != null && (bool)runIsShopMethod.Invoke(null, null))
				{
					return true;
				}
				if (shopManagerInstanceField != null && shopManagerInstanceField.GetValue(null) != null)
				{
					return true;
				}
				return false;
			}
			catch
			{
				return false;
			}
		}

		private static void HurtCollider_PlayerHurt_Postfix(object __instance, object __0)
		{
			try
			{
				if (__instance == null || __0 == null)
				{
					return;
				}
				Type type = __0.GetType();
				Type type2 = __instance.GetType();
				Type type3 = AccessTools.TypeByName("Enemy");
				if (type3 == null)
				{
					return;
				}
				ResolveReflectionFields(type, type2, type3);
				object obj = playerIsLocalField?.GetValue(__0);
				if (!(obj is bool) || !(bool)obj)
				{
					return;
				}
				string text = (playerSteamIdFieldCached?.GetValue(__0) as string) ?? "";
				if (string.IsNullOrEmpty(text))
				{
					return;
				}
				object obj2 = hcEnemyHostField?.GetValue(__instance);
				if (obj2 != null)
				{
					int item = -1;
					try
					{
						if (semiFuncEnemyGetIndexMethod != null)
						{
							item = (int)semiFuncEnemyGetIndexMethod.Invoke(null, new object[1] { obj2 });
						}
					}
					catch
					{
					}
					LastEnemyContact[text] = (item, EnemyNameFromObject(obj2), Time.time);
				}
				else
				{
					object obj4 = hcPlayerCausingHurtField?.GetValue(__instance);
					if (obj4 != null && obj4 != __0)
					{
						LastPlayerContact[text] = Time.time;
					}
				}
			}
			catch
			{
			}
		}

		private static void PlayerHealth_HurtOtherRPC_Postfix(object __instance, int damage, Vector3 hurtPosition, int enemyIndex)
		{
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (__instance == null || damage <= 0)
				{
					return;
				}
				Type type = __instance.GetType();
				if ((object)phPlayerAvatarField == null)
				{
					phPlayerAvatarField = AccessTools.Field(type, "playerAvatar");
				}
				object obj = phPlayerAvatarField?.GetValue(__instance);
				if (obj == null)
				{
					return;
				}
				Type type2 = obj.GetType();
				Type type3 = AccessTools.TypeByName("HurtCollider");
				Type type4 = AccessTools.TypeByName("Enemy");
				if (type3 != null && type4 != null)
				{
					ResolveReflectionFields(type2, type3, type4);
				}
				object obj2 = playerIsLocalField?.GetValue(obj);
				if (!(obj2 is bool) || !(bool)obj2)
				{
					return;
				}
				string text = (playerSteamIdFieldCached?.GetValue(obj) as string) ?? "";
				if (!string.IsNullOrEmpty(text))
				{
					int enemyIndex2;
					string enemyName;
					if (enemyIndex >= 0)
					{
						LastEnemyContact[text] = (enemyIndex, ResolveEnemyName(enemyIndex), Time.time);
					}
					else if (TryNearestEnemy(hurtPosition, 5f, out enemyIndex2, out enemyName))
					{
						LastEnemyContact[text] = (enemyIndex2, enemyName, Time.time);
					}
				}
			}
			catch
			{
			}
		}

		internal static bool TryNearestEnemy(Vector3 pos, float maxDist, out int enemyIndex, out string enemyName)
		{
			//IL_017f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_019c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
			enemyIndex = -1;
			enemyName = "";
			try
			{
				Type type = AccessTools.TypeByName("EnemyDirector");
				if (type == null)
				{
					return false;
				}
				if ((object)edInstanceField == null)
				{
					edInstanceField = AccessTools.Field(type, "instance");
				}
				if ((object)edEnemiesSpawnedField == null)
				{
					edEnemiesSpawnedField = AccessTools.Field(type, "enemiesSpawned");
				}
				object obj = edInstanceField?.GetValue(null);
				if (obj == null)
				{
					return false;
				}
				if (!(edEnemiesSpawnedField?.GetValue(obj) is IEnumerable enumerable))
				{
					return false;
				}
				Type type2 = AccessTools.TypeByName("EnemyParent");
				if (type2 != null)
				{
					if ((object)epSpawnedField == null)
					{
						epSpawnedField = AccessTools.Field(type2, "Spawned");
					}
					if ((object)epEnableObjectField == null)
					{
						epEnableObjectField = AccessTools.Field(type2, "EnableObject");
					}
					if ((object)enemyParentEnemyNameField == null)
					{
						enemyParentEnemyNameField = AccessTools.Field(type2, "enemyName");
					}
				}
				if ((object)enemyTypeCached == null)
				{
					enemyTypeCached = AccessTools.TypeByName("Enemy");
				}
				float num = maxDist * maxDist;
				object obj2 = null;
				foreach (object item in enumerable)
				{
					if (item == null)
					{
						continue;
					}
					object obj3 = epSpawnedField?.GetValue(item);
					if (obj3 is bool && !(bool)obj3)
					{
						continue;
					}
					object? obj4 = epEnableObjectField?.GetValue(item);
					GameObject val = (GameObject)((obj4 is GameObject) ? obj4 : null);
					Vector3 position;
					if ((Object)(object)val != (Object)null)
					{
						position = val.transform.position;
					}
					else
					{
						Component val2 = (Component)((item is Component) ? item : null);
						if (val2 == null)
						{
							continue;
						}
						position = val2.transform.position;
					}
					Vector3 val3 = position - pos;
					float sqrMagnitude = ((Vector3)(ref val3)).sqrMagnitude;
					if (sqrMagnitude < num)
					{
						num = sqrMagnitude;
						obj2 = item;
					}
				}
				if (obj2 == null)
				{
					return false;
				}
				enemyName = (enemyParentEnemyNameField?.GetValue(obj2) as string) ?? "";
				if (string.IsNullOrEmpty(enemyName))
				{
					enemyName = "Unknown";
				}
				try
				{
					if (enemyTypeCached != null)
					{
						Component val4 = (Component)((obj2 is Component) ? obj2 : null);
						if (val4 != null && semiFuncEnemyGetIndexMethod != null)
						{
							Component componentInChildren = val4.GetComponentInChildren(enemyTypeCached, true);
							if ((Object)(object)componentInChildren != (Object)null)
							{
								enemyIndex = (int)semiFuncEnemyGetIndexMethod.Invoke(null, new object[1] { componentInChildren });
							}
						}
					}
				}
				catch
				{
				}
				return true;
			}
			catch
			{
				return false;
			}
		}

		private static void PlayerTumble_ImpactHurtSet_Postfix(object __instance)
		{
			try
			{
				if (__instance == null)
				{
					return;
				}
				Type type = AccessTools.TypeByName("PlayerAvatar");
				Type type2 = AccessTools.TypeByName("HurtCollider");
				Type type3 = AccessTools.TypeByName("Enemy");
				if (type == null || type2 == null || type3 == null)
				{
					return;
				}
				ResolveReflectionFields(type, type2, type3);
				object obj = ptPlayerAvatarField?.GetValue(__instance);
				if (obj == null)
				{
					return;
				}
				object obj2 = playerIsLocalField?.GetValue(obj);
				if (obj2 is bool && (bool)obj2)
				{
					string text = (playerSteamIdFieldCached?.GetValue(obj) as string) ?? "";
					if (!string.IsNullOrEmpty(text))
					{
						LastFallDamage[text] = Time.time;
					}
				}
			}
			catch
			{
			}
		}

		private static void PlayerAvatar_PlayerDeath_Prefix(object __instance, ref int __0)
		{
			try
			{
				if (__instance == null)
				{
					return;
				}
				Type type = __instance.GetType();
				Type type2 = AccessTools.TypeByName("HurtCollider");
				Type type3 = AccessTools.TypeByName("Enemy");
				if (type2 != null && type3 != null)
				{
					ResolveReflectionFields(type, type2, type3);
				}
				object obj = playerIsLocalField?.GetValue(__instance);
				if (!(obj is bool) || !(bool)obj || (ignoreShopDeaths.Value && IsInShop()))
				{
					return;
				}
				string text = (playerSteamIdFieldCached?.GetValue(__instance) as string) ?? "";
				if (string.IsNullOrEmpty(text))
				{
					return;
				}
				ManualLogSource? log = Log;
				if (log != null)
				{
					log.LogInfo((object)$"[DeathCounter] PlayerDeath fired: enemyIndex={__0}, steamId={text}");
				}
				if (__0 != -1)
				{
					return;
				}
				float time = Time.time;
				(int, string, float) value2;
				float value3;
				if (LastPlayerContact.TryGetValue(text, out var value) && time - value < 8f)
				{
					ManualLogSource? log2 = Log;
					if (log2 != null)
					{
						log2.LogInfo((object)$"[DeathCounter] Encoding PvP (-3), last contact {time - value:F2}s ago");
					}
					__0 = -3;
				}
				else if (LastEnemyContact.TryGetValue(text, out value2) && time - value2.Item3 < 8f)
				{
					ManualLogSource? log3 = Log;
					if (log3 != null)
					{
						log3.LogInfo((object)$"[DeathCounter] Encoding enemy impact, idx={value2.Item1}, name={value2.Item2}, last contact {time - value2.Item3:F2}s ago");
					}
					__0 = ((value2.Item1 >= 0) ? (-(100 + value2.Item1)) : (-4));
				}
				else if (LastFallDamage.TryGetValue(text, out value3) && time - value3 < 8f)
				{
					ManualLogSource? log4 = Log;
					if (log4 != null)
					{
						log4.LogInfo((object)$"[DeathCounter] Encoding Fall (-2), last fall {time - value3:F2}s ago");
					}
					__0 = -2;
				}
				else
				{
					ManualLogSource? log5 = Log;
					if (log5 != null)
					{
						log5.LogInfo((object)"[DeathCounter] No contact found, stays Environment (-1)");
					}
				}
			}
			catch (Exception arg)
			{
				ManualLogSource? log6 = Log;
				if (log6 != null)
				{
					log6.LogError((object)$"[DeathCounter] PlayerDeath_Prefix error: {arg}");
				}
			}
		}

		private static void RunManager_ResetProgress_Postfix()
		{
			try
			{
				ManualLogSource? log = Log;
				if (log != null)
				{
					log.LogInfo((object)"[DeathCounter] RunManager.ResetProgress — clearing death counts (save wiped).");
				}
				DeathCountsBySteamId.Clear();
				PlayerNamesBySteamId.Clear();
				DeathCausesBySteamId.Clear();
				LastEnemyContact.Clear();
				LastPlayerContact.Clear();
				LastFallDamage.Clear();
				if (DeathSyncBehaviour.IsHost())
				{
					DeathSyncBehaviour.SaveToDisk();
					DeathSyncBehaviour.BroadcastDeathsPublic();
				}
			}
			catch (Exception arg)
			{
				ManualLogSource? log2 = Log;
				if (log2 != null)
				{
					log2.LogError((object)$"[DeathCounter] ResetProgress postfix error: {arg}");
				}
			}
		}

		internal static void TrackerLog(string message)
		{
			ManualLogSource? log = Log;
			if (log != null)
			{
				log.LogInfo((object)message);
			}
		}

		internal static void TrackerErr(string message)
		{
			ManualLogSource? log = Log;
			if (log != null)
			{
				log.LogError((object)message);
			}
		}
	}
}