diff --git a/PropUndoButton/Main.cs b/PropUndoButton/Main.cs new file mode 100644 index 0000000..2d8bb3e --- /dev/null +++ b/PropUndoButton/Main.cs @@ -0,0 +1,147 @@ +using ABI_RC.Core.AudioEffects; +using ABI_RC.Core.Savior; +using ABI_RC.Core.Util; +using MelonLoader; +using System.Reflection; +using UnityEngine; + +namespace NAK.Melons.UndoPropButton; + +// https://pixabay.com/sound-effects/selection-sounds-73225/ + +public class PropUndoButton : MelonMod +{ + private static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(nameof(PropUndoButton)); + + public static readonly MelonPreferences_Entry EntryEnabled = + Category.CreateEntry("Enabled", true, description: "Toggle Undo Prop Button."); + + public static readonly MelonPreferences_Entry EntryUseSFX = + Category.CreateEntry("Use SFX", true, description: "Enable or disable undo SFX."); + + // audio clip names, InterfaceAudio adds "PropUndo_" prefix + public const string sfx_spawn = "PropUndo_sfx_spawn"; + public const string sfx_undo = "PropUndo_sfx_undo"; + public const string sfx_warn = "PropUndo_sfx_warn"; + + public override void OnInitializeMelon() + { + HarmonyInstance.Patch( // prop spawn sfx + typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.SpawnProp)), + null, + new HarmonyLib.HarmonyMethod(typeof(PropUndoButton).GetMethod(nameof(OnSpawnProp), BindingFlags.NonPublic | BindingFlags.Static)) + ); + HarmonyInstance.Patch( // desktop input patch so we don't run in menus/gui + typeof(InputModuleMouseKeyboard).GetMethod(nameof(InputModuleMouseKeyboard.UpdateInput)), + null, + new HarmonyLib.HarmonyMethod(typeof(PropUndoButton).GetMethod(nameof(OnUpdateInput), BindingFlags.NonPublic | BindingFlags.Static)) + ); + SetupDefaultAudioClips(); + } + + private void SetupDefaultAudioClips() + { + // PropUndo and audio folders do not exist, create them if dont exist yet + string path = Application.streamingAssetsPath + "/Cohtml/UIResources/GameUI/mods/PropUndo/audio/"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + LoggerInstance.Msg("Created PropUndo/audio directory!"); + } + + // copy embedded resources to this folder if they do not exist + string[] clipNames = { "sfx_spawn.wav", "sfx_undo.wav", "sfx_warn.wav" }; + foreach (string clipName in clipNames) + { + string clipPath = Path.Combine(path, clipName); + if (!File.Exists(clipPath)) + { + // read the clip data from embedded resources + byte[] clipData = null; + string resourceName = "PropUndoButton.SFX." + clipName; + using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)) + { + clipData = new byte[stream.Length]; + stream.Read(clipData, 0, clipData.Length); + } + + // write the clip data to the file + using (FileStream fileStream = new FileStream(clipPath, FileMode.CreateNew)) + { + fileStream.Write(clipData, 0, clipData.Length); + } + + LoggerInstance.Msg("Placed missing sfx in audio folder: " + clipName); + } + } + } + + private static void OnUpdateInput() + { + if (Input.GetKeyDown(KeyCode.Z)) + { + DeleteLatestSpawnable(); + } + } + + private static void OnSpawnProp() + { + if (!EntryEnabled.Value) return; + + if (!MetaPort.Instance.worldAllowProps || !MetaPort.Instance.settings.GetSettingsBool("ContentFilterPropsEnabled", false)) + { + PlayAudioModule(sfx_warn); + return; + } + + if (GetAllProps().Count >= 20) + { + PlayAudioModule(sfx_warn); + return; + } + + PlayAudioModule(sfx_spawn); + } + + private static void DeleteLatestSpawnable() + { + if (!EntryEnabled.Value) return; + + var propData = GetLatestProp(); + + if (propData == null) + { + PlayAudioModule(sfx_warn); + return; + } + + if (propData.Spawnable == null) + { + propData.Recycle(); + // what should i do here? + return; + } + + propData.Spawnable.Delete(); + PlayAudioModule(sfx_undo); + } + + private static void PlayAudioModule(string module) + { + if (!EntryUseSFX.Value) return; + InterfaceAudio.PlayModule(module); + } + + private static CVRSyncHelper.PropData GetLatestProp() + { + // should already be sorted by spawn order + return CVRSyncHelper.Props.LastOrDefault((CVRSyncHelper.PropData match) => match.SpawnedBy == MetaPort.Instance.ownerId); + } + + private static List GetAllProps() + { + // im not storing the count because there is good chance itll desync from server + return CVRSyncHelper.Props.FindAll((CVRSyncHelper.PropData match) => match.SpawnedBy == MetaPort.Instance.ownerId); + } +} \ No newline at end of file diff --git a/PropUndoButton/PropUndoButton.csproj b/PropUndoButton/PropUndoButton.csproj new file mode 100644 index 0000000..fda9805 --- /dev/null +++ b/PropUndoButton/PropUndoButton.csproj @@ -0,0 +1,50 @@ + + + + + net472 + enable + latest + false + True + + + + + + + + + + + + + + + + + C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\MelonLoader\0Harmony.dll + + + C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll + + + C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll + + + C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\MelonLoader\MelonLoader.dll + + + C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll + + + C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.InputLegacyModule.dll + + + + + + + + + diff --git a/PropUndoButton/PropUndoButton.sln b/PropUndoButton/PropUndoButton.sln new file mode 100644 index 0000000..0d15d70 --- /dev/null +++ b/PropUndoButton/PropUndoButton.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32630.192 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PropUndoButton", "PropUndoButton.csproj", "{240E33F2-E4DD-458D-8E79-96073872CE65}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {240E33F2-E4DD-458D-8E79-96073872CE65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {240E33F2-E4DD-458D-8E79-96073872CE65}.Debug|Any CPU.Build.0 = Debug|Any CPU + {240E33F2-E4DD-458D-8E79-96073872CE65}.Release|Any CPU.ActiveCfg = Release|Any CPU + {240E33F2-E4DD-458D-8E79-96073872CE65}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C8F281CB-C5D8-4C4B-A7F6-9F1702C436EB} + EndGlobalSection +EndGlobal diff --git a/PropUndoButton/Properties/AssemblyInfo.cs b/PropUndoButton/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..0249734 --- /dev/null +++ b/PropUndoButton/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using NAK.Melons.UndoPropButton.Properties; +using MelonLoader; +using System.Reflection; + + +[assembly: AssemblyVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] +[assembly: AssemblyTitle(nameof(NAK.Melons.UndoPropButton))] +[assembly: AssemblyCompany(AssemblyInfoParams.Author)] +[assembly: AssemblyProduct(nameof(NAK.Melons.UndoPropButton))] + +[assembly: MelonInfo( + typeof(NAK.Melons.UndoPropButton.PropUndoButton), + nameof(NAK.Melons.UndoPropButton), + AssemblyInfoParams.Version, + AssemblyInfoParams.Author, + downloadLink: "https://github.com/NotAKidOnSteam/UndoPropButton" +)] + +[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] +[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] +[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] +[assembly: MelonOptionalDependencies("BTKUILib")] +[assembly: HarmonyDontPatchAll] + +namespace NAK.Melons.UndoPropButton.Properties; +internal static class AssemblyInfoParams +{ + public const string Version = "1.0.0"; + public const string Author = "NotAKidoS"; +} \ No newline at end of file diff --git a/PropUndoButton/SFX/sfx_spawn.wav b/PropUndoButton/SFX/sfx_spawn.wav new file mode 100644 index 0000000..069a478 Binary files /dev/null and b/PropUndoButton/SFX/sfx_spawn.wav differ diff --git a/PropUndoButton/SFX/sfx_undo.wav b/PropUndoButton/SFX/sfx_undo.wav new file mode 100644 index 0000000..0a0fc43 Binary files /dev/null and b/PropUndoButton/SFX/sfx_undo.wav differ diff --git a/PropUndoButton/SFX/sfx_warn.wav b/PropUndoButton/SFX/sfx_warn.wav new file mode 100644 index 0000000..d27b30a Binary files /dev/null and b/PropUndoButton/SFX/sfx_warn.wav differ diff --git a/PropUndoButton/format.json b/PropUndoButton/format.json new file mode 100644 index 0000000..b243e12 --- /dev/null +++ b/PropUndoButton/format.json @@ -0,0 +1,23 @@ +{ + "_id": -1, + "name": "PropUndoButton", + "modversion": "1.0.0", + "gameversion": "2022r170", + "loaderversion": "0.5.7", + "modtype": "Mod", + "author": "NotAKidoS", + "description": "Press Z to undo latest spawned prop. Includes SFX for prop spawn, undo, and warning, which can be disabled in settings.", + "searchtags": [ + "prop", + "undo", + "bind", + "button" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidOnSteam/PropUndoButton/releases/download/v1.0.0/PropUndoButton.dll", + "sourcelink": "https://github.com/NotAKidOnSteam/PropUndoButton/", + "changelog": "- Initial Release", + "embedcolor": "#00FFFF" +} \ No newline at end of file