diff --git a/OpenCS2hock/CS2MessageHandler.cs b/OpenCS2hock/CS2MessageHandler.cs new file mode 100644 index 0000000..63e462d --- /dev/null +++ b/OpenCS2hock/CS2MessageHandler.cs @@ -0,0 +1,69 @@ +using Newtonsoft.Json.Linq; + +namespace OpenCS2hock; + +public class CS2MessageHandler +{ + public delegate void CS2EventHandler(); + public event CS2EventHandler? OnKill; + public event CS2EventHandler? OnDeath; + public event CS2EventHandler? OnRoundStart; + public event CS2EventHandler? OnRoundEnd; + public event CS2EventHandler? OnRoundWin; + public event CS2EventHandler? OnRoundLoss; + + public void HandleCS2Message(string message) + { + JObject messageJson = JObject.Parse(message); + + JToken? previously = messageJson.GetValue("previously"); + + RoundState currentRoundState = ParseRoundStateFromString(messageJson["round"]?.Value("phase")); + RoundState previousRoundState = ParseRoundStateFromString(previously?["round"]?.Value("phase")); + if(previousRoundState == RoundState.FreezeTime && currentRoundState == RoundState.Live) + OnRoundStart?.Invoke(); + if(previousRoundState == RoundState.Live && currentRoundState == RoundState.FreezeTime) + OnRoundEnd?.Invoke(); + + Team playerTeam = ParseTeamFromString(messageJson["player"]?.Value("team")); + Team winnerTeam = ParseTeamFromString(messageJson["round"]?.Value("win_team")); + if(winnerTeam != Team.None && playerTeam == winnerTeam) + OnRoundWin?.Invoke(); + else if(winnerTeam != Team.None && playerTeam != winnerTeam) + OnRoundLoss?.Invoke(); + + int? previousDeaths = previously?["player"]?["match_stats"]?.Value("deaths"); + int? currentDeaths = messageJson["player"]?["match_stats"]?.Value("deaths"); + if(currentDeaths > previousDeaths) + OnDeath?.Invoke(); + + int? previousKills = previously?["player"]?["match_stats"]?.Value("kills"); + int? currentKills = messageJson["player"]?["match_stats"]?.Value("kills"); + if(currentKills > previousKills) + OnKill?.Invoke(); + } + + private RoundState ParseRoundStateFromString(string? str) + { + return str switch + { + "live" => RoundState.Live, + "freezetime" => RoundState.FreezeTime, + _ => RoundState.Unknown + }; + } + + private Team ParseTeamFromString(string? str) + { + return str switch + { + "T" => Team.T, + "CT" => Team.CT, + _ => Team.None + }; + } + + private enum RoundState {FreezeTime, Live, Unknown} + + private enum Team {T, CT, None} +} \ No newline at end of file diff --git a/OpenCS2hock/ConfiguredInteger.cs b/OpenCS2hock/ConfiguredInteger.cs new file mode 100644 index 0000000..a57567e --- /dev/null +++ b/OpenCS2hock/ConfiguredInteger.cs @@ -0,0 +1,17 @@ +namespace OpenCS2hock; + +public class ConfiguredInteger +{ + private readonly int _min, _max; + + public ConfiguredInteger(int min = 0, int max = 50) + { + this._min = min; + this._max = max; + } + + public int GetValue() + { + return Random.Shared.Next(_min, _max); + } +} \ No newline at end of file diff --git a/OpenCS2hock/Installer.cs b/OpenCS2hock/Installer.cs index da9a6e3..9a728c5 100644 --- a/OpenCS2hock/Installer.cs +++ b/OpenCS2hock/Installer.cs @@ -13,6 +13,16 @@ public static class Installer return JsonConvert.DeserializeObject(File.ReadAllText(settingsFilePath)); } + + public static List GetShockers(Settings settings) + { + List shockers = new(); + shockers.Add(new OpenShock(settings.OpenShockSettings.Endpoint, settings.OpenShockSettings.ApiKey, + settings.OpenShockSettings.Shockers, + new ConfiguredInteger(settings.IntensityRange.Min, settings.IntensityRange.Max), + new ConfiguredInteger(settings.DurationRange.Min, settings.DurationRange.Max))); + return shockers; + } public static void InstallGsi() { diff --git a/OpenCS2hock/OpenCS2hock.cs b/OpenCS2hock/OpenCS2hock.cs index 394d43d..95421b1 100644 --- a/OpenCS2hock/OpenCS2hock.cs +++ b/OpenCS2hock/OpenCS2hock.cs @@ -3,13 +3,18 @@ public class OpenCS2hock { private GSIServer GSIServer { get; init; } - private List _shockers = new(); + private readonly CS2MessageHandler _cs2MessageHandler; + private readonly List _shockers; private readonly Settings _settings; public OpenCS2hock(string? settingsPath = null) { _settings = Installer.GetSettings(settingsPath); + this._shockers = Installer.GetShockers(_settings); Installer.InstallGsi(); + + this._cs2MessageHandler = new CS2MessageHandler(); + this.GSIServer = new GSIServer(3000); this.GSIServer.OnMessage += OnGSIMessage; @@ -21,10 +26,42 @@ public class OpenCS2hock runningThread.Start(); } + private void SetupEventHandlers() + { + foreach (Shocker shocker in this._shockers) + { + foreach (KeyValuePair kv in _settings.Actions) + { + switch (kv.Key) + { + case "OnKill": + this._cs2MessageHandler.OnKill += () => shocker.Control(Settings.StringToAction(kv.Value)); + break; + case "OnDeath": + this._cs2MessageHandler.OnDeath += () => shocker.Control(Settings.StringToAction(kv.Value)); + break; + case "OnRoundStart": + this._cs2MessageHandler.OnRoundStart += () => shocker.Control(Settings.StringToAction(kv.Value)); + break; + case "OnRoundEnd": + this._cs2MessageHandler.OnRoundEnd += () => shocker.Control(Settings.StringToAction(kv.Value)); + break; + case "OnRoundLoss": + this._cs2MessageHandler.OnRoundLoss += () => shocker.Control(Settings.StringToAction(kv.Value)); + break; + case "OnRoundWin": + this._cs2MessageHandler.OnRoundWin += () => shocker.Control(Settings.StringToAction(kv.Value)); + break; + } + } + } + } + private void OnGSIMessage(string content) { string fileName = Path.Combine(Environment.CurrentDirectory, $"{DateTime.Now.ToLongTimeString().Replace(':','.')}.json"); File.WriteAllText(fileName, content); Console.WriteLine(fileName); + _cs2MessageHandler.HandleCS2Message(content); } } \ No newline at end of file diff --git a/OpenCS2hock/OpenCS2hock.csproj b/OpenCS2hock/OpenCS2hock.csproj index d7b8c2c..885795a 100644 --- a/OpenCS2hock/OpenCS2hock.csproj +++ b/OpenCS2hock/OpenCS2hock.csproj @@ -23,4 +23,8 @@ + + + + diff --git a/OpenCS2hock/OpenShock.cs b/OpenCS2hock/OpenShock.cs new file mode 100644 index 0000000..104609e --- /dev/null +++ b/OpenCS2hock/OpenShock.cs @@ -0,0 +1,51 @@ +using System.Net.Http.Headers; + +namespace OpenCS2hock; + +public class OpenShock : Shocker +{ + public override void Control(ControlAction action, string? shockerId = null) + { + if(shockerId is null) + foreach(string shocker in ShockerIds) + SendRequestMessage(action, shocker); + else + SendRequestMessage(action, shockerId); + } + + private void SendRequestMessage(ControlAction action, string shockerId) + { + HttpRequestMessage request = new (HttpMethod.Post, $"{Endpoint}/1/shockers/control") + { + Headers = + { + UserAgent = { new ProductInfoHeaderValue("OpenCS2hock", "1") }, + Accept = { new MediaTypeWithQualityHeaderValue("application/json") }, + Authorization = new AuthenticationHeaderValue("Basic", ApiKey) + }, + Content = new StringContent(@"[ { "+ + $"\"id\": \"{shockerId}\"," + + $"\"type\": {ControlActionToByte(action)},"+ + $"\"intensity\": {Intensity.GetValue()},"+ + $"\"duration\": {Duration.GetValue()}"+ + "}]") + }; + this.HttpClient.Send(request); + } + + private byte ControlActionToByte(ControlAction action) + { + return action switch + { + ControlAction.Beep => 3, + ControlAction.Vibrate => 2, + ControlAction.Shock => 1, + _ => 0 + }; + } + + public OpenShock(string endpoint, string apiKey, string[] shockerIds, ConfiguredInteger intensity, ConfiguredInteger duration) : base(endpoint, apiKey, shockerIds, intensity, duration) + { + + } +} \ No newline at end of file diff --git a/OpenCS2hock/Settings.cs b/OpenCS2hock/Settings.cs index d8f8994..fb6b640 100644 --- a/OpenCS2hock/Settings.cs +++ b/OpenCS2hock/Settings.cs @@ -9,9 +9,6 @@ public struct Settings Shockers = Array.Empty() }; - public short FixedIntensity = 30; - public short FixedDuration = 1000; - public Range IntensityRange = new () { Min = 0, @@ -24,22 +21,22 @@ public struct Settings Max = 2000 }; - public Actions Actions = new() + public Dictionary Actions = new() { - OnKill = "Nothing", - OnDeath = "Shock", - OnRoundStart = "Vibrate", - OnRoundEnd = "Nothing", - OnRoundWin = "Beep", - OnRoundLoss = "Nothing" + {"OnKill", "Nothing"}, + {"OnDeath", "Shock"}, + {"OnRoundStart", "Vibrate"}, + {"OnRoundEnd", "Nothing"}, + {"OnRoundWin", "Beep"}, + {"OnRoundLoss", "Nothing"} }; public Settings() { } - - internal Shocker.ControlAction StringToAction(string str) + + public static Shocker.ControlAction StringToAction(string str) { return str.ToLower() switch { @@ -60,9 +57,4 @@ public struct OpenShockSettings public struct Range { public short Min, Max; -} - -public struct Actions -{ - public string OnKill, OnDeath, OnRoundStart, OnRoundEnd, OnRoundWin, OnRoundLoss; } \ No newline at end of file diff --git a/OpenCS2hock/Shocker.cs b/OpenCS2hock/Shocker.cs index 7884f7f..90406f7 100644 --- a/OpenCS2hock/Shocker.cs +++ b/OpenCS2hock/Shocker.cs @@ -3,19 +3,21 @@ public abstract class Shocker { protected readonly HttpClient HttpClient; - protected readonly string ApiKey; - protected readonly string Endpoint; - protected string[] ShockerIds; + protected readonly string ApiKey,Endpoint; + protected readonly string[] ShockerIds; + protected readonly ConfiguredInteger Intensity, Duration; public enum ControlAction { Beep, Vibrate, Shock, Nothing } - public abstract void Control(ControlAction action, byte intensity, short duration, string? shockerId = null); + public abstract void Control(ControlAction action, string? shockerId = null); - protected Shocker(string endpoint, string apiKey, string[] shockerIds) + protected Shocker(string endpoint, string apiKey, string[] shockerIds, ConfiguredInteger intensity, ConfiguredInteger duration) { this.Endpoint = endpoint; this.ApiKey = apiKey; this.HttpClient = new HttpClient(); this.ShockerIds = shockerIds; + this.Intensity = intensity; + this.Duration = duration; } } \ No newline at end of file