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 LiveStats v1.0.5
LiveStats.dll
Decompiled 2 hours agousing System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using LiveStatsMod.Overlay; using LiveStatsMod.Stats; using Microsoft.CodeAnalysis; using Steamworks.Data; using TMPro; using Unity.Netcode; using UnityEngine; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.UI; [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("LiveStats")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("LiveStats")] [assembly: AssemblyTitle("LiveStats")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [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 LiveStatsMod { internal class HeartbeatDriver : MonoBehaviour { public ConfigEntry<float> IntervalSeconds; private float _timer; private void Awake() { Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); } private void OnDestroy() { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)"HeartbeatDriver was destroyed - live stats will stop updating until relaunch."); } } private void Update() { _timer += Time.unscaledDeltaTime; if (_timer >= IntervalSeconds.Value) { _timer = 0f; StatsBroadcaster.Refresh(); } } } [BepInPlugin("alterego.livestats", "LiveStats", "1.0.5")] public class Plugin : BaseUnityPlugin { public const string Guid = "alterego.livestats"; public const string Name = "LiveStats"; public const string Version = "1.0.5"; internal static ManualLogSource Log; internal static ConfigEntry<float> HeartbeatSeconds; internal static ConfigEntry<bool> OverlayEnabled; internal static ConfigEntry<bool> ShowStreamSchedule; private Harmony _harmony; private void Awake() { //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); HeartbeatSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Overlay", "HeartbeatSeconds", 1f, "How often (seconds) to refresh live stats even without a discrete game event, so timers/quota countdowns stay live."); OverlayEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Overlay", "Enabled", true, "Show the in-game HUD overlay (day, moon, weather, quota, average scrap per day, scrap on ship)."); ShowStreamSchedule = ((BaseUnityPlugin)this).Config.Bind<bool>("PauseMenu", "ShowStreamSchedule", false, "Show a small \"STREAM SCHEDULE\" box in the pause menu's Stats tab. Off by default since the schedule text baked into the mod belongs to its original author - only turn this on if that's you."); _harmony = new Harmony("alterego.livestats"); _harmony.PatchAll(); PlayerLifetimeStats.ResetAllHighestQuotasOnce(); PlayerLifetimeStats.ResetArchNemesisTallyOnce(); Log.LogInfo((object)"LiveStats v1.0.5 loaded."); } internal static void SpawnHeartbeat() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) new GameObject("LiveStats_Heartbeat").AddComponent<HeartbeatDriver>().IntervalSeconds = HeartbeatSeconds; } internal static void SpawnOverlay() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) new GameObject("LiveStats_Overlay").AddComponent<OverlayDriver>(); } } } namespace LiveStatsMod.Stats { internal static class EnemyAttackTracker { private static readonly TimeSpan FreshWindow = TimeSpan.FromSeconds(15.0); private static readonly ConcurrentDictionary<int, (string EnemyName, DateTime RecordedAt)> LastAttacker = new ConcurrentDictionary<int, (string, DateTime)>(); public static void Record(PlayerControllerB player, EnemyAI enemy) { if (!((Object)(object)player == (Object)null) && !((Object)(object)enemy == (Object)null) && !((Object)(object)enemy.enemyType == (Object)null)) { LastAttacker[(int)player.playerClientId] = (enemy.enemyType.enemyName, DateTime.UtcNow); } } public static bool TryGetAttacker(int playerClientId, out string enemyName) { if (LastAttacker.TryGetValue(playerClientId, out (string, DateTime) value) && DateTime.UtcNow - value.Item2 <= FreshWindow) { (enemyName, _) = value; return true; } enemyName = null; return false; } public static void ClearAll() { LastAttacker.Clear(); } } internal static class LiveStats { public static bool InSaveFile; public static int DayNumber = 1; public static int Seed; public static string MoonName = ""; public static string Weather = ""; public static float TimeUntilDeadline; public static int DaysUntilDeadline; public static int ProfitQuota; public static int QuotaFulfilled; public static int ScrapValueOnShip; public static int AverageScrapSum; public static int AverageScrapDayCount; public static int QuotaNumber; public static int LastObservedProfitQuota = -1; public static int ScrapValueCollected; public static int LastDayScrapCollected; public static bool IsLanded; public static DateTime? MoonArrivedAt; public static bool WasInSaveFile; public static string OnScreenMessage = ""; public static double CurrentRunMoonSeconds; public static bool CurrentRunSegmentActive; public static DateTime? CurrentRunSegmentStartedAt; public static double CurrentRunDowntimeSeconds; public static bool DowntimeActive; public static DateTime? DowntimeSegmentStartedAt; public static string PersistedValuesLoadedForSave; public static readonly ConcurrentDictionary<ulong, PlayerStats> Players = new ConcurrentDictionary<ulong, PlayerStats>(); private const string CrewDeathsKey = "LCLiveStatsTracker_CrewDeaths"; private static readonly Dictionary<string, int> PersistedCrewDeathsByUsername = new Dictionary<string, int>(); public static int AverageScrapPerDay() { if (AverageScrapDayCount != 0) { return (int)Math.Round((double)AverageScrapSum / (double)AverageScrapDayCount); } return 0; } public static void LoadPersistedAverage(string saveFileName) { AverageScrapSum = ES3.Load<int>("LCLiveStatsTracker_AvgScrapSum", saveFileName, 0); AverageScrapDayCount = ES3.Load<int>("LCLiveStatsTracker_AvgScrapDayCount", saveFileName, 0); } public static void LoadPersistedRunMoonSeconds(string saveFileName) { CurrentRunMoonSeconds = ES3.Load<double>("LCLiveStatsTracker_RunMoonSeconds", saveFileName, 0.0); } public static void LoadPersistedDowntime(string saveFileName) { CurrentRunDowntimeSeconds = ES3.Load<double>("LCLiveStatsTracker_RunDowntimeSeconds", saveFileName, 0.0); } public static void LoadPersistedCrewDeaths(string saveFileName) { PersistedCrewDeathsByUsername.Clear(); foreach (KeyValuePair<string, int> item in ES3.Load<Dictionary<string, int>>("LCLiveStatsTracker_CrewDeaths", saveFileName, new Dictionary<string, int>())) { PersistedCrewDeathsByUsername[item.Key] = item.Value; } } public static int GetPersistedDeathCount(string username) { if (string.IsNullOrEmpty(username) || !PersistedCrewDeathsByUsername.TryGetValue(username, out var value)) { return 0; } return value; } public static void ResetRunScopedStatsInMemory() { AverageScrapSum = 0; AverageScrapDayCount = 0; CurrentRunMoonSeconds = 0.0; CurrentRunDowntimeSeconds = 0.0; PersistedCrewDeathsByUsername.Clear(); } public static void PersistCrewDeath(string username, int deathCount, string saveFileName) { PersistedCrewDeathsByUsername[username] = deathCount; if (!string.IsNullOrEmpty(saveFileName)) { Dictionary<string, int> dictionary = ES3.Load<Dictionary<string, int>>("LCLiveStatsTracker_CrewDeaths", saveFileName, new Dictionary<string, int>()); dictionary[username] = deathCount; ES3.Save<Dictionary<string, int>>("LCLiveStatsTracker_CrewDeaths", dictionary, saveFileName); } } public static void ResetCrewDeaths(string saveFileName) { foreach (PlayerStats value in Players.Values) { value.DeathCount = 0; } PersistedCrewDeathsByUsername.Clear(); if (!string.IsNullOrEmpty(saveFileName)) { ES3.Save<Dictionary<string, int>>("LCLiveStatsTracker_CrewDeaths", new Dictionary<string, int>(), saveFileName); } } public static void ResetForNewSession() { DayNumber = 1; Seed = 0; MoonName = ""; Weather = ""; TimeUntilDeadline = 0f; DaysUntilDeadline = 0; ProfitQuota = 0; QuotaFulfilled = 0; ScrapValueOnShip = 0; QuotaNumber = 0; LastObservedProfitQuota = -1; ScrapValueCollected = 0; LastDayScrapCollected = 0; IsLanded = false; MoonArrivedAt = null; CurrentRunSegmentActive = false; CurrentRunSegmentStartedAt = null; DowntimeActive = false; DowntimeSegmentStartedAt = null; PersistedValuesLoadedForSave = null; Players.Clear(); } } internal class PlayerStats { public string Name; public bool Alive = true; public bool Disconnected; public string CauseOfDeath; public int DeathCount; public int JumpsThisRound; } internal static class LobbyPrivacy { public const string PublicDataKey = "LiveStats_Public"; public static bool IsCurrentLobbyPublic() { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) Lobby? val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.currentLobby : ((Lobby?)null)); if (!val.HasValue) { return false; } Lobby value = val.Value; return ((Lobby)(ref value)).GetData("LiveStats_Public") == "1"; } } internal static class PlayerLifetimeStats { private const string GlobalFile = "LCLiveStatsTracker_GlobalPlayerStats"; private const string KnownPlayersKey = "LCLiveStatsTracker_KnownPlayers"; private const string HighestQuotaResetDoneKey = "LCLiveStatsTracker_HighestQuotaResetDone"; private const string ArchNemesisResetDoneKey = "LCLiveStatsTracker_ArchNemesisResetDone"; private static readonly string[] AllSuffixes = new string[15] { "DaysSurvived", "CurrentStreak", "MaxStreak", "TotalSteps", "TotalScrapCollected", "ScrapDayCount", "TotalJumps", "TotalDamageTaken", "HighestQuota", "MoonVisits", "TotalMoonTimeSeconds", "TotalDeaths", "DeathCauses", "DeathMoons", "EnemyKills" }; private static string Key(string username, string suffix) { return "LCLiveStatsTracker_P_" + username + "_" + suffix; } private static void RememberPlayer(string username) { List<string> list = ES3.Load<List<string>>("LCLiveStatsTracker_KnownPlayers", "LCLiveStatsTracker_GlobalPlayerStats", new List<string>()); if (!list.Contains(username)) { list.Add(username); ES3.Save<List<string>>("LCLiveStatsTracker_KnownPlayers", list, "LCLiveStatsTracker_GlobalPlayerStats"); } } public static void RecordDayEnd(string username, bool survived, int stepsThisRound, int scrapThisRound, int jumpsThisRound, int damageTakenThisRound, int currentQuota, string moonName, bool countsForMoon) { if (string.IsNullOrEmpty(username)) { return; } RememberPlayer(username); if (survived) { ES3.Save<int>(Key(username, "DaysSurvived"), ES3.Load<int>(Key(username, "DaysSurvived"), "LCLiveStatsTracker_GlobalPlayerStats", 0) + 1, "LCLiveStatsTracker_GlobalPlayerStats"); int num = ES3.Load<int>(Key(username, "CurrentStreak"), "LCLiveStatsTracker_GlobalPlayerStats", 0) + 1; ES3.Save<int>(Key(username, "CurrentStreak"), num, "LCLiveStatsTracker_GlobalPlayerStats"); if (num > ES3.Load<int>(Key(username, "MaxStreak"), "LCLiveStatsTracker_GlobalPlayerStats", 0)) { ES3.Save<int>(Key(username, "MaxStreak"), num, "LCLiveStatsTracker_GlobalPlayerStats"); } } else { ES3.Save<int>(Key(username, "CurrentStreak"), 0, "LCLiveStatsTracker_GlobalPlayerStats"); } if (stepsThisRound > 0) { ES3.Save<int>(Key(username, "TotalSteps"), ES3.Load<int>(Key(username, "TotalSteps"), "LCLiveStatsTracker_GlobalPlayerStats", 0) + stepsThisRound, "LCLiveStatsTracker_GlobalPlayerStats"); } if (scrapThisRound > 0) { ES3.Save<int>(Key(username, "TotalScrapCollected"), ES3.Load<int>(Key(username, "TotalScrapCollected"), "LCLiveStatsTracker_GlobalPlayerStats", 0) + scrapThisRound, "LCLiveStatsTracker_GlobalPlayerStats"); } if (countsForMoon) { ES3.Save<int>(Key(username, "ScrapDayCount"), ES3.Load<int>(Key(username, "ScrapDayCount"), "LCLiveStatsTracker_GlobalPlayerStats", 0) + 1, "LCLiveStatsTracker_GlobalPlayerStats"); } if (jumpsThisRound > 0) { ES3.Save<int>(Key(username, "TotalJumps"), ES3.Load<int>(Key(username, "TotalJumps"), "LCLiveStatsTracker_GlobalPlayerStats", 0) + jumpsThisRound, "LCLiveStatsTracker_GlobalPlayerStats"); } if (damageTakenThisRound > 0) { ES3.Save<int>(Key(username, "TotalDamageTaken"), ES3.Load<int>(Key(username, "TotalDamageTaken"), "LCLiveStatsTracker_GlobalPlayerStats", 0) + damageTakenThisRound, "LCLiveStatsTracker_GlobalPlayerStats"); } if (!LobbyPrivacy.IsCurrentLobbyPublic() && currentQuota > ES3.Load<int>(Key(username, "HighestQuota"), "LCLiveStatsTracker_GlobalPlayerStats", 0)) { ES3.Save<int>(Key(username, "HighestQuota"), currentQuota, "LCLiveStatsTracker_GlobalPlayerStats"); } if (countsForMoon && !string.IsNullOrEmpty(moonName)) { Dictionary<string, int> dictionary = ES3.Load<Dictionary<string, int>>(Key(username, "MoonVisits"), "LCLiveStatsTracker_GlobalPlayerStats", new Dictionary<string, int>()); dictionary[moonName] = ((!dictionary.TryGetValue(moonName, out var value)) ? 1 : (value + 1)); ES3.Save<Dictionary<string, int>>(Key(username, "MoonVisits"), dictionary, "LCLiveStatsTracker_GlobalPlayerStats"); } } public static void RecordMoonTime(string username, double elapsedSeconds) { if (!string.IsNullOrEmpty(username) && !(elapsedSeconds <= 0.0)) { RememberPlayer(username); double num = ES3.Load<double>(Key(username, "TotalMoonTimeSeconds"), "LCLiveStatsTracker_GlobalPlayerStats", 0.0) + elapsedSeconds; ES3.Save<double>(Key(username, "TotalMoonTimeSeconds"), num, "LCLiveStatsTracker_GlobalPlayerStats"); } } public static void RecordDeath(string username, string cause, string moonName, string killedBy) { if (!string.IsNullOrEmpty(username)) { RememberPlayer(username); ES3.Save<int>(Key(username, "TotalDeaths"), ES3.Load<int>(Key(username, "TotalDeaths"), "LCLiveStatsTracker_GlobalPlayerStats", 0) + 1, "LCLiveStatsTracker_GlobalPlayerStats"); Dictionary<string, int> dictionary = ES3.Load<Dictionary<string, int>>(Key(username, "DeathCauses"), "LCLiveStatsTracker_GlobalPlayerStats", new Dictionary<string, int>()); dictionary[cause] = ((!dictionary.TryGetValue(cause, out var value)) ? 1 : (value + 1)); ES3.Save<Dictionary<string, int>>(Key(username, "DeathCauses"), dictionary, "LCLiveStatsTracker_GlobalPlayerStats"); if (!string.IsNullOrEmpty(moonName)) { Dictionary<string, int> dictionary2 = ES3.Load<Dictionary<string, int>>(Key(username, "DeathMoons"), "LCLiveStatsTracker_GlobalPlayerStats", new Dictionary<string, int>()); dictionary2[moonName] = ((!dictionary2.TryGetValue(moonName, out var value2)) ? 1 : (value2 + 1)); ES3.Save<Dictionary<string, int>>(Key(username, "DeathMoons"), dictionary2, "LCLiveStatsTracker_GlobalPlayerStats"); } if (!string.IsNullOrEmpty(killedBy)) { Dictionary<string, int> dictionary3 = ES3.Load<Dictionary<string, int>>(Key(username, "EnemyKills"), "LCLiveStatsTracker_GlobalPlayerStats", new Dictionary<string, int>()); dictionary3[killedBy] = ((!dictionary3.TryGetValue(killedBy, out var value3)) ? 1 : (value3 + 1)); ES3.Save<Dictionary<string, int>>(Key(username, "EnemyKills"), dictionary3, "LCLiveStatsTracker_GlobalPlayerStats"); } } } public static List<string> GetKnownPlayers() { return ES3.Load<List<string>>("LCLiveStatsTracker_KnownPlayers", "LCLiveStatsTracker_GlobalPlayerStats", new List<string>()); } public static bool RemovePlayer(string username) { List<string> knownPlayers = GetKnownPlayers(); if (knownPlayers.RemoveAll((string n) => string.Equals(n, username, StringComparison.OrdinalIgnoreCase)) == 0) { return false; } string[] allSuffixes = AllSuffixes; foreach (string suffix in allSuffixes) { ES3.DeleteKey(Key(username, suffix), "LCLiveStatsTracker_GlobalPlayerStats"); } ES3.Save<List<string>>("LCLiveStatsTracker_KnownPlayers", knownPlayers, "LCLiveStatsTracker_GlobalPlayerStats"); return true; } public static void ResetAllHighestQuotasOnce() { if (ES3.Load<bool>("LCLiveStatsTracker_HighestQuotaResetDone", "LCLiveStatsTracker_GlobalPlayerStats", false)) { return; } foreach (string knownPlayer in GetKnownPlayers()) { ES3.Save<int>(Key(knownPlayer, "HighestQuota"), 0, "LCLiveStatsTracker_GlobalPlayerStats"); } ES3.Save<bool>("LCLiveStatsTracker_HighestQuotaResetDone", true, "LCLiveStatsTracker_GlobalPlayerStats"); } public static void ResetArchNemesisTallyOnce() { if (ES3.Load<bool>("LCLiveStatsTracker_ArchNemesisResetDone", "LCLiveStatsTracker_GlobalPlayerStats", false)) { return; } foreach (string knownPlayer in GetKnownPlayers()) { ES3.DeleteKey(Key(knownPlayer, "EnemyKills"), "LCLiveStatsTracker_GlobalPlayerStats"); } ES3.Save<bool>("LCLiveStatsTracker_ArchNemesisResetDone", true, "LCLiveStatsTracker_GlobalPlayerStats"); } public static string BuildProfileText(string username) { int num = ES3.Load<int>(Key(username, "DaysSurvived"), "LCLiveStatsTracker_GlobalPlayerStats", 0); int num2 = ES3.Load<int>(Key(username, "MaxStreak"), "LCLiveStatsTracker_GlobalPlayerStats", 0); int num3 = ES3.Load<int>(Key(username, "HighestQuota"), "LCLiveStatsTracker_GlobalPlayerStats", 0); int num4 = ES3.Load<int>(Key(username, "TotalDeaths"), "LCLiveStatsTracker_GlobalPlayerStats", 0); int num5 = ES3.Load<int>(Key(username, "TotalScrapCollected"), "LCLiveStatsTracker_GlobalPlayerStats", 0); int num6 = ES3.Load<int>(Key(username, "ScrapDayCount"), "LCLiveStatsTracker_GlobalPlayerStats", 0); int num7 = ES3.Load<int>(Key(username, "TotalSteps"), "LCLiveStatsTracker_GlobalPlayerStats", 0); int num8 = ES3.Load<int>(Key(username, "TotalJumps"), "LCLiveStatsTracker_GlobalPlayerStats", 0); int num9 = ES3.Load<int>(Key(username, "TotalDamageTaken"), "LCLiveStatsTracker_GlobalPlayerStats", 0); double totalSeconds = ES3.Load<double>(Key(username, "TotalMoonTimeSeconds"), "LCLiveStatsTracker_GlobalPlayerStats", 0.0); Dictionary<string, int> dictionary = ES3.Load<Dictionary<string, int>>(Key(username, "MoonVisits"), "LCLiveStatsTracker_GlobalPlayerStats", new Dictionary<string, int>()); Dictionary<string, int> dictionary2 = ES3.Load<Dictionary<string, int>>(Key(username, "DeathMoons"), "LCLiveStatsTracker_GlobalPlayerStats", new Dictionary<string, int>()); Dictionary<string, int> dictionary3 = ES3.Load<Dictionary<string, int>>(Key(username, "EnemyKills"), "LCLiveStatsTracker_GlobalPlayerStats", new Dictionary<string, int>()); int num10 = ((num6 > 0) ? (num5 / num6) : 0); string text = ((dictionary.Count > 0) ? dictionary.OrderByDescending((KeyValuePair<string, int> kv) => kv.Value).First().Key : "-"); string text2 = ((dictionary2.Count > 0) ? dictionary2.OrderByDescending((KeyValuePair<string, int> kv) => kv.Value).First().Key : "-"); string text3 = ((dictionary3.Count > 0) ? dictionary3.OrderByDescending((KeyValuePair<string, int> kv) => kv.Value).First().Key : "-"); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("PROFILE: " + username); stringBuilder.AppendLine(); stringBuilder.AppendLine($"Days Survived: {num}"); stringBuilder.AppendLine($"Best Streak: {num2}"); stringBuilder.AppendLine($"Highest Quota: ${num3}"); stringBuilder.AppendLine($"Total Deaths: {num4}"); stringBuilder.AppendLine($"Total Scrap Collected: ${num5}"); stringBuilder.AppendLine($"Average Scrap/Day: ${num10}"); stringBuilder.AppendLine("Favorite Moon: " + text); stringBuilder.AppendLine("Most Dangerous Moon: " + text2); stringBuilder.AppendLine("Total Time Spent On A Moon: " + FormatDuration(totalSeconds)); stringBuilder.AppendLine($"Total Steps Taken: {num7}"); stringBuilder.AppendLine($"Total Jumps: {num8}"); stringBuilder.AppendLine($"Total Damage Taken: {num9}"); stringBuilder.AppendLine("Arch Nemesis: " + text3); return stringBuilder.ToString(); } private static string FormatDuration(double totalSeconds) { TimeSpan timeSpan = TimeSpan.FromSeconds(totalSeconds); if (!(timeSpan.TotalHours >= 1.0)) { return $"{timeSpan.Minutes}m {timeSpan.Seconds}s"; } return $"{(int)timeSpan.TotalHours}h {timeSpan.Minutes}m"; } } internal static class StatsBroadcaster { private const string ClientSessionMarker = "__client_session__"; public static string GetPersistenceSaveFileName() { NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsHost) { return null; } if (!((Object)(object)GameNetworkManager.Instance != (Object)null)) { return null; } return GameNetworkManager.Instance.currentSaveFileName; } public static void Refresh() { try { UpdateLiveStats(); } catch (Exception arg) { Plugin.Log.LogWarning((object)$"Failed to update live stats: {arg}"); } } private static void UpdateLiveStats() { StartOfRound instance = StartOfRound.Instance; TimeOfDay instance2 = TimeOfDay.Instance; bool flag = (Object)(object)NetworkManager.Singleton != (Object)null && NetworkManager.Singleton.IsListening; if (flag && !LiveStats.WasInSaveFile) { LiveStats.ResetForNewSession(); } LiveStats.WasInSaveFile = flag; LiveStats.InSaveFile = flag; if (flag) { if (NetworkManager.Singleton.IsHost) { string text = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.currentSaveFileName : null); if (!string.IsNullOrEmpty(text) && LiveStats.PersistedValuesLoadedForSave != text) { LiveStats.LoadPersistedAverage(text); LiveStats.LoadPersistedRunMoonSeconds(text); LiveStats.LoadPersistedDowntime(text); LiveStats.LoadPersistedCrewDeaths(text); LiveStats.PersistedValuesLoadedForSave = text; } } else if (LiveStats.PersistedValuesLoadedForSave != "__client_session__") { LiveStats.ResetRunScopedStatsInMemory(); LiveStats.PersistedValuesLoadedForSave = "__client_session__"; } } if (flag && (Object)(object)instance != (Object)null) { LiveStats.Seed = instance.randomMapSeed; LiveStats.MoonName = (((Object)(object)instance.currentLevel != (Object)null) ? instance.currentLevel.PlanetName : ""); LiveStats.DayNumber = ((instance.gameStats != null) ? instance.gameStats.daysSpent : 0) + 1; if (instance.allPlayerScripts != null) { PlayerControllerB[] allPlayerScripts = instance.allPlayerScripts; foreach (PlayerControllerB p in allPlayerScripts) { if (!((Object)(object)p == (Object)null) && (LiveStats.Players.ContainsKey(p.playerClientId) || p.isPlayerControlled || p.isPlayerDead || p.disconnectedMidGame)) { PlayerStats orAdd = LiveStats.Players.GetOrAdd(p.playerClientId, (ulong _) => new PlayerStats { Name = p.playerUsername, DeathCount = LiveStats.GetPersistedDeathCount(p.playerUsername) }); orAdd.Name = p.playerUsername; orAdd.Alive = !p.isPlayerDead; bool flag2 = instance.ClientPlayerList != null && instance.ClientPlayerList.ContainsKey(p.actualClientId); orAdd.Disconnected = p.disconnectedMidGame || !flag2; } } } } if (flag && (Object)(object)instance2 != (Object)null) { LiveStats.Weather = ((object)Unsafe.As<LevelWeatherType, LevelWeatherType>(ref instance2.currentLevelWeather)/*cast due to .constrained prefix*/).ToString(); LiveStats.ProfitQuota = instance2.profitQuota; LiveStats.QuotaFulfilled = instance2.quotaFulfilled; LiveStats.TimeUntilDeadline = instance2.timeUntilDeadline; LiveStats.DaysUntilDeadline = instance2.daysUntilDeadline; if (LiveStats.LastObservedProfitQuota == -1) { LiveStats.QuotaNumber = instance2.timesFulfilledQuota + 1; } else if (instance2.profitQuota != LiveStats.LastObservedProfitQuota) { LiveStats.QuotaNumber++; } LiveStats.LastObservedProfitQuota = instance2.profitQuota; bool currentDayTimeStarted = instance2.currentDayTimeStarted; if (currentDayTimeStarted && !LiveStats.IsLanded) { LiveStats.MoonArrivedAt = DateTime.UtcNow; } else if (!currentDayTimeStarted && LiveStats.IsLanded && LiveStats.MoonArrivedAt.HasValue) { double totalSeconds = (DateTime.UtcNow - LiveStats.MoonArrivedAt.Value).TotalSeconds; if ((Object)(object)instance != (Object)null && instance.allPlayerScripts != null) { PlayerControllerB[] allPlayerScripts = instance.allPlayerScripts; foreach (PlayerControllerB val in allPlayerScripts) { if (!((Object)(object)val == (Object)null) && !val.disconnectedMidGame && (val.isPlayerControlled || val.isPlayerDead)) { PlayerLifetimeStats.RecordMoonTime(val.playerUsername, totalSeconds); } } } } LiveStats.IsLanded = currentDayTimeStarted; bool flag3 = !currentDayTimeStarted && (Object)(object)instance != (Object)null && (Object)(object)instance.currentLevel != (Object)null && !instance.currentLevel.spawnEnemiesAndScrap && instance.shipDoorsEnabled; bool flag4 = currentDayTimeStarted || flag3; if (flag4 && !LiveStats.CurrentRunSegmentActive) { LiveStats.CurrentRunSegmentStartedAt = DateTime.UtcNow; } else if (!flag4 && LiveStats.CurrentRunSegmentActive && LiveStats.CurrentRunSegmentStartedAt.HasValue) { double totalSeconds2 = (DateTime.UtcNow - LiveStats.CurrentRunSegmentStartedAt.Value).TotalSeconds; LiveStats.CurrentRunMoonSeconds += totalSeconds2; string persistenceSaveFileName = GetPersistenceSaveFileName(); if (!string.IsNullOrEmpty(persistenceSaveFileName)) { ES3.Save<double>("LCLiveStatsTracker_RunMoonSeconds", LiveStats.CurrentRunMoonSeconds, persistenceSaveFileName); } } LiveStats.CurrentRunSegmentActive = flag4; bool flag5 = !currentDayTimeStarted && !flag3; if (flag5 && !LiveStats.DowntimeActive) { LiveStats.DowntimeSegmentStartedAt = DateTime.UtcNow; } else if (!flag5 && LiveStats.DowntimeActive && LiveStats.DowntimeSegmentStartedAt.HasValue) { double totalSeconds3 = (DateTime.UtcNow - LiveStats.DowntimeSegmentStartedAt.Value).TotalSeconds; LiveStats.CurrentRunDowntimeSeconds += totalSeconds3; string persistenceSaveFileName2 = GetPersistenceSaveFileName(); if (!string.IsNullOrEmpty(persistenceSaveFileName2)) { ES3.Save<double>("LCLiveStatsTracker_RunDowntimeSeconds", LiveStats.CurrentRunDowntimeSeconds, persistenceSaveFileName2); } } LiveStats.DowntimeActive = flag5; } else { LiveStats.IsLanded = false; } if (flag && (Object)(object)instance != (Object)null) { LiveStats.ScrapValueOnShip = instance.GetValueOfAllScrap(true, false); LiveStats.ScrapValueCollected = ((instance.gameStats != null) ? instance.gameStats.scrapValueCollected : 0); } } } internal static class StatsSummaryText { private static readonly Regex PhantomPlayerName = new Regex("^Player #\\d+$", RegexOptions.IgnoreCase); public static string Build(bool richText) { string text = (richText ? "<b>" : ""); string text2 = (richText ? "</b>" : ""); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(text + "CURRENT RUN: " + FormatElapsed() + text2); stringBuilder.AppendLine("Downtime: " + FormatDowntime()); stringBuilder.AppendLine(); stringBuilder.AppendLine($"Day: {LiveStats.DayNumber}"); stringBuilder.AppendLine("Moon: " + ((!LiveStats.IsLanded) ? "In Orbit" : (string.IsNullOrEmpty(LiveStats.MoonName) ? "-" : LiveStats.MoonName))); stringBuilder.AppendLine("Weather: " + (LiveStats.IsLanded ? LiveStats.Weather : "-")); stringBuilder.AppendLine($"Quota {LiveStats.QuotaNumber}: ${LiveStats.ProfitQuota}"); stringBuilder.AppendLine($"Scrap On Ship: ${LiveStats.ScrapValueOnShip}"); stringBuilder.AppendLine($"Total Scrap Sold: ${LiveStats.ScrapValueCollected}"); stringBuilder.AppendLine($"Average Scrap/Day: ${LiveStats.AverageScrapPerDay()}"); stringBuilder.AppendLine(); stringBuilder.AppendLine(text + "CREW" + text2); bool flag = false; foreach (KeyValuePair<ulong, PlayerStats> player in LiveStats.Players) { if (!PhantomPlayerName.IsMatch((player.Value.Name ?? "").Trim())) { flag = true; PlayerStats value = player.Value; stringBuilder.AppendLine($"{value.Name}: {value.DeathCount} deaths"); } } if (!flag) { stringBuilder.AppendLine("-"); } return stringBuilder.ToString(); } private static string FormatElapsed() { double num = LiveStats.CurrentRunMoonSeconds; if (LiveStats.CurrentRunSegmentActive && LiveStats.CurrentRunSegmentStartedAt.HasValue) { num += (DateTime.UtcNow - LiveStats.CurrentRunSegmentStartedAt.Value).TotalSeconds; } return FormatDuration(num); } private static string FormatDowntime() { double num = LiveStats.CurrentRunDowntimeSeconds; if (LiveStats.DowntimeActive && LiveStats.DowntimeSegmentStartedAt.HasValue) { num += (DateTime.UtcNow - LiveStats.DowntimeSegmentStartedAt.Value).TotalSeconds; } return FormatDuration(num); } private static string FormatDuration(double totalSeconds) { TimeSpan timeSpan = TimeSpan.FromSeconds(totalSeconds); if (!(timeSpan.TotalHours >= 1.0)) { return $"{timeSpan.Minutes:00}:{timeSpan.Seconds:00}"; } return $"{(int)timeSpan.TotalHours}:{timeSpan.Minutes:00}:{timeSpan.Seconds:00}"; } } } namespace LiveStatsMod.Patches { internal static class EnemyAttackHelpers { public static void FromCollision(EnemyAI instance, Collider other) { EnemyAttackTracker.Record(instance.MeetsStandardPlayerCollisionConditions(other, false, false), instance); } public static void FromPlayerId(EnemyAI instance, int playerId) { StartOfRound instance2 = StartOfRound.Instance; if (!((Object)(object)instance2 == (Object)null) && instance2.allPlayerScripts != null && playerId >= 0 && playerId < instance2.allPlayerScripts.Length) { EnemyAttackTracker.Record(instance2.allPlayerScripts[playerId], instance); } } public static void FromLocalPlayer(EnemyAI instance) { EnemyAttackTracker.Record(((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null, instance); } } [HarmonyPatch(typeof(BaboonBirdAI), "OnCollideWithPlayer")] internal static class BaboonBirdAttackPatch { private static void Prefix(BaboonBirdAI __instance, Collider other) { EnemyAttackHelpers.FromCollision((EnemyAI)(object)__instance, other); } } [HarmonyPatch(typeof(BlobAI), "OnCollideWithPlayer")] internal static class BlobAttackPatch { private static void Prefix(BlobAI __instance, Collider other) { EnemyAttackHelpers.FromCollision((EnemyAI)(object)__instance, other); } } [HarmonyPatch(typeof(BushWolfEnemy), "OnCollideWithPlayer")] internal static class BushWolfAttackPatch { private static void Prefix(BushWolfEnemy __instance) { EnemyAttackTracker.Record(((EnemyAI)__instance).targetPlayer, (EnemyAI)(object)__instance); } } [HarmonyPatch(typeof(ButlerBeesEnemyAI), "OnCollideWithPlayer")] internal static class ButlerBeesAttackPatch { private static void Prefix(ButlerBeesEnemyAI __instance, Collider other) { EnemyAttackHelpers.FromCollision((EnemyAI)(object)__instance, other); } } [HarmonyPatch(typeof(ButlerEnemyAI), "OnCollideWithPlayer")] internal static class ButlerAttackPatch { private static void Prefix(ButlerEnemyAI __instance, Collider other) { EnemyAttackHelpers.FromCollision((EnemyAI)(object)__instance, other); } } [HarmonyPatch(typeof(CadaverBloomAI))] internal static class CadaverBloomAttackPatch { [HarmonyPatch("BurstForth")] [HarmonyPrefix] private static void BurstForthPrefix(CadaverBloomAI __instance, PlayerControllerB player) { EnemyAttackTracker.Record(player, (EnemyAI)(object)__instance); } [HarmonyPatch("OnCollideWithPlayer")] [HarmonyPrefix] private static void CollidePrefix(CadaverBloomAI __instance, Collider other) { EnemyAttackHelpers.FromCollision((EnemyAI)(object)__instance, other); } } [HarmonyPatch(typeof(CaveDwellerAI), "KillPlayerAnimationClientRpc")] internal static class CaveDwellerAttackPatch { private static void Prefix(CaveDwellerAI __instance, int playerObjectId) { EnemyAttackHelpers.FromPlayerId((EnemyAI)(object)__instance, playerObjectId); } } [HarmonyPatch(typeof(CentipedeAI), "DamagePlayerOnIntervals")] internal static class CentipedeAttackPatch { private static void Prefix(CentipedeAI __instance) { EnemyAttackTracker.Record(__instance.clingingToPlayer, (EnemyAI)(object)__instance); } } [HarmonyPatch(typeof(ClaySurgeonAI), "OnCollideWithPlayer")] internal static class ClaySurgeonAttackPatch { private static void Prefix(ClaySurgeonAI __instance, Collider other) { EnemyAttackHelpers.FromCollision((EnemyAI)(object)__instance, other); } } [HarmonyPatch(typeof(CrawlerAI), "OnCollideWithPlayer")] internal static class CrawlerAttackPatch { private static void Prefix(CrawlerAI __instance, Collider other) { EnemyAttackHelpers.FromCollision((EnemyAI)(object)__instance, other); } } [HarmonyPatch(typeof(DressGirlAI), "OnCollideWithPlayer")] internal static class DressGirlAttackPatch { private static void Prefix(DressGirlAI __instance) { EnemyAttackTracker.Record(__instance.hauntingPlayer, (EnemyAI)(object)__instance); } } [HarmonyPatch(typeof(FlowermanAI), "KillPlayerAnimationClientRpc")] internal static class FlowermanAttackPatch { private static void Prefix(FlowermanAI __instance, int playerObjectId) { EnemyAttackHelpers.FromPlayerId((EnemyAI)(object)__instance, playerObjectId); } } [HarmonyPatch(typeof(ForestGiantAI))] internal static class ForestGiantAttackPatch { [HarmonyPatch("AnimationEventA")] [HarmonyPrefix] private static void AnimationEventAPrefix(ForestGiantAI __instance) { EnemyAttackHelpers.FromLocalPlayer((EnemyAI)(object)__instance); } [HarmonyPatch("BeginEatPlayer")] [HarmonyPrefix] private static void BeginEatPlayerPrefix(ForestGiantAI __instance, PlayerControllerB playerBeingEaten) { EnemyAttackTracker.Record(playerBeingEaten, (EnemyAI)(object)__instance); } } [HarmonyPatch(typeof(GiantKiwiAI), "AnimationEventB")] internal static class GiantKiwiAttackPatch { private static void Prefix(GiantKiwiAI __instance) { EnemyAttackHelpers.FromLocalPlayer((EnemyAI)(object)__instance); } } [HarmonyPatch(typeof(HoarderBugAI), "OnCollideWithPlayer")] internal static class HoarderBugAttackPatch { private static void Prefix(HoarderBugAI __instance, Collider other) { EnemyAttackHelpers.FromCollision((EnemyAI)(object)__instance, other); } } [HarmonyPatch(typeof(JesterAI), "KillPlayerClientRpc")] internal static class JesterAttackPatch { private static void Prefix(JesterAI __instance, int playerId) { EnemyAttackHelpers.FromPlayerId((EnemyAI)(object)__instance, playerId); } } [HarmonyPatch(typeof(LassoManAI), "OnCollideWithPlayer")] internal static class LassoManAttackPatch { private static void Prefix(LassoManAI __instance, Collider other) { EnemyAttackTracker.Record(((Component)other).gameObject.GetComponent<PlayerControllerB>(), (EnemyAI)(object)__instance); } } [HarmonyPatch(typeof(MaskedPlayerEnemy), "KillPlayerAnimationClientRpc")] internal static class MaskedAttackPatch { private static void Prefix(MaskedPlayerEnemy __instance, int playerObjectId) { EnemyAttackHelpers.FromPlayerId((EnemyAI)(object)__instance, playerObjectId); } } [HarmonyPatch(typeof(MouthDogAI), "KillPlayerClientRpc")] internal static class MouthDogAttackPatch { private static void Prefix(MouthDogAI __instance, int playerId) { EnemyAttackHelpers.FromPlayerId((EnemyAI)(object)__instance, playerId); } } [HarmonyPatch(typeof(NutcrackerEnemyAI), "LegKickPlayer")] internal static class NutcrackerAttackPatch { private static void Prefix(NutcrackerEnemyAI __instance, int playerId) { EnemyAttackHelpers.FromPlayerId((EnemyAI)(object)__instance, playerId); } } [HarmonyPatch(typeof(PufferAI), "OnCollideWithPlayer")] internal static class PufferAttackPatch { private static void Prefix(PufferAI __instance, Collider other) { EnemyAttackHelpers.FromCollision((EnemyAI)(object)__instance, other); } } [HarmonyPatch(typeof(PumaAI), "OnCollideWithPlayer")] internal static class PumaAttackPatch { private static void Prefix(PumaAI __instance, Collider other) { EnemyAttackHelpers.FromCollision((EnemyAI)(object)__instance, other); } } [HarmonyPatch(typeof(RadMechAI))] internal static class RadMechAttackPatch { [HarmonyPatch("BeginTorchPlayer")] [HarmonyPrefix] private static void BeginTorchPlayerPrefix(RadMechAI __instance, PlayerControllerB playerBeingTorched) { EnemyAttackTracker.Record(playerBeingTorched, (EnemyAI)(object)__instance); } [HarmonyPatch("Stomp")] [HarmonyPrefix] private static void StompPrefix(RadMechAI __instance) { EnemyAttackHelpers.FromLocalPlayer((EnemyAI)(object)__instance); } } [HarmonyPatch(typeof(RedLocustBees))] internal static class RedLocustBeesAttackPatch { [HarmonyPatch("BeeKillPlayerOnLocalClient")] [HarmonyPrefix] private static void BeeKillPrefix(RedLocustBees __instance, int playerId) { EnemyAttackHelpers.FromPlayerId((EnemyAI)(object)__instance, playerId); } [HarmonyPatch("OnCollideWithPlayer")] [HarmonyPrefix] private static void CollidePrefix(RedLocustBees __instance, Collider other) { EnemyAttackHelpers.FromCollision((EnemyAI)(object)__instance, other); } } [HarmonyPatch(typeof(SandSpiderAI), "OnCollideWithPlayer")] internal static class SandSpiderAttackPatch { private static void Prefix(SandSpiderAI __instance, Collider other) { EnemyAttackHelpers.FromCollision((EnemyAI)(object)__instance, other); } } [HarmonyPatch(typeof(SandWormAI), "EatPlayer")] internal static class SandWormAttackPatch { private static void Prefix(SandWormAI __instance, PlayerControllerB playerScript) { EnemyAttackTracker.Record(playerScript, (EnemyAI)(object)__instance); } } [HarmonyPatch(typeof(SpringManAI), "OnCollideWithPlayer")] internal static class SpringManAttackPatch { private static void Prefix(SpringManAI __instance, Collider other) { EnemyAttackHelpers.FromCollision((EnemyAI)(object)__instance, other); } } [HarmonyPatch(typeof(GameNetworkManager), "SteamMatchmaking_OnLobbyCreated")] internal static class LobbyPrivacyPatch { private static void Postfix(GameNetworkManager __instance, Lobby lobby) { bool flag = __instance.lobbyHostSettings != null && __instance.lobbyHostSettings.isLobbyPublic; ((Lobby)(ref lobby)).SetData("LiveStats_Public", flag ? "1" : "0"); } } [HarmonyPatch(typeof(MenuManager), "Start")] internal static class MenuManagerStartPatch { private static bool _spawned; private static void Postfix(MenuManager __instance) { if (!_spawned && !__instance.isInitScene) { _spawned = true; Plugin.SpawnHeartbeat(); Plugin.SpawnOverlay(); } } } [HarmonyPatch(typeof(QuickMenuManager), "Start")] internal static class QuickMenuStatsTabPatch { private static void Postfix(QuickMenuManager __instance) { try { PauseMenuStatsPanel.Install(__instance); } catch (Exception arg) { Plugin.Log.LogWarning((object)$"Failed to install pause menu Stats tab: {arg}"); } } } [HarmonyPatch(typeof(QuickMenuManager), "CloseQuickMenuPanels")] internal static class QuickMenuCloseStatsTabPatch { private static void Postfix() { try { PauseMenuStatsPanel.Close(); } catch (Exception arg) { Plugin.Log.LogWarning((object)$"Failed to close pause menu Stats tab: {arg}"); } } } [HarmonyPatch(typeof(TimeOfDay), "OnDayChanged")] internal static class DayChangedPatch { private static void Postfix() { StatsBroadcaster.Refresh(); EnemyAttackTracker.ClearAll(); } } [HarmonyPatch(typeof(StartOfRound), "FirePlayersAfterDeadlineClientRpc")] internal static class PlayersFiredPatch { private static void Postfix() { StatsBroadcaster.Refresh(); } } [HarmonyPatch(typeof(StartOfRound), "ResetShip")] internal static class ShipResetPatch { private static void Postfix() { LiveStats.QuotaNumber = 0; LiveStats.LastObservedProfitQuota = -1; LiveStats.CurrentRunMoonSeconds = 0.0; LiveStats.CurrentRunSegmentActive = false; LiveStats.CurrentRunSegmentStartedAt = null; LiveStats.CurrentRunDowntimeSeconds = 0.0; LiveStats.DowntimeActive = false; LiveStats.DowntimeSegmentStartedAt = null; string persistenceSaveFileName = StatsBroadcaster.GetPersistenceSaveFileName(); if (!string.IsNullOrEmpty(persistenceSaveFileName)) { ES3.Save<double>("LCLiveStatsTracker_RunMoonSeconds", 0.0, persistenceSaveFileName); ES3.Save<double>("LCLiveStatsTracker_RunDowntimeSeconds", 0.0, persistenceSaveFileName); } LiveStats.ResetCrewDeaths(persistenceSaveFileName); foreach (PlayerStats value in LiveStats.Players.Values) { value.JumpsThisRound = 0; } } } [HarmonyPatch(typeof(StartOfRound), "Awake")] internal static class JumpTrackingPatch { private static void Postfix(StartOfRound __instance) { ((UnityEvent<PlayerControllerB>)(object)__instance.PlayerJumpEvent).AddListener((UnityAction<PlayerControllerB>)OnPlayerJump); } private static void OnPlayerJump(PlayerControllerB player) { if (!((Object)(object)player == (Object)null)) { LiveStats.Players.GetOrAdd(player.playerClientId, (ulong _) => new PlayerStats { Name = player.playerUsername, DeathCount = LiveStats.GetPersistedDeathCount(player.playerUsername) }).JumpsThisRound++; } } } [HarmonyPatch(typeof(StartOfRound), "EndOfGame")] internal static class EndOfDayScrapPatch { private static void Prefix(int scrapCollected) { LiveStats.LastDayScrapCollected = scrapCollected; StartOfRound instance = StartOfRound.Instance; string persistenceSaveFileName = StatsBroadcaster.GetPersistenceSaveFileName(); SelectableLevel val = (((Object)(object)instance != (Object)null) ? instance.currentLevel : null); bool flag = (Object)(object)val != (Object)null && val.spawnEnemiesAndScrap; if (flag) { LiveStats.AverageScrapSum += scrapCollected; LiveStats.AverageScrapDayCount++; if (!string.IsNullOrEmpty(persistenceSaveFileName)) { ES3.Save<int>("LCLiveStatsTracker_AvgScrapSum", LiveStats.AverageScrapSum, persistenceSaveFileName); ES3.Save<int>("LCLiveStatsTracker_AvgScrapDayCount", LiveStats.AverageScrapDayCount, persistenceSaveFileName); } } if (!((Object)(object)instance != (Object)null) || instance.gameStats == null || instance.allPlayerScripts == null) { return; } string moonName = (((Object)(object)val != (Object)null) ? val.PlanetName : null); int currentQuota = (((Object)(object)TimeOfDay.Instance != (Object)null) ? TimeOfDay.Instance.profitQuota : 0); PlayerStats[] allPlayerStats = instance.gameStats.allPlayerStats; for (int i = 0; i < instance.allPlayerScripts.Length && i < allPlayerStats.Length; i++) { PlayerControllerB val2 = instance.allPlayerScripts[i]; if (!((Object)(object)val2 == (Object)null) && (val2.isPlayerControlled || val2.isPlayerDead || val2.disconnectedMidGame)) { LiveStats.Players.TryGetValue(val2.playerClientId, out var value); int jumpsThisRound = value?.JumpsThisRound ?? 0; if (value != null) { value.JumpsThisRound = 0; } PlayerLifetimeStats.RecordDayEnd(val2.playerUsername, !val2.isPlayerDead, allPlayerStats[i].stepsTaken, allPlayerStats[i].profitable, jumpsThisRound, allPlayerStats[i].damageTaken, currentQuota, moonName, flag); } } } } [HarmonyPatch(typeof(HUDManager))] internal static class ControlTipSuppressPatch { private static bool ShouldSuppress() { if (Plugin.OverlayEnabled.Value) { return LiveStats.InSaveFile; } return false; } [HarmonyPatch("ChangeControlTip")] [HarmonyPrefix] private static bool ChangeControlTipPrefix() { return !ShouldSuppress(); } [HarmonyPatch("ChangeControlTipMultiple")] [HarmonyPrefix] private static bool ChangeControlTipMultiplePrefix() { return !ShouldSuppress(); } } [HarmonyPatch(typeof(GameNetworkManager), "ResetSavedGameValues")] internal static class ResetSavedGameValuesPatch { private static void Postfix(GameNetworkManager __instance) { LiveStats.AverageScrapSum = 0; LiveStats.AverageScrapDayCount = 0; LiveStats.CurrentRunMoonSeconds = 0.0; LiveStats.CurrentRunDowntimeSeconds = 0.0; ES3.Save<int>("LCLiveStatsTracker_AvgScrapSum", 0, __instance.currentSaveFileName); ES3.Save<int>("LCLiveStatsTracker_AvgScrapDayCount", 0, __instance.currentSaveFileName); ES3.Save<double>("LCLiveStatsTracker_RunMoonSeconds", 0.0, __instance.currentSaveFileName); ES3.Save<double>("LCLiveStatsTracker_RunDowntimeSeconds", 0.0, __instance.currentSaveFileName); LiveStats.ResetCrewDeaths(__instance.currentSaveFileName); } } [HarmonyPatch(typeof(PlayerControllerB), "KillPlayerClientRpc")] internal static class PlayerDeathPatch { private static void Postfix(int playerId, int causeOfDeath) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || playerId < 0 || playerId >= instance.allPlayerScripts.Length) { return; } PlayerControllerB player = instance.allPlayerScripts[playerId]; if (player.isPlayerDead) { string text = ((object)(CauseOfDeath)causeOfDeath/*cast due to .constrained prefix*/).ToString(); PlayerStats orAdd = LiveStats.Players.GetOrAdd(player.playerClientId, (ulong _) => new PlayerStats { Name = player.playerUsername, DeathCount = LiveStats.GetPersistedDeathCount(player.playerUsername) }); if (orAdd.Alive) { orAdd.Alive = false; orAdd.CauseOfDeath = text; orAdd.DeathCount++; LiveStats.PersistCrewDeath(player.playerUsername, orAdd.DeathCount, StatsBroadcaster.GetPersistenceSaveFileName()); string moonName = (((Object)(object)instance.currentLevel != (Object)null) ? instance.currentLevel.PlanetName : null); EnemyAttackTracker.TryGetAttacker(playerId, out var enemyName); PlayerLifetimeStats.RecordDeath(player.playerUsername, text, moonName, enemyName); StatsBroadcaster.Refresh(); } } } } [HarmonyPatch(typeof(Terminal), "ParsePlayerSentence")] internal static class TerminalMessageCommandPatch { private const string ClearCommand = "message clear"; private const string AddPrefix = "message add "; private const string MessagePrefix = "message "; private static bool Prefix(Terminal __instance, ref TerminalNode __result) { string text = __instance.screenText.text.Substring(__instance.screenText.text.Length - __instance.textAdded); text = text.Trim().TrimEnd('.', '!', '?'); if (string.Equals(text, "message clear", StringComparison.OrdinalIgnoreCase)) { LiveStats.OnScreenMessage = ""; __result = MakeNode("Message cleared."); return false; } if (text.Length > "message add ".Length && text.Substring(0, "message add ".Length).Equals("message add ", StringComparison.OrdinalIgnoreCase)) { string text2 = text.Substring("message add ".Length).Trim(); if (string.IsNullOrEmpty(text2)) { return true; } LiveStats.OnScreenMessage = (string.IsNullOrEmpty(LiveStats.OnScreenMessage) ? text2 : (LiveStats.OnScreenMessage + " " + text2)); __result = MakeNode("Message updated: \"" + LiveStats.OnScreenMessage + "\""); return false; } if (text.Length > "message ".Length && text.Substring(0, "message ".Length).Equals("message ", StringComparison.OrdinalIgnoreCase)) { string text3 = text.Substring("message ".Length).Trim(); if (string.IsNullOrEmpty(text3)) { return true; } LiveStats.OnScreenMessage = text3; __result = MakeNode("Message set: \"" + text3 + "\""); return false; } return true; } private static TerminalNode MakeNode(string text) { TerminalNode obj = ScriptableObject.CreateInstance<TerminalNode>(); obj.displayText = text + "\n"; obj.clearPreviousText = true; return obj; } } [HarmonyPatch(typeof(Terminal), "ParsePlayerSentence")] internal static class TerminalSellCommandPatch { private static bool Prefix(Terminal __instance, ref TerminalNode __result) { string[] array = __instance.screenText.text.Substring(__instance.screenText.text.Length - __instance.textAdded).Trim().Split(new char[1] { ' ' }); if (array.Length != 2 || array[0].ToLowerInvariant() != "sell") { return true; } if (!int.TryParse(array[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return true; } TimeOfDay instance = TimeOfDay.Instance; if ((Object)(object)instance == (Object)null) { __result = MakeNode("No active quota to calculate against.\n"); return false; } int groupCredits = __instance.groupCredits; if (groupCredits >= result) { __result = MakeNode($"You already have {groupCredits} credits, which meets your {result} credit goal.\n"); return false; } int num = RequiredSaleAmount(groupCredits, instance.quotaFulfilled, instance.profitQuota, instance.daysUntilDeadline, result); __result = MakeNode($"Sell ${num} worth of scrap to reach {result} credits\n(including the overtime bonus for meeting quota).\n"); return false; } private static int RequiredSaleAmount(int currentCredits, int quotaFulfilled, int profitQuota, int daysUntilDeadline, int target) { int num = (5 * (target - currentCredits) - quotaFulfilled + profitQuota - 75 * daysUntilDeadline) / 6; int i; for (i = Mathf.Max(0, num - 2); currentCredits + i + OvertimeBonus(quotaFulfilled + i, profitQuota, daysUntilDeadline) < target; i++) { } return i; } private static int OvertimeBonus(int quotaFulfilledAfterSale, int profitQuota, int daysUntilDeadline) { return (quotaFulfilledAfterSale - profitQuota) / 5 + 15 * daysUntilDeadline; } private static TerminalNode MakeNode(string text) { TerminalNode obj = ScriptableObject.CreateInstance<TerminalNode>(); obj.displayText = text; obj.clearPreviousText = true; return obj; } } [HarmonyPatch(typeof(Terminal), "ParsePlayerSentence")] internal static class TerminalStatsCommandPatch { private static bool Prefix(Terminal __instance, ref TerminalNode __result) { if (string.Equals(__instance.screenText.text.Substring(__instance.screenText.text.Length - __instance.textAdded).Trim().TrimEnd('.', '!', '?'), "stats", StringComparison.OrdinalIgnoreCase)) { PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); string text = (((Object)(object)val != (Object)null) ? val.playerUsername : null); __result = MakeNode((!string.IsNullOrEmpty(text)) ? (PlayerLifetimeStats.BuildProfileText(text) + "\n") : "Unable to determine your player name.\n"); return false; } return true; } private static TerminalNode MakeNode(string text) { TerminalNode obj = ScriptableObject.CreateInstance<TerminalNode>(); obj.displayText = text; obj.clearPreviousText = true; return obj; } } } namespace LiveStatsMod.Overlay { internal class OverlayDriver : MonoBehaviour { private static readonly Regex PhantomPlayerName = new Regex("^Player #\\d+$", RegexOptions.IgnoreCase); private TextMeshProUGUI _safetyLine; private void Awake() { Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); } private void Update() { try { Tick(); } catch (Exception arg) { Plugin.Log.LogWarning((object)$"Overlay update failed: {arg}"); } } private void Tick() { HandleToggleKeybind(); if (!Plugin.OverlayEnabled.Value || !LiveStats.InSaveFile) { return; } HUDManager instance = HUDManager.Instance; if ((Object)(object)instance == (Object)null || instance.controlTipLines == null) { return; } PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); if ((Object)(object)val != (Object)null && (Object)(object)val.quickMenuManager != (Object)null && val.quickMenuManager.isMenuOpen) { ClearLines(instance); return; } string text = "Moon: In Orbit"; if (LiveStats.IsLanded) { text = "Moon: " + (string.IsNullOrEmpty(LiveStats.MoonName) ? "-" : LiveStats.MoonName); string weatherGlyph = GetWeatherGlyph(GetLineFont(instance, 1), LiveStats.Weather); if (!string.IsNullOrEmpty(weatherGlyph)) { text = text + " " + weatherGlyph; } } string text2 = PartySizeLabel(); string text3 = (string.IsNullOrEmpty(text2) ? $"Day: {LiveStats.DayNumber}" : $"Day: {LiveStats.DayNumber} | {text2}"); SetLine(instance, 0, text3); SetLine(instance, 1, text); SetLine(instance, 2, $"Quota {LiveStats.QuotaNumber}: ${LiveStats.ProfitQuota}"); SetLine(instance, 3, $"Avg Loot/Day: ${LiveStats.AverageScrapPerDay()}"); UpdateSafetyLine(instance); } private void UpdateSafetyLine(HUDManager hud) { EnsureSafetyLine(hud); if (!((Object)(object)_safetyLine == (Object)null)) { PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); ShotgunItem val2 = (ShotgunItem)(((Object)(object)val != (Object)null) ? /*isinst with value type is only supported in some contexts*/: null); if ((Object)(object)val2 == (Object)null) { ((Component)_safetyLine).gameObject.SetActive(false); return; } ((Component)_safetyLine).gameObject.SetActive(true); ((TMP_Text)_safetyLine).enableWordWrapping = false; ((TMP_Text)_safetyLine).text = (val2.safetyOn ? "Turn safety off: [Q]" : "Turn safety on: [Q]"); } } private void EnsureSafetyLine(HUDManager hud) { //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_safetyLine != (Object)null || hud.controlTipLines == null || hud.controlTipLines.Length < 4) { return; } TextMeshProUGUI val = hud.controlTipLines[3]; TextMeshProUGUI val2 = hud.controlTipLines[2]; if (!((Object)(object)val == (Object)null) && !((Object)(object)val2 == (Object)null)) { RectTransform component = ((Component)val).GetComponent<RectTransform>(); RectTransform component2 = ((Component)val2).GetComponent<RectTransform>(); if (!((Object)(object)component == (Object)null) && !((Object)(object)component2 == (Object)null)) { GameObject val3 = Object.Instantiate<GameObject>(((Component)val).gameObject, ((TMP_Text)val).transform.parent); ((Object)val3).name = "LiveStats_SafetyLine"; RectTransform component3 = val3.GetComponent<RectTransform>(); Vector2 val4 = component.anchoredPosition - component2.anchoredPosition; component3.anchoredPosition = component.anchoredPosition + val4; _safetyLine = val3.GetComponent<TextMeshProUGUI>(); ((TMP_Text)_safetyLine).text = ""; val3.SetActive(false); } } } private void ClearLines(HUDManager hud) { for (int i = 0; i < hud.controlTipLines.Length; i++) { SetLine(hud, i, ""); } if ((Object)(object)_safetyLine != (Object)null) { ((Component)_safetyLine).gameObject.SetActive(false); } } private static void HandleToggleKeybind() { Keyboard current = Keyboard.current; if (current != null && ((ButtonControl)current.oKey).wasPressedThisFrame) { PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); if (!((Object)(object)val == (Object)null) && !val.inTerminalMenu && !val.isTypingChat && (!((Object)(object)val.quickMenuManager != (Object)null) || !val.quickMenuManager.isMenuOpen)) { Plugin.OverlayEnabled.Value = !Plugin.OverlayEnabled.Value; } } } private static string PartySizeLabel() { return LiveStats.Players.Values.Count((PlayerStats p) => !p.Disconnected && !PhantomPlayerName.IsMatch((p.Name ?? "").Trim())) switch { 0 => "", 1 => "Solo", 2 => "Duos", 3 => "Trios", _ => "Squads", }; } private static TMP_FontAsset GetLineFont(HUDManager hud, int index) { if (index >= hud.controlTipLines.Length || (Object)(object)hud.controlTipLines[index] == (Object)null) { return null; } return ((TMP_Text)hud.controlTipLines[index]).font; } private static string GetWeatherGlyph(TMP_FontAsset font, string weather) { if ((Object)(object)font == (Object)null) { return ""; } char c; switch (weather) { case "None": c = '☀'; break; case "DustClouds": c = '☁'; break; case "Rainy": c = '☂'; break; case "Stormy": c = '⚡'; break; case "Foggy": c = '☁'; break; case "Flooded": c = '≈'; break; case "Eclipsed": c = '☾'; break; default: return ""; } if (!font.HasCharacter(c, true, true)) { return ""; } return c.ToString(); } private static void SetLine(HUDManager hud, int index, string text) { if (index < hud.controlTipLines.Length && !((Object)(object)hud.controlTipLines[index] == (Object)null)) { TextMeshProUGUI obj = hud.controlTipLines[index]; ((TMP_Text)obj).enableWordWrapping = false; ((TMP_Text)obj).text = text; } } } internal static class PauseMenuStatsPanel { private class StatsPanelRefresher : MonoBehaviour { private void Update() { RefreshStatsText(); } } private const string ButtonObjectName = "LiveStats_StatsButton"; private const string PanelObjectName = "LiveStats_StatsPanel"; private const string ScheduleText = "STREAM SCHEDULE\n\nMonday @ 5pm EST\nTuesday @ 5pm EST\nWednesday @ 5pm EST\nThursday @ 5pm EST\nFriday @ 5pm EST\nSaturday & Sunday - Event Streams\n"; private static GameObject _panel; private static TextMeshProUGUI _statsText; private static TextMeshProUGUI _messageTitleText; public static void Close() { if ((Object)(object)_panel != (Object)null) { _panel.SetActive(false); } } public static void Install(QuickMenuManager quickMenu) { if (!((Object)(object)quickMenu.mainButtonsPanel == (Object)null) && !((Object)(object)quickMenu.leaveGameConfirmPanel == (Object)null) && !((Object)(object)quickMenu.mainButtonsPanel.transform.Find("LiveStats_StatsButton") != (Object)null)) { BuildPanel(quickMenu); BuildButton(quickMenu); } } private static void BuildPanel(QuickMenuManager quickMenu) { //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) _panel = Object.Instantiate<GameObject>(quickMenu.leaveGameConfirmPanel, quickMenu.leaveGameConfirmPanel.transform.parent); ((Object)_panel).name = "LiveStats_StatsPanel"; _panel.SetActive(false); Button[] componentsInChildren = _panel.GetComponentsInChildren<Button>(true); for (int i = 0; i < componentsInChildren.Length; i++) { Object.Destroy((Object)(object)((Component)componentsInChildren[i]).gameObject); } TextMeshProUGUI[] array = _panel.GetComponentsInChildren<TextMeshProUGUI>(true).OrderByDescending(delegate(TextMeshProUGUI t) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) Rect rect = ((TMP_Text)t).rectTransform.rect; float width = ((Rect)(ref rect)).width; rect = ((TMP_Text)t).rectTransform.rect; return width * ((Rect)(ref rect)).height; }).ToArray(); if (array.Length != 0) { _statsText = array[0]; ((TMP_Text)_statsText).alignment = (TextAlignmentOptions)257; ((TMP_Text)_statsText).enableWordWrapping = true; ((TMP_Text)_statsText).overflowMode = (TextOverflowModes)0; ((TMP_Text)_statsText).richText = true; ((TMP_Text)_statsText).text = ""; RectTransform component = ((Component)_statsText).GetComponent<RectTransform>(); component.anchoredPosition += new Vector2(0f, ((TMP_Text)_statsText).fontSize * 6f); BuildMessageTitle(); if (Plugin.ShowStreamSchedule.Value) { BuildScheduleBox(); } _panel.AddComponent<StatsPanelRefresher>(); } } private static void BuildMessageTitle() { //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) RectTransform component = ((Component)_statsText).GetComponent<RectTransform>(); GameObject obj = Object.Instantiate<GameObject>(((Component)_statsText).gameObject, ((Transform)component).parent); ((Object)obj).name = "LiveStats_MessageNote"; _messageTitleText = obj.GetComponent<TextMeshProUGUI>(); ((TMP_Text)_messageTitleText).alignment = (TextAlignmentOptions)1028; ((TMP_Text)_messageTitleText).enableWordWrapping = true; ((TMP_Text)_messageTitleText).overflowMode = (TextOverflowModes)0; ((TMP_Text)_messageTitleText).richText = true; ((TMP_Text)_messageTitleText).fontStyle = (FontStyles)2; ((TMP_Text)_messageTitleText).fontSize = ((TMP_Text)_statsText).fontSize * 0.7f; ((TMP_Text)_messageTitleText).text = ""; obj.SetActive(false); RectTransform component2 = obj.GetComponent<RectTransform>(); component2.anchorMin = new Vector2(1f, 0f); component2.anchorMax = new Vector2(1f, 0f); component2.pivot = new Vector2(1f, 0f); component2.sizeDelta = new Vector2(320f, 80f); component2.anchoredPosition = new Vector2(-20f, 20f); } private static void BuildScheduleBox() { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) GameObject obj = Object.Instantiate<GameObject>(((Component)_statsText).gameObject, ((TMP_Text)_statsText).transform.parent); ((Object)obj).name = "LiveStats_ScheduleBox"; TextMeshProUGUI component = obj.GetComponent<TextMeshProUGUI>(); ((TMP_Text)component).alignment = (TextAlignmentOptions)260; ((TMP_Text)component).enableWordWrapping = true; ((TMP_Text)component).overflowMode = (TextOverflowModes)0; ((TMP_Text)component).richText = true; ((TMP_Text)component).fontSize = ((TMP_Text)_statsText).fontSize * 0.65f; ((TMP_Text)component).text = "STREAM SCHEDULE\n\nMonday @ 5pm EST\nTuesday @ 5pm EST\nWednesday @ 5pm EST\nThursday @ 5pm EST\nFriday @ 5pm EST\nSaturday & Sunday - Event Streams\n"; RectTransform component2 = obj.GetComponent<RectTransform>(); component2.anchorMin = new Vector2(1f, 1f); component2.anchorMax = new Vector2(1f, 1f); component2.pivot = new Vector2(1f, 1f); component2.sizeDelta = new Vector2(260f, 160f); component2.anchoredPosition = new Vector2(-20f, -20f); } private static void BuildButton(QuickMenuManager quickMenu) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Expected O, but got Unknown //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Expected O, but got Unknown Transform transform = quickMenu.mainButtonsPanel.transform; Transform val = transform.Find("Quit"); if (!((Object)(object)val == (Object)null)) { GameObject gameObject = ((Component)val).gameObject; GameObject val2 = Object.Instantiate<GameObject>(gameObject, transform); ((Object)val2).name = "LiveStats_StatsButton"; Button component = val2.GetComponent<Button>(); ((UnityEventBase)component.onClick).RemoveAllListeners(); component.onClick = new ButtonClickedEvent(); ((UnityEvent)component.onClick).AddListener((UnityAction)delegate { RefreshStatsText(); quickMenu.mainButtonsPanel.SetActive(false); quickMenu.EnableUIPanel(_panel); }); TextMeshProUGUI componentInChildren = val2.GetComponentInChildren<TextMeshProUGUI>(); if ((Object)(object)componentInChildren != (Object)null) { ((TMP_Text)componentInChildren).text = "> Stats"; } RepositionButtons(transform, gameObject, val2); } } private static void RepositionButtons(Transform mainButtonsTransform, GameObject quitButton, GameObject clonedButton) { //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) List<GameObject> source = (from b in ((Component)mainButtonsTransform).GetComponentsInChildren<Button>(true) select ((Component)b).gameObject).ToList(); List<float> list = (from b in source where (Object)(object)b != (Object)(object)clonedButton select b.GetComponent<RectTransform>() into t where (Object)(object)t != (Object)null select t.anchoredPosition.y).ToList(); if (list.Count < 2) { return; } float num = list.Zip(list.Skip(1), (float y1, float y2) => Mathf.Abs(y2 - y1)).Min(); foreach (GameObject item in source.Where((GameObject g) => (Object)(object)g != (Object)(object)quitButton)) { RectTransform component = item.GetComponent<RectTransform>(); if ((Object)(object)component != (Object)null) { component.anchoredPosition += new Vector2(0f, num); } } clonedButton.GetComponent<RectTransform>().anchoredPosition = quitButton.GetComponent<RectTransform>().anchoredPosition + new Vector2(0f, num); } private static void RefreshStatsText() { if ((Object)(object)_statsText == (Object)null) { return; } ((TMP_Text)_statsText).text = StatsSummaryText.Build(richText: true); if ((Object)(object)_messageTitleText != (Object)null) { bool flag = !string.IsNullOrEmpty(LiveStats.OnScreenMessage); ((Component)_messageTitleText).gameObject.SetActive(flag); if (flag) { ((TMP_Text)_messageTitleText).text = LiveStats.OnScreenMessage; } } } } }