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 value)) { value = (Plugin.DeathCausesBySteamId[array7[0]] = new Dictionary()); } 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 value2)) { value2 = (Plugin.DeathCausesBySteamId[text] = new Dictionary()); } 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 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 list = new List(); foreach (KeyValuePair> item2 in Plugin.DeathCausesBySteamId) { foreach (KeyValuePair 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 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 value2)) { foreach (KeyValuePair 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 value)) { value = (Plugin.DeathCausesBySteamId[text2] = new Dictionary()); } 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 DeathCountsBySteamId = new Dictionary(); public static Dictionary PlayerNamesBySteamId = new Dictionary(); public static Dictionary> DeathCausesBySteamId = new Dictionary>(); internal static readonly Dictionary LastEnemyContact = new Dictionary(); internal static readonly Dictionary LastPlayerContact = new Dictionary(); internal static readonly Dictionary LastFallDamage = new Dictionary(); internal const float ContactWindow = 8f; private static MethodInfo? enemyGetFromIndexMethod; private static bool enemyGetFromIndexResolved; private static MethodInfo? semiFuncEnemyGetIndexMethod; private static ConfigEntry showOverlay = null; private static ConfigEntry overlayCorner = null; private static ConfigEntry titleFontSize = null; private static ConfigEntry itemFontSize = null; private static ConfigEntry titleColorHex = null; private static ConfigEntry itemColorHex = null; private static ConfigEntry sortMode = null; private static ConfigEntry showTotal = null; private static ConfigEntry highlightSelf = null; private static ConfigEntry selfColorHex = null; private static ConfigEntry showDeathCause = null; private static ConfigEntry 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("Display", "ShowOverlay", true, "Show the death-count overlay in the truck lobby."); overlayCorner = ((BaseUnityPlugin)this).Config.Bind("Display", "Corner", OverlayCorner.TopRight, "Which screen corner the overlay is anchored to."); titleFontSize = ((BaseUnityPlugin)this).Config.Bind("Display", "TitleFontSize", 24, "Font size of the header."); itemFontSize = ((BaseUnityPlugin)this).Config.Bind("Display", "FontSize", 20, "Font size of each player line."); titleColorHex = ((BaseUnityPlugin)this).Config.Bind("Display", "TitleColorHex", "FF3333", "Header text color, hex RGB (e.g. FF3333). '#' optional."); itemColorHex = ((BaseUnityPlugin)this).Config.Bind("Display", "TextColorHex", "FFFFFF", "Player-line text color, hex RGB (e.g. FFFFFF). '#' optional."); sortMode = ((BaseUnityPlugin)this).Config.Bind("Display", "SortMode", SortMode.DeathsAscending, "List order. DeathsAscending = fewest deaths on top."); showTotal = ((BaseUnityPlugin)this).Config.Bind("Display", "ShowTotal", true, "Show a 'Total' line summing every player's deaths at the bottom of the list."); highlightSelf = ((BaseUnityPlugin)this).Config.Bind("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("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("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("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(); 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()); 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()); } 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()); num8 += item3; } if (showTotal.Value) { GUILayout.Label($"Total: {num8}", val3, Array.Empty()); } } GUILayout.EndArea(); } private static List<(string SteamId, string Text, int Deaths)> BuildSortedLines() { List> list = new List>(DeathCountsBySteamId); switch (sortMode.Value) { case SortMode.DeathsAscending: list.Sort((KeyValuePair a, KeyValuePair b) => a.Value.CompareTo(b.Value)); break; case SortMode.DeathsDescending: list.Sort((KeyValuePair a, KeyValuePair b) => b.Value.CompareTo(a.Value)); break; case SortMode.NameAscending: list.Sort((KeyValuePair a, KeyValuePair b) => string.Compare(NameOf(a.Key), NameOf(b.Key), StringComparison.OrdinalIgnoreCase)); break; case SortMode.NameDescending: list.Sort((KeyValuePair a, KeyValuePair 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 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 value) || value.Count == 0) { return null; } string result = null; int num = 0; foreach (KeyValuePair 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); } } } }