mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2026-04-12 21:07:01 +00:00
287 lines
No EOL
11 KiB
C#
287 lines
No EOL
11 KiB
C#
using System.Reflection;
|
|
using ABI_RC.Core.InteractionSystem;
|
|
using ABI_RC.Core.Player;
|
|
using ABI_RC.Core.Util;
|
|
using ABI_RC.Systems.InputManagement.InputModules;
|
|
using ABI_RC.Systems.UI.UILib;
|
|
using ABI.CCK.Components;
|
|
using HarmonyLib;
|
|
using MelonLoader;
|
|
using UnityEngine;
|
|
|
|
namespace NAK.PropsButBetter;
|
|
|
|
public class PropsButBetterMod : MelonMod
|
|
{
|
|
internal static MelonLogger.Instance Logger;
|
|
|
|
public override void OnInitializeMelon()
|
|
{
|
|
Logger = LoggerInstance;
|
|
|
|
// Replace prop select method
|
|
HarmonyInstance.Patch(
|
|
typeof(ControllerRay).GetMethod(nameof(ControllerRay.HandleSpawnableClicked),
|
|
BindingFlags.NonPublic | BindingFlags.Instance),
|
|
prefix: new HarmonyMethod(typeof(PropsButBetterMod).GetMethod(nameof(OnPreHandleSpawnableClicked),
|
|
BindingFlags.NonPublic | BindingFlags.Static))
|
|
);
|
|
// Replace player select method
|
|
HarmonyInstance.Patch(
|
|
typeof(ControllerRay).GetMethod(nameof(ControllerRay.HandlePlayerClicked),
|
|
BindingFlags.NonPublic | BindingFlags.Instance),
|
|
prefix: new HarmonyMethod(typeof(PropsButBetterMod).GetMethod(nameof(OnPreHandlePlayerClicked),
|
|
BindingFlags.NonPublic | BindingFlags.Static))
|
|
);
|
|
|
|
// Desktop keybindings for undo/redo
|
|
HarmonyInstance.Patch(
|
|
typeof(CVRInputModule_Keyboard).GetMethod(nameof(CVRInputModule_Keyboard.Update_Binds),
|
|
BindingFlags.Public | BindingFlags.Instance),
|
|
postfix: new HarmonyMethod(typeof(PropsButBetterMod).GetMethod(nameof(OnUpdateKeyboardBinds),
|
|
BindingFlags.NonPublic | BindingFlags.Static))
|
|
);
|
|
|
|
HarmonyInstance.Patch( // delete my props in reverse order for redo
|
|
typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.DeleteMyProps),
|
|
BindingFlags.Public | BindingFlags.Static),
|
|
prefix: new HarmonyMethod(typeof(PropHelper).GetMethod(nameof(PropHelper.OnPreDeleteMyProps),
|
|
BindingFlags.Public | BindingFlags.Static))
|
|
);
|
|
HarmonyInstance.Patch( // delete all props in reverse order for redo
|
|
typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.DeleteAllProps),
|
|
BindingFlags.Public | BindingFlags.Static),
|
|
prefix: new HarmonyMethod(typeof(PropHelper).GetMethod(nameof(PropHelper.OnPreDeleteAllProps),
|
|
BindingFlags.Public | BindingFlags.Static))
|
|
);
|
|
HarmonyInstance.Patch( // prop spawn sfx
|
|
typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.SpawnProp),
|
|
BindingFlags.Public | BindingFlags.Static),
|
|
postfix: new HarmonyMethod(typeof(PropHelper).GetMethod(nameof(PropHelper.OnTrySpawnProp),
|
|
BindingFlags.Public | BindingFlags.Static))
|
|
);
|
|
|
|
HarmonyInstance.Patch(
|
|
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.SelectPropToSpawn),
|
|
BindingFlags.Public | BindingFlags.Instance),
|
|
postfix: new HarmonyMethod(typeof(PropHelper).GetMethod(nameof(PropHelper.OnSelectPropToSpawn),
|
|
BindingFlags.Public | BindingFlags.Static))
|
|
);
|
|
|
|
HarmonyInstance.Patch(
|
|
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.EnterPropDeleteMode),
|
|
BindingFlags.Public | BindingFlags.Instance),
|
|
postfix: new HarmonyMethod(typeof(PropHelper).GetMethod(nameof(PropHelper.OnClearPropToSpawn),
|
|
BindingFlags.Public | BindingFlags.Static))
|
|
);
|
|
|
|
HarmonyInstance.Patch(
|
|
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearPropToSpawn),
|
|
BindingFlags.Public | BindingFlags.Instance),
|
|
postfix: new HarmonyMethod(typeof(PropHelper).GetMethod(nameof(PropHelper.OnClearPropToSpawn),
|
|
BindingFlags.Public | BindingFlags.Static))
|
|
);
|
|
|
|
HarmonyInstance.Patch(
|
|
typeof(ControllerRay).GetMethod(nameof(ControllerRay.HandlePropSpawn),
|
|
BindingFlags.NonPublic | BindingFlags.Instance),
|
|
postfix: new HarmonyMethod(typeof(PropHelper).GetMethod(nameof(PropHelper.OnHandlePropSpawn),
|
|
BindingFlags.Public | BindingFlags.Static))
|
|
);
|
|
|
|
UILibHelper.LoadIcons();
|
|
QuickMenuPropList.BuildUI();
|
|
QuickMenuPropSelect.BuildUI();
|
|
PropHelper.Initialize();
|
|
|
|
// Bono approved hack
|
|
QuickMenuAPI.InjectCSSStyle("""
|
|
.shit {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
border-radius: 10px;
|
|
display: block;
|
|
position: absolute;
|
|
top: 3px;
|
|
left: 3px;
|
|
}
|
|
|
|
.shit2 {
|
|
opacity: 0;
|
|
transition: opacity 0.3s ease-in-out;
|
|
}
|
|
|
|
""");
|
|
|
|
// Build ui once quick menu is loaded
|
|
QuickMenuAPI.OnMenuGenerated += _ =>
|
|
{
|
|
QuickMenuPropSelect.ListenForQM();
|
|
PropListEntry.ListenForQM();
|
|
GlobalButton.ListenForQM();
|
|
UndoRedoButtons.ListenForButtons();
|
|
};
|
|
|
|
SetupDefaultAudioClips();
|
|
}
|
|
|
|
/*public override void OnUpdate()
|
|
{
|
|
PropDistanceHider.Tick();
|
|
}*/
|
|
|
|
// ReSharper disable once RedundantAssignment
|
|
private static bool OnPreHandleSpawnableClicked(ref ControllerRay __instance, ref CVRSpawnable __result)
|
|
{
|
|
__result = null;
|
|
|
|
CVRSpawnable spawnable = __instance.hitTransform.GetComponentInParent<CVRSpawnable>();
|
|
if (spawnable == null) return false;
|
|
|
|
PlayerSetup.PropSelectionMode selectionMode = PlayerSetup.Instance.GetCurrentPropSelectionMode();
|
|
switch (selectionMode)
|
|
{
|
|
case PlayerSetup.PropSelectionMode.None:
|
|
{
|
|
// Click a prop while a menu is open to open the details menu
|
|
if (__instance._interactDown
|
|
&& __instance.CanSelectPlayersAndProps()
|
|
&& spawnable.TryGetComponent(out CVRAssetInfo assetInfo))
|
|
{
|
|
// Direct to Main Menu if it is open
|
|
if (ViewManager.Instance.IsViewShown)
|
|
{
|
|
ViewManager.Instance.GetPropDetails(assetInfo.objectId);
|
|
ViewManager.Instance.UiStateToggle(true);
|
|
}
|
|
// Direct to Quick Menu by default
|
|
else
|
|
{
|
|
QuickMenuPropSelect.ShowInfo(spawnable.PropData);
|
|
}
|
|
|
|
__instance._interactDown = false; // Consume the click
|
|
}
|
|
break;
|
|
}
|
|
case PlayerSetup.PropSelectionMode.Delete:
|
|
{
|
|
// Click a prop while in delete mode to delete it
|
|
if (__instance._interactDown
|
|
&& !spawnable.IsSpawnedByAdmin())
|
|
{
|
|
spawnable.Delete();
|
|
__instance._interactDown = false; // Consume the click
|
|
return false; // Don't return the spawnable, it's been deleted
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Return normal prop hover
|
|
__result = spawnable;
|
|
return false;
|
|
}
|
|
|
|
private static bool OnPreHandlePlayerClicked(ref ControllerRay __instance, ref PlayerBase __result)
|
|
{
|
|
if (!ModSettings.EntryFixPlayerSelectRedirect.Value)
|
|
return true;
|
|
|
|
__result = null;
|
|
|
|
if (PlayerSetup.Instance.GetCurrentPropSelectionMode()
|
|
!= PlayerSetup.PropSelectionMode.None)
|
|
return false;
|
|
|
|
PlayerBase playerBase = null;
|
|
if (__instance.CanSelectPlayersAndProps())
|
|
{
|
|
bool foundPlayerDescriptor = __instance.hitTransform.TryGetComponent(out playerBase);
|
|
if (!foundPlayerDescriptor && __instance.hitTransform.TryGetComponent(out CameraIndicator cameraIndicator))
|
|
{
|
|
PlayerBase cameraOwner = cameraIndicator.ownerPlayer;
|
|
if (!playerBase.IsLocalPlayer)
|
|
{
|
|
playerBase = cameraOwner;
|
|
foundPlayerDescriptor = true;
|
|
}
|
|
}
|
|
|
|
if (foundPlayerDescriptor && __instance._interactDown)
|
|
{
|
|
// Direct to Main Menu if it is open
|
|
if (ViewManager.Instance.IsViewShown)
|
|
{
|
|
ViewManager.Instance.RequestUserDetailsPage(playerBase.PlayerId);
|
|
ViewManager.Instance.UiStateToggle(true);
|
|
}
|
|
// Direct to Quick Menu by default
|
|
else
|
|
{
|
|
QuickMenuAPI.OpenPlayerListByUserID(playerBase.PlayerId);
|
|
CVR_MenuManager.Instance.ToggleQuickMenu(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
__result = playerBase;
|
|
return false;
|
|
}
|
|
|
|
private static void OnUpdateKeyboardBinds()
|
|
{
|
|
if (!ModSettings.EntryUseUndoRedoKeybinds.Value)
|
|
return;
|
|
|
|
if (Input.GetKey(KeyCode.LeftControl))
|
|
{
|
|
if (Input.GetKey(KeyCode.LeftShift)
|
|
&& Input.GetKeyDown(KeyCode.Z))
|
|
PropHelper.RedoProp();
|
|
else if (Input.GetKeyDown(KeyCode.Z))
|
|
PropHelper.UndoProp();
|
|
}
|
|
}
|
|
|
|
private void SetupDefaultAudioClips()
|
|
{
|
|
// PropUndo and audio folders do not exist, create them if dont exist yet
|
|
var path = Application.streamingAssetsPath + $"/Cohtml/UIResources/GameUI/mods/{nameof(PropsButBetter)}/audio/";
|
|
if (!Directory.Exists(path))
|
|
{
|
|
Directory.CreateDirectory(path);
|
|
LoggerInstance.Msg("Created audio directory!");
|
|
}
|
|
|
|
// copy embedded resources to this folder if they do not exist
|
|
string[] clipNames = { "sfx_spawn.wav", "sfx_undo.wav", "sfx_redo.wav", "sfx_warn.wav", "sfx_deny.wav" };
|
|
Assembly executingAssembly = Assembly.GetExecutingAssembly();
|
|
|
|
foreach (var clipName in clipNames)
|
|
{
|
|
var clipPath = Path.Combine(path, clipName);
|
|
if (!File.Exists(clipPath))
|
|
{
|
|
// read the clip data from embedded resources
|
|
byte[] clipData;
|
|
var resourceName = $"{nameof(PropsButBetter)}.Resources.SFX." + clipName;
|
|
using (Stream stream = executingAssembly.GetManifestResourceStream(resourceName))
|
|
{
|
|
clipData = new byte[stream!.Length];
|
|
// ReSharper disable once MustUseReturnValue
|
|
stream.Read(clipData, 0, clipData.Length);
|
|
}
|
|
|
|
// write the clip data to the file
|
|
using (FileStream fileStream = new(clipPath, FileMode.CreateNew))
|
|
{
|
|
fileStream.Write(clipData, 0, clipData.Length);
|
|
}
|
|
|
|
LoggerInstance.Msg("Placed missing sfx in audio folder: " + clipName);
|
|
}
|
|
}
|
|
}
|
|
} |