From 2ac072939aa607aeeb6874b0f9eb0868fc171b51 Mon Sep 17 00:00:00 2001 From: Oshgnacknak Date: Wed, 23 Jul 2025 00:33:05 +0200 Subject: [PATCH] Initial Cult of the Shock plugin --- .gitignore | 2 + Cult_of_the_Shock.csproj | 29 +++++++++ Cult_of_the_Shock.sln | 24 +++++++ Hooks.cs | 37 +++++++++++ Main.cs | 40 ++++++++++++ Main.exe | Bin 0 -> 4608 bytes Plugin.cs | 64 ++++++++++++++++++ ShockConfig.cs | 137 +++++++++++++++++++++++++++++++++++++++ Shocker.cs | 37 +++++++++++ 9 files changed, 370 insertions(+) create mode 100644 .gitignore create mode 100644 Cult_of_the_Shock.csproj create mode 100644 Cult_of_the_Shock.sln create mode 100644 Hooks.cs create mode 100644 Main.cs create mode 100644 Main.exe create mode 100644 Plugin.cs create mode 100644 ShockConfig.cs create mode 100644 Shocker.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d4a6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin +obj \ No newline at end of file diff --git a/Cult_of_the_Shock.csproj b/Cult_of_the_Shock.csproj new file mode 100644 index 0000000..2b21c0f --- /dev/null +++ b/Cult_of_the_Shock.csproj @@ -0,0 +1,29 @@ + + + + net472 + Cult_of_the_Shock + Cult of the Shock + 1.0.0 + true + latest + + https://api.nuget.org/v3/index.json; + https://nuget.bepinex.dev/v3/index.json; + https://nuget.samboy.dev/v3/index.json + + Cult_of_the_Shock + + + + + + + + + + + + + + diff --git a/Cult_of_the_Shock.sln b/Cult_of_the_Shock.sln new file mode 100644 index 0000000..dcb5b30 --- /dev/null +++ b/Cult_of_the_Shock.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cult of the Shock", "Cult of the Shock.csproj", "{CEAACF8E-CA24-1B25-FC4E-1D718312D8A1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CEAACF8E-CA24-1B25-FC4E-1D718312D8A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CEAACF8E-CA24-1B25-FC4E-1D718312D8A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CEAACF8E-CA24-1B25-FC4E-1D718312D8A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CEAACF8E-CA24-1B25-FC4E-1D718312D8A1}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2B7C2765-F52A-4A8D-BA5E-B4CBF9C9C57B} + EndGlobalSection +EndGlobal diff --git a/Hooks.cs b/Hooks.cs new file mode 100644 index 0000000..a204d64 --- /dev/null +++ b/Hooks.cs @@ -0,0 +1,37 @@ +using HarmonyLib; +using UnityEngine; +using static Health; + +namespace Cult_of_the_Shock +{ + + [HarmonyPatch] + public class Hooks + { + private static Shocker Shocker; + + public static void OnDamage(GameObject attacker, Vector3 attackLocation, float damage, AttackTypes attackType, AttackFlags attackFlag) + { + Plugin.Logger.LogInfo($"PlayerController.Health.OnDamage: " + damage); + Shocker.Shock(Plugin.Config.GetDamageJson()); + } + + public static void OnDie(GameObject Attacker, Vector3 AttackLocation, Health Victim, AttackTypes AttackType, AttackFlags AttackFlags) + { + Plugin.Logger.LogInfo($"PlayerController.Health.OnDie"); + Shocker.Shock(Plugin.Config.GetDeathJson()); + } + + public static void OnHit(GameObject Attacker, Vector3 AttackLocation, AttackTypes AttackType, bool FromBehind) + { + Plugin.Logger.LogInfo($"OnHit: " + Attacker); + } + + public static void OnEnable(PlayerController __instance) + { + __instance.health.OnDamaged += OnDamage; + __instance.health.OnDie += OnDie; + Shocker = new Shocker(); + } + } +} diff --git a/Main.cs b/Main.cs new file mode 100644 index 0000000..b67dc0d --- /dev/null +++ b/Main.cs @@ -0,0 +1,40 @@ +using System; +using System.Reflection; + +class Program +{ + static void Main(string[] args) + { + if (args.Length < 1) + { + Console.WriteLine("Usage: ListAdd "); + return; + } + + foreach (var path in args) + { + Assembly asm; + try + { + asm = Assembly.LoadFrom(path); + } + catch (Exception e) + { + Console.WriteLine($"ERROR: {path}: {e.Message}"); + continue; + } + + foreach (var t in asm.GetTypes()) + { + foreach (var m in t.GetMethods( + BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.Instance | BindingFlags.Static | + BindingFlags.DeclaredOnly)) + { + + Console.WriteLine($"{path}: {t.FullName}.{m.Name}"); + } + } + } + } +} diff --git a/Main.exe b/Main.exe new file mode 100644 index 0000000000000000000000000000000000000000..c44eacbbcb6e8e5806af8f49510cdd4245945f00 GIT binary patch literal 4608 zcmeHKO>7&-75bYgltQbYpaooTt2*ay6hUFM@t9hg1mPEF@J9)O@dTkz9*4pr!#qVr4 zXL|=}bZR*uL5XaHzQ>3RwrMtr40e$)Zc-|K*<+85atUpi{yy-q`fK$KHZ;MT8DLe? z)4)qI37>{dpzG7J`{9kl(3N$9{If^KWdpGrkbg8rM4eh^BW`jE3}7?s`CgHnifx*N zv`?}ffoVpc^@Q!xP{q1QrS!$6ABAaP_9!pue{lU0WNZ<%!yW{#F%KGfHaXjW1t)V+ ze8dqPt0QvT>i;B~j6JEtqo6uO$4QL;S$d3g_H~WFK_Z^G(+zYCwNuv{#a}bM?eH$q zy2Hy8>+LRsNDjp%QsdvgCCBSgmq#Pn8|&+hpR4LIJ$|Q`0zGm1qT3-A9eusYbI zdh$+hCre@!z`M?w_A_75J6)P3_lKCq#%$1&{nvVv zdQ$JWZ9Vle^f=kL9@k@JZz0ittt%0~ZB2do;Naj1PN8ys4qZCAV5Ke7Lwr1ODiIb~m3tn)+NWPj#r97qJN?Ab!GDre5s*b!Q;w97M&;{%TYIpPvS?+XmpR09)q{Y@!TCkzV-P{thl9` zEiR(s7hTV`3Mh-<#)|NLvn+7Qb$r(rN_N?-gv3J4wiWZ7X9Z%}as<}vRe?ECsFllR z!4@+Ccf3#wq=QP;vdzG9oh!mNcNOKg^jB(5U{!=PqpD!pR#0zgS4_*vid}*51Ys>K z%gERLKvb~onxzHLtw7o}gxM=%%N9jBA?A0BqN=DB5p27qC1=aUil`KXr^swEaJ}ZZ zIm;|Nt{+%MAB!RgNBB)`BhGBN9^vJMw__EB-&iz{H}goABV{gp%vw%~(goY(!8bi3 z=CWu8SS@^m;bF%28Hin%doptsv_TmH7hqtS`|N`zDDh`tf;yi+feQE9Mx8rdVsEpT zzl*>_^SHv_Dx36xc7@xrYR6xF_l;Zocb}N~)z9DmOY(0oe2A!_X^A!iT003{?~ivy zPNfp5o`IfIr+NkxJ%guuGKro{6q=T5ht@NYqH!vYXi7r60b0ckjaK>y^(hX1^;^$x z42-?=L$VrKt#RjlQGOHak-q0VvwGQe4mV_d+jC#=HQL2B732Gj8ZMdGV?>6eJ`2rS zA8Kw~a=rOop|)S0Um>z3Th<`H1TrwD74-o9KV=}I?i7Qss1scFTKJkel7O&gkKaQs zjrNXMhGb~J{TWqwZeLJ^H~X z&6}h-R`yd3Mpo=_mWF`coRV#@B6##;u?7hGbg%_i8J&e3wN9-`sZYJhN@7v#C_V6U z^xJf*F{Xf47Uku8c)F%+a=dlI-xw=Tjvk)mJ*FMn&vAAiMdhb@Y#xKQITnGI?FMZ< zTDM}!v39i@a8~)2s|l2?!`N-+%VgaX^e6K&q->VSmzDMsS5{?3lq)G?nj}7T$D?hXpfX^udM)<$TiLZQ4 M{vWvgpU%L)04>TtDF6Tf literal 0 HcmV?d00001 diff --git a/Plugin.cs b/Plugin.cs new file mode 100644 index 0000000..0d5791a --- /dev/null +++ b/Plugin.cs @@ -0,0 +1,64 @@ +using UnityEngine; +using System.Reflection; +using BepInEx; +using BepInEx.Logging; +using HarmonyLib; +using Unity.VideoHelper; + +namespace Cult_of_the_Shock +{ + + [BepInPlugin(MyPluginInfo.PLUGIN_GUID, MyPluginInfo.PLUGIN_NAME, MyPluginInfo.PLUGIN_VERSION)] + public class Plugin : BaseUnityPlugin + { + internal static new ManualLogSource Logger; + + internal static new ShockConfig Config; + + internal readonly static Harmony Harmony = new Harmony(MyPluginInfo.PLUGIN_GUID); + + public void Awake() + { + Logger = base.Logger; + Config = new ShockConfig(base.Config); + + Logger.LogInfo($"Plugin {MyPluginInfo.PLUGIN_GUID} is loaded! Prepare for a shocking experiance..."); + + PatchMethods(); + } + + private void PatchMethods() + { + MethodInfo method = null; + + foreach (var m in typeof(PlayerController).GetRuntimeMethods()) + { + if (m.Name == "OnEnable") + { + method = m; + break; + } + } + + Logger.LogDebug("A possible method: " + method); + Harmony.Patch( + method, + postfix: new HarmonyMethod(typeof(Hooks).GetMethod("OnEnable")) + ); + } + + public void Update() + { + Logger.LogInfo("Update"); + + if (Input.GetKeyDown(KeyCode.F5)) + { + Logger.LogInfo("Reloading config... "); + base.Config.Reload(); + } + } + } + +} + +// TODO: https://github.com/InfernoDragon0/CotLMods/blob/4a157380362dccb1d8564368367ac3588eb5fa06/Patches/KnucklebonesPatch.cs#L54 diff --git a/ShockConfig.cs b/ShockConfig.cs new file mode 100644 index 0000000..e66fbf9 --- /dev/null +++ b/ShockConfig.cs @@ -0,0 +1,137 @@ +using BepInEx.Configuration; +using System.Collections.Generic; +using System.Linq; + +namespace Cult_of_the_Shock +{ + public class ShockConfig + { + private static HashSet ACTIONS = new HashSet { "Shock", "Vibrate", "Sound" }; + + private readonly ConfigFile config; + + private readonly ConfigEntry url; + private readonly ConfigEntry token; + private readonly ConfigEntry shockerIds; + + private readonly ConfigEntry deathAction; + private readonly ConfigEntry deathIntensity; + private readonly ConfigEntry deathDuration; + + private readonly ConfigEntry damageAction; + private readonly ConfigEntry damageIntensity; + private readonly ConfigEntry damageDuration; + + public ShockConfig(ConfigFile config) + { + this.config = config; + + url = config.Bind("Shocker", + "url", + "https://api.openshock.app", + "The base url of your api (without trailing slash)"); + token = config.Bind("Shocker", + "token", + "secrettoken", + "Your API token"); + shockerIds = config.Bind("Shocker", + "shockerIds", + "uuid1,uuid2", + "Ids of your shockers (separated by commas, to spaces)"); + + deathAction = config.Bind("Death", + "action", + "Nothing", + "What to do on death (Nothing, Shock, Vibrate, Sound)"); + deathIntensity = config.Bind("Death", + "intensity", + "1", + "Intensity on death (1 - 100)"); + deathDuration = config.Bind("Death", + "duration", + "300", + "Duration on death in milliseconds (min 300)"); + + damageAction = config.Bind("Damage", + "action", + "Nothing", + "What to do on damage (Nothing, Shock, Vibrate, Sound)"); + damageIntensity = config.Bind("Damage", + "intensity", + "1", + "Intensity on death (1 - 100)"); + damageDuration = config.Bind("Damage", + "duration", + "300", + "Duration on damage in milliseconds (min 300)"); + + config.ConfigReloaded += (_, _) => OnReload(); + } + + private void OnReload() + { + Plugin.Logger.LogInfo($"Config reloaded"); + } + + public string GetDeathJson() + { + if (!ACTIONS.Contains(deathAction.Value)) + { + return null; + } + + var controls = GetIds().Select(id => + $@"{{ + ""id"": ""{id}"", + ""type"": ""{deathAction.Value}"", + ""intensity"": {deathIntensity.Value}, + ""duration"": {deathDuration.Value}, + ""exclusive"": false + }}" + ); + + return "[" + string.Join(",\n", controls) + "]"; + } + + public string GetDamageJson() + { + if (!ACTIONS.Contains(damageAction.Value)) + { + return null; + } + + var controls = GetIds().Select(id => + $@"{{ + ""id"": ""{id}"", + ""type"": ""{damageAction.Value}"", + ""intensity"": {damageIntensity.Value}, + ""duration"": {damageDuration.Value}, + ""exclusive"": false + }}" + ); + + return "[" + string.Join(",\n", controls) + "]"; + } + + public void Reload() + { + config.Reload(); + } + + private string[] GetIds() + { + return shockerIds.Value.Split(','); + } + + public string GetToken() + { + return token.Value; + } + + public string GetURL() + { + return url.Value; + } + } + +} diff --git a/Shocker.cs b/Shocker.cs new file mode 100644 index 0000000..92aff13 --- /dev/null +++ b/Shocker.cs @@ -0,0 +1,37 @@ +using System; +using System.Text; +using System.Threading; +using BepInEx.Configuration; +using UnityEngine.Networking; + +namespace Cult_of_the_Shock +{ + public class Shocker + { + public void Shock(string json) + { + var uwr = new UnityWebRequest(Plugin.Config.GetURL() + "/1/shockers/control", "POST"); + uwr.SetTimeoutMsec(1000); + + uwr.SetRequestHeader("OpenShockToken", Plugin.Config.GetToken()); + uwr.SetRequestHeader("Content-Type", "application/json"); + + byte[] requestData = new UTF8Encoding().GetBytes(json); + uwr.uploadHandler = new UploadHandlerRaw(requestData); + uwr.downloadHandler = new DownloadHandlerBuffer(); + + uwr.SendWebRequest(); + while (!uwr.isDone) + { + Thread.Yield(); + } + + if (uwr.result != UnityWebRequest.Result.Success) + { + Plugin.Logger.LogError("Error while Sending Request: " + uwr.error); + Plugin.Logger.LogError(uwr.downloadHandler.text); + Plugin.Logger.LogError("Sent JSON: " + json); + } + } + } +}