mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2025-09-02 22:39:22 +00:00
CVRLuaToolsExtension: initial testing
This commit is contained in:
parent
c0208d8e97
commit
6810bcf021
13 changed files with 668 additions and 0 deletions
6
CVRLuaToolsExtension/CVRLuaToolsExtension.csproj
Normal file
6
CVRLuaToolsExtension/CVRLuaToolsExtension.csproj
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<RootNamespace>LoadedObjectHack</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
44
CVRLuaToolsExtension/LuaToolsExtension/Base/Singleton.cs
Normal file
44
CVRLuaToolsExtension/LuaToolsExtension/Base/Singleton.cs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NAK.CVRLuaToolsExtension;
|
||||||
|
|
||||||
|
public abstract class Singleton<T> : MonoBehaviour where T : MonoBehaviour
|
||||||
|
{
|
||||||
|
private static T _instance;
|
||||||
|
private static readonly object _lock = new();
|
||||||
|
private static bool _isShuttingDown;
|
||||||
|
|
||||||
|
public static T Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_isShuttingDown)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[Singleton] Instance of {typeof(T)} already destroyed. Returning null.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (_instance != null) return _instance;
|
||||||
|
_instance = (T)FindObjectOfType(typeof(T));
|
||||||
|
if (_instance != null) return _instance;
|
||||||
|
GameObject singletonObject = new($"{typeof(T).Name} (Singleton)");
|
||||||
|
_instance = singletonObject.AddComponent<T>();
|
||||||
|
DontDestroyOnLoad(singletonObject);
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnApplicationQuit()
|
||||||
|
{
|
||||||
|
_isShuttingDown = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
if (_instance == this)
|
||||||
|
_isShuttingDown = true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
using ABI_RC.Core.Player;
|
||||||
|
using ABI_RC.Core.Savior;
|
||||||
|
using ABI.CCK.Components;
|
||||||
|
using ABI.Scripting.CVRSTL.Common;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NAK.CVRLuaToolsExtension;
|
||||||
|
|
||||||
|
public static class CVRBaseLuaBehaviourExtensions
|
||||||
|
{
|
||||||
|
// check if the script is eligible for hot reload
|
||||||
|
public static bool IsScriptEligibleForHotReload(this CVRBaseLuaBehaviour script)
|
||||||
|
{
|
||||||
|
return script.Context.IsWornByMe // avatar if worn
|
||||||
|
|| script.Context.IsSpawnedByMe // prop if spawned
|
||||||
|
|| script.Context.objContext == CVRLuaObjectContext.WORLD; // always world scripts
|
||||||
|
}
|
||||||
|
|
||||||
|
// gets the asset id from the script
|
||||||
|
public static string GetAssetIdFromScript(this CVRBaseLuaBehaviour script)
|
||||||
|
{
|
||||||
|
switch (script.Context.objContext)
|
||||||
|
{
|
||||||
|
case CVRLuaObjectContext.WORLD:
|
||||||
|
//return MetaPort.Instance.CurrentWorldId; // do not trust CVRAssetInfo, can be destroyed at runtime
|
||||||
|
return "SYSTEM"; // default for world scripts is SYSTEM, but TODO: use actual world id
|
||||||
|
case CVRLuaObjectContext.AVATAR:
|
||||||
|
Component rootComponent = script.Context.RootComponent;
|
||||||
|
return rootComponent switch
|
||||||
|
{
|
||||||
|
PlayerSetup => MetaPort.Instance.currentAvatarGuid, // local avatar
|
||||||
|
PuppetMaster puppetMaster => puppetMaster.CVRPlayerEntity.AvatarId, // remote avatar
|
||||||
|
_ => string.Empty // fuck
|
||||||
|
};
|
||||||
|
case CVRLuaObjectContext.PROP:
|
||||||
|
{
|
||||||
|
CVRSpawnable spawnable = (CVRSpawnable)script.Context.RootComponent;
|
||||||
|
if (!string.IsNullOrEmpty(spawnable.guid)) return spawnable.guid; // after filtering has occured
|
||||||
|
return spawnable.TryGetComponent(out CVRAssetInfo assetInfo)
|
||||||
|
? assetInfo.objectId // before filtering has occured
|
||||||
|
: string.Empty; // well shit
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
using ABI.CCK.Components;
|
||||||
|
using ABI.Scripting.CVRSTL.Client;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using MTJobSystem;
|
||||||
|
|
||||||
|
namespace NAK.CVRLuaToolsExtension;
|
||||||
|
|
||||||
|
public static class CVRLuaClientBehaviourExtensions
|
||||||
|
{
|
||||||
|
internal static readonly Dictionary<CVRLuaClientBehaviour, bool> _isRestarting = new();
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
public static void Restart(this CVRLuaClientBehaviour behaviour)
|
||||||
|
{
|
||||||
|
if (_isRestarting.TryGetValue(behaviour, out bool isRestarting) && isRestarting)
|
||||||
|
{
|
||||||
|
CVRLuaToolsExtensionMod.Logger.Warning($"Restart is already in progress for {behaviour.ScriptName}.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isRestarting[behaviour] = true;
|
||||||
|
|
||||||
|
bool wasEnabled = behaviour.enabled;
|
||||||
|
if (behaviour.Crashed)
|
||||||
|
{
|
||||||
|
CVRLuaToolsExtensionMod.Logger.Warning($"Restarting a script ({behaviour.ScriptName}) in a crashed state. Unable to determine original enabled state, defaulting to true.");
|
||||||
|
wasEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
behaviour.enabled = false;
|
||||||
|
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
behaviour.ResetScriptCompletely();
|
||||||
|
if (CVRLuaToolsExtensionMod.EntryAttemptInitOffMainThread.Value)
|
||||||
|
{
|
||||||
|
CVRLuaToolsExtensionMod.Logger.Warning("Attempting to initialize the lua script off main thread. This may cause crashes or corruption if you are accessing UnityEngine Objects outside of Unity's callbacks!");
|
||||||
|
behaviour.DoInitialLoadOfScript();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
CVRLuaToolsExtensionMod.Logger.Error(e); // don't wanna die in task
|
||||||
|
}
|
||||||
|
|
||||||
|
MTJobManager.RunOnMainThread("RestartScript", () =>
|
||||||
|
{
|
||||||
|
Stopwatch stopwatch = new();
|
||||||
|
stopwatch.Start();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!CVRLuaToolsExtensionMod.EntryAttemptInitOffMainThread.Value)
|
||||||
|
behaviour.DoInitialLoadOfScript();
|
||||||
|
|
||||||
|
if (behaviour.InitialCodeRun) behaviour.IsScriptInitialized = true; // allow callbacks to run again
|
||||||
|
|
||||||
|
// invoke custom event on reset
|
||||||
|
if (!behaviour.ShouldSkipEvent("CVRLuaTools_OnReset")) behaviour.ExecuteEvent(5000, "CVRLuaTools_OnReset");
|
||||||
|
|
||||||
|
// re-enable the script & invoke the lifecycle events
|
||||||
|
if (!behaviour.ShouldSkipEvent("Awake")) behaviour.ExecuteEvent(1000, "Awake");
|
||||||
|
behaviour.enabled = wasEnabled;
|
||||||
|
if (wasEnabled)
|
||||||
|
{
|
||||||
|
if (!behaviour.ShouldSkipEvent("OnEnable")) behaviour.ExecuteEvent(1000, "OnEnable");
|
||||||
|
if (!behaviour.ShouldSkipEvent("Start")) behaviour.ExecuteEvent(1000, "Start");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
CVRLuaToolsExtensionMod.Logger.Error(e); // don't wanna die prior to resetting restart flag
|
||||||
|
}
|
||||||
|
|
||||||
|
_isRestarting.Remove(behaviour);
|
||||||
|
|
||||||
|
stopwatch.Stop();
|
||||||
|
CVRLuaToolsExtensionMod.Logger.Msg($"Restarted {behaviour.ScriptName} in {stopwatch.ElapsedMilliseconds}ms.");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Public Methods
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
|
||||||
|
private static void ResetScriptCompletely(this CVRLuaClientBehaviour behaviour)
|
||||||
|
{
|
||||||
|
var boundObjectEntries = new List<LuaScriptFactory.BoundObjectEntry>();
|
||||||
|
foreach (var kvp in behaviour.BoundObjects)
|
||||||
|
{
|
||||||
|
LuaScriptFactory.BoundObjectEntry entry = new(behaviour, behaviour.Context, kvp.Key, kvp.Value);
|
||||||
|
boundObjectEntries.Add(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
behaviour.IsScriptInitialized = false; // prevent callbacks from causing null refs while restarting
|
||||||
|
behaviour.InitialCodeRun = false; // needs to be set so LoadAndRunScriptIfNeeded will run the script again
|
||||||
|
behaviour.Crashed = false; // reset the crash flag
|
||||||
|
|
||||||
|
behaviour._scriptGlobalFunctions.Clear(); // will be repopulated
|
||||||
|
behaviour._startupMessageQueue.Clear(); // will be repopulated
|
||||||
|
behaviour.LogInfo("[CVRLuaToolsExtension] Resetting script...\n");
|
||||||
|
|
||||||
|
behaviour.script = null;
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DoInitialLoadOfScript(this CVRLuaClientBehaviour behaviour)
|
||||||
|
{
|
||||||
|
behaviour.LogInfo("[CVRLuaToolsExtension] Interpreting " + behaviour.ScriptName + "...\n");
|
||||||
|
behaviour.LoadAndRunScript(behaviour.ScriptText); // fucking slow
|
||||||
|
behaviour.PopulateScriptGlobalFunctionsCache(behaviour.script.Globals); // tbh don't think we need to clear in first place
|
||||||
|
behaviour.HandleMessageQueueEntries(); // shouldn't be any
|
||||||
|
behaviour.InitialCodeRun = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Private Methods
|
||||||
|
}
|
155
CVRLuaToolsExtension/LuaToolsExtension/LuaHotReloadManager.cs
Normal file
155
CVRLuaToolsExtension/LuaToolsExtension/LuaHotReloadManager.cs
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
using System.Text;
|
||||||
|
using ABI_RC.Core.InteractionSystem;
|
||||||
|
using ABI_RC.Core.UI;
|
||||||
|
using ABI.CCK.Components;
|
||||||
|
using ABI.CCK.Components.ScriptableObjects;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NAK.CVRLuaToolsExtension;
|
||||||
|
|
||||||
|
public static class LuaHotReloadManager
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<string, List<int>> s_AssetIdToLuaClientBehaviourIds = new();
|
||||||
|
private static readonly Dictionary<int, CVRLuaClientBehaviour> s_LuaComponentIdsToLuaClientBehaviour = new();
|
||||||
|
|
||||||
|
#region Game Events
|
||||||
|
|
||||||
|
public static void OnCVRLuaBaseBehaviourLoadAndRunScript(CVRLuaClientBehaviour clientBehaviour)
|
||||||
|
{
|
||||||
|
//CVRLuaToolsExtensionMod.Logger.Msg($"[LuaHotReloadManager] Script awake: {clientBehaviour.name}");
|
||||||
|
|
||||||
|
if (!clientBehaviour.IsScriptEligibleForHotReload())
|
||||||
|
return;
|
||||||
|
|
||||||
|
var assetId = clientBehaviour.GetAssetIdFromScript();
|
||||||
|
if (!s_AssetIdToLuaClientBehaviourIds.ContainsKey(assetId))
|
||||||
|
s_AssetIdToLuaClientBehaviourIds[assetId] = new List<int>();
|
||||||
|
|
||||||
|
var luaComponentId = GetGameObjectPathHashCode(clientBehaviour.transform);
|
||||||
|
if (s_AssetIdToLuaClientBehaviourIds[assetId].Contains(luaComponentId))
|
||||||
|
{
|
||||||
|
CVRLuaToolsExtensionMod.Logger.Warning(
|
||||||
|
$"[LuaHotReloadManager] Script already exists: {clientBehaviour.name}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_AssetIdToLuaClientBehaviourIds[assetId].Add(luaComponentId);
|
||||||
|
s_LuaComponentIdsToLuaClientBehaviour[luaComponentId] = clientBehaviour;
|
||||||
|
CVRLuaToolsExtensionMod.Logger.Msg($"[LuaHotReloadManager] Added script: {clientBehaviour.name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void OnCVRLuaBaseBehaviourDestroy(CVRLuaClientBehaviour clientBehaviour)
|
||||||
|
{
|
||||||
|
//CVRLuaToolsExtensionMod.Logger.Msg($"[LuaHotReloadManager] Script destroy: {clientBehaviour.name}");
|
||||||
|
|
||||||
|
var assetId = clientBehaviour.GetAssetIdFromScript();
|
||||||
|
if (!s_AssetIdToLuaClientBehaviourIds.ContainsKey(assetId))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var luaClientBehaviourIds = s_AssetIdToLuaClientBehaviourIds[assetId];
|
||||||
|
foreach (var luaComponentId in luaClientBehaviourIds)
|
||||||
|
{
|
||||||
|
if (!s_LuaComponentIdsToLuaClientBehaviour.TryGetValue(luaComponentId,
|
||||||
|
out CVRLuaClientBehaviour luaClientBehaviour))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (luaClientBehaviour != clientBehaviour)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
s_LuaComponentIdsToLuaClientBehaviour.Remove(luaComponentId);
|
||||||
|
luaClientBehaviourIds.Remove(luaComponentId);
|
||||||
|
if (luaClientBehaviourIds.Count == 0) s_AssetIdToLuaClientBehaviourIds.Remove(assetId);
|
||||||
|
CVRLuaToolsExtensionMod.Logger.Msg($"[LuaHotReloadManager] Removed script: {clientBehaviour.name}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void OnReceiveUpdatedScript(ScriptInfo info)
|
||||||
|
{
|
||||||
|
if (!s_AssetIdToLuaClientBehaviourIds.TryGetValue(info.AssetId, out var luaComponentIds))
|
||||||
|
{
|
||||||
|
CVRLuaToolsExtensionMod.Logger.Warning(
|
||||||
|
$"[LuaHotReloadManager] No scripts found for asset id: {info.AssetId}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
foreach (var luaComponentId in luaComponentIds)
|
||||||
|
{
|
||||||
|
if (!s_LuaComponentIdsToLuaClientBehaviour.TryGetValue(luaComponentId,
|
||||||
|
out CVRLuaClientBehaviour clientBehaviour))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
found = true;
|
||||||
|
//CVRLuaToolsExtensionMod.Logger.Msg($"[LuaHotReloadManager] Reloading script: {info.ScriptName} for {clientBehaviour.name}");
|
||||||
|
|
||||||
|
if (clientBehaviour.asset.m_ScriptPath == info.ScriptPath)
|
||||||
|
{
|
||||||
|
CVRLuaToolsExtensionMod.Logger.Msg("[LuaHotReloadManager] Script path match, updating script.");
|
||||||
|
clientBehaviour.asset.name = info.ScriptName;
|
||||||
|
clientBehaviour.asset.m_ScriptText = info.ScriptText;
|
||||||
|
clientBehaviour.Restart();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CVRLuaToolsExtensionMod.Logger.Msg("[LuaHotReloadManager] Script path mismatch, creating new script.");
|
||||||
|
|
||||||
|
clientBehaviour.asset = null;
|
||||||
|
clientBehaviour.asset = ScriptableObject.CreateInstance<CVRLuaScript>();
|
||||||
|
|
||||||
|
clientBehaviour.asset.name = info.ScriptName;
|
||||||
|
clientBehaviour.asset.m_ScriptPath = info.ScriptPath;
|
||||||
|
clientBehaviour.asset.m_ScriptText = info.ScriptText;
|
||||||
|
|
||||||
|
clientBehaviour.Restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found) CohtmlHud.Instance.ViewDropTextImmediate("(Local) CVRLuaTools", "Received script update", "Reloaded script: " + info.ScriptName);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Game Events
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
|
||||||
|
private static int GetGameObjectPathHashCode(Transform transform)
|
||||||
|
{
|
||||||
|
// Attempt to find the root component transform in one step
|
||||||
|
|
||||||
|
Transform rootComponentTransform = null;
|
||||||
|
|
||||||
|
// both CVRAvatar & CVRSpawnable *should* have an asset info component
|
||||||
|
CVRAssetInfo rootComponent = transform.GetComponentInParent<CVRAssetInfo>(true);
|
||||||
|
if (rootComponent != null && rootComponent.type != CVRAssetInfo.AssetType.World)
|
||||||
|
rootComponentTransform = rootComponent.transform;
|
||||||
|
|
||||||
|
// easy case, no need to crawl up the hierarchy
|
||||||
|
if (rootComponentTransform == transform)
|
||||||
|
return 581452743; // hash code for "[Root]"
|
||||||
|
|
||||||
|
StringBuilder pathBuilder = new(transform.name);
|
||||||
|
Transform parentTransform = transform.parent;
|
||||||
|
|
||||||
|
while (parentTransform != null)
|
||||||
|
{
|
||||||
|
// reached root component
|
||||||
|
// due to object loader renaming root, we can't rely on transform name, so we use "[Root]" instead
|
||||||
|
if (parentTransform == rootComponentTransform)
|
||||||
|
{
|
||||||
|
pathBuilder.Insert(0, "[Root]/");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
pathBuilder.Insert(0, parentTransform.name + "/");
|
||||||
|
parentTransform = parentTransform.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
string path = pathBuilder.ToString();
|
||||||
|
|
||||||
|
//Debug.Log($"[LuaComponentManager] Path: {path}");
|
||||||
|
|
||||||
|
return path.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Private Methods
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO.Pipes;
|
||||||
|
using System.Text;
|
||||||
|
using ABI_RC.Core;
|
||||||
|
|
||||||
|
namespace NAK.CVRLuaToolsExtension.NamedPipes;
|
||||||
|
|
||||||
|
public class NamedPipeServer : Singleton<NamedPipeServer>
|
||||||
|
{
|
||||||
|
private const string PipeName = "UnityPipe";
|
||||||
|
|
||||||
|
public async void StartListening()
|
||||||
|
{
|
||||||
|
Stopwatch stopwatch = new();
|
||||||
|
while (!CommonTools.IsQuitting)
|
||||||
|
{
|
||||||
|
await using NamedPipeServerStream namedPipeServer = new(PipeName, PipeDirection.In);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CVRLuaToolsExtensionMod.Logger.Msg("Waiting for client...");
|
||||||
|
await namedPipeServer.WaitForConnectionAsync();
|
||||||
|
stopwatch.Restart();
|
||||||
|
if (!namedPipeServer.IsConnected)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
//Debug.Log("Client connected.");
|
||||||
|
|
||||||
|
// receive and process the ScriptInfo
|
||||||
|
ScriptInfo receivedScript = await ReceiveScriptInfo(namedPipeServer);
|
||||||
|
ProcessScriptInfo(receivedScript);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
CVRLuaToolsExtensionMod.Logger.Error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (namedPipeServer.IsConnected)
|
||||||
|
{
|
||||||
|
namedPipeServer.Disconnect();
|
||||||
|
CVRLuaToolsExtensionMod.Logger.Msg("Client disconnected.");
|
||||||
|
}
|
||||||
|
|
||||||
|
stopwatch.Stop();
|
||||||
|
CVRLuaToolsExtensionMod.Logger.Msg($"Elapsed time: {stopwatch.ElapsedMilliseconds}ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
CVRLuaToolsExtensionMod.Logger.Msg("Quitting...");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<ScriptInfo> ReceiveScriptInfo(NamedPipeServerStream stream)
|
||||||
|
{
|
||||||
|
// Read LuaComponentId (4 bytes for an int)
|
||||||
|
byte[] intBuffer = new byte[4];
|
||||||
|
await stream.ReadAsync(intBuffer, 0, intBuffer.Length);
|
||||||
|
int luaComponentId = BitConverter.ToInt32(intBuffer, 0);
|
||||||
|
|
||||||
|
string assetId = await ReadStringAsync(stream);
|
||||||
|
//CVRLuaToolsExtensionMod.Logger.Msg($"Received assetId: {assetId}");
|
||||||
|
|
||||||
|
string scriptName = await ReadStringAsync(stream);
|
||||||
|
//CVRLuaToolsExtensionMod.Logger.Msg($"Received scriptName: {scriptName}");
|
||||||
|
|
||||||
|
string scriptPath = await ReadStringAsync(stream);
|
||||||
|
//CVRLuaToolsExtensionMod.Logger.Msg($"Received scriptPath: {scriptPath}");
|
||||||
|
|
||||||
|
string scriptText = await ReadStringAsync(stream);
|
||||||
|
//CVRLuaToolsExtensionMod.Logger.Msg($"Received scriptText: {scriptText}");
|
||||||
|
|
||||||
|
//Debug.Log("ScriptInfo received.");
|
||||||
|
|
||||||
|
return new ScriptInfo
|
||||||
|
{
|
||||||
|
LuaComponentId = luaComponentId,
|
||||||
|
AssetId = assetId,
|
||||||
|
ScriptName = scriptName,
|
||||||
|
ScriptPath = scriptPath,
|
||||||
|
ScriptText = scriptText
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<string> ReadStringAsync(Stream stream)
|
||||||
|
{
|
||||||
|
// Define a buffer size and initialize a memory stream
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
using MemoryStream memoryStream = new();
|
||||||
|
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
|
||||||
|
{
|
||||||
|
memoryStream.Write(buffer, 0, bytesRead);
|
||||||
|
//CVRLuaToolsExtensionMod.Logger.Msg($"Read {bytesRead} bytes.");
|
||||||
|
if (bytesRead < buffer.Length) break; // Assuming no partial writes
|
||||||
|
}
|
||||||
|
|
||||||
|
return Encoding.UTF8.GetString(memoryStream.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ProcessScriptInfo(ScriptInfo scriptInfo)
|
||||||
|
{
|
||||||
|
// Handle the received ScriptInfo data
|
||||||
|
//Debug.Log($"Received ScriptInfo: {scriptInfo.AssetId}, {scriptInfo.LuaComponentId}, {scriptInfo.ScriptName}");
|
||||||
|
|
||||||
|
LuaHotReloadManager.OnReceiveUpdatedScript(scriptInfo);
|
||||||
|
}
|
||||||
|
}
|
12
CVRLuaToolsExtension/LuaToolsExtension/ScriptInfo.cs
Normal file
12
CVRLuaToolsExtension/LuaToolsExtension/ScriptInfo.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
namespace NAK.CVRLuaToolsExtension;
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public struct ScriptInfo
|
||||||
|
{
|
||||||
|
public string AssetId; // we will reload all scripts with this asset id
|
||||||
|
public int LuaComponentId; // the target lua component id
|
||||||
|
|
||||||
|
public string ScriptName; // the new script name
|
||||||
|
public string ScriptPath;
|
||||||
|
public string ScriptText; // the new script text
|
||||||
|
}
|
67
CVRLuaToolsExtension/Main.cs
Normal file
67
CVRLuaToolsExtension/Main.cs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
using System.Reflection;
|
||||||
|
using ABI_RC.Systems.GameEventSystem;
|
||||||
|
using ABI.CCK.Components;
|
||||||
|
using HarmonyLib;
|
||||||
|
using MelonLoader;
|
||||||
|
using NAK.CVRLuaToolsExtension.NamedPipes;
|
||||||
|
|
||||||
|
namespace NAK.CVRLuaToolsExtension;
|
||||||
|
|
||||||
|
public class CVRLuaToolsExtensionMod : MelonMod
|
||||||
|
{
|
||||||
|
internal static MelonLogger.Instance Logger;
|
||||||
|
|
||||||
|
#region Melon Preferences
|
||||||
|
|
||||||
|
private const string ModName = nameof(CVRLuaToolsExtension);
|
||||||
|
|
||||||
|
private static readonly MelonPreferences_Category Category =
|
||||||
|
MelonPreferences.CreateCategory(ModName);
|
||||||
|
|
||||||
|
internal static readonly MelonPreferences_Entry<bool> EntryAttemptInitOffMainThread =
|
||||||
|
Category.CreateEntry("attempt_init_off_main_thread", false,
|
||||||
|
"Attempt init off Main Thread", "Attempt to initialize the lua script off main thread.");
|
||||||
|
|
||||||
|
#endregion Melon Preferences
|
||||||
|
|
||||||
|
#region Melon Events
|
||||||
|
|
||||||
|
public override void OnInitializeMelon()
|
||||||
|
{
|
||||||
|
Logger = LoggerInstance;
|
||||||
|
|
||||||
|
HarmonyInstance.Patch(
|
||||||
|
typeof(CVRLuaClientBehaviour).GetMethod(nameof(CVRLuaClientBehaviour.InitializeLuaVm)), // protected
|
||||||
|
postfix: new HarmonyMethod(typeof(CVRLuaToolsExtensionMod).GetMethod(nameof(OnCVRBaseLuaBehaviourLoadAndRunScript),
|
||||||
|
BindingFlags.NonPublic | BindingFlags.Static))
|
||||||
|
);
|
||||||
|
HarmonyInstance.Patch(
|
||||||
|
typeof(CVRLuaClientBehaviour).GetMethod(nameof(CVRLuaClientBehaviour.OnDestroy)), // public for some reason
|
||||||
|
postfix: new HarmonyMethod(typeof(CVRLuaToolsExtensionMod).GetMethod(nameof(OnCVRBaseLuaBehaviourDestroy),
|
||||||
|
BindingFlags.NonPublic | BindingFlags.Static))
|
||||||
|
);
|
||||||
|
|
||||||
|
CVRGameEventSystem.Initialization.OnPlayerSetupStart.AddListener(
|
||||||
|
() => NamedPipeServer.Instance.StartListening());
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Melon Events
|
||||||
|
|
||||||
|
#region Harmony Patches
|
||||||
|
|
||||||
|
// theres no good place to patch where GetOwnerId & GetObjectId would be valid to call...
|
||||||
|
// pls move InitializeMain after Context is created... pls
|
||||||
|
private static void OnCVRBaseLuaBehaviourLoadAndRunScript(CVRLuaClientBehaviour __instance)
|
||||||
|
{
|
||||||
|
LuaHotReloadManager.OnCVRLuaBaseBehaviourLoadAndRunScript(__instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnCVRBaseLuaBehaviourDestroy(CVRLuaClientBehaviour __instance)
|
||||||
|
{
|
||||||
|
LuaHotReloadManager.OnCVRLuaBaseBehaviourDestroy(__instance);
|
||||||
|
if (CVRLuaClientBehaviourExtensions._isRestarting.ContainsKey(__instance))
|
||||||
|
CVRLuaClientBehaviourExtensions._isRestarting.Remove(__instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Harmony Patches
|
||||||
|
}
|
32
CVRLuaToolsExtension/Properties/AssemblyInfo.cs
Normal file
32
CVRLuaToolsExtension/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
using MelonLoader;
|
||||||
|
using NAK.CVRLuaToolsExtension.Properties;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||||
|
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||||
|
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||||
|
[assembly: AssemblyTitle(nameof(NAK.CVRLuaToolsExtension))]
|
||||||
|
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||||
|
[assembly: AssemblyProduct(nameof(NAK.CVRLuaToolsExtension))]
|
||||||
|
|
||||||
|
[assembly: MelonInfo(
|
||||||
|
typeof(NAK.CVRLuaToolsExtension.CVRLuaToolsExtensionMod),
|
||||||
|
nameof(NAK.CVRLuaToolsExtension),
|
||||||
|
AssemblyInfoParams.Version,
|
||||||
|
AssemblyInfoParams.Author,
|
||||||
|
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/CVRLuaToolsExtension"
|
||||||
|
)]
|
||||||
|
|
||||||
|
[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.CVRLuaToolsExtension.Properties;
|
||||||
|
internal static class AssemblyInfoParams
|
||||||
|
{
|
||||||
|
public const string Version = "1.0.0";
|
||||||
|
public const string Author = "NotAKidoS";
|
||||||
|
}
|
14
CVRLuaToolsExtension/README.md
Normal file
14
CVRLuaToolsExtension/README.md
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# IKSimulatedRootAngleFix
|
||||||
|
|
||||||
|
Fixes a small issue with Desktop & HalfBody root angle being incorrectly calculated while on rotating Movement Parents. If you've ever noticed your body/feet insisting on facing opposite of the direction you are rotating, this fixes that.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
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.
|
24
CVRLuaToolsExtension/format.json
Normal file
24
CVRLuaToolsExtension/format.json
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"_id": -1,
|
||||||
|
"name": "AASDefaultProfileFix",
|
||||||
|
"modversion": "1.0.0",
|
||||||
|
"gameversion": "2024r175",
|
||||||
|
"loaderversion": "0.6.1",
|
||||||
|
"modtype": "Mod",
|
||||||
|
"author": "NotAKidoS",
|
||||||
|
"description": "Fixes the Default AAS profile not being applied when loading into an avatar without a profile selected.\n\nBy default, the game will not apply anything and the avatar will default to the state found within the Controller parameters.",
|
||||||
|
"searchtags": [
|
||||||
|
"aas",
|
||||||
|
"profile",
|
||||||
|
"default",
|
||||||
|
"fix",
|
||||||
|
"meow"
|
||||||
|
],
|
||||||
|
"requirements": [
|
||||||
|
"None"
|
||||||
|
],
|
||||||
|
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r33/AASDefaultProfileFix.dll",
|
||||||
|
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/AASDefaultProfileFix/",
|
||||||
|
"changelog": "- Initial release",
|
||||||
|
"embedcolor": "#f61963"
|
||||||
|
}
|
|
@ -85,6 +85,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvatarQueueSystemTweaks", "
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomSpawnPoint", "CustomSpawnPoint\CustomSpawnPoint.csproj", "{51CA34CA-7684-4819-AC9E-89DFAD63E9AB}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomSpawnPoint", "CustomSpawnPoint\CustomSpawnPoint.csproj", "{51CA34CA-7684-4819-AC9E-89DFAD63E9AB}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NukePostPresentHandoff", "NukePostPresentHandoff\NukePostPresentHandoff.csproj", "{77F332CD-019A-472F-9269-CFDEB087B3F9}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CVRLuaToolsExtension", "CVRLuaToolsExtension\CVRLuaToolsExtension.csproj", "{FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stickers", "Stickers\Stickers.csproj", "{E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
@ -255,6 +261,18 @@ Global
|
||||||
{51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Release|Any CPU.Build.0 = Release|Any CPU
|
{51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{77F332CD-019A-472F-9269-CFDEB087B3F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{77F332CD-019A-472F-9269-CFDEB087B3F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{77F332CD-019A-472F-9269-CFDEB087B3F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{77F332CD-019A-472F-9269-CFDEB087B3F9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
|
@ -76,6 +76,14 @@
|
||||||
<HintPath>$(MsBuildThisFileDirectory)\.ManagedLibs\Cohtml.Runtime.dll</HintPath>
|
<HintPath>$(MsBuildThisFileDirectory)\.ManagedLibs\Cohtml.Runtime.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="com.dogu.gamium.engine.unity.runtime">
|
||||||
|
<HintPath>$(MsBuildThisFileDirectory)\.ManagedLibs\com.dogu.gamium.engine.unity.runtime.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="com.dogu.gamium.engine.unity.runtime.protocol">
|
||||||
|
<HintPath>$(MsBuildThisFileDirectory)\.ManagedLibs\com.dogu.gamium.engine.unity.runtime.protocol.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
<Reference Include="Crc32.NET">
|
<Reference Include="Crc32.NET">
|
||||||
<HintPath>$(MsBuildThisFileDirectory)\.ManagedLibs\Crc32.NET.dll</HintPath>
|
<HintPath>$(MsBuildThisFileDirectory)\.ManagedLibs\Crc32.NET.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
|
@ -136,6 +144,10 @@
|
||||||
<HintPath>$(MsBuildThisFileDirectory)\.ManagedLibs\MeshBakerCore.dll</HintPath>
|
<HintPath>$(MsBuildThisFileDirectory)\.ManagedLibs\MeshBakerCore.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="Meta.XR.BuildingBlocks">
|
||||||
|
<HintPath>$(MsBuildThisFileDirectory)\.ManagedLibs\Meta.XR.BuildingBlocks.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
<Reference Include="Mono.Security">
|
<Reference Include="Mono.Security">
|
||||||
<HintPath>$(MsBuildThisFileDirectory)\.ManagedLibs\Mono.Security.dll</HintPath>
|
<HintPath>$(MsBuildThisFileDirectory)\.ManagedLibs\Mono.Security.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
|
@ -172,6 +184,14 @@
|
||||||
<HintPath>$(MsBuildThisFileDirectory)\.ManagedLibs\Oculus.LipSync.dll</HintPath>
|
<HintPath>$(MsBuildThisFileDirectory)\.ManagedLibs\Oculus.LipSync.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="Oculus.Platform">
|
||||||
|
<HintPath>$(MsBuildThisFileDirectory)\.ManagedLibs\Oculus.Platform.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Oculus.VR">
|
||||||
|
<HintPath>$(MsBuildThisFileDirectory)\.ManagedLibs\Oculus.VR.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
<Reference Include="PICO.Platform">
|
<Reference Include="PICO.Platform">
|
||||||
<HintPath>$(MsBuildThisFileDirectory)\.ManagedLibs\PICO.Platform.dll</HintPath>
|
<HintPath>$(MsBuildThisFileDirectory)\.ManagedLibs\PICO.Platform.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue