mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2025-09-02 06:19:22 +00:00
LuaNetworkVariables: idk
This commit is contained in:
parent
db07d53971
commit
fe768029eb
19 changed files with 1680 additions and 0 deletions
6
LuaNetworkVariables/LuaNetworkVariables.csproj
Normal file
6
LuaNetworkVariables/LuaNetworkVariables.csproj
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
</PropertyGroup>
|
||||
</Project>
|
58
LuaNetworkVariables/Main.cs
Normal file
58
LuaNetworkVariables/Main.cs
Normal file
|
@ -0,0 +1,58 @@
|
|||
using ABI_RC.Core.Player;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.LuaNetVars;
|
||||
|
||||
public class LuaNetVarsMod : MelonMod
|
||||
{
|
||||
internal static MelonLogger.Instance Logger;
|
||||
|
||||
#region Melon Preferences
|
||||
|
||||
|
||||
#endregion Melon Preferences
|
||||
|
||||
#region Melon Events
|
||||
|
||||
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>();
|
||||
// }
|
||||
}
|
||||
|
||||
#endregion Melon Events
|
||||
|
||||
#region Melon Mod Utilities
|
||||
|
||||
private void ApplyPatches(Type type)
|
||||
{
|
||||
try
|
||||
{
|
||||
HarmonyInstance.PatchAll(type);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LoggerInstance.Msg($"Failed while patching {type.Name}!");
|
||||
LoggerInstance.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Melon Mod Utilities
|
||||
}
|
137
LuaNetworkVariables/NetLuaModule.cs
Normal file
137
LuaNetworkVariables/NetLuaModule.cs
Normal file
|
@ -0,0 +1,137 @@
|
|||
using ABI_RC.Core.Base;
|
||||
using ABI.Scripting.CVRSTL.Common;
|
||||
using JetBrains.Annotations;
|
||||
using NAK.LuaNetVars;
|
||||
using MoonSharp.Interpreter;
|
||||
|
||||
namespace NAK.LuaNetVars.Modules;
|
||||
|
||||
[PublicAPI] // Indicates that this class is used and should not be considered unused
|
||||
public class LuaNetModule : BaseScriptedStaticWrapper
|
||||
{
|
||||
public const string MODULE_ID = "NetworkModule";
|
||||
|
||||
private LuaNetVarController _controller;
|
||||
|
||||
public LuaNetModule(CVRLuaContext context) : base(context)
|
||||
{
|
||||
_controller = context.behaviour.AddComponentIfMissing<LuaNetVarController>();
|
||||
}
|
||||
|
||||
internal static object RegisterUserData(Script script, CVRLuaContext context)
|
||||
{
|
||||
// Register the LuaNetModule type with MoonSharp
|
||||
UserData.RegisterType<LuaNetModule>(InteropAccessMode.Default, MODULE_ID);
|
||||
return new LuaNetModule(context);
|
||||
}
|
||||
|
||||
#region Module Instance Methods
|
||||
|
||||
/// <summary>
|
||||
/// Registers a network variable that can be synchronized over the network.
|
||||
/// </summary>
|
||||
/// <param name="varName">The name of the variable to register.</param>
|
||||
public void RegisterNetworkVar(string varName)
|
||||
{
|
||||
CheckIfCanAccessMethod(nameof(RegisterNetworkVar), false,
|
||||
CVRLuaEnvironmentContext.CLIENT, CVRLuaObjectContext.ALL_BUT_EVENTS, CVRLuaOwnerContext.ANY);
|
||||
|
||||
if (_controller == null)
|
||||
{
|
||||
LuaNetVarsMod.Logger.Error("LuaNetVarController is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
_controller.RegisterNetworkVar(varName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a callback function to be called when the specified network variable changes.
|
||||
/// </summary>
|
||||
/// <param name="varName">The name of the variable to watch.</param>
|
||||
/// <param name="callback">The Lua function to call when the variable changes.</param>
|
||||
public void RegisterNotifyCallback(string varName, DynValue callback)
|
||||
{
|
||||
CheckIfCanAccessMethod(nameof(RegisterNotifyCallback), false,
|
||||
CVRLuaEnvironmentContext.CLIENT, CVRLuaObjectContext.ALL_BUT_EVENTS, CVRLuaOwnerContext.ANY);
|
||||
|
||||
if (_controller == null)
|
||||
{
|
||||
LuaNetVarsMod.Logger.Error("LuaNetVarController is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
_controller.RegisterNotifyCallback(varName, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a callback function to be called when the specified event is received.
|
||||
/// </summary>
|
||||
/// <param name="eventName">The name of the event to watch.</param>
|
||||
/// <param name="callback">The Lua function to call when the event occurs.</param>
|
||||
public void RegisterEventCallback(string eventName, DynValue callback)
|
||||
{
|
||||
CheckIfCanAccessMethod(nameof(RegisterEventCallback), false,
|
||||
CVRLuaEnvironmentContext.CLIENT, CVRLuaObjectContext.ALL_BUT_EVENTS, CVRLuaOwnerContext.ANY);
|
||||
|
||||
if (_controller == null)
|
||||
{
|
||||
LuaNetVarsMod.Logger.Error("LuaNetVarController is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
_controller.RegisterEventCallback(eventName, callback);
|
||||
}
|
||||
|
||||
/// <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 SendLuaEvent(string eventName, params DynValue[] args)
|
||||
{
|
||||
CheckIfCanAccessMethod(nameof(SendLuaEvent), false,
|
||||
CVRLuaEnvironmentContext.CLIENT, CVRLuaObjectContext.ALL_BUT_EVENTS, CVRLuaOwnerContext.ANY);
|
||||
|
||||
if (_controller == null)
|
||||
{
|
||||
LuaNetVarsMod.Logger.Error("LuaNetVarController is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
_controller.SendLuaEvent(eventName, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the current client is the owner of the synchronized object.
|
||||
/// </summary>
|
||||
public bool IsSyncOwner()
|
||||
{
|
||||
CheckIfCanAccessMethod(nameof(IsSyncOwner), false,
|
||||
CVRLuaEnvironmentContext.CLIENT, CVRLuaObjectContext.ALL_BUT_EVENTS, CVRLuaOwnerContext.ANY);
|
||||
|
||||
if (_controller == null)
|
||||
{
|
||||
LuaNetVarsMod.Logger.Error("LuaNetVarController is null.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return _controller.IsSyncOwner();
|
||||
}
|
||||
|
||||
public string GetSyncOwner()
|
||||
{
|
||||
CheckIfCanAccessMethod(nameof(GetSyncOwner), false,
|
||||
CVRLuaEnvironmentContext.CLIENT, CVRLuaObjectContext.ALL_BUT_EVENTS, CVRLuaOwnerContext.ANY);
|
||||
|
||||
if (_controller == null)
|
||||
{
|
||||
LuaNetVarsMod.Logger.Error("LuaNetVarController is null.");
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return _controller.GetSyncOwner();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
41
LuaNetworkVariables/NetworkVariables/LuaEventContext.cs
Normal file
41
LuaNetworkVariables/NetworkVariables/LuaEventContext.cs
Normal file
|
@ -0,0 +1,41 @@
|
|||
using MoonSharp.Interpreter;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Savior;
|
||||
|
||||
namespace NAK.LuaNetVars;
|
||||
|
||||
public struct LuaEventContext
|
||||
{
|
||||
private string SenderId { get; set; }
|
||||
public string SenderName { get; private set; }
|
||||
private DateTime LastInvokeTime { get; set; }
|
||||
private double TimeSinceLastInvoke { get; set; }
|
||||
private bool IsLocal { get; set; }
|
||||
|
||||
public static LuaEventContext Create(string senderId, DateTime lastInvokeTime)
|
||||
{
|
||||
var playerName = CVRPlayerManager.Instance.TryGetPlayerName(senderId);
|
||||
|
||||
return new LuaEventContext
|
||||
{
|
||||
SenderId = senderId,
|
||||
SenderName = playerName ?? "Unknown",
|
||||
LastInvokeTime = lastInvokeTime,
|
||||
TimeSinceLastInvoke = (DateTime.Now - lastInvokeTime).TotalSeconds,
|
||||
IsLocal = senderId == MetaPort.Instance.ownerId
|
||||
};
|
||||
}
|
||||
|
||||
public Table ToLuaTable(Script script)
|
||||
{
|
||||
Table table = new(script)
|
||||
{
|
||||
["senderId"] = SenderId,
|
||||
["senderName"] = SenderName,
|
||||
["lastInvokeTime"] = LastInvokeTime.ToUniversalTime().ToString("O"),
|
||||
["timeSinceLastInvoke"] = TimeSinceLastInvoke,
|
||||
["isLocal"] = IsLocal
|
||||
};
|
||||
return table;
|
||||
}
|
||||
}
|
60
LuaNetworkVariables/NetworkVariables/LuaEventTracker.cs
Normal file
60
LuaNetworkVariables/NetworkVariables/LuaEventTracker.cs
Normal file
|
@ -0,0 +1,60 @@
|
|||
namespace NAK.LuaNetVars;
|
||||
|
||||
internal class LuaEventTracker
|
||||
{
|
||||
private class EventMetadata
|
||||
{
|
||||
public DateTime LastInvokeTime { get; set; }
|
||||
public Dictionary<string, DateTime> LastInvokeTimePerSender { get; } = new();
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, EventMetadata> _eventMetadata = new();
|
||||
|
||||
public DateTime GetLastInvokeTime(string eventName)
|
||||
{
|
||||
if (!_eventMetadata.TryGetValue(eventName, out var metadata))
|
||||
{
|
||||
metadata = new EventMetadata();
|
||||
_eventMetadata[eventName] = metadata;
|
||||
}
|
||||
return metadata.LastInvokeTime;
|
||||
}
|
||||
|
||||
public DateTime GetLastInvokeTimeForSender(string eventName, string senderId)
|
||||
{
|
||||
if (!_eventMetadata.TryGetValue(eventName, out var metadata))
|
||||
{
|
||||
metadata = new EventMetadata();
|
||||
_eventMetadata[eventName] = metadata;
|
||||
}
|
||||
|
||||
if (!metadata.LastInvokeTimePerSender.TryGetValue(senderId, out DateTime time))
|
||||
{
|
||||
return DateTime.MinValue;
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
public void UpdateInvokeTime(string eventName, string senderId)
|
||||
{
|
||||
if (!_eventMetadata.TryGetValue(eventName, out EventMetadata metadata))
|
||||
{
|
||||
metadata = new EventMetadata();
|
||||
_eventMetadata[eventName] = metadata;
|
||||
}
|
||||
|
||||
DateTime now = DateTime.Now;
|
||||
metadata.LastInvokeTime = now;
|
||||
metadata.LastInvokeTimePerSender[senderId] = now;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_eventMetadata.Clear();
|
||||
}
|
||||
|
||||
public void ClearEvent(string eventName)
|
||||
{
|
||||
_eventMetadata.Remove(eventName);
|
||||
}
|
||||
}
|
146
LuaNetworkVariables/NetworkVariables/LuaNetVarController.Base.cs
Normal file
146
LuaNetworkVariables/NetworkVariables/LuaNetVarController.Base.cs
Normal file
|
@ -0,0 +1,146 @@
|
|||
using ABI_RC.Core.Savior;
|
||||
using ABI_RC.Core.Util;
|
||||
using ABI_RC.Systems.ModNetwork;
|
||||
using ABI.CCK.Components;
|
||||
using ABI.Scripting.CVRSTL.Common;
|
||||
using MoonSharp.Interpreter;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.LuaNetVars;
|
||||
|
||||
public partial class LuaNetVarController : MonoBehaviour
|
||||
{
|
||||
private static readonly HashSet<int> _hashes = new();
|
||||
|
||||
private const string MODULE_ID = "NAK.LNV:";
|
||||
|
||||
private int _uniquePathHash;
|
||||
private string ModNetworkID { get; set; }
|
||||
private CVRLuaClientBehaviour _luaClientBehaviour;
|
||||
|
||||
private readonly Dictionary<string, DynValue> _registeredNetworkVars = new();
|
||||
private readonly Dictionary<string, DynValue> _registeredNotifyCallbacks = new();
|
||||
private readonly Dictionary<string, DynValue> _registeredEventCallbacks = new();
|
||||
private readonly HashSet<string> _dirtyVariables = new();
|
||||
|
||||
private bool _requestInitialSync;
|
||||
private CVRSpawnable _spawnable;
|
||||
private CVRObjectSync _objectSync;
|
||||
|
||||
#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());
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
=> Cleanup();
|
||||
|
||||
#endregion Unity Events
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private bool Initialize()
|
||||
{
|
||||
if (!TryGetComponent(out _luaClientBehaviour)) return false;
|
||||
if (!TryGetUniqueNetworkID(out _uniquePathHash)) return false;
|
||||
|
||||
ModNetworkID = MODULE_ID + _uniquePathHash.ToString("X8");
|
||||
|
||||
if (ModNetworkID.Length > ModNetworkManager.MaxMessageIdLength)
|
||||
{
|
||||
LuaNetVarsMod.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}");
|
||||
|
||||
switch (_luaClientBehaviour.Context.objContext)
|
||||
{
|
||||
case CVRLuaObjectContext.AVATAR:
|
||||
_requestInitialSync = !_luaClientBehaviour.Context.IsWornByMe;
|
||||
break;
|
||||
case CVRLuaObjectContext.PROP:
|
||||
_spawnable = _luaClientBehaviour.Context.RootComponent as CVRSpawnable;
|
||||
_requestInitialSync = !_luaClientBehaviour.Context.IsSpawnedByMe;
|
||||
break;
|
||||
case CVRLuaObjectContext.WORLD:
|
||||
_objectSync = GetComponentInParent<CVRObjectSync>();
|
||||
_requestInitialSync = true; // idk probably works
|
||||
break;
|
||||
default:
|
||||
_requestInitialSync = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: evaluate if having dedicated globals is better behaviour (i think so)
|
||||
// private void ConfigureLuaEnvironment()
|
||||
// {
|
||||
// _luaClientBehaviour.script.Globals["SendLuaEvent"] = DynValue.NewCallback(SendLuaEventCallback);
|
||||
// }
|
||||
|
||||
private void Cleanup()
|
||||
{
|
||||
_eventTracker.Clear();
|
||||
|
||||
if (_uniquePathHash == 0 || string.IsNullOrEmpty(ModNetworkID))
|
||||
return;
|
||||
|
||||
ModNetworkManager.Unsubscribe(ModNetworkID);
|
||||
LuaNetVarsMod.Logger.Msg($"Unsubscribed LuaNetVarController with ModNetworkID: {ModNetworkID}");
|
||||
_hashes.Remove(_uniquePathHash);
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator SendVariableUpdatesCoroutine()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
if (IsSyncOwner()) SendVariableUpdates();
|
||||
if (!_requestInitialSync) continue;
|
||||
_requestInitialSync = false;
|
||||
RequestVariableSync();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
|
||||
#region Ownership Methods
|
||||
|
||||
public bool IsSyncOwner()
|
||||
{
|
||||
if (_objectSync) return _objectSync.SyncedByMe; // idk
|
||||
if (_spawnable)
|
||||
{
|
||||
if (_spawnable.IsSyncedByMe()) return true; // is held / attached locally
|
||||
CVRSyncHelper.PropData propData = CVRSyncHelper.Props.Find(x => x.InstanceId == _spawnable.instanceId);
|
||||
if (propData != null) return propData.syncedBy == MetaPort.Instance.ownerId; // last updated by me
|
||||
return false; // not held / attached locally and not last updated by me
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public string GetSyncOwner()
|
||||
{
|
||||
if (_objectSync) return _objectSync.syncedBy;
|
||||
if (_spawnable)
|
||||
{
|
||||
CVRSyncHelper.PropData propData = CVRSyncHelper.Props.Find(x => x.InstanceId == _spawnable.instanceId);
|
||||
return propData?.syncedBy ?? string.Empty;
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
#endregion Ownership Methods
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
using ABI_RC.Core.Savior;
|
||||
using ABI_RC.Systems.ModNetwork;
|
||||
using MoonSharp.Interpreter;
|
||||
using Unity.Services.Authentication.Internal;
|
||||
|
||||
namespace NAK.LuaNetVars
|
||||
{
|
||||
public partial class LuaNetVarController
|
||||
{
|
||||
private enum MessageType : byte
|
||||
{
|
||||
LuaVariable = 0,
|
||||
LuaEvent = 1,
|
||||
SyncVariables = 2,
|
||||
RequestSync = 3
|
||||
}
|
||||
|
||||
private readonly LuaEventTracker _eventTracker = new();
|
||||
|
||||
#region Mod Network Events
|
||||
|
||||
private void OnMessageReceived(ModNetworkMessage msg)
|
||||
{
|
||||
msg.Read(out byte msgTypeRaw);
|
||||
if (!Enum.IsDefined(typeof(MessageType), msgTypeRaw)) return;
|
||||
|
||||
MessageType msgType = (MessageType)msgTypeRaw;
|
||||
switch (msgType)
|
||||
{
|
||||
case MessageType.LuaVariable:
|
||||
HandleLuaVariableUpdate(msg);
|
||||
break;
|
||||
case MessageType.LuaEvent:
|
||||
HandleLuaEvent(msg);
|
||||
break;
|
||||
case MessageType.SyncVariables:
|
||||
HandleSyncVariables(msg);
|
||||
break;
|
||||
case MessageType.RequestSync:
|
||||
HandleRequestSyncVariables(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleLuaVariableUpdate(ModNetworkMessage msg)
|
||||
{
|
||||
msg.Read(out string varName);
|
||||
DynValue newValue = DeserializeDynValue(msg);
|
||||
|
||||
LuaNetVarsMod.Logger.Msg($"Received LuaVariable update: {varName} = {newValue}");
|
||||
|
||||
if (_registeredNetworkVars.TryGetValue(varName, out DynValue var))
|
||||
{
|
||||
UpdateNetworkVariable(varName, var, newValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
LuaNetVarsMod.Logger.Warning($"Received update for unregistered variable {varName}");
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleLuaEvent(ModNetworkMessage msg)
|
||||
{
|
||||
string senderId = msg.Sender;
|
||||
msg.Read(out string eventName);
|
||||
msg.Read(out int argsCount);
|
||||
|
||||
DateTime lastInvokeTime = _eventTracker.GetLastInvokeTimeForSender(eventName, senderId);
|
||||
LuaEventContext context = LuaEventContext.Create(senderId, lastInvokeTime);
|
||||
|
||||
// Update tracking
|
||||
_eventTracker.UpdateInvokeTime(eventName, senderId);
|
||||
|
||||
// Read event arguments
|
||||
var args = new DynValue[argsCount + 1]; // +1 for context
|
||||
args[0] = DynValue.FromObject(_luaClientBehaviour.script, context.ToLuaTable(_luaClientBehaviour.script));
|
||||
|
||||
for (int i = 0; i < argsCount; i++)
|
||||
{
|
||||
args[i + 1] = DeserializeDynValue(msg);
|
||||
}
|
||||
|
||||
LuaNetVarsMod.Logger.Msg($"Received LuaEvent: {eventName} from {context.SenderName} with {argsCount} args");
|
||||
|
||||
InvokeLuaEvent(eventName, args);
|
||||
}
|
||||
|
||||
private void HandleSyncVariables(ModNetworkMessage msg)
|
||||
{
|
||||
msg.Read(out int varCount);
|
||||
for (int i = 0; i < varCount; i++)
|
||||
{
|
||||
msg.Read(out string varName);
|
||||
DynValue newValue = DeserializeDynValue(msg);
|
||||
|
||||
if (_registeredNetworkVars.TryGetValue(varName, out DynValue var))
|
||||
{
|
||||
UpdateNetworkVariable(varName, var, newValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
LuaNetVarsMod.Logger.Warning($"Received sync for unregistered variable {varName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleRequestSyncVariables(ModNetworkMessage msg)
|
||||
{
|
||||
if (!IsSyncOwner()) return;
|
||||
SendVariableSyncToUser(msg.Sender);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Invocation
|
||||
|
||||
private void InvokeLuaEvent(string eventName, DynValue[] args)
|
||||
{
|
||||
if (_registeredEventCallbacks.TryGetValue(eventName, out DynValue callback))
|
||||
{
|
||||
_luaClientBehaviour.script.Call(callback, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
LuaNetVarsMod.Logger.Warning($"No registered callback for event {eventName}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Sending Methods
|
||||
|
||||
private void SendVariableUpdates()
|
||||
{
|
||||
if (_dirtyVariables.Count == 0) return;
|
||||
|
||||
using ModNetworkMessage modMsg = new(ModNetworkID); // can pass target userids as params if needed
|
||||
modMsg.Write((byte)MessageType.SyncVariables);
|
||||
modMsg.Write(_dirtyVariables.Count);
|
||||
modMsg.Send();
|
||||
|
||||
foreach (var varName in _dirtyVariables)
|
||||
{
|
||||
modMsg.Write(varName);
|
||||
SerializeDynValue(modMsg, _registeredNetworkVars[varName]);
|
||||
}
|
||||
|
||||
_dirtyVariables.Clear();
|
||||
}
|
||||
|
||||
private void SendVariableSyncToUser(string userId)
|
||||
{
|
||||
using ModNetworkMessage modMsg = new(ModNetworkID, userId);
|
||||
modMsg.Write((byte)MessageType.SyncVariables);
|
||||
modMsg.Write(_registeredNetworkVars.Count);
|
||||
foreach (var kvp in _registeredNetworkVars)
|
||||
{
|
||||
modMsg.Write(kvp.Key);
|
||||
SerializeDynValue(modMsg, kvp.Value);
|
||||
}
|
||||
modMsg.Send();
|
||||
|
||||
LuaNetVarsMod.Logger.Msg($"Sent variable sync to {userId}");
|
||||
}
|
||||
|
||||
private void RequestVariableSync()
|
||||
{
|
||||
using ModNetworkMessage modMsg = new(ModNetworkID);
|
||||
modMsg.Write((byte)MessageType.RequestSync);
|
||||
modMsg.Send();
|
||||
LuaNetVarsMod.Logger.Msg("Requested variable sync");
|
||||
}
|
||||
|
||||
// private DynValue SendLuaEventCallback(ScriptExecutionContext context, CallbackArguments args)
|
||||
// {
|
||||
// if (args.Count < 1) return DynValue.Nil;
|
||||
//
|
||||
// var eventName = args[0].CastToString();
|
||||
// var eventArgs = args.GetArray().Skip(1).ToArray();
|
||||
//
|
||||
// SendLuaEvent(eventName, eventArgs);
|
||||
//
|
||||
// return DynValue.Nil;
|
||||
// }
|
||||
|
||||
internal void SendLuaEvent(string eventName, DynValue[] args)
|
||||
{
|
||||
string senderId = MetaPort.Instance.ownerId;
|
||||
DateTime lastInvokeTime = _eventTracker.GetLastInvokeTimeForSender(eventName, senderId);
|
||||
LuaEventContext context = LuaEventContext.Create(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);
|
||||
modMsg.Write((byte)MessageType.LuaEvent);
|
||||
modMsg.Write(eventName);
|
||||
modMsg.Write(args.Length);
|
||||
|
||||
foreach (DynValue arg in args)
|
||||
SerializeDynValue(modMsg, arg);
|
||||
|
||||
modMsg.Send();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
using MoonSharp.Interpreter;
|
||||
|
||||
namespace NAK.LuaNetVars;
|
||||
|
||||
public partial class LuaNetVarController
|
||||
{
|
||||
internal void RegisterNetworkVar(string varName)
|
||||
{
|
||||
if (_registeredNetworkVars.ContainsKey(varName))
|
||||
{
|
||||
LuaNetVarsMod.Logger.Warning($"Network variable {varName} already registered!");
|
||||
return;
|
||||
}
|
||||
|
||||
_registeredNetworkVars[varName] = DynValue.Nil;
|
||||
_luaClientBehaviour.script.Globals[varName] = DynValue.Nil;
|
||||
|
||||
RegisterGetterFunction(varName);
|
||||
RegisterSetterFunction(varName);
|
||||
|
||||
LuaNetVarsMod.Logger.Msg($"Registered network variable {varName}");
|
||||
}
|
||||
|
||||
private void RegisterGetterFunction(string varName)
|
||||
{
|
||||
_luaClientBehaviour.script.Globals["Get" + varName] = DynValue.NewCallback((context, args) =>
|
||||
{
|
||||
return _registeredNetworkVars.TryGetValue(varName, out var value) ? value : DynValue.Nil;
|
||||
});
|
||||
}
|
||||
|
||||
private void RegisterSetterFunction(string varName)
|
||||
{
|
||||
_luaClientBehaviour.script.Globals["Set" + varName] = DynValue.NewCallback((context, args) =>
|
||||
{
|
||||
if (args.Count < 1) return DynValue.Nil;
|
||||
|
||||
var newValue = args[0];
|
||||
if (!IsSupportedDynValue(newValue))
|
||||
{
|
||||
LuaNetVarsMod.Logger.Error($"Unsupported DynValue type: {newValue.Type} for variable {varName}");
|
||||
return DynValue.Nil;
|
||||
}
|
||||
|
||||
if (_registeredNetworkVars.TryGetValue(varName, out var oldValue))
|
||||
{
|
||||
UpdateNetworkVariable(varName, oldValue, newValue);
|
||||
}
|
||||
|
||||
return DynValue.Nil;
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateNetworkVariable(string varName, DynValue oldValue, DynValue newValue)
|
||||
{
|
||||
_registeredNetworkVars[varName] = newValue;
|
||||
_luaClientBehaviour.script.Globals[varName] = newValue;
|
||||
_dirtyVariables.Add(varName);
|
||||
|
||||
if (_registeredNotifyCallbacks.TryGetValue(varName, out var callback))
|
||||
{
|
||||
_luaClientBehaviour.script.Call(callback, DynValue.NewString(varName), oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
internal void RegisterNotifyCallback(string varName, DynValue callback)
|
||||
{
|
||||
if (!ValidateCallback(callback) || !ValidateNetworkVar(varName)) return;
|
||||
|
||||
if (_registeredNotifyCallbacks.ContainsKey(varName))
|
||||
LuaNetVarsMod.Logger.Warning($"Overwriting notify callback for {varName}");
|
||||
|
||||
_registeredNotifyCallbacks[varName] = callback;
|
||||
LuaNetVarsMod.Logger.Msg($"Registered notify callback for {varName}");
|
||||
}
|
||||
|
||||
internal void RegisterEventCallback(string eventName, DynValue callback)
|
||||
{
|
||||
if (!ValidateCallback(callback)) return;
|
||||
|
||||
if (_registeredEventCallbacks.ContainsKey(eventName))
|
||||
LuaNetVarsMod.Logger.Warning($"Overwriting event callback for {eventName}");
|
||||
|
||||
_registeredEventCallbacks[eventName] = callback;
|
||||
LuaNetVarsMod.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");
|
||||
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}.");
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
using ABI_RC.Systems.ModNetwork;
|
||||
using MoonSharp.Interpreter;
|
||||
|
||||
namespace NAK.LuaNetVars;
|
||||
|
||||
public partial class LuaNetVarController
|
||||
{
|
||||
private static DynValue DeserializeDynValue(ModNetworkMessage msg)
|
||||
{
|
||||
msg.Read(out byte dataTypeByte);
|
||||
DataType dataType = (DataType)dataTypeByte;
|
||||
|
||||
switch (dataType)
|
||||
{
|
||||
case DataType.Boolean:
|
||||
msg.Read(out bool boolValue);
|
||||
return DynValue.NewBoolean(boolValue);
|
||||
case DataType.Number:
|
||||
msg.Read(out double numberValue);
|
||||
return DynValue.NewNumber(numberValue);
|
||||
case DataType.String:
|
||||
msg.Read(out string stringValue);
|
||||
return DynValue.NewString(stringValue);
|
||||
case DataType.Nil:
|
||||
return DynValue.Nil;
|
||||
default:
|
||||
LuaNetVarsMod.Logger.Error($"Unsupported data type received: {dataType}");
|
||||
return DynValue.Nil;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SerializeDynValue(ModNetworkMessage msg, DynValue value)
|
||||
{
|
||||
switch (value.Type)
|
||||
{
|
||||
case DataType.Boolean:
|
||||
msg.Write((byte)DataType.Boolean);
|
||||
msg.Write(value.Boolean);
|
||||
break;
|
||||
case DataType.Number:
|
||||
msg.Write((byte)DataType.Number);
|
||||
msg.Write(value.Number);
|
||||
break;
|
||||
case DataType.String:
|
||||
msg.Write((byte)DataType.String);
|
||||
msg.Write(value.String);
|
||||
break;
|
||||
case DataType.Nil:
|
||||
msg.Write((byte)DataType.Nil);
|
||||
break;
|
||||
default:
|
||||
LuaNetVarsMod.Logger.Error($"Unsupported DynValue type: {value.Type}");
|
||||
msg.Write((byte)DataType.Nil);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsSupportedDynValue(DynValue value)
|
||||
{
|
||||
return value.Type is DataType.Boolean or DataType.Number or DataType.String or DataType.Nil;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
using ABI_RC.Core.Savior;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.LuaNetVars;
|
||||
|
||||
public partial class LuaNetVarController
|
||||
{
|
||||
private bool TryGetUniqueNetworkID(out int hash)
|
||||
{
|
||||
string path = GetGameObjectPath(transform);
|
||||
hash = path.GetHashCode();
|
||||
|
||||
// Check if it already exists (this **should** only matter in worlds)
|
||||
if (_hashes.Contains(hash))
|
||||
{
|
||||
LuaNetVarsMod.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}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_hashes.Add(hash);
|
||||
|
||||
return true;
|
||||
|
||||
static bool FindAvailableHash(ref int hash)
|
||||
{
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
hash += 1;
|
||||
if (!_hashes.Contains(hash)) return true;
|
||||
}
|
||||
return false; // Failed to find a hash in 16 tries
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetGameObjectPath(Transform transform)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
path = transform.name + "/" + path;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
65
LuaNetworkVariables/Patches.cs
Normal file
65
LuaNetworkVariables/Patches.cs
Normal file
|
@ -0,0 +1,65 @@
|
|||
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.Common;
|
||||
using HarmonyLib;
|
||||
using MoonSharp.Interpreter;
|
||||
using NAK.LuaNetVars.Modules;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.LuaNetVars.Patches;
|
||||
|
||||
internal static class LuaScriptFactory_Patches
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(LuaScriptFactory.CVRRequireModule), nameof(LuaScriptFactory.CVRRequireModule.Require))]
|
||||
private static void Postfix_CVRRequireModule_require(
|
||||
string moduleFriendlyName,
|
||||
ref LuaScriptFactory.CVRRequireModule __instance,
|
||||
ref object __result,
|
||||
ref Script ____script,
|
||||
ref CVRLuaContext ____context)
|
||||
{
|
||||
if (LuaNetModule.MODULE_ID != moduleFriendlyName)
|
||||
return; // not our module
|
||||
|
||||
__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;
|
||||
}
|
||||
}
|
32
LuaNetworkVariables/Properties/AssemblyInfo.cs
Normal file
32
LuaNetworkVariables/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
using NAK.LuaNetVars.Properties;
|
||||
using MelonLoader;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyTitle(nameof(NAK.LuaNetVars))]
|
||||
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||
[assembly: AssemblyProduct(nameof(NAK.LuaNetVars))]
|
||||
|
||||
[assembly: MelonInfo(
|
||||
typeof(NAK.LuaNetVars.LuaNetVarsMod),
|
||||
nameof(NAK.LuaNetVars),
|
||||
AssemblyInfoParams.Version,
|
||||
AssemblyInfoParams.Author,
|
||||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/WhereAmIPointing"
|
||||
)]
|
||||
|
||||
[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.LuaNetVars.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.0";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
14
LuaNetworkVariables/README.md
Normal file
14
LuaNetworkVariables/README.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
# WhereAmIPointing
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
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.
|
326
LuaNetworkVariables/SyncedBehaviour/MNSyncedBehaviour.cs
Normal file
326
LuaNetworkVariables/SyncedBehaviour/MNSyncedBehaviour.cs
Normal file
|
@ -0,0 +1,326 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
161
LuaNetworkVariables/SyncedBehaviour/PickupableBehaviour.cs
Normal file
161
LuaNetworkVariables/SyncedBehaviour/PickupableBehaviour.cs
Normal file
|
@ -0,0 +1,161 @@
|
|||
using UnityEngine;
|
||||
using ABI_RC.Core.InteractionSystem;
|
||||
using ABI_RC.Systems.ModNetwork;
|
||||
|
||||
namespace NAK.LuaNetVars;
|
||||
|
||||
public class PickupableBehaviour : MNSyncedBehaviour
|
||||
{
|
||||
private enum PickupMessageType : byte
|
||||
{
|
||||
GrabState,
|
||||
Transform
|
||||
}
|
||||
|
||||
private bool isHeld;
|
||||
private string holderId;
|
||||
private Vector3 lastPosition;
|
||||
private Quaternion lastRotation;
|
||||
|
||||
public PickupableObject Pickupable { get; private set; }
|
||||
|
||||
public PickupableBehaviour(string networkId, PickupableObject pickupable) : base(networkId, autoAcceptTransfers: false)
|
||||
{
|
||||
Pickupable = pickupable;
|
||||
isHeld = false;
|
||||
holderId = string.Empty;
|
||||
lastPosition = pickupable.transform.position;
|
||||
lastRotation = pickupable.transform.rotation;
|
||||
}
|
||||
|
||||
public void OnGrabbed(InteractionContext context)
|
||||
{
|
||||
RequestOwnership(success => {
|
||||
if (success)
|
||||
{
|
||||
isHeld = true;
|
||||
holderId = LocalUserId;
|
||||
SendNetworkedData(WriteGrabState);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ownership request failed, drop the object
|
||||
Pickupable.ControllerRay = null; // Force drop
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void OnDropped()
|
||||
{
|
||||
if (!HasOwnership) return;
|
||||
|
||||
isHeld = false;
|
||||
holderId = string.Empty;
|
||||
SendNetworkedData(WriteGrabState);
|
||||
}
|
||||
|
||||
public void UpdateTransform(Vector3 position, Quaternion rotation)
|
||||
{
|
||||
if (!HasOwnership || !isHeld) return;
|
||||
|
||||
lastPosition = position;
|
||||
lastRotation = rotation;
|
||||
SendNetworkedData(WriteTransform);
|
||||
}
|
||||
|
||||
protected override OwnershipResponse OnOwnershipRequested(string requesterId)
|
||||
{
|
||||
// If the object is held by the current owner, reject the transfer
|
||||
if (isHeld && holderId == LocalUserId)
|
||||
return OwnershipResponse.Rejected;
|
||||
|
||||
// If theft is disallowed and the object is held by someone, reject the transfer
|
||||
if (Pickupable.DisallowTheft && !string.IsNullOrEmpty(holderId))
|
||||
return OwnershipResponse.Rejected;
|
||||
|
||||
return OwnershipResponse.Accepted;
|
||||
}
|
||||
|
||||
protected override void WriteState(ModNetworkMessage message)
|
||||
{
|
||||
message.Write(isHeld);
|
||||
message.Write(holderId);
|
||||
message.Write(lastPosition);
|
||||
message.Write(lastRotation);
|
||||
}
|
||||
|
||||
protected override void ReadState(ModNetworkMessage message)
|
||||
{
|
||||
message.Read(out isHeld);
|
||||
message.Read(out holderId);
|
||||
message.Read(out lastPosition);
|
||||
message.Read(out lastRotation);
|
||||
|
||||
UpdatePickupableState();
|
||||
}
|
||||
|
||||
private void WriteGrabState(ModNetworkMessage message)
|
||||
{
|
||||
message.Write((byte)PickupMessageType.GrabState);
|
||||
message.Write(isHeld);
|
||||
message.Write(holderId);
|
||||
}
|
||||
|
||||
private void WriteTransform(ModNetworkMessage message)
|
||||
{
|
||||
message.Write((byte)PickupMessageType.Transform);
|
||||
message.Write(lastPosition);
|
||||
message.Write(lastRotation);
|
||||
}
|
||||
|
||||
protected override void ReadCustomData(ModNetworkMessage message)
|
||||
{
|
||||
message.Read(out byte messageType);
|
||||
|
||||
switch ((PickupMessageType)messageType)
|
||||
{
|
||||
case PickupMessageType.GrabState:
|
||||
message.Read(out isHeld);
|
||||
message.Read(out holderId);
|
||||
break;
|
||||
|
||||
case PickupMessageType.Transform:
|
||||
message.Read(out Vector3 position);
|
||||
message.Read(out Quaternion rotation);
|
||||
lastPosition = position;
|
||||
lastRotation = rotation;
|
||||
break;
|
||||
}
|
||||
|
||||
UpdatePickupableState();
|
||||
}
|
||||
|
||||
private void UpdatePickupableState()
|
||||
{
|
||||
// Update transform if we're not the holder
|
||||
if (!isHeld || holderId != LocalUserId)
|
||||
{
|
||||
Pickupable.transform.position = lastPosition;
|
||||
Pickupable.transform.rotation = lastRotation;
|
||||
}
|
||||
|
||||
// Force drop if we were holding but someone else took ownership
|
||||
if (isHeld && holderId != LocalUserId)
|
||||
{
|
||||
Pickupable.ControllerRay = null; // Force drop
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnOwnershipChanged(string newOwnerId)
|
||||
{
|
||||
base.OnOwnershipChanged(newOwnerId);
|
||||
|
||||
// If we lost ownership and were holding, force drop
|
||||
if (!HasOwnership && holderId == LocalUserId)
|
||||
{
|
||||
isHeld = false;
|
||||
holderId = string.Empty;
|
||||
Pickupable.ControllerRay = null; // Force drop
|
||||
}
|
||||
}
|
||||
}
|
72
LuaNetworkVariables/SyncedBehaviour/PickupableObject.cs
Normal file
72
LuaNetworkVariables/SyncedBehaviour/PickupableObject.cs
Normal file
|
@ -0,0 +1,72 @@
|
|||
using ABI_RC.Core.InteractionSystem;
|
||||
using ABI_RC.Core.InteractionSystem.Base;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.LuaNetVars;
|
||||
|
||||
public class PickupableObject : Pickupable
|
||||
{
|
||||
[SerializeField] private bool canPickup = true;
|
||||
[SerializeField] private bool disallowTheft = false;
|
||||
[SerializeField] private float maxGrabDistance = 2f;
|
||||
[SerializeField] private float maxPushDistance = 2f;
|
||||
[SerializeField] private bool isAutoHold = false;
|
||||
[SerializeField] private bool allowRotation = true;
|
||||
[SerializeField] private bool allowPushPull = true;
|
||||
[SerializeField] private bool allowInteraction = true;
|
||||
|
||||
private PickupableBehaviour behaviour;
|
||||
private bool isInitialized;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// Generate a unique network ID based on the instance ID
|
||||
string networkId = $"pickup_{gameObject.name}";
|
||||
behaviour = new PickupableBehaviour(networkId, this);
|
||||
isInitialized = true;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
behaviour?.Dispose();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (behaviour?.HasOwnership == true)
|
||||
{
|
||||
transform.SetPositionAndRotation(ControllerRay.pivotPoint.position, ControllerRay.pivotPoint.rotation);
|
||||
behaviour.UpdateTransform(transform.position, transform.rotation);
|
||||
}
|
||||
}
|
||||
|
||||
#region Pickupable Implementation
|
||||
|
||||
public override void OnGrab(InteractionContext context, Vector3 grabPoint)
|
||||
{
|
||||
if (!isInitialized) return;
|
||||
behaviour.OnGrabbed(context);
|
||||
}
|
||||
|
||||
public override void OnDrop(InteractionContext context)
|
||||
{
|
||||
if (!isInitialized) return;
|
||||
behaviour.OnDropped();
|
||||
}
|
||||
|
||||
public override void OnFlingTowardsTarget(Vector3 target)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
public override bool CanPickup => canPickup;
|
||||
public override bool DisallowTheft => disallowTheft;
|
||||
public override float MaxGrabDistance => maxGrabDistance;
|
||||
public override float MaxPushDistance => maxPushDistance;
|
||||
public override bool IsAutoHold => isAutoHold;
|
||||
public override bool IsObjectRotationAllowed => allowRotation;
|
||||
public override bool IsObjectPushPullAllowed => allowPushPull;
|
||||
public override bool IsObjectInteractionAllowed => allowInteraction;
|
||||
|
||||
#endregion Pickupable Implementation
|
||||
}
|
63
LuaNetworkVariables/SyncedBehaviour/TestSyncedBehaviour.cs
Normal file
63
LuaNetworkVariables/SyncedBehaviour/TestSyncedBehaviour.cs
Normal file
|
@ -0,0 +1,63 @@
|
|||
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;
|
||||
}
|
||||
}
|
42
LuaNetworkVariables/SyncedBehaviour/TestSyncedObject.cs
Normal file
42
LuaNetworkVariables/SyncedBehaviour/TestSyncedObject.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
23
LuaNetworkVariables/format.json
Normal file
23
LuaNetworkVariables/format.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"_id": 234,
|
||||
"name": "WhereAmIPointing",
|
||||
"modversion": "1.0.1",
|
||||
"gameversion": "2024r175",
|
||||
"loaderversion": "0.6.1",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "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.",
|
||||
"searchtags": [
|
||||
"controller",
|
||||
"ray",
|
||||
"line",
|
||||
"tomato"
|
||||
],
|
||||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r40/WhereAmIPointing.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/WhereAmIPointing/",
|
||||
"changelog": "- Fixed line renderer alpha not being reset when the menu is closed.",
|
||||
"embedcolor": "#f61963"
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue