[LuaNetworkVariables] Remove exp stuff, fix context properties

This commit is contained in:
NotAKidoS 2025-04-14 18:11:23 -05:00
parent ece15e0dfc
commit e85c1e2f25
10 changed files with 95 additions and 514 deletions

View file

@ -18,24 +18,22 @@ public class LuaNetVarsMod : MelonMod
public override void OnInitializeMelon() public override void OnInitializeMelon()
{ {
Logger = LoggerInstance; Logger = LoggerInstance;
ApplyPatches(typeof(Patches.LuaScriptFactory_Patches)); ApplyPatches(typeof(Patches.LuaScriptFactory_Patches));
ApplyPatches(typeof(Patches.CVRSyncHelper_Patches));
} }
public override void OnUpdate() // public override void OnUpdate()
{ // {
// if (Input.GetKeyDown(KeyCode.F1)) // // if (Input.GetKeyDown(KeyCode.F1))
// { // // {
// PlayerSetup.Instance.DropProp("be0b5acc-a987-48dc-a28b-62bd912fe3a0"); // // PlayerSetup.Instance.DropProp("be0b5acc-a987-48dc-a28b-62bd912fe3a0");
// } // // }
// // //
// if (Input.GetKeyDown(KeyCode.F2)) // // if (Input.GetKeyDown(KeyCode.F2))
// { // // {
// GameObject go = new("TestSyncedObject"); // // GameObject go = new("TestSyncedObject");
// go.AddComponent<TestSyncedObject>(); // // go.AddComponent<TestSyncedObject>();
// } // // }
} // }
#endregion Melon Events #endregion Melon Events

View file

@ -1,12 +1,11 @@
using ABI_RC.Core.Base; using ABI_RC.Core.Base;
using ABI.Scripting.CVRSTL.Common; using ABI.Scripting.CVRSTL.Common;
using JetBrains.Annotations; using JetBrains.Annotations;
using NAK.LuaNetVars;
using MoonSharp.Interpreter; using MoonSharp.Interpreter;
namespace NAK.LuaNetVars.Modules; namespace NAK.LuaNetVars.Modules;
[PublicAPI] // Indicates that this class is used and should not be considered unused [PublicAPI]
public class LuaNetModule : BaseScriptedStaticWrapper public class LuaNetModule : BaseScriptedStaticWrapper
{ {
public const string MODULE_ID = "NetworkModule"; public const string MODULE_ID = "NetworkModule";
@ -101,6 +100,25 @@ public class LuaNetModule : BaseScriptedStaticWrapper
_controller.SendLuaEvent(eventName, args); _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)
{
LuaNetVarsMod.Logger.Error("LuaNetVarController is null.");
return;
}
_controller.SendLuaEventToUser(eventName, userId, args);
}
/// <summary> /// <summary>
/// Checks if the current client is the owner of the synchronized object. /// Checks if the current client is the owner of the synchronized object.

View file

@ -1,6 +1,6 @@
using MoonSharp.Interpreter; using ABI_RC.Core.Networking;
using MoonSharp.Interpreter;
using ABI_RC.Core.Player; using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
namespace NAK.LuaNetVars; namespace NAK.LuaNetVars;
@ -12,9 +12,9 @@ public struct LuaEventContext
private double TimeSinceLastInvoke { get; set; } private double TimeSinceLastInvoke { get; set; }
private bool IsLocal { 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 return new LuaEventContext
{ {
@ -22,7 +22,7 @@ public struct LuaEventContext
SenderName = playerName ?? "Unknown", SenderName = playerName ?? "Unknown",
LastInvokeTime = lastInvokeTime, LastInvokeTime = lastInvokeTime,
TimeSinceLastInvoke = (DateTime.Now - lastInvokeTime).TotalSeconds, TimeSinceLastInvoke = (DateTime.Now - lastInvokeTime).TotalSeconds,
IsLocal = senderId == MetaPort.Instance.ownerId IsLocal = isLocal
}; };
} }
@ -30,11 +30,11 @@ public struct LuaEventContext
{ {
Table table = new(script) Table table = new(script)
{ {
["senderId"] = SenderId, ["SenderId"] = SenderId,
["senderName"] = SenderName, ["SenderName"] = SenderName,
["lastInvokeTime"] = LastInvokeTime.ToUniversalTime().ToString("O"), ["LastInvokeTime"] = LastInvokeTime.ToUniversalTime().ToString("O"),
["timeSinceLastInvoke"] = TimeSinceLastInvoke, ["TimeSinceLastInvoke"] = TimeSinceLastInvoke,
["isLocal"] = IsLocal ["IsLocal"] = IsLocal
}; };
return table; return table;
} }

View file

@ -5,6 +5,7 @@ using ABI.CCK.Components;
using ABI.Scripting.CVRSTL.Common; using ABI.Scripting.CVRSTL.Common;
using MoonSharp.Interpreter; using MoonSharp.Interpreter;
using UnityEngine; using UnityEngine;
using Coroutine = UnityEngine.Coroutine;
namespace NAK.LuaNetVars; namespace NAK.LuaNetVars;
@ -26,21 +27,23 @@ public partial class LuaNetVarController : MonoBehaviour
private bool _requestInitialSync; private bool _requestInitialSync;
private CVRSpawnable _spawnable; private CVRSpawnable _spawnable;
private CVRObjectSync _objectSync; private CVRObjectSync _objectSync;
private bool _isInitialized;
private Coroutine _syncCoroutine;
#region Unity Events #region Unity Events
private void Awake() private void Awake()
{ => _isInitialized = Initialize();
if (!Initialize())
return;
// TODO: a manager script should be in charge of this
// TODO: disabling object will kill coroutine
StartCoroutine(SendVariableUpdatesCoroutine());
}
private void OnDestroy() private void OnDestroy()
=> Cleanup(); => Cleanup();
private void OnEnable()
=> StartStopVariableUpdatesCoroutine(true);
private void OnDisable()
=> StartStopVariableUpdatesCoroutine(false);
#endregion Unity Events #endregion Unity Events
@ -102,9 +105,16 @@ public partial class LuaNetVarController : MonoBehaviour
_hashes.Remove(_uniquePathHash); _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() private System.Collections.IEnumerator SendVariableUpdatesCoroutine()
{ {
while (true) while (isActiveAndEnabled)
{ {
yield return new WaitForSeconds(0.1f); yield return new WaitForSeconds(0.1f);
if (IsSyncOwner()) SendVariableUpdates(); if (IsSyncOwner()) SendVariableUpdates();

View file

@ -1,7 +1,6 @@
using ABI_RC.Core.Savior; using ABI_RC.Core.Savior;
using ABI_RC.Systems.ModNetwork; using ABI_RC.Systems.ModNetwork;
using MoonSharp.Interpreter; using MoonSharp.Interpreter;
using Unity.Services.Authentication.Internal;
namespace NAK.LuaNetVars namespace NAK.LuaNetVars
{ {
@ -66,7 +65,7 @@ namespace NAK.LuaNetVars
msg.Read(out int argsCount); msg.Read(out int argsCount);
DateTime lastInvokeTime = _eventTracker.GetLastInvokeTimeForSender(eventName, senderId); DateTime lastInvokeTime = _eventTracker.GetLastInvokeTimeForSender(eventName, senderId);
LuaEventContext context = LuaEventContext.Create(senderId, lastInvokeTime); LuaEventContext context = LuaEventContext.Create(false, senderId, lastInvokeTime);
// Update tracking // Update tracking
_eventTracker.UpdateInvokeTime(eventName, senderId); _eventTracker.UpdateInvokeTime(eventName, senderId);
@ -187,7 +186,7 @@ namespace NAK.LuaNetVars
{ {
string senderId = MetaPort.Instance.ownerId; string senderId = MetaPort.Instance.ownerId;
DateTime lastInvokeTime = _eventTracker.GetLastInvokeTimeForSender(eventName, senderId); DateTime lastInvokeTime = _eventTracker.GetLastInvokeTimeForSender(eventName, senderId);
LuaEventContext context = LuaEventContext.Create(senderId, lastInvokeTime); LuaEventContext context = LuaEventContext.Create(true, senderId, lastInvokeTime);
// Update tracking // Update tracking
_eventTracker.UpdateInvokeTime(eventName, senderId); _eventTracker.UpdateInvokeTime(eventName, senderId);
@ -209,6 +208,32 @@ namespace NAK.LuaNetVars
modMsg.Send(); 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 #endregion
} }
} }

View file

@ -1,13 +1,8 @@
using ABI_RC.Core.Base; using ABI.Scripting.CVRSTL.Client;
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 ABI.Scripting.CVRSTL.Common;
using HarmonyLib; using HarmonyLib;
using MoonSharp.Interpreter; using MoonSharp.Interpreter;
using NAK.LuaNetVars.Modules; using NAK.LuaNetVars.Modules;
using UnityEngine;
namespace NAK.LuaNetVars.Patches; namespace NAK.LuaNetVars.Patches;
@ -28,38 +23,4 @@ internal static class LuaScriptFactory_Patches
__result = LuaNetModule.RegisterUserData(____script, ____context); __result = LuaNetModule.RegisterUserData(____script, ____context);
__instance.RegisteredModules[LuaNetModule.MODULE_ID] = __result; // add module to cache __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;
}
} }

View file

@ -14,7 +14,7 @@ using System.Reflection;
nameof(NAK.LuaNetVars), nameof(NAK.LuaNetVars),
AssemblyInfoParams.Version, AssemblyInfoParams.Version,
AssemblyInfoParams.Author, 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")] [assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
@ -27,6 +27,6 @@ using System.Reflection;
namespace NAK.LuaNetVars.Properties; namespace NAK.LuaNetVars.Properties;
internal static class AssemblyInfoParams internal static class AssemblyInfoParams
{ {
public const string Version = "1.0.0"; public const string Version = "1.0.1";
public const string Author = "NotAKidoS"; public const string Author = "NotAKidoS";
} }

View file

@ -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);
}
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}
}
}