diff --git a/PropUndoButton/Main.cs b/PropUndoButton/Main.cs index 105616a..7dc2791 100644 --- a/PropUndoButton/Main.cs +++ b/PropUndoButton/Main.cs @@ -1,6 +1,9 @@ using ABI_RC.Core.AudioEffects; +using ABI_RC.Core.Networking; using ABI_RC.Core.Savior; +using ABI_RC.Core.UI; using ABI_RC.Core.Util; +using DarkRift; using MelonLoader; using System.Reflection; using UnityEngine; @@ -11,6 +14,8 @@ namespace NAK.Melons.PropUndoButton; public class PropUndoButton : MelonMod { + public static List deletedProps = new List(); + private static readonly MelonPreferences_Category Category = MelonPreferences.CreateCategory(nameof(PropUndoButton)); @@ -23,19 +28,22 @@ public class PropUndoButton : MelonMod // 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_redo = "PropUndo_sfx_redo"; 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)) + postfix: new HarmonyLib.HarmonyMethod(typeof(PropUndoButton).GetMethod(nameof(OnSpawnProp), BindingFlags.NonPublic | BindingFlags.Static)) + ); + HarmonyInstance.Patch( // prop delete sfx + typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.DeletePropByInstanceId)), + postfix: new HarmonyLib.HarmonyMethod(typeof(PropUndoButton).GetMethod(nameof(OnDeletePropByInstanceId), 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)) + postfix: new HarmonyLib.HarmonyMethod(typeof(PropUndoButton).GetMethod(nameof(OnUpdateInput), BindingFlags.NonPublic | BindingFlags.Static)) ); SetupDefaultAudioClips(); } @@ -51,7 +59,7 @@ public class PropUndoButton : MelonMod } // copy embedded resources to this folder if they do not exist - string[] clipNames = { "sfx_spawn.wav", "sfx_undo.wav", "sfx_warn.wav" }; + string[] clipNames = { "sfx_spawn.wav", "sfx_undo.wav", "sfx_redo.wav", "sfx_warn.wav" }; foreach (string clipName in clipNames) { string clipPath = Path.Combine(path, clipName); @@ -79,9 +87,16 @@ public class PropUndoButton : MelonMod private static void OnUpdateInput() { - if (Input.GetKeyDown(KeyCode.Z)) + if (Input.GetKey(KeyCode.LeftControl)) { - DeleteLatestSpawnable(); + if (Input.GetKey(KeyCode.LeftShift) && Input.GetKeyDown(KeyCode.Z)) + { + RedoProp(); + } + else if (Input.GetKeyDown(KeyCode.Z)) + { + UndoProp(); + } } } @@ -95,7 +110,8 @@ public class PropUndoButton : MelonMod return; } - if (GetAllProps().Count >= 20) + // might need to touch + if (IsAtPropLimit()) { PlayAudioModule(sfx_warn); return; @@ -104,12 +120,26 @@ public class PropUndoButton : MelonMod PlayAudioModule(sfx_spawn); } - private static void DeleteLatestSpawnable() + private static void OnDeletePropByInstanceId(string instanceId) + { + if (string.IsNullOrEmpty(instanceId)) return; + + CVRSyncHelper.PropData propData = GetPropByInstanceId(instanceId); + if (propData == null) return; + + // Add the spawned prop to the history of deleted props + if (deletedProps.Count >= 5) deletedProps.RemoveAt(0); // Remove the oldest item + DeletedProp deletedProp = new DeletedProp(propData.ObjectId, propData.Spawnable.transform.position, propData.Spawnable.transform.rotation); + deletedProps.Add(deletedProp); + + PlayAudioModule(sfx_undo); + } + + private static void UndoProp() { if (!EntryEnabled.Value) return; var propData = GetLatestProp(); - if (propData == null) { PlayAudioModule(sfx_warn); @@ -124,15 +154,86 @@ public class PropUndoButton : MelonMod } propData.Spawnable.Delete(); - PlayAudioModule(sfx_undo); } - private static void PlayAudioModule(string module) + public static void RedoProp() + { + int index = deletedProps.Count - 1; + if (index < 0 || index >= deletedProps.Count || IsAtPropLimit()) + { + PlayAudioModule(sfx_warn); + return; + } + + DeletedProp deletedProp = deletedProps[index]; + if (Time.time - deletedProp.timeDeleted <= 60) // only allow redo of prop spawned in last minute + { + if (AttemptRedoProp(deletedProp.propGuid, deletedProp.position, deletedProp.rotation)) + { + deletedProps.RemoveAt(index); + PlayAudioModule(sfx_redo); + return; + } + } + + PlayAudioModule(sfx_warn); + } + + // original spawn prop method does not let you specify rotation + public static bool AttemptRedoProp(string propGuid, Vector3 position, Quaternion quaternion) + { + if (MetaPort.Instance.worldAllowProps + && MetaPort.Instance.settings.GetSettingsBool("ContentFilterPropsEnabled", false) + && NetworkManager.Instance.GameNetwork.ConnectionState == ConnectionState.Connected) + { + using (DarkRiftWriter darkRiftWriter = DarkRiftWriter.Create()) + { + darkRiftWriter.Write(propGuid); + darkRiftWriter.Write(position.x); + darkRiftWriter.Write(position.y); + darkRiftWriter.Write(position.z); + darkRiftWriter.Write(0f); + darkRiftWriter.Write(quaternion.eulerAngles.y); + darkRiftWriter.Write(0f); + darkRiftWriter.Write(1f); + darkRiftWriter.Write(1f); + darkRiftWriter.Write(1f); + darkRiftWriter.Write(0f); + using (Message message = Message.Create(10050, darkRiftWriter)) + { + NetworkManager.Instance.GameNetwork.SendMessage(message, SendMode.Reliable); + } + return true; + } + } + else + { + if (!MetaPort.Instance.worldAllowProps) + { + CohtmlHud.Instance.ViewDropText("Props are not allowed in this world", ""); + } + return false; + } + } + + public static void PlayAudioModule(string module) { if (!EntryUseSFX.Value) return; InterfaceAudio.PlayModule(module); } + public static bool IsAtPropLimit() + { + // might need rework + return GetAllProps().Count >= 20; + } + + private static CVRSyncHelper.PropData GetPropByInstanceId(string instanceId) + { + // find prop by instance id and if it is ours + return CVRSyncHelper.Props.Find((CVRSyncHelper.PropData match) => (match.InstanceId == instanceId && match.SpawnedBy == MetaPort.Instance.ownerId)); + } + private static CVRSyncHelper.PropData GetLatestProp() { // should already be sorted by spawn order @@ -144,4 +245,20 @@ public class PropUndoButton : MelonMod // 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); } + + public class DeletedProp + { + public string propGuid; + public Vector3 position; + public Quaternion rotation; + public float timeDeleted; + + public DeletedProp(string propGuid, Vector3 position, Quaternion rotation) + { + this.propGuid = propGuid; + this.position = position; + this.rotation = rotation; + this.timeDeleted = Time.time; + } + } } \ No newline at end of file diff --git a/PropUndoButton/PropUndoButton.csproj b/PropUndoButton/PropUndoButton.csproj index fda9805..e6a31c9 100644 --- a/PropUndoButton/PropUndoButton.csproj +++ b/PropUndoButton/PropUndoButton.csproj @@ -10,12 +10,14 @@ + + @@ -31,6 +33,9 @@ C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp-firstpass.dll + + ..\..\..\_ManagedLibs\DarkRift.dll + C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\MelonLoader\MelonLoader.dll diff --git a/PropUndoButton/Properties/AssemblyInfo.cs b/PropUndoButton/Properties/AssemblyInfo.cs index fc06330..a20c993 100644 --- a/PropUndoButton/Properties/AssemblyInfo.cs +++ b/PropUndoButton/Properties/AssemblyInfo.cs @@ -20,12 +20,11 @@ using System.Reflection; [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.PropUndoButton.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.0"; + public const string Version = "1.0.1"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/PropUndoButton/SFX/sfx_redo.wav b/PropUndoButton/SFX/sfx_redo.wav new file mode 100644 index 0000000..cdc632a Binary files /dev/null and b/PropUndoButton/SFX/sfx_redo.wav differ diff --git a/PropUndoButton/format.json b/PropUndoButton/format.json index fa4d906..8643aa7 100644 --- a/PropUndoButton/format.json +++ b/PropUndoButton/format.json @@ -1,7 +1,7 @@ { "_id": -1, "name": "PropUndoButton", - "modversion": "1.0.0", + "modversion": "1.0.1", "gameversion": "2022r170", "loaderversion": "0.5.7", "modtype": "Mod", @@ -16,8 +16,8 @@ "requirements": [ "None" ], - "downloadlink": "https://github.com/NotAKidOnSteam/PropUndoButton/releases/download/v1.0.0/PropUndoButton.dll", + "downloadlink": "https://github.com/NotAKidOnSteam/PropUndoButton/releases/download/v1.0.1/PropUndoButton.dll", "sourcelink": "https://github.com/NotAKidOnSteam/PropUndoButton/", - "changelog": "- Initial Release", + "changelog": "- Initial Release\n- Added redo button.", "embedcolor": "#00FFFF" } \ No newline at end of file