using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Net.Http; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading.Tasks; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Microsoft.CodeAnalysis; using Unity.Netcode; 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("GsLethalStatsEmitter")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.6.0")] [assembly: AssemblyInformationalVersion("0.1.6+fced9be4a94b811c8f1c3907a535efface3a854e")] [assembly: AssemblyProduct("GsLethalStatsEmitter")] [assembly: AssemblyTitle("GsLethalStatsEmitter")] [assembly: AssemblyVersion("0.1.6.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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 GsLethalStatsEmitter { [BepInPlugin("net.cproudlock.gslethalstatsemitter", "gs Lethal Company Stats", "0.1.0")] public class Plugin : BaseUnityPlugin { public const string GUID = "net.cproudlock.gslethalstatsemitter"; public const string NAME = "gs Lethal Company Stats"; public const string VERSION = "0.1.0"; internal static ManualLogSource Log; internal static ConfigEntry IngestUrl; internal static ConfigEntry IngestToken; internal static ConfigEntry Enabled; internal static ConfigEntry EmitOnHostOnly; internal static SessionState Session; private static readonly HttpClient http = new HttpClient { Timeout = TimeSpan.FromSeconds(15.0) }; private Harmony harmony; private void Awake() { //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; IngestUrl = ((BaseUnityPlugin)this).Config.Bind("Ingest", "Url", "https://gs.proudtech.net/api/lethal/ingest", "POST endpoint that receives the session payload at game-over."); IngestToken = ((BaseUnityPlugin)this).Config.Bind("Ingest", "Token", "", "Bearer token sent in the Authorization header. Set this in BepInEx/config/net.cproudlock.gslethalstatsemitter.cfg"); Enabled = ((BaseUnityPlugin)this).Config.Bind("Ingest", "Enabled", true, "Master toggle. Set false to keep the mod loaded but skip ingest."); EmitOnHostOnly = ((BaseUnityPlugin)this).Config.Bind("Ingest", "EmitOnHostOnly", true, "Only POST when running as the lobby host (avoids duplicate sessions from co-op clients)."); harmony = new Harmony("net.cproudlock.gslethalstatsemitter"); harmony.PatchAll(typeof(Plugin).Assembly); Log.LogInfo((object)("gs Lethal Company Stats v0.1.0 loaded ยท ingest=" + IngestUrl.Value)); } internal static bool IsHost() { try { NetworkManager singleton = NetworkManager.Singleton; return (Object)(object)singleton != (Object)null && singleton.IsHost; } catch { return false; } } internal static SessionState EnsureSession(StartOfRound sor) { if (Session != null) { return Session; } string text = "host"; try { if ((Object)(object)sor != (Object)null && sor.allPlayerScripts != null) { PlayerControllerB[] allPlayerScripts = sor.allPlayerScripts; foreach (PlayerControllerB val in allPlayerScripts) { if ((Object)(object)val != (Object)null && val.isHostPlayerObject) { text = val.playerUsername; break; } } if (text == "host" && sor.allPlayerScripts.Length != 0 && (Object)(object)sor.allPlayerScripts[0] != (Object)null) { text = sor.allPlayerScripts[0].playerUsername ?? "host"; } } } catch { } Session = new SessionState { sessionIdLocal = Guid.NewGuid().ToString(), hostName = text, seed = (((Object)(object)sor != (Object)null) ? sor.randomMapSeed : 0).ToString(CultureInfo.InvariantCulture), startedAtUtc = DateTime.UtcNow }; Log.LogInfo((object)("[gs] session start id=" + Session.sessionIdLocal + " host=" + text)); return Session; } internal static PlayerSessionState GetPlayerSlot(string name, bool isHost = false) { if (string.IsNullOrEmpty(name)) { name = "(unknown)"; } if (Session == null) { return null; } if (!Session.players.TryGetValue(name, out var value)) { value = new PlayerSessionState { playerName = name, isHost = isHost }; Session.players[name] = value; } else if (isHost) { value.isHost = true; } return value; } internal static DayState CurrentDay() { if (Session == null || Session.days.Count == 0) { return null; } return Session.days[Session.days.Count - 1]; } internal static string CurrentMoon() { try { return CleanMoonName((!((Object)(object)StartOfRound.Instance != (Object)null)) ? null : StartOfRound.Instance.currentLevel?.PlanetName); } catch { return null; } } internal static string CleanMoonName(string raw) { if (string.IsNullOrEmpty(raw)) { return raw; } int i; for (i = 0; i < raw.Length && raw[i] >= '0' && raw[i] <= '9'; i++) { } if (i == 0 || i >= raw.Length) { return raw; } for (; i < raw.Length && raw[i] == ' '; i++) { } return raw.Substring(i); } internal static int CurrentCredits() { try { Terminal val = Object.FindObjectOfType(); return ((Object)(object)val != (Object)null) ? val.groupCredits : 0; } catch { return 0; } } internal static void EmitAndReset(string outcome) { SessionState session = Session; if (session == null) { return; } Session = null; try { if (EmitOnHostOnly.Value && !IsHost()) { Log.LogInfo((object)("[gs] not host, skip POST for session " + session.sessionIdLocal)); return; } session.outcome = outcome; session.endedAtUtc = DateTime.UtcNow; ComputeAggregates(session); string json = SessionJson.Serialize(session); Log.LogInfo((object)$"[gs] emit session id={session.sessionIdLocal} outcome={outcome} days={session.daysSurvived} players={session.players.Count} deaths={session.deaths.Count} bytes={json.Length}"); Task.Run(() => PostJson(json)); } catch (Exception arg) { Log.LogError((object)$"[gs] emit error: {arg}"); } } private static void ComputeAggregates(SessionState s) { s.daysSurvived = s.days.Count; int num = 0; int num2 = 0; foreach (DayState day in s.days) { num += day.scrapValueCollected; if (day.scrapValueCollected > num2) { num2 = day.scrapValueCollected; } } s.totalScrapValue = num; s.peakScrapValue = num2; try { s.finalQuota = (((Object)(object)TimeOfDay.Instance != (Object)null) ? TimeOfDay.Instance.profitQuota : 0); } catch { } try { s.quotasMet = (((Object)(object)TimeOfDay.Instance != (Object)null) ? TimeOfDay.Instance.timesFulfilledQuota : 0); } catch { } s.finalCredits = CurrentCredits(); s.quotaMargin = s.finalCredits - s.finalQuota; foreach (PlayerSessionState value in s.players.Values) { value.metersTraveled = (int)Math.Round(value.metersTraveledFloat); } } private static async Task PostJson(string json) { if (!Enabled.Value) { Log.LogInfo((object)"[gs] disabled, skip POST"); return; } if (string.IsNullOrEmpty(IngestToken.Value)) { Log.LogWarning((object)"[gs] no token configured, skipping"); return; } try { HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, IngestUrl.Value) { Content = new StringContent(json, Encoding.UTF8, "application/json") }; httpRequestMessage.Headers.TryAddWithoutValidation("Authorization", "Bearer " + IngestToken.Value); HttpResponseMessage res = await http.SendAsync(httpRequestMessage).ConfigureAwait(continueOnCapturedContext: false); string text = await res.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false); if (!res.IsSuccessStatusCode) { Log.LogWarning((object)$"[gs] POST {res.StatusCode}: {text}"); } else { Log.LogInfo((object)("[gs] POST ok: " + text)); } } catch (Exception ex) { Log.LogWarning((object)("[gs] POST error: " + ex.Message)); } } } internal class SessionState { public string sessionIdLocal; public string hostName; public string seed; public DateTime startedAtUtc; public DateTime? endedAtUtc; public int daysSurvived; public int quotasMet; public int finalQuota; public int finalCredits; public int quotaMargin; public int peakScrapValue; public int totalScrapValue; public string outcome; public List days = new List(); public Dictionary players = new Dictionary(); public List deaths = new List(); public string lastTerminalUser; } internal class DayState { public int dayIndex; public string moon; public string weather; public int quotaBefore; public int creditsBefore; public int creditsAfter; public int scrapValueCollected; public int scrapPiecesCollected; public int scrapValueLeftBehind; public bool apparatusPulled; public int deaths; public bool returnedToCompany; public DateTime startedAtUtc; public DateTime? endedAtUtc; public int totalScrapValueOnMoon; } internal class PlayerSessionState { public string playerName; public ulong steamId; public bool isHost; public int deaths; public int scrapValueCollected; public int daysSurvived; public int mostCreditsHeld; public int metersTraveled; public float metersTraveledFloat; public Dictionary items = new Dictionary(); public Dictionary purchases = new Dictionary(); public Dictionary mobKills = new Dictionary(); public Dictionary distanceByWeather = new Dictionary(); public string lastDamagedByEnemy; public DateTime lastDamagedAt; } internal class PurchaseBag { public string itemName; public int count; public int totalSpent; public bool isUnlockable; } internal class PlayerItemBag { public string itemName; public int picks; public int totalValue; public int soldValue; } internal class DeathEvent { public int dayIndex; public string playerName; public string causeOfDeath; public string killer; public string moon; public float posX; public float posY; public float posZ; public DateTime tsUtc; } internal static class SessionJson { public static string Serialize(SessionState s) { StringBuilder stringBuilder = new StringBuilder(4096); stringBuilder.Append('{'); Kv(stringBuilder, "sessionIdLocal", s.sessionIdLocal); Comma(stringBuilder); Kv(stringBuilder, "hostName", s.hostName); Comma(stringBuilder); Kv(stringBuilder, "seed", s.seed); Comma(stringBuilder); Kv(stringBuilder, "startedAtUtc", Iso(s.startedAtUtc)); Comma(stringBuilder); Kv(stringBuilder, "endedAtUtc", s.endedAtUtc.HasValue ? Iso(s.endedAtUtc.Value) : null); Comma(stringBuilder); Kv(stringBuilder, "daysSurvived", s.daysSurvived); Comma(stringBuilder); Kv(stringBuilder, "quotasMet", s.quotasMet); Comma(stringBuilder); Kv(stringBuilder, "finalQuota", s.finalQuota); Comma(stringBuilder); Kv(stringBuilder, "finalCredits", s.finalCredits); Comma(stringBuilder); Kv(stringBuilder, "peakScrapValue", s.peakScrapValue); Comma(stringBuilder); Kv(stringBuilder, "totalScrapValue", s.totalScrapValue); Comma(stringBuilder); Kv(stringBuilder, "quotaMargin", s.quotaMargin); Comma(stringBuilder); Kv(stringBuilder, "outcome", s.outcome); Comma(stringBuilder); stringBuilder.Append("\"days\":["); bool flag = true; foreach (DayState day in s.days) { if (!flag) { stringBuilder.Append(','); } flag = false; stringBuilder.Append('{'); Kv(stringBuilder, "dayIndex", day.dayIndex); Comma(stringBuilder); Kv(stringBuilder, "moon", day.moon); Comma(stringBuilder); Kv(stringBuilder, "weather", day.weather); Comma(stringBuilder); Kv(stringBuilder, "quotaBefore", day.quotaBefore); Comma(stringBuilder); Kv(stringBuilder, "creditsBefore", day.creditsBefore); Comma(stringBuilder); Kv(stringBuilder, "creditsAfter", day.creditsAfter); Comma(stringBuilder); Kv(stringBuilder, "scrapValueCollected", day.scrapValueCollected); Comma(stringBuilder); Kv(stringBuilder, "scrapPiecesCollected", day.scrapPiecesCollected); Comma(stringBuilder); Kv(stringBuilder, "scrapValueLeftBehind", day.scrapValueLeftBehind); Comma(stringBuilder); Kv(stringBuilder, "apparatusPulled", day.apparatusPulled); Comma(stringBuilder); Kv(stringBuilder, "deaths", day.deaths); Comma(stringBuilder); Kv(stringBuilder, "returnedToCompany", day.returnedToCompany); Comma(stringBuilder); Kv(stringBuilder, "startedAtUtc", Iso(day.startedAtUtc)); Comma(stringBuilder); Kv(stringBuilder, "endedAtUtc", day.endedAtUtc.HasValue ? Iso(day.endedAtUtc.Value) : null); stringBuilder.Append('}'); } stringBuilder.Append("],"); stringBuilder.Append("\"players\":["); flag = true; foreach (PlayerSessionState value2 in s.players.Values) { if (!flag) { stringBuilder.Append(','); } flag = false; stringBuilder.Append('{'); Kv(stringBuilder, "name", value2.playerName); Comma(stringBuilder); Kv(stringBuilder, "isHost", value2.isHost); Comma(stringBuilder); Kv(stringBuilder, "deaths", value2.deaths); Comma(stringBuilder); Kv(stringBuilder, "scrapValueCollected", value2.scrapValueCollected); Comma(stringBuilder); Kv(stringBuilder, "daysSurvived", value2.daysSurvived); Comma(stringBuilder); Kv(stringBuilder, "mostCreditsHeld", value2.mostCreditsHeld); Comma(stringBuilder); Kv(stringBuilder, "metersTraveled", value2.metersTraveled); Comma(stringBuilder); stringBuilder.Append("\"items\":["); bool flag2 = true; foreach (PlayerItemBag value3 in value2.items.Values) { if (!flag2) { stringBuilder.Append(','); } flag2 = false; stringBuilder.Append('{'); Kv(stringBuilder, "itemName", value3.itemName); Comma(stringBuilder); Kv(stringBuilder, "picks", value3.picks); Comma(stringBuilder); Kv(stringBuilder, "totalValue", value3.totalValue); Comma(stringBuilder); Kv(stringBuilder, "soldValue", value3.soldValue); stringBuilder.Append('}'); } stringBuilder.Append("],"); stringBuilder.Append("\"purchases\":["); bool flag3 = true; foreach (PurchaseBag value4 in value2.purchases.Values) { if (!flag3) { stringBuilder.Append(','); } flag3 = false; stringBuilder.Append('{'); Kv(stringBuilder, "itemName", value4.itemName); Comma(stringBuilder); Kv(stringBuilder, "count", value4.count); Comma(stringBuilder); Kv(stringBuilder, "totalSpent", value4.totalSpent); Comma(stringBuilder); Kv(stringBuilder, "isUnlockable", value4.isUnlockable); stringBuilder.Append('}'); } stringBuilder.Append("],"); stringBuilder.Append("\"mobKills\":["); bool flag4 = true; foreach (KeyValuePair mobKill in value2.mobKills) { if (!flag4) { stringBuilder.Append(','); } flag4 = false; stringBuilder.Append('{'); Kv(stringBuilder, "enemyType", mobKill.Key); Comma(stringBuilder); Kv(stringBuilder, "kills", mobKill.Value); stringBuilder.Append('}'); } stringBuilder.Append("],"); stringBuilder.Append("\"distanceByWeather\":["); bool flag5 = true; foreach (KeyValuePair item in value2.distanceByWeather) { if (!flag5) { stringBuilder.Append(','); } flag5 = false; stringBuilder.Append('{'); Kv(stringBuilder, "weather", item.Key); Comma(stringBuilder); Kv(stringBuilder, "meters", (int)Math.Round(item.Value)); stringBuilder.Append('}'); } stringBuilder.Append(']'); stringBuilder.Append('}'); } stringBuilder.Append("],"); stringBuilder.Append("\"mods\":["); flag = true; try { Dictionary pluginInfos = Chainloader.PluginInfos; if (pluginInfos != null) { foreach (KeyValuePair item2 in pluginInfos) { PluginInfo value = item2.Value; if (value == null || value.Metadata == null) { continue; } string text = ""; string v = ""; try { string directoryName = Path.GetDirectoryName(value.Location ?? ""); if (!string.IsNullOrEmpty(directoryName)) { text = Path.GetFileName(directoryName); int num = text.IndexOf('-'); if (num > 0) { v = text.Substring(0, num); } } } catch { } if (!flag) { stringBuilder.Append(','); } flag = false; stringBuilder.Append('{'); Kv(stringBuilder, "guid", value.Metadata.GUID ?? ""); Comma(stringBuilder); Kv(stringBuilder, "name", value.Metadata.Name ?? ""); Comma(stringBuilder); Kv(stringBuilder, "version", value.Metadata.Version?.ToString() ?? ""); Comma(stringBuilder); Kv(stringBuilder, "author", v); Comma(stringBuilder); Kv(stringBuilder, "folder", text); stringBuilder.Append('}'); } } } catch { } stringBuilder.Append("],"); stringBuilder.Append("\"deaths\":["); flag = true; foreach (DeathEvent death in s.deaths) { if (!flag) { stringBuilder.Append(','); } flag = false; stringBuilder.Append('{'); Kv(stringBuilder, "dayIndex", death.dayIndex); Comma(stringBuilder); Kv(stringBuilder, "playerName", death.playerName); Comma(stringBuilder); Kv(stringBuilder, "causeOfDeath", death.causeOfDeath); Comma(stringBuilder); Kv(stringBuilder, "killer", death.killer); Comma(stringBuilder); Kv(stringBuilder, "moon", death.moon); Comma(stringBuilder); Kv(stringBuilder, "posX", death.posX); Comma(stringBuilder); Kv(stringBuilder, "posY", death.posY); Comma(stringBuilder); Kv(stringBuilder, "posZ", death.posZ); Comma(stringBuilder); Kv(stringBuilder, "tsUtc", Iso(death.tsUtc)); stringBuilder.Append('}'); } stringBuilder.Append(']'); stringBuilder.Append('}'); return stringBuilder.ToString(); } private static void Kv(StringBuilder sb, string k, string v) { sb.Append('"').Append(k).Append("\":"); WriteString(sb, v); } private static void Kv(StringBuilder sb, string k, int v) { sb.Append('"').Append(k).Append("\":") .Append(v.ToString(CultureInfo.InvariantCulture)); } private static void Kv(StringBuilder sb, string k, float v) { sb.Append('"').Append(k).Append("\":") .Append(v.ToString("R", CultureInfo.InvariantCulture)); } private static void Kv(StringBuilder sb, string k, bool v) { sb.Append('"').Append(k).Append("\":") .Append(v ? "true" : "false"); } private static void Comma(StringBuilder sb) { sb.Append(','); } private static void WriteString(StringBuilder sb, string v) { if (v == null) { sb.Append("null"); return; } sb.Append('"'); foreach (char c in v) { switch (c) { case '"': sb.Append("\\\""); continue; case '\\': sb.Append("\\\\"); continue; case '\n': sb.Append("\\n"); continue; case '\r': sb.Append("\\r"); continue; case '\t': sb.Append("\\t"); continue; } if (c < ' ') { sb.AppendFormat(CultureInfo.InvariantCulture, "\\u{0:x4}", (int)c); } else { sb.Append(c); } } sb.Append('"'); } private static string Iso(DateTime dt) { return dt.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture); } } [HarmonyPatch(typeof(StartOfRound), "ArriveAtLevel")] internal static class P_StartOfRound_ArriveAtLevel { private static void Postfix(StartOfRound __instance) { SessionState sessionState = Plugin.EnsureSession(__instance); string text = Plugin.CleanMoonName(((Object)(object)__instance.currentLevel != (Object)null) ? __instance.currentLevel.PlanetName : null); int num = sessionState.days.Count + 1; sessionState.days.Add(new DayState { dayIndex = num, moon = text, weather = SafeWeather(__instance.currentLevel), quotaBefore = (((Object)(object)TimeOfDay.Instance != (Object)null) ? TimeOfDay.Instance.profitQuota : 0), creditsBefore = Plugin.CurrentCredits(), startedAtUtc = DateTime.UtcNow }); Plugin.Log.LogInfo((object)$"[gs] day {num} start moon={text}"); } private static string SafeWeather(SelectableLevel lvl) { try { return ((Object)(object)lvl != (Object)null) ? ((object)(LevelWeatherType)(ref lvl.currentWeather)).ToString() : null; } catch { return null; } } } [HarmonyPatch(typeof(StartOfRound), "ShipHasLeft")] internal static class P_StartOfRound_ShipHasLeft { private static void Postfix() { DayState dayState = Plugin.CurrentDay(); if (dayState != null) { dayState.endedAtUtc = DateTime.UtcNow; dayState.creditsAfter = Plugin.CurrentCredits(); if (dayState.totalScrapValueOnMoon > 0) { dayState.scrapValueLeftBehind = Math.Max(0, dayState.totalScrapValueOnMoon - dayState.scrapValueCollected); } Plugin.Log.LogInfo((object)$"[gs] day {dayState.dayIndex} end credits={dayState.creditsAfter} left=${dayState.scrapValueLeftBehind}"); } } } [HarmonyPatch] internal static class P_DepositItemsDesk_SellItemsOnServer { private static MethodBase TargetMethod() { Type type = AccessTools.TypeByName("DepositItemsDesk"); if (type == null) { return null; } return AccessTools.Method(type, "SellItemsOnServer", (Type[])null, (Type[])null); } private static void Postfix() { try { DayState dayState = Plugin.CurrentDay(); if (dayState != null) { int num = Plugin.CurrentCredits(); int num2 = num - dayState.creditsBefore; if (num2 > 0) { dayState.scrapValueCollected += num2; } dayState.creditsAfter = num; dayState.returnedToCompany = true; Plugin.Log.LogInfo((object)$"[gs] sold on day {dayState.dayIndex} delta=${num2}"); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[gs] sell hook err: " + ex.Message)); } } } [HarmonyPatch(typeof(GrabbableObject), "GrabItemOnClient")] internal static class P_GrabbableObject_GrabItemOnClient { private static void Postfix(GrabbableObject __instance) { if (Plugin.Session == null) { return; } try { PlayerControllerB playerHeldBy = __instance.playerHeldBy; if ((Object)(object)playerHeldBy == (Object)null) { return; } PlayerSessionState playerSlot = Plugin.GetPlayerSlot(playerHeldBy.playerUsername); if (playerSlot != null) { string text = SafeItemName(__instance); int scrapValue = __instance.scrapValue; if (!playerSlot.items.TryGetValue(text, out var value)) { Dictionary items = playerSlot.items; PlayerItemBag obj = new PlayerItemBag { itemName = text }; value = obj; items[text] = obj; } value.picks++; value.totalValue += scrapValue; playerSlot.scrapValueCollected += scrapValue; DayState dayState = Plugin.CurrentDay(); if (dayState != null) { dayState.scrapPiecesCollected++; } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[gs] grab hook err: " + ex.Message)); } } private static string SafeItemName(GrabbableObject g) { try { if ((Object)(object)g.itemProperties != (Object)null && !string.IsNullOrEmpty(g.itemProperties.itemName)) { return g.itemProperties.itemName.Replace(" ", ""); } } catch { } return ((object)g).GetType().Name; } } [HarmonyPatch(typeof(EnemyAI), "OnCollideWithPlayer")] internal static class P_EnemyAI_OnCollideWithPlayer { private static void Postfix(EnemyAI __instance, Collider other) { if (Plugin.Session == null || (Object)(object)__instance == (Object)null || (Object)(object)other == (Object)null) { return; } try { PlayerControllerB component = ((Component)other).GetComponent(); if (!((Object)(object)component == (Object)null)) { PlayerSessionState playerSlot = Plugin.GetPlayerSlot(component.playerUsername); if (playerSlot != null) { playerSlot.lastDamagedByEnemy = ((object)__instance).GetType().Name; playerSlot.lastDamagedAt = DateTime.UtcNow; } } } catch { } } } [HarmonyPatch(typeof(PlayerControllerB), "KillPlayer")] internal static class P_PlayerControllerB_KillPlayer { private static void Postfix(PlayerControllerB __instance, Vector3 bodyVelocity, bool spawnBody, CauseOfDeath causeOfDeath, int deathAnimation, Vector3 positionOffset, bool setOverrideDropItems) { //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Expected I4, but got Unknown //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) if (Plugin.Session == null || (Object)(object)__instance == (Object)null) { return; } try { PlayerSessionState playerSlot = Plugin.GetPlayerSlot(__instance.playerUsername); if (playerSlot != null) { playerSlot.deaths++; } string text = "environment"; if (playerSlot != null && !string.IsNullOrEmpty(playerSlot.lastDamagedByEnemy) && (DateTime.UtcNow - playerSlot.lastDamagedAt).TotalSeconds < 10.0) { text = playerSlot.lastDamagedByEnemy; } else { switch (causeOfDeath - 2) { case 0: case 3: case 6: case 7: case 9: case 11: case 14: text = "environment"; break; case 5: text = "self"; break; default: text = "unknown"; break; } } Vector3 val = (((Object)(object)((Component)__instance).transform != (Object)null) ? ((Component)__instance).transform.position : Vector3.zero); Plugin.Session.deaths.Add(new DeathEvent { dayIndex = (Plugin.CurrentDay()?.dayIndex ?? Math.Max(1, Plugin.Session.days.Count)), playerName = (__instance.playerUsername ?? "(unknown)"), causeOfDeath = ((object)(CauseOfDeath)(ref causeOfDeath)).ToString(), killer = text, moon = Plugin.CurrentMoon(), posX = val.x, posY = val.y, posZ = val.z, tsUtc = DateTime.UtcNow }); DayState dayState = Plugin.CurrentDay(); if (dayState != null) { dayState.deaths++; } Plugin.Log.LogInfo((object)$"[gs] death {__instance.playerUsername} cause={causeOfDeath} killer={text}"); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[gs] death hook err: " + ex.Message)); } } } [HarmonyPatch(typeof(PlayerControllerB), "Update")] internal static class P_PlayerControllerB_Update_Distance { private static readonly Dictionary lastPos = new Dictionary(); private const float TeleportThreshold = 25f; private static void Postfix(PlayerControllerB __instance) { //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) if (Plugin.Session == null || (Object)(object)__instance == (Object)null) { return; } try { if (!((Component)__instance).gameObject.activeInHierarchy || (Object)(object)((Component)__instance).transform == (Object)null) { return; } string playerUsername = __instance.playerUsername; if (string.IsNullOrEmpty(playerUsername)) { return; } Vector3 position = ((Component)__instance).transform.position; if (lastPos.TryGetValue(playerUsername, out var value)) { float num = Vector3.Distance(value, position); if (num > 0.01f && num < 25f) { PlayerSessionState playerSlot = Plugin.GetPlayerSlot(playerUsername); if (playerSlot != null) { playerSlot.metersTraveledFloat += num; string key = "None"; try { if ((Object)(object)TimeOfDay.Instance != (Object)null && (Object)(object)TimeOfDay.Instance.currentLevel != (Object)null) { key = ((object)(LevelWeatherType)(ref TimeOfDay.Instance.currentLevelWeather)).ToString(); } } catch { } playerSlot.distanceByWeather.TryGetValue(key, out var value2); playerSlot.distanceByWeather[key] = value2 + num; } } } lastPos[playerUsername] = position; } catch { } } } [HarmonyPatch(typeof(StartOfRound), "FirePlayersAfterDeadlineClientRpc")] internal static class P_StartOfRound_FirePlayers { private static void Postfix() { Plugin.EmitAndReset("quota_failed"); } } [HarmonyPatch(typeof(StartOfRound), "EndOfGameClientRpc")] internal static class P_StartOfRound_EndOfGame { private static void Postfix(StartOfRound __instance, int bodiesInsured, int daysPlayersSurvived, int connectedPlayersOnServer, int scrapCollectedOnServer) { if (Plugin.Session == null) { return; } try { if (connectedPlayersOnServer <= 0) { Plugin.EmitAndReset("all_dead"); } } catch { } } } [HarmonyPatch(typeof(StartOfRound), "ResetShip")] internal static class P_StartOfRound_ResetShip { private static void Postfix() { Plugin.EmitAndReset("abandoned"); } } [HarmonyPatch(typeof(Terminal), "BeginUsingTerminal")] internal static class P_Terminal_BeginUsingTerminal { private static void Postfix() { if (Plugin.Session == null) { return; } try { PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; if ((Object)(object)val != (Object)null) { Plugin.Session.lastTerminalUser = val.playerUsername; } } catch { } } } [HarmonyPatch(typeof(Terminal), "BuyItemsServerRpc")] internal static class P_Terminal_BuyItemsServerRpc { private static void Postfix(Terminal __instance, int[] boughtItems, int newGroupCredits, int numItemsInShip) { if (Plugin.Session == null || (Object)(object)__instance == (Object)null || boughtItems == null) { return; } try { PlayerSessionState playerSlot = Plugin.GetPlayerSlot(Plugin.Session.lastTerminalUser ?? Plugin.Session.hostName ?? "(unknown)"); if (playerSlot == null) { return; } Item[] buyableItemsList = __instance.buyableItemsList; if (buyableItemsList == null) { return; } foreach (int num in boughtItems) { if (num < 0 || num >= buyableItemsList.Length) { continue; } Item val = buyableItemsList[num]; if (!((Object)(object)val == (Object)null)) { string text = (val.itemName ?? "Unknown").Replace(" ", ""); int creditsWorth = val.creditsWorth; if (!playerSlot.purchases.TryGetValue(text, out var value)) { Dictionary purchases = playerSlot.purchases; PurchaseBag obj = new PurchaseBag { itemName = text, isUnlockable = false }; value = obj; purchases[text] = obj; } value.count++; value.totalSpent += creditsWorth; } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[gs] purchase hook err: " + ex.Message)); } } } [HarmonyPatch(typeof(StartOfRound), "BuyShipUnlockableServerRpc")] internal static class P_StartOfRound_BuyShipUnlockableServerRpc { private static void Postfix(StartOfRound __instance, int unlockableID, int newGroupCreditsAmount) { if (Plugin.Session == null || (Object)(object)__instance == (Object)null) { return; } try { PlayerSessionState playerSlot = Plugin.GetPlayerSlot(Plugin.Session.lastTerminalUser ?? Plugin.Session.hostName ?? "(unknown)"); if (playerSlot == null) { return; } string text = "ShipUnlockable_" + unlockableID; try { UnlockablesList unlockablesList = __instance.unlockablesList; if ((Object)(object)unlockablesList != (Object)null && unlockablesList.unlockables != null && unlockableID >= 0 && unlockableID < unlockablesList.unlockables.Count) { UnlockableItem val = unlockablesList.unlockables[unlockableID]; if (val != null && !string.IsNullOrEmpty(val.unlockableName)) { text = val.unlockableName.Replace(" ", ""); } } } catch { } if (!playerSlot.purchases.TryGetValue(text, out var value)) { Dictionary purchases = playerSlot.purchases; string key = text; PurchaseBag obj2 = new PurchaseBag { itemName = text, isUnlockable = true }; value = obj2; purchases[key] = obj2; } value.count++; value.isUnlockable = true; } catch (Exception ex) { Plugin.Log.LogWarning((object)("[gs] unlockable hook err: " + ex.Message)); } } } [HarmonyPatch(typeof(EnemyAI), "HitEnemy")] internal static class P_EnemyAI_HitEnemy { internal static readonly Dictionary LastHitBy = new Dictionary(); private static void Postfix(EnemyAI __instance, int force, PlayerControllerB playerWhoHit, bool playHitSFX, int hitID) { if (Plugin.Session == null || (Object)(object)__instance == (Object)null || (Object)(object)playerWhoHit == (Object)null) { return; } try { LastHitBy[((Object)__instance).GetInstanceID()] = playerWhoHit.playerUsername; } catch { } } } [HarmonyPatch(typeof(EnemyAI), "KillEnemy")] internal static class P_EnemyAI_KillEnemy { private static void Postfix(EnemyAI __instance, bool destroy) { if (Plugin.Session == null || (Object)(object)__instance == (Object)null) { return; } try { int instanceID = ((Object)__instance).GetInstanceID(); if (P_EnemyAI_HitEnemy.LastHitBy.TryGetValue(instanceID, out var value)) { P_EnemyAI_HitEnemy.LastHitBy.Remove(instanceID); PlayerSessionState playerSlot = Plugin.GetPlayerSlot(value); if (playerSlot != null) { string name = ((object)__instance).GetType().Name; playerSlot.mobKills.TryGetValue(name, out var value2); playerSlot.mobKills[name] = value2 + 1; Plugin.Log.LogInfo((object)("[gs] mob kill " + value + " -> " + name)); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[gs] kill hook err: " + ex.Message)); } } } [HarmonyPatch(typeof(RoundManager), "SpawnScrapInLevel")] internal static class P_RoundManager_SpawnScrapInLevel { private static void Postfix(RoundManager __instance) { DayState dayState = Plugin.CurrentDay(); if (dayState == null || (Object)(object)__instance == (Object)null) { return; } try { dayState.totalScrapValueOnMoon = (int)__instance.totalScrapValueInLevel; } catch { } } } [HarmonyPatch(typeof(GrabbableObject), "GrabItemOnClient")] internal static class P_GrabbableObject_GrabItemOnClient_Apparatus { private static void Postfix(GrabbableObject __instance) { if (Plugin.Session == null || (Object)(object)__instance == (Object)null) { return; } try { if (!((Object)(object)__instance.itemProperties == (Object)null) && string.Equals(__instance.itemProperties.itemName, "Apparatus", StringComparison.OrdinalIgnoreCase)) { DayState dayState = Plugin.CurrentDay(); if (dayState != null) { dayState.apparatusPulled = true; } } } catch { } } } }