using 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)
{
}
}
}