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 LCStatsCapture v1.0.0
BepInEx\plugins\LCStatsCapture\LCStatsCapture.dll
Decompiled 2 days agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Logging; using DunGen; using DunGen.Graph; using GameNetcodeStuff; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("LCStatsCapture")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Lethal Company end-of-day stats capture mod")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("LCStatsCapture")] [assembly: AssemblyTitle("LCStatsCapture")] [assembly: AssemblyVersion("1.0.0.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.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; } } } namespace LCStatsCapture { internal static class SessionState { internal static string SessionId = NewId(); internal static int DayNumber = 0; internal static bool RoundActive = false; internal static readonly List<DeathRecord> DeathsThisRound = new List<DeathRecord>(); internal static readonly HashSet<string> DiedThisRound = new HashSet<string>(); internal static int InitialShipScrapValue = 0; internal static int TotalMoonScrapValue = 0; internal static int TotalMoonScrapItemCount = 0; internal static string NewId() { return Guid.NewGuid().ToString("N").Substring(0, 8); } internal static void ResetForRound() { DeathsThisRound.Clear(); DiedThisRound.Clear(); RoundActive = true; InitialShipScrapValue = 0; TotalMoonScrapValue = 0; TotalMoonScrapItemCount = 0; } } [HarmonyPatch(typeof(StartOfRound), "Start")] internal static class Patch_StartOfRound_Start { [HarmonyPostfix] private static void Postfix() { SessionState.SessionId = SessionState.NewId(); SessionState.DayNumber = 0; SessionState.RoundActive = false; ContentDiscovery.Load(); ContentDiscovery.Scan(); Plugin.Log.LogInfo((object)("[StatsCapture] New session started — ID: " + SessionState.SessionId)); } } [HarmonyPatch(typeof(RoundManager), "FinishGeneratingNewLevelClientRpc")] internal static class Patch_RoundManager_FinishGenerating { [HarmonyPostfix] private static void Postfix() { SessionState.ResetForRound(); ContentDiscovery.Scan(); try { GrabbableObject[] array = Object.FindObjectsOfType<GrabbableObject>(); int num = 0; int num2 = 0; int num3 = 0; GrabbableObject[] array2 = array; foreach (GrabbableObject val in array2) { if (!((Object)(object)val?.itemProperties == (Object)null) && val.itemProperties.isScrap) { if (val.isInShipRoom) { num += val.scrapValue; continue; } num2 += val.scrapValue; num3++; } } SessionState.InitialShipScrapValue = num; SessionState.TotalMoonScrapValue = num2; SessionState.TotalMoonScrapItemCount = num3; Plugin.Log.LogInfo((object)("[StatsCapture] Level generated — " + $"ship carry-in: {num} cr | " + $"moon pool: {num3} items / {num2} cr | " + "round tracking active.")); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[StatsCapture] Could not snapshot scrap state: " + ex.Message)); } } } [HarmonyPatch(typeof(PlayerControllerB), "KillPlayer")] internal static class Patch_PlayerControllerB_KillPlayer { [HarmonyPostfix] private static void Postfix(PlayerControllerB __instance, Vector3 bodyVelocity, bool spawnBody, CauseOfDeath causeOfDeath, int deathAnimation) { //IL_008b: Unknown result type (might be due to invalid IL or missing references) if (SessionState.RoundActive) { string playerUsername = __instance.playerUsername; if (!SessionState.DiedThisRound.Contains(playerUsername)) { SessionState.DiedThisRound.Add(playerUsername); float num = (((Object)(object)TimeOfDay.Instance != (Object)null) ? TimeOfDay.Instance.normalizedTimeOfDay : 0f); SessionState.DeathsThisRound.Add(new DeathRecord { PlayerName = playerUsername, CauseOfDeath = ((object)(CauseOfDeath)(ref causeOfDeath)).ToString(), TimeNormalized = (float)Math.Round(num, 3) }); Plugin.Log.LogInfo((object)$"[StatsCapture] Death — {playerUsername} ({causeOfDeath}) at t={num:F2}"); } } } } [HarmonyPatch(typeof(RoundManager), "DespawnPropsAtEndOfRound")] internal static class Patch_RoundManager_DespawnProps { [HarmonyPrefix] private static void Prefix() { if (SessionState.RoundActive) { SessionState.RoundActive = false; SessionState.DayNumber++; SnapshotWriter.Capture(); } } } internal static class SnapshotWriter { private static readonly string OutputDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "LCStatsCapture"); internal static void Capture() { try { StartOfRound instance = StartOfRound.Instance; RoundManager instance2 = RoundManager.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null) { Plugin.Log.LogWarning((object)"[StatsCapture] Capture skipped — game instances not ready."); return; } SelectableLevel currentLevel = instance2.currentLevel; List<PlayerControllerB> list = instance.allPlayerScripts.Where((PlayerControllerB p) => (Object)(object)p != (Object)null && p.isPlayerControlled).ToList(); List<PlayerControllerB> list2 = list.Where((PlayerControllerB p) => !p.isPlayerDead).ToList(); foreach (PlayerControllerB item in list) { if (item.isPlayerDead && !SessionState.DiedThisRound.Contains(item.playerUsername)) { SessionState.DeathsThisRound.Add(new DeathRecord { PlayerName = item.playerUsername, CauseOfDeath = "Unknown", TimeNormalized = 1f }); } } List<CollectedScrapRecord> list3 = new List<CollectedScrapRecord>(); GrabbableObject[] array = Object.FindObjectsOfType<GrabbableObject>(); foreach (GrabbableObject val in array) { if (!((Object)(object)val?.itemProperties == (Object)null) && val.itemProperties.isScrap && val.isInShipRoom) { float weightLbs = (float)Math.Round((val.itemProperties.weight - 1f) * 105f, 1); list3.Add(new CollectedScrapRecord { Name = val.itemProperties.itemName, Value = val.scrapValue, WeightLbs = weightLbs, TwoHanded = val.itemProperties.twoHanded, IsConductive = val.itemProperties.isConductiveMetal }); } } ContentDiscovery.Scan(); int num = list3.Sum((CollectedScrapRecord s) => s.Value); int initialShipScrapValue = SessionState.InitialShipScrapValue; int num2 = num - initialShipScrapValue; string interiorType = "Unknown"; try { DungeonFlow val2 = instance2.dungeonGenerator?.Generator?.DungeonFlow; if ((Object)(object)val2 != (Object)null) { interiorType = ((Object)val2).name; } } catch { } DaySnapshot daySnapshot = new DaySnapshot { SessionId = SessionState.SessionId, DayNumber = SessionState.DayNumber, CapturedAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), Seed = instance.randomMapSeed, MoonName = (currentLevel?.PlanetName ?? "Unknown"), Weather = (((currentLevel != null) ? ((object)(LevelWeatherType)(ref currentLevel.currentWeather)).ToString() : null) ?? "None"), InteriorType = interiorType, PlayerCount = list.Count, SurvivorCount = list2.Count, SurvivorNames = list2.Select((PlayerControllerB p) => p.playerUsername).ToList(), Deaths = new List<DeathRecord>(SessionState.DeathsThisRound), TotalMoonScrapItemCount = SessionState.TotalMoonScrapItemCount, TotalMoonScrapValue = SessionState.TotalMoonScrapValue, InitialShipScrapValue = initialShipScrapValue, FinalShipScrapValue = num, CollectedScrapValue = num2, TotalScrapItemCount = list3.Count, TotalScrapValueCollected = num, TotalScrapWeightCollectedLbs = (float)Math.Round(list3.Sum((CollectedScrapRecord s) => s.WeightLbs), 1), ScrapCollected = list3, KnownContent = ContentDiscovery.GetSnapshot() }; if (!Directory.Exists(OutputDir)) { Directory.CreateDirectory(OutputDir); } string text = string.Concat(daySnapshot.MoonName.Split(Path.GetInvalidFileNameChars())); string text2 = DateTime.Now.ToString("yyyyMMdd_HHmmss"); string text3 = $"{daySnapshot.SessionId}_day{daySnapshot.DayNumber:D2}_{text}_{text2}.json"; File.WriteAllText(Path.Combine(OutputDir, text3), JsonConvert.SerializeObject((object)daySnapshot, (Formatting)1)); string text4 = ((SessionState.TotalMoonScrapValue > 0) ? $"{(double)num2 / (double)SessionState.TotalMoonScrapValue * 100.0:F1}%" : "N/A"); Plugin.Log.LogInfo((object)($"[StatsCapture] Day {daySnapshot.DayNumber} | {daySnapshot.MoonName} ({daySnapshot.Weather}) | " + $"{daySnapshot.SurvivorCount}/{daySnapshot.PlayerCount} survived | " + $"moon pool: {SessionState.TotalMoonScrapItemCount} items / {SessionState.TotalMoonScrapValue} cr | " + $"ship: {initialShipScrapValue} → {num} cr (+{num2} cr, {text4} of moon) | " + "→ " + text3)); } catch (Exception arg) { Plugin.Log.LogError((object)$"[StatsCapture] Snapshot capture failed:\n{arg}"); } } } public class DaySnapshot { public string SessionId { get; set; } public int DayNumber { get; set; } public string CapturedAt { get; set; } public int Seed { get; set; } public string MoonName { get; set; } public string Weather { get; set; } public string InteriorType { get; set; } public int PlayerCount { get; set; } public int SurvivorCount { get; set; } public List<string> SurvivorNames { get; set; } = new List<string>(); public List<DeathRecord> Deaths { get; set; } = new List<DeathRecord>(); public int TotalMoonScrapItemCount { get; set; } public int TotalMoonScrapValue { get; set; } public int InitialShipScrapValue { get; set; } public int FinalShipScrapValue { get; set; } public int CollectedScrapValue { get; set; } public int TotalScrapValueCollected { get; set; } public float TotalScrapWeightCollectedLbs { get; set; } public int TotalScrapItemCount { get; set; } public List<CollectedScrapRecord> ScrapCollected { get; set; } = new List<CollectedScrapRecord>(); public ContentRegistry KnownContent { get; set; } } public class DeathRecord { public string PlayerName { get; set; } public string CauseOfDeath { get; set; } public float TimeNormalized { get; set; } } public class CollectedScrapRecord { public string Name { get; set; } public int Value { get; set; } public float WeightLbs { get; set; } public bool TwoHanded { get; set; } public bool IsConductive { get; set; } } public class ContentRegistry { public List<string> Moons { get; set; } = new List<string>(); public List<string> WeatherTypes { get; set; } = new List<string>(); public List<string> EnemyTypes { get; set; } = new List<string>(); public List<string> ScrapTypes { get; set; } = new List<string>(); public List<string> InteriorFlows { get; set; } = new List<string>(); public string LastUpdated { get; set; } } [BepInPlugin("com.yourname.lcstatscapture", "LC Stats Capture", "1.0.0")] public class Plugin : BaseUnityPlugin { internal static ManualLogSource Log; internal static Plugin Instance; private void Awake() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; ContentDiscovery.Load(); new Harmony("com.yourname.lcstatscapture").PatchAll(); Log.LogInfo((object)"LC Stats Capture v1.0.0 loaded."); } } internal static class PluginInfo { internal const string PLUGIN_GUID = "com.yourname.lcstatscapture"; internal const string PLUGIN_NAME = "LC Stats Capture"; internal const string PLUGIN_VERSION = "1.0.0"; } internal static class ContentDiscovery { private static ContentRegistry _registry = new ContentRegistry(); private static readonly string RegistryPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "LCStatsCapture", "content_registry.json"); internal static void Load() { try { if (File.Exists(RegistryPath)) { _registry = JsonConvert.DeserializeObject<ContentRegistry>(File.ReadAllText(RegistryPath)) ?? new ContentRegistry(); Plugin.Log.LogInfo((object)("[StatsCapture] Registry loaded — " + $"{_registry.Moons.Count} moons, " + $"{_registry.EnemyTypes.Count} enemies, " + $"{_registry.ScrapTypes.Count} scrap types, " + $"{_registry.InteriorFlows.Count} interiors")); } else { Plugin.Log.LogInfo((object)"[StatsCapture] No registry file yet — will create on first scan."); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[StatsCapture] Registry load error: " + ex.Message + ". Starting fresh.")); _registry = new ContentRegistry(); } } internal static void Scan() { //IL_026f: Unknown result type (might be due to invalid IL or missing references) //IL_0275: Invalid comparison between Unknown and I4 try { bool flag = false; if (StartOfRound.Instance?.levels != null) { SelectableLevel[] levels = StartOfRound.Instance.levels; foreach (SelectableLevel val in levels) { if ((Object)(object)val == (Object)null) { continue; } flag |= TryAdd(_registry.Moons, val.PlanetName); if (val.randomWeathers != null) { RandomWeatherWithVariables[] randomWeathers = val.randomWeathers; foreach (RandomWeatherWithVariables val2 in randomWeathers) { flag |= TryAdd(_registry.WeatherTypes, ((object)(LevelWeatherType)(ref val2.weatherType)).ToString()); } } if (val.Enemies != null) { foreach (SpawnableEnemyWithRarity enemy in val.Enemies) { if ((Object)(object)enemy?.enemyType != (Object)null) { flag |= TryAdd(_registry.EnemyTypes, enemy.enemyType.enemyName); } } } if (val.OutsideEnemies != null) { foreach (SpawnableEnemyWithRarity outsideEnemy in val.OutsideEnemies) { if ((Object)(object)outsideEnemy?.enemyType != (Object)null) { flag |= TryAdd(_registry.EnemyTypes, outsideEnemy.enemyType.enemyName); } } } if (val.DaytimeEnemies == null) { continue; } foreach (SpawnableEnemyWithRarity daytimeEnemy in val.DaytimeEnemies) { if ((Object)(object)daytimeEnemy?.enemyType != (Object)null) { flag |= TryAdd(_registry.EnemyTypes, daytimeEnemy.enemyType.enemyName); } } } } DungeonGenerator val3 = RoundManager.Instance?.dungeonGenerator?.Generator; if ((Object)(object)val3?.DungeonFlow != (Object)null) { flag |= TryAdd(_registry.InteriorFlows, ((Object)val3.DungeonFlow).name); } SelectableLevel val4 = RoundManager.Instance?.currentLevel; if ((Object)(object)val4 != (Object)null && (int)val4.currentWeather != -1) { flag |= TryAdd(_registry.WeatherTypes, ((object)(LevelWeatherType)(ref val4.currentWeather)).ToString()); } GrabbableObject[] array = Object.FindObjectsOfType<GrabbableObject>(); foreach (GrabbableObject val5 in array) { if (!((Object)(object)val5?.itemProperties == (Object)null) && val5.itemProperties.isScrap) { flag |= TryAdd(_registry.ScrapTypes, val5.itemProperties.itemName); } } if (flag) { Save(); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[StatsCapture] Scan error: " + ex.Message)); } } internal static ContentRegistry GetSnapshot() { return new ContentRegistry { Moons = new List<string>(_registry.Moons), WeatherTypes = new List<string>(_registry.WeatherTypes), EnemyTypes = new List<string>(_registry.EnemyTypes), ScrapTypes = new List<string>(_registry.ScrapTypes), InteriorFlows = new List<string>(_registry.InteriorFlows), LastUpdated = _registry.LastUpdated }; } private static bool TryAdd(List<string> list, string value) { if (string.IsNullOrWhiteSpace(value)) { return false; } if (list.Contains(value)) { return false; } list.Add(value); return true; } private static void Save() { try { _registry.LastUpdated = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); string directoryName = Path.GetDirectoryName(RegistryPath); if (!Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } File.WriteAllText(RegistryPath, JsonConvert.SerializeObject((object)_registry, (Formatting)1)); Plugin.Log.LogInfo((object)("[StatsCapture] Registry updated — " + $"{_registry.Moons.Count} moons, " + $"{_registry.WeatherTypes.Count} weathers, " + $"{_registry.EnemyTypes.Count} enemies, " + $"{_registry.ScrapTypes.Count} scrap types, " + $"{_registry.InteriorFlows.Count} interiors")); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[StatsCapture] Registry save error: " + ex.Message)); } } } }