mass commit of laziness

This commit is contained in:
NotAKidoS 2025-12-28 20:30:00 -06:00
parent ce992c70ee
commit 6d4fc549d9
167 changed files with 5471 additions and 675 deletions

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk" />

View file

@ -0,0 +1,42 @@
using MelonLoader;
using NAK.AvatarQueueSystemTweaks.Patches;
namespace NAK.AvatarQueueSystemTweaks;
public class AvatarQueueSystemTweaksMod : MelonMod
{
public static readonly MelonPreferences_Category Category =
MelonPreferences.CreateCategory(nameof(AvatarQueueSystemTweaks));
public static readonly MelonPreferences_Entry<bool> EntryPrioritizeSelf =
Category.CreateEntry("prioritize_self", true, "Prioritize Self", description: "Prioritize loading of your own avatar over others.");
public static readonly MelonPreferences_Entry<bool> EntryPrioritizeFriends =
Category.CreateEntry("prioritize_friends", true, "Prioritize Friends", description: "Prioritize loading of friends avatars over others.");
public static readonly MelonPreferences_Entry<bool> EntryLoadByDistance =
Category.CreateEntry("load_by_distance", true, "Load By Distance", description: "Prioritize loading of avatars by distance.");
// public static readonly MelonPreferences_Entry<bool> EntryChokeInstantiation =
// Category.CreateEntry("choke_instantiation", false, "Choke Instantiation",
// description: "Chokes the instantiation queue by waiting 0.2s");
public override void OnInitializeMelon()
{
//ApplyPatches(typeof(CVRObjectLoaderPatches));
ApplyPatches(typeof(AvatarQueueSystemPatches));
}
private void ApplyPatches(Type type)
{
try
{
HarmonyInstance.PatchAll(type);
}
catch (Exception e)
{
LoggerInstance.Msg($"Failed while patching {type.Name}!");
LoggerInstance.Error(e);
}
}
}

View file

@ -0,0 +1,116 @@
using ABI_RC.Core;
using ABI_RC.Core.IO;
using ABI_RC.Core.Networking.IO.Social;
using ABI_RC.Core.Player;
using ABI_RC.Core.Util;
using HarmonyLib;
using UnityEngine;
namespace NAK.AvatarQueueSystemTweaks.Patches;
// internal static class CVRObjectLoaderPatches
// {
// private static readonly YieldInstruction _yieldInstruction = new WaitForSeconds(0.2f);
//
// [HarmonyPostfix]
// [HarmonyPatch(typeof(CVRObjectLoader), nameof(CVRObjectLoader.InstantiateAvatarFromBundle))]
// [HarmonyPatch(typeof(CVRObjectLoader), nameof(CVRObjectLoader.InstantiateSpawnableFromBundle))]
// //[HarmonyPatch(typeof(CVRObjectLoader), nameof(CVRObjectLoader.InstantiateAvatarFromExistingPrefab))]
// //[HarmonyPatch(typeof(CVRObjectLoader), nameof(CVRObjectLoader.InstantiateSpawnableFromExistingPrefab))]
// private static IEnumerator MyWrapper(IEnumerator result)
// {
// while (result.MoveNext())
// yield return result.Current;
//
// if (AvatarQueueSystemTweaksMod.EntryChokeInstantiation.Value)
// yield return _yieldInstruction;
// }
// }
internal static class AvatarQueueSystemPatches
{
[HarmonyPrefix]
[HarmonyPatch(typeof(AvatarQueueSystem.CoroutineHandle), nameof(AvatarQueueSystem.CoroutineHandle.RunManagedCoroutine))]
private static bool Prefix_AvatarQueueSystem_CoroutineHandle_RunManagedCoroutine(AvatarQueueSystem.CoroutineHandle __instance)
{
AvatarQueueSystem.Instance.jobIsActive = true;
// difference from original: doesn't immediately call CheckAvailability on same frame of completion
__instance._activeCoroutine = AvatarQueueSystem.Instance.StartCoroutine(CoroutineUtil.RunThrowingIterator(__instance._function, delegate(Exception exception)
{
CommonTools.Log(CommonTools.LogLevelType_t.Error, string.Format("[CVRGame => {0}] Avatar loading job has failed!\n{1}", __instance.GetType().Name, exception), "A0108");
__instance.CleanCurrentJob();
var onException = __instance._onException;
onException?.Invoke(exception);
}, delegate
{
CommonTools.Log(CommonTools.LogLevelType_t.Info, "[CVRGame => " + __instance.GetType().Name + "] Avatar loading job has completed successfully.", "A0107");
__instance.CleanCurrentJob();
Action onSuccess = __instance._onSuccess;
onSuccess?.Invoke();
}));
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(AvatarQueueSystem), nameof(AvatarQueueSystem.CheckAvailability))]
private static bool Prefix_AvatarQueueSystem_CheckAvailability(AvatarQueueSystem __instance)
{
if (__instance.JobIsActive
|| __instance.activeCoroutines.Count == 0)
return false;
bool prioritizeSelf = AvatarQueueSystemTweaksMod.EntryPrioritizeSelf.Value;
bool prioritizeFriends = AvatarQueueSystemTweaksMod.EntryPrioritizeFriends.Value;
bool loadByDistance = AvatarQueueSystemTweaksMod.EntryLoadByDistance.Value;
bool foundFriend = false;
float nearestDistance = float.MaxValue;
AvatarQueueSystem.CoroutineHandle nextCoroutine = null;
Vector3 playerPosition = PlayerSetup.Instance.activeCam.transform.position;
foreach (AvatarQueueSystem.CoroutineHandle coroutine in __instance.activeCoroutines)
{
if (prioritizeSelf && coroutine.player == "_PLAYERLOCAL") // prioritize local player if setting is enabled
{
nextCoroutine = coroutine;
break;
}
CVRPlayerManager.Instance.GetPlayerPuppetMaster(coroutine.player, out PuppetMaster puppetMaster);
if (puppetMaster == null)
continue;
if (prioritizeFriends)
{
switch (foundFriend)
{
// attempt find first friend
case false when Friends.FriendsWith(coroutine.player):
foundFriend = true;
nearestDistance = float.MaxValue;
nextCoroutine = coroutine;
if (!loadByDistance) break; // no reason to continue loop
continue;
// found a friend, filtering will now only respect friends for this iteration
case true when !Friends.FriendsWith(coroutine.player):
continue;
}
}
// filtering by distance
if (!loadByDistance) continue;
float distance = Vector3.Distance(playerPosition, puppetMaster.transform.position);
if (!(distance < nearestDistance)) continue; // update nearest coroutine if closer
nearestDistance = distance;
nextCoroutine = coroutine;
}
nextCoroutine?.RunManagedCoroutine();
return false;
}
}

View file

@ -0,0 +1,32 @@
using MelonLoader;
using NAK.AvatarQueueSystemTweaks.Properties;
using System.Reflection;
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyTitle(nameof(NAK.AvatarQueueSystemTweaks))]
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
[assembly: AssemblyProduct(nameof(NAK.AvatarQueueSystemTweaks))]
[assembly: MelonInfo(
typeof(NAK.AvatarQueueSystemTweaks.AvatarQueueSystemTweaksMod),
nameof(NAK.AvatarQueueSystemTweaks),
AssemblyInfoParams.Version,
AssemblyInfoParams.Author,
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/AvatatQueueSystemTweaks"
)]
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
[assembly: MelonColor(255, 246, 25, 99)] // red-pink
[assembly: MelonAuthorColor(255, 158, 21, 32)] // red
[assembly: HarmonyDontPatchAll]
namespace NAK.AvatarQueueSystemTweaks.Properties;
internal static class AssemblyInfoParams
{
public const string Version = "1.0.1";
public const string Author = "NotAKidoS";
}

View file

@ -0,0 +1,18 @@
# AvatarQueueSystemTweaks
Small tweaks to the Avatar Queue System.
- Prioritize loading own avatar over others.
- Prioritize loading friends' avatars over others.
- Load avatars by distance from player.
---
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI.
https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive.
> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use.
> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.

View file

@ -0,0 +1,24 @@
{
"_id": 226,
"name": "AvatarQueueSystemTweaks",
"modversion": "1.0.1",
"gameversion": "2025r179",
"loaderversion": "0.6.1",
"modtype": "Mod",
"author": "NotAKidoS",
"description": "Small tweaks to the Avatar Queue System.\n\n- Prioritize loading own avatar over others.\n- Prioritize loading friends' avatars over others.\n- Load avatars by distance from player.",
"searchtags": [
"avatar",
"load",
"queue",
"initialize",
"download"
],
"requirements": [
"None"
],
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/AvatarQueueSystemTweaks.dll",
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/AvatarQueueSystemTweaks/",
"changelog": "- Recompiled for 2025r179",
"embedcolor": "#f61963"
}

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk" />

View file

@ -0,0 +1,109 @@
using System.Reflection;
using ABI_RC.Core.Networking.API.Responses;
using ABI_RC.Core.Util.AssetFiltering;
using HarmonyLib;
using MelonLoader;
using UnityEngine;
namespace NAK.BufferParticleFixer;
public class BufferParticleFixerMod : MelonMod
{
private static MelonLogger.Instance Logger;
private static readonly MelonPreferences_Category Category =
MelonPreferences.CreateCategory(nameof(BufferParticleFixer));
private static readonly MelonPreferences_Entry<bool> EntryFixBufferParticles =
Category.CreateEntry(
identifier: "fix_buffer_particles",
true,
display_name: "Fix Buffer Particles",
description: "Should the mod attempt to fix buffer particles by modifying their lifetime and sub-emitter settings?");
public override void OnInitializeMelon()
{
Logger = LoggerInstance;
HarmonyInstance.Patch(
typeof(SharedFilter).GetMethod(nameof(SharedFilter.ProcessParticleComponent),
BindingFlags.Public | BindingFlags.Static),
postfix: new HarmonyMethod(typeof(BufferParticleFixerMod).GetMethod(nameof(OnProcessParticleComponent),
BindingFlags.NonPublic | BindingFlags.Static))
);
}
private static void OnProcessParticleComponent(
string collectionId,
Component particleComponent,
bool physicsCollision,
CompatibilityVersions compatibilityVersion)
{
if (particleComponent is not ParticleSystem particleSystem)
return;
if (!EntryFixBufferParticles.Value)
return;
// Logger.Msg($"Processing particle system on collection '{collectionId}'.");
if (!IsLikelyBufferParticle(particleSystem))
return;
Logger.Msg($"Detected likely buffer particle system '{particleSystem.name}' on collection '{collectionId}'. Applying fix...");
// Set start lifetime to 1000
// All sub-emitters to "Spawn on Birth"
// https://x.com/hfcRedddd/status/1696914565919813679
ParticleSystem.MainModule mainModule = particleSystem.main;
mainModule.startLifetime = 1f;
for (int i = 0; i < particleSystem.subEmitters.subEmittersCount; i++)
{
ParticleSystem subEmitter = particleSystem.subEmitters.GetSubEmitterSystem(i);
if (subEmitter) particleSystem.subEmitters.SetSubEmitterType(i, ParticleSystemSubEmitterType.Birth);
}
}
// https://x.com/hfcRedddd/status/1696913727415537807
private static bool IsLikelyBufferParticle(ParticleSystem ps)
{
// Check if the sub-emitters are children of the particle system
Transform psTransform = ps.transform;
bool hasSubEmitterNotChild = false;
ParticleSystem.SubEmittersModule subEmitters = ps.subEmitters;
int subEmitterCount = subEmitters.subEmittersCount;
for (int i = 0; i < subEmitterCount; i++)
{
ParticleSystem subEmitter = subEmitters.GetSubEmitterSystem(i);
// Skip null sub-emitters
if (!subEmitter)
{
Logger.Warning($"Particle system '{ps.name}' has a null sub-emitter at index {i}.");
continue;
}
// If any sub-emitter is not a child of the particle system, it's likely a buffer particle.
// This setup is also what shits into our logs...
if (!subEmitter.transform.IsChildOf(psTransform))
hasSubEmitterNotChild = true;
}
if (hasSubEmitterNotChild)
{
// Buffer particles have very short lifetimes
if (!(ps.main.startLifetime.constant > 0.05f))
return true;
Logger.Msg($"A potential buffer particle system '{ps.name}' has a start lifetime of {ps.main.startLifetime.constant}, which is longer than expected.");
}
return false;
}
}

View file

@ -0,0 +1,32 @@
using MelonLoader;
using NAK.BufferParticleFixer.Properties;
using System.Reflection;
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyTitle(nameof(NAK.BufferParticleFixer))]
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
[assembly: AssemblyProduct(nameof(NAK.BufferParticleFixer))]
[assembly: MelonInfo(
typeof(NAK.BufferParticleFixer.BufferParticleFixerMod),
nameof(NAK.BufferParticleFixer),
AssemblyInfoParams.Version,
AssemblyInfoParams.Author,
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/BufferParticleFixer"
)]
[assembly: MelonGame("ChilloutVR", "ChilloutVR")]
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
[assembly: MelonColor(255, 246, 25, 99)] // red-pink
[assembly: MelonAuthorColor(255, 158, 21, 32)] // red
[assembly: HarmonyDontPatchAll]
namespace NAK.BufferParticleFixer.Properties;
internal static class AssemblyInfoParams
{
public const string Version = "1.0.0";
public const string Author = "NotAKidoS";
}

View file

@ -0,0 +1,14 @@
# SearchWithSpacesFix
Fixes search terms that use spaces.
---
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI.
https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive.
> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use.
> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.

View file

@ -0,0 +1,23 @@
{
"_id": -1,
"name": "SearchWithSpacesFix",
"modversion": "1.0.0",
"gameversion": "2024r177",
"loaderversion": "0.6.1",
"modtype": "Mod",
"author": "NotAKidoS",
"description": "Fixes search terms that include spaces.",
"searchtags": [
"search",
"spaces",
"fix",
"meow"
],
"requirements": [
"None"
],
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r42/SearchWithSpacesFix.dll",
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/SearchWithSpacesFix/",
"changelog": "- Initial release",
"embedcolor": "#f61963"
}

View file

@ -17,7 +17,7 @@ using System.Reflection;
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/CVRGizmos"
)]
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
[assembly: MelonGame("ChilloutVR", "ChilloutVR")]
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>PlayerCloneAttachment</RootNamespace>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\playercloneattachment.assets" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,92 @@
using System.Reflection;
using ABI_RC.Core.InteractionSystem;
using ABI_RC.Core.UI.UIRework.Managers;
using ABI_RC.Systems.ChatBox;
using ABI_RC.Systems.InputManagement;
using HarmonyLib;
using MelonLoader;
using System;
using System.Linq;
using UnityEngine;
using UnityEngine.LowLevel;
using UnityEngine.PlayerLoop;
namespace NAK.ChatBoxTweaks;
public class ChatBoxTweaksMod : MelonMod
{
public override void OnInitializeMelon()
{
HarmonyInstance.Patch(
typeof(KeyboardManager).GetMethod(nameof(KeyboardManager.OnKeyboardSubmit),
BindingFlags.NonPublic | BindingFlags.Instance),
prefix: new HarmonyMethod(typeof(ChatBoxTweaksMod).GetMethod(nameof(OnPreKeyboardManagerKeyboardSubmit),
BindingFlags.NonPublic | BindingFlags.Static)),
postfix: new HarmonyMethod(typeof(ChatBoxTweaksMod).GetMethod(nameof(OnPostKeyboardManagerKeyboardSubmit),
BindingFlags.NonPublic | BindingFlags.Static))
);
}
private static void OnPreKeyboardManagerKeyboardSubmit(ref KeyboardManager __instance, ref KeyboardManager.OpenSource? __state)
{
__state = __instance.KeyboardOpenSource;
}
private static void OnPostKeyboardManagerKeyboardSubmit(ref KeyboardManager.OpenSource? __state)
{
if (__state == KeyboardManager.OpenSource.TextComms) ChatBoxAPI.OpenKeyboard();
}
}
/*
public static class NetworkLoopInjector
{
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
static void InjectNetworkFixedUpdate()
{
var playerLoop = PlayerLoop.GetCurrentPlayerLoop();
// Find the FixedUpdate phase
int fixedUpdateIndex = Array.FindIndex(playerLoop.subSystemList, s => s.type == typeof(FixedUpdate));
if (fixedUpdateIndex < 0)
{
Debug.LogError("FixedUpdate not found in player loop!");
return;
}
var fixedUpdate = playerLoop.subSystemList[fixedUpdateIndex];
// Create your custom PlayerLoopSystem
var networkSystem = new PlayerLoopSystem
{
type = typeof(NetworkFixedUpdate),
updateDelegate = NetworkFixedUpdate.Run
};
// Insert at the start so it runs before physics, animation, etc.
var subs = fixedUpdate.subSystemList.ToList();
subs.Insert(0, networkSystem);
fixedUpdate.subSystemList = subs.ToArray();
// Reassign and set back
playerLoop.subSystemList[fixedUpdateIndex] = fixedUpdate;
PlayerLoop.SetPlayerLoop(playerLoop);
Debug.Log("[NetworkLoopInjector] Inserted NetworkFixedUpdate at start of FixedUpdate loop");
}
static class NetworkFixedUpdate
{
static int lastStateFrame = -1;
public static void Run()
{
// Apply your networked object state syncs before physics simulation
Debug.Log("Last State Frame: " + lastStateFrame + " Current Frame: " + Time.frameCount);
lastStateFrame = Time.frameCount;
}
}
}
*/

View file

@ -0,0 +1,32 @@
using MelonLoader;
using NAK.ChatBoxTweaks.Properties;
using System.Reflection;
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyTitle(nameof(NAK.ChatBoxTweaks))]
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
[assembly: AssemblyProduct(nameof(NAK.ChatBoxTweaks))]
[assembly: MelonInfo(
typeof(NAK.ChatBoxTweaks.ChatBoxTweaksMod),
nameof(NAK.ChatBoxTweaks),
AssemblyInfoParams.Version,
AssemblyInfoParams.Author,
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ChatBoxTweaks"
)]
[assembly: MelonGame("ChilloutVR", "ChilloutVR")]
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
[assembly: MelonColor(255, 246, 25, 99)] // red-pink
[assembly: MelonAuthorColor(255, 158, 21, 32)] // red
[assembly: HarmonyDontPatchAll]
namespace NAK.ChatBoxTweaks.Properties;
internal static class AssemblyInfoParams
{
public const string Version = "1.0.0";
public const string Author = "NotAKidoS";
}

View file

@ -0,0 +1,22 @@
# YouAreMyPropNowWeAreHavingSoftTacosLater
Lets you bring held, attached, and occupied props through world loads. This is configurable in the mod settings.
https://youtu.be/9P6Jeh-VN58?si=eXTPGyKB_0wq1gZO
There is special logic in place for bringing air vehicles through world loads.
If above the ground you will be placed up to 20m above the spawnpoint of the next world.
## Examples
https://fixupx.com/NotAKidoS/status/1910545346922422675
---
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI.
https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive.
> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use.
> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.

View file

@ -0,0 +1,23 @@
{
"_id": 262,
"name": "YouAreMyPropNowWeAreHavingSoftTacosLater",
"modversion": "1.0.0",
"gameversion": "2025r180",
"loaderversion": "0.7.2",
"modtype": "Mod",
"author": "NotAKidoS",
"description": "Lets you bring held, attached, and occupied props through world loads. This is configurable in the mod settings.\nhttps://youtu.be/9P6Jeh-VN58?si=eXTPGyKB_0wq1gZO\n\nThere is special logic in place for bringing air vehicles through world loads. If above the ground you will be placed up to 20m above the spawnpoint of the next world.",
"searchtags": [
"prop",
"spawn",
"friend",
"load"
],
"requirements": [
"None"
],
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/YouAreMyPropNowWeAreHavingSoftTacosLater.dll",
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/YouAreMyPropNowWeAreHavingSoftTacosLater/",
"changelog": "- Initial release",
"embedcolor": "#f61963"
}

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>YouAreMineNow</RootNamespace>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,236 @@
using System.Reflection;
using ABI_RC.Core.InteractionSystem;
using ABI_RC.Core.Player;
using ABI_RC.Core.Util;
using ABI_RC.Systems.Communications;
using ABI_RC.Systems.GameEventSystem;
using ABI_RC.Systems.RuntimeDebug;
using HarmonyLib;
using MelonLoader;
using UnityEngine;
namespace NAK.YouAreMyPropNowWeAreHavingSoftTacosLater;
public class ChatBubblesMod : MelonMod
{
#region Melon Preferences
private static readonly MelonPreferences_Category Category =
MelonPreferences.CreateCategory(nameof(YouAreMyPropNowWeAreHavingSoftTacosLater));
private static readonly MelonPreferences_Entry<KeyCode> EntryChatBubblesKey =
Category.CreateEntry("keyboard_bind", KeyCode.Y, display_name: "Chat Bubbles Key",
description: "Key to open chat bubble input.");
private static readonly MelonPreferences_Entry<float> EntryBubbleDuration =
Category.CreateEntry("bubble_duration", 5.0f, display_name: "Bubble Duration",
description: "Base duration in seconds for chat bubbles to stay visible.");
private static readonly MelonPreferences_Entry<float> EntryDurationPerChar =
Category.CreateEntry("duration_per_char", 0.05f, display_name: "Duration Per Character",
description: "Additional duration per character in the message.");
private static readonly MelonPreferences_Entry<int> EntryPoolSize =
Category.CreateEntry("pool_size", 20, display_name: "Pool Size",
description: "Maximum number of chat bubbles that can be displayed at once.");
private static readonly MelonPreferences_Entry<Color> EntryBubbleColor =
Category.CreateEntry("bubble_color", new Color(1.0f, 1.0f, 1.0f, 1.0f), display_name: "Bubble Color",
description: "Color of chat bubble text.");
#endregion Melon Preferences
#region Object Pool
private class ChatBubble
{
public CVRPlayerEntity Player { get; set; }
public string Message { get; set; }
public float Duration { get; set; }
public float Timer { get; set; }
public bool IsActive { get; set; }
public void Reset()
{
Player = null;
Message = string.Empty;
Duration = 0f;
Timer = 0f;
IsActive = false;
}
}
private readonly List<ChatBubble> _chatBubblePool = [];
private readonly List<ChatBubble> _activeBubbles = [];
private void InitializePool()
{
MelonLogger.Msg("Initializing chat bubble pool with size: " + EntryPoolSize.Value);
_chatBubblePool.Clear();
for (int i = 0; i < EntryPoolSize.Value; i++)
{
_chatBubblePool.Add(new ChatBubble());
}
}
private ChatBubble GetBubbleFromPool()
{
// First try to find an inactive bubble
foreach (var bubble in _chatBubblePool)
{
if (!bubble.IsActive)
{
bubble.Reset();
bubble.IsActive = true;
_activeBubbles.Add(bubble);
return bubble;
}
}
// If all bubbles are active, reuse the oldest one
if (_activeBubbles.Count > 0)
{
var oldestBubble = _activeBubbles[0];
_activeBubbles.RemoveAt(0);
oldestBubble.Reset();
oldestBubble.IsActive = true;
_activeBubbles.Add(oldestBubble);
return oldestBubble;
}
// This should never happen if the pool is initialized properly
MelonLogger.Warning("No chat bubbles available in pool!");
return null;
}
private void ReturnBubbleToPool(ChatBubble bubble)
{
bubble.IsActive = false;
_activeBubbles.Remove(bubble);
}
#endregion Object Pool
#region Melon Events
public override void OnInitializeMelon()
{
MelonLogger.Msg("Initializing Chat Bubbles Mod");
InitializePool();
CVRGameEventSystem.Communications.TextChat.Local.OnMessageReceived.AddListener(OnLocalMessageReceived);
CVRGameEventSystem.Communications.TextChat.Direct.OnMessageReceived.AddListener(OnGlobalMessageReceived);
CVRGameEventSystem.Communications.TextChat.Global.OnMessageReceived.AddListener(OnDirectMessageReceived);
HarmonyInstance.Patch(
typeof(ViewManager).GetMethod(nameof(ViewManager.SendToWorldUi),
BindingFlags.NonPublic | BindingFlags.Instance),
postfix: new HarmonyMethod(typeof(ChatBubblesMod).GetMethod(nameof(OnViewManagerSendToWorldUi),
BindingFlags.NonPublic | BindingFlags.Static))
);
MelonLogger.Msg("Chat Bubbles Mod initialized successfully");
}
public override void OnUpdate()
{
// Process key input to open chat
if (Input.GetKeyDown(EntryChatBubblesKey.Value))
{
if (!CVRSyncHelper.IsConnectedToGameNetwork())
return;
IsInKeyboardToWriteANiceMessage = true;
ViewManager.Instance.openMenuKeyboard(string.Empty);
}
// Update and render active chat bubbles
UpdateChatBubbles();
}
private void UpdateChatBubbles()
{
// Make a copy of the list to avoid issues when modifying during iteration
var bubblesToUpdate = new List<ChatBubble>(_activeBubbles);
foreach (var bubble in bubblesToUpdate)
{
if (bubble.Player == null || bubble.Player.PuppetMaster == null)
{
ReturnBubbleToPool(bubble);
continue;
}
bubble.Timer += Time.deltaTime;
if (bubble.Timer >= bubble.Duration)
{
ReturnBubbleToPool(bubble);
continue;
}
// Draw the bubble text at the player's nameplate position
var nameplatePosition = bubble.Player.PuppetMaster.GetNamePlateWorldPosition();
// Offset slightly above the nameplate
nameplatePosition.y += 0.2f;
RuntimeGizmos.DrawText(nameplatePosition, bubble.Message, 5, EntryBubbleColor.Value);
}
}
#endregion Melon Events
#region Game Events
public static bool IsInKeyboardToWriteANiceMessage { get; set; }
private static void OnViewManagerSendToWorldUi(string value)
{
if (!IsInKeyboardToWriteANiceMessage) return;
IsInKeyboardToWriteANiceMessage = false;
Comms_Manager.SendLocalTextMessage(value);
Comms_Manager.SendDirectTextMessage(CVRPlayerManager.Instance.NetworkPlayers[0].Uuid, value);
Comms_Manager.SendGlobalTextMessage(value);
MelonLogger.Msg("Sending message: " + value);
}
private void OnLocalMessageReceived(CVRPlayerEntity player, string message)
{
CreateChatBubble(player, message);
MelonLogger.Msg("OnLocalMessageReceived - Player: " + player?.Username + ", Message: " + message);
}
private void OnDirectMessageReceived(CVRPlayerEntity player, string message)
{
CreateChatBubble(player, message);
MelonLogger.Msg("OnDirectMessageReceived - Player: " + player?.Username + ", Message: " + message);
}
private void OnGlobalMessageReceived(CVRPlayerEntity player, string message)
{
CreateChatBubble(player, message);
MelonLogger.Msg("OnGlobalMessageReceived - Player: " + player?.Username + ", Message: " + message);
}
private void CreateChatBubble(CVRPlayerEntity player, string message)
{
if (player == null || player.PuppetMaster == null)
return;
// Calculate duration based on message length
float baseDuration = EntryBubbleDuration.Value;
float charDuration = EntryDurationPerChar.Value * message.Length;
float totalDuration = Mathf.Clamp(baseDuration + charDuration, baseDuration, 15f);
ChatBubble bubble = GetBubbleFromPool();
if (bubble == null) return;
bubble.Player = player;
bubble.Message = message;
bubble.Duration = totalDuration;
bubble.Timer = 0f;
}
#endregion Game Events
}

View file

@ -0,0 +1,32 @@
using MelonLoader;
using NAK.YouAreMyPropNowWeAreHavingSoftTacosLater.Properties;
using System.Reflection;
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyTitle(nameof(NAK.YouAreMyPropNowWeAreHavingSoftTacosLater))]
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
[assembly: AssemblyProduct(nameof(NAK.YouAreMyPropNowWeAreHavingSoftTacosLater))]
[assembly: MelonInfo(
typeof(NAK.YouAreMyPropNowWeAreHavingSoftTacosLater.ChatBubblesMod),
nameof(NAK.YouAreMyPropNowWeAreHavingSoftTacosLater),
AssemblyInfoParams.Version,
AssemblyInfoParams.Author,
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ChatBubbles"
)]
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
[assembly: MelonColor(255, 246, 25, 99)] // red-pink
[assembly: MelonAuthorColor(255, 158, 21, 32)] // red
[assembly: HarmonyDontPatchAll]
namespace NAK.YouAreMyPropNowWeAreHavingSoftTacosLater.Properties;
internal static class AssemblyInfoParams
{
public const string Version = "1.0.0";
public const string Author = "NotAKidoS";
}

View file

@ -0,0 +1,19 @@
# YouAreMyPropNowWeAreHavingSoftTacosLater
Lets you bring held & attached props through world loads.
https://youtu.be/9P6Jeh-VN58?si=eXTPGyKB_0wq1gZO
## Examples
https://fixupx.com/NotAKidoS/status/1910545346922422675
---
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI.
https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive.
> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use.
> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.

View file

@ -0,0 +1,23 @@
{
"_id": -1,
"name": "YouAreMyPropNowWeAreHavingSoftTacosLater",
"modversion": "1.0.0",
"gameversion": "2025r179",
"loaderversion": "0.6.1",
"modtype": "Mod",
"author": "NotAKidoS",
"description": "Lets you bring held & attached props through world loads.\nhttps://youtu.be/9P6Jeh-VN58?si=eXTPGyKB_0wq1gZO",
"searchtags": [
"prop",
"spawn",
"friend",
"load"
],
"requirements": [
"None"
],
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/YouAreMyPropNowWeAreHavingSoftTacosLater.dll",
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/YouAreMyPropNowWeAreHavingSoftTacosLater/",
"changelog": "- Initial Release",
"embedcolor": "#00FFFF"
}

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>PlayerCloneAttachment</RootNamespace>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\playercloneattachment.assets" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,169 @@
using ABI_RC.Core;
using ABI_RC.Core.InteractionSystem;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Systems.FaceTracking;
using ABI_RC.Systems.InputManagement;
using MelonLoader;
using UnityEngine;
namespace NAK.ControlToUnlockEyes;
public class ControlToUnlockEyesMod : MelonMod
{
public override void OnInitializeMelon()
{
HarmonyInstance.Patch(
typeof(FaceTrackingManager).GetMethod(nameof(FaceTrackingManager.RegisterBuiltinModules),
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance),
postfix: new HarmonyLib.HarmonyMethod(typeof(ControlToUnlockEyesMod).GetMethod(nameof(OnPostFaceTrackingManagerRegisterBuiltinModules),
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static))
);
}
private static void OnPostFaceTrackingManagerRegisterBuiltinModules(FaceTrackingManager __instance)
=> __instance.RegisterEyeModule(new DefaultEyeModule());
public class DefaultEyeModule : IEyeTrackingModule
{
private const float FixedDistance = 10f;
private bool _useFixedDistance = true;
private readonly EyeTrackingData _eyeTrackingData = new();
private bool _dataAvailable;
private bool _running;
private ControllerRay _activeRay;
private Transform _rayDirectionTransform;
private CVRHand lastInteractHand = CVRHand.Right;
public bool Start(bool vr)
{
_running = true;
return true;
}
public void Stop()
{
_running = false;
}
public void Update()
{
if (!PlayerSetup.Instance)
return;
UpdateLastInteractHand();
UpdateFakedEyeTrackingData();
}
private void UpdateLastInteractHand()
{
ControllerRay leftRay = PlayerSetup.Instance.vrRayLeft;
ControllerRay rightRay = PlayerSetup.Instance.vrRayRight;
if (!MetaPort.Instance.isUsingVr)
{
_activeRay = PlayerSetup.Instance.desktopRay;
_rayDirectionTransform = _activeRay.rayDirectionTransform;
return;
}
bool leftAvailable = IsHandAvailable(leftRay, CVRHand.Left);
bool rightAvailable = IsHandAvailable(rightRay, CVRHand.Right);
if (CVRInputManager.Instance.interactLeftDown && leftAvailable)
lastInteractHand = CVRHand.Left;
else if (CVRInputManager.Instance.interactRightDown && rightAvailable)
lastInteractHand = CVRHand.Right;
_activeRay = GetLastInteractRay();
_rayDirectionTransform = _activeRay.rayDirectionTransform;
}
private void UpdateFakedEyeTrackingData()
{
_dataAvailable = _activeRay.CanSelectPlayersAndProps();
if (!_dataAvailable)
return;
_eyeTrackingData.blinking = false;
Transform ourCameraTransform = PlayerSetup.Instance.activeCam.transform;
Vector3 rayForward = _rayDirectionTransform.forward;
float rayDistance = _useFixedDistance ? FixedDistance : _activeRay.Hit.distance;
// TODO: dot product check to flip direction if behind camera
// Convert to camera-local *direction* (normalized) and multiply by selected distance so the gazePoint
// is on a sphere around the camera rather than mapped to a "square".
Vector3 localDir = ourCameraTransform.InverseTransformDirection(rayForward).normalized;
Vector3 localGazePoint = localDir * rayDistance;
_eyeTrackingData.gazePoint = localGazePoint;
}
private ControllerRay GetLastInteractRay()
{
if (!MetaPort.Instance.isUsingVr)
return PlayerSetup.Instance.desktopRay;
ControllerRay leftRay = PlayerSetup.Instance.vrRayLeft;
ControllerRay rightRay = PlayerSetup.Instance.vrRayRight;
if (lastInteractHand == CVRHand.Left && IsHandAvailable(leftRay, CVRHand.Left))
return leftRay;
if (lastInteractHand == CVRHand.Right && IsHandAvailable(rightRay, CVRHand.Right))
return rightRay;
return GetBestAvailableHand();
}
private ControllerRay GetBestAvailableHand()
{
if (!MetaPort.Instance.isUsingVr)
return PlayerSetup.Instance.desktopRay;
ControllerRay leftRay = PlayerSetup.Instance.vrRayLeft;
ControllerRay rightRay = PlayerSetup.Instance.vrRayRight;
bool leftAvailable = IsHandAvailable(leftRay, CVRHand.Left);
bool rightAvailable = IsHandAvailable(rightRay, CVRHand.Right);
if (CVRInputManager.Instance.interactLeftDown && leftAvailable)
return leftRay;
if (CVRInputManager.Instance.interactRightDown && rightAvailable)
return rightRay;
if (lastInteractHand == CVRHand.Left && leftAvailable)
return leftRay;
if (lastInteractHand == CVRHand.Right && rightAvailable)
return rightRay;
if (rightAvailable) return rightRay;
if (leftAvailable) return leftRay;
return rightRay;
}
private static bool IsHandAvailable(ControllerRay ray, CVRHand hand)
{
if (ray.grabbedObject)
return false;
if (CVR_MenuManager.Instance.IsViewShown &&
CVR_MenuManager.Instance.SelectedQuickMenuHand == hand)
return false;
return true;
}
public bool IsRunning() => _running;
public bool IsDataAvailable() => _dataAvailable;
public EyeTrackingData GetTrackingData() => _eyeTrackingData;
public string GetModuleName() => "None";
public string GetModuleShortName() => "None";
}
}

View file

@ -0,0 +1,32 @@
using MelonLoader;
using NAK.ControlToUnlockEyes.Properties;
using System.Reflection;
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyTitle(nameof(NAK.ControlToUnlockEyes))]
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
[assembly: AssemblyProduct(nameof(NAK.ControlToUnlockEyes))]
[assembly: MelonInfo(
typeof(NAK.ControlToUnlockEyes.ControlToUnlockEyesMod),
nameof(NAK.ControlToUnlockEyes),
AssemblyInfoParams.Version,
AssemblyInfoParams.Author,
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ControlToUnlockEyes"
)]
[assembly: MelonGame("ChilloutVR", "ChilloutVR")]
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
[assembly: MelonColor(255, 246, 25, 99)] // red-pink
[assembly: MelonAuthorColor(255, 158, 21, 32)] // red
[assembly: HarmonyDontPatchAll]
namespace NAK.ControlToUnlockEyes.Properties;
internal static class AssemblyInfoParams
{
public const string Version = "1.0.0";
public const string Author = "NotAKidoS";
}

View file

@ -0,0 +1,22 @@
# YouAreMyPropNowWeAreHavingSoftTacosLater
Lets you bring held, attached, and occupied props through world loads. This is configurable in the mod settings.
https://youtu.be/9P6Jeh-VN58?si=eXTPGyKB_0wq1gZO
There is special logic in place for bringing air vehicles through world loads.
If above the ground you will be placed up to 20m above the spawnpoint of the next world.
## Examples
https://fixupx.com/NotAKidoS/status/1910545346922422675
---
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI.
https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive.
> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use.
> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.

View file

@ -0,0 +1,23 @@
{
"_id": 262,
"name": "YouAreMyPropNowWeAreHavingSoftTacosLater",
"modversion": "1.0.0",
"gameversion": "2025r180",
"loaderversion": "0.7.2",
"modtype": "Mod",
"author": "NotAKidoS",
"description": "Lets you bring held, attached, and occupied props through world loads. This is configurable in the mod settings.\nhttps://youtu.be/9P6Jeh-VN58?si=eXTPGyKB_0wq1gZO\n\nThere is special logic in place for bringing air vehicles through world loads. If above the ground you will be placed up to 20m above the spawnpoint of the next world.",
"searchtags": [
"prop",
"spawn",
"friend",
"load"
],
"requirements": [
"None"
],
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/YouAreMyPropNowWeAreHavingSoftTacosLater.dll",
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/YouAreMyPropNowWeAreHavingSoftTacosLater/",
"changelog": "- Initial release",
"embedcolor": "#f61963"
}

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>YouAreMineNow</RootNamespace>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,228 @@
using ABI_RC.Core.Util.AssetFiltering;
using System.Reflection;
using HarmonyLib;
using MelonLoader;
using UnityEngine;
namespace NAK.FuckCameras;
public class FuckCamerasMod : MelonMod
{
public override void OnInitializeMelon()
{
HarmonyInstance.Patch(
typeof(SharedFilter).GetMethod(nameof(SharedFilter.ProcessCamera),
BindingFlags.Public | BindingFlags.Static),
postfix: new HarmonyMethod(typeof(FuckCamerasMod).GetMethod(nameof(OnProcessCamera),
BindingFlags.NonPublic | BindingFlags.Static))
);
}
private static void OnProcessCamera(string collectionId, Camera camera)
=> camera.gameObject.AddComponent<FuckCameraComponent>();
public class FuckCameraComponent : MonoBehaviour
{
private Camera _cam;
private int _originalMask;
private Camera _pooledCam;
private bool fuck;
private void Awake()
{
_originalMask = _cam.cullingMask;
_cam.Reset();
// _cam.cullingMask = _originalMask;
}
/*private void OnPreCull()
{
MelonLogger.Msg("PreCull");
if (!TryGetComponent(out _cam)) return;
if (fuck)
{
// unset default layer
_originalMask &= ~(1 << 0);
_cam.cullingMask = _originalMask;
Destroy(this);
return;
}
_originalMask = _cam.cullingMask;
_cam.cullingMask = 0;
fuck = true;
}*/
/*private IEnumerator OnPostRender()
{
MelonLogger.Msg("PostRender");
// Restore the original mask if it has not changed since we set it to 0
if (_cam.cullingMask == 0) _cam.cullingMask = _originalMask;
_cam.enabled = false;
yield return new WaitForEndOfFrame();
_cam.enabled = true;
MelonLogger.Msg("FuckCameraComponent: OnPostRender called for camera: " + _cam.name);
// Destroy now that we have saved the day
enabled = false;
Destroy(this);
}*/
}
public class CameraPoolManager : MonoBehaviour
{
private static CameraPoolManager _instance;
public static CameraPoolManager Instance
{
get
{
if (_instance == null)
{
GameObject obj = new("CameraPoolManager");
obj.SetActive(false);
_instance = obj.AddComponent<CameraPoolManager>();
DontDestroyOnLoad(obj);
}
return _instance;
}
}
private readonly List<GameObject> _cameraObjects = new();
public Camera CreatePooledCamera(Camera source)
{
var go = new GameObject("PooledCamera");
go.SetActive(false);
var camera = go.AddComponent<Camera>();
camera.CopyFrom(source);
_cameraObjects.Add(go);
return camera;
}
public void RestoreCameraProperties(Camera target, Camera pooledCam)
{
// target.CopyFrom(pooledCam);
CopyCameraProperties(pooledCam, target);
}
public void ReleasePooledCamera(Camera pooledCam)
{
if (pooledCam != null)
{
var go = pooledCam.gameObject;
_cameraObjects.Remove(go);
Destroy(go);
}
}
// Skipped known diffs:
// nearClipPlane
// farClipPlane
// fieldOfView
// aspect
// cullingMask
// useOcclusionCulling
// clearFlags
// depthTextureMode
// pixelRect
// targetTexture
public static void CopyCameraProperties(Camera source, Camera target)
{
if (source == null || target == null) return;
target.nearClipPlane = source.nearClipPlane;
target.farClipPlane = source.farClipPlane;
target.fieldOfView = source.fieldOfView;
target.aspect = source.aspect;
int cullingMask = 0;
cullingMask = (cullingMask & ~(1 << 0)) | (source.cullingMask & (1 << 0));
cullingMask = (cullingMask & ~(1 << 1)) | (source.cullingMask & (1 << 1));
cullingMask = (cullingMask & ~(1 << 2)) | (source.cullingMask & (1 << 2));
cullingMask = (cullingMask & ~(1 << 3)) | (source.cullingMask & (1 << 3));
cullingMask = (cullingMask & ~(1 << 4)) | (source.cullingMask & (1 << 4));
cullingMask = (cullingMask & ~(1 << 5)) | (source.cullingMask & (1 << 5));
cullingMask = (cullingMask & ~(1 << 6)) | (source.cullingMask & (1 << 6));
cullingMask = (cullingMask & ~(1 << 7)) | (source.cullingMask & (1 << 7));
cullingMask = (cullingMask & ~(1 << 8)) | (source.cullingMask & (1 << 8));
cullingMask = (cullingMask & ~(1 << 9)) | (source.cullingMask & (1 << 9));
cullingMask = (cullingMask & ~(1 << 10)) | (source.cullingMask & (1 << 10));
cullingMask = (cullingMask & ~(1 << 11)) | (source.cullingMask & (1 << 11));
cullingMask = (cullingMask & ~(1 << 12)) | (source.cullingMask & (1 << 12));
cullingMask = (cullingMask & ~(1 << 13)) | (source.cullingMask & (1 << 13));
cullingMask = (cullingMask & ~(1 << 14)) | (source.cullingMask & (1 << 14));
cullingMask = (cullingMask & ~(1 << 15)) | (source.cullingMask & (1 << 15));
cullingMask = (cullingMask & ~(1 << 16)) | (source.cullingMask & (1 << 16));
cullingMask = (cullingMask & ~(1 << 17)) | (source.cullingMask & (1 << 17));
cullingMask = (cullingMask & ~(1 << 18)) | (source.cullingMask & (1 << 18));
cullingMask = (cullingMask & ~(1 << 19)) | (source.cullingMask & (1 << 19));
cullingMask = (cullingMask & ~(1 << 20)) | (source.cullingMask & (1 << 20));
cullingMask = (cullingMask & ~(1 << 21)) | (source.cullingMask & (1 << 21));
cullingMask = (cullingMask & ~(1 << 22)) | (source.cullingMask & (1 << 22));
cullingMask = (cullingMask & ~(1 << 23)) | (source.cullingMask & (1 << 23));
cullingMask = (cullingMask & ~(1 << 24)) | (source.cullingMask & (1 << 24));
cullingMask = (cullingMask & ~(1 << 25)) | (source.cullingMask & (1 << 25));
cullingMask = (cullingMask & ~(1 << 26)) | (source.cullingMask & (1 << 26));
cullingMask = (cullingMask & ~(1 << 27)) | (source.cullingMask & (1 << 27));
cullingMask = (cullingMask & ~(1 << 28)) | (source.cullingMask & (1 << 28));
cullingMask = (cullingMask & ~(1 << 29)) | (source.cullingMask & (1 << 29));
cullingMask = (cullingMask & ~(1 << 30)) | (source.cullingMask & (1 << 30));
cullingMask = (cullingMask & ~(1 << 31)) | (source.cullingMask & (1 << 31));
target.cullingMask = cullingMask;
target.clearFlags = source.clearFlags;
target.depthTextureMode = source.depthTextureMode;
target.useOcclusionCulling = source.useOcclusionCulling;
target.pixelRect = source.pixelRect;
target.targetTexture = source.targetTexture;
target.renderingPath = source.renderingPath;
target.allowHDR = source.allowHDR;
target.allowMSAA = source.allowMSAA;
target.allowDynamicResolution = source.allowDynamicResolution;
target.forceIntoRenderTexture = source.forceIntoRenderTexture;
target.orthographic = source.orthographic;
target.orthographicSize = source.orthographicSize;
target.depth = source.depth;
target.eventMask = source.eventMask;
target.layerCullSpherical = source.layerCullSpherical;
target.backgroundColor = source.backgroundColor;
target.clearStencilAfterLightingPass = source.clearStencilAfterLightingPass;
target.usePhysicalProperties = source.usePhysicalProperties;
// target.iso = source.iso;
// target.shutterSpeed = source.shutterSpeed;
// target.aperture = source.aperture;
// target.focusDistance = source.focusDistance;
// target.bladeCount = source.bladeCount;
// target.curvature = source.curvature;
// target.barrelClipping = source.barrelClipping;
// target.anamorphism = source.anamorphism;
// target.enabled = source.enabled;
// target.transform.position = source.transform.position;
// target.transform.rotation = source.transform.rotation;
// target.transform.localScale = source.transform.localScale;
target.focalLength = source.focalLength;
target.sensorSize = source.sensorSize;
target.lensShift = source.lensShift;
target.gateFit = source.gateFit;
target.rect = source.rect;
target.targetDisplay = source.targetDisplay;
target.stereoSeparation = source.stereoSeparation;
target.stereoConvergence = source.stereoConvergence;
target.stereoTargetEye = source.stereoTargetEye;
}
}
}

View file

@ -0,0 +1,32 @@
using MelonLoader;
using NAK.FuckCameras.Properties;
using System.Reflection;
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyTitle(nameof(NAK.FuckCameras))]
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
[assembly: AssemblyProduct(nameof(NAK.FuckCameras))]
[assembly: MelonInfo(
typeof(NAK.FuckCameras.FuckCamerasMod),
nameof(NAK.FuckCameras),
AssemblyInfoParams.Version,
AssemblyInfoParams.Author,
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/FuckCameras"
)]
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
[assembly: MelonColor(255, 246, 25, 99)] // red-pink
[assembly: MelonAuthorColor(255, 158, 21, 32)] // red
[assembly: HarmonyDontPatchAll]
namespace NAK.FuckCameras.Properties;
internal static class AssemblyInfoParams
{
public const string Version = "1.0.0";
public const string Author = "NotAKidoS";
}

View file

@ -0,0 +1,19 @@
# YouAreMyPropNowWeAreHavingSoftTacosLater
Lets you bring held & attached props through world loads.
https://youtu.be/9P6Jeh-VN58?si=eXTPGyKB_0wq1gZO
## Examples
https://fixupx.com/NotAKidoS/status/1910545346922422675
---
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI.
https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive.
> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use.
> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.

View file

@ -0,0 +1,23 @@
{
"_id": -1,
"name": "YouAreMyPropNowWeAreHavingSoftTacosLater",
"modversion": "1.0.0",
"gameversion": "2025r179",
"loaderversion": "0.6.1",
"modtype": "Mod",
"author": "NotAKidoS",
"description": "Lets you bring held & attached props through world loads.\nhttps://youtu.be/9P6Jeh-VN58?si=eXTPGyKB_0wq1gZO",
"searchtags": [
"prop",
"spawn",
"friend",
"load"
],
"requirements": [
"None"
],
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/YouAreMyPropNowWeAreHavingSoftTacosLater.dll",
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/YouAreMyPropNowWeAreHavingSoftTacosLater/",
"changelog": "- Initial Release",
"embedcolor": "#00FFFF"
}

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>YouAreMineNow</RootNamespace>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,57 @@
using System.Reflection;
using ABI_RC.Core.UI;
using HarmonyLib;
using MelonLoader;
namespace NAK.FuckCohtml2;
public class FuckCohtml2Mod : MelonMod
{
private static readonly MelonPreferences_Category Category =
MelonPreferences.CreateCategory(nameof(FuckCohtml2));
private static readonly MelonPreferences_Entry<bool> EntryFixShouldAdvance =
Category.CreateEntry(
identifier: "fix_should_advance",
true,
display_name: "Fix ShouldAdvance",
description: "Fix CohtmlControlledView.ShouldAdvance to respect the Enabled property.");
private static readonly MelonPreferences_Entry<bool> EntryFixShouldRender =
Category.CreateEntry(
identifier: "fix_should_render",
true,
display_name: "Fix ShouldRender",
description: "Fix CohtmlControlledView.ShouldRender to respect the Enabled property.");
public override void OnInitializeMelon()
{
PatchProperty(nameof(CohtmlControlledView.ShouldAdvance), nameof(OnShouldAdvance));
PatchProperty(nameof(CohtmlControlledView.ShouldRender), nameof(OnShouldRender));
}
private void PatchProperty(string propertyName, string handlerName)
{
PropertyInfo prop = typeof(CohtmlControlledView).GetProperty(propertyName,
BindingFlags.Public | BindingFlags.Instance);
MethodInfo getter = prop!.GetGetMethod(true);
MethodInfo postfixMethod = typeof(FuckCohtml2Mod).GetMethod(handlerName,
BindingFlags.NonPublic | BindingFlags.Static, null,
[typeof(object), typeof(bool).MakeByRefType()], null);
HarmonyInstance.Patch(getter, postfix: new HarmonyMethod(postfixMethod));
}
private static void OnShouldAdvance(object __instance, ref bool __result)
{
if (!EntryFixShouldAdvance.Value) return;
if (__instance is CohtmlControlledView inst) __result &= inst.Enabled;
}
private static void OnShouldRender(object __instance, ref bool __result)
{
if (!EntryFixShouldRender.Value) return;
if (__instance is CohtmlControlledView inst) __result &= inst.Enabled;
}
}

View file

@ -0,0 +1,32 @@
using MelonLoader;
using NAK.FuckCohtml2.Properties;
using System.Reflection;
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyTitle(nameof(NAK.FuckCohtml2))]
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
[assembly: AssemblyProduct(nameof(NAK.FuckCohtml2))]
[assembly: MelonInfo(
typeof(NAK.FuckCohtml2.FuckCohtml2Mod),
nameof(NAK.FuckCohtml2),
AssemblyInfoParams.Version,
AssemblyInfoParams.Author,
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/FuckCohtml2"
)]
[assembly: MelonGame("ChilloutVR", "ChilloutVR")]
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
[assembly: MelonColor(255, 246, 25, 99)] // red-pink
[assembly: MelonAuthorColor(255, 158, 21, 32)] // red
[assembly: HarmonyDontPatchAll]
namespace NAK.FuckCohtml2.Properties;
internal static class AssemblyInfoParams
{
public const string Version = "1.0.0";
public const string Author = "NotAKidoS";
}

View file

@ -0,0 +1,19 @@
# YouAreMyPropNowWeAreHavingSoftTacosLater
Lets you bring held & attached props through world loads.
https://youtu.be/9P6Jeh-VN58?si=eXTPGyKB_0wq1gZO
## Examples
https://fixupx.com/NotAKidoS/status/1910545346922422675
---
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI.
https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive.
> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use.
> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.

View file

@ -0,0 +1,23 @@
{
"_id": -1,
"name": "YouAreMyPropNowWeAreHavingSoftTacosLater",
"modversion": "1.0.0",
"gameversion": "2025r179",
"loaderversion": "0.6.1",
"modtype": "Mod",
"author": "NotAKidoS",
"description": "Lets you bring held & attached props through world loads.\nhttps://youtu.be/9P6Jeh-VN58?si=eXTPGyKB_0wq1gZO",
"searchtags": [
"prop",
"spawn",
"friend",
"load"
],
"requirements": [
"None"
],
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/YouAreMyPropNowWeAreHavingSoftTacosLater.dll",
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/YouAreMyPropNowWeAreHavingSoftTacosLater/",
"changelog": "- Initial Release",
"embedcolor": "#00FFFF"
}

View file

@ -17,7 +17,7 @@ using System.Reflection;
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/FuckOffUICamera"
)]
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
[assembly: MelonGame("ChilloutVR", "ChilloutVR")]
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
[assembly: MelonColor(255, 246, 25, 99)] // red-pink

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>YouAreMineNow</RootNamespace>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,39 @@
using ABI_RC.Core.Player;
using ABI_RC.Core.Util.AnimatorManager;
using ABI_RC.Systems.GameEventSystem;
using ABI.CCK.Components;
using MelonLoader;
using UnityEngine;
namespace NAK.IFUCKINGHATECAMERAS;
public class IFUCKINGHATECAMERASMod : MelonMod
{
private static readonly MelonPreferences_Category Category =
MelonPreferences.CreateCategory(nameof(IFUCKINGHATECAMERAS));
private static readonly MelonPreferences_Entry<bool> EntryRunHack =
Category.CreateEntry(
identifier: "run_hack",
true,
display_name: "Run Camera Hack (Avatars Only)?",
description: "Should the camera hack run? Btw I fucking hate cameras.");
public override void OnInitializeMelon()
{
CVRGameEventSystem.Avatar.OnRemoteAvatarLoad.AddListener(OnRemoteAvatarLoad);
}
private static void OnRemoteAvatarLoad(CVRPlayerEntity playerEntity, CVRAvatar avatar)
{
if (!EntryRunHack.Value) return;
// HACK: Fixes a native crash (animating camera off on first frame) due to culling in specific worlds.
// I am unsure the root cause, but the local player doesn't crash, and this is similar to what that does.
AvatarAnimatorManager AnimatorManager = playerEntity.PuppetMaster.AnimatorManager;
AnimatorManager.Animator.cullingMode = AnimatorCullingMode.AlwaysAnimate; // Set culling mode to always animate
AnimatorManager.Animator.Update(0f); // Update the animator to force it to do the first frame
AnimatorManager.Animator.cullingMode = AnimatorCullingMode.CullUpdateTransforms; // Set to cull update transforms
}
}

View file

@ -0,0 +1,32 @@
using MelonLoader;
using NAK.IFUCKINGHATECAMERAS.Properties;
using System.Reflection;
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyTitle(nameof(NAK.IFUCKINGHATECAMERAS))]
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
[assembly: AssemblyProduct(nameof(NAK.IFUCKINGHATECAMERAS))]
[assembly: MelonInfo(
typeof(NAK.IFUCKINGHATECAMERAS.IFUCKINGHATECAMERASMod),
nameof(NAK.IFUCKINGHATECAMERAS),
AssemblyInfoParams.Version,
AssemblyInfoParams.Author,
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/IFUCKINGHATECAMERAS"
)]
[assembly: MelonGame("ChilloutVR", "ChilloutVR")]
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
[assembly: MelonColor(255, 246, 25, 99)] // red-pink
[assembly: MelonAuthorColor(255, 158, 21, 32)] // red
[assembly: HarmonyDontPatchAll]
namespace NAK.IFUCKINGHATECAMERAS.Properties;
internal static class AssemblyInfoParams
{
public const string Version = "1.0.0";
public const string Author = "NotAKidoS";
}

View file

@ -0,0 +1,19 @@
# Tinyboard
Makes the keyboard small and smart.
Few small tweaks to the keyboard:
- Shrinks the keyboard to a size that isn't fit for grandma.
- Adjusts keyboard placement logic to align with the menu that it spawns from.
- Enforces a title on the keyboard input if one is not found.
---
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI.
https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive.
> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use.
> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.

View file

@ -0,0 +1,23 @@
{
"_id": -1,
"name": "Tinyboard",
"modversion": "1.0.0",
"gameversion": "2025r180",
"loaderversion": "0.7.2",
"modtype": "Mod",
"author": "NotAKidoS",
"description": "Few small tweaks to the keyboard:\n- Shrinks the keyboard to a size that isn't fit for grandma.\n- Adjusts keyboard placement logic to align with the menu that it spawns from.\n- Enforces a title on the keyboard input if one is not found.",
"searchtags": [
"keyboard",
"menu",
"ui",
"input"
],
"requirements": [
"None"
],
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/Tinyboard.dll",
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/Tinyboard/",
"changelog": "- Initial release",
"embedcolor": "#f61963"
}

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>LoadedObjectHack</RootNamespace>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,34 @@
using System.Reflection;
using ABI_RC.Systems.Movement;
using HarmonyLib;
using MelonLoader;
using UnityEngine;
namespace NAK.KeepVelocityOnExitFlight;
public class KeepVelocityOnExitFlightMod : MelonMod
{
public override void OnInitializeMelon()
{
HarmonyInstance.Patch(
typeof(BetterBetterCharacterController).GetMethod(nameof(BetterBetterCharacterController.ChangeFlight),
BindingFlags.Public | BindingFlags.Instance),
prefix: new HarmonyMethod(typeof(KeepVelocityOnExitFlightMod).GetMethod(nameof(Prefix_OnChangeFlight),
BindingFlags.NonPublic | BindingFlags.Static)),
postfix: new HarmonyMethod(typeof(KeepVelocityOnExitFlightMod).GetMethod(nameof(Postfix_OnChangeFlight),
BindingFlags.NonPublic | BindingFlags.Static))
);
}
// ReSharper disable once RedundantAssignment
private static void Prefix_OnChangeFlight(ref BetterBetterCharacterController __instance, ref Vector3 __state)
{
__state = __instance.GetVelocity();
}
private static void Postfix_OnChangeFlight(ref BetterBetterCharacterController __instance, ref Vector3 __state)
{
if (__instance.FlightAllowedInWorld && !__instance.IsFlying())
__instance.SetVelocity(__state);
}
}

View file

@ -0,0 +1,32 @@
using MelonLoader;
using NAK.KeepVelocityOnExitFlight.Properties;
using System.Reflection;
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyTitle(nameof(NAK.KeepVelocityOnExitFlight))]
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
[assembly: AssemblyProduct(nameof(NAK.KeepVelocityOnExitFlight))]
[assembly: MelonInfo(
typeof(NAK.KeepVelocityOnExitFlight.KeepVelocityOnExitFlightMod),
nameof(NAK.KeepVelocityOnExitFlight),
AssemblyInfoParams.Version,
AssemblyInfoParams.Author,
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/KeepVelocityOnExitFlight"
)]
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
[assembly: MelonColor(255, 246, 25, 99)] // red-pink
[assembly: MelonAuthorColor(255, 158, 21, 32)] // red
[assembly: HarmonyDontPatchAll]
namespace NAK.KeepVelocityOnExitFlight.Properties;
internal static class AssemblyInfoParams
{
public const string Version = "1.0.1";
public const string Author = "NotAKidoS";
}

View file

@ -0,0 +1,14 @@
# KeepVelocityOnExitFlight
Keeps the player's velocity when exiting flight mode. Makes it possible to fling yourself like in Garry's Mod.
---
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI.
https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive.
> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use.
> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.

View file

@ -0,0 +1,24 @@
{
"_id": 222,
"name": "KeepVelocityOnExitFlight",
"modversion": "1.0.1",
"gameversion": "2025r179",
"loaderversion": "0.6.1",
"modtype": "Mod",
"author": "NotAKidoS",
"description": "Keeps the player's velocity when exiting flight mode. Makes it possible to fling yourself like in Garry's Mod.",
"searchtags": [
"flight",
"mode",
"velocity",
"flying",
"fly"
],
"requirements": [
"None"
],
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/KeepVelocityOnExitFlight.dll",
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/KeepVelocityOnExitFlight/",
"changelog": "- Recompiled for 2025r179",
"embedcolor": "#f61963"
}

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>LoadedObjectHack</RootNamespace>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,214 @@
using System.Reflection;
using ABI_RC.Core.EventSystem;
using ABI_RC.Core.IO;
using ABI_RC.Core.Networking.API.Responses;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Systems.GameEventSystem;
using HarmonyLib;
using MelonLoader;
using UnityEngine;
namespace NAK.LazyPrune;
public class LazyPrune : MelonMod
{
private static MelonLogger.Instance Logger;
private const int MAX_OBJECTS_UNLOADED_AT_ONCE = 3; // just to alleviate hitch on mass destruction
private const float OBJECT_CACHE_TIMEOUT = 2f; // minutes
private static readonly Dictionary<CVRObjectLoader.LoadedObject, float> _loadedObjects = new();
private static string _lastLoadedWorld;
private static bool _isInitialized;
public override void OnInitializeMelon()
{
Logger = LoggerInstance;
// listen for world load
CVRGameEventSystem.World.OnLoad.AddListener(OnWorldLoaded);
// listen for local avatar bundle load
HarmonyInstance.Patch(
typeof(CVRObjectLoader).GetMethod(nameof(CVRObjectLoader.InstantiateAvatarFromExistingPrefab),
BindingFlags.Public | BindingFlags.Instance), // earliest callback (why the fuck are you public)
prefix: new HarmonyMethod(typeof(LazyPrune).GetMethod(nameof(OnInstantiateAvatarFromExistingPrefab),
BindingFlags.NonPublic | BindingFlags.Static))
);
// listen for prop bundle load
HarmonyInstance.Patch(
typeof(CVRObjectLoader).GetMethod(nameof(CVRObjectLoader.InstantiateSpawnableFromExistingPrefab),
BindingFlags.NonPublic | BindingFlags.Instance), // earliest callback
prefix: new HarmonyMethod(typeof(LazyPrune).GetMethod(nameof(OnInstantiateSpawnableFromExistingPrefab),
BindingFlags.NonPublic | BindingFlags.Static))
);
// listen for object destruction
HarmonyInstance.Patch(
typeof(CVRObjectLoader).GetMethod(nameof(CVRObjectLoader.CheckForDestruction),
BindingFlags.Public | BindingFlags.Instance), // earliest callback
prefix: new HarmonyMethod(typeof(LazyPrune).GetMethod(nameof(OnObjectDestroyed),
BindingFlags.NonPublic | BindingFlags.Static))
);
// nuke qm debug method as there is a race condition that locks up download queue
HarmonyInstance.Patch(
typeof(CVRDownloadManager).GetMethod(nameof(CVRDownloadManager.UpdateUiDownloadStatus),
BindingFlags.Public | BindingFlags.Instance),
prefix: new HarmonyMethod(typeof(LazyPrune).GetMethod(nameof(OnUpdateUiDownloadStatus),
BindingFlags.NonPublic | BindingFlags.Static))
);
// another race condition that causes instantiation queue to lock up
HarmonyInstance.Patch(
typeof(PuppetMaster).GetMethod(nameof(PuppetMaster.AvatarInstantiated),
BindingFlags.Public | BindingFlags.Instance),
prefix: new HarmonyMethod(typeof(LazyPrune).GetMethod(nameof(OnAvatarInstantiated),
BindingFlags.NonPublic | BindingFlags.Static))
);
}
#region Game Fixes
private static bool OnUpdateUiDownloadStatus() => false;
private static bool OnAvatarInstantiated(ref GameObject ___avatarObject)
{
if (___avatarObject != null) return true; // instantiate coroutine is try-catch'd, so purposefully throw to have it catch
Logger.Warning("Caught null avatar object in PuppetMaster.AvatarInstantiated. Throwing exception so ObjectLoader can catch.");
throw new Exception("LazyPrune: PuppetMaster Avatar instantiated with null object!");
}
#endregion Game Fixes
#region Game Events
private static void OnInstantiateAvatarFromExistingPrefab(string objectId, string instantiationTarget,
GameObject prefabObject,
ref CVRObjectLoader.LoadedObject loadedObject, string blockReason, AssetManagement.AvatarTags? avatarTags,
bool shouldFilter, bool isBlocked,
CompatibilityVersions compatibilityVersion)
{
if (prefabObject == MetaPort.Instance.blockedAvatarPrefab)
{
Logger.Msg($"Blocked avatar prefab is loading. Ignoring...");
return;
}
OnObjectCreated(ref loadedObject);
}
private static void OnInstantiateSpawnableFromExistingPrefab(string objectId, string instantiationTarget,
GameObject prefabObject, CVRObjectLoader.LoadedObject loadedObject, AssetManagement.PropTags propTags,
CompatibilityVersions compatibilityVersion)
{
if (prefabObject == MetaPort.Instance.blockedSpawnablePrefab)
{
Logger.Msg($"Blocked spawnable prefab is loading. Ignoring...");
return;
}
OnObjectCreated(ref loadedObject);
}
private static void OnWorldLoaded(string guid)
{
if (!_isInitialized)
{
// every minute, check for objects to prune
SchedulerSystem.AddJob(CheckForObjectsToPrune, 1f, 10f, -1);
_isInitialized = true;
}
if (_lastLoadedWorld != guid)
ForcePrunePendingObjects();
_lastLoadedWorld = guid;
}
private static void OnObjectCreated(ref CVRObjectLoader.LoadedObject ___loadedObject)
{
if (___loadedObject == null)
{
Logger.Error("Avatar/Prop created with no backed LoadedObject.");
return;
}
if (_loadedObjects.ContainsKey(___loadedObject))
{
_loadedObjects[___loadedObject] = -1; // mark as ineligible for pruning
return;
}
___loadedObject.refCount++; // increment ref count
_loadedObjects.Add(___loadedObject, -1); // mark as ineligible for pruning
}
private static void OnObjectDestroyed(CVRObjectLoader.LoadedObject loadedObject)
{
if (loadedObject == null)
{
Logger.Error("Avatar/Prop destroyed with no backed LoadedObject.");
return;
}
if (loadedObject.refCount > 1)
return;
if (_loadedObjects.ContainsKey(loadedObject))
_loadedObjects[loadedObject] = Time.time + OBJECT_CACHE_TIMEOUT * 60f;
}
#endregion Game Events
#region Lazy Pruning
private static void ForcePrunePendingObjects()
{
for (int i = _loadedObjects.Count - 1; i >= 0; i--)
{
(CVRObjectLoader.LoadedObject loadedObject, var killTime) = _loadedObjects.ElementAt(i);
if (killTime > 0) AttemptPruneObject(loadedObject);
}
}
private static void CheckForObjectsToPrune()
{
int unloaded = 0;
float time = Time.time;
for (int i = _loadedObjects.Count - 1; i >= 0; i--)
{
(CVRObjectLoader.LoadedObject loadedObject, var killTime) = _loadedObjects.ElementAt(i);
if (!(killTime < time) || killTime < 0) continue;
AttemptPruneObject(loadedObject);
if (unloaded++ >= MAX_OBJECTS_UNLOADED_AT_ONCE) break;
}
}
private static void AttemptPruneObject(CVRObjectLoader.LoadedObject loadedObject)
{
if (loadedObject == null)
{
Logger.Error("Attempted to prune null object. This happens on initial load sometimes.");
return;
}
if (loadedObject.refCount > 1)
{
Logger.Error($"Object {loadedObject.prefabName} has ref count {loadedObject.refCount}, expected 1");
_loadedObjects[loadedObject] = -1;
return;
}
Logger.Msg($"Pruning object {loadedObject.prefabName}");
_loadedObjects.Remove(loadedObject);
loadedObject.refCount--;
if (CVRObjectLoader.Instance != null)
CVRObjectLoader.Instance.CheckForDestruction(loadedObject);
}
#endregion Lazy Pruning
}

View file

@ -0,0 +1,32 @@
using MelonLoader;
using NAK.LazyPrune.Properties;
using System.Reflection;
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyTitle(nameof(NAK.LazyPrune))]
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
[assembly: AssemblyProduct(nameof(NAK.LazyPrune))]
[assembly: MelonInfo(
typeof(NAK.LazyPrune.LazyPrune),
nameof(NAK.LazyPrune),
AssemblyInfoParams.Version,
AssemblyInfoParams.Author,
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/LazyPrune"
)]
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
[assembly: MelonColor(255, 246, 25, 99)] // red-pink
[assembly: MelonAuthorColor(255, 158, 21, 32)] // red
[assembly: HarmonyDontPatchAll]
namespace NAK.LazyPrune.Properties;
internal static class AssemblyInfoParams
{
public const string Version = "1.0.3";
public const string Author = "NotAKidoS";
}

View file

@ -0,0 +1,16 @@
# LazyPrune
Prevents loaded objects from immediately unloading on destruction. Should prevent needlessly unloading & reloading all avatars/props on world rejoin or GS reconnection.
Unused objects are pruned after 3 minutes, or when loading into a different world.
---
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI.
https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive.
> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use.
> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.

View file

@ -0,0 +1,24 @@
{
"_id": 214,
"name": "LazyPrune",
"modversion": "1.0.3",
"gameversion": "2025r179",
"loaderversion": "0.6.1",
"modtype": "Mod",
"author": "NotAKidoS",
"description": "Prevents loaded objects from immediately unloading on destruction. Should prevent needlessly unloading & reloading all avatars/props on world rejoin or GS reconnection.\n\nUnused objects are pruned after 3 minutes, or when loading into a different world.",
"searchtags": [
"cache",
"prune",
"bundle",
"download",
"load"
],
"requirements": [
"None"
],
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/LazyPrune.dll",
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/LazyPrune/",
"changelog": "- Recompiled for 2025r179",
"embedcolor": "#1c75f1"
}

View file

@ -3,22 +3,6 @@
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine;
using UnityEngine.Rendering;
[RequireComponent(typeof(Camera))]
public class DepthTextureFix : MonoBehaviour
{

View file

@ -1,8 +1,6 @@
using ABI_RC.Core.InteractionSystem;
using ABI_RC.Core.InteractionSystem.Base;
using ABI_RC.Systems.InputManagement;
using UnityEngine;
using UnityEngine.Serialization;
namespace ABI_RC.Core.Player.Interaction
{

View file

@ -1,214 +1,214 @@
using ABI_RC.Core.Player.Interaction.RaycastImpl;
using ABI_RC.Core.Savior;
using ABI_RC.Systems.InputManagement;
using UnityEngine;
namespace ABI_RC.Core.Player.Interaction
{
public class CVRPlayerInteractionManager : MonoBehaviour
{
#region Singleton
public static CVRPlayerInteractionManager Instance { get; private set; }
#endregion Singleton
#region Serialized Fields
[Header("Hand Components")]
[SerializeField] private CVRPlayerHand handVrLeft;
[SerializeField] private CVRPlayerHand handVrRight;
[SerializeField] private CVRPlayerHand handDesktopRight; // Desktop does not have a left hand
[Header("Raycast Transforms")]
[SerializeField] private Transform raycastTransformVrRight;
[SerializeField] private Transform raycastTransformVrLeft;
[SerializeField] private Transform raycastTransformDesktopRight;
[Header("Settings")]
[SerializeField] private bool interactionEnabled = true;
[SerializeField] private LayerMask interactionLayerMask = -1; // Default to all layers, will be filtered
#endregion Serialized Fields
#region Properties
private CVRPlayerHand _rightHand;
private CVRPlayerHand _leftHand;
private CVRPlayerRaycaster _rightRaycaster;
private CVRPlayerRaycaster _leftRaycaster;
private CVRRaycastResult _rightRaycastResult;
private CVRRaycastResult _leftRaycastResult;
// Input handler
private CVRPlayerInputHandler _inputHandler;
// Interaction flags
public bool InteractionEnabled
{
get => interactionEnabled;
set => interactionEnabled = value;
}
#endregion Properties
#region Unity Events
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
// Create the input handler
_inputHandler = gameObject.AddComponent<CVRPlayerInputHandler>();
}
private void Start()
{
// Setup interaction for current device mode
SetupInteractionForDeviceMode();
// Listen for VR mode changes
MetaPort.Instance.onVRModeSwitch.AddListener(SetupInteractionForDeviceMode);
}
private void Update()
{
if (!interactionEnabled)
return;
// Process right hand
if (_rightRaycaster != null)
{
// Determine raycast flags based on current mode
CVRPlayerRaycaster.RaycastFlags flags = DetermineRaycastFlags(_rightHand);
// Get raycast results
_rightRaycastResult = _rightRaycaster.GetRaycastResults(flags);
// Process input based on raycast results
_inputHandler.ProcessInput(CVRHand.Right, _rightRaycastResult);
}
// Process left hand (if available)
if (_leftRaycaster != null)
{
// Determine raycast flags based on current mode
CVRPlayerRaycaster.RaycastFlags flags = DetermineRaycastFlags(_leftHand);
// Get raycast results
_leftRaycastResult = _leftRaycaster.GetRaycastResults(flags);
// Process input based on raycast results
_inputHandler.ProcessInput(CVRHand.Left, _leftRaycastResult);
}
}
private void OnDestroy()
{
// Clean up event listener
if (MetaPort.Instance != null)
MetaPort.Instance.onVRModeSwitch.RemoveListener(SetupInteractionForDeviceMode);
}
#endregion Unity Events
#region Public Methods
/// <summary>
/// Register a custom tool mode
/// </summary>
public void RegisterCustomToolMode(System.Action<CVRHand, CVRRaycastResult, InputState> callback)
{
_inputHandler.RegisterCustomTool(callback);
}
/// <summary>
/// Unregister the current custom tool mode
/// </summary>
public void UnregisterCustomToolMode()
{
_inputHandler.UnregisterCustomTool();
}
/// <summary>
/// Set the interaction mode
/// </summary>
public void SetInteractionMode(CVRPlayerInputHandler.InteractionMode mode)
{
_inputHandler.SetInteractionMode(mode);
}
/// <summary>
/// Get the raycast result for a specific hand
/// </summary>
public CVRRaycastResult GetRaycastResult(CVRHand hand)
{
return hand == CVRHand.Left ? _leftRaycastResult : _rightRaycastResult;
}
#endregion Public Methods
#region Private Methods
private void SetupInteractionForDeviceMode()
{
bool isVr = MetaPort.Instance.isUsingVr;
if (isVr)
{
// VR mode
_rightHand = handVrRight;
_leftHand = handVrLeft;
// VR uses the controller transform for raycasting
_rightRaycaster = new CVRPlayerRaycasterTransform(raycastTransformVrRight);
_leftRaycaster = new CVRPlayerRaycasterTransform(raycastTransformVrLeft);
}
else
{
// Desktop mode
_rightHand = handDesktopRight;
_leftHand = null;
// Desktop uses the mouse position for raycasting when unlocked
Camera desktopCamera = PlayerSetup.Instance.desktopCam;
_rightRaycaster = new CVRPlayerRaycasterMouse(raycastTransformDesktopRight, desktopCamera);
_leftRaycaster = null;
}
// Set the layer mask for raycasters
if (_rightRaycaster != null)
_rightRaycaster.SetLayerMask(interactionLayerMask);
if (_leftRaycaster != null)
_leftRaycaster.SetLayerMask(interactionLayerMask);
}
private static CVRPlayerRaycaster.RaycastFlags DetermineRaycastFlags(CVRPlayerHand hand)
{
// Default to all flags
CVRPlayerRaycaster.RaycastFlags flags = CVRPlayerRaycaster.RaycastFlags.All;
// Check if hand is holding a pickup
if (hand != null && hand.IsHoldingObject)
{
// When holding an object, only check for COHTML interaction
flags = CVRPlayerRaycaster.RaycastFlags.CohtmlInteract;
}
// Could add more conditional flag adjustments here based on the current mode
// For example, in a teleport tool mode, you might only want world hits
return flags;
}
#endregion Private Methods
}
}
// using ABI_RC.Core.Player.Interaction.RaycastImpl;
// using ABI_RC.Core.Savior;
// using ABI_RC.Systems.InputManagement;
// using UnityEngine;
//
// namespace ABI_RC.Core.Player.Interaction
// {
// public class CVRPlayerInteractionManager : MonoBehaviour
// {
// #region Singleton
//
// public static CVRPlayerInteractionManager Instance { get; private set; }
//
// #endregion Singleton
//
// #region Serialized Fields
//
// [Header("Hand Components")]
// [SerializeField] private CVRPlayerHand handVrLeft;
// [SerializeField] private CVRPlayerHand handVrRight;
// [SerializeField] private CVRPlayerHand handDesktopRight; // Desktop does not have a left hand
//
// [Header("Raycast Transforms")]
// [SerializeField] private Transform raycastTransformVrRight;
// [SerializeField] private Transform raycastTransformVrLeft;
// [SerializeField] private Transform raycastTransformDesktopRight;
//
// [Header("Settings")]
// [SerializeField] private bool interactionEnabled = true;
// [SerializeField] private LayerMask interactionLayerMask = -1; // Default to all layers, will be filtered
//
// #endregion Serialized Fields
//
// #region Properties
//
// private CVRPlayerHand _rightHand;
// private CVRPlayerHand _leftHand;
//
// private CVRPlayerRaycaster _rightRaycaster;
// private CVRPlayerRaycaster _leftRaycaster;
//
// private CVRRaycastResult _rightRaycastResult;
// private CVRRaycastResult _leftRaycastResult;
//
// // Input handler
// private CVRPlayerInputHandler _inputHandler;
//
// // Interaction flags
// public bool InteractionEnabled
// {
// get => interactionEnabled;
// set => interactionEnabled = value;
// }
//
// #endregion Properties
//
// #region Unity Events
//
// private void Awake()
// {
// if (Instance != null && Instance != this)
// {
// Destroy(gameObject);
// return;
// }
// Instance = this;
//
// // Create the input handler
// _inputHandler = gameObject.AddComponent<CVRPlayerInputHandler>();
// }
//
// private void Start()
// {
// // Setup interaction for current device mode
// SetupInteractionForDeviceMode();
//
// // Listen for VR mode changes
// MetaPort.Instance.onVRModeSwitch.AddListener(SetupInteractionForDeviceMode);
// }
//
// private void Update()
// {
// if (!interactionEnabled)
// return;
//
// // Process right hand
// if (_rightRaycaster != null)
// {
// // Determine raycast flags based on current mode
// CVRPlayerRaycaster.RaycastFlags flags = DetermineRaycastFlags(_rightHand);
//
// // Get raycast results
// _rightRaycastResult = _rightRaycaster.GetRaycastResults(flags);
//
// // Process input based on raycast results
// _inputHandler.ProcessInput(CVRHand.Right, _rightRaycastResult);
// }
//
// // Process left hand (if available)
// if (_leftRaycaster != null)
// {
// // Determine raycast flags based on current mode
// CVRPlayerRaycaster.RaycastFlags flags = DetermineRaycastFlags(_leftHand);
//
// // Get raycast results
// _leftRaycastResult = _leftRaycaster.GetRaycastResults(flags);
//
// // Process input based on raycast results
// _inputHandler.ProcessInput(CVRHand.Left, _leftRaycastResult);
// }
// }
//
// private void OnDestroy()
// {
// // Clean up event listener
// if (MetaPort.Instance != null)
// MetaPort.Instance.onVRModeSwitch.RemoveListener(SetupInteractionForDeviceMode);
// }
//
// #endregion Unity Events
//
// #region Public Methods
//
// /// <summary>
// /// Register a custom tool mode
// /// </summary>
// public void RegisterCustomToolMode(System.Action<CVRHand, CVRRaycastResult, InputState> callback)
// {
// _inputHandler.RegisterCustomTool(callback);
// }
//
// /// <summary>
// /// Unregister the current custom tool mode
// /// </summary>
// public void UnregisterCustomToolMode()
// {
// _inputHandler.UnregisterCustomTool();
// }
//
// /// <summary>
// /// Set the interaction mode
// /// </summary>
// public void SetInteractionMode(CVRPlayerInputHandler.InteractionMode mode)
// {
// _inputHandler.SetInteractionMode(mode);
// }
//
// /// <summary>
// /// Get the raycast result for a specific hand
// /// </summary>
// public CVRRaycastResult GetRaycastResult(CVRHand hand)
// {
// return hand == CVRHand.Left ? _leftRaycastResult : _rightRaycastResult;
// }
//
// #endregion Public Methods
//
// #region Private Methods
//
// private void SetupInteractionForDeviceMode()
// {
// bool isVr = MetaPort.Instance.isUsingVr;
//
// if (isVr)
// {
// // VR mode
// _rightHand = handVrRight;
// _leftHand = handVrLeft;
//
// // VR uses the controller transform for raycasting
// _rightRaycaster = new CVRPlayerRaycasterTransform(raycastTransformVrRight);
// _leftRaycaster = new CVRPlayerRaycasterTransform(raycastTransformVrLeft);
// }
// else
// {
// // Desktop mode
// _rightHand = handDesktopRight;
// _leftHand = null;
//
// // Desktop uses the mouse position for raycasting when unlocked
// Camera desktopCamera = PlayerSetup.Instance.desktopCam;
// _rightRaycaster = new CVRPlayerRaycasterMouse(raycastTransformDesktopRight, desktopCamera);
// _leftRaycaster = null;
// }
//
// // Set the layer mask for raycasters
// if (_rightRaycaster != null)
// _rightRaycaster.SetLayerMask(interactionLayerMask);
//
// if (_leftRaycaster != null)
// _leftRaycaster.SetLayerMask(interactionLayerMask);
// }
//
// private static CVRPlayerRaycaster.RaycastFlags DetermineRaycastFlags(CVRPlayerHand hand)
// {
// // Default to all flags
// CVRPlayerRaycaster.RaycastFlags flags = CVRPlayerRaycaster.RaycastFlags.All;
//
// // Check if hand is holding a pickup
// if (hand != null && hand.IsHoldingObject)
// {
// // When holding an object, only check for COHTML interaction
// flags = CVRPlayerRaycaster.RaycastFlags.CohtmlInteract;
// }
//
// // Could add more conditional flag adjustments here based on the current mode
// // For example, in a teleport tool mode, you might only want world hits
//
// return flags;
// }
//
// #endregion Private Methods
// }
// }

View file

@ -1,121 +1,121 @@
using ABI_RC.Core.Base;
using ABI_RC.Core.Player;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace NAK.SuperAwesomeMod.Components
{
public class CVRCanvasWrapper : MonoBehaviour
{
public bool IsInteractable = true;
public float MaxInteractDistance = 10f;
private Canvas _canvas;
private GraphicRaycaster _graphicsRaycaster;
private static readonly List<RaycastResult> _raycastResults = new();
private static readonly PointerEventData _pointerEventData = new(EventSystem.current);
private static Selectable _workingSelectable;
private Camera _camera;
private RectTransform _rectTransform;
#region Unity Events
private void Awake()
{
if (!TryGetComponent(out _canvas)
|| _canvas.renderMode != RenderMode.WorldSpace)
{
IsInteractable = false;
return;
}
_rectTransform = _canvas.GetComponent<RectTransform>();
}
private void Start()
{
_graphicsRaycaster = _canvas.gameObject.AddComponent<GraphicRaycaster>();
_camera = PlayerSetup.Instance.activeCam;
_canvas.worldCamera = _camera;
}
#endregion Unity Events
#region Public Methods
public bool GetGraphicsHit(Ray worldRay, out RaycastResult result)
{
result = default;
if (!IsInteractable || _camera == null) return false;
// Get the plane of the canvas
Plane canvasPlane = new(transform.forward, transform.position);
// Find where the ray intersects the canvas plane
if (!canvasPlane.Raycast(worldRay, out float distance))
return false;
// Get the world point of intersection
Vector3 worldHitPoint = worldRay.origin + worldRay.direction * distance;
// Check if hit point is within max interaction distance
if (Vector3.Distance(worldRay.origin, worldHitPoint) > MaxInteractDistance)
return false;
// Check if hit point is within canvas bounds
Vector3 localHitPoint = transform.InverseTransformPoint(worldHitPoint);
Rect canvasRect = _rectTransform.rect;
if (!canvasRect.Contains(new Vector2(localHitPoint.x, localHitPoint.y)))
return false;
// Convert world hit point to screen space
Vector2 screenPoint = _camera.WorldToScreenPoint(worldHitPoint);
// Update pointer event data
_pointerEventData.position = screenPoint;
_pointerEventData.delta = Vector2.zero;
// Clear previous results and perform raycast
_raycastResults.Clear();
_graphicsRaycaster.Raycast(_pointerEventData, _raycastResults);
// Early out if no hits
if (_raycastResults.Count == 0)
{
//Debug.Log($"No hits on canvas {_canvas.name}");
return false;
}
// Find first valid interactive UI element
foreach (RaycastResult hit in _raycastResults)
{
if (!hit.isValid)
{
//Debug.Log($"Invalid hit on canvas {_canvas.name}");
continue;
}
// Check if the hit object has a Selectable component and is interactable
GameObject hitObject = hit.gameObject;
if (!hitObject.TryGetComponent(out _workingSelectable)
|| !_workingSelectable.interactable)
{
//Debug.Log($"Non-interactable hit on canvas {_canvas.name} - {hitObject.name}");
continue;
}
//Debug.Log($"Hit on canvas {_canvas.name} with {hitObject.name}");
result = hit;
return true;
}
return false;
}
#endregion Public Methods
}
}
// using ABI_RC.Core.Base;
// using ABI_RC.Core.Player;
// using UnityEngine;
// using UnityEngine.EventSystems;
// using UnityEngine.UI;
//
// namespace NAK.SuperAwesomeMod.Components
// {
// public class CVRCanvasWrapper : MonoBehaviour
// {
// public bool IsInteractable = true;
// public float MaxInteractDistance = 10f;
//
// private Canvas _canvas;
// private GraphicRaycaster _graphicsRaycaster;
// private static readonly List<RaycastResult> _raycastResults = new();
// private static readonly PointerEventData _pointerEventData = new(EventSystem.current);
//
// private static Selectable _workingSelectable;
// private Camera _camera;
// private RectTransform _rectTransform;
//
// #region Unity Events
//
// private void Awake()
// {
// if (!TryGetComponent(out _canvas)
// || _canvas.renderMode != RenderMode.WorldSpace)
// {
// IsInteractable = false;
// return;
// }
//
// _rectTransform = _canvas.GetComponent<RectTransform>();
// }
//
// private void Start()
// {
// _graphicsRaycaster = _canvas.gameObject.AddComponent<GraphicRaycaster>();
// _camera = PlayerSetup.Instance.activeCam;
// _canvas.worldCamera = _camera;
// }
//
// #endregion Unity Events
//
// #region Public Methods
//
// public bool GetGraphicsHit(Ray worldRay, out RaycastResult result)
// {
// result = default;
//
// if (!IsInteractable || _camera == null) return false;
//
// // Get the plane of the canvas
// Plane canvasPlane = new(transform.forward, transform.position);
//
// // Find where the ray intersects the canvas plane
// if (!canvasPlane.Raycast(worldRay, out float distance))
// return false;
//
// // Get the world point of intersection
// Vector3 worldHitPoint = worldRay.origin + worldRay.direction * distance;
//
// // Check if hit point is within max interaction distance
// if (Vector3.Distance(worldRay.origin, worldHitPoint) > MaxInteractDistance)
// return false;
//
// // Check if hit point is within canvas bounds
// Vector3 localHitPoint = transform.InverseTransformPoint(worldHitPoint);
// Rect canvasRect = _rectTransform.rect;
// if (!canvasRect.Contains(new Vector2(localHitPoint.x, localHitPoint.y)))
// return false;
//
// // Convert world hit point to screen space
// Vector2 screenPoint = _camera.WorldToScreenPoint(worldHitPoint);
//
// // Update pointer event data
// _pointerEventData.position = screenPoint;
// _pointerEventData.delta = Vector2.zero;
//
// // Clear previous results and perform raycast
// _raycastResults.Clear();
// _graphicsRaycaster.Raycast(_pointerEventData, _raycastResults);
//
// // Early out if no hits
// if (_raycastResults.Count == 0)
// {
// //Debug.Log($"No hits on canvas {_canvas.name}");
// return false;
// }
//
// // Find first valid interactive UI element
// foreach (RaycastResult hit in _raycastResults)
// {
// if (!hit.isValid)
// {
// //Debug.Log($"Invalid hit on canvas {_canvas.name}");
// continue;
// }
//
// // Check if the hit object has a Selectable component and is interactable
// GameObject hitObject = hit.gameObject;
// if (!hitObject.TryGetComponent(out _workingSelectable)
// || !_workingSelectable.interactable)
// {
// //Debug.Log($"Non-interactable hit on canvas {_canvas.name} - {hitObject.name}");
// continue;
// }
//
// //Debug.Log($"Hit on canvas {_canvas.name} with {hitObject.name}");
//
// result = hit;
// return true;
// }
//
// return false;
// }
//
// #endregion Public Methods
// }
// }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,85 @@
using ABI_RC.Core;
using ABI_RC.Core.InteractionSystem;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Systems.InputManagement;
using UnityEngine;
using UnityEngine.EventSystems;
namespace NAK.SuperAwesomeMod.Interaction;
[DefaultExecutionOrder(1000)]
public class CustomBaseInput : BaseInput
{
private Vector2 mousePositionCache;
#region Input Overrides
public override Vector2 mousePosition => Input.mousePosition;
public override bool GetMouseButton(int button)
=> button == (int)CVRHand.Right
? CVRInputManager.Instance.interactLeftValue > 0.75f
: CVRInputManager.Instance.interactRightValue > 0.75f;
public override bool GetMouseButtonDown(int button)
=> button == (int)CVRHand.Right
? CVRInputManager.Instance.interactLeftDown
: CVRInputManager.Instance.interactRightDown;
public override Vector2 mouseScrollDelta => Vector2.zero;
public override float GetAxisRaw(string axisName)
{
return axisName switch
{
"Mouse ScrollWheel" => CVRInputManager.Instance.scrollValue,
"Horizontal" => CVRInputManager.Instance.movementVector.x,
"Vertical" => CVRInputManager.Instance.movementVector.y,
_ => 0f
};
}
public override bool GetButtonDown(string buttonName)
{
return buttonName switch
{
"Mouse ScrollWheel" => CVRInputManager.Instance.scrollValue > 0.1f,
"Horizontal" => CVRInputManager.Instance.movementVector.x > 0.5f,
"Vertical" => CVRInputManager.Instance.movementVector.y > 0.5f,
_ => false
};
}
#endregion Input Overrides
private CVRHand lastInteractHand;
private void Update()
{
if (!MetaPort.Instance.isUsingVr)
{
mousePositionCache = Input.mousePosition;
return;
}
ControllerRay leftRay = PlayerSetup.Instance.vrRayLeft;
ControllerRay rightRay = PlayerSetup.Instance.vrRayRight;
if (leftRay._interactDown) lastInteractHand = leftRay.hand;
if (rightRay._interactDown) lastInteractHand = rightRay.hand;
Camera vrCamera = PlayerSetup.Instance.vrCam;
// transform the raycast position to screen position
Vector3 hitPoint = lastInteractHand == CVRHand.Left
? leftRay.HitPoint
: rightRay.HitPoint;
Vector3 screenPoint = vrCamera.WorldToScreenPoint(hitPoint);
screenPoint.x = Mathf.Clamp(screenPoint.x, 0, Screen.width);
screenPoint.y = Mathf.Clamp(screenPoint.y, 0, Screen.height);
mousePositionCache = new Vector2(screenPoint.x, screenPoint.y);
}
}

View file

@ -0,0 +1,67 @@
using UnityEngine;
using UnityEngine.EventSystems;
namespace NAK.SuperAwesomeMod.Interaction;
public class CustomInputModule : StandaloneInputModule
{
bool meow = false;
#region Unity Events
protected override void Start()
{
base.Start();
m_InputOverride = gameObject.AddComponent<CustomBaseInput>();
// Disable other event systems in the scene
DisableOtherEventSystems();
}
#endregion
#region Overrides
public override void Process()
{
CursorLockMode currentLockState = Cursor.lockState;
Cursor.lockState = CursorLockMode.None;
base.Process();
Cursor.lockState = currentLockState;
}
protected override MouseState GetMousePointerEventData(int id)
{
MouseState pointerEventData = base.GetMousePointerEventData(id);
MouseButtonEventData leftEventData = pointerEventData.GetButtonState(PointerEventData.InputButton.Left).eventData;
RaycastResult pointerRaycast = leftEventData.buttonData.pointerCurrentRaycast;
if (meow) leftEventData.buttonData.pointerCurrentRaycast = new RaycastResult();
return pointerEventData;
}
#endregion Overrides
#region Private Methods
private void DisableOtherEventSystems()
{
EventSystem thisEventSystem = GetComponent<EventSystem>();
EventSystem[] systems = FindObjectsOfType<EventSystem>();
foreach (EventSystem system in systems)
{
if (system.gameObject.name == "UniverseLibCanvas") continue;
if (system != thisEventSystem)
{
system.enabled = false;
}
}
}
#endregion Private Methods
}

View file

@ -1,9 +1,7 @@
using ABI_RC.Core.InteractionSystem.Base;
using ABI_RC.Core.UI;
using ABI.CCK.Components;
using NAK.SuperAwesomeMod.Components;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace ABI_RC.Core.Player.Interaction.RaycastImpl
@ -108,7 +106,8 @@ namespace ABI_RC.Core.Player.Interaction.RaycastImpl
// Check if there are pickups or interactables in immediate proximity
if ((flags & RaycastFlags.ProximityInteract) != 0)
{
ProcessProximityHits(ray, ref result); // TODO: Offset origin to center of palm based on hand type
Ray proximityRay = GetProximityRayFromImpl();
ProcessProximityHits(proximityRay, ref result);
if (result.isProximityHit)
return result;
}
@ -150,7 +149,7 @@ namespace ABI_RC.Core.Player.Interaction.RaycastImpl
int proximityHits = Physics.SphereCastNonAlloc(
ray.origin,
RAYCAST_SPHERE_RADIUS,
Vector3.up,
ray.direction,
_hits,
0.001f,
_layerMask,
@ -313,6 +312,7 @@ namespace ABI_RC.Core.Player.Interaction.RaycastImpl
result.hitInteractable = _workingInteractable;
hitValidComponent = true;
}
if (_workingGameObject.TryGetComponent(out _workingPickupable)
&& _workingPickupable.CanPickup
&& IsCVRPickupableWithinRange(_workingPickupable, hit))
@ -343,6 +343,7 @@ namespace ABI_RC.Core.Player.Interaction.RaycastImpl
#region Protected Methods
protected abstract Ray GetRayFromImpl();
protected abstract Ray GetProximityRayFromImpl();
#endregion Protected Methods
@ -375,10 +376,10 @@ namespace ABI_RC.Core.Player.Interaction.RaycastImpl
return hit.distance <= pickupable.MaxGrabDistance;
}
private static bool IsCVRCanvasWrapperWithinRange(CVRCanvasWrapper canvasWrapper, RaycastHit hit)
{
return hit.distance <= canvasWrapper.MaxInteractDistance;
}
// private static bool IsCVRCanvasWrapperWithinRange(CVRCanvasWrapper canvasWrapper, RaycastHit hit)
// {
// return hit.distance <= canvasWrapper.MaxInteractDistance;
// }
#endregion Utility Because Original Methods Are Broken
}

View file

@ -4,10 +4,24 @@ namespace ABI_RC.Core.Player.Interaction.RaycastImpl
{
public class CVRPlayerRaycasterMouse : CVRPlayerRaycaster
{
private readonly Camera _camera;
#region Constructor
public CVRPlayerRaycasterMouse(Transform rayOrigin, Camera camera) : base(rayOrigin) { _camera = camera; }
private readonly Camera _camera;
#endregion Constructor
#region Overrides
protected override Ray GetRayFromImpl() => Cursor.lockState == CursorLockMode.Locked
? new Ray(_camera.transform.position, _camera.transform.forward)
: _camera.ScreenPointToRay(Input.mousePosition);
protected override Ray GetProximityRayFromImpl() => Cursor.lockState == CursorLockMode.Locked
? new Ray(_camera.transform.position, _camera.transform.forward)
: _camera.ScreenPointToRay(Input.mousePosition);
#endregion Overrides
}
}

View file

@ -4,7 +4,44 @@ namespace ABI_RC.Core.Player.Interaction.RaycastImpl
{
public class CVRPlayerRaycasterTransform : CVRPlayerRaycaster
{
public CVRPlayerRaycasterTransform(Transform rayOrigin) : base(rayOrigin) { }
#region Proximity Grab
public const float ProximityGrabRadiusScaleDefault = 0.1f;
private float _proximityDetectionRadiusRelativeValue = ProximityGrabRadiusScaleDefault;
private float ProximityDetectionRadius => _proximityDetectionRadiusRelativeValue * PlayerSetup.Instance.GetPlaySpaceScale();
#endregion Proximity Grab
#region Constructor
public CVRPlayerRaycasterTransform(Transform rayOrigin, CVRHand hand) : base(rayOrigin) { _hand = hand; }
private readonly CVRHand _hand;
#endregion Constructor
#region Overrides
protected override Ray GetRayFromImpl() => new(_rayOrigin.position, _rayOrigin.forward);
protected override Ray GetProximityRayFromImpl()
{
Vector3 handPosition = _rayOrigin.position;
Vector3 handRight = _rayOrigin.right;
// Offset the detection center forward, so we don't grab stuff behind our writs
handPosition += _rayOrigin.forward * (ProximityDetectionRadius * 0.25f);
// Offset the detection center away from the palm, so we don't grab stuff behind our hand palm
Vector3 palmOffset = handRight * (ProximityDetectionRadius * 0.75f);
if (_hand == CVRHand.Left)
handPosition += palmOffset;
else
handPosition -= palmOffset;
return new Ray(handPosition, _hand == CVRHand.Left ? handRight : -handRight);
}
#endregion Overrides
}
}

View file

@ -1,8 +1,6 @@
using ABI_RC.Core.InteractionSystem.Base;
using ABI_RC.Core.UI;
using NAK.SuperAwesomeMod.Components;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace ABI_RC.Core.Player.Interaction.RaycastImpl
@ -19,6 +17,7 @@ namespace ABI_RC.Core.Player.Interaction.RaycastImpl
// Main raycast hit info
public RaycastHit hit;
public RaycastHit? waterHit; // Only valid if hitWater is true
public Vector2 hitScreenPoint; // Screen coordinates of the hit
// Specific hit components
public Pickupable hitPickupable;

View file

@ -1,5 +1,4 @@
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace ABI_RC.Core.Player.Interaction.RaycastImpl

View file

@ -0,0 +1,362 @@
using ABI_RC.Core.Base;
using ABI_RC.Core.InteractionSystem;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI.CCK.Components;
using MelonLoader;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace NAK.SuperAwesomeMod.UExplorer;
public class UEMenuHelper : MenuPositionHelperBase
{
#region Singleton
public static void Create()
{
if (Instance != null)
return;
_universeLibCanvas = GameObject.Find("UniverseLibCanvas");
if (_universeLibCanvas == null)
{
MelonLogger.Error(
"Failed to create UniverseLibCanvas"); // TODO: mod logger, casue https://github.com/knah/VRCMods/pull/227
return;
}
_explorerRoot = _universeLibCanvas.transform.Find("com.sinai.unityexplorer_Root").gameObject;
// Fix the canvas so it renders in the UI camera
// _universeLibCanvas.SetLayerRecursive(CVRLayers.UIInternal);
Transform menuParent = new GameObject("UEMenuParent").transform;
menuParent.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
menuParent.localScale = Vector3.one;
DontDestroyOnLoad(menuParent.gameObject);
Transform offsetTransform = new GameObject("UEMenuOffset").transform;
offsetTransform.SetParent(menuParent, false);
offsetTransform.localScale = Vector3.one;
Transform contentTransform = new GameObject("UEMenuContent").transform;
contentTransform.SetParent(offsetTransform, false);
contentTransform.localScale = Vector3.one;
Instance = menuParent.AddComponentIfMissing<UEMenuHelper>();
Instance.menuTransform = contentTransform;
// Instance._offsetTransform = offsetTransform; // Got in MenuPositionHelperBase.Start
// Apply the component filters done in worlds
// foreach (Component c in _universeLibCanvas.GetComponentsInChildren<Component>(true))
// SetupCollidersOnUnityUi(c);
CVRCanvasWrapper.AddForCanvas(_universeLibCanvas.GetComponent<Canvas>(), true);
Instance.ConfigureUECanvasRenderMode(RenderMode.WorldSpace);
}
public static UEMenuHelper Instance { get; private set; }
private static GameObject _universeLibCanvas;
private static GameObject _explorerRoot;
private static RenderMode _currentRenderMode = RenderMode.WorldSpace;
#endregion Singleton
#region Overrides
public override bool IsMenuOpen => _explorerRoot.activeInHierarchy;
public override float MenuScaleModifier => !MetaPort.Instance.isUsingVr ? 1f : 0.3f;
public override float MenuDistanceModifier => !MetaPort.Instance.isUsingVr ? 1.2f : 1f;
#endregion Overrides
#region Unity Events
private void Update()
{
if (Input.GetKeyDown(KeyCode.F9))
ToggleUeCanvasRenderMode();
}
#endregion Unity Events
#region Private Methods
private void ToggleUeCanvasRenderMode()
{
ConfigureUECanvasRenderMode(_currentRenderMode == RenderMode.WorldSpace
? RenderMode.ScreenSpaceOverlay
: RenderMode.WorldSpace);
}
private void ConfigureUECanvasRenderMode(RenderMode targetMode)
{
_currentRenderMode = targetMode;
var canvases = _universeLibCanvas.GetComponentsInChildren<Canvas>(true);
if (targetMode == RenderMode.WorldSpace)
{
foreach (Canvas canvas in canvases)
{
canvas.renderMode = RenderMode.WorldSpace;
canvas.worldCamera = PlayerSetup.Instance.activeCam;
}
_universeLibCanvas.transform.SetParent(menuTransform, false);
_universeLibCanvas.transform.localScale = Vector3.one * 0.0032f;
// Center the canvas on the menuTransform
CenterCanvasOnMenuTransform(_universeLibCanvas, menuTransform);
return;
}
foreach (Canvas canvas in canvases)
{
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvas.worldCamera = null;
}
_universeLibCanvas.transform.SetParent(null, false);
_universeLibCanvas.transform.localScale = Vector3.one;
}
private void CenterCanvasOnMenuTransform(GameObject canvasRoot, Transform parentTransform)
{
// Find all the rectTransforms under the canvas, determine their bounds, and center the canvas local position
// on the menuTransform
RectTransform canvasTransform = _explorerRoot.transform as RectTransform;
// get the extents of the rectTransform
Vector3[] corners = new Vector3[4];
canvasTransform.GetWorldCorners(corners);
// now center by offsettings its localPosition
Vector3 center = (corners[0] + corners[2]) / 2f;
Vector3 extents = (corners[2] - corners[0]) / 2f;
Vector3 offset = center - extents;
offset.z = 0f; // set z to 0 to avoid depth issues
canvasTransform.localPosition = offset;
MelonLogger.Msg($"Centered canvas on menuTransform: {canvasTransform.localPosition}");
}
private static bool IsFloatValid(float val)
=> (!float.IsNaN(val) && !float.IsInfinity(val));
private static void SetupCollidersOnUnityUi(Component c)
{
GameObject go = c.gameObject;
if (c is Button)
{
BoxCollider col = go.AddComponentIfMissing<BoxCollider>();
col.isTrigger = true;
var rectTransform = go.GetComponent<RectTransform>();
if (rectTransform)
{
Vector3 newSize = new Vector3(rectTransform.sizeDelta.x, rectTransform.sizeDelta.y,
0.05f / rectTransform.lossyScale.z);
if (!IsFloatValid(newSize.x))
newSize.x = 0.05f;
if (!IsFloatValid(newSize.y))
newSize.y = 0.05f;
if (!IsFloatValid(newSize.z))
newSize.z = 0.05f;
col.size = newSize;
col.center = new Vector3(col.size.x * (0.5f - rectTransform.pivot.x),
col.size.y * (0.5f - rectTransform.pivot.y), 0f);
}
}
if (c is Toggle)
{
BoxCollider col = go.AddComponentIfMissing<BoxCollider>();
col.isTrigger = true;
var rectTransform = go.GetComponent<RectTransform>();
if (rectTransform)
{
Vector3 newSize = new Vector3(Mathf.Max(rectTransform.sizeDelta.x, rectTransform.rect.width),
rectTransform.sizeDelta.y, 0.05f / rectTransform.lossyScale.z);
if (!IsFloatValid(newSize.x))
newSize.x = 0.05f;
if (!IsFloatValid(newSize.y))
newSize.y = 0.05f;
if (!IsFloatValid(newSize.z))
newSize.z = 0.05f;
col.size = newSize;
col.center = new Vector3(col.size.x * (0.5f - rectTransform.pivot.x),
col.size.y * (0.5f - rectTransform.pivot.y), 0f);
//Check Child if Size = 0
if (col.size.x + col.size.y == 0f && go.transform.childCount > 0)
{
var childRectTransform = go.transform.GetChild(0).GetComponent<RectTransform>();
if (childRectTransform != null)
{
newSize = new Vector3(
Mathf.Max(childRectTransform.sizeDelta.x, rectTransform.rect.width),
childRectTransform.sizeDelta.y, 0.1f);
if (!IsFloatValid(newSize.x))
newSize.x = 0.05f;
if (!IsFloatValid(newSize.y))
newSize.y = 0.05f;
if (!IsFloatValid(newSize.z))
newSize.z = 0.05f;
col.size = newSize;
col.center = Vector3.zero;
}
}
}
}
if (c is Slider)
{
BoxCollider col = go.AddComponentIfMissing<BoxCollider>();
var rectTransform = go.GetComponent<RectTransform>();
if (rectTransform)
{
Vector3 newSize = new Vector3(rectTransform.sizeDelta.x, rectTransform.sizeDelta.y,
0.05f / rectTransform.lossyScale.z);
if (!IsFloatValid(newSize.x))
newSize.x = 0.05f;
if (!IsFloatValid(newSize.y))
newSize.y = 0.05f;
if (!IsFloatValid(newSize.z))
newSize.z = 0.05f;
col.size = newSize;
col.center = new Vector3(col.size.x * (0.5f - rectTransform.pivot.x),
col.size.y * (0.5f - rectTransform.pivot.y), 0f);
}
col.isTrigger = true;
}
if (c is EventTrigger)
{
BoxCollider col = go.AddComponentIfMissing<BoxCollider>();
var rectTransform = go.GetComponent<RectTransform>();
if (rectTransform)
{
Vector3 newSize = new Vector3(rectTransform.sizeDelta.x, rectTransform.sizeDelta.y,
0.025f / rectTransform.lossyScale.z);
if (!IsFloatValid(newSize.x))
newSize.x = 0.05f;
if (!IsFloatValid(newSize.y))
newSize.y = 0.05f;
if (!IsFloatValid(newSize.z))
newSize.z = 0.05f;
col.size = newSize;
col.center = new Vector3(col.size.x * (0.5f - rectTransform.pivot.x),
col.size.y * (0.5f - rectTransform.pivot.y), 0f);
}
col.isTrigger = true;
}
if (c is InputField || c is TMP_InputField)
{
BoxCollider col = go.AddComponentIfMissing<BoxCollider>();
col.isTrigger = true;
var rectTransform = go.GetComponent<RectTransform>();
if (rectTransform)
{
Vector3 newSize = new Vector3(rectTransform.sizeDelta.x, rectTransform.sizeDelta.y,
0.05f / rectTransform.lossyScale.z);
if (!IsFloatValid(newSize.x))
newSize.x = 0.05f;
if (!IsFloatValid(newSize.y))
newSize.y = 0.05f;
if (!IsFloatValid(newSize.z))
newSize.z = 0.05f;
col.size = newSize;
col.center = new Vector3(col.size.x * (0.5f - rectTransform.pivot.x),
col.size.y * (0.5f - rectTransform.pivot.y), 0f);
}
}
if (c is ScrollRect)
{
BoxCollider col = go.AddComponentIfMissing<BoxCollider>();
col.isTrigger = true;
var rectTransform = go.GetComponent<RectTransform>();
if (rectTransform)
{
Vector3 newSize = new Vector3(rectTransform.sizeDelta.x, rectTransform.sizeDelta.y,
0.025f / rectTransform.lossyScale.z);
if (!IsFloatValid(newSize.x))
newSize.x = 0.025f;
if (!IsFloatValid(newSize.y))
newSize.y = 0.025f;
if (!IsFloatValid(newSize.z))
newSize.z = 0.025f;
col.size = newSize;
col.center = new Vector3(col.size.x * (0.5f - rectTransform.pivot.x),
col.size.y * (0.5f - rectTransform.pivot.y), 0f);
}
}
if (c is Dropdown)
{
BoxCollider col = go.AddComponentIfMissing<BoxCollider>();
col.isTrigger = true;
var rectTransform = go.GetComponent<RectTransform>();
if (rectTransform)
{
Vector3 newSize = new Vector3(rectTransform.sizeDelta.x, rectTransform.sizeDelta.y,
0.05f / rectTransform.lossyScale.z);
if (!IsFloatValid(newSize.x))
newSize.x = 0.05f;
if (!IsFloatValid(newSize.y))
newSize.y = 0.05f;
if (!IsFloatValid(newSize.z))
newSize.z = 0.05f;
col.size = newSize;
col.center = new Vector3(col.size.x * (0.5f - rectTransform.pivot.x),
col.size.y * (0.5f - rectTransform.pivot.y), 0f);
}
}
//Canvas
if (c is Canvas)
{
BoxCollider col = go.AddComponentIfMissing<BoxCollider>();
var rectTransform = go.GetComponent<RectTransform>();
if (rectTransform)
{
Vector3 newSize = new Vector3(rectTransform.sizeDelta.x, rectTransform.sizeDelta.y,
0.0125f / rectTransform.lossyScale.z);
if (!IsFloatValid(newSize.x))
newSize.x = 0.0125f;
if (!IsFloatValid(newSize.y))
newSize.y = 0.0125f;
if (!IsFloatValid(newSize.z))
newSize.z = 0.0125f;
col.size = newSize;
col.center = new Vector3(col.size.x * (0.5f - rectTransform.pivot.x),
col.size.y * (0.5f - rectTransform.pivot.y), 0f);
}
col.isTrigger = true;
}
}
#endregion Private Methods
}

View file

@ -1,15 +1,14 @@
using System.Reflection;
using ABI_RC.Core.Base;
using ABI_RC.Core.Base.Jobs;
using ABI_RC.Core.InteractionSystem;
using ABI_RC.Core.Player;
using ABI_RC.Core.Player.Interaction.RaycastImpl;
using ABI_RC.Core.Util.AssetFiltering;
using ABI.CCK.Components;
using HarmonyLib;
using MelonLoader;
using NAK.SuperAwesomeMod.Components;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
namespace NAK.SuperAwesomeMod;
@ -33,13 +32,13 @@ public class SuperAwesomeModMod : MelonMod
BindingFlags.NonPublic | BindingFlags.Static))
);
// patch SharedFilter.ProcessCanvas
HarmonyInstance.Patch(
typeof(SharedFilter).GetMethod(nameof(SharedFilter.ProcessCanvas),
BindingFlags.Public | BindingFlags.Static),
postfix: new HarmonyMethod(typeof(SuperAwesomeModMod).GetMethod(nameof(OnProcessCanvas),
BindingFlags.NonPublic | BindingFlags.Static))
);
// // patch SharedFilter.ProcessCanvas
// HarmonyInstance.Patch(
// typeof(SharedFilter).GetMethod(nameof(SharedFilter.ProcessCanvas),
// BindingFlags.Public | BindingFlags.Static),
// postfix: new HarmonyMethod(typeof(SuperAwesomeModMod).GetMethod(nameof(OnProcessCanvas),
// BindingFlags.NonPublic | BindingFlags.Static))
// );
LoggerInstance.Msg("SuperAwesomeModMod! OnInitializeMelon! :D");
}
@ -53,29 +52,43 @@ public class SuperAwesomeModMod : MelonMod
private static void OnPlayerSetupStart()
{
CVRRaycastDebugManager.Initialize(PlayerSetup.Instance.desktopCam);
// CVRRaycastDebugManager.Initialize(PlayerSetup.Instance.desktopCam);
// UEMenuHelper.Create();
}
private static void OnShitLoaded(Component c, List<Task> asyncTasks = null, Scene? scene = null)
{
if (c == null)
if (!c)
return;
if (c.gameObject == null)
if (!c.gameObject)
return;
if (c.gameObject.scene.buildIndex > 0)
return;
if ((scene != null)
&& (c.gameObject.scene != scene))
if ((scene != null) && (c.gameObject.scene != scene))
return;
if (c is Canvas canvas) canvas.gameObject.AddComponent<CVRCanvasWrapper>();
if (c is InputField input)
{
input.AddComponentIfMissing<InputFocusIntentDetector>();
}
if (c is TMPro.TMP_InputField tmpInput)
{
tmpInput.AddComponentIfMissing<InputFocusIntentDetector>();
}
}
private static void OnProcessCanvas(string collectionId, Canvas canvas)
public class InputFocusIntentDetector : MonoBehaviour, IPointerClickHandler
{
canvas.gameObject.AddComponent<CVRCanvasWrapper>();
public void OnPointerClick(PointerEventData eventData)
{
if (TryGetComponent(out TMPro.TMP_InputField input) && input.isActiveAndEnabled)
ViewManager.Instance.openMenuKeyboard(input);
else if (TryGetComponent(out InputField inputField) && inputField.isActiveAndEnabled)
ViewManager.Instance.openMenuKeyboard(inputField);
}
}
}

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>ASTExtension</RootNamespace>
<RootNamespace>SuperAwesomeMod</RootNamespace>
</PropertyGroup>
<ItemGroup>
<Reference Include="BTKUILib">