Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of RepoDeathCounter v1.3.2
Matsu.RepoDeathCounter.dll
Decompiled a month agousing 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); } } } }