commit 8aa772cc8e432238e308a87af355ed2549e9062e Author: Oshgnacknak Date: Wed Jul 23 00:33:05 2025 +0200 Initial Cult of the Shock plugin 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..c0bf13d --- /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..a864bc9 --- /dev/null +++ b/Hooks.cs @@ -0,0 +1,38 @@ +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(); + Plugin.Config.Reload(); + } + } +} diff --git a/Plugin.cs b/Plugin.cs new file mode 100644 index 0000000..bd91691 --- /dev/null +++ b/Plugin.cs @@ -0,0 +1,53 @@ +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")) + ); + } + } + +} + +// 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); + } + } + } +}