LuaNetworkVariables: idk

This commit is contained in:
NotAKidoS 2024-12-02 20:05:34 -06:00
parent db07d53971
commit fe768029eb
19 changed files with 1680 additions and 0 deletions

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

View 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
}
}
}

View 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
}

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

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