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 GsLethalStatsEmitter v0.1.6
GsLethalStatsEmitter.dll
Decompiled 10 hours agousing 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<string> IngestUrl; internal static ConfigEntry<string> IngestToken; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<bool> 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<string>("Ingest", "Url", "https://gs.proudtech.net/api/lethal/ingest", "POST endpoint that receives the session payload at game-over."); IngestToken = ((BaseUnityPlugin)this).Config.Bind<string>("Ingest", "Token", "", "Bearer token sent in the Authorization header. Set this in BepInEx/config/net.cproudlock.gslethalstatsemitter.cfg"); Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Ingest", "Enabled", true, "Master toggle. Set false to keep the mod loaded but skip ingest."); EmitOnHostOnly = ((BaseUnityPlugin)this).Config.Bind<bool>("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<Terminal>(); 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<DayState> days = new List<DayState>(); public Dictionary<string, PlayerSessionState> players = new Dictionary<string, PlayerSessionState>(); public List<DeathEvent> deaths = new List<DeathEvent>(); 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<string, PlayerItemBag> items = new Dictionary<string, PlayerItemBag>(); public Dictionary<string, PurchaseBag> purchases = new Dictionary<string, PurchaseBag>(); public Dictionary<string, int> mobKills = new Dictionary<string, int>(); public Dictionary<string, float> distanceByWeather = new Dictionary<string, float>(); 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<string, int> 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<string, float> 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<string, PluginInfo> pluginInfos = Chainloader.PluginInfos; if (pluginInfos != null) { foreach (KeyValuePair<string, PluginInfo> 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<string, PlayerItemBag> 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<PlayerControllerB>(); 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<string, Vector3> lastPos = new Dictionary<string, Vector3>(); 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<string, PurchaseBag> 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<string, PurchaseBag> 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<int, string> LastHitBy = new Dictionary<int, string>(); 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 { } } } }