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 LethalWebsocketEvents v1.0.3
plugins\com.github.luckofthelefty.LethalWebsocketEvents.dll
Decompiled a month agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Unity.Netcode; using UnityEngine; using WebSocketSharp.Server; using com.github.luckofthelefty.LethalWebsocketEvents.Events; using com.github.luckofthelefty.LethalWebsocketEvents.Helpers; using com.github.luckofthelefty.LethalWebsocketEvents.Managers; using com.github.luckofthelefty.LethalWebsocketEvents.Patches; using com.github.luckofthelefty.LethalWebsocketEvents.Server; [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("LuckOfTheLefty")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Streams Lethal Company in-game events (deaths, monsters, items, rounds) over WebSocket for Streamerbot integration.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+636990f9269cff6b2f191ee6269052b009b73676")] [assembly: AssemblyProduct("LethalWebsocketEvents")] [assembly: AssemblyTitle("com.github.luckofthelefty.LethalWebsocketEvents")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [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 com.github.luckofthelefty.LethalWebsocketEvents { internal static class Logger { public static ManualLogSource ManualLogSource { get; private set; } public static void Initialize(ManualLogSource manualLogSource) { ManualLogSource = manualLogSource; } public static void LogDebug(object data) { Log((LogLevel)32, data); } public static void LogInfo(object data) { Log((LogLevel)16, data); } public static void LogWarning(object data) { Log((LogLevel)4, data); } public static void LogError(object data) { Log((LogLevel)2, data); } public static void LogFatal(object data) { Log((LogLevel)1, data); } public static void Log(LogLevel logLevel, object data) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) ManualLogSource manualLogSource = ManualLogSource; if (manualLogSource != null) { manualLogSource.Log(logLevel, data); } } } [BepInPlugin("com.github.luckofthelefty.LethalWebsocketEvents", "LethalWebsocketEvents", "1.0.0")] internal class Plugin : BaseUnityPlugin { private readonly Harmony _harmony = new Harmony("com.github.luckofthelefty.LethalWebsocketEvents"); internal static Plugin Instance { get; private set; } private void Awake() { Instance = this; Logger.Initialize(Logger.CreateLogSource("com.github.luckofthelefty.LethalWebsocketEvents")); Logger.LogInfo("LethalWebsocketEvents v1.0.0 loaded!"); ConfigManager.Initialize(((BaseUnityPlugin)this).Config); _harmony.PatchAll(typeof(PlayerDeathPatch)); _harmony.PatchAll(typeof(PlayerDamagePatch)); _harmony.PatchAll(typeof(EmotePatch)); _harmony.PatchAll(typeof(RoundPatch)); _harmony.PatchAll(typeof(QuotaPatch)); _harmony.PatchAll(typeof(EnemyPatch)); _harmony.PatchAll(typeof(ItemPatch)); _harmony.PatchAll(typeof(ApparatusPatch)); _harmony.PatchAll(typeof(LandminePatch)); _harmony.PatchAll(typeof(TurretPatch)); _harmony.PatchAll(typeof(TeleporterPatch)); _harmony.PatchAll(typeof(BrackenPatch)); _harmony.PatchAll(typeof(JesterPatch)); _harmony.PatchAll(typeof(GhostGirlPatch)); _harmony.PatchAll(typeof(CoilheadPatch)); _harmony.PatchAll(typeof(MaskedPatch)); _harmony.PatchAll(typeof(NutcrackerPatch)); _harmony.PatchAll(typeof(ForestGiantPatch)); _harmony.PatchAll(typeof(SnareFleaPatch)); _harmony.PatchAll(typeof(SpiderPatch)); _harmony.PatchAll(typeof(BlobPatch)); _harmony.PatchAll(typeof(ThumperPatch)); _harmony.PatchAll(typeof(EyelessDogPatch)); _harmony.PatchAll(typeof(BaboonHawkPatch)); _harmony.PatchAll(typeof(ConnectionPatch)); _harmony.PatchAll(typeof(DisconnectPatch)); _harmony.PatchAll(typeof(VoteToLeavePatch)); Task.Run((Action)EventServer.Initialize); } } public static class MyPluginInfo { public const string PLUGIN_GUID = "com.github.luckofthelefty.LethalWebsocketEvents"; public const string PLUGIN_NAME = "LethalWebsocketEvents"; public const string PLUGIN_VERSION = "1.0.0"; } } namespace com.github.luckofthelefty.LethalWebsocketEvents.Server { public class EventBehavior : WebSocketBehavior { protected override void OnOpen() { GameEvent gameEvent = GameEvent.Create("connected", new Dictionary<string, object> { { "modVersion", "1.0.0" }, { "modName", "LethalWebsocketEvents" } }); ((WebSocketBehavior)this).Send(JsonConvert.SerializeObject((object)gameEvent)); } } internal static class EventServer { private static WebSocketServer _webSocketServer; private static readonly object _broadcastLock = new object(); public static int WebSocketPort => ConfigManager.Server_WebSocketPort.Value; public static bool IsRunning { get; private set; } public static void Initialize() { Application.quitting += delegate { Stop(); }; if (ConfigManager.Server_AutoStart.Value) { Start(); } } public static void Start() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown if (IsRunning) { Logger.LogWarning("Event server is already running!"); return; } try { IsRunning = true; _webSocketServer = new WebSocketServer($"ws://{IPAddress.Any}:{WebSocketPort}"); _webSocketServer.AddWebSocketService<EventBehavior>("/events"); _webSocketServer.Start(); if (!_webSocketServer.IsListening) { Logger.LogError("Failed to start WebSocket server. The port might already be in use."); Stop(); } else { Logger.LogInfo($"WebSocket event server started on ws://localhost:{WebSocketPort}/events"); } } catch (SocketException ex) { Logger.LogError("Failed to start WebSocket server. " + ex.Message); Stop(); } catch (Exception arg) { Logger.LogError($"Failed to start WebSocket server. {arg}"); Stop(); } } public static void Stop() { if (IsRunning) { IsRunning = false; WebSocketServer webSocketServer = _webSocketServer; if (webSocketServer != null) { webSocketServer.Stop(); } _webSocketServer = null; Logger.LogInfo("Event server stopped."); } } public static void SendEvent(GameEvent gameEvent) { if (_webSocketServer == null || !_webSocketServer.IsListening) { return; } lock (_broadcastLock) { try { string text = JsonConvert.SerializeObject((object)gameEvent); if (ConfigManager.ExtendedLogging.Value) { Logger.LogInfo("Broadcasting event: " + text); } foreach (string path in _webSocketServer.WebSocketServices.Paths) { WebSocketServiceHost val = _webSocketServer.WebSocketServices[path]; val.Sessions.Broadcast(text); } } catch (Exception arg) { Logger.LogError($"Failed to broadcast event. {arg}"); } } } public static void SendEvent(string eventName, Dictionary<string, object> data = null) { SendEvent(GameEvent.Create(eventName, data)); } } } namespace com.github.luckofthelefty.LethalWebsocketEvents.Patches { [HarmonyPatch(typeof(EnemyAI))] internal static class EnemyPatch { [HarmonyPatch("Start")] [HarmonyPostfix] private static void StartPatch(EnemyAI __instance) { if (!((Object)(object)__instance.enemyType == (Object)null)) { string value = __instance.enemyType.enemyName ?? ((object)__instance).GetType().Name; bool isOutside = __instance.isOutside; EventServer.SendEvent("enemy_spawned", new Dictionary<string, object> { { "enemyType", value }, { "location", isOutside ? "outdoor" : "indoor" } }); } } [HarmonyPatch("KillEnemyClientRpc")] [HarmonyPostfix] private static void KillEnemyClientRpcPatch(EnemyAI __instance) { if (NetworkUtils.ShouldProcess($"enemy_killed_{((Object)__instance).GetInstanceID()}") && !((Object)(object)__instance.enemyType == (Object)null)) { string value = __instance.enemyType.enemyName ?? ((object)__instance).GetType().Name; EventServer.SendEvent("enemy_killed", new Dictionary<string, object> { { "enemyType", value } }); } } [HarmonyPatch("SwitchToBehaviourClientRpc")] [HarmonyPostfix] private static void SwitchToBehaviourClientRpcPatch(EnemyAI __instance, int stateIndex) { if (NetworkUtils.ShouldProcess($"enemy_state_{((Object)__instance).GetInstanceID()}_{stateIndex}") && !((Object)(object)__instance.enemyType == (Object)null)) { string value = __instance.enemyType.enemyName ?? ((object)__instance).GetType().Name; EventServer.SendEvent("enemy_state_changed", new Dictionary<string, object> { { "enemyType", value }, { "state", stateIndex } }); } } [HarmonyPatch("HitEnemyClientRpc")] [HarmonyPostfix] private static void HitEnemyClientRpcPatch(EnemyAI __instance, int force, int playerWhoHit, bool playHitSFX, int hitID) { if (NetworkUtils.ShouldProcess($"enemy_hit_{((Object)__instance).GetInstanceID()}_{hitID}") && !((Object)(object)__instance.enemyType == (Object)null)) { string value = __instance.enemyType.enemyName ?? ((object)__instance).GetType().Name; string playerName = PlayerUtils.GetPlayerName(playerWhoHit); EventServer.SendEvent("enemy_hit", new Dictionary<string, object> { { "enemyType", value }, { "player", playerName }, { "force", force } }); } } } [HarmonyPatch(typeof(Landmine))] internal static class LandminePatch { [HarmonyPatch("ExplodeMineClientRpc")] [HarmonyPostfix] private static void ExplodeMineClientRpcPatch(Landmine __instance) { if (NetworkUtils.ShouldProcess($"landmine_{((Object)__instance).GetInstanceID()}")) { EventServer.SendEvent("landmine_exploded", new Dictionary<string, object>()); } } } [HarmonyPatch(typeof(Turret))] internal static class TurretPatch { [HarmonyPatch("SetToModeClientRpc")] [HarmonyPostfix] private static void SetToModeClientRpcPatch(Turret __instance, int mode) { if (NetworkUtils.ShouldProcess($"turret_{((Object)__instance).GetInstanceID()}_{mode}")) { string[] array = new string[4] { "Detection", "Charging", "Firing", "Berserk" }; string value = ((mode >= 0 && mode < array.Length) ? array[mode] : "Unknown"); EventServer.SendEvent("turret_mode_changed", new Dictionary<string, object> { { "mode", value }, { "modeIndex", mode } }); } } } [HarmonyPatch(typeof(ShipTeleporter))] internal static class TeleporterPatch { [HarmonyPatch("PressTeleportButtonClientRpc")] [HarmonyPostfix] private static void PressTeleportButtonClientRpcPatch(ShipTeleporter __instance) { if (NetworkUtils.ShouldProcess($"teleporter_{((Object)__instance).GetInstanceID()}")) { EventServer.SendEvent("teleporter_used", new Dictionary<string, object> { { "isInverse", __instance.isInverseTeleporter } }); } } } [HarmonyPatch(typeof(PlayerControllerB))] internal static class ItemPatch { [HarmonyPatch("GrabObjectClientRpc")] [HarmonyPostfix] private static void GrabObjectClientRpcPatch(PlayerControllerB __instance, NetworkObjectReference grabbedObject) { if (NetworkUtils.ShouldProcess($"item_grab_{((Object)__instance).GetInstanceID()}") && PlayerUtils.ShouldTrackPlayer(__instance)) { string playerName = PlayerUtils.GetPlayerName(__instance); string value = "Unknown"; int num = 0; NetworkObject val = default(NetworkObject); GrabbableObject val2 = default(GrabbableObject); if (((NetworkObjectReference)(ref grabbedObject)).TryGet(ref val, (NetworkManager)null) && ((Component)val).TryGetComponent<GrabbableObject>(ref val2)) { value = val2.itemProperties?.itemName ?? "Unknown"; num = val2.scrapValue; } EventServer.SendEvent("item_grabbed", new Dictionary<string, object> { { "player", playerName }, { "item", value }, { "scrapValue", num } }); } } [HarmonyPatch("ThrowObjectClientRpc")] [HarmonyPostfix] private static void ThrowObjectClientRpcPatch(PlayerControllerB __instance, bool droppedInElevator, bool droppedInShipRoom) { if (NetworkUtils.ShouldProcess($"item_drop_{((Object)__instance).GetInstanceID()}") && PlayerUtils.ShouldTrackPlayer(__instance)) { string playerName = PlayerUtils.GetPlayerName(__instance); EventServer.SendEvent("item_dropped", new Dictionary<string, object> { { "player", playerName }, { "inShip", droppedInElevator || droppedInShipRoom } }); } } } [HarmonyPatch(typeof(LungProp))] internal static class ApparatusPatch { [HarmonyPatch("DisconnectFromMachinery")] [HarmonyPostfix] private static void DisconnectFromMachineryPatch(LungProp __instance) { string value = "Unknown"; if (((GrabbableObject)__instance).isHeld && (Object)(object)((GrabbableObject)__instance).playerHeldBy != (Object)null) { if (!PlayerUtils.ShouldTrackPlayer(((GrabbableObject)__instance).playerHeldBy)) { return; } value = PlayerUtils.GetPlayerName(((GrabbableObject)__instance).playerHeldBy); } EventServer.SendEvent("apparatus_pulled", new Dictionary<string, object> { { "player", value } }); } } [HarmonyPatch(typeof(PlayerControllerB))] internal static class ConnectionPatch { [HarmonyPatch("SendNewPlayerValuesClientRpc")] [HarmonyPostfix] private static void SendNewPlayerValuesClientRpcPatch(PlayerControllerB __instance) { if (NetworkUtils.ShouldProcess($"player_joined_{((Object)__instance).GetInstanceID()}")) { string playerName = PlayerUtils.GetPlayerName(__instance); int connectedPlayerCount = PlayerUtils.GetConnectedPlayerCount(); EventServer.SendEvent("player_joined", new Dictionary<string, object> { { "player", playerName }, { "playerCount", connectedPlayerCount } }); } } } [HarmonyPatch(typeof(StartOfRound))] internal static class DisconnectPatch { [HarmonyPatch("OnPlayerDC")] [HarmonyPostfix] private static void OnPlayerDCPatch(int playerObjectNumber) { string value = "Unknown"; if (StartOfRound.Instance?.allPlayerScripts != null && playerObjectNumber >= 0 && playerObjectNumber < StartOfRound.Instance.allPlayerScripts.Length) { value = PlayerUtils.GetPlayerName(StartOfRound.Instance.allPlayerScripts[playerObjectNumber]); } int connectedPlayerCount = PlayerUtils.GetConnectedPlayerCount(); EventServer.SendEvent("player_left", new Dictionary<string, object> { { "player", value }, { "playerCount", connectedPlayerCount } }); } } [HarmonyPatch(typeof(PlayerControllerB))] internal static class EmotePatch { [HarmonyPatch("PerformEmote")] [HarmonyPostfix] private static void PerformEmotePatch(PlayerControllerB __instance, int emoteID) { if (PlayerUtils.ShouldTrackPlayer(__instance)) { string playerName = PlayerUtils.GetPlayerName(__instance); EventServer.SendEvent("player_emote", new Dictionary<string, object> { { "player", playerName }, { "emoteId", emoteID } }); } } } [HarmonyPatch(typeof(TimeOfDay))] internal static class VoteToLeavePatch { [HarmonyPatch("VoteShipToLeaveEarly")] [HarmonyPostfix] private static void VoteShipToLeaveEarlyPatch() { EventServer.SendEvent("vote_to_leave", new Dictionary<string, object>()); } } [HarmonyPatch(typeof(FlowermanAI))] internal static class BrackenPatch { [HarmonyPatch("KillPlayerAnimationClientRpc")] [HarmonyPostfix] private static void KillPlayerAnimationClientRpcPatch(FlowermanAI __instance, int playerObjectId) { if (NetworkUtils.ShouldProcess($"bracken_grab_{playerObjectId}")) { string playerName = PlayerUtils.GetPlayerName(playerObjectId); EventServer.SendEvent("bracken_grab", new Dictionary<string, object> { { "player", playerName } }); } } } [HarmonyPatch(typeof(JesterAI))] internal static class JesterPatch { [HarmonyPatch("KillPlayerClientRpc")] [HarmonyPostfix] private static void KillPlayerClientRpcPatch(JesterAI __instance, int playerId) { if (NetworkUtils.ShouldProcess($"jester_kill_{playerId}")) { string playerName = PlayerUtils.GetPlayerName(playerId); EventServer.SendEvent("jester_kill", new Dictionary<string, object> { { "player", playerName } }); } } } [HarmonyPatch(typeof(DressGirlAI))] internal static class GhostGirlPatch { [HarmonyPatch("ChooseNewHauntingPlayerClientRpc")] [HarmonyPostfix] private static void ChooseNewHauntingPlayerClientRpcPatch(DressGirlAI __instance) { if (NetworkUtils.ShouldProcess($"ghost_haunt_{((Object)__instance).GetInstanceID()}")) { string value = "Unknown"; if ((Object)(object)__instance.hauntingPlayer != (Object)null) { value = PlayerUtils.GetPlayerName(__instance.hauntingPlayer); } EventServer.SendEvent("ghost_girl_haunt", new Dictionary<string, object> { { "player", value } }); } } } [HarmonyPatch(typeof(SpringManAI))] internal static class CoilheadPatch { [HarmonyPatch("SetAnimationGoClientRpc")] [HarmonyPostfix] private static void SetAnimationGoClientRpcPatch(SpringManAI __instance) { if (NetworkUtils.ShouldProcess($"coilhead_go_{((Object)__instance).GetInstanceID()}")) { EventServer.SendEvent("coilhead_moving", new Dictionary<string, object>()); } } [HarmonyPatch("SetAnimationStopClientRpc")] [HarmonyPostfix] private static void SetAnimationStopClientRpcPatch(SpringManAI __instance) { if (NetworkUtils.ShouldProcess($"coilhead_stop_{((Object)__instance).GetInstanceID()}")) { EventServer.SendEvent("coilhead_stopped", new Dictionary<string, object>()); } } } [HarmonyPatch(typeof(MaskedPlayerEnemy))] internal static class MaskedPatch { [HarmonyPatch("CreateMimicClientRpc")] [HarmonyPostfix] private static void CreateMimicClientRpcPatch(MaskedPlayerEnemy __instance) { if (NetworkUtils.ShouldProcess($"masked_mimic_{((Object)__instance).GetInstanceID()}")) { string value = (((Object)(object)__instance.mimickingPlayer != (Object)null) ? PlayerUtils.GetPlayerName(__instance.mimickingPlayer) : "Unknown"); EventServer.SendEvent("masked_mimic", new Dictionary<string, object> { { "mimicking", value } }); } } } [HarmonyPatch(typeof(NutcrackerEnemyAI))] internal static class NutcrackerPatch { [HarmonyPatch("FireGunClientRpc")] [HarmonyPostfix] private static void FireGunClientRpcPatch(NutcrackerEnemyAI __instance) { if (NetworkUtils.ShouldProcess($"nutcracker_shot_{((Object)__instance).GetInstanceID()}")) { EventServer.SendEvent("nutcracker_shot", new Dictionary<string, object>()); } } } [HarmonyPatch(typeof(ForestGiantAI))] internal static class ForestGiantPatch { [HarmonyPatch("GrabPlayerClientRpc")] [HarmonyPostfix] private static void GrabPlayerClientRpcPatch(ForestGiantAI __instance, int playerId) { if (NetworkUtils.ShouldProcess($"giant_grab_{playerId}")) { string playerName = PlayerUtils.GetPlayerName(playerId); EventServer.SendEvent("giant_grab", new Dictionary<string, object> { { "player", playerName } }); } } } [HarmonyPatch(typeof(CentipedeAI))] internal static class SnareFleaPatch { [HarmonyPatch("ClingToPlayerClientRpc")] [HarmonyPostfix] private static void ClingToPlayerClientRpcPatch(CentipedeAI __instance) { if (NetworkUtils.ShouldProcess($"snare_flea_{((Object)__instance).GetInstanceID()}")) { string value = "Unknown"; if ((Object)(object)__instance.clingingToPlayer != (Object)null) { value = PlayerUtils.GetPlayerName(__instance.clingingToPlayer); } EventServer.SendEvent("snare_flea_cling", new Dictionary<string, object> { { "player", value } }); } } } [HarmonyPatch(typeof(SandSpiderAI))] internal static class SpiderPatch { [HarmonyPatch("PlayerTripWebClientRpc")] [HarmonyPostfix] private static void PlayerTripWebClientRpcPatch(SandSpiderAI __instance, int playerNum) { if (NetworkUtils.ShouldProcess($"spider_web_{((Object)__instance).GetInstanceID()}_{playerNum}")) { string playerName = PlayerUtils.GetPlayerName(playerNum); EventServer.SendEvent("spider_web_trip", new Dictionary<string, object> { { "player", playerName } }); } } } [HarmonyPatch(typeof(BlobAI))] internal static class BlobPatch { [HarmonyPatch("SlimeKillPlayerEffectClientRpc")] [HarmonyPostfix] private static void SlimeKillPlayerEffectClientRpcPatch(BlobAI __instance, int playerKilled) { if (NetworkUtils.ShouldProcess($"blob_kill_{playerKilled}")) { string playerName = PlayerUtils.GetPlayerName(playerKilled); EventServer.SendEvent("blob_kill", new Dictionary<string, object> { { "player", playerName } }); } } } [HarmonyPatch(typeof(CrawlerAI))] internal static class ThumperPatch { [HarmonyPatch("HitPlayerClientRpc")] [HarmonyPostfix] private static void HitPlayerClientRpcPatch(CrawlerAI __instance, int playerId) { if (NetworkUtils.ShouldProcess($"thumper_hit_{playerId}")) { string playerName = PlayerUtils.GetPlayerName(playerId); EventServer.SendEvent("thumper_hit", new Dictionary<string, object> { { "player", playerName } }); } } } [HarmonyPatch(typeof(MouthDogAI))] internal static class EyelessDogPatch { [HarmonyPatch("KillPlayerClientRpc")] [HarmonyPostfix] private static void KillPlayerClientRpcPatch(MouthDogAI __instance, int playerId) { if (NetworkUtils.ShouldProcess($"dog_kill_{playerId}")) { string playerName = PlayerUtils.GetPlayerName(playerId); EventServer.SendEvent("eyeless_dog_kill", new Dictionary<string, object> { { "player", playerName } }); } } } [HarmonyPatch(typeof(BaboonBirdAI))] internal static class BaboonHawkPatch { [HarmonyPatch("StabPlayerDeathAnimClientRpc")] [HarmonyPostfix] private static void StabPlayerDeathAnimClientRpcPatch(BaboonBirdAI __instance, int playerObject) { if (NetworkUtils.ShouldProcess($"baboon_stab_{playerObject}")) { string playerName = PlayerUtils.GetPlayerName(playerObject); EventServer.SendEvent("baboon_hawk_stab", new Dictionary<string, object> { { "player", playerName } }); } } } [HarmonyPatch(typeof(PlayerControllerB))] internal static class PlayerDamagePatch { [HarmonyPatch("DamagePlayerClientRpc")] [HarmonyPostfix] private static void DamagePlayerClientRpcPatch(PlayerControllerB __instance, int damageNumber) { if (!NetworkUtils.ShouldProcess($"damage_{((Object)__instance).GetInstanceID()}_{damageNumber}") || !PlayerUtils.ShouldTrackPlayer(__instance)) { return; } string playerName = PlayerUtils.GetPlayerName(__instance); if (damageNumber < 0) { EventServer.SendEvent("player_healed", new Dictionary<string, object> { { "player", playerName }, { "amount", -damageNumber }, { "health", __instance.health } }); return; } string text = EnemyUtils.FindAttackingEnemy(__instance); Dictionary<string, object> dictionary = new Dictionary<string, object> { { "player", playerName }, { "damage", damageNumber }, { "health", __instance.health }, { "critical", __instance.health <= 0 } }; if (text != null) { dictionary["enemy"] = text; } EventServer.SendEvent("player_damage", dictionary); } } [HarmonyPatch(typeof(PlayerControllerB))] internal static class PlayerDeathPatch { [HarmonyPatch("KillPlayerClientRpc")] [HarmonyPostfix] private static void KillPlayerClientRpcPatch(PlayerControllerB __instance, int playerId, int causeOfDeath) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) if (!NetworkUtils.ShouldProcess($"death_{playerId}")) { return; } PlayerControllerB playerScript = ((StartOfRound.Instance?.allPlayerScripts != null && playerId >= 0 && playerId < StartOfRound.Instance.allPlayerScripts.Length) ? StartOfRound.Instance.allPlayerScripts[playerId] : __instance); if (PlayerUtils.ShouldTrackPlayer(playerScript)) { string playerName = PlayerUtils.GetPlayerName(playerScript); CauseOfDeath val = (CauseOfDeath)causeOfDeath; string value = ((object)(CauseOfDeath)(ref val)).ToString(); string text = EnemyUtils.FindAttackingEnemy(playerScript); Dictionary<string, object> dictionary = new Dictionary<string, object> { { "player", playerName }, { "causeOfDeath", value }, { "playerId", playerId } }; if (text != null) { dictionary["enemy"] = text; } EventServer.SendEvent("player_death", dictionary); } } } [HarmonyPatch(typeof(StartOfRound))] internal static class RoundPatch { private static DateTime _lastDayChangedTime = DateTime.MinValue; private static string _lastDayChangedKey = ""; private static readonly TimeSpan EventDebounce = TimeSpan.FromSeconds(2.0); private static DateTime _lastShipLandedTime = DateTime.MinValue; [HarmonyPatch("StartGame")] [HarmonyPostfix] private static void StartGamePatch() { string value = StartOfRound.Instance?.currentLevel?.PlanetName ?? "Unknown"; StartOfRound instance = StartOfRound.Instance; object obj; if (instance == null) { obj = null; } else { SelectableLevel currentLevel = instance.currentLevel; obj = ((currentLevel != null) ? ((object)(LevelWeatherType)(ref currentLevel.currentWeather)).ToString() : null); } if (obj == null) { obj = "None"; } string value2 = (string)obj; EventServer.SendEvent("round_start", new Dictionary<string, object> { { "moon", value }, { "weather", value2 } }); } [HarmonyPatch("EndOfGame")] [HarmonyPrefix] private static void EndOfGamePatch() { EventServer.SendEvent("round_end", new Dictionary<string, object>()); } [HarmonyPatch("ShipLeave")] [HarmonyPostfix] private static void ShipLeavePatch() { EventServer.SendEvent("ship_leaving", new Dictionary<string, object>()); } [HarmonyPatch("ReviveDeadPlayers")] [HarmonyPostfix] private static void ReviveDeadPlayersPatch() { EventServer.SendEvent("players_revived", new Dictionary<string, object>()); } [HarmonyPatch("SetMapScreenInfoToCurrentLevel")] [HarmonyPostfix] private static void SetMapScreenInfoToCurrentLevelPatch() { string text = StartOfRound.Instance?.currentLevel?.PlanetName ?? "Unknown"; StartOfRound instance = StartOfRound.Instance; object obj; if (instance == null) { obj = null; } else { SelectableLevel currentLevel = instance.currentLevel; obj = ((currentLevel != null) ? ((object)(LevelWeatherType)(ref currentLevel.currentWeather)).ToString() : null); } if (obj == null) { obj = "None"; } string text2 = (string)obj; string text3 = text + "|" + text2; DateTime utcNow = DateTime.UtcNow; if (!(utcNow - _lastDayChangedTime < EventDebounce) || !(_lastDayChangedKey == text3)) { _lastDayChangedTime = utcNow; _lastDayChangedKey = text3; EventServer.SendEvent("day_changed", new Dictionary<string, object> { { "moon", text }, { "weather", text2 } }); } } [HarmonyPatch("ChangeLevelClientRpc")] [HarmonyPostfix] private static void ChangeLevelClientRpcPatch(StartOfRound __instance) { if (NetworkUtils.ShouldProcess("moon_changed")) { string value = StartOfRound.Instance?.currentLevel?.PlanetName ?? "Unknown"; StartOfRound instance = StartOfRound.Instance; object obj; if (instance == null) { obj = null; } else { SelectableLevel currentLevel = instance.currentLevel; obj = ((currentLevel != null) ? ((object)(LevelWeatherType)(ref currentLevel.currentWeather)).ToString() : null); } if (obj == null) { obj = "None"; } string value2 = (string)obj; EventServer.SendEvent("moon_changed", new Dictionary<string, object> { { "moon", value }, { "weather", value2 } }); } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPostfix] private static void OpeningDoorsSequencePatch() { DateTime utcNow = DateTime.UtcNow; if (!(utcNow - _lastShipLandedTime < EventDebounce)) { _lastShipLandedTime = utcNow; string value = StartOfRound.Instance?.currentLevel?.PlanetName ?? "Unknown"; EventServer.SendEvent("ship_landed", new Dictionary<string, object> { { "moon", value } }); } } } [HarmonyPatch(typeof(TimeOfDay))] internal static class QuotaPatch { [HarmonyPatch("SyncNewProfitQuotaClientRpc")] [HarmonyPostfix] private static void SyncNewProfitQuotaClientRpcPatch(TimeOfDay __instance) { if (NetworkUtils.ShouldProcess("quota_fulfilled")) { int num = TimeOfDay.Instance?.profitQuota ?? 0; int num2 = (TimeOfDay.Instance?.timesFulfilledQuota ?? 0) + 1; EventServer.SendEvent("quota_fulfilled", new Dictionary<string, object> { { "newQuota", num }, { "quotaIndex", num2 } }); } } } } namespace com.github.luckofthelefty.LethalWebsocketEvents.Managers { internal static class ConfigManager { public static ConfigFile ConfigFile { get; private set; } public static ConfigEntry<bool> ExtendedLogging { get; private set; } public static ConfigEntry<bool> Server_AutoStart { get; private set; } public static ConfigEntry<int> Server_WebSocketPort { get; private set; } public static ConfigEntry<bool> Filter_LocalPlayerOnly { get; private set; } public static ConfigEntry<string> Filter_PlayerName { get; private set; } public static void Initialize(ConfigFile configFile) { ConfigFile = configFile; ExtendedLogging = configFile.Bind<bool>("General", "ExtendedLogging", false, "Enable extended logging for debugging."); Server_AutoStart = configFile.Bind<bool>("Server", "AutoStart", true, "If enabled, the WebSocket server will automatically start when the game launches."); Server_WebSocketPort = configFile.Bind<int>("Server", "WebSocketPort", 8765, "The WebSocket port for the event server."); Filter_LocalPlayerOnly = configFile.Bind<bool>("Filter", "LocalPlayerOnly", false, "If enabled, player-specific events (death, damage, items) only fire for the local player. Global events (enemy spawns, round start, etc.) always fire regardless of this setting."); Filter_PlayerName = configFile.Bind<string>("Filter", "PlayerName", "", "If set, player-specific events only fire for this player name. Leave empty to use LocalPlayerOnly logic instead. Useful if you want to track a specific player who isn't running the mod."); } } } namespace com.github.luckofthelefty.LethalWebsocketEvents.Helpers { internal static class EnemyUtils { public static string FindAttackingEnemy(PlayerControllerB playerScript) { if ((Object)(object)playerScript == (Object)null) { return null; } if ((Object)(object)playerScript.inAnimationWithEnemy != (Object)null) { return GetEnemyName(playerScript.inAnimationWithEnemy); } EnemyAI[] array = Object.FindObjectsOfType<EnemyAI>(); if (array == null) { return null; } EnemyAI[] array2 = array; foreach (EnemyAI val in array2) { if (!((Object)(object)val == (Object)null) && !((Object)(object)val.enemyType == (Object)null)) { if ((Object)(object)val.inSpecialAnimationWithPlayer == (Object)(object)playerScript) { return GetEnemyName(val); } if ((Object)(object)val.targetPlayer == (Object)(object)playerScript && val.movingTowardsTargetPlayer) { return GetEnemyName(val); } } } return null; } private static string GetEnemyName(EnemyAI enemy) { if ((Object)(object)enemy?.enemyType == (Object)null) { return null; } return enemy.enemyType.enemyName ?? ((object)enemy).GetType().Name; } } internal static class NetworkUtils { private static readonly Dictionary<string, int> _lastProcessedFrame = new Dictionary<string, int>(); public static bool IsConnected { get { NetworkManager singleton = NetworkManager.Singleton; if (singleton == null) { return false; } return singleton.IsConnectedClient; } } public static bool IsServer { get { NetworkManager singleton = NetworkManager.Singleton; if (singleton == null) { return false; } return singleton.IsServer; } } public static ulong GetLocalClientId() { NetworkManager singleton = NetworkManager.Singleton; if (singleton == null) { return 0uL; } return singleton.LocalClientId; } public static bool IsLocalClientId(ulong clientId) { return clientId == GetLocalClientId(); } public static bool ShouldProcess(string eventKey) { int frameCount = Time.frameCount; if (_lastProcessedFrame.TryGetValue(eventKey, out var value) && value == frameCount) { return false; } _lastProcessedFrame[eventKey] = frameCount; return true; } } internal static class PlayerUtils { public static PlayerControllerB GetLocalPlayerScript() { if ((Object)(object)GameNetworkManager.Instance == (Object)null) { return null; } return GameNetworkManager.Instance.localPlayerController; } public static bool IsLocalPlayer(PlayerControllerB playerScript) { return (Object)(object)playerScript == (Object)(object)GetLocalPlayerScript(); } public static bool IsLocalPlayerSpawned() { return (Object)(object)GetLocalPlayerScript() != (Object)null; } public static PlayerControllerB GetPlayerScriptByClientId(ulong clientId) { if ((Object)(object)StartOfRound.Instance == (Object)null) { return null; } PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts; foreach (PlayerControllerB val in allPlayerScripts) { if (val.actualClientId == clientId) { return val; } } return null; } public static int GetConnectedPlayerCount() { if ((Object)(object)StartOfRound.Instance == (Object)null) { return 1; } return StartOfRound.Instance.connectedPlayersAmount + 1; } public static string GetPlayerName(PlayerControllerB playerScript) { if ((Object)(object)playerScript == (Object)null) { return "Unknown"; } return playerScript.playerUsername ?? "Unknown"; } public static string GetPlayerName(int playerObjectIndex) { if (StartOfRound.Instance?.allPlayerScripts == null || playerObjectIndex < 0 || playerObjectIndex >= StartOfRound.Instance.allPlayerScripts.Length) { return "Unknown"; } return GetPlayerName(StartOfRound.Instance.allPlayerScripts[playerObjectIndex]); } public static bool ShouldTrackPlayer(PlayerControllerB playerScript) { string text = ConfigManager.Filter_PlayerName?.Value; if (!string.IsNullOrEmpty(text)) { string playerName = GetPlayerName(playerScript); return string.Equals(playerName, text, StringComparison.OrdinalIgnoreCase); } ConfigEntry<bool> filter_LocalPlayerOnly = ConfigManager.Filter_LocalPlayerOnly; if (filter_LocalPlayerOnly != null && filter_LocalPlayerOnly.Value) { return IsLocalPlayer(playerScript); } return true; } public static bool ShouldTrackPlayer(string playerName) { string text = ConfigManager.Filter_PlayerName?.Value; if (!string.IsNullOrEmpty(text)) { return string.Equals(playerName, text, StringComparison.OrdinalIgnoreCase); } ConfigEntry<bool> filter_LocalPlayerOnly = ConfigManager.Filter_LocalPlayerOnly; if (filter_LocalPlayerOnly != null && filter_LocalPlayerOnly.Value) { PlayerControllerB localPlayerScript = GetLocalPlayerScript(); if ((Object)(object)localPlayerScript != (Object)null) { return string.Equals(GetPlayerName(localPlayerScript), playerName, StringComparison.OrdinalIgnoreCase); } return false; } return true; } public static bool ShouldTrackPlayer(int playerId) { if (StartOfRound.Instance?.allPlayerScripts == null || playerId < 0 || playerId >= StartOfRound.Instance.allPlayerScripts.Length) { return true; } return ShouldTrackPlayer(StartOfRound.Instance.allPlayerScripts[playerId]); } } } namespace com.github.luckofthelefty.LethalWebsocketEvents.Events { internal class GameEvent { [JsonProperty("event")] public string Event { get; set; } [JsonProperty("timestamp")] public string Timestamp { get; set; } [JsonProperty("data")] public Dictionary<string, object> Data { get; set; } public GameEvent(string eventName, Dictionary<string, object> data = null) { Event = eventName; Timestamp = DateTime.UtcNow.ToString("o"); Data = data ?? new Dictionary<string, object>(); } public static GameEvent Create(string eventName, Dictionary<string, object> data = null) { return new GameEvent(eventName, data); } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }