mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2025-09-04 23:39:22 +00:00
Compare commits
94 commits
Author | SHA1 | Date | |
---|---|---|---|
|
226b369537 | ||
|
e54e50e76d | ||
|
6bef1d0c96 | ||
|
1496c25184 | ||
|
c368daab4f | ||
|
30c069388c | ||
|
c9acb00088 | ||
|
4123a1f25d | ||
|
7f5ca4b29d | ||
|
6dc7f8a267 | ||
|
6ad23e6fc5 | ||
|
2f668b289c | ||
|
6d28f734da | ||
|
969bd00df3 | ||
|
7deddd88ea | ||
|
1a591749c6 | ||
|
218344a121 | ||
|
07daceea44 | ||
|
aaeb187b9e | ||
|
e378a717d3 | ||
|
a0a859aa86 | ||
|
13e206cd58 | ||
|
548fcf72bc | ||
|
ee4df06d2e | ||
|
faf9d48fb6 | ||
|
c455d20f9c | ||
|
0cdef04a53 | ||
|
e96a0e164d | ||
|
bf877124c1 | ||
|
cc7762293d | ||
|
b75dce1d02 | ||
|
0a01534aa4 | ||
|
8b34359d1b | ||
|
d54ec06190 | ||
|
daf2d7db0f | ||
|
bdf6b0e51a | ||
|
abd7b9d674 | ||
|
da6520beb0 | ||
|
3521453010 | ||
|
0f6006db83 | ||
|
e85c1e2f25 | ||
|
ece15e0dfc | ||
|
47b69dfbc7 | ||
|
63948ddf69 | ||
|
f6afea3c44 | ||
|
8343d6c5bb | ||
|
6e37bcbabb | ||
|
c4ab9cce47 | ||
|
88f3b1a41f | ||
|
9606b10c9d | ||
|
d5d4e3eddd | ||
|
377b365cdc | ||
|
21b791083b | ||
|
8ad74b5ef6 | ||
|
134c8e7878 | ||
|
1f8435edb9 | ||
|
446a89f35d | ||
|
1f99312c22 | ||
|
a6fa59d24c | ||
|
89b8f19288 | ||
|
8764339ac8 | ||
|
a8e0553e53 | ||
|
2af27cc81d | ||
|
784249a08b | ||
|
1fbfcd80cd | ||
|
d5eb9ae1a0 | ||
|
88faf93b3b | ||
|
a2e29149e2 | ||
|
8f8f2ad1fb | ||
|
a2aa3b9871 | ||
|
febd9f2741 | ||
|
b246f71e6e | ||
|
0cde9ecb21 | ||
|
964d6009b7 | ||
|
392390cde7 | ||
|
ba26a1faae | ||
|
c13e48638c | ||
|
77426c4235 | ||
|
ef6ad34a4e | ||
|
e67d571971 | ||
|
d63fb02026 | ||
|
d5480e32b4 | ||
|
21aa646359 | ||
|
02be4ec445 | ||
|
dafff6a9a2 | ||
|
68d7c22b7c | ||
|
3c81f71d15 | ||
|
0564de3ebf | ||
|
7030ed8650 | ||
|
364de89b30 | ||
|
fea10ac221 | ||
|
6249696efa | ||
|
ea5a9eef97 | ||
|
063669e8a6 |
107 changed files with 2772 additions and 3530 deletions
106
.Deprecated/SuperAwesomeMod/DepthCameraFix.cs
Normal file
106
.Deprecated/SuperAwesomeMod/DepthCameraFix.cs
Normal file
|
@ -0,0 +1,106 @@
|
|||
namespace NAK.SuperAwesomeMod;
|
||||
|
||||
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
|
||||
{
|
||||
private Camera cam;
|
||||
private CommandBuffer beforeCommandBuffer;
|
||||
private CommandBuffer afterCommandBuffer;
|
||||
|
||||
void Start()
|
||||
{
|
||||
cam = GetComponent<Camera>();
|
||||
|
||||
// Ensure camera generates depth texture
|
||||
cam.depthTextureMode |= DepthTextureMode.Depth;
|
||||
|
||||
// Create command buffers
|
||||
beforeCommandBuffer = new CommandBuffer();
|
||||
beforeCommandBuffer.name = "DepthTextureFix_Before";
|
||||
|
||||
afterCommandBuffer = new CommandBuffer();
|
||||
afterCommandBuffer.name = "DepthTextureFix_After";
|
||||
|
||||
// Add command buffers at the right events
|
||||
cam.AddCommandBuffer(CameraEvent.BeforeDepthTexture, beforeCommandBuffer);
|
||||
cam.AddCommandBuffer(CameraEvent.AfterDepthTexture, afterCommandBuffer);
|
||||
}
|
||||
|
||||
void OnPreRender()
|
||||
{
|
||||
// Set up command buffers each frame to handle dynamic changes
|
||||
SetupCommandBuffers();
|
||||
}
|
||||
|
||||
void SetupCommandBuffers()
|
||||
{
|
||||
// Get current camera viewport in pixels
|
||||
Rect pixelRect = cam.pixelRect;
|
||||
|
||||
// Before depth texture: override viewport to full screen
|
||||
beforeCommandBuffer.Clear();
|
||||
beforeCommandBuffer.SetViewport(new Rect(0, 0, Screen.width, Screen.height));
|
||||
|
||||
// After depth texture: restore original viewport
|
||||
afterCommandBuffer.Clear();
|
||||
afterCommandBuffer.SetViewport(pixelRect);
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
// Clean up
|
||||
if (cam != null)
|
||||
{
|
||||
if (beforeCommandBuffer != null)
|
||||
{
|
||||
cam.RemoveCommandBuffer(CameraEvent.BeforeDepthTexture, beforeCommandBuffer);
|
||||
beforeCommandBuffer.Dispose();
|
||||
}
|
||||
|
||||
if (afterCommandBuffer != null)
|
||||
{
|
||||
cam.RemoveCommandBuffer(CameraEvent.AfterDepthTexture, afterCommandBuffer);
|
||||
afterCommandBuffer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
if (cam != null)
|
||||
{
|
||||
if (beforeCommandBuffer != null)
|
||||
cam.RemoveCommandBuffer(CameraEvent.BeforeDepthTexture, beforeCommandBuffer);
|
||||
if (afterCommandBuffer != null)
|
||||
cam.RemoveCommandBuffer(CameraEvent.AfterDepthTexture, afterCommandBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (cam != null && beforeCommandBuffer != null && afterCommandBuffer != null)
|
||||
{
|
||||
cam.AddCommandBuffer(CameraEvent.BeforeDepthTexture, beforeCommandBuffer);
|
||||
cam.AddCommandBuffer(CameraEvent.AfterDepthTexture, afterCommandBuffer);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
using ABI.CCK.Components;
|
||||
using ABI.Scripting.CVRSTL.Client;
|
||||
using System.Diagnostics;
|
||||
using ABI_RC.Scripting.Persistence;
|
||||
using MTJobSystem;
|
||||
using UnityEngine;
|
||||
|
||||
|
@ -108,8 +109,11 @@ public static class CVRLuaClientBehaviourExtensions
|
|||
behaviour._startupMessageQueue.Clear(); // will be repopulated
|
||||
behaviour.LogInfo("[CVRLuaToolsExtension] Resetting script...\n");
|
||||
|
||||
// remove the script from the persistence manager, as the storage needs to be reinitialized
|
||||
PersistenceManager.HandleUnsubscribe(behaviour.Storage, behaviour.script, behaviour.Context.ParentContent.ContentType, behaviour.Context.AssetID);
|
||||
|
||||
behaviour.script = null;
|
||||
behaviour.script = LuaScriptFactory.ForLuaBehaviour(behaviour, boundObjectEntries, behaviour.gameObject, behaviour.transform, PersistentDataPath);
|
||||
behaviour.script = LuaScriptFactory.ForLuaBehaviour(behaviour, boundObjectEntries, behaviour.gameObject, behaviour.transform);
|
||||
|
||||
behaviour.InitTimerIfNeeded(); // only null if crashed prior
|
||||
behaviour.script.AttachDebugger(behaviour.timer); // reattach the debugger
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# DropPropTweak
|
||||
# Custom Rich Presence
|
||||
|
||||
Gives the Drop Prop button more utility by allowing you to drop props in the air.
|
||||
Lets you customize the Steam & Discord rich presence messages & values.
|
||||
|
||||
---
|
||||
|
|
@ -2,9 +2,9 @@
|
|||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.LuaNetVars;
|
||||
namespace NAK.LuaNetworkVariables;
|
||||
|
||||
public class LuaNetVarsMod : MelonMod
|
||||
public class LuaNetworkVariablesMod : MelonMod
|
||||
{
|
||||
internal static MelonLogger.Instance Logger;
|
||||
|
||||
|
@ -18,24 +18,22 @@ public class LuaNetVarsMod : MelonMod
|
|||
public override void OnInitializeMelon()
|
||||
{
|
||||
Logger = LoggerInstance;
|
||||
|
||||
ApplyPatches(typeof(Patches.LuaScriptFactory_Patches));
|
||||
ApplyPatches(typeof(Patches.CVRSyncHelper_Patches));
|
||||
}
|
||||
|
||||
public override void OnUpdate()
|
||||
{
|
||||
// if (Input.GetKeyDown(KeyCode.F1))
|
||||
// {
|
||||
// PlayerSetup.Instance.DropProp("be0b5acc-a987-48dc-a28b-62bd912fe3a0");
|
||||
// }
|
||||
//
|
||||
// if (Input.GetKeyDown(KeyCode.F2))
|
||||
// {
|
||||
// GameObject go = new("TestSyncedObject");
|
||||
// go.AddComponent<TestSyncedObject>();
|
||||
// }
|
||||
}
|
||||
// public override void OnUpdate()
|
||||
// {
|
||||
// // if (Input.GetKeyDown(KeyCode.F1))
|
||||
// // {
|
||||
// // PlayerSetup.Instance.DropProp("be0b5acc-a987-48dc-a28b-62bd912fe3a0");
|
||||
// // }
|
||||
// //
|
||||
// // if (Input.GetKeyDown(KeyCode.F2))
|
||||
// // {
|
||||
// // GameObject go = new("TestSyncedObject");
|
||||
// // go.AddComponent<TestSyncedObject>();
|
||||
// // }
|
||||
// }
|
||||
|
||||
#endregion Melon Events
|
||||
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
using ABI_RC.Core.Base;
|
||||
using ABI.Scripting.CVRSTL.Common;
|
||||
using JetBrains.Annotations;
|
||||
using NAK.LuaNetVars;
|
||||
using MoonSharp.Interpreter;
|
||||
|
||||
namespace NAK.LuaNetVars.Modules;
|
||||
namespace NAK.LuaNetworkVariables.Modules;
|
||||
|
||||
[PublicAPI] // Indicates that this class is used and should not be considered unused
|
||||
[PublicAPI]
|
||||
public class LuaNetModule : BaseScriptedStaticWrapper
|
||||
{
|
||||
public const string MODULE_ID = "NetworkModule";
|
||||
|
@ -38,7 +37,7 @@ public class LuaNetModule : BaseScriptedStaticWrapper
|
|||
|
||||
if (_controller == null)
|
||||
{
|
||||
LuaNetVarsMod.Logger.Error("LuaNetVarController is null.");
|
||||
LuaNetworkVariablesMod.Logger.Error("LuaNetVarController is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -57,7 +56,7 @@ public class LuaNetModule : BaseScriptedStaticWrapper
|
|||
|
||||
if (_controller == null)
|
||||
{
|
||||
LuaNetVarsMod.Logger.Error("LuaNetVarController is null.");
|
||||
LuaNetworkVariablesMod.Logger.Error("LuaNetVarController is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -76,7 +75,7 @@ public class LuaNetModule : BaseScriptedStaticWrapper
|
|||
|
||||
if (_controller == null)
|
||||
{
|
||||
LuaNetVarsMod.Logger.Error("LuaNetVarController is null.");
|
||||
LuaNetworkVariablesMod.Logger.Error("LuaNetVarController is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -95,12 +94,31 @@ public class LuaNetModule : BaseScriptedStaticWrapper
|
|||
|
||||
if (_controller == null)
|
||||
{
|
||||
LuaNetVarsMod.Logger.Error("LuaNetVarController is null.");
|
||||
LuaNetworkVariablesMod.Logger.Error("LuaNetVarController is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
_controller.SendLuaEvent(eventName, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a Lua event to other clients.
|
||||
/// </summary>
|
||||
/// <param name="eventName">The name of the event to send.</param>
|
||||
/// <param name="args">Optional arguments to send with the event.</param>
|
||||
public void SendLuaEventToUser(string eventName, string userId, params DynValue[] args)
|
||||
{
|
||||
CheckIfCanAccessMethod(nameof(SendLuaEventToUser), false,
|
||||
CVRLuaEnvironmentContext.CLIENT, CVRLuaObjectContext.ALL_BUT_EVENTS, CVRLuaOwnerContext.ANY);
|
||||
|
||||
if (_controller == null)
|
||||
{
|
||||
LuaNetworkVariablesMod.Logger.Error("LuaNetVarController is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
_controller.SendLuaEventToUser(eventName, userId, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the current client is the owner of the synchronized object.
|
||||
|
@ -112,7 +130,7 @@ public class LuaNetModule : BaseScriptedStaticWrapper
|
|||
|
||||
if (_controller == null)
|
||||
{
|
||||
LuaNetVarsMod.Logger.Error("LuaNetVarController is null.");
|
||||
LuaNetworkVariablesMod.Logger.Error("LuaNetVarController is null.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -126,7 +144,7 @@ public class LuaNetModule : BaseScriptedStaticWrapper
|
|||
|
||||
if (_controller == null)
|
||||
{
|
||||
LuaNetVarsMod.Logger.Error("LuaNetVarController is null.");
|
||||
LuaNetworkVariablesMod.Logger.Error("LuaNetVarController is null.");
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
using MoonSharp.Interpreter;
|
||||
using ABI_RC.Core.Networking;
|
||||
using MoonSharp.Interpreter;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Savior;
|
||||
|
||||
namespace NAK.LuaNetVars;
|
||||
namespace NAK.LuaNetworkVariables;
|
||||
|
||||
public struct LuaEventContext
|
||||
{
|
||||
|
@ -12,9 +12,9 @@ public struct LuaEventContext
|
|||
private double TimeSinceLastInvoke { get; set; }
|
||||
private bool IsLocal { get; set; }
|
||||
|
||||
public static LuaEventContext Create(string senderId, DateTime lastInvokeTime)
|
||||
public static LuaEventContext Create(bool isLocal, string senderId, DateTime lastInvokeTime)
|
||||
{
|
||||
var playerName = CVRPlayerManager.Instance.TryGetPlayerName(senderId);
|
||||
var playerName = isLocal ? AuthManager.Username : CVRPlayerManager.Instance.TryGetPlayerName(senderId);
|
||||
|
||||
return new LuaEventContext
|
||||
{
|
||||
|
@ -22,7 +22,7 @@ public struct LuaEventContext
|
|||
SenderName = playerName ?? "Unknown",
|
||||
LastInvokeTime = lastInvokeTime,
|
||||
TimeSinceLastInvoke = (DateTime.Now - lastInvokeTime).TotalSeconds,
|
||||
IsLocal = senderId == MetaPort.Instance.ownerId
|
||||
IsLocal = isLocal
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -30,11 +30,11 @@ public struct LuaEventContext
|
|||
{
|
||||
Table table = new(script)
|
||||
{
|
||||
["senderId"] = SenderId,
|
||||
["senderName"] = SenderName,
|
||||
["lastInvokeTime"] = LastInvokeTime.ToUniversalTime().ToString("O"),
|
||||
["timeSinceLastInvoke"] = TimeSinceLastInvoke,
|
||||
["isLocal"] = IsLocal
|
||||
["SenderId"] = SenderId,
|
||||
["SenderName"] = SenderName,
|
||||
["LastInvokeTime"] = LastInvokeTime.ToUniversalTime().ToString("O"),
|
||||
["TimeSinceLastInvoke"] = TimeSinceLastInvoke,
|
||||
["IsLocal"] = IsLocal
|
||||
};
|
||||
return table;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace NAK.LuaNetVars;
|
||||
namespace NAK.LuaNetworkVariables;
|
||||
|
||||
internal class LuaEventTracker
|
||||
{
|
||||
|
|
|
@ -5,8 +5,9 @@ using ABI.CCK.Components;
|
|||
using ABI.Scripting.CVRSTL.Common;
|
||||
using MoonSharp.Interpreter;
|
||||
using UnityEngine;
|
||||
using Coroutine = UnityEngine.Coroutine;
|
||||
|
||||
namespace NAK.LuaNetVars;
|
||||
namespace NAK.LuaNetworkVariables;
|
||||
|
||||
public partial class LuaNetVarController : MonoBehaviour
|
||||
{
|
||||
|
@ -26,21 +27,23 @@ public partial class LuaNetVarController : MonoBehaviour
|
|||
private bool _requestInitialSync;
|
||||
private CVRSpawnable _spawnable;
|
||||
private CVRObjectSync _objectSync;
|
||||
|
||||
|
||||
private bool _isInitialized;
|
||||
private Coroutine _syncCoroutine;
|
||||
|
||||
#region Unity Events
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (!Initialize())
|
||||
return;
|
||||
|
||||
// TODO: a manager script should be in charge of this
|
||||
// TODO: disabling object will kill coroutine
|
||||
StartCoroutine(SendVariableUpdatesCoroutine());
|
||||
}
|
||||
|
||||
=> _isInitialized = Initialize();
|
||||
|
||||
private void OnDestroy()
|
||||
=> Cleanup();
|
||||
|
||||
private void OnEnable()
|
||||
=> StartStopVariableUpdatesCoroutine(true);
|
||||
|
||||
private void OnDisable()
|
||||
=> StartStopVariableUpdatesCoroutine(false);
|
||||
|
||||
#endregion Unity Events
|
||||
|
||||
|
@ -55,13 +58,13 @@ public partial class LuaNetVarController : MonoBehaviour
|
|||
|
||||
if (ModNetworkID.Length > ModNetworkManager.MaxMessageIdLength)
|
||||
{
|
||||
LuaNetVarsMod.Logger.Error($"ModNetworkID ({ModNetworkID}) exceeds max length of {ModNetworkManager.MaxMessageIdLength} characters!");
|
||||
LuaNetworkVariablesMod.Logger.Error($"ModNetworkID ({ModNetworkID}) exceeds max length of {ModNetworkManager.MaxMessageIdLength} characters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
_hashes.Add(_uniquePathHash);
|
||||
ModNetworkManager.Subscribe(ModNetworkID, OnMessageReceived);
|
||||
LuaNetVarsMod.Logger.Msg($"Registered LuaNetVarController with ModNetworkID: {ModNetworkID}");
|
||||
LuaNetworkVariablesMod.Logger.Msg($"Registered LuaNetVarController with ModNetworkID: {ModNetworkID}");
|
||||
|
||||
switch (_luaClientBehaviour.Context.objContext)
|
||||
{
|
||||
|
@ -98,13 +101,20 @@ public partial class LuaNetVarController : MonoBehaviour
|
|||
return;
|
||||
|
||||
ModNetworkManager.Unsubscribe(ModNetworkID);
|
||||
LuaNetVarsMod.Logger.Msg($"Unsubscribed LuaNetVarController with ModNetworkID: {ModNetworkID}");
|
||||
LuaNetworkVariablesMod.Logger.Msg($"Unsubscribed LuaNetVarController with ModNetworkID: {ModNetworkID}");
|
||||
_hashes.Remove(_uniquePathHash);
|
||||
}
|
||||
|
||||
private void StartStopVariableUpdatesCoroutine(bool start)
|
||||
{
|
||||
if (_syncCoroutine != null) StopCoroutine(_syncCoroutine);
|
||||
_syncCoroutine = null;
|
||||
if (start) _syncCoroutine = StartCoroutine(SendVariableUpdatesCoroutine());
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator SendVariableUpdatesCoroutine()
|
||||
{
|
||||
while (true)
|
||||
while (isActiveAndEnabled)
|
||||
{
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
if (IsSyncOwner()) SendVariableUpdates();
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
using ABI_RC.Core.Savior;
|
||||
using ABI_RC.Systems.ModNetwork;
|
||||
using MoonSharp.Interpreter;
|
||||
using Unity.Services.Authentication.Internal;
|
||||
|
||||
namespace NAK.LuaNetVars
|
||||
namespace NAK.LuaNetworkVariables
|
||||
{
|
||||
public partial class LuaNetVarController
|
||||
{
|
||||
|
@ -47,7 +46,7 @@ namespace NAK.LuaNetVars
|
|||
msg.Read(out string varName);
|
||||
DynValue newValue = DeserializeDynValue(msg);
|
||||
|
||||
LuaNetVarsMod.Logger.Msg($"Received LuaVariable update: {varName} = {newValue}");
|
||||
LuaNetworkVariablesMod.Logger.Msg($"Received LuaVariable update: {varName} = {newValue}");
|
||||
|
||||
if (_registeredNetworkVars.TryGetValue(varName, out DynValue var))
|
||||
{
|
||||
|
@ -55,7 +54,7 @@ namespace NAK.LuaNetVars
|
|||
}
|
||||
else
|
||||
{
|
||||
LuaNetVarsMod.Logger.Warning($"Received update for unregistered variable {varName}");
|
||||
LuaNetworkVariablesMod.Logger.Warning($"Received update for unregistered variable {varName}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,7 +65,7 @@ namespace NAK.LuaNetVars
|
|||
msg.Read(out int argsCount);
|
||||
|
||||
DateTime lastInvokeTime = _eventTracker.GetLastInvokeTimeForSender(eventName, senderId);
|
||||
LuaEventContext context = LuaEventContext.Create(senderId, lastInvokeTime);
|
||||
LuaEventContext context = LuaEventContext.Create(false, senderId, lastInvokeTime);
|
||||
|
||||
// Update tracking
|
||||
_eventTracker.UpdateInvokeTime(eventName, senderId);
|
||||
|
@ -80,7 +79,7 @@ namespace NAK.LuaNetVars
|
|||
args[i + 1] = DeserializeDynValue(msg);
|
||||
}
|
||||
|
||||
LuaNetVarsMod.Logger.Msg($"Received LuaEvent: {eventName} from {context.SenderName} with {argsCount} args");
|
||||
LuaNetworkVariablesMod.Logger.Msg($"Received LuaEvent: {eventName} from {context.SenderName} with {argsCount} args");
|
||||
|
||||
InvokeLuaEvent(eventName, args);
|
||||
}
|
||||
|
@ -99,7 +98,7 @@ namespace NAK.LuaNetVars
|
|||
}
|
||||
else
|
||||
{
|
||||
LuaNetVarsMod.Logger.Warning($"Received sync for unregistered variable {varName}");
|
||||
LuaNetworkVariablesMod.Logger.Warning($"Received sync for unregistered variable {varName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -122,7 +121,7 @@ namespace NAK.LuaNetVars
|
|||
}
|
||||
else
|
||||
{
|
||||
LuaNetVarsMod.Logger.Warning($"No registered callback for event {eventName}");
|
||||
LuaNetworkVariablesMod.Logger.Warning($"No registered callback for event {eventName}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -160,7 +159,7 @@ namespace NAK.LuaNetVars
|
|||
}
|
||||
modMsg.Send();
|
||||
|
||||
LuaNetVarsMod.Logger.Msg($"Sent variable sync to {userId}");
|
||||
LuaNetworkVariablesMod.Logger.Msg($"Sent variable sync to {userId}");
|
||||
}
|
||||
|
||||
private void RequestVariableSync()
|
||||
|
@ -168,7 +167,7 @@ namespace NAK.LuaNetVars
|
|||
using ModNetworkMessage modMsg = new(ModNetworkID);
|
||||
modMsg.Write((byte)MessageType.RequestSync);
|
||||
modMsg.Send();
|
||||
LuaNetVarsMod.Logger.Msg("Requested variable sync");
|
||||
LuaNetworkVariablesMod.Logger.Msg("Requested variable sync");
|
||||
}
|
||||
|
||||
// private DynValue SendLuaEventCallback(ScriptExecutionContext context, CallbackArguments args)
|
||||
|
@ -187,7 +186,7 @@ namespace NAK.LuaNetVars
|
|||
{
|
||||
string senderId = MetaPort.Instance.ownerId;
|
||||
DateTime lastInvokeTime = _eventTracker.GetLastInvokeTimeForSender(eventName, senderId);
|
||||
LuaEventContext context = LuaEventContext.Create(senderId, lastInvokeTime);
|
||||
LuaEventContext context = LuaEventContext.Create(true, senderId, lastInvokeTime);
|
||||
|
||||
// Update tracking
|
||||
_eventTracker.UpdateInvokeTime(eventName, senderId);
|
||||
|
@ -209,6 +208,32 @@ namespace NAK.LuaNetVars
|
|||
modMsg.Send();
|
||||
}
|
||||
|
||||
internal void SendLuaEventToUser(string eventName, string userId, DynValue[] args)
|
||||
{
|
||||
string senderId = MetaPort.Instance.ownerId;
|
||||
DateTime lastInvokeTime = _eventTracker.GetLastInvokeTimeForSender(eventName, senderId);
|
||||
LuaEventContext context = LuaEventContext.Create(true, senderId, lastInvokeTime);
|
||||
|
||||
// Update tracking
|
||||
_eventTracker.UpdateInvokeTime(eventName, senderId);
|
||||
|
||||
var argsWithContext = new DynValue[args.Length + 1];
|
||||
argsWithContext[0] = DynValue.FromObject(_luaClientBehaviour.script, context.ToLuaTable(_luaClientBehaviour.script));
|
||||
Array.Copy(args, 0, argsWithContext, 1, args.Length);
|
||||
|
||||
InvokeLuaEvent(eventName, argsWithContext);
|
||||
|
||||
using ModNetworkMessage modMsg = new(ModNetworkID, userId);
|
||||
modMsg.Write((byte)MessageType.LuaEvent);
|
||||
modMsg.Write(eventName);
|
||||
modMsg.Write(args.Length);
|
||||
|
||||
foreach (DynValue arg in args)
|
||||
SerializeDynValue(modMsg, arg);
|
||||
|
||||
modMsg.Send();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
using MoonSharp.Interpreter;
|
||||
|
||||
namespace NAK.LuaNetVars;
|
||||
namespace NAK.LuaNetworkVariables;
|
||||
|
||||
public partial class LuaNetVarController
|
||||
{
|
||||
|
@ -8,7 +8,7 @@ public partial class LuaNetVarController
|
|||
{
|
||||
if (_registeredNetworkVars.ContainsKey(varName))
|
||||
{
|
||||
LuaNetVarsMod.Logger.Warning($"Network variable {varName} already registered!");
|
||||
LuaNetworkVariablesMod.Logger.Warning($"Network variable {varName} already registered!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ public partial class LuaNetVarController
|
|||
RegisterGetterFunction(varName);
|
||||
RegisterSetterFunction(varName);
|
||||
|
||||
LuaNetVarsMod.Logger.Msg($"Registered network variable {varName}");
|
||||
LuaNetworkVariablesMod.Logger.Msg($"Registered network variable {varName}");
|
||||
}
|
||||
|
||||
private void RegisterGetterFunction(string varName)
|
||||
|
@ -38,7 +38,7 @@ public partial class LuaNetVarController
|
|||
var newValue = args[0];
|
||||
if (!IsSupportedDynValue(newValue))
|
||||
{
|
||||
LuaNetVarsMod.Logger.Error($"Unsupported DynValue type: {newValue.Type} for variable {varName}");
|
||||
LuaNetworkVariablesMod.Logger.Error($"Unsupported DynValue type: {newValue.Type} for variable {varName}");
|
||||
return DynValue.Nil;
|
||||
}
|
||||
|
||||
|
@ -68,10 +68,10 @@ public partial class LuaNetVarController
|
|||
if (!ValidateCallback(callback) || !ValidateNetworkVar(varName)) return;
|
||||
|
||||
if (_registeredNotifyCallbacks.ContainsKey(varName))
|
||||
LuaNetVarsMod.Logger.Warning($"Overwriting notify callback for {varName}");
|
||||
LuaNetworkVariablesMod.Logger.Warning($"Overwriting notify callback for {varName}");
|
||||
|
||||
_registeredNotifyCallbacks[varName] = callback;
|
||||
LuaNetVarsMod.Logger.Msg($"Registered notify callback for {varName}");
|
||||
LuaNetworkVariablesMod.Logger.Msg($"Registered notify callback for {varName}");
|
||||
}
|
||||
|
||||
internal void RegisterEventCallback(string eventName, DynValue callback)
|
||||
|
@ -79,23 +79,23 @@ public partial class LuaNetVarController
|
|||
if (!ValidateCallback(callback)) return;
|
||||
|
||||
if (_registeredEventCallbacks.ContainsKey(eventName))
|
||||
LuaNetVarsMod.Logger.Warning($"Overwriting event callback for {eventName}");
|
||||
LuaNetworkVariablesMod.Logger.Warning($"Overwriting event callback for {eventName}");
|
||||
|
||||
_registeredEventCallbacks[eventName] = callback;
|
||||
LuaNetVarsMod.Logger.Msg($"Registered event callback for {eventName}");
|
||||
LuaNetworkVariablesMod.Logger.Msg($"Registered event callback for {eventName}");
|
||||
}
|
||||
|
||||
private bool ValidateCallback(DynValue callback)
|
||||
{
|
||||
if (callback?.Function != null) return true;
|
||||
LuaNetVarsMod.Logger.Error("Passed DynValue must be a function");
|
||||
LuaNetworkVariablesMod.Logger.Error("Passed DynValue must be a function");
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool ValidateNetworkVar(string varName)
|
||||
{
|
||||
if (_registeredNetworkVars.ContainsKey(varName)) return true;
|
||||
LuaNetVarsMod.Logger.Error($"Attempted to register notify callback for non-registered variable {varName}.");
|
||||
LuaNetworkVariablesMod.Logger.Error($"Attempted to register notify callback for non-registered variable {varName}.");
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
using ABI_RC.Systems.ModNetwork;
|
||||
using MoonSharp.Interpreter;
|
||||
|
||||
namespace NAK.LuaNetVars;
|
||||
namespace NAK.LuaNetworkVariables;
|
||||
|
||||
public partial class LuaNetVarController
|
||||
{
|
||||
|
@ -24,7 +24,7 @@ public partial class LuaNetVarController
|
|||
case DataType.Nil:
|
||||
return DynValue.Nil;
|
||||
default:
|
||||
LuaNetVarsMod.Logger.Error($"Unsupported data type received: {dataType}");
|
||||
LuaNetworkVariablesMod.Logger.Error($"Unsupported data type received: {dataType}");
|
||||
return DynValue.Nil;
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ public partial class LuaNetVarController
|
|||
msg.Write((byte)DataType.Nil);
|
||||
break;
|
||||
default:
|
||||
LuaNetVarsMod.Logger.Error($"Unsupported DynValue type: {value.Type}");
|
||||
LuaNetworkVariablesMod.Logger.Error($"Unsupported DynValue type: {value.Type}");
|
||||
msg.Write((byte)DataType.Nil);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using ABI_RC.Core.Savior;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.LuaNetVars;
|
||||
namespace NAK.LuaNetworkVariables;
|
||||
|
||||
public partial class LuaNetVarController
|
||||
{
|
||||
|
@ -13,10 +13,10 @@ public partial class LuaNetVarController
|
|||
// Check if it already exists (this **should** only matter in worlds)
|
||||
if (_hashes.Contains(hash))
|
||||
{
|
||||
LuaNetVarsMod.Logger.Warning($"Duplicate RelativeSyncMarker found at path {path}");
|
||||
LuaNetworkVariablesMod.Logger.Warning($"Duplicate RelativeSyncMarker found at path {path}");
|
||||
if (!FindAvailableHash(ref hash)) // Super lazy fix idfc
|
||||
{
|
||||
LuaNetVarsMod.Logger.Error($"Failed to find available hash for RelativeSyncMarker after 16 tries! {path}");
|
||||
LuaNetworkVariablesMod.Logger.Error($"Failed to find available hash for RelativeSyncMarker after 16 tries! {path}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
using ABI_RC.Core.Base;
|
||||
using ABI_RC.Core.Savior;
|
||||
using ABI_RC.Core.Util;
|
||||
using ABI.CCK.Components;
|
||||
using ABI.Scripting.CVRSTL.Client;
|
||||
using ABI.Scripting.CVRSTL.Client;
|
||||
using ABI.Scripting.CVRSTL.Common;
|
||||
using HarmonyLib;
|
||||
using MoonSharp.Interpreter;
|
||||
using NAK.LuaNetVars.Modules;
|
||||
using UnityEngine;
|
||||
using NAK.LuaNetworkVariables.Modules;
|
||||
|
||||
namespace NAK.LuaNetVars.Patches;
|
||||
namespace NAK.LuaNetworkVariables.Patches;
|
||||
|
||||
internal static class LuaScriptFactory_Patches
|
||||
{
|
||||
|
@ -28,38 +23,4 @@ internal static class LuaScriptFactory_Patches
|
|||
__result = LuaNetModule.RegisterUserData(____script, ____context);
|
||||
__instance.RegisteredModules[LuaNetModule.MODULE_ID] = __result; // add module to cache
|
||||
}
|
||||
}
|
||||
|
||||
internal static class CVRSyncHelper_Patches
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(CVRSyncHelper), nameof(CVRSyncHelper.UpdatePropValues))]
|
||||
private static void Postfix_CVRSyncHelper_UpdatePropValues(
|
||||
Vector3 position, Vector3 rotation, Vector3 scale,
|
||||
float[] syncValues, string guid, string instanceId,
|
||||
Span<float> subSyncValues, int numSyncValues, int syncType = 0)
|
||||
{
|
||||
CVRSyncHelper.PropData propData = CVRSyncHelper.Props.Find(prop => prop.InstanceId == instanceId);
|
||||
if (propData == null) return;
|
||||
|
||||
// Update locally stored prop data with new values
|
||||
// as GS does not reply with our own data...
|
||||
|
||||
propData.PositionX = position.x;
|
||||
propData.PositionY = position.y;
|
||||
propData.PositionZ = position.z;
|
||||
propData.RotationX = rotation.x;
|
||||
propData.RotationY = rotation.y;
|
||||
propData.RotationZ = rotation.z;
|
||||
propData.ScaleX = scale.x;
|
||||
propData.ScaleY = scale.y;
|
||||
propData.ScaleZ = scale.z;
|
||||
propData.CustomFloatsAmount = numSyncValues;
|
||||
for (int i = 0; i < numSyncValues; i++)
|
||||
propData.CustomFloats[i] = syncValues[i];
|
||||
|
||||
//propData.SpawnedBy
|
||||
propData.syncedBy = MetaPort.Instance.ownerId;
|
||||
propData.syncType = syncType;
|
||||
}
|
||||
}
|
|
@ -1,20 +1,20 @@
|
|||
using NAK.LuaNetVars.Properties;
|
||||
using NAK.LuaNetworkVariables.Properties;
|
||||
using MelonLoader;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyTitle(nameof(NAK.LuaNetVars))]
|
||||
[assembly: AssemblyTitle(nameof(NAK.LuaNetworkVariables))]
|
||||
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||
[assembly: AssemblyProduct(nameof(NAK.LuaNetVars))]
|
||||
[assembly: AssemblyProduct(nameof(NAK.LuaNetworkVariables))]
|
||||
|
||||
[assembly: MelonInfo(
|
||||
typeof(NAK.LuaNetVars.LuaNetVarsMod),
|
||||
nameof(NAK.LuaNetVars),
|
||||
typeof(NAK.LuaNetworkVariables.LuaNetworkVariablesMod),
|
||||
nameof(NAK.LuaNetworkVariables),
|
||||
AssemblyInfoParams.Version,
|
||||
AssemblyInfoParams.Author,
|
||||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/WhereAmIPointing"
|
||||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/LuaNetworkVariables"
|
||||
)]
|
||||
|
||||
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
|
||||
|
@ -24,9 +24,9 @@ using System.Reflection;
|
|||
[assembly: MelonAuthorColor(255, 158, 21, 32)] // red
|
||||
[assembly: HarmonyDontPatchAll]
|
||||
|
||||
namespace NAK.LuaNetVars.Properties;
|
||||
namespace NAK.LuaNetworkVariables.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.0";
|
||||
public const string Version = "1.0.2";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
|
@ -1,6 +1,77 @@
|
|||
# WhereAmIPointing
|
||||
# LuaNetworkVariables
|
||||
|
||||
Simple mod that makes your controller rays always visible when the menus are open. Useful for when you're trying to aim at something in the distance. Also visualizes which ray is being used for menu interaction.
|
||||
Adds a simple module for creating network variables & events *kinda* similar to Garry's Mod.
|
||||
|
||||
Example Usage:
|
||||
```lua
|
||||
-- Requires UnityEngine and NetworkModule
|
||||
UnityEngine = require("UnityEngine")
|
||||
NetworkModule = require("NetworkModule")
|
||||
|
||||
-- Unity Events --
|
||||
|
||||
function Start()
|
||||
|
||||
if NetworkModule == nil then
|
||||
print("NetworkModule did not load.")
|
||||
return
|
||||
end
|
||||
|
||||
-- Registers "AvatarHeight" as a network variable
|
||||
-- This creates Get and Set functions (GetAvatarHeight() and SetAvatarHeight())
|
||||
NetworkModule:RegisterNetworkVar("AvatarHeight")
|
||||
|
||||
-- Registers a callback for when "AvatarHeight" is changed.
|
||||
NetworkModule:RegisterNotifyCallback("AvatarHeight", function(varName, oldValue, newValue)
|
||||
print(varName .. " changed from " .. tostring(oldValue) .. " to " .. tostring(newValue))
|
||||
end)
|
||||
|
||||
-- Registers "ButtonClickedEvent" as a networked event. This provides context alongside the arguments passed.
|
||||
NetworkModule:RegisterEventCallback("ButtonClickedEvent", function(context, message)
|
||||
print("ButtonClickedEvent triggered by " .. tostring(context.SenderName) .. " with message: " .. tostring(message))
|
||||
print("Context details:")
|
||||
print(" SenderId: " .. tostring(context.SenderId))
|
||||
print(" SenderName: " .. tostring(context.SenderName))
|
||||
print(" LastInvokeTime: " .. tostring(context.LastInvokeTime))
|
||||
print(" TimeSinceLastInvoke: " .. tostring(context.TimeSinceLastInvoke))
|
||||
print(" IsLocal: " .. tostring(context.IsLocal))
|
||||
end)
|
||||
|
||||
-- Secondry example
|
||||
NetworkModule:RegisterEventCallback("CoolEvent", OnCoolEventOccured)
|
||||
end
|
||||
|
||||
function Update()
|
||||
if not NetworkModule:IsSyncOwner() then
|
||||
return
|
||||
end
|
||||
|
||||
SetAvatarHeight(PlayerAPI.LocalPlayer:GetViewPointPosition().y)
|
||||
end
|
||||
|
||||
-- Global Functions --
|
||||
|
||||
function SendClickEvent()
|
||||
NetworkModule:SendLuaEvent("ButtonClickedEvent", "The button was clicked!")
|
||||
print("Sent ButtonClickedEvent")
|
||||
end
|
||||
|
||||
function SendCoolEvent()
|
||||
NetworkModule:SendLuaEvent("CoolEvent", 1, 2)
|
||||
end
|
||||
|
||||
-- Listener Functions --
|
||||
|
||||
function OnCoolEventOccured(context, value, value2)
|
||||
print("CoolEvent triggered by " .. tostring(context.SenderName))
|
||||
print("Received values: " .. tostring(value) .. ", " .. tostring(value2))
|
||||
print("Context details:")
|
||||
print(" SenderId: " .. tostring(context.SenderId))
|
||||
print(" LastInvokeTime: " .. tostring(context.LastInvokeTime))
|
||||
print(" TimeSinceLastInvoke: " .. tostring(context.TimeSinceLastInvoke))
|
||||
print(" IsLocal: " .. tostring(context.IsLocal))
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -1,326 +0,0 @@
|
|||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using ABI_RC.Core.Savior;
|
||||
using ABI_RC.Systems.ModNetwork;
|
||||
|
||||
namespace NAK.LuaNetVars
|
||||
{
|
||||
public abstract class MNSyncedBehaviour : IDisposable
|
||||
{
|
||||
// Add static property for clarity
|
||||
protected static string LocalUserId => MetaPort.Instance.ownerId;
|
||||
|
||||
protected enum MessageType : byte
|
||||
{
|
||||
OwnershipRequest,
|
||||
OwnershipResponse,
|
||||
OwnershipTransfer,
|
||||
StateRequest,
|
||||
StateUpdate,
|
||||
CustomData
|
||||
}
|
||||
|
||||
protected enum OwnershipResponse : byte
|
||||
{
|
||||
Accepted,
|
||||
Rejected
|
||||
}
|
||||
|
||||
protected readonly string networkId;
|
||||
protected string currentOwnerId;
|
||||
private readonly bool autoAcceptTransfers;
|
||||
private readonly Dictionary<string, Action<bool>> pendingRequests;
|
||||
private bool isInitialized;
|
||||
private bool disposedValue;
|
||||
private bool isSoftOwner = false;
|
||||
private Timer stateRequestTimer;
|
||||
private const int StateRequestTimeout = 3000; // 3 seconds
|
||||
|
||||
public string CurrentOwnerId => currentOwnerId;
|
||||
public bool HasOwnership => currentOwnerId == LocalUserId;
|
||||
|
||||
protected MNSyncedBehaviour(string networkId, string currentOwnerId = "", bool autoAcceptTransfers = false)
|
||||
{
|
||||
this.networkId = networkId;
|
||||
this.currentOwnerId = currentOwnerId;
|
||||
this.autoAcceptTransfers = autoAcceptTransfers;
|
||||
this.pendingRequests = new Dictionary<string, Action<bool>>();
|
||||
|
||||
ModNetworkManager.Subscribe(networkId, OnMessageReceived);
|
||||
|
||||
if (!HasOwnership)
|
||||
RequestInitialState();
|
||||
else
|
||||
isInitialized = true;
|
||||
}
|
||||
|
||||
private void RequestInitialState()
|
||||
{
|
||||
using ModNetworkMessage msg = new(networkId);
|
||||
msg.Write((byte)MessageType.StateRequest);
|
||||
msg.Send();
|
||||
|
||||
stateRequestTimer = new Timer(StateRequestTimeoutCallback, null, StateRequestTimeout, Timeout.Infinite);
|
||||
}
|
||||
|
||||
private void StateRequestTimeoutCallback(object state)
|
||||
{
|
||||
// If isInitialized is still false, we assume soft ownership
|
||||
if (!isInitialized)
|
||||
{
|
||||
currentOwnerId = LocalUserId;
|
||||
isSoftOwner = true;
|
||||
isInitialized = true;
|
||||
OnOwnershipChanged(currentOwnerId);
|
||||
}
|
||||
|
||||
stateRequestTimer.Dispose();
|
||||
stateRequestTimer = null;
|
||||
}
|
||||
|
||||
public virtual void RequestOwnership(Action<bool> callback = null)
|
||||
{
|
||||
if (HasOwnership)
|
||||
{
|
||||
callback?.Invoke(true);
|
||||
return;
|
||||
}
|
||||
|
||||
using (ModNetworkMessage msg = new(networkId))
|
||||
{
|
||||
msg.Write((byte)MessageType.OwnershipRequest);
|
||||
msg.Send();
|
||||
}
|
||||
|
||||
if (callback != null)
|
||||
{
|
||||
pendingRequests[LocalUserId] = callback;
|
||||
}
|
||||
}
|
||||
|
||||
protected void SendNetworkedData(Action<ModNetworkMessage> writeData)
|
||||
{
|
||||
if (!HasOwnership)
|
||||
{
|
||||
Debug.LogWarning($"[MNSyncedBehaviour] Cannot send data without ownership. NetworkId: {networkId}");
|
||||
return;
|
||||
}
|
||||
|
||||
using (ModNetworkMessage msg = new(networkId))
|
||||
{
|
||||
msg.Write((byte)MessageType.CustomData);
|
||||
writeData(msg);
|
||||
msg.Send();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnMessageReceived(ModNetworkMessage message)
|
||||
{
|
||||
message.Read(out byte type);
|
||||
MessageType messageType = (MessageType)type;
|
||||
|
||||
if (!Enum.IsDefined(typeof(MessageType), messageType))
|
||||
return;
|
||||
|
||||
switch (messageType)
|
||||
{
|
||||
case MessageType.OwnershipRequest:
|
||||
if (!HasOwnership) break;
|
||||
HandleOwnershipRequest(message);
|
||||
break;
|
||||
|
||||
case MessageType.OwnershipResponse:
|
||||
if (message.Sender != currentOwnerId) break;
|
||||
HandleOwnershipResponse(message);
|
||||
break;
|
||||
|
||||
case MessageType.OwnershipTransfer:
|
||||
if (message.Sender != currentOwnerId) break;
|
||||
currentOwnerId = message.Sender;
|
||||
OnOwnershipChanged(currentOwnerId);
|
||||
break;
|
||||
|
||||
case MessageType.StateRequest:
|
||||
if (!HasOwnership) break; // this is the only safeguard against ownership hijacking... idk how to prevent it
|
||||
// TODO: only respond to a StateUpdate if expecting one
|
||||
HandleStateRequest(message);
|
||||
break;
|
||||
|
||||
case MessageType.StateUpdate:
|
||||
// Accept state updates from current owner or if we have soft ownership
|
||||
if (message.Sender != currentOwnerId && !isSoftOwner) break;
|
||||
HandleStateUpdate(message);
|
||||
break;
|
||||
|
||||
case MessageType.CustomData:
|
||||
if (message.Sender != currentOwnerId)
|
||||
{
|
||||
// If we have soft ownership and receive data from real owner, accept it
|
||||
if (isSoftOwner && message.Sender != LocalUserId)
|
||||
{
|
||||
currentOwnerId = message.Sender;
|
||||
isSoftOwner = false;
|
||||
OnOwnershipChanged(currentOwnerId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ignore data from non-owner
|
||||
break;
|
||||
}
|
||||
}
|
||||
HandleCustomData(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void HandleOwnershipRequest(ModNetworkMessage message)
|
||||
{
|
||||
if (!HasOwnership)
|
||||
return;
|
||||
|
||||
string requesterId = message.Sender;
|
||||
var response = autoAcceptTransfers ? OwnershipResponse.Accepted :
|
||||
OnOwnershipRequested(requesterId);
|
||||
|
||||
using (ModNetworkMessage responseMsg = new(networkId))
|
||||
{
|
||||
responseMsg.Write((byte)MessageType.OwnershipResponse);
|
||||
responseMsg.Write((byte)response);
|
||||
responseMsg.Send();
|
||||
}
|
||||
|
||||
if (response == OwnershipResponse.Accepted)
|
||||
{
|
||||
TransferOwnership(requesterId);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void HandleOwnershipResponse(ModNetworkMessage message)
|
||||
{
|
||||
message.Read(out byte responseByte);
|
||||
OwnershipResponse response = (OwnershipResponse)responseByte;
|
||||
|
||||
if (pendingRequests.TryGetValue(LocalUserId, out var callback))
|
||||
{
|
||||
bool accepted = response == OwnershipResponse.Accepted;
|
||||
callback(accepted);
|
||||
pendingRequests.Remove(LocalUserId);
|
||||
|
||||
// Update ownership locally only if accepted
|
||||
if (accepted)
|
||||
{
|
||||
currentOwnerId = LocalUserId;
|
||||
OnOwnershipChanged(currentOwnerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void HandleStateRequest(ModNetworkMessage message)
|
||||
{
|
||||
if (!HasOwnership)
|
||||
return;
|
||||
|
||||
using ModNetworkMessage response = new(networkId, message.Sender);
|
||||
response.Write((byte)MessageType.StateUpdate);
|
||||
WriteState(response);
|
||||
response.Send();
|
||||
}
|
||||
|
||||
protected virtual void HandleStateUpdate(ModNetworkMessage message)
|
||||
{
|
||||
currentOwnerId = message.Sender;
|
||||
isSoftOwner = false;
|
||||
ReadState(message);
|
||||
isInitialized = true;
|
||||
|
||||
// Dispose of the state request timer if it's still running
|
||||
if (stateRequestTimer != null)
|
||||
{
|
||||
stateRequestTimer.Dispose();
|
||||
stateRequestTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void HandleCustomData(ModNetworkMessage message)
|
||||
{
|
||||
if (!isInitialized)
|
||||
{
|
||||
Debug.LogWarning($"[MNSyncedBehaviour] Received custom data before initialization. NetworkId: {networkId}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.Sender != currentOwnerId)
|
||||
{
|
||||
// If we have soft ownership and receive data from real owner, accept it
|
||||
if (isSoftOwner && message.Sender != LocalUserId)
|
||||
{
|
||||
currentOwnerId = message.Sender;
|
||||
isSoftOwner = false;
|
||||
OnOwnershipChanged(currentOwnerId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ignore data from non-owner
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ReadCustomData(message);
|
||||
}
|
||||
|
||||
protected virtual void TransferOwnership(string newOwnerId)
|
||||
{
|
||||
using (ModNetworkMessage msg = new(networkId))
|
||||
{
|
||||
msg.Write((byte)MessageType.OwnershipTransfer);
|
||||
msg.Write(newOwnerId); // Include the new owner ID in transfer message
|
||||
msg.Send();
|
||||
}
|
||||
|
||||
currentOwnerId = newOwnerId;
|
||||
OnOwnershipChanged(newOwnerId);
|
||||
}
|
||||
|
||||
protected virtual OwnershipResponse OnOwnershipRequested(string requesterId)
|
||||
{
|
||||
return OwnershipResponse.Rejected;
|
||||
}
|
||||
|
||||
protected virtual void OnOwnershipChanged(string newOwnerId)
|
||||
{
|
||||
// Override to handle ownership changes
|
||||
}
|
||||
|
||||
protected virtual void WriteState(ModNetworkMessage message) { }
|
||||
protected virtual void ReadState(ModNetworkMessage message) { }
|
||||
protected virtual void ReadCustomData(ModNetworkMessage message) { }
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposedValue)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
ModNetworkManager.Unsubscribe(networkId);
|
||||
pendingRequests.Clear();
|
||||
|
||||
if (stateRequestTimer != null)
|
||||
{
|
||||
stateRequestTimer.Dispose();
|
||||
stateRequestTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
using ABI_RC.Systems.ModNetwork;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.LuaNetVars;
|
||||
|
||||
|
||||
// Test implementation
|
||||
public class TestSyncedBehaviour : MNSyncedBehaviour
|
||||
{
|
||||
private readonly System.Random random = new();
|
||||
private int testValue;
|
||||
private int incrementValue;
|
||||
|
||||
public TestSyncedBehaviour(string networkId) : base(networkId, autoAcceptTransfers: true)
|
||||
{
|
||||
Debug.Log($"[TestSyncedBehaviour] Initialized. NetworkId: {networkId}");
|
||||
}
|
||||
|
||||
public void SendTestMessage()
|
||||
{
|
||||
if (!HasOwnership) return;
|
||||
|
||||
SendNetworkedData(msg => {
|
||||
testValue = random.Next(1000);
|
||||
incrementValue++;
|
||||
msg.Write(testValue);
|
||||
msg.Write(incrementValue);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void WriteState(ModNetworkMessage message)
|
||||
{
|
||||
message.Write(testValue);
|
||||
message.Write(incrementValue);
|
||||
}
|
||||
|
||||
protected override void ReadState(ModNetworkMessage message)
|
||||
{
|
||||
message.Read(out testValue);
|
||||
message.Read(out incrementValue);
|
||||
Debug.Log($"[TestSyncedBehaviour] State synchronized. TestValue: {testValue}, IncrementValue: {incrementValue}");
|
||||
}
|
||||
|
||||
protected override void ReadCustomData(ModNetworkMessage message)
|
||||
{
|
||||
message.Read(out int receivedValue);
|
||||
message.Read(out int receivedIncrement);
|
||||
testValue = receivedValue;
|
||||
incrementValue = receivedIncrement;
|
||||
Debug.Log($"[TestSyncedBehaviour] Received custom data: TestValue: {testValue}, IncrementValue: {incrementValue}");
|
||||
}
|
||||
|
||||
protected override void OnOwnershipChanged(string newOwnerId)
|
||||
{
|
||||
Debug.Log($"[TestSyncedBehaviour] Ownership changed to: {newOwnerId}");
|
||||
}
|
||||
|
||||
protected override OwnershipResponse OnOwnershipRequested(string requesterId)
|
||||
{
|
||||
Debug.Log($"[TestSyncedBehaviour] Ownership requested by: {requesterId}");
|
||||
return OwnershipResponse.Accepted;
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
using ABI_RC.Core.Savior;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.LuaNetVars;
|
||||
|
||||
public class TestSyncedObject : MonoBehaviour
|
||||
{
|
||||
private const string TEST_NETWORK_ID = "test.synced.object.1";
|
||||
private TestSyncedBehaviour syncBehaviour;
|
||||
private float messageTimer = 0f;
|
||||
private const float MESSAGE_INTERVAL = 2f;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
syncBehaviour = new TestSyncedBehaviour(TEST_NETWORK_ID);
|
||||
Debug.Log($"TestSyncedObject started. Local Player ID: {MetaPort.Instance.ownerId}");
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// Request ownership on Space key
|
||||
if (Input.GetKeyDown(KeyCode.Space))
|
||||
{
|
||||
Debug.Log("Requesting ownership...");
|
||||
syncBehaviour.RequestOwnership((success) =>
|
||||
{
|
||||
Debug.Log($"Ownership request {(success ? "accepted" : "rejected")}");
|
||||
});
|
||||
}
|
||||
|
||||
// If we have ownership, send custom data periodically
|
||||
if (syncBehaviour.HasOwnership)
|
||||
{
|
||||
messageTimer += Time.deltaTime;
|
||||
if (messageTimer >= MESSAGE_INTERVAL)
|
||||
{
|
||||
messageTimer = 0f;
|
||||
syncBehaviour.SendTestMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
185
.github/scripts/update-modlist.js
vendored
Normal file
185
.github/scripts/update-modlist.js
vendored
Normal file
|
@ -0,0 +1,185 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const https = require('https');
|
||||
|
||||
// Configuration
|
||||
const ROOT = '.';
|
||||
const EXPERIMENTAL = '.Experimental';
|
||||
const README_PATH = 'README.md';
|
||||
const MARKER_START = '<!-- BEGIN MOD LIST -->';
|
||||
const MARKER_END = '<!-- END MOD LIST -->';
|
||||
const REPO_OWNER = process.env.REPO_OWNER || 'NotAKidoS';
|
||||
const REPO_NAME = process.env.REPO_NAME || 'NAK_CVR_Mods';
|
||||
|
||||
// Function to get latest release info from GitHub API
|
||||
async function getLatestRelease() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
hostname: 'api.github.com',
|
||||
path: `/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'User-Agent': 'Node.js GitHub Release Checker',
|
||||
'Accept': 'application/vnd.github.v3+json'
|
||||
}
|
||||
};
|
||||
|
||||
const req = https.request(options, (res) => {
|
||||
let data = '';
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
if (res.statusCode === 200) {
|
||||
try {
|
||||
resolve(JSON.parse(data));
|
||||
} catch (e) {
|
||||
reject(new Error(`Failed to parse GitHub API response: ${e.message}`));
|
||||
}
|
||||
} else {
|
||||
reject(new Error(`GitHub API request failed with status code: ${res.statusCode}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (e) => {
|
||||
reject(new Error(`GitHub API request error: ${e.message}`));
|
||||
});
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
function getModFolders(baseDir) {
|
||||
const entries = fs.readdirSync(baseDir, { withFileTypes: true });
|
||||
return entries
|
||||
.filter(entry => entry.isDirectory())
|
||||
.map(entry => path.join(baseDir, entry.name))
|
||||
.filter(dir => fs.existsSync(path.join(dir, 'README.md')));
|
||||
}
|
||||
|
||||
function extractDescription(readmePath) {
|
||||
try {
|
||||
const content = fs.readFileSync(readmePath, 'utf8');
|
||||
const lines = content.split('\n');
|
||||
|
||||
// Find the first header (# something)
|
||||
let headerIndex = -1;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (lines[i].trim().startsWith('# ')) {
|
||||
headerIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we found a header, look for the first non-empty line after it
|
||||
if (headerIndex !== -1) {
|
||||
for (let i = headerIndex + 1; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
if (line && !line.startsWith('#')) {
|
||||
return line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 'No description available';
|
||||
} catch (error) {
|
||||
console.error(`Error reading ${readmePath}:`, error);
|
||||
return 'No description available';
|
||||
}
|
||||
}
|
||||
|
||||
async function formatTable(mods, baseDir) {
|
||||
if (mods.length === 0) return '';
|
||||
|
||||
try {
|
||||
// Get the latest release info from GitHub
|
||||
const latestRelease = await getLatestRelease();
|
||||
const releaseAssets = latestRelease.assets || [];
|
||||
|
||||
// Create a map of available files in the release
|
||||
const availableFiles = {};
|
||||
releaseAssets.forEach(asset => {
|
||||
availableFiles[asset.name] = asset.browser_download_url;
|
||||
});
|
||||
|
||||
let rows = mods.map(modPath => {
|
||||
const modName = path.basename(modPath);
|
||||
const readmeLink = path.join(modPath, 'README.md');
|
||||
const readmePath = path.join(modPath, 'README.md');
|
||||
const description = extractDescription(readmePath);
|
||||
|
||||
// Check if the DLL exists in the latest release
|
||||
const dllFilename = `${modName}.dll`;
|
||||
let downloadSection;
|
||||
|
||||
if (availableFiles[dllFilename]) {
|
||||
downloadSection = `[Download](${availableFiles[dllFilename]})`;
|
||||
} else {
|
||||
downloadSection = 'No Download';
|
||||
}
|
||||
|
||||
return `| [${modName}](${readmeLink}) | ${description} | ${downloadSection} |`;
|
||||
});
|
||||
|
||||
return [
|
||||
`### ${baseDir === EXPERIMENTAL ? 'Experimental Mods' : 'Released Mods'}`,
|
||||
'',
|
||||
'| Name | Description | Download |',
|
||||
'|------|-------------|----------|',
|
||||
...rows,
|
||||
''
|
||||
].join('\n');
|
||||
} catch (error) {
|
||||
console.error('Error fetching release information:', error);
|
||||
|
||||
// Fallback to showing "No Download" for all mods if we can't fetch release info
|
||||
let rows = mods.map(modPath => {
|
||||
const modName = path.basename(modPath);
|
||||
const readmeLink = path.join(modPath, 'README.md');
|
||||
const readmePath = path.join(modPath, 'README.md');
|
||||
const description = extractDescription(readmePath);
|
||||
|
||||
return `| [${modName}](${readmeLink}) | ${description} | No Download |`;
|
||||
});
|
||||
|
||||
return [
|
||||
`### ${baseDir === EXPERIMENTAL ? 'Experimental Mods' : 'Released Mods'}`,
|
||||
'',
|
||||
'| Name | Description | Download |',
|
||||
'|------|-------------|----------|',
|
||||
...rows,
|
||||
''
|
||||
].join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
function updateReadme(modListSection) {
|
||||
const readme = fs.readFileSync(README_PATH, 'utf8');
|
||||
const before = readme.split(MARKER_START)[0];
|
||||
const after = readme.split(MARKER_END)[1];
|
||||
const newReadme = `${before}${MARKER_START}\n\n${modListSection}\n${MARKER_END}${after}`;
|
||||
fs.writeFileSync(README_PATH, newReadme);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
const mainMods = getModFolders(ROOT).filter(dir => !dir.startsWith(EXPERIMENTAL));
|
||||
const experimentalMods = getModFolders(EXPERIMENTAL);
|
||||
|
||||
const mainModsTable = await formatTable(mainMods, ROOT);
|
||||
const experimentalModsTable = await formatTable(experimentalMods, EXPERIMENTAL);
|
||||
|
||||
const tableContent = [mainModsTable, experimentalModsTable].join('\n');
|
||||
updateReadme(tableContent);
|
||||
|
||||
console.log('README.md updated successfully!');
|
||||
} catch (error) {
|
||||
console.error('Error updating README:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
35
.github/workflows/update-modlist.yml
vendored
Normal file
35
.github/workflows/update-modlist.yml
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
name: Update Mod List
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.github/scripts/update-modlist.js'
|
||||
- '.github/workflows/update-modlist.yml'
|
||||
- 'README.md'
|
||||
- '**/README.md'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update-modlist:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Run mod list updater
|
||||
run: node .github/scripts/update-modlist.js
|
||||
|
||||
- name: Commit and push changes
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git remote set-url origin https://x-access-token:${{ secrets.GH_TOKEN }}@github.com/${{ github.repository }}
|
||||
git add README.md
|
||||
git commit -m "[NAK_CVR_Mods] Update mod list in README" || echo "No changes to commit"
|
||||
git push
|
|
@ -9,13 +9,13 @@ public static class PlayerSetupExtensions
|
|||
// immediate measurement of the player's avatar height
|
||||
public static float GetCurrentAvatarHeight(this PlayerSetup playerSetup)
|
||||
{
|
||||
if (playerSetup._avatar == null)
|
||||
if (!playerSetup.IsAvatarLoaded)
|
||||
{
|
||||
ASTExtensionMod.Logger.Error("GetCurrentAvatarHeight: Avatar is null");
|
||||
return 0f;
|
||||
}
|
||||
|
||||
Vector3 localScale = playerSetup._avatar.transform.localScale;
|
||||
Vector3 localScale = playerSetup.AvatarTransform.localScale;
|
||||
Vector3 initialScale = playerSetup.initialScale;
|
||||
float initialHeight = playerSetup._initialAvatarHeight;
|
||||
Vector3 scaleDifference = CVRTools.DivideVectors(localScale - initialScale, initialScale);
|
||||
|
|
|
@ -49,7 +49,7 @@ public static partial class BtkUiAddon
|
|||
if (!CVRPlayerManager.Instance.GetPlayerPuppetMaster(_selectedPlayer, out PuppetMaster player))
|
||||
return;
|
||||
|
||||
if (player._avatar == null)
|
||||
if (!player.IsAvatarLoaded)
|
||||
return;
|
||||
|
||||
float height = player.netIkController.GetRemoteHeight();
|
||||
|
@ -64,8 +64,8 @@ public static partial class BtkUiAddon
|
|||
if (!CVRPlayerManager.Instance.GetPlayerPuppetMaster(_selectedPlayer, out PuppetMaster player))
|
||||
return;
|
||||
|
||||
AvatarAnimatorManager localAnimator = PlayerSetup.Instance.animatorManager;
|
||||
AvatarAnimatorManager remoteAnimator = player.animatorManager;
|
||||
AvatarAnimatorManager localAnimator = PlayerSetup.Instance.AnimatorManager;
|
||||
AvatarAnimatorManager remoteAnimator = player.AnimatorManager;
|
||||
if (!localAnimator.IsInitialized
|
||||
|| !remoteAnimator.IsInitialized)
|
||||
return;
|
||||
|
|
|
@ -81,7 +81,7 @@ public class ASTExtensionMod : MelonMod
|
|||
);
|
||||
|
||||
HarmonyInstance.Patch(
|
||||
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.ClearAvatar),
|
||||
typeof(PlayerBase).GetMethod(nameof(PlayerBase.ClearAvatar),
|
||||
BindingFlags.Public | BindingFlags.Instance),
|
||||
prefix: new HarmonyMethod(typeof(ASTExtensionMod).GetMethod(nameof(OnClearAvatar),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
|
@ -112,8 +112,11 @@ public class ASTExtensionMod : MelonMod
|
|||
Instance.OnLocalAvatarLoad();
|
||||
}
|
||||
|
||||
private static void OnClearAvatar(ref CVRAvatar ____avatarDescriptor)
|
||||
=> Instance.OnLocalAvatarClear(____avatarDescriptor);
|
||||
private static void OnClearAvatar(ref PlayerBase __instance)
|
||||
{
|
||||
if (!__instance.IsLocalPlayer) return;
|
||||
Instance.OnLocalAvatarClear(__instance.AvatarDescriptor);
|
||||
}
|
||||
|
||||
#endregion Harmony Patches
|
||||
|
||||
|
@ -121,7 +124,8 @@ public class ASTExtensionMod : MelonMod
|
|||
|
||||
private void OnLocalAvatarLoad()
|
||||
{
|
||||
if (!FindSupportedParameter(out string parameterName))
|
||||
_currentAvatarSupported = FindSupportedParameter(out string parameterName);
|
||||
if (!_currentAvatarSupported)
|
||||
return;
|
||||
|
||||
if (!AttemptCalibrateParameter(parameterName, out float minHeight, out float maxHeight, out float modifier))
|
||||
|
@ -226,7 +230,7 @@ public class ASTExtensionMod : MelonMod
|
|||
{
|
||||
parameterName = null;
|
||||
|
||||
AvatarAnimatorManager animatorManager = PlayerSetup.Instance.animatorManager;
|
||||
AvatarAnimatorManager animatorManager = PlayerSetup.Instance.AnimatorManager;
|
||||
if (!animatorManager.IsInitialized)
|
||||
{
|
||||
Logger.Error("AnimatorManager is not initialized!");
|
||||
|
@ -253,7 +257,7 @@ public class ASTExtensionMod : MelonMod
|
|||
maxHeight = 0f;
|
||||
modifier = 1f;
|
||||
|
||||
AvatarAnimatorManager animatorManager = PlayerSetup.Instance.animatorManager;
|
||||
AvatarAnimatorManager animatorManager = PlayerSetup.Instance.AnimatorManager;
|
||||
if (!animatorManager.IsInitialized)
|
||||
{
|
||||
Logger.Error("AnimatorManager is not initialized!");
|
||||
|
@ -318,7 +322,7 @@ public class ASTExtensionMod : MelonMod
|
|||
if (!_currentAvatarSupported)
|
||||
return;
|
||||
|
||||
AvatarAnimatorManager animatorManager = PlayerSetup.Instance.animatorManager;
|
||||
AvatarAnimatorManager animatorManager = PlayerSetup.Instance.AnimatorManager;
|
||||
if (!animatorManager.IsInitialized)
|
||||
{
|
||||
Logger.Error("AnimatorManager is not initialized!");
|
||||
|
|
|
@ -17,7 +17,7 @@ using System.Reflection;
|
|||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ASTExtension"
|
||||
)]
|
||||
|
||||
[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
|
||||
|
@ -27,6 +27,6 @@ using System.Reflection;
|
|||
namespace NAK.ASTExtension.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.3";
|
||||
public const string Version = "1.0.4";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"_id": 223,
|
||||
"name": "ASTExtension",
|
||||
"modversion": "1.0.3",
|
||||
"gameversion": "2025r179",
|
||||
"loaderversion": "0.6.1",
|
||||
"modversion": "1.0.4",
|
||||
"gameversion": "2025r180",
|
||||
"loaderversion": "0.7.2",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool):\n- VR Gesture to scale\n- Persistent height\n- Copy height from others\n\nBest used with Avatar Scale Tool, but will attempt to work with found scaling setups.\nRequires already having Avatar Scaling on the avatar. This is **not** Universal Scaling.",
|
||||
|
@ -17,8 +17,8 @@
|
|||
"requirements": [
|
||||
"BTKUILib"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ASTExtension.dll",
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/ASTExtension.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ASTExtension/",
|
||||
"changelog": "- Recompiled for 2025r179",
|
||||
"changelog": "- Fixes for 2025r180",
|
||||
"embedcolor": "#f61963"
|
||||
}
|
6
ConfigureCalibrationPose/ConfigureCalibrationPose.csproj
Normal file
6
ConfigureCalibrationPose/ConfigureCalibrationPose.csproj
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<RootNamespace>YouAreMineNow</RootNamespace>
|
||||
</PropertyGroup>
|
||||
</Project>
|
543
ConfigureCalibrationPose/Main.cs
Normal file
543
ConfigureCalibrationPose/Main.cs
Normal file
|
@ -0,0 +1,543 @@
|
|||
using System.Reflection;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Systems.IK;
|
||||
using ABI_RC.Systems.IK.SubSystems;
|
||||
using ABI_RC.Systems.InputManagement;
|
||||
using ABI_RC.Systems.Movement;
|
||||
using HarmonyLib;
|
||||
using MelonLoader;
|
||||
using RootMotion.FinalIK;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.ConfigureCalibrationPose;
|
||||
|
||||
public class ConfigureCalibrationPoseMod : MelonMod
|
||||
{
|
||||
#region Enums
|
||||
|
||||
private enum CalibrationPose
|
||||
{
|
||||
TPose,
|
||||
APose,
|
||||
IKPose,
|
||||
BikePose,
|
||||
RacushSit,
|
||||
CCKSitting,
|
||||
CCKCrouch,
|
||||
CCKProne,
|
||||
}
|
||||
|
||||
#endregion Enums
|
||||
|
||||
#region Melon Preferences
|
||||
|
||||
private static readonly MelonPreferences_Category Category =
|
||||
MelonPreferences.CreateCategory(nameof(ConfigureCalibrationPose));
|
||||
|
||||
private static readonly MelonPreferences_Entry<CalibrationPose> EntryCalibrationPose =
|
||||
Category.CreateEntry("calibration_pose", CalibrationPose.APose, display_name: "Calibration Pose",
|
||||
description: "What pose to use for FBT calibration.");
|
||||
|
||||
#endregion Melon Preferences
|
||||
|
||||
#region Melon Events
|
||||
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
#region BodySystem Patches
|
||||
|
||||
HarmonyInstance.Patch(
|
||||
typeof(BodySystem).GetMethod(nameof(BodySystem.MuscleUpdate),
|
||||
BindingFlags.Public | BindingFlags.Instance),
|
||||
prefix: new HarmonyMethod(typeof(ConfigureCalibrationPoseMod).GetMethod(nameof(OnPreBodySystemMuscleUpdate),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
#endregion BodySystem Patches
|
||||
}
|
||||
|
||||
#endregion Melon Events
|
||||
|
||||
#region Harmony Patches
|
||||
|
||||
private static bool OnPreBodySystemMuscleUpdate(ref float[] muscles)
|
||||
{
|
||||
PlayerSetup playerSetup = PlayerSetup.Instance;
|
||||
IKSystem ikSystem = IKSystem.Instance;
|
||||
ref HumanPose humanPose = ref ikSystem._humanPose;
|
||||
|
||||
if (BodySystem.isCalibrating)
|
||||
{
|
||||
switch (EntryCalibrationPose.Value)
|
||||
{
|
||||
default:
|
||||
case CalibrationPose.TPose:
|
||||
for (int i = 0; i < MusclePoses.TPoseMuscles.Length; i++)
|
||||
ikSystem.ApplyMuscleValue((MuscleIndex) i, MusclePoses.TPoseMuscles[i], ref muscles);
|
||||
break;
|
||||
case CalibrationPose.APose:
|
||||
for (int i = 0; i < MusclePoses.APoseMuscles.Length; i++)
|
||||
ikSystem.ApplyMuscleValue((MuscleIndex) i, MusclePoses.APoseMuscles[i], ref muscles);
|
||||
break;
|
||||
case CalibrationPose.IKPose:
|
||||
for (int i = 0; i < MusclePoses.IKPoseMuscles.Length; i++)
|
||||
ikSystem.ApplyMuscleValue((MuscleIndex) i, MusclePoses.IKPoseMuscles[i], ref muscles);
|
||||
break;
|
||||
case CalibrationPose.BikePose:
|
||||
for (int i = 0; i < MusclePoses.TPoseMuscles.Length; i++)
|
||||
ikSystem.ApplyMuscleValue((MuscleIndex) i, 0f, ref muscles);
|
||||
break;
|
||||
case CalibrationPose.CCKSitting:
|
||||
for (int i = 0; i < CCKSittingMuscles.Length; i++)
|
||||
ikSystem.ApplyMuscleValue((MuscleIndex) i, CCKSittingMuscles[i], ref muscles);
|
||||
break;
|
||||
case CalibrationPose.CCKCrouch:
|
||||
for (int i = 0; i < CCKCrouchMuscles.Length; i++)
|
||||
ikSystem.ApplyMuscleValue((MuscleIndex) i, CCKCrouchMuscles[i], ref muscles);
|
||||
break;
|
||||
case CalibrationPose.CCKProne:
|
||||
for (int i = 0; i < CCKProneMuscles.Length; i++)
|
||||
ikSystem.ApplyMuscleValue((MuscleIndex) i, CCKProneMuscles[i], ref muscles);
|
||||
break;
|
||||
case CalibrationPose.RacushSit:
|
||||
for (int i = 0; i < RacushSitMuscles.Length; i++)
|
||||
ikSystem.ApplyMuscleValue((MuscleIndex)i, RacushSitMuscles[i], ref muscles);
|
||||
break;
|
||||
}
|
||||
|
||||
humanPose.bodyPosition = Vector3.up;
|
||||
humanPose.bodyRotation = Quaternion.identity;
|
||||
}
|
||||
else if (BodySystem.isCalibratedAsFullBody && BodySystem.TrackingPositionWeight > 0f)
|
||||
{
|
||||
BetterBetterCharacterController characterController = playerSetup.CharacterController;
|
||||
|
||||
bool isRunning = characterController.IsMoving();
|
||||
bool isGrounded = characterController.IsGrounded();
|
||||
bool isFlying = characterController.IsFlying();
|
||||
bool isSwimming = characterController.IsSwimming();
|
||||
|
||||
if ((BodySystem.PlayRunningAnimationInFullBody
|
||||
&& (isRunning || !isGrounded && !isFlying && !isSwimming)))
|
||||
{
|
||||
ikSystem.applyOriginalHipPosition = true;
|
||||
ikSystem.applyOriginalHipRotation = true;
|
||||
|
||||
IKSolverVR solver = IKSystem.vrik.solver;
|
||||
BodySystem.SetPelvisWeight(solver.spine, 0f);
|
||||
BodySystem.SetLegWeight(solver.leftLeg, 0f);
|
||||
BodySystem.SetLegWeight(solver.rightLeg, 0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
ikSystem.applyOriginalHipPosition = true;
|
||||
ikSystem.applyOriginalHipRotation = false;
|
||||
humanPose.bodyRotation = Quaternion.identity;
|
||||
}
|
||||
}
|
||||
|
||||
return false; // dont run original
|
||||
}
|
||||
|
||||
#endregion Harmony Patches
|
||||
|
||||
#region Custom Pose Arrays
|
||||
|
||||
private static readonly float[] CCKSittingMuscles =
|
||||
[
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
-0.8000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
-0.8000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
-1.0000f,
|
||||
0.0000f,
|
||||
-0.3000f,
|
||||
0.3000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
-1.0000f,
|
||||
0.0000f,
|
||||
-0.3000f,
|
||||
0.3000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f
|
||||
];
|
||||
|
||||
private static readonly float[] CCKCrouchMuscles =
|
||||
[
|
||||
-1.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.5000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.5000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
-0.6279f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
-0.8095f,
|
||||
0.0000f,
|
||||
-1.0091f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
-0.4126f,
|
||||
0.0013f,
|
||||
-0.0860f,
|
||||
-0.9331f,
|
||||
-0.0869f,
|
||||
-1.3586f,
|
||||
0.1791f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
-0.1998f,
|
||||
-0.2300f,
|
||||
0.1189f,
|
||||
0.3479f,
|
||||
0.1364f,
|
||||
-0.3737f,
|
||||
0.0069f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
-0.1994f,
|
||||
-0.2301f,
|
||||
0.0267f,
|
||||
0.7532f,
|
||||
0.1922f,
|
||||
0.0009f,
|
||||
-0.0005f,
|
||||
-1.4747f,
|
||||
-0.0443f,
|
||||
-0.3347f,
|
||||
-0.3062f,
|
||||
-0.7596f,
|
||||
-1.2067f,
|
||||
-0.7329f,
|
||||
-0.7329f,
|
||||
-0.5984f,
|
||||
-2.7162f,
|
||||
-0.7439f,
|
||||
-0.7439f,
|
||||
-0.5812f,
|
||||
1.8528f,
|
||||
-0.7520f,
|
||||
-0.7520f,
|
||||
-0.7242f,
|
||||
0.5912f,
|
||||
-0.7632f,
|
||||
-0.7632f,
|
||||
-1.4747f,
|
||||
-0.0443f,
|
||||
-0.3347f,
|
||||
-0.3062f,
|
||||
-0.7596f,
|
||||
-1.2067f,
|
||||
-0.7329f,
|
||||
-0.7329f,
|
||||
-0.5984f,
|
||||
-2.7162f,
|
||||
-0.7439f,
|
||||
-0.7439f,
|
||||
-0.5812f,
|
||||
1.8528f,
|
||||
-0.7520f,
|
||||
0.8104f,
|
||||
-0.7242f,
|
||||
0.5912f,
|
||||
-0.7632f,
|
||||
0.8105f
|
||||
];
|
||||
|
||||
private static readonly float[] CCKProneMuscles =
|
||||
[
|
||||
0.6604f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.7083f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.7083f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.2444f,
|
||||
-0.0554f,
|
||||
-0.8192f,
|
||||
0.9301f,
|
||||
0.5034f,
|
||||
1.0274f,
|
||||
-0.1198f,
|
||||
0.5849f,
|
||||
0.2360f,
|
||||
-0.0837f,
|
||||
-1.1803f,
|
||||
0.9676f,
|
||||
0.7390f,
|
||||
0.9944f,
|
||||
-0.1717f,
|
||||
0.5849f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.2823f,
|
||||
-0.6297f,
|
||||
0.3200f,
|
||||
-0.3376f,
|
||||
0.0714f,
|
||||
0.9260f,
|
||||
-1.5768f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.1561f,
|
||||
-0.6712f,
|
||||
0.2997f,
|
||||
-0.3392f,
|
||||
0.0247f,
|
||||
0.7672f,
|
||||
-1.5269f,
|
||||
-1.1422f,
|
||||
0.0392f,
|
||||
0.6457f,
|
||||
0.0000f,
|
||||
0.6185f,
|
||||
-0.5393f,
|
||||
0.8104f,
|
||||
0.8104f,
|
||||
0.6223f,
|
||||
-0.8225f,
|
||||
0.8104f,
|
||||
0.8104f,
|
||||
0.6218f,
|
||||
-0.3961f,
|
||||
0.8104f,
|
||||
0.8104f,
|
||||
0.6160f,
|
||||
-0.3721f,
|
||||
0.8105f,
|
||||
0.8105f,
|
||||
-1.1422f,
|
||||
0.0392f,
|
||||
0.6457f,
|
||||
0.0000f,
|
||||
0.6185f,
|
||||
-0.5393f,
|
||||
0.8104f,
|
||||
0.8104f,
|
||||
0.6223f,
|
||||
-0.8226f,
|
||||
0.8104f,
|
||||
0.8104f,
|
||||
0.6218f,
|
||||
-0.3961f,
|
||||
0.8104f,
|
||||
0.8104f,
|
||||
0.6160f,
|
||||
-0.3721f,
|
||||
0.8105f,
|
||||
0.8105f
|
||||
];
|
||||
|
||||
public static readonly float[] RacushSitMuscles =
|
||||
[
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
-0.7600f,
|
||||
0.1000f,
|
||||
0.0600f,
|
||||
-0.1800f,
|
||||
-0.0991f,
|
||||
0.1300f,
|
||||
0.0001f,
|
||||
0.0000f,
|
||||
-0.7600f,
|
||||
0.1000f,
|
||||
0.0600f,
|
||||
-0.1800f,
|
||||
-0.0991f,
|
||||
0.1300f,
|
||||
0.0001f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.3927f,
|
||||
0.3115f,
|
||||
0.0931f,
|
||||
0.9650f,
|
||||
-0.0662f,
|
||||
0.0026f,
|
||||
0.0006f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.3927f,
|
||||
0.3115f,
|
||||
0.0931f,
|
||||
0.9650f,
|
||||
-0.0662f,
|
||||
0.0026f,
|
||||
0.0006f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f,
|
||||
0.0000f
|
||||
];
|
||||
|
||||
#endregion Custom Pose Arrays
|
||||
}
|
32
ConfigureCalibrationPose/Properties/AssemblyInfo.cs
Normal file
32
ConfigureCalibrationPose/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
using MelonLoader;
|
||||
using NAK.ConfigureCalibrationPose.Properties;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyTitle(nameof(NAK.ConfigureCalibrationPose))]
|
||||
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||
[assembly: AssemblyProduct(nameof(NAK.ConfigureCalibrationPose))]
|
||||
|
||||
[assembly: MelonInfo(
|
||||
typeof(NAK.ConfigureCalibrationPose.ConfigureCalibrationPoseMod),
|
||||
nameof(NAK.ConfigureCalibrationPose),
|
||||
AssemblyInfoParams.Version,
|
||||
AssemblyInfoParams.Author,
|
||||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ConfigureCalibrationPose"
|
||||
)]
|
||||
|
||||
[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.ConfigureCalibrationPose.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.0";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
14
ConfigureCalibrationPose/README.md
Normal file
14
ConfigureCalibrationPose/README.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
# ConfigureCalibrationPose
|
||||
|
||||
Select FBT calibration pose.
|
||||
|
||||
---
|
||||
|
||||
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.
|
23
ConfigureCalibrationPose/format.json
Normal file
23
ConfigureCalibrationPose/format.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"_id": -1,
|
||||
"name": "ConfigureCalibrationPose",
|
||||
"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/ConfigureCalibrationPose.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ConfigureCalibrationPose/",
|
||||
"changelog": "- Initial Release",
|
||||
"embedcolor": "#00FFFF"
|
||||
}
|
|
@ -17,7 +17,7 @@ using System.Reflection;
|
|||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/CustomSpawnPoint"
|
||||
)]
|
||||
|
||||
[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
|
||||
|
@ -27,6 +27,6 @@ using System.Reflection;
|
|||
namespace NAK.CustomSpawnPoint.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.2";
|
||||
public const string Version = "1.0.3";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
|
@ -41,20 +41,20 @@ internal static class SpawnPointManager
|
|||
{
|
||||
while (ViewManager.Instance == null)
|
||||
yield return null;
|
||||
while (ViewManager.Instance.gameMenuView == null)
|
||||
while (ViewManager.Instance.cohtmlView == null)
|
||||
yield return null;
|
||||
while (ViewManager.Instance.gameMenuView.Listener == null)
|
||||
while (ViewManager.Instance.cohtmlView.Listener == null)
|
||||
yield return null;
|
||||
|
||||
ViewManager.Instance.OnUiConfirm.AddListener(OnClearSpawnpointConfirm);
|
||||
ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) =>
|
||||
ViewManager.Instance.cohtmlView.Listener.FinishLoad += (_) =>
|
||||
{
|
||||
ViewManager.Instance.gameMenuView.View._view.ExecuteScript(spawnpointJs);
|
||||
ViewManager.Instance.cohtmlView.View._view.ExecuteScript(spawnpointJs);
|
||||
};
|
||||
ViewManager.Instance.gameMenuView.Listener.ReadyForBindings += () =>
|
||||
ViewManager.Instance.cohtmlView.Listener.ReadyForBindings += () =>
|
||||
{
|
||||
// listen for setting the spawn point on our custom button
|
||||
ViewManager.Instance.gameMenuView.View.BindCall("NAKCallSetSpawnpoint", SetSpawnPoint);
|
||||
ViewManager.Instance.cohtmlView.View.BindCall("NAKCallSetSpawnpoint", SetSpawnPoint);
|
||||
};
|
||||
|
||||
// create our custom spawn point object
|
||||
|
@ -179,7 +179,7 @@ internal static class SpawnPointManager
|
|||
|
||||
private static void UpdateMenuButtonState(bool hasSpawnpoint, bool isInWorld)
|
||||
{
|
||||
ViewManager.Instance.gameMenuView.View.TriggerEvent("NAKUpdateSpawnpointStatus", hasSpawnpoint.ToString(), isInWorld.ToString());
|
||||
ViewManager.Instance.cohtmlView.View.TriggerEvent("NAKUpdateSpawnpointStatus", hasSpawnpoint.ToString(), isInWorld.ToString());
|
||||
}
|
||||
|
||||
private static void ClearCurrentWorldState()
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"_id": 228,
|
||||
"name": "CustomSpawnPoint",
|
||||
"modversion": "1.0.2",
|
||||
"gameversion": "2025r179",
|
||||
"loaderversion": "0.6.1",
|
||||
"modversion": "1.0.3",
|
||||
"gameversion": "2025r180",
|
||||
"loaderversion": "0.7.2",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Replaces the unused Images button in the World Details page with a button to set a custom spawn point.",
|
||||
|
@ -17,8 +17,8 @@
|
|||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/CustomSpawnPoint.dll",
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/CustomSpawnPoint.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/CustomSpawnPoint/",
|
||||
"changelog": "- Recompiled for 2025r179",
|
||||
"changelog": "- Fixes for 2025r180",
|
||||
"embedcolor": "#f61963"
|
||||
}
|
6
DoubleTapJumpToExitSeat/DoubleTapJumpToExitSeat.csproj
Normal file
6
DoubleTapJumpToExitSeat/DoubleTapJumpToExitSeat.csproj
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<RootNamespace>YouAreMineNow</RootNamespace>
|
||||
</PropertyGroup>
|
||||
</Project>
|
92
DoubleTapJumpToExitSeat/Main.cs
Normal file
92
DoubleTapJumpToExitSeat/Main.cs
Normal file
|
@ -0,0 +1,92 @@
|
|||
using System.Reflection;
|
||||
using ABI_RC.Core.InteractionSystem;
|
||||
using ABI_RC.Systems.InputManagement;
|
||||
using ABI_RC.Systems.Movement;
|
||||
using HarmonyLib;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.DoubleTapJumpToExitSeat;
|
||||
|
||||
public class DoubleTapJumpToExitSeatMod : MelonMod
|
||||
{
|
||||
#region Melon Events
|
||||
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
#region CVRSeat Patches
|
||||
|
||||
HarmonyInstance.Patch(
|
||||
typeof(CVRSeat).GetMethod(nameof(CVRSeat.Update),
|
||||
BindingFlags.NonPublic | BindingFlags.Instance),
|
||||
prefix: new HarmonyMethod(typeof(DoubleTapJumpToExitSeatMod).GetMethod(nameof(OnPreCVRSeatUpdate),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
#endregion CVRSeat Patches
|
||||
|
||||
#region ViewManager Patches
|
||||
|
||||
HarmonyInstance.Patch(
|
||||
typeof(ViewManager).GetMethod(nameof(ViewManager.Update),
|
||||
BindingFlags.NonPublic | BindingFlags.Instance),
|
||||
prefix: new HarmonyMethod(typeof(DoubleTapJumpToExitSeatMod).GetMethod(nameof(OnPreViewManagerUpdate),
|
||||
BindingFlags.NonPublic | BindingFlags.Static)),
|
||||
postfix: new HarmonyMethod(typeof(DoubleTapJumpToExitSeatMod).GetMethod(nameof(OnPostViewManagerUpdate),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
#endregion ViewManager Patches
|
||||
}
|
||||
|
||||
#endregion Melon Events
|
||||
|
||||
#region Harmony Patches
|
||||
|
||||
private static float _lastJumpTime = -1f;
|
||||
private static bool _wasJumping;
|
||||
|
||||
private static bool OnPreCVRSeatUpdate(CVRSeat __instance)
|
||||
{
|
||||
if (!__instance.occupied) return false;
|
||||
|
||||
// Crazy?
|
||||
bool jumped = CVRInputManager.Instance.jump;
|
||||
bool justJumped = jumped && !_wasJumping;
|
||||
_wasJumping = jumped;
|
||||
if (justJumped)
|
||||
{
|
||||
float t = Time.time;
|
||||
if (t - _lastJumpTime <= BetterBetterCharacterController.DoubleJumpFlightTimeOut)
|
||||
{
|
||||
_lastJumpTime = -1f;
|
||||
__instance.ExitSeat();
|
||||
return false;
|
||||
}
|
||||
_lastJumpTime = t;
|
||||
}
|
||||
|
||||
// Double update this frame (this ensures Extrapolate / Every Frame Updated objects are seated correctly)
|
||||
if (__instance.vrSitPosition.position != __instance._lastPosition || __instance.vrSitPosition.rotation != __instance._lastRotation)
|
||||
__instance.MovePlayerToSeat(__instance.vrSitPositionReady ? __instance.vrSitPosition : __instance.transform);
|
||||
|
||||
// Steal sync
|
||||
if (__instance.lockControls)
|
||||
{
|
||||
if (__instance._spawnable) __instance._spawnable.ForceUpdate(4);
|
||||
if (__instance._objectSync) __instance._objectSync.ForceUpdate(4);
|
||||
}
|
||||
|
||||
return false; // don't call original method
|
||||
}
|
||||
|
||||
// ReSharper disable once RedundantAssignment
|
||||
private static void OnPreViewManagerUpdate(ref bool __state)
|
||||
=> (__state, BetterBetterCharacterController.Instance._isSitting)
|
||||
= (BetterBetterCharacterController.Instance._isSitting, false);
|
||||
|
||||
private static void OnPostViewManagerUpdate(ref bool __state)
|
||||
=> BetterBetterCharacterController.Instance._isSitting = __state;
|
||||
|
||||
#endregion Harmony Patches
|
||||
}
|
32
DoubleTapJumpToExitSeat/Properties/AssemblyInfo.cs
Normal file
32
DoubleTapJumpToExitSeat/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
using MelonLoader;
|
||||
using NAK.DoubleTapJumpToExitSeat.Properties;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyTitle(nameof(NAK.DoubleTapJumpToExitSeat))]
|
||||
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||
[assembly: AssemblyProduct(nameof(NAK.DoubleTapJumpToExitSeat))]
|
||||
|
||||
[assembly: MelonInfo(
|
||||
typeof(NAK.DoubleTapJumpToExitSeat.DoubleTapJumpToExitSeatMod),
|
||||
nameof(NAK.DoubleTapJumpToExitSeat),
|
||||
AssemblyInfoParams.Version,
|
||||
AssemblyInfoParams.Author,
|
||||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/DoubleTapJumpToExitSeat"
|
||||
)]
|
||||
|
||||
[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.DoubleTapJumpToExitSeat.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.1";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
14
DoubleTapJumpToExitSeat/README.md
Normal file
14
DoubleTapJumpToExitSeat/README.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
# DoubleTapJumpToExitSeat
|
||||
|
||||
Replaces seat exit controls with a double-tap of the jump button, avoiding accidental exits from joystick drift or opening the menu.
|
||||
|
||||
---
|
||||
|
||||
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.
|
23
DoubleTapJumpToExitSeat/format.json
Normal file
23
DoubleTapJumpToExitSeat/format.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"_id": 255,
|
||||
"name": "DoubleTapJumpToExitSeat",
|
||||
"modversion": "1.0.1",
|
||||
"gameversion": "2025r180",
|
||||
"loaderversion": "0.7.2",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Replaces seat exit controls with a double-tap of the jump button, avoiding accidental exits from joystick drift or opening the menu.",
|
||||
"searchtags": [
|
||||
"double",
|
||||
"jump",
|
||||
"chair",
|
||||
"seat"
|
||||
],
|
||||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/DoubleTapJumpToExitSeat.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/DoubleTapJumpToExitSeat/",
|
||||
"changelog": "- Fixes for 2025r180",
|
||||
"embedcolor": "#f61963"
|
||||
}
|
|
@ -3,23 +3,22 @@ using ABI_RC.Systems.IK;
|
|||
using MelonLoader;
|
||||
using RootMotion.FinalIK;
|
||||
using System.Reflection;
|
||||
using ABI_RC.Core;
|
||||
|
||||
namespace NAK.FuckToes;
|
||||
|
||||
public class FuckToesMod : MelonMod
|
||||
{
|
||||
private static MelonLogger.Instance Logger;
|
||||
|
||||
#region Melon Preferences
|
||||
|
||||
private static readonly MelonPreferences_Category Category =
|
||||
MelonPreferences.CreateCategory(nameof(FuckToesMod));
|
||||
MelonPreferences.CreateCategory(nameof(FuckToes));
|
||||
|
||||
private static readonly MelonPreferences_Entry<bool> EntryEnabledVR =
|
||||
Category.CreateEntry("Enabled in HalfBody", true, description: "Nuke VRIK toes when in Halfbody.");
|
||||
Category.CreateEntry("use_in_halfbody", true, display_name:"No Toes in Halfbody", description: "Nuke VRIK toes when in Halfbody.");
|
||||
|
||||
private static readonly MelonPreferences_Entry<bool> EntryEnabledFBT =
|
||||
Category.CreateEntry("Enabled in FBT", true, description: "Nuke VRIK toes when in FBT.");
|
||||
Category.CreateEntry("use_in_fbt", true, display_name:"No Toes in Fullbody", description: "Nuke VRIK toes when in FBT.");
|
||||
|
||||
#endregion Melon Preferences
|
||||
|
||||
|
@ -27,10 +26,10 @@ public class FuckToesMod : MelonMod
|
|||
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
Logger = LoggerInstance;
|
||||
HarmonyInstance.Patch(
|
||||
typeof(VRIK).GetMethod(nameof(VRIK.AutoDetectReferences)),
|
||||
prefix: new HarmonyLib.HarmonyMethod(typeof(FuckToesMod).GetMethod(nameof(OnVRIKAutoDetectReferences_Prefix), BindingFlags.NonPublic | BindingFlags.Static))
|
||||
prefix: new HarmonyLib.HarmonyMethod(typeof(FuckToesMod).GetMethod(nameof(OnVRIKAutoDetectReferences_Prefix),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -43,13 +42,12 @@ public class FuckToesMod : MelonMod
|
|||
try
|
||||
{
|
||||
// Must be PlayerLocal layer and in VR
|
||||
if (__instance.gameObject.layer != 8
|
||||
if (__instance.gameObject.layer != CVRLayers.PlayerLocal
|
||||
|| !MetaPort.Instance.isUsingVr)
|
||||
return;
|
||||
|
||||
switch (IKSystem.Instance.BodySystem.FullBodyActive)
|
||||
{
|
||||
|
||||
case false when !EntryEnabledVR.Value: // Not in FBT, and not enabled, perish
|
||||
case true when !EntryEnabledFBT.Value: // In FBT, and not enabled in fbt, perish
|
||||
return;
|
||||
|
@ -61,8 +59,8 @@ public class FuckToesMod : MelonMod
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error($"Error during the patched method {nameof(OnVRIKAutoDetectReferences_Prefix)}");
|
||||
Logger.Error(e);
|
||||
MelonLogger.Error($"Error during the patched method {nameof(OnVRIKAutoDetectReferences_Prefix)}");
|
||||
MelonLogger.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,6 @@ using System.Reflection;
|
|||
namespace NAK.FuckToes.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.3";
|
||||
public const string Version = "1.0.4";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
# FuckToes
|
||||
Prevents VRIK from autodetecting toes in HalfbodyIK.
|
||||
|
||||
Optionally can be applied in FBT, but toes in FBT are nice so you are a monster if so.
|
||||
Prevents VRIK from autodetecting toes in Halfbody or Fullbody.
|
||||
|
||||

|
||||
|
||||
|
@ -14,5 +13,4 @@ https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
|
|||
|
||||
> 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.
|
||||
|
||||
> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"_id": 129,
|
||||
"name": "FuckToes",
|
||||
"modversion": "1.0.3",
|
||||
"gameversion": "2023r171",
|
||||
"modversion": "1.0.4",
|
||||
"gameversion": "2025r179",
|
||||
"loaderversion": "0.6.1",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Prevents VRIK from using toe bones in HalfBody or FBT.\n\nVRIK calculates weird center of mass when toes are mapped, so it is sometimes desired to unmap toes to prevent an avatars feet from resting far back.\n\nPlease see the README for relevant imagery detailing the problem.",
|
||||
"description": "Prevents VRIK from using toe bones in HalfBody or FBT.\n\nVRIK calculates weird center of mass when toes are mapped, so it is sometimes desired to unmap toes to prevent an avatars feet from resting far back.\n\nPlease see the [README](https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/FuckToes/README.md) for relevant imagery detailing the problem.",
|
||||
"searchtags": [
|
||||
"toes",
|
||||
"vrik",
|
||||
|
@ -16,8 +16,8 @@
|
|||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r14/FuckToes.dll",
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/FuckToes.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/FuckToes/",
|
||||
"changelog": "- Fixes for 2023r171.",
|
||||
"changelog": "- Recompiled for 2025r179",
|
||||
"embedcolor": "#f61963"
|
||||
}
|
|
@ -54,6 +54,20 @@ EndProject
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RCCVirtualSteeringWheel", "RCCVirtualSteeringWheel\RCCVirtualSteeringWheel.csproj", "{4A378F81-3805-41E8-9565-A8A89A8C00D6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YouAreMyPropNowWeAreHavingSoftTacosLater", "YouAreMyPropNowWeAreHavingSoftTacosLater\YouAreMyPropNowWeAreHavingSoftTacosLater.csproj", "{8DA821CC-F911-4FCB-8C29-5EF3D76A5F76}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DoubleTapJumpToExitSeat", "DoubleTapJumpToExitSeat\DoubleTapJumpToExitSeat.csproj", "{36BF2B8B-F444-4886-AA4C-0EDF7540F1CE}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FuckToes", "FuckToes\FuckToes.csproj", "{751E4140-2F4D-4550-A4A9-65ABA9F7893A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuaNetworkVariables", ".Experimental\LuaNetworkVariables\LuaNetworkVariables.csproj", "{6E7857D9-07AC-419F-B111-0DB0348D1C92}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TouchySquishy", ".Blackbox\TouchySquishy\TouchySquishy.csproj", "{FF4BF0E7-698D-49A0-96E9-0E2646FEAFFA}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CVRLuaToolsExtension", ".Experimental\CVRLuaToolsExtension\CVRLuaToolsExtension.csproj", "{3D221A25-007F-4764-98CD-CEEF2EB92165}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConfigureCalibrationPose", "ConfigureCalibrationPose\ConfigureCalibrationPose.csproj", "{31667A36-D069-4708-9DCA-E3446009941B}"
|
||||
EndProject
|
||||
EndProject
|
||||
EndProject
|
||||
EndProject
|
||||
|
@ -289,6 +303,34 @@ Global
|
|||
{ED2CAA2D-4E49-4636-86C4-367D0CDC3572}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{ED2CAA2D-4E49-4636-86C4-367D0CDC3572}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{ED2CAA2D-4E49-4636-86C4-367D0CDC3572}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8DA821CC-F911-4FCB-8C29-5EF3D76A5F76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8DA821CC-F911-4FCB-8C29-5EF3D76A5F76}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8DA821CC-F911-4FCB-8C29-5EF3D76A5F76}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8DA821CC-F911-4FCB-8C29-5EF3D76A5F76}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{36BF2B8B-F444-4886-AA4C-0EDF7540F1CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{36BF2B8B-F444-4886-AA4C-0EDF7540F1CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{36BF2B8B-F444-4886-AA4C-0EDF7540F1CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{36BF2B8B-F444-4886-AA4C-0EDF7540F1CE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{751E4140-2F4D-4550-A4A9-65ABA9F7893A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{751E4140-2F4D-4550-A4A9-65ABA9F7893A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{751E4140-2F4D-4550-A4A9-65ABA9F7893A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{751E4140-2F4D-4550-A4A9-65ABA9F7893A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6E7857D9-07AC-419F-B111-0DB0348D1C92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6E7857D9-07AC-419F-B111-0DB0348D1C92}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6E7857D9-07AC-419F-B111-0DB0348D1C92}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6E7857D9-07AC-419F-B111-0DB0348D1C92}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FF4BF0E7-698D-49A0-96E9-0E2646FEAFFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FF4BF0E7-698D-49A0-96E9-0E2646FEAFFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FF4BF0E7-698D-49A0-96E9-0E2646FEAFFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FF4BF0E7-698D-49A0-96E9-0E2646FEAFFA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3D221A25-007F-4764-98CD-CEEF2EB92165}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3D221A25-007F-4764-98CD-CEEF2EB92165}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3D221A25-007F-4764-98CD-CEEF2EB92165}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3D221A25-007F-4764-98CD-CEEF2EB92165}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{31667A36-D069-4708-9DCA-E3446009941B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{31667A36-D069-4708-9DCA-E3446009941B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{31667A36-D069-4708-9DCA-E3446009941B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{31667A36-D069-4708-9DCA-E3446009941B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
@ -17,7 +17,7 @@ using System.Reflection;
|
|||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/PathCamDisabler"
|
||||
)]
|
||||
|
||||
[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
|
||||
|
@ -27,6 +27,6 @@ using System.Reflection;
|
|||
namespace NAK.PathCamDisabler.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.3";
|
||||
public const string Version = "1.0.4";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"_id": 110,
|
||||
"name": "PathCamDisabler",
|
||||
"modversion": "1.0.3",
|
||||
"gameversion": "2025r179",
|
||||
"loaderversion": "0.6.1",
|
||||
"modversion": "1.0.4",
|
||||
"gameversion": "2025r180",
|
||||
"loaderversion": "0.7.2",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Adds option to disable the Path Camera Controller to free up your numkeys.\nAdditional option to disable flight binding.",
|
||||
|
@ -16,8 +16,8 @@
|
|||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/PathCamDisabler.dll",
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/PathCamDisabler.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/PathCamDisabler/",
|
||||
"changelog": "- Recompiled for 2025r179",
|
||||
"changelog": "- Fixes for 2025r180",
|
||||
"embedcolor": "#f61963"
|
||||
}
|
|
@ -16,17 +16,17 @@ namespace NAK.PropUndoButton;
|
|||
|
||||
public class PropUndoButton : MelonMod
|
||||
{
|
||||
public static readonly MelonPreferences_Category Category =
|
||||
private static readonly MelonPreferences_Category Category =
|
||||
MelonPreferences.CreateCategory(nameof(PropUndoButton));
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> EntryEnabled =
|
||||
private static readonly MelonPreferences_Entry<bool> EntryEnabled =
|
||||
Category.CreateEntry("Enabled", true, description: "Toggle Undo Prop Button.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> EntryUseSFX =
|
||||
private static readonly MelonPreferences_Entry<bool> EntryUseSFX =
|
||||
Category.CreateEntry("Use SFX", true,
|
||||
description: "Toggle audio queues for prop spawn, undo, redo, and warning.");
|
||||
|
||||
internal static List<DeletedProp> deletedProps = new();
|
||||
private static readonly List<DeletedProp> deletedProps = [];
|
||||
|
||||
// audio clip names, InterfaceAudio adds "PropUndo_" prefix
|
||||
private const string sfx_spawn = "PropUndo_sfx_spawn";
|
||||
|
@ -92,11 +92,11 @@ public class PropUndoButton : MelonMod
|
|||
if (!File.Exists(clipPath))
|
||||
{
|
||||
// read the clip data from embedded resources
|
||||
byte[] clipData = null;
|
||||
byte[] clipData;
|
||||
var resourceName = "PropUndoButton.SFX." + clipName;
|
||||
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
|
||||
{
|
||||
clipData = new byte[stream.Length];
|
||||
clipData = new byte[stream!.Length];
|
||||
stream.Read(clipData, 0, clipData.Length);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ using System.Reflection;
|
|||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/UndoPropButton"
|
||||
)]
|
||||
|
||||
[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
|
||||
|
@ -27,6 +27,6 @@ using System.Reflection;
|
|||
namespace NAK.PropUndoButton.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.3";
|
||||
public const string Version = "1.0.4";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"_id": 147,
|
||||
"name": "PropUndoButton",
|
||||
"modversion": "1.0.3",
|
||||
"gameversion": "2025r179",
|
||||
"loaderversion": "0.6.1",
|
||||
"modversion": "1.0.4",
|
||||
"gameversion": "2025r180",
|
||||
"loaderversion": "0.7.2",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "**CTRL+Z** to undo latest spawned prop. **CTRL+SHIFT+Z** to redo deleted prop.\nIncludes optional SFX for prop spawn, undo, redo, warn, and deny, which can be disabled in settings.\n\nYou can replace the sfx in 'ChilloutVR\\ChilloutVR_Data\\StreamingAssets\\Cohtml\\UIResources\\GameUI\\mods\\PropUndo\\audio'.",
|
||||
|
@ -16,8 +16,8 @@
|
|||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/PropUndoButton.dll",
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/PropUndoButton.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/PropUndoButton/",
|
||||
"changelog": "- Recompiled for 2025r179",
|
||||
"changelog": "- Fixes for 2025r180",
|
||||
"embedcolor": "#00FFFF"
|
||||
}
|
|
@ -18,7 +18,7 @@ using NAK.RCCVirtualSteeringWheel.Properties;
|
|||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RCCVirtualSteeringWheel"
|
||||
)]
|
||||
|
||||
[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
|
||||
|
@ -28,6 +28,6 @@ using NAK.RCCVirtualSteeringWheel.Properties;
|
|||
namespace NAK.RCCVirtualSteeringWheel.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.4";
|
||||
public const string Version = "1.0.6";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
|
@ -26,8 +26,8 @@ public class SteeringWheelPickup : Pickupable
|
|||
|
||||
public override void OnUseDown(InteractionContext context) { }
|
||||
public override void OnUseUp(InteractionContext context) { }
|
||||
public override void OnFlingTowardsTarget(Vector3 target) { }
|
||||
|
||||
public override void FlingTowardsTarget(ControllerRay controllerRay) { }
|
||||
|
||||
public override void OnGrab(InteractionContext context, Vector3 grabPoint) {
|
||||
if (ControllerRay?.pivotPoint != null)
|
||||
root.StartTrackingTransform(ControllerRay.transform);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"_id": 248,
|
||||
"name": "RCCVirtualSteeringWheel",
|
||||
"modversion": "1.0.4",
|
||||
"gameversion": "2025r179",
|
||||
"loaderversion": "0.6.1",
|
||||
"modversion": "1.0.6",
|
||||
"gameversion": "2025r180",
|
||||
"loaderversion": "0.7.2",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Allows you to physically grab rigged RCC steering wheels in VR to provide steering input. No explicit setup required other than defining the Steering Wheel transform within the RCC component.\n",
|
||||
|
@ -16,8 +16,8 @@
|
|||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/RCCVirtualSteeringWheel.dll",
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/RCCVirtualSteeringWheel.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RCCVirtualSteeringWheel/",
|
||||
"changelog": "- Recompiled for 2025r179\n- Fixed generated steering wheel pickup collider not being marked isTrigger\n- Fixed error spam when sitting in a CVRSeat with lockControls, but no RCC component in parent",
|
||||
"changelog": "- Fixes for 2025r180",
|
||||
"embedcolor": "#f61963"
|
||||
}
|
70
README.md
70
README.md
|
@ -1,41 +1,41 @@
|
|||
## NotAKids ChilloutVR Mods
|
||||
|
||||
## Released Mods
|
||||
<!-- BEGIN MOD LIST -->
|
||||
|
||||
| Mod Name | README | Download | Description |
|
||||
|------------------------------|--------------------------------------------------------|-----------------------------------------------------------------------------------------------------|--------------------------------------------------------------------|
|
||||
| AASDefaultProfileFix | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/AASDefaultProfileFix) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/AASDefaultProfileFix.dll) | Fixes the Default AAS profile not being applied. |
|
||||
| ASTExtension | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/ASTExtension) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/ASTExtension.dll) | Avatar scaling gesture & persistance on existing avatars. |
|
||||
| AvatarQueueSystemTweaks | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/AvatarQueueSystemTweaks) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/AvatarQueueSystemTweaks.dll) | Tweaks to improve the avatar queuing system. |
|
||||
| ChatBoxExtensions | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/ChatBoxExtensions) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/ChatBoxExtensions.dll) | Adds some chat commands to the ChatBox mod. |
|
||||
| CustomSpawnPoint | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/CustomSpawnPoint) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/CustomSpawnPoint.dll) | Allows setting custom spawn points in worlds. |
|
||||
| CVRGizmos | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/CVRGizmos) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/CVRGizmos.dll) | Adds runtime gizmos to common CCK components. |
|
||||
| DropPropTweak | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/DropPropTweak) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/DropPropTweak.dll) | Allows you to drop props in the air. |
|
||||
| FOVAdjustment | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/FOVAdjustment) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/FOVAdjustment.dll) | Makes CVR_DesktopCameraController default FOV configurable. |
|
||||
| HeadLookLockingInputFix | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/HeadLookLockingInputFix) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/HeadLookLockingInputFix.dll) | Fixes head look locking input issues. |
|
||||
| IKAdjustments | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/IKAdjustments) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/IKAdjustments.dll) | Allows grabbing IK points for manual adjustment. |
|
||||
| IKSimulatedRootAngleFix | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/IKSimulatedRootAngleFix) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/IKSimulatedRootAngleFix.dll) | Fixes Desktop & HalfBody root angle issues. |
|
||||
| KeepVelocityOnExitFlight | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/KeepVelocityOnExitFlight) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/KeepVelocityOnExitFlight.dll) | Keeps the player's velocity when exiting flight mode. |
|
||||
| LazyPrune | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/LazyPrune) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/LazyPrune.dll) | Prevents loaded objects from immediately unloading. |
|
||||
| LuaTTS | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/LuaTTS) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/LuaTTS.dll) | Adds Text-to-Speech (TTS) functionality through Lua. |
|
||||
| MoreMenuOptions | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/MoreMenuOptions) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/MoreMenuOptions.dll) | Exposes some menu placement configuration options. |
|
||||
| MuteSFX | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/MuteSFX) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/MuteSFX.dll) | Adds an audio cue for muting and unmuting. |
|
||||
| OriginShift | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/OriginShift) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/OriginShift.dll) | Shifts the world origin to avoid precision issues. |
|
||||
| PathCamDisabler | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/PathCamDisabler) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/PathCamDisabler.dll) | Adds option to disable the Path Camera Controller keybinds. |
|
||||
| PortableCameraAdditions | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/PortableCameraAdditions) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/PortableCameraAdditions.dll) | Adds a few basic settings to the Portable Camera. |
|
||||
| PropLoadingHexagon | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/PropLoadingHexagon) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/PropLoadingHexagon.dll) | Adds a hexagon indicator to downloading props. |
|
||||
| PropUndoButton | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/PropUndoButton) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/PropUndoButton.dll) | CTRL+Z to undo latest spawned prop. |
|
||||
| ReconnectionSystemFix | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/ReconnectionSystemFix) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/ReconnectionSystemFix.dll) | Prevents recreating and reloading all remote players. |
|
||||
| RelativeSync | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/RelativeSync) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/RelativeSync.dll) | Relative sync for Movement Parent & Chairs. |
|
||||
| ScrollFlight | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/ScrollFlight) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/ScrollFlight.dll) | Scroll-wheel to adjust flight speed in Desktop. |
|
||||
| ShadowCloneFallback | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/ShadowCloneFallback) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/ShadowCloneFallback.dll) | Exposes a toggle for the Fallback Shadow Clone. |
|
||||
| SmartReticle | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/SmartReticle) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/SmartReticle.dll) | Makes the reticle only appear when hovering interactables. |
|
||||
| Stickers | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/Stickers) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/Stickers.dll) | Allows you to place small images on any surface. |
|
||||
| StopClosingMyMenuOnWorldLoad | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/StopClosingMyMenuOnWorldLoad)| [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/StopClosingMyMenuOnWorldLoad.dll) | Prevents your menu from being closed when a world is loaded. |
|
||||
| SwitchToDesktopOnSteamVRExit | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/SwitchToDesktopOnSteamVRExit)| [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/SwitchToDesktopOnSteamVRExit.dll) | Initiates a VR Switch to Desktop when SteamVR is exited. |
|
||||
| ThirdPerson | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/ThirdPerson) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/ThirdPerson.dll) | Allows you to go into third person view. |
|
||||
| VisualCloneFix | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/VisualCloneFix) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/VisualCloneFix.dll) | Fixes the Visual Clone system. |
|
||||
| WhereAmIPointing | [README](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/WhereAmIPointing) | [Download](https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/latest/download/WhereAmIPointing.dll) | Makes your controller rays always visible when the menus are open. |
|
||||
### Released Mods
|
||||
|
||||
| Name | Description | Download |
|
||||
|------|-------------|----------|
|
||||
| [ASTExtension](ASTExtension/README.md) | Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool): | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/ASTExtension.dll) |
|
||||
| [AvatarQueueSystemTweaks](AvatarQueueSystemTweaks/README.md) | Small tweaks to the Avatar Queue System. | No Download |
|
||||
| [ConfigureCalibrationPose](ConfigureCalibrationPose/README.md) | Select FBT calibration pose. | No Download |
|
||||
| [CustomSpawnPoint](CustomSpawnPoint/README.md) | Replaces the unused Images button in the World Details page with a button to set a custom spawn point. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/CustomSpawnPoint.dll) |
|
||||
| [DoubleTapJumpToExitSeat](DoubleTapJumpToExitSeat/README.md) | Replaces seat exit controls with a double-tap of the jump button, avoiding accidental exits from joystick drift or opening the menu. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/DoubleTapJumpToExitSeat.dll) |
|
||||
| [FuckToes](FuckToes/README.md) | Prevents VRIK from autodetecting toes in Halfbody or Fullbody. | No Download |
|
||||
| [KeepVelocityOnExitFlight](KeepVelocityOnExitFlight/README.md) | Keeps the player's velocity when exiting flight mode. Makes it possible to fling yourself like in Garry's Mod. | No Download |
|
||||
| [LazyPrune](LazyPrune/README.md) | Prevents loaded objects from immediately unloading on destruction. Should prevent needlessly unloading & reloading all avatars/props on world rejoin or GS reconnection. | No Download |
|
||||
| [PropLoadingHexagon](PropLoadingHexagon/README.md) | https://github.com/NotAKidoS/NAK_CVR_Mods/assets/37721153/a892c765-71c1-47f3-a781-bdb9b60ba117 | No Download |
|
||||
| [RCCVirtualSteeringWheel](RCCVirtualSteeringWheel/README.md) | Allows you to physically grab rigged RCC steering wheels in VR to provide steering input. No explicit setup required other than defining the Steering Wheel transform within the RCC component. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/RCCVirtualSteeringWheel.dll) |
|
||||
| [RelativeSyncJitterFix](RelativeSyncJitterFix/README.md) | Relative sync jitter fix is the single harmony patch that could not make it into the native release of RelativeSync. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/RelativeSyncJitterFix.dll) |
|
||||
| [ShareBubbles](ShareBubbles/README.md) | Share Bubbles! Allows you to drop down bubbles containing Avatars & Props. Requires both users to have the mod installed. Synced over Mod Network. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/ShareBubbles.dll) |
|
||||
| [SmootherRay](SmootherRay/README.md) | Smoothes your controller while the raycast lines are visible. | No Download |
|
||||
| [Stickers](Stickers/README.md) | Stickers! Allows you to place small images on any surface. Requires both users to have the mod installed. Synced over Mod Network. | No Download |
|
||||
| [ThirdPerson](ThirdPerson/README.md) | Original repo: https://github.com/oestradiol/CVR-Mods | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/ThirdPerson.dll) |
|
||||
| [Tinyboard](Tinyboard/README.md) | Makes the keyboard small and smart. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/Tinyboard.dll) |
|
||||
| [YouAreMyPropNowWeAreHavingSoftTacosLater](YouAreMyPropNowWeAreHavingSoftTacosLater/README.md) | Lets you bring held, attached, and occupied props through world loads. This is configurable in the mod settings. | [Download](https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/YouAreMyPropNowWeAreHavingSoftTacosLater.dll) |
|
||||
|
||||
### Experimental Mods
|
||||
|
||||
| Name | Description | Download |
|
||||
|------|-------------|----------|
|
||||
| [CVRLuaToolsExtension](.Experimental/CVRLuaToolsExtension/README.md) | Extension mod for [CVRLuaTools](https://github.com/NotAKidoS/CVRLuaTools) Hot Reload functionality. | No Download |
|
||||
| [CustomRichPresence](.Experimental/CustomRichPresence/README.md) | Lets you customize the Steam & Discord rich presence messages & values. | No Download |
|
||||
| [LuaNetworkVariables](.Experimental/LuaNetworkVariables/README.md) | Adds a simple module for creating network variables & events *kinda* similar to Garry's Mod. | No Download |
|
||||
| [LuaTTS](.Experimental/LuaTTS/README.md) | Provides access to the built-in text-to-speech (TTS) functionality to lua scripts. Allows you to make the local player speak. | No Download |
|
||||
| [OriginShift](.Experimental/OriginShift/README.md) | Experimental mod that allows world origin to be shifted to prevent floating point precision issues. | No Download |
|
||||
| [ScriptingSpoofer](.Experimental/ScriptingSpoofer/README.md) | Prevents **local** scripts from accessing your Username or UserID by spoofing them with random values each session. | No Download |
|
||||
|
||||
<!-- END MOD LIST -->
|
||||
|
||||
# How To Install
|
||||
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
using MelonLoader;
|
||||
using NAK.RelativeSync.Networking;
|
||||
using NAK.RelativeSync.Patches;
|
||||
|
||||
namespace NAK.RelativeSync;
|
||||
|
||||
public class RelativeSyncMod : MelonMod
|
||||
{
|
||||
internal static MelonLogger.Instance Logger;
|
||||
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
Logger = LoggerInstance;
|
||||
|
||||
ModNetwork.Subscribe();
|
||||
ModSettings.Initialize();
|
||||
|
||||
// Experimental sync hack
|
||||
ApplyPatches(typeof(CVRSpawnablePatches));
|
||||
|
||||
// Experimental no interpolation on Better Better Character Controller
|
||||
ApplyPatches(typeof(BetterBetterCharacterControllerPatches));
|
||||
|
||||
// Send relative sync update after network root data update
|
||||
ApplyPatches(typeof(NetworkRootDataUpdatePatches));
|
||||
|
||||
// Add components if missing (for relative sync monitor and controller)
|
||||
ApplyPatches(typeof(PlayerSetupPatches));
|
||||
ApplyPatches(typeof(PuppetMasterPatches));
|
||||
|
||||
// Add components if missing (for relative sync markers)
|
||||
ApplyPatches(typeof(CVRSeatPatches));
|
||||
ApplyPatches(typeof(CVRMovementParentPatches));
|
||||
}
|
||||
|
||||
private void ApplyPatches(Type type)
|
||||
{
|
||||
try
|
||||
{
|
||||
HarmonyInstance.PatchAll(type);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LoggerInstance.Msg($"Failed while patching {type.Name}!");
|
||||
LoggerInstance.Error(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
using MelonLoader;
|
||||
using NAK.RelativeSync.Networking;
|
||||
|
||||
namespace NAK.RelativeSync;
|
||||
|
||||
internal static class ModSettings
|
||||
{
|
||||
internal const string ModName = nameof(RelativeSync);
|
||||
|
||||
#region Melon Preferences
|
||||
|
||||
private static readonly MelonPreferences_Category Category =
|
||||
MelonPreferences.CreateCategory(ModName);
|
||||
|
||||
private static readonly MelonPreferences_Entry<bool> DebugLogInbound =
|
||||
Category.CreateEntry("DebugLogInbound", false,
|
||||
"Debug Log Inbound", description: "Log inbound network messages.");
|
||||
|
||||
private static readonly MelonPreferences_Entry<bool> DebugLogOutbound =
|
||||
Category.CreateEntry("DebugLogOutbound", false,
|
||||
"Debug Log Outbound", description: "Log outbound network messages.");
|
||||
|
||||
private static readonly MelonPreferences_Entry<bool> ExpSyncedObjectHack =
|
||||
Category.CreateEntry("ExpSyncedObjectHack", true,
|
||||
"Exp Spawnable Sync Fix", description: "Forces CVRSpawnable to update position in FixedUpdate. May help reduce local jitter on synced movement parents.");
|
||||
|
||||
private static readonly MelonPreferences_Entry<bool> ExpNoInterpolationOnBBCC =
|
||||
Category.CreateEntry("ExpNoInterpolationOnBBCC", true,
|
||||
"Exp Disable Interpolation on BBCC", description: "Disable interpolation on Better Better Character Controller. May help reduce local jitter on synced movement parents.");
|
||||
|
||||
#endregion Melon Preferences
|
||||
|
||||
internal static void Initialize()
|
||||
{
|
||||
foreach (MelonPreferences_Entry setting in Category.Entries)
|
||||
setting.OnEntryValueChangedUntyped.Subscribe(OnSettingsChanged);
|
||||
|
||||
OnSettingsChanged();
|
||||
}
|
||||
|
||||
private static void OnSettingsChanged(object oldValue = null, object newValue = null)
|
||||
{
|
||||
ModNetwork.Debug_NetworkInbound = DebugLogInbound.Value;
|
||||
ModNetwork.Debug_NetworkOutbound = DebugLogOutbound.Value;
|
||||
Patches.CVRSpawnablePatches.UseHack = ExpSyncedObjectHack.Value;
|
||||
Patches.BetterBetterCharacterControllerPatches.NoInterpolation = ExpNoInterpolationOnBBCC.Value;
|
||||
}
|
||||
}
|
|
@ -1,151 +0,0 @@
|
|||
using ABI_RC.Core.Networking;
|
||||
using ABI_RC.Systems.ModNetwork;
|
||||
using DarkRift;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.RelativeSync.Networking;
|
||||
|
||||
public static class ModNetwork
|
||||
{
|
||||
public static bool Debug_NetworkInbound = false;
|
||||
public static bool Debug_NetworkOutbound = false;
|
||||
|
||||
private static bool _isSubscribedToModNetwork;
|
||||
|
||||
private struct MovementParentSyncData
|
||||
{
|
||||
public bool HasSyncedThisData;
|
||||
public int MarkerHash;
|
||||
public Vector3 RootPosition;
|
||||
public Vector3 RootRotation;
|
||||
// public Vector3 HipPosition;
|
||||
// public Vector3 HipRotation;
|
||||
}
|
||||
|
||||
private static MovementParentSyncData _latestMovementParentSyncData;
|
||||
|
||||
#region Constants
|
||||
|
||||
private const string ModId = "MelonMod.NAK.RelativeSync";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Enums
|
||||
|
||||
private enum MessageType : byte
|
||||
{
|
||||
MovementParentOrChair = 0
|
||||
//RelativePickup = 1,
|
||||
//RelativeAttachment = 2,
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Mod Network Internals
|
||||
|
||||
internal static void Subscribe()
|
||||
{
|
||||
ModNetworkManager.Subscribe(ModId, OnMessageReceived);
|
||||
|
||||
_isSubscribedToModNetwork = ModNetworkManager.IsSubscribed(ModId);
|
||||
if (!_isSubscribedToModNetwork)
|
||||
Debug.LogError("Failed to subscribe to Mod Network!");
|
||||
}
|
||||
|
||||
// Called right after NetworkRootDataUpdate.Submit()
|
||||
internal static void SendRelativeSyncUpdate()
|
||||
{
|
||||
if (!_isSubscribedToModNetwork)
|
||||
return;
|
||||
|
||||
if (_latestMovementParentSyncData.HasSyncedThisData)
|
||||
return;
|
||||
|
||||
SendMessage(MessageType.MovementParentOrChair, _latestMovementParentSyncData.MarkerHash,
|
||||
_latestMovementParentSyncData.RootPosition, _latestMovementParentSyncData.RootRotation);
|
||||
|
||||
_latestMovementParentSyncData.HasSyncedThisData = true;
|
||||
}
|
||||
|
||||
public static void SetLatestRelativeSync(
|
||||
int markerHash,
|
||||
Vector3 position, Vector3 rotation)
|
||||
{
|
||||
// check if the data has changed
|
||||
if (_latestMovementParentSyncData.MarkerHash == markerHash
|
||||
&& _latestMovementParentSyncData.RootPosition == position
|
||||
&& _latestMovementParentSyncData.RootRotation == rotation)
|
||||
return; // no need to update (shocking)
|
||||
|
||||
_latestMovementParentSyncData.HasSyncedThisData = false; // reset
|
||||
_latestMovementParentSyncData.MarkerHash = markerHash;
|
||||
_latestMovementParentSyncData.RootPosition = position;
|
||||
_latestMovementParentSyncData.RootRotation = rotation;
|
||||
}
|
||||
|
||||
private static void SendMessage(MessageType messageType, int markerHash, Vector3 position, Vector3 rotation)
|
||||
{
|
||||
if (!IsConnectedToGameNetwork())
|
||||
return;
|
||||
|
||||
using ModNetworkMessage modMsg = new(ModId);
|
||||
modMsg.Write((byte)messageType);
|
||||
modMsg.Write(markerHash);
|
||||
modMsg.Write(position);
|
||||
modMsg.Write(rotation);
|
||||
modMsg.Send();
|
||||
|
||||
if (Debug_NetworkOutbound)
|
||||
Debug.Log(
|
||||
$"[Outbound] MessageType: {messageType}, MarkerHash: {markerHash}, Position: {position}, " +
|
||||
$"Rotation: {rotation}");
|
||||
}
|
||||
|
||||
private static void OnMessageReceived(ModNetworkMessage msg)
|
||||
{
|
||||
msg.Read(out byte msgTypeRaw);
|
||||
|
||||
if (!Enum.IsDefined(typeof(MessageType), msgTypeRaw))
|
||||
return;
|
||||
|
||||
switch ((MessageType)msgTypeRaw)
|
||||
{
|
||||
case MessageType.MovementParentOrChair:
|
||||
msg.Read(out int markerHash);
|
||||
msg.Read(out Vector3 receivedPosition);
|
||||
msg.Read(out Vector3 receivedRotation);
|
||||
// msg.Read(out Vector3 receivedHipPosition);
|
||||
// msg.Read(out Vector3 receivedHipRotation);
|
||||
|
||||
OnNetworkPositionUpdateReceived(msg.Sender, markerHash, receivedPosition, receivedRotation);
|
||||
|
||||
if (Debug_NetworkInbound)
|
||||
Debug.Log($"[Inbound] Sender: {msg.Sender}, MarkerHash: {markerHash}, " +
|
||||
$"Position: {receivedPosition}, Rotation: {receivedRotation}");
|
||||
break;
|
||||
default:
|
||||
Debug.LogError($"Invalid message type received from: {msg.Sender}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private static bool IsConnectedToGameNetwork()
|
||||
{
|
||||
return NetworkManager.Instance != null
|
||||
&& NetworkManager.Instance.GameNetwork != null
|
||||
&& NetworkManager.Instance.GameNetwork.ConnectionState == ConnectionState.Connected;
|
||||
}
|
||||
|
||||
private static void OnNetworkPositionUpdateReceived(
|
||||
string sender, int markerHash,
|
||||
Vector3 position, Vector3 rotation)
|
||||
{
|
||||
RelativeSyncManager.ApplyRelativeSync(sender, markerHash, position, rotation);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
using ABI_RC.Core.Base;
|
||||
using ABI_RC.Core.InteractionSystem;
|
||||
using ABI_RC.Core.Networking.Jobs;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Systems.Movement;
|
||||
using ABI.CCK.Components;
|
||||
using HarmonyLib;
|
||||
using NAK.RelativeSync.Components;
|
||||
using NAK.RelativeSync.Networking;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.RelativeSync.Patches;
|
||||
|
||||
internal static class PlayerSetupPatches
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.Start))]
|
||||
private static void Postfix_PlayerSetup_Start(ref PlayerSetup __instance)
|
||||
{
|
||||
__instance.AddComponentIfMissing<RelativeSyncMonitor>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal static class PuppetMasterPatches
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(PuppetMaster), nameof(PuppetMaster.Start))]
|
||||
private static void Postfix_PuppetMaster_Start(ref PuppetMaster __instance)
|
||||
{
|
||||
__instance.AddComponentIfMissing<RelativeSyncController>();
|
||||
}
|
||||
}
|
||||
|
||||
internal static class CVRSeatPatches
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(CVRSeat), nameof(CVRSeat.Awake))]
|
||||
private static void Postfix_CVRSeat_Awake(ref CVRSeat __instance)
|
||||
{
|
||||
__instance.AddComponentIfMissing<RelativeSyncMarker>();
|
||||
}
|
||||
}
|
||||
|
||||
internal static class CVRMovementParentPatches
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(CVRMovementParent), nameof(CVRMovementParent.Start))]
|
||||
private static void Postfix_CVRMovementParent_Start(ref CVRMovementParent __instance)
|
||||
{
|
||||
__instance.AddComponentIfMissing<RelativeSyncMarker>();
|
||||
}
|
||||
}
|
||||
|
||||
internal static class NetworkRootDataUpdatePatches
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(NetworkRootDataUpdate), nameof(NetworkRootDataUpdate.Submit))]
|
||||
private static void Postfix_NetworkRootDataUpdater_Submit()
|
||||
{
|
||||
ModNetwork.SendRelativeSyncUpdate(); // Send the relative sync update after the network root data update
|
||||
}
|
||||
}
|
||||
|
||||
internal static class CVRSpawnablePatches
|
||||
{
|
||||
internal static bool UseHack;
|
||||
|
||||
private static bool _canUpdate;
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(CVRSpawnable), nameof(CVRSpawnable.Update))]
|
||||
private static bool Prefix_CVRSpawnable_Update()
|
||||
=> !UseHack || _canUpdate;
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(CVRSpawnable), nameof(CVRSpawnable.FixedUpdate))]
|
||||
private static void Postfix_CVRSpawnable_FixedUpdate(ref CVRSpawnable __instance)
|
||||
{
|
||||
if (!UseHack) return;
|
||||
|
||||
_canUpdate = true;
|
||||
__instance.Update();
|
||||
_canUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class BetterBetterCharacterControllerPatches
|
||||
{
|
||||
private static bool _noInterpolation;
|
||||
internal static bool NoInterpolation
|
||||
{
|
||||
get => _noInterpolation;
|
||||
set
|
||||
{
|
||||
_noInterpolation = value;
|
||||
if (_rigidbody == null) return;
|
||||
_rigidbody.interpolation = value ? RigidbodyInterpolation.None : _initialInterpolation;
|
||||
}
|
||||
}
|
||||
|
||||
private static Rigidbody _rigidbody;
|
||||
private static RigidbodyInterpolation _initialInterpolation;
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(BetterBetterCharacterController), nameof(BetterBetterCharacterController.Start))]
|
||||
private static void Postfix_BetterBetterCharacterController_Update(ref BetterBetterCharacterController __instance)
|
||||
{
|
||||
_rigidbody = __instance.GetComponent<Rigidbody>();
|
||||
_initialInterpolation = _rigidbody.interpolation;
|
||||
NoInterpolation = _noInterpolation; // get initial value as patch runs later than settings init
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
# RelativeSync
|
||||
|
||||
Relative sync for Movement Parent & Chairs. Requires both users to have the mod installed. Synced over Mod Network.
|
||||
|
||||
https://github.com/NotAKidoS/NAK_CVR_Mods/assets/37721153/ae6c6e4b-7529-42e2-bd2c-afa050849906
|
||||
|
||||
## Mod Settings
|
||||
- **Debug Network Inbound**: Log network messages received from other players.
|
||||
- **Debug Network Outbound**: Log network messages sent to other players.
|
||||
- **Exp Spawnable Sync Hack**: Forces CVRSpawnable to update position in FixedUpdate. This can help with local jitter while on a remote synced movement parent.
|
||||
- **Exp Disable Interpolation on BBCC**: Disables interpolation on BetterBetterCharacterController. This can help with local jitter while on any movement parent.
|
||||
|
||||
## Known Issues
|
||||
- Movement Parents on remote users will still locally jitter.
|
||||
- PuppetMaster/NetIkController applies received position updates in LateUpdate, while character controller updates in FixedUpdate.
|
||||
- Movement Parents using CVRObjectSync synced by remote users will still locally jitter.
|
||||
- CVRObjectSync applies received position updates in LateUpdate, while character controller updates in FixedUpdate.
|
||||
- Slight interpolation issue with humanoid avatar hips while standing on a Movement Parent.
|
||||
- Requires further investigation. I believe it to be because hips are not synced properly, requiring me to relative sync the hips as well.
|
||||
|
||||
---
|
||||
|
||||
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.
|
|
@ -1,185 +0,0 @@
|
|||
using ABI_RC.Core.Player;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.RelativeSync.Components;
|
||||
|
||||
[DefaultExecutionOrder(9000)] // make sure this runs after NetIKController, but before Totally Wholesome LineController (9999)
|
||||
public class RelativeSyncController : MonoBehaviour
|
||||
{
|
||||
private const float MaxMagnitude = 750000000000f;
|
||||
|
||||
private float _updateInterval = 0.05f;
|
||||
private float _lastUpdate;
|
||||
|
||||
private string _userId;
|
||||
private PuppetMaster puppetMaster { get; set; }
|
||||
private RelativeSyncMarker _relativeSyncMarker;
|
||||
|
||||
private RelativeSyncData _relativeSyncData;
|
||||
private RelativeSyncData _lastSyncData;
|
||||
|
||||
#region Unity Events
|
||||
|
||||
private void Start()
|
||||
{
|
||||
puppetMaster = GetComponent<PuppetMaster>();
|
||||
|
||||
_userId = puppetMaster._playerDescriptor.ownerId;
|
||||
RelativeSyncManager.RelativeSyncControllers.Add(_userId, this);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
RelativeSyncManager.RelativeSyncControllers.Remove(_userId);
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
// if (puppetMaster._isHidden)
|
||||
// return;
|
||||
|
||||
if (_relativeSyncMarker == null)
|
||||
return;
|
||||
|
||||
if (!_relativeSyncMarker.IsComponentActive)
|
||||
return;
|
||||
|
||||
Animator animator = puppetMaster._animator;
|
||||
if (animator == null)
|
||||
return;
|
||||
|
||||
Transform avatarTransform = animator.transform;
|
||||
Transform hipTrans = (animator.avatar != null && animator.isHuman)
|
||||
? animator.GetBoneTransform(HumanBodyBones.Hips) : null;
|
||||
|
||||
Vector3 relativeHipPos = default;
|
||||
Quaternion relativeHipRot = default;
|
||||
if (hipTrans != null)
|
||||
{
|
||||
Vector3 worldRootPos = avatarTransform.position;
|
||||
Quaternion worldRootRot = avatarTransform.rotation;
|
||||
|
||||
Vector3 hipPos = hipTrans.position;
|
||||
Quaternion hipRot = hipTrans.rotation;
|
||||
|
||||
relativeHipPos = Quaternion.Inverse(worldRootRot) * (hipPos - worldRootPos);
|
||||
relativeHipRot = Quaternion.Inverse(worldRootRot) * hipRot;
|
||||
}
|
||||
|
||||
// TODO: handle the case where hip is not synced but is found on remote client
|
||||
|
||||
float lerp = Mathf.Min((Time.time - _lastUpdate) / _updateInterval, 1f);
|
||||
|
||||
ApplyRelativeRotation(avatarTransform, hipTrans, lerp);
|
||||
ApplyRelativePosition(hipTrans, lerp);
|
||||
|
||||
// idk if needed (both player root & avatar root are set to same world position) -_-_-_-
|
||||
avatarTransform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
|
||||
|
||||
// fix hip syncing because it is not relative to root, it is synced in world space -_-
|
||||
if (hipTrans != null)
|
||||
{
|
||||
hipTrans.position = transform.position + transform.rotation * relativeHipPos;
|
||||
hipTrans.rotation = transform.rotation * relativeHipRot;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyRelativeRotation(Transform avatarTransform, Transform hipTransform, float lerp)
|
||||
{
|
||||
if (!_relativeSyncMarker.ApplyRelativeRotation ||
|
||||
!(_relativeSyncData.LocalRootRotation.sqrMagnitude < MaxMagnitude))
|
||||
return; // not applying relative rotation or data is invalid
|
||||
|
||||
Quaternion markerRotation = _relativeSyncMarker.transform.rotation;
|
||||
Quaternion lastWorldRotation = markerRotation * Quaternion.Euler(_lastSyncData.LocalRootRotation);
|
||||
Quaternion worldRotation = markerRotation * Quaternion.Euler(_relativeSyncData.LocalRootRotation);
|
||||
|
||||
if (_relativeSyncMarker.OnlyApplyRelativeHeading)
|
||||
{
|
||||
Vector3 currentWorldUp = avatarTransform.up;
|
||||
|
||||
Vector3 currentForward = lastWorldRotation * Vector3.forward;
|
||||
Vector3 targetForward = worldRotation * Vector3.forward;
|
||||
|
||||
currentForward = Vector3.ProjectOnPlane(currentForward, currentWorldUp).normalized;
|
||||
targetForward = Vector3.ProjectOnPlane(targetForward, currentWorldUp).normalized;
|
||||
|
||||
lastWorldRotation = Quaternion.LookRotation(currentForward, currentWorldUp);
|
||||
worldRotation = Quaternion.LookRotation(targetForward, currentWorldUp);
|
||||
}
|
||||
|
||||
transform.rotation = Quaternion.Slerp(lastWorldRotation, worldRotation, lerp);
|
||||
}
|
||||
|
||||
private void ApplyRelativePosition(Transform hipTransform, float lerp)
|
||||
{
|
||||
if (!_relativeSyncMarker.ApplyRelativePosition ||
|
||||
!(_relativeSyncData.LocalRootPosition.sqrMagnitude < MaxMagnitude))
|
||||
return; // not applying relative position or data is invalid
|
||||
|
||||
Transform targetTransform = _relativeSyncMarker.transform;
|
||||
|
||||
Vector3 lastWorldPosition = targetTransform.TransformPoint(_lastSyncData.LocalRootPosition);
|
||||
Vector3 worldPosition = targetTransform.TransformPoint(_relativeSyncData.LocalRootPosition);
|
||||
transform.position = Vector3.Lerp(lastWorldPosition, worldPosition, lerp);
|
||||
|
||||
// if (hipTransform == null)
|
||||
// return;
|
||||
//
|
||||
// Vector3 lastWorldHipPosition = targetTransform.TransformPoint(_lastSyncData.LocalHipPosition);
|
||||
// Vector3 worldHipPosition = targetTransform.TransformPoint(_relativeSyncData.LocalHipPosition);
|
||||
// hipTransform.position = Vector3.Lerp(lastWorldHipPosition, worldHipPosition, lerp);
|
||||
}
|
||||
|
||||
#endregion Unity Events
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public void SetRelativeSyncMarker(RelativeSyncMarker target)
|
||||
{
|
||||
if (_relativeSyncMarker == target)
|
||||
return;
|
||||
|
||||
_relativeSyncMarker = target;
|
||||
|
||||
// calculate relative position and rotation so lerp can smooth it out (hack)
|
||||
if (_relativeSyncMarker == null)
|
||||
return;
|
||||
|
||||
Animator avatarAnimator = puppetMaster._animator;
|
||||
if (avatarAnimator == null)
|
||||
return; // i dont care to bother
|
||||
|
||||
RelativeSyncManager.GetRelativeAvatarPositionsFromMarker(
|
||||
avatarAnimator, _relativeSyncMarker.transform,
|
||||
out Vector3 relativePosition, out Vector3 relativeRotation);
|
||||
|
||||
// set last sync data to current position and rotation so we don't lerp from the last marker
|
||||
_lastSyncData.LocalRootPosition = relativePosition;
|
||||
_lastSyncData.LocalRootRotation = relativeRotation;
|
||||
_lastUpdate = Time.time; // reset update time
|
||||
}
|
||||
|
||||
public void SetRelativePositions(Vector3 position, Vector3 rotation)
|
||||
{
|
||||
// calculate update interval
|
||||
float prevUpdate = _lastUpdate;
|
||||
_lastUpdate = Time.time;
|
||||
_updateInterval = _lastUpdate - prevUpdate;
|
||||
|
||||
// cycle last sync data
|
||||
_lastSyncData = _relativeSyncData;
|
||||
|
||||
// set new sync data
|
||||
_relativeSyncData.LocalRootPosition = position;
|
||||
_relativeSyncData.LocalRootRotation = rotation;
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
private struct RelativeSyncData
|
||||
{
|
||||
public Vector3 LocalRootPosition;
|
||||
public Vector3 LocalRootRotation;
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
using ABI_RC.Core.InteractionSystem;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Savior;
|
||||
using ABI.CCK.Components;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.RelativeSync.Components;
|
||||
|
||||
public class RelativeSyncMarker : MonoBehaviour
|
||||
{
|
||||
public int pathHash { get; private set; }
|
||||
|
||||
public bool IsComponentActive
|
||||
=> _component.isActiveAndEnabled;
|
||||
|
||||
public bool ApplyRelativePosition = true;
|
||||
public bool ApplyRelativeRotation = true;
|
||||
public bool OnlyApplyRelativeHeading;
|
||||
|
||||
private MonoBehaviour _component;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
string path = GetGameObjectPath(transform);
|
||||
int hash = path.GetHashCode();
|
||||
|
||||
// check if it already exists (this **should** only matter in worlds)
|
||||
if (RelativeSyncManager.RelativeSyncTransforms.ContainsKey(hash))
|
||||
{
|
||||
RelativeSyncMod.Logger.Warning($"Duplicate RelativeSyncMarker found at path {path}");
|
||||
if (!FindAvailableHash(ref hash)) // super lazy fix idfc
|
||||
{
|
||||
RelativeSyncMod.Logger.Error($"Failed to find available hash for RelativeSyncMarker after 16 tries! {path}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
pathHash = hash;
|
||||
RelativeSyncManager.RelativeSyncTransforms.Add(hash, this);
|
||||
|
||||
ConfigureForPotentialMovementParent();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
RelativeSyncManager.RelativeSyncTransforms.Remove(pathHash);
|
||||
}
|
||||
|
||||
private void ConfigureForPotentialMovementParent()
|
||||
{
|
||||
if (!gameObject.TryGetComponent(out CVRMovementParent movementParent))
|
||||
{
|
||||
_component = GetComponent<CVRSeat>(); // users cant animate enabled state so i dont think matters
|
||||
return;
|
||||
}
|
||||
_component = movementParent;
|
||||
|
||||
// TODO: a refactor may be needed to handle the orientation mode being animated
|
||||
|
||||
// respect orientation mode & gravity zone
|
||||
ApplyRelativeRotation = movementParent.orientationMode == CVRMovementParent.OrientationMode.RotateWithParent;
|
||||
OnlyApplyRelativeHeading = movementParent.GetComponent<GravityZone>() == null;
|
||||
}
|
||||
|
||||
private static string GetGameObjectPath(Transform transform)
|
||||
{
|
||||
// props already have a unique instance identifier at root
|
||||
// worlds uhhhh, dont duplicate the same thing over and over thx
|
||||
// avatars on remote/local client have diff path, we need to account for it -_-
|
||||
|
||||
string path = transform.name;
|
||||
while (transform.parent != null)
|
||||
{
|
||||
transform = transform.parent;
|
||||
|
||||
// only true at root of local player object
|
||||
if (transform.CompareTag("Player"))
|
||||
{
|
||||
path = MetaPort.Instance.ownerId + "/" + path;
|
||||
break;
|
||||
} // remote player object root is already player guid
|
||||
|
||||
path = transform.name + "/" + path;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private bool FindAvailableHash(ref int hash)
|
||||
{
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
hash += 1;
|
||||
if (!RelativeSyncManager.RelativeSyncTransforms.ContainsKey(hash)) return true;
|
||||
}
|
||||
|
||||
// failed to find a hash in 16 tries, dont care
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Systems.Movement;
|
||||
using NAK.RelativeSync.Networking;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.RelativeSync.Components;
|
||||
|
||||
[DefaultExecutionOrder(int.MaxValue)]
|
||||
public class RelativeSyncMonitor : MonoBehaviour
|
||||
{
|
||||
private BetterBetterCharacterController _characterController { get; set; }
|
||||
|
||||
private RelativeSyncMarker _relativeSyncMarker;
|
||||
private RelativeSyncMarker _lastRelativeSyncMarker;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_characterController = GetComponent<BetterBetterCharacterController>();
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (_characterController == null)
|
||||
return;
|
||||
|
||||
CheckForRelativeSyncMarker();
|
||||
|
||||
if (_relativeSyncMarker == null)
|
||||
{
|
||||
if (_lastRelativeSyncMarker == null)
|
||||
return;
|
||||
|
||||
// send empty position and rotation to stop syncing
|
||||
SendEmptyPositionAndRotation();
|
||||
_lastRelativeSyncMarker = null;
|
||||
return;
|
||||
}
|
||||
|
||||
_lastRelativeSyncMarker = _relativeSyncMarker;
|
||||
|
||||
Animator avatarAnimator = PlayerSetup.Instance._animator;
|
||||
if (avatarAnimator == null)
|
||||
return; // i dont care to bother
|
||||
|
||||
RelativeSyncManager.GetRelativeAvatarPositionsFromMarker(
|
||||
avatarAnimator, _relativeSyncMarker.transform,
|
||||
out Vector3 relativePosition, out Vector3 relativeRotation);
|
||||
|
||||
ModNetwork.SetLatestRelativeSync(
|
||||
_relativeSyncMarker.pathHash,
|
||||
relativePosition, relativeRotation);
|
||||
}
|
||||
|
||||
private void CheckForRelativeSyncMarker()
|
||||
{
|
||||
if (_characterController._isSitting && _characterController._lastCvrSeat)
|
||||
{
|
||||
RelativeSyncMarker newMarker = _characterController._lastCvrSeat.GetComponent<RelativeSyncMarker>();
|
||||
_relativeSyncMarker = newMarker;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_characterController._previousMovementParent != null)
|
||||
{
|
||||
RelativeSyncMarker newMarker = _characterController._previousMovementParent.GetComponent<RelativeSyncMarker>();
|
||||
_relativeSyncMarker = newMarker;
|
||||
return;
|
||||
}
|
||||
|
||||
// none found
|
||||
_relativeSyncMarker = null;
|
||||
}
|
||||
|
||||
private void SendEmptyPositionAndRotation()
|
||||
{
|
||||
ModNetwork.SetLatestRelativeSync(RelativeSyncManager.NoTarget,
|
||||
Vector3.zero, Vector3.zero);
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
using ABI_RC.Core.Base;
|
||||
using ABI_RC.Core.Player;
|
||||
using NAK.RelativeSync.Components;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.RelativeSync;
|
||||
|
||||
public static class RelativeSyncManager
|
||||
{
|
||||
public const int NoTarget = -1;
|
||||
|
||||
public static readonly Dictionary<int, RelativeSyncMarker> RelativeSyncTransforms = new();
|
||||
public static readonly Dictionary<string, RelativeSyncController> RelativeSyncControllers = new();
|
||||
|
||||
public static void ApplyRelativeSync(string userId, int target, Vector3 position, Vector3 rotation)
|
||||
{
|
||||
if (!RelativeSyncControllers.TryGetValue(userId, out RelativeSyncController controller))
|
||||
{
|
||||
if (CVRPlayerManager.Instance.GetPlayerPuppetMaster(userId, out PuppetMaster pm))
|
||||
{
|
||||
controller = pm.AddComponentIfMissing<RelativeSyncController>();
|
||||
RelativeSyncMod.Logger.Msg($"Found PuppetMaster for user {userId}. This user is now eligible for relative sync.");
|
||||
}
|
||||
else
|
||||
{
|
||||
RelativeSyncControllers.Add(userId, null); // add null controller to prevent future lookups
|
||||
RelativeSyncMod.Logger.Warning($"Failed to find PuppetMaster for user {userId}. This is likely because the user is blocked or has blocked you. This user will not be eligible for relative sync until next game restart.");
|
||||
}
|
||||
}
|
||||
|
||||
if (controller == null)
|
||||
return;
|
||||
|
||||
// find target transform
|
||||
RelativeSyncMarker syncMarker = null;
|
||||
if (target != NoTarget) RelativeSyncTransforms.TryGetValue(target, out syncMarker);
|
||||
|
||||
controller.SetRelativeSyncMarker(syncMarker);
|
||||
controller.SetRelativePositions(position, rotation);
|
||||
}
|
||||
|
||||
public static void GetRelativeAvatarPositionsFromMarker(
|
||||
Animator avatarAnimator, Transform markerTransform,
|
||||
out Vector3 relativePosition, out Vector3 relativeRotation)
|
||||
// out Vector3 relativeHipPosition, out Vector3 relativeHipRotation)
|
||||
{
|
||||
Transform avatarTransform = avatarAnimator.transform;
|
||||
|
||||
// because our syncing is retarded, we need to sync relative from the avatar root...
|
||||
Vector3 avatarRootPosition = avatarTransform.position; // PlayerSetup.Instance.GetPlayerPosition()
|
||||
Quaternion avatarRootRotation = avatarTransform.rotation; // PlayerSetup.Instance.GetPlayerRotation()
|
||||
|
||||
relativePosition = markerTransform.InverseTransformPoint(avatarRootPosition);
|
||||
relativeRotation = (Quaternion.Inverse(markerTransform.rotation) * avatarRootRotation).eulerAngles;
|
||||
|
||||
// Transform hipTrans = (avatarAnimator.avatar != null && avatarAnimator.isHuman)
|
||||
// ? avatarAnimator.GetBoneTransform(HumanBodyBones.Hips) : null;
|
||||
//
|
||||
// if (hipTrans == null)
|
||||
// {
|
||||
// relativeHipPosition = Vector3.zero;
|
||||
// relativeHipRotation = Vector3.zero;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// relativeHipPosition = markerTransform.InverseTransformPoint(hipTrans.position);
|
||||
// relativeHipRotation = (Quaternion.Inverse(markerTransform.rotation) * hipTrans.rotation).eulerAngles;
|
||||
// }
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"_id": 211,
|
||||
"name": "RelativeSync",
|
||||
"modversion": "1.0.5",
|
||||
"gameversion": "2025r179",
|
||||
"loaderversion": "0.6.1",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Relative sync for Movement Parent & Chairs. Requires both users to have the mod installed. Synced over Mod Network.\n\nProvides some Experimental settings to also fix local jitter on movement parents.",
|
||||
"searchtags": [
|
||||
"relative",
|
||||
"sync",
|
||||
"movement",
|
||||
"chair"
|
||||
],
|
||||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/RelativeSync.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RelativeSync/",
|
||||
"changelog": "- Recompiled for 2025r179",
|
||||
"embedcolor": "#f61963"
|
||||
}
|
25
RelativeSyncJitterFix/Main.cs
Normal file
25
RelativeSyncJitterFix/Main.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using MelonLoader;
|
||||
|
||||
namespace NAK.RelativeSyncJitterFix;
|
||||
|
||||
public class RelativeSyncJitterFixMod : MelonMod
|
||||
{
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
// Experimental sync hack
|
||||
ApplyPatches(typeof(Patches.CVRSpawnablePatches));
|
||||
}
|
||||
|
||||
private void ApplyPatches(Type type)
|
||||
{
|
||||
try
|
||||
{
|
||||
HarmonyInstance.PatchAll(type);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LoggerInstance.Msg($"Failed while patching {type.Name}!");
|
||||
LoggerInstance.Error(e);
|
||||
}
|
||||
}
|
||||
}
|
22
RelativeSyncJitterFix/Patches.cs
Normal file
22
RelativeSyncJitterFix/Patches.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using ABI.CCK.Components;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace NAK.RelativeSyncJitterFix.Patches;
|
||||
|
||||
internal static class CVRSpawnablePatches
|
||||
{
|
||||
private static bool _canUpdate;
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(CVRSpawnable), nameof(CVRSpawnable.FixedUpdate))]
|
||||
private static void Postfix_CVRSpawnable_FixedUpdate(ref CVRSpawnable __instance)
|
||||
{
|
||||
_canUpdate = true;
|
||||
__instance.Update();
|
||||
_canUpdate = false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(CVRSpawnable), nameof(CVRSpawnable.Update))]
|
||||
private static bool Prefix_CVRSpawnable_Update() => _canUpdate;
|
||||
}
|
32
RelativeSyncJitterFix/Properties/AssemblyInfo.cs
Normal file
32
RelativeSyncJitterFix/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
using NAK.RelativeSyncJitterFix.Properties;
|
||||
using MelonLoader;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyTitle(nameof(NAK.RelativeSyncJitterFix))]
|
||||
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||
[assembly: AssemblyProduct(nameof(NAK.RelativeSyncJitterFix))]
|
||||
|
||||
[assembly: MelonInfo(
|
||||
typeof(NAK.RelativeSyncJitterFix.RelativeSyncJitterFixMod),
|
||||
nameof(NAK.RelativeSyncJitterFix),
|
||||
AssemblyInfoParams.Version,
|
||||
AssemblyInfoParams.Author,
|
||||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RelativeSyncJitterFix"
|
||||
)]
|
||||
|
||||
[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.RelativeSyncJitterFix.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.0";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
21
RelativeSyncJitterFix/README.md
Normal file
21
RelativeSyncJitterFix/README.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
# RelativeSyncJitterFix
|
||||
|
||||
Relative sync jitter fix is the single harmony patch that could not make it into the native release of RelativeSync.
|
||||
Changes when props apply their incoming sync data to be before the character controller simulation.
|
||||
|
||||
## Known Issues
|
||||
- Movement Parents on remote users will still locally jitter.
|
||||
- PuppetMaster/NetIkController applies received position updates in LateUpdate, while character controller updates in FixedUpdate.
|
||||
- Movement Parents using CVRObjectSync synced by remote users will still locally jitter.
|
||||
- CVRObjectSync applies received position updates in LateUpdate, while character controller updates in FixedUpdate.
|
||||
|
||||
---
|
||||
|
||||
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ChilloutVR.
|
||||
https://docs.chilloutvr.net/official/legal/tos/#7-modding-our-games
|
||||
|
||||
> This mod is an independent creation not affiliated with, supported by, or approved by ChilloutVR.
|
||||
|
||||
> 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 ChilloutVR.
|
23
RelativeSyncJitterFix/format.json
Normal file
23
RelativeSyncJitterFix/format.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"_id": -1,
|
||||
"name": "RelativeSyncJitterFix",
|
||||
"modversion": "1.0.0",
|
||||
"gameversion": "2025r180",
|
||||
"loaderversion": "0.7.2",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Relative sync jitter fix is the single harmony patch that could not make it into the native release of RelativeSync.\nChanges when props apply their incoming sync data to be before the character controller simulation.",
|
||||
"searchtags": [
|
||||
"relative",
|
||||
"sync",
|
||||
"movement",
|
||||
"chair"
|
||||
],
|
||||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/RelativeSyncJitterFix.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RelativeSyncJitterFix/",
|
||||
"changelog": "- Removed RelativeSync except for a single harmony patch",
|
||||
"embedcolor": "#f61963"
|
||||
}
|
|
@ -1,10 +1,7 @@
|
|||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using ABI_RC.Core.UI;
|
||||
using ABI_RC.Systems.IK.VRIKHandlers;
|
||||
using ABI_RC.Systems.Movement;
|
||||
using ABI.CCK.Components;
|
||||
using HarmonyLib;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ using System.Reflection;
|
|||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ScrollFlight"
|
||||
)]
|
||||
|
||||
[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
|
||||
|
@ -27,6 +27,6 @@ using System.Reflection;
|
|||
namespace NAK.ScrollFlight.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.3";
|
||||
public const string Version = "1.0.4";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"_id": 219,
|
||||
"name": "ScrollFlight",
|
||||
"modversion": "1.0.3",
|
||||
"gameversion": "2025r179",
|
||||
"loaderversion": "0.6.1",
|
||||
"modversion": "1.0.4",
|
||||
"gameversion": "2025r180",
|
||||
"loaderversion": "0.7.2",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Scroll-wheel to adjust flight speed in Desktop. Stole idea from Luc.",
|
||||
|
@ -16,8 +16,8 @@
|
|||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ScrollFlight.dll",
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/ScrollFlight.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ScrollFlight/",
|
||||
"changelog": "- Recompiled for 2025r179\n- Added an option to reset flight speed to default on exit flight",
|
||||
"changelog": "- Fixes for 2025r180",
|
||||
"embedcolor": "#f61963"
|
||||
}
|
|
@ -28,7 +28,7 @@ public class ShareBubblesMod : MelonMod
|
|||
|
||||
LoadAssetBundle();
|
||||
}
|
||||
|
||||
|
||||
public override void OnApplicationQuit()
|
||||
{
|
||||
ModNetwork.Unsubscribe();
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -17,7 +17,7 @@ using NAK.ShareBubbles.Properties;
|
|||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ShareBubbles"
|
||||
)]
|
||||
|
||||
[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
|
||||
|
@ -27,6 +27,6 @@ using NAK.ShareBubbles.Properties;
|
|||
namespace NAK.ShareBubbles.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.5";
|
||||
public const string Version = "1.1.6";
|
||||
public const string Author = "NotAKidoS, Exterrata, Noachi, RaidShadowLily, Tejler";
|
||||
}
|
|
@ -28,7 +28,8 @@ While viewing content details of an item shared *to you* by another player, you
|
|||
|
||||
Access Control is only available for **Private Content** and serves as a way to share content in-game.
|
||||
|
||||
**Note:** Session Access requires the game to be running to revoke access once you or the claimant leaves the instance. If the game is closed unexpectedly, the claimant will keep the content until you next launch the game and connect to an online instance.
|
||||
**Note:** Session Access requires the game to be running to revoke access once you or the claimant leaves the instance.
|
||||
If the game is closed unexpectedly, the claimant will keep the content until you next launch the game and connect to an online instance.
|
||||
|
||||
## Credits
|
||||
- Noachi - the bubble
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
using System.Net;
|
||||
|
||||
namespace NAK.ShareBubbles.API.Exceptions;
|
||||
|
||||
public class ShareApiException : Exception
|
||||
{
|
||||
public HttpStatusCode StatusCode { get; } // TODO: network back status code to claiming client, to show why the request failed
|
||||
public string UserFriendlyMessage { get; }
|
||||
|
||||
public ShareApiException(HttpStatusCode statusCode, string message, string userFriendlyMessage)
|
||||
: base(message)
|
||||
{
|
||||
StatusCode = statusCode;
|
||||
UserFriendlyMessage = userFriendlyMessage;
|
||||
}
|
||||
}
|
||||
|
||||
public class ContentNotSharedException : ShareApiException
|
||||
{
|
||||
public ContentNotSharedException(string contentId)
|
||||
: base(HttpStatusCode.BadRequest,
|
||||
$"Content {contentId} is not currently shared",
|
||||
"This content is not currently shared with anyone")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class ContentNotFoundException : ShareApiException
|
||||
{
|
||||
public ContentNotFoundException(string contentId)
|
||||
: base(HttpStatusCode.NotFound,
|
||||
$"Content {contentId} not found",
|
||||
"The specified content could not be found")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class UserOnlyAllowsSharesFromFriendsException : ShareApiException
|
||||
{
|
||||
public UserOnlyAllowsSharesFromFriendsException(string userId)
|
||||
: base(HttpStatusCode.Forbidden,
|
||||
$"User {userId} only accepts shares from friends",
|
||||
"This user only accepts shares from friends")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class UserNotFoundException : ShareApiException
|
||||
{
|
||||
public UserNotFoundException(string userId)
|
||||
: base(HttpStatusCode.NotFound,
|
||||
$"User {userId} not found",
|
||||
"The specified user could not be found")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class ContentAlreadySharedException : ShareApiException
|
||||
{
|
||||
public ContentAlreadySharedException(string contentId, string userId)
|
||||
: base(HttpStatusCode.Conflict,
|
||||
$"Content {contentId} is already shared with user {userId}",
|
||||
"This content is already shared with this user")
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
using ABI_RC.Core.Networking.API;
|
||||
using ABI_RC.Core.Networking.API.Responses;
|
||||
using NAK.ShareBubbles.API.Responses;
|
||||
|
||||
namespace NAK.ShareBubbles.API;
|
||||
|
||||
|
@ -34,6 +33,8 @@ public static class PedestalInfoBatchProcessor
|
|||
{ PedestalType.Prop, false }
|
||||
};
|
||||
|
||||
// This breaks compile accepting this change.
|
||||
// ReSharper disable once ChangeFieldTypeToSystemThreadingLock
|
||||
private static readonly object _lock = new();
|
||||
private const float BATCH_DELAY = 2f;
|
||||
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace NAK.ShareBubbles.API.Responses;
|
||||
|
||||
public class ActiveSharesResponse
|
||||
{
|
||||
[JsonProperty("value")]
|
||||
public List<ShareUser> Value { get; set; }
|
||||
}
|
||||
|
||||
// Idk why not just reuse UserDetails
|
||||
public class ShareUser
|
||||
{
|
||||
[JsonProperty("image")]
|
||||
public string Image { get; set; }
|
||||
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
}
|
|
@ -1,236 +0,0 @@
|
|||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using ABI_RC.Core.Networking;
|
||||
using ABI_RC.Core.Networking.API;
|
||||
using ABI_RC.Core.Networking.API.Responses;
|
||||
using ABI_RC.Core.Savior;
|
||||
using NAK.ShareBubbles.API.Exceptions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace NAK.ShareBubbles.API;
|
||||
|
||||
/// <summary>
|
||||
/// API for content sharing management.
|
||||
/// </summary>
|
||||
public static class ShareApiHelper
|
||||
{
|
||||
#region Enums
|
||||
|
||||
public enum ShareContentType
|
||||
{
|
||||
Avatar,
|
||||
Spawnable
|
||||
}
|
||||
|
||||
private enum ShareApiOperation
|
||||
{
|
||||
ShareAvatar,
|
||||
ReleaseAvatar,
|
||||
|
||||
ShareSpawnable,
|
||||
ReleaseSpawnable,
|
||||
|
||||
GetAvatarShares,
|
||||
GetSpawnableShares
|
||||
}
|
||||
|
||||
#endregion Enums
|
||||
|
||||
#region Public API
|
||||
|
||||
/// <summary>
|
||||
/// Shares content with a specified user.
|
||||
/// </summary>
|
||||
/// <param name="type">Type of content to share</param>
|
||||
/// <param name="contentId">ID of the content</param>
|
||||
/// <param name="userId">Target user ID</param>
|
||||
/// <returns>Response containing share information</returns>
|
||||
/// <exception cref="ShareApiException">Thrown when API request fails</exception>
|
||||
/// <exception cref="UserNotFoundException">Thrown when target user is not found</exception>
|
||||
/// <exception cref="ContentNotFoundException">Thrown when content is not found</exception>
|
||||
/// <exception cref="UserOnlyAllowsSharesFromFriendsException">Thrown when user only accepts shares from friends</exception>
|
||||
/// <exception cref="ContentAlreadySharedException">Thrown when content is already shared with user</exception>
|
||||
public static Task<BaseResponse<T>> ShareContentAsync<T>(ShareContentType type, string contentId, string userId)
|
||||
{
|
||||
ShareApiOperation operation = type == ShareContentType.Avatar
|
||||
? ShareApiOperation.ShareAvatar
|
||||
: ShareApiOperation.ShareSpawnable;
|
||||
|
||||
ShareRequest data = new()
|
||||
{
|
||||
ContentId = contentId,
|
||||
UserId = userId
|
||||
};
|
||||
|
||||
return MakeApiRequestAsync<T>(operation, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases shared content from a specified user.
|
||||
/// </summary>
|
||||
/// <param name="type">Type of content to release</param>
|
||||
/// <param name="contentId">ID of the content</param>
|
||||
/// <param name="userId">Optional user ID. If null, releases share from self</param>
|
||||
/// <returns>Response indicating success</returns>
|
||||
/// <exception cref="ShareApiException">Thrown when API request fails</exception>
|
||||
/// <exception cref="ContentNotSharedException">Thrown when content is not shared</exception>
|
||||
/// <exception cref="ContentNotFoundException">Thrown when content is not found</exception>
|
||||
/// <exception cref="UserNotFoundException">Thrown when specified user is not found</exception>
|
||||
public static Task<BaseResponse<T>> ReleaseShareAsync<T>(ShareContentType type, string contentId, string userId = null)
|
||||
{
|
||||
ShareApiOperation operation = type == ShareContentType.Avatar
|
||||
? ShareApiOperation.ReleaseAvatar
|
||||
: ShareApiOperation.ReleaseSpawnable;
|
||||
|
||||
// If no user ID is provided, release share from self
|
||||
userId ??= MetaPort.Instance.ownerId;
|
||||
|
||||
ShareRequest data = new()
|
||||
{
|
||||
ContentId = contentId,
|
||||
UserId = userId
|
||||
};
|
||||
|
||||
return MakeApiRequestAsync<T>(operation, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all shares for specified content.
|
||||
/// </summary>
|
||||
/// <param name="type">Type of content</param>
|
||||
/// <param name="contentId">ID of the content</param>
|
||||
/// <returns>Response containing share information</returns>
|
||||
/// <exception cref="ShareApiException">Thrown when API request fails</exception>
|
||||
/// <exception cref="ContentNotFoundException">Thrown when content is not found</exception>
|
||||
public static Task<BaseResponse<T>> GetSharesAsync<T>(ShareContentType type, string contentId)
|
||||
{
|
||||
ShareApiOperation operation = type == ShareContentType.Avatar
|
||||
? ShareApiOperation.GetAvatarShares
|
||||
: ShareApiOperation.GetSpawnableShares;
|
||||
|
||||
ShareRequest data = new() { ContentId = contentId };
|
||||
return MakeApiRequestAsync<T>(operation, data);
|
||||
}
|
||||
|
||||
#endregion Public API
|
||||
|
||||
#region Private Implementation
|
||||
|
||||
[Serializable]
|
||||
private record ShareRequest
|
||||
{
|
||||
public string ContentId { get; set; }
|
||||
public string UserId { get; set; }
|
||||
}
|
||||
|
||||
private static async Task<BaseResponse<T>> MakeApiRequestAsync<T>(ShareApiOperation operation, ShareRequest data)
|
||||
{
|
||||
ValidateAuthenticationState();
|
||||
|
||||
(string endpoint, HttpMethod method) = GetApiEndpointAndMethod(operation, data);
|
||||
using HttpRequestMessage request = CreateHttpRequest(endpoint, method, data);
|
||||
|
||||
try
|
||||
{
|
||||
using HttpResponseMessage response = await ApiConnection._client.SendAsync(request);
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
return HandleApiResponse<T>(response, content, data.ContentId, data.UserId);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
throw new ShareApiException(
|
||||
HttpStatusCode.ServiceUnavailable,
|
||||
$"Failed to communicate with the server: {ex.Message}",
|
||||
"Unable to connect to the server. Please check your internet connection.");
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
throw new ShareApiException(
|
||||
HttpStatusCode.UnprocessableEntity,
|
||||
$"Failed to process response data: {ex.Message}",
|
||||
"Server returned invalid data. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateAuthenticationState()
|
||||
{
|
||||
if (!AuthManager.IsAuthenticated)
|
||||
{
|
||||
throw new ShareApiException(
|
||||
HttpStatusCode.Unauthorized,
|
||||
"User is not authenticated",
|
||||
"Please log in to perform this action");
|
||||
}
|
||||
}
|
||||
|
||||
private static HttpRequestMessage CreateHttpRequest(string endpoint, HttpMethod method, ShareRequest data)
|
||||
{
|
||||
HttpRequestMessage request = new(method, endpoint);
|
||||
|
||||
if (method == HttpMethod.Post)
|
||||
{
|
||||
JObject json = JObject.FromObject(data);
|
||||
request.Content = new StringContent(json.ToString(), Encoding.UTF8, "application/json");
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private static BaseResponse<T> HandleApiResponse<T>(HttpResponseMessage response, string content, string contentId, string userId)
|
||||
{
|
||||
if (response.IsSuccessStatusCode)
|
||||
return CreateSuccessResponse<T>(content);
|
||||
|
||||
// Let specific exceptions propagate up to the caller
|
||||
throw response.StatusCode switch
|
||||
{
|
||||
HttpStatusCode.BadRequest => new ContentNotSharedException(contentId),
|
||||
HttpStatusCode.NotFound when userId != null => new UserNotFoundException(userId),
|
||||
HttpStatusCode.NotFound => new ContentNotFoundException(contentId),
|
||||
HttpStatusCode.Forbidden => new UserOnlyAllowsSharesFromFriendsException(userId),
|
||||
HttpStatusCode.Conflict => new ContentAlreadySharedException(contentId, userId),
|
||||
_ => new ShareApiException(
|
||||
response.StatusCode,
|
||||
$"API request failed with status {response.StatusCode}: {content}",
|
||||
"An unexpected error occurred. Please try again later.")
|
||||
};
|
||||
}
|
||||
|
||||
private static BaseResponse<T> CreateSuccessResponse<T>(string content)
|
||||
{
|
||||
var response = new BaseResponse<T>("")
|
||||
{
|
||||
IsSuccessStatusCode = true,
|
||||
HttpStatusCode = HttpStatusCode.OK
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(content))
|
||||
{
|
||||
response.Data = JsonConvert.DeserializeObject<T>(content);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private static (string endpoint, HttpMethod method) GetApiEndpointAndMethod(ShareApiOperation operation, ShareRequest data)
|
||||
{
|
||||
string baseUrl = $"{ApiConnection.APIAddress}/{ApiConnection.APIVersion}";
|
||||
string encodedContentId = HttpUtility.UrlEncode(data.ContentId);
|
||||
|
||||
return operation switch
|
||||
{
|
||||
ShareApiOperation.GetAvatarShares => ($"{baseUrl}/avatars/{encodedContentId}/shares", HttpMethod.Get),
|
||||
ShareApiOperation.ShareAvatar => ($"{baseUrl}/avatars/{encodedContentId}/shares/{HttpUtility.UrlEncode(data.UserId)}", HttpMethod.Post),
|
||||
ShareApiOperation.ReleaseAvatar => ($"{baseUrl}/avatars/{encodedContentId}/shares/{HttpUtility.UrlEncode(data.UserId)}", HttpMethod.Delete),
|
||||
ShareApiOperation.GetSpawnableShares => ($"{baseUrl}/spawnables/{encodedContentId}/shares", HttpMethod.Get),
|
||||
ShareApiOperation.ShareSpawnable => ($"{baseUrl}/spawnables/{encodedContentId}/shares/{HttpUtility.UrlEncode(data.UserId)}", HttpMethod.Post),
|
||||
ShareApiOperation.ReleaseSpawnable => ($"{baseUrl}/spawnables/{encodedContentId}/shares/{HttpUtility.UrlEncode(data.UserId)}", HttpMethod.Delete),
|
||||
_ => throw new ArgumentException($"Unknown operation: {operation}")
|
||||
};
|
||||
}
|
||||
|
||||
#endregion Private Implementation
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
using ABI_RC.Core.EventSystem;
|
||||
using ABI_RC.Core.InteractionSystem;
|
||||
using ABI_RC.Core.IO;
|
||||
using ABI_RC.Core.Networking.API;
|
||||
using ABI_RC.Core.Networking.API.Exceptions;
|
||||
using ABI_RC.Core.Networking.API.UserWebsocket;
|
||||
using ABI_RC.Core.Savior;
|
||||
using NAK.ShareBubbles.API;
|
||||
using NAK.ShareBubbles.API.Exceptions;
|
||||
using ShareBubbles.ShareBubbles.Implementation;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
using ABI_RC.Core.InteractionSystem;
|
||||
using ABI_RC.Core.IO;
|
||||
using ABI_RC.Core.Networking.API;
|
||||
using ABI_RC.Core.Networking.API.Exceptions;
|
||||
using ABI_RC.Core.Networking.API.UserWebsocket;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Savior;
|
||||
using NAK.ShareBubbles.API;
|
||||
using NAK.ShareBubbles.API.Exceptions;
|
||||
using ShareBubbles.ShareBubbles.Implementation;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
using ABI_RC.Core.Networking.API.UserWebsocket;
|
||||
using ABI_RC.Core.Networking.API;
|
||||
using ABI_RC.Core.Networking.API.UserWebsocket;
|
||||
using ABI_RC.Core.Networking.IO.Instancing;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Systems.GameEventSystem;
|
||||
using NAK.ShareBubbles.API;
|
||||
using Newtonsoft.Json;
|
||||
using UnityEngine;
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using ABI_RC.Core.Networking;
|
||||
using ABI_RC.Core.Player;
|
||||
using DarkRift;
|
||||
using UnityEngine;
|
||||
|
||||
|
@ -9,7 +10,9 @@ public static partial class ModNetwork
|
|||
#region Private Methods
|
||||
|
||||
private static bool CanSendModNetworkMessage()
|
||||
=> _isSubscribedToModNetwork && IsConnectedToGameNetwork();
|
||||
=> _isSubscribedToModNetwork
|
||||
&& IsConnectedToGameNetwork()
|
||||
&& CVRPlayerManager.Instance.NetworkPlayers.Count > 0; // No need to send if there are no players
|
||||
|
||||
private static bool IsConnectedToGameNetwork()
|
||||
{
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using ABI_RC.Core.Networking.IO.Social;
|
||||
using ABI_RC.Core.Networking.API;
|
||||
using ABI_RC.Core.Networking.IO.Social;
|
||||
using ABI_RC.Core.Savior;
|
||||
using ABI_RC.Systems.ModNetwork;
|
||||
using NAK.ShareBubbles.API;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.ShareBubbles.Networking;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
using ABI_RC.Systems.ModNetwork;
|
||||
using NAK.ShareBubbles.API;
|
||||
using ABI_RC.Core.Networking.API;
|
||||
using ABI_RC.Systems.ModNetwork;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.ShareBubbles.Networking;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
using ABI_RC.Core.InteractionSystem;
|
||||
using ABI_RC.Core.InteractionSystem.Base;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Savior;
|
||||
using ABI_RC.Systems.InputManagement;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.ShareBubbles.UI;
|
||||
|
@ -9,11 +11,23 @@ namespace NAK.ShareBubbles.UI;
|
|||
// Must be added manually by ShareBubble creation...
|
||||
public class BubbleInteract : Interactable
|
||||
{
|
||||
public override bool IsInteractableWithinRange(Vector3 sourcePos)
|
||||
public override bool IsInteractable
|
||||
{
|
||||
return Vector3.Distance(transform.position, sourcePos) < 1.5f;
|
||||
get
|
||||
{
|
||||
if (ViewManager.Instance.IsAnyMenuOpen)
|
||||
return true;
|
||||
|
||||
if (!MetaPort.Instance.isUsingVr
|
||||
&& CVRInputManager.Instance.unlockMouse)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsInteractableWithinRange(Vector3 sourcePos) => true;
|
||||
|
||||
public override void OnInteractDown(InteractionContext context, ControllerRay controllerRay)
|
||||
{
|
||||
// Not used
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"_id": 244,
|
||||
"name": "ShareBubbles",
|
||||
"modversion": "1.0.5",
|
||||
"gameversion": "2025r179",
|
||||
"loaderversion": "0.6.1",
|
||||
"modversion": "1.1.6",
|
||||
"gameversion": "2025r180",
|
||||
"loaderversion": "0.7.2",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS, Exterrata, Noachi, RaidShadowLily, Tejler, Luc",
|
||||
"description": "Share Bubbles! Allows you to drop down bubbles containing Avatars & Props. Requires both users to have the mod installed. Synced over Mod Network.\n### Features:\n- Can drop Share Bubbles linking to **any** Avatar or Prop page.\n - Share Bubbles can grant Permanent or Temporary shares to your **Private** Content if configured.\n- Adds basic in-game Share Management:\n - Directly share content to users within the current instance.\n - Revoke granted or received content shares.\n### Important:\nThe claiming of content shares through Share Bubbles will likely fail if you do not allow content shares from non-friends in your ABI Account settings!\n\n-# More information can be found on the [README](https://github.com/NotAKidoS/NAK_CVR_Mods/blob/main/ShareBubbles/README.md).",
|
||||
"description": "Share Bubbles! Allows you to drop down bubbles containing Avatars & Props. Requires both users to have the mod installed. Synced over Mod Network.\n### Features:\n- Can drop Share Bubbles linking to **any** Avatar or Prop page.\n - Share Bubbles can grant Permanent or Temporary shares to your **Private** Content if configured.\n### Important:\nThe claiming of content shares through Share Bubbles will likely fail if you do not allow content shares from non-friends in your ChilloutVR Account settings on the Hub!\n\n-# More information can be found on the [README](https://github.com/NotAKidoS/NAK_CVR_Mods/blob/main/ShareBubbles/README.md).",
|
||||
"searchtags": [
|
||||
"share",
|
||||
"bubbles",
|
||||
|
@ -17,8 +17,8 @@
|
|||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ShareBubbles.dll",
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/ShareBubbles.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ShareBubbles/",
|
||||
"changelog": "- Fixes for 2025r179\n- Fixed Public/Private text on bubble not being correct\n- Fixed bubble displaying as locked or not claimed despite being shared the content if it was private and not owned by you",
|
||||
"changelog": "- Fixes for 2025r180",
|
||||
"embedcolor": "#f61963"
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
"loaderversion": "0.6.1",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Smoothes your controller while the raycast lines are visible.\nThis is a CVR adaptation of a Beat Saber mod: [BeatSaber_SmoothedController](https://github.com/kinsi55/BeatSaber_SmoothedController)\n\n- An option is provided to only smooth when aiming at menus.\n- Smoothing characteristics are completely configurable, but the defaults are basically perfect.\n- Pairs well with the [WhereAmIPointing](https://discord.com/channels/1001388809184870441/1002058238545641542/1282798820073406556) mod.\n\n**Only supports OpenVR, not OpenXR.**\n\n-# NOTE: This disables the shitty built-in Smooth Ray setting when the mod is enabled. Compare both & you'll see why.",
|
||||
"description": "Smoothes your controller while the raycast lines are visible.\nThis is a CVR adaptation of a Beat Saber mod: [BeatSaber_SmoothedController](https://github.com/kinsi55/BeatSaber_SmoothedController)\n\n- An option is provided to only smooth when aiming at menus.\n- Smoothing characteristics are completely configurable, but the defaults are basically perfect.\n\n**Only supports OpenVR, not OpenXR.**\n\n-# NOTE: This disables the shitty built-in Smooth Ray setting when the mod is enabled. Compare both & you'll see why.",
|
||||
"searchtags": [
|
||||
"vr",
|
||||
"ray",
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Savior;
|
||||
using ABI_RC.Core.Util.Object_Behaviour;
|
||||
using System.Collections;
|
||||
using ABI_RC.Core;
|
||||
using ABI.CCK.Components;
|
||||
using UnityEngine;
|
||||
|
@ -13,7 +12,6 @@ internal static class CameraLogic
|
|||
private static float _dist;
|
||||
private static float _scale = 1f;
|
||||
private static Camera _thirdPersonCam;
|
||||
private static Camera _uiCam;
|
||||
private static Camera _desktopCam;
|
||||
private static int _storedCamMask;
|
||||
private static CameraFovClone _cameraFovClone;
|
||||
|
@ -43,19 +41,16 @@ internal static class CameraLogic
|
|||
}
|
||||
}
|
||||
|
||||
internal static IEnumerator SetupCamera()
|
||||
internal static void SetupCamera()
|
||||
{
|
||||
yield return new WaitUntil(() => PlayerSetup.Instance);
|
||||
|
||||
_thirdPersonCam = new GameObject("ThirdPersonCameraObj", typeof(Camera)).GetComponent<Camera>();
|
||||
|
||||
_cameraFovClone = _thirdPersonCam.gameObject.AddComponent<CameraFovClone>();
|
||||
|
||||
_desktopCam = PlayerSetup.Instance.desktopCamera.GetComponent<Camera>();
|
||||
_desktopCam = PlayerSetup.Instance.desktopCam;
|
||||
_cameraFovClone.targetCamera = _desktopCam;
|
||||
|
||||
_thirdPersonCam.transform.SetParent(_desktopCam.transform);
|
||||
_uiCam = _desktopCam.transform.Find("_UICamera").GetComponent<Camera>();
|
||||
|
||||
RelocateCam(CameraLocation.Default);
|
||||
|
||||
|
@ -66,15 +61,15 @@ internal static class CameraLogic
|
|||
|
||||
internal static void CopyPlayerCamValues()
|
||||
{
|
||||
Camera activePlayerCam = PlayerSetup.Instance.GetActiveCamera().GetComponent<Camera>();
|
||||
if (_thirdPersonCam == null || activePlayerCam == null)
|
||||
Camera activePlayerCam = PlayerSetup.Instance.activeCam;
|
||||
if (!_thirdPersonCam || !activePlayerCam)
|
||||
return;
|
||||
|
||||
ThirdPerson.Logger.Msg("Copying active camera settings & components.");
|
||||
CVRTools.CopyToDestCam(activePlayerCam, _thirdPersonCam, true);
|
||||
CVRTools.CopyToDestCam(activePlayerCam, _thirdPersonCam);
|
||||
|
||||
// Remove PlayerClone
|
||||
_thirdPersonCam.cullingMask &= ~(1 << CVRLayers.PlayerClone);
|
||||
// _thirdPersonCam.cullingMask &= ~(1 << CVRLayers.PlayerClone);
|
||||
|
||||
if (!CheckIsRestricted())
|
||||
return;
|
||||
|
@ -116,9 +111,9 @@ internal static class CameraLogic
|
|||
|
||||
private static void ResetDist() => _dist = 0;
|
||||
internal static void ScrollDist(float sign) { _dist += sign * 0.25f; RelocateCam(CurrentLocation); }
|
||||
internal static void AdjustScale(float height) { _scale = height; RelocateCam(CurrentLocation); }
|
||||
internal static void AdjustScale(float avatarScaleRelation) { _scale = avatarScaleRelation; RelocateCam(CurrentLocation); }
|
||||
internal static void CheckVRMode() { if (MetaPort.Instance.isUsingVr) State = false; }
|
||||
|
||||
private static bool CheckIsRestricted()
|
||||
=> CVRWorld.Instance != null && !CVRWorld.Instance.enableZoom;
|
||||
=> CVRWorld.Instance && !CVRWorld.Instance.enableZoom;
|
||||
}
|
|
@ -32,6 +32,6 @@ internal static class Patches
|
|||
//Copy camera settings & postprocessing components
|
||||
private static void OnPostWorldStart() => CopyPlayerCamValues();
|
||||
//Adjust camera distance with height as modifier
|
||||
private static void OnScaleAdjusted(float height) => AdjustScale(height);
|
||||
private static void OnScaleAdjusted(ref float ____avatarScaleRelation) => AdjustScale(____avatarScaleRelation);
|
||||
private static void OnConfigureHudAffinity() => CheckVRMode();
|
||||
}
|
|
@ -17,7 +17,7 @@ using System.Reflection;
|
|||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ThirdPerson"
|
||||
)]
|
||||
|
||||
[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, 97)] // do not change color, originally chosen by Davi
|
||||
|
@ -27,6 +27,6 @@ using System.Reflection;
|
|||
namespace NAK.ThirdPerson.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.1.1";
|
||||
public const string Version = "1.1.3";
|
||||
public const string Author = "Davi & NotAKidoS";
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using MelonLoader;
|
||||
using ABI_RC.Systems.GameEventSystem;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
using static NAK.ThirdPerson.CameraLogic;
|
||||
|
||||
|
@ -13,7 +14,7 @@ public class ThirdPerson : MelonMod
|
|||
Logger = LoggerInstance;
|
||||
|
||||
Patches.Apply(HarmonyInstance);
|
||||
MelonCoroutines.Start(SetupCamera());
|
||||
CVRGameEventSystem.Initialization.OnPlayerSetupStart.AddListener(SetupCamera);
|
||||
}
|
||||
|
||||
public override void OnUpdate()
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
{
|
||||
"_id": 16,
|
||||
"name": "ThirdPerson",
|
||||
"modversion": "1.1.1",
|
||||
"gameversion": "2025r179",
|
||||
"loaderversion": "0.6.1",
|
||||
"modversion": "1.1.3",
|
||||
"gameversion": "2025r180",
|
||||
"loaderversion": "0.7.2",
|
||||
"modtype": "Mod",
|
||||
"author": "Davi & NotAKidoS",
|
||||
"description": "Allows you to go into third person view by pressing Ctrl + T to toggle and Ctrl + Y to cycle modes.",
|
||||
|
@ -14,9 +14,9 @@
|
|||
"third person"
|
||||
],
|
||||
"requirements": [],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r46/ThirdPerson.dll",
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r47/ThirdPerson.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ThirdPerson",
|
||||
"changelog": "- Recompiled for 2025r179",
|
||||
"changelog": "- Fixes for 2025r180",
|
||||
"embedcolor": "#F61961"
|
||||
}
|
||||
]
|
346
Tinyboard/Main.cs
Normal file
346
Tinyboard/Main.cs
Normal file
|
@ -0,0 +1,346 @@
|
|||
using System.Reflection;
|
||||
using ABI_RC.Core.InteractionSystem;
|
||||
using ABI_RC.Core.Savior;
|
||||
using ABI_RC.Core.UI;
|
||||
using ABI_RC.Core.UI.UIRework.Managers;
|
||||
using ABI_RC.Systems.VRModeSwitch;
|
||||
using ABI_RC.VideoPlayer.Scripts;
|
||||
using HarmonyLib;
|
||||
using MelonLoader;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace NAK.Tinyboard;
|
||||
|
||||
public class TinyboardMod : MelonMod
|
||||
{
|
||||
#region Melon Preferences
|
||||
|
||||
private static readonly MelonPreferences_Category Category =
|
||||
MelonPreferences.CreateCategory(nameof(Tinyboard));
|
||||
|
||||
private static readonly MelonPreferences_Entry<bool> EntrySmartAlignToMenu =
|
||||
Category.CreateEntry(
|
||||
identifier: "smart_align_to_menu",
|
||||
true,
|
||||
display_name: "Smart Align To Menu",
|
||||
description: "Should the keyboard align to the menu it was opened from? (Main Menu, World-Anchored Quick Menu)");
|
||||
|
||||
private static readonly MelonPreferences_Entry<bool> EntryEnforceTitle =
|
||||
Category.CreateEntry(
|
||||
identifier: "enforce_title",
|
||||
true,
|
||||
display_name: "Enforce Title",
|
||||
description: "Should the keyboard enforce a title when opened from an input field or main menu?");
|
||||
|
||||
private static readonly MelonPreferences_Entry<bool> EntryResizeKeyboard =
|
||||
Category.CreateEntry(
|
||||
identifier: "resize_keyboard",
|
||||
true,
|
||||
display_name: "Resize Keyboard",
|
||||
description: "Should the keyboard be resized to match XSOverlays width?");
|
||||
|
||||
private static readonly MelonPreferences_Entry<bool> EntryUseModifiers =
|
||||
Category.CreateEntry(
|
||||
identifier: "use_scale_distance_modifiers",
|
||||
true,
|
||||
display_name: "Use Scale/Distance/Offset Modifiers",
|
||||
description: "Should the scale/distance/offset modifiers be used?");
|
||||
|
||||
private static readonly MelonPreferences_Entry<float> EntryDesktopScaleModifier =
|
||||
Category.CreateEntry(
|
||||
identifier: "desktop_scale_modifier",
|
||||
0.75f,
|
||||
display_name: "Desktop Scale Modifier",
|
||||
description: "Scale modifier for desktop mode.");
|
||||
|
||||
private static readonly MelonPreferences_Entry<float> EntryDesktopDistance =
|
||||
Category.CreateEntry(
|
||||
identifier: "desktop_distance_modifier",
|
||||
0f,
|
||||
display_name: "Desktop Distance Modifier",
|
||||
description: "Distance modifier for desktop mode.");
|
||||
|
||||
private static readonly MelonPreferences_Entry<float> EntryDesktopVerticalAdjustment =
|
||||
Category.CreateEntry(
|
||||
identifier: "desktop_vertical_adjustment",
|
||||
0.1f,
|
||||
display_name: "Desktop Vertical Adjustment",
|
||||
description: "Vertical adjustment for desktop mode.");
|
||||
|
||||
private static readonly MelonPreferences_Entry<float> EntryVRScaleModifier =
|
||||
Category.CreateEntry(
|
||||
identifier: "vr_scale_modifier",
|
||||
0.85f,
|
||||
display_name: "VR Scale Modifier",
|
||||
description: "Scale modifier for VR mode.");
|
||||
|
||||
private static readonly MelonPreferences_Entry<float> EntryVRDistance =
|
||||
Category.CreateEntry(
|
||||
identifier: "vr_distance_modifier",
|
||||
0.2f,
|
||||
display_name: "VR Distance Modifier",
|
||||
description: "Distance modifier for VR mode.");
|
||||
|
||||
private static readonly MelonPreferences_Entry<float> EntryVRVerticalAdjustment =
|
||||
Category.CreateEntry(
|
||||
identifier: "vr_vertical_adjustment",
|
||||
0f,
|
||||
display_name: "VR Vertical Adjustment",
|
||||
description: "Vertical adjustment for VR mode.");
|
||||
|
||||
#endregion Melon Preferences
|
||||
|
||||
private static Transform _tinyBoardOffset;
|
||||
private static void ApplyTinyBoardOffsetsForVRMode()
|
||||
{
|
||||
if (!EntryUseModifiers.Value)
|
||||
{
|
||||
_tinyBoardOffset.localScale = Vector3.one;
|
||||
_tinyBoardOffset.localPosition = Vector3.zero;
|
||||
return;
|
||||
}
|
||||
float distanceModifier;
|
||||
float scaleModifier;
|
||||
float verticalAdjustment;
|
||||
if (MetaPort.Instance.isUsingVr)
|
||||
{
|
||||
scaleModifier = EntryVRScaleModifier.Value;
|
||||
distanceModifier = EntryVRDistance.Value;
|
||||
verticalAdjustment = EntryVRVerticalAdjustment.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
scaleModifier = EntryDesktopScaleModifier.Value;
|
||||
distanceModifier = EntryDesktopDistance.Value;
|
||||
verticalAdjustment = EntryDesktopVerticalAdjustment.Value;
|
||||
}
|
||||
_tinyBoardOffset.localScale = Vector3.one * scaleModifier;
|
||||
_tinyBoardOffset.localPosition = new Vector3(0f, verticalAdjustment, distanceModifier);
|
||||
}
|
||||
|
||||
private static void ApplyTinyBoardWidthResize()
|
||||
{
|
||||
KeyboardManager km = KeyboardManager.Instance;
|
||||
CohtmlControlledView cohtmlView = km.cohtmlView;
|
||||
Transform keyboardTransform = cohtmlView.transform;
|
||||
|
||||
int targetWidthPixels = EntryResizeKeyboard.Value ? 1330 : 1520;
|
||||
float targetScaleX = EntryResizeKeyboard.Value ? 1.4f : 1.6f;
|
||||
|
||||
cohtmlView.Width = targetWidthPixels;
|
||||
Vector3 currentScale = keyboardTransform.localScale;
|
||||
currentScale.x = targetScaleX;
|
||||
keyboardTransform.localScale = currentScale;
|
||||
}
|
||||
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
// add our shim transform to scale the menu down by 0.75
|
||||
HarmonyInstance.Patch(
|
||||
typeof(CVRKeyboardPositionHelper).GetMethod(nameof(CVRKeyboardPositionHelper.Awake),
|
||||
BindingFlags.NonPublic | BindingFlags.Instance),
|
||||
postfix: new HarmonyMethod(typeof(TinyboardMod).GetMethod(nameof(OnCVRKeyboardPositionHelperAwake),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
// reposition the keyboard when it is opened to match the menu position if it is opened from a menu
|
||||
HarmonyInstance.Patch(
|
||||
typeof(MenuPositionHelperBase).GetMethod(nameof(MenuPositionHelperBase.OnMenuOpen),
|
||||
BindingFlags.NonPublic | BindingFlags.Instance),
|
||||
postfix: new HarmonyMethod(typeof(TinyboardMod).GetMethod(nameof(OnMenuPositionHelperBaseOnMenuOpen),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
// enforces a title for the keyboard in cases it did not already have one
|
||||
HarmonyInstance.Patch(
|
||||
typeof(KeyboardManager).GetMethod(nameof(KeyboardManager.ShowKeyboard),
|
||||
BindingFlags.Public | BindingFlags.Instance),
|
||||
prefix: new HarmonyMethod(typeof(TinyboardMod).GetMethod(nameof(OnKeyboardManagerShowKeyboard),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
// resize keyboard to match XSOverlays width
|
||||
HarmonyInstance.Patch(
|
||||
typeof(KeyboardManager).GetMethod(nameof(KeyboardManager.Start),
|
||||
BindingFlags.NonPublic | BindingFlags.Instance),
|
||||
postfix: new HarmonyMethod(typeof(TinyboardMod).GetMethod(nameof(OnKeyboardManagerStart),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
// update offsets when switching VR modes
|
||||
VRModeSwitchEvents.OnPostVRModeSwitch.AddListener((_) => ApplyTinyBoardOffsetsForVRMode());
|
||||
|
||||
// listen for setting changes
|
||||
EntryUseModifiers.OnEntryValueChanged.Subscribe((_,_) => ApplyTinyBoardOffsetsForVRMode());
|
||||
EntryDesktopScaleModifier.OnEntryValueChanged.Subscribe((_,_) => ApplyTinyBoardOffsetsForVRMode());
|
||||
EntryVRScaleModifier.OnEntryValueChanged.Subscribe((_,_) => ApplyTinyBoardOffsetsForVRMode());
|
||||
EntryDesktopDistance.OnEntryValueChanged.Subscribe((_,_) => ApplyTinyBoardOffsetsForVRMode());
|
||||
EntryVRDistance.OnEntryValueChanged.Subscribe((_,_) => ApplyTinyBoardOffsetsForVRMode());
|
||||
EntryResizeKeyboard.OnEntryValueChanged.Subscribe((_,_) => ApplyTinyBoardWidthResize());
|
||||
}
|
||||
|
||||
private static void OnCVRKeyboardPositionHelperAwake(CVRKeyboardPositionHelper __instance)
|
||||
{
|
||||
_tinyBoardOffset = new GameObject("NAKTinyBoard").transform;
|
||||
|
||||
Transform offsetTransform = __instance.transform.GetChild(0);
|
||||
_tinyBoardOffset.SetParent(offsetTransform, false);
|
||||
|
||||
ApplyTinyBoardOffsetsForVRMode();
|
||||
|
||||
Transform menuTransform = __instance.menuTransform;
|
||||
menuTransform.SetParent(_tinyBoardOffset, false);
|
||||
}
|
||||
|
||||
private static void OnMenuPositionHelperBaseOnMenuOpen(MenuPositionHelperBase __instance)
|
||||
{
|
||||
if (!EntrySmartAlignToMenu.Value) return;
|
||||
if (__instance is not CVRKeyboardPositionHelper { IsMenuOpen: true }) return;
|
||||
|
||||
// Check if the open source was an open menu
|
||||
KeyboardManager.OpenSource? openSource = KeyboardManager.Instance._keyboardOpenSource;
|
||||
|
||||
MenuPositionHelperBase menuPositionHelper;
|
||||
switch (openSource)
|
||||
{
|
||||
case KeyboardManager.OpenSource.MainMenu:
|
||||
menuPositionHelper = CVRMainMenuPositionHelper.Instance;
|
||||
break;
|
||||
case KeyboardManager.OpenSource.QuickMenu:
|
||||
menuPositionHelper = CVRQuickMenuPositionHelper.Instance;
|
||||
if (!menuPositionHelper.IsUsingWorldAnchoredMenu) return; // hand anchored quick menu, don't touch
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
|
||||
// get modifiers
|
||||
float rootScaleModifier = __instance.transform.lossyScale.x;
|
||||
float keyboardDistanceModifier = __instance.MenuDistanceModifier;
|
||||
float menuDistanceModifier = menuPositionHelper.MenuDistanceModifier;
|
||||
|
||||
// get difference between modifiers
|
||||
float distanceModifier = keyboardDistanceModifier - menuDistanceModifier;
|
||||
|
||||
// place keyboard at menu position + difference in modifiers
|
||||
Transform menuOffsetTransform = menuPositionHelper._offsetTransform;
|
||||
Quaternion keyboardRotation = menuOffsetTransform.rotation;
|
||||
Vector3 keyboardPosition = menuOffsetTransform.position +
|
||||
menuOffsetTransform.forward * (rootScaleModifier * distanceModifier);
|
||||
|
||||
// place keyboard as if it was opened with player camera in same place as menu was
|
||||
__instance._offsetTransform.SetPositionAndRotation(keyboardPosition, keyboardRotation);
|
||||
}
|
||||
|
||||
private static void OnKeyboardManagerStart() => ApplyTinyBoardWidthResize();
|
||||
|
||||
/*
|
||||
public void ShowKeyboard(
|
||||
string currentText,
|
||||
Action<string> callback,
|
||||
string placeholder = null,
|
||||
string successText = "Success",
|
||||
int maxCharacterCount = 0,
|
||||
bool hidden = false,
|
||||
bool multiLine = false,
|
||||
string title = null,
|
||||
OpenSource openSource = OpenSource.Other)
|
||||
*/
|
||||
|
||||
// using mix of index and args params because otherwise explodes with invalid IL ?
|
||||
private static void OnKeyboardManagerShowKeyboard(ref string __7, ref string __2, object[] __args)
|
||||
{
|
||||
if (!EntryEnforceTitle.Value) return;
|
||||
|
||||
// ReSharper disable thrice InlineTemporaryVariable
|
||||
ref string title = ref __7;
|
||||
ref string placeholder = ref __2;
|
||||
if (!string.IsNullOrWhiteSpace(title)) return;
|
||||
|
||||
Action<string> callback = __args[1] as Action<string>;
|
||||
KeyboardManager.OpenSource? openSource = __args[8] as KeyboardManager.OpenSource?;
|
||||
|
||||
if (callback?.Target != null)
|
||||
{
|
||||
var target = callback.Target;
|
||||
switch (openSource)
|
||||
{
|
||||
case KeyboardManager.OpenSource.CVRInputFieldKeyboardHandler:
|
||||
TrySetPlaceholderFromKeyboardHandler(target, ref title, ref placeholder);
|
||||
break;
|
||||
case KeyboardManager.OpenSource.MainMenu:
|
||||
title = TryExtractTitleFromMainMenu(target);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(placeholder))
|
||||
{
|
||||
// fallback to placeholder if no title found
|
||||
if (string.IsNullOrWhiteSpace(title)) title = placeholder;
|
||||
|
||||
// clear placeholder if it is longer than 10 characters
|
||||
if (placeholder.Length > 10) placeholder = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private static void TrySetPlaceholderFromKeyboardHandler(object target, ref string title, ref string placeholder)
|
||||
{
|
||||
Type type = target.GetType();
|
||||
|
||||
TMP_InputField tmpInput = type.GetField("input", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(target) as TMP_InputField;
|
||||
if (tmpInput != null)
|
||||
{
|
||||
if (tmpInput.GetComponentInParent<ViewManagerVideoPlayer>()) title = "VideoPlayer URL or Search";
|
||||
if (tmpInput.placeholder is TMP_Text ph)
|
||||
{
|
||||
placeholder = ph.text;
|
||||
return;
|
||||
}
|
||||
placeholder = PrettyString(tmpInput.gameObject.name);
|
||||
return;
|
||||
}
|
||||
|
||||
InputField legacyInput = type.GetField("inputField", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(target) as InputField;
|
||||
if (legacyInput != null)
|
||||
{
|
||||
if (legacyInput.placeholder is Text ph)
|
||||
{
|
||||
placeholder = ph.text;
|
||||
return;
|
||||
}
|
||||
placeholder = PrettyString(legacyInput.gameObject.name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static string TryExtractTitleFromMainMenu(object target)
|
||||
{
|
||||
Type type = target.GetType();
|
||||
string targetId = type.GetField("targetId", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(target) as string;
|
||||
return string.IsNullOrWhiteSpace(targetId) ? null : PrettyString(targetId);
|
||||
}
|
||||
|
||||
private static string PrettyString(string str)
|
||||
{
|
||||
int len = str.Length;
|
||||
Span<char> buffer = stackalloc char[len * 2];
|
||||
int pos = 0;
|
||||
bool newWord = true;
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
char c = str[i];
|
||||
if (c is '_' or '-')
|
||||
{
|
||||
buffer[pos++] = ' ';
|
||||
newWord = true;
|
||||
continue;
|
||||
}
|
||||
if (char.IsUpper(c) && i > 0 && !newWord) buffer[pos++] = ' ';
|
||||
buffer[pos++] = newWord ? char.ToUpperInvariant(c) : c;
|
||||
newWord = false;
|
||||
}
|
||||
return new string(buffer[..pos]);
|
||||
}
|
||||
}
|
|
@ -1,32 +1,32 @@
|
|||
using NAK.RelativeSync.Properties;
|
||||
using MelonLoader;
|
||||
using MelonLoader;
|
||||
using NAK.Tinyboard.Properties;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyTitle(nameof(NAK.RelativeSync))]
|
||||
[assembly: AssemblyTitle(nameof(NAK.Tinyboard))]
|
||||
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||
[assembly: AssemblyProduct(nameof(NAK.RelativeSync))]
|
||||
[assembly: AssemblyProduct(nameof(NAK.Tinyboard))]
|
||||
|
||||
[assembly: MelonInfo(
|
||||
typeof(NAK.RelativeSync.RelativeSyncMod),
|
||||
nameof(NAK.RelativeSync),
|
||||
typeof(NAK.Tinyboard.TinyboardMod),
|
||||
nameof(NAK.Tinyboard),
|
||||
AssemblyInfoParams.Version,
|
||||
AssemblyInfoParams.Author,
|
||||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/RelativeSync"
|
||||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/Tinyboard"
|
||||
)]
|
||||
|
||||
[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
|
||||
[assembly: MelonAuthorColor(255, 158, 21, 32)] // red
|
||||
[assembly: HarmonyDontPatchAll]
|
||||
|
||||
namespace NAK.RelativeSync.Properties;
|
||||
namespace NAK.Tinyboard.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.5";
|
||||
public const string Version = "1.0.0";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
19
Tinyboard/README.md
Normal file
19
Tinyboard/README.md
Normal 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.
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue