solution-wide: file-scoped namespaces

This commit is contained in:
JohnCorby 2022-03-02 19:46:33 -08:00
parent b99dfaa510
commit e4da50a656
374 changed files with 20610 additions and 20984 deletions

View File

@ -50,82 +50,81 @@ using System.Collections.Generic;
/// MIT License
/// </summary>
namespace EpicTransport
namespace EpicTransport;
public class BidirectionalDictionary<T1, T2> : IEnumerable
{
public class BidirectionalDictionary<T1, T2> : IEnumerable
private Dictionary<T1, T2> t1ToT2Dict = new();
private Dictionary<T2, T1> t2ToT1Dict = new();
public IEnumerable<T1> FirstTypes => t1ToT2Dict.Keys;
public IEnumerable<T2> SecondTypes => t2ToT1Dict.Keys;
public IEnumerator GetEnumerator() => t1ToT2Dict.GetEnumerator();
public int Count => t1ToT2Dict.Count;
public void Add(T1 key, T2 value)
{
private Dictionary<T1, T2> t1ToT2Dict = new();
private Dictionary<T2, T1> t2ToT1Dict = new();
t1ToT2Dict[key] = value;
t2ToT1Dict[value] = key;
}
public IEnumerable<T1> FirstTypes => t1ToT2Dict.Keys;
public IEnumerable<T2> SecondTypes => t2ToT1Dict.Keys;
public void Add(T2 key, T1 value)
{
t2ToT1Dict[key] = value;
t1ToT2Dict[value] = key;
}
public IEnumerator GetEnumerator() => t1ToT2Dict.GetEnumerator();
public T2 Get(T1 key) => t1ToT2Dict[key];
public int Count => t1ToT2Dict.Count;
public T1 Get(T2 key) => t2ToT1Dict[key];
public void Add(T1 key, T2 value)
public bool TryGetValue(T1 key, out T2 value) => t1ToT2Dict.TryGetValue(key, out value);
public bool TryGetValue(T2 key, out T1 value) => t2ToT1Dict.TryGetValue(key, out value);
public bool Contains(T1 key) => t1ToT2Dict.ContainsKey(key);
public bool Contains(T2 key) => t2ToT1Dict.ContainsKey(key);
public void Remove(T1 key)
{
if (Contains(key))
{
t1ToT2Dict[key] = value;
t2ToT1Dict[value] = key;
var val = t1ToT2Dict[key];
t1ToT2Dict.Remove(key);
t2ToT1Dict.Remove(val);
}
}
public void Add(T2 key, T1 value)
public void Remove(T2 key)
{
if (Contains(key))
{
var val = t2ToT1Dict[key];
t1ToT2Dict.Remove(val);
t2ToT1Dict.Remove(key);
}
}
public T1 this[T2 key]
{
get => t2ToT1Dict[key];
set
{
t2ToT1Dict[key] = value;
t1ToT2Dict[value] = key;
}
}
public T2 Get(T1 key) => t1ToT2Dict[key];
public T1 Get(T2 key) => t2ToT1Dict[key];
public bool TryGetValue(T1 key, out T2 value) => t1ToT2Dict.TryGetValue(key, out value);
public bool TryGetValue(T2 key, out T1 value) => t2ToT1Dict.TryGetValue(key, out value);
public bool Contains(T1 key) => t1ToT2Dict.ContainsKey(key);
public bool Contains(T2 key) => t2ToT1Dict.ContainsKey(key);
public void Remove(T1 key)
public T2 this[T1 key]
{
get => t1ToT2Dict[key];
set
{
if (Contains(key))
{
var val = t1ToT2Dict[key];
t1ToT2Dict.Remove(key);
t2ToT1Dict.Remove(val);
}
}
public void Remove(T2 key)
{
if (Contains(key))
{
var val = t2ToT1Dict[key];
t1ToT2Dict.Remove(val);
t2ToT1Dict.Remove(key);
}
}
public T1 this[T2 key]
{
get => t2ToT1Dict[key];
set
{
t2ToT1Dict[key] = value;
t1ToT2Dict[value] = key;
}
}
public T2 this[T1 key]
{
get => t1ToT2Dict[key];
set
{
t1ToT2Dict[key] = value;
t2ToT1Dict[value] = key;
}
t1ToT2Dict[key] = value;
t2ToT1Dict[value] = key;
}
}
}

View File

@ -5,193 +5,192 @@ using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
namespace EpicTransport
namespace EpicTransport;
public class Client : Common
{
public class Client : Common
public SocketId socketId;
public ProductUserId serverId;
public bool Connected { get; private set; }
public bool Error { get; private set; }
private event Action<byte[], int> OnReceivedData;
private event Action OnConnected;
public event Action OnDisconnected;
private Action<string> SetTransportError;
private TimeSpan ConnectionTimeout;
public bool isConnecting = false;
public string hostAddress = "";
private ProductUserId hostProductId = null;
private TaskCompletionSource<Task> connectedComplete;
private CancellationTokenSource cancelToken;
private Client(EosTransport transport) : base(transport) => ConnectionTimeout = TimeSpan.FromSeconds(Math.Max(1, transport.timeout));
public static Client CreateClient(EosTransport transport, string host)
{
public SocketId socketId;
public ProductUserId serverId;
var c = new Client(transport);
public bool Connected { get; private set; }
public bool Error { get; private set; }
c.hostAddress = host;
c.socketId = new SocketId() { SocketName = RandomString.Generate(20) };
private event Action<byte[], int> OnReceivedData;
private event Action OnConnected;
public event Action OnDisconnected;
private Action<string> SetTransportError;
c.OnConnected += () => transport.OnClientConnected.Invoke();
c.OnDisconnected += () => transport.OnClientDisconnected.Invoke();
c.OnReceivedData += (data, channel) => transport.OnClientDataReceived.Invoke(new ArraySegment<byte>(data), channel);
c.SetTransportError = transport.SetTransportError;
private TimeSpan ConnectionTimeout;
public bool isConnecting = false;
public string hostAddress = "";
private ProductUserId hostProductId = null;
private TaskCompletionSource<Task> connectedComplete;
private CancellationTokenSource cancelToken;
private Client(EosTransport transport) : base(transport) => ConnectionTimeout = TimeSpan.FromSeconds(Math.Max(1, transport.timeout));
public static Client CreateClient(EosTransport transport, string host)
{
var c = new Client(transport);
c.hostAddress = host;
c.socketId = new SocketId() { SocketName = RandomString.Generate(20) };
c.OnConnected += () => transport.OnClientConnected.Invoke();
c.OnDisconnected += () => transport.OnClientDisconnected.Invoke();
c.OnReceivedData += (data, channel) => transport.OnClientDataReceived.Invoke(new ArraySegment<byte>(data), channel);
c.SetTransportError = transport.SetTransportError;
return c;
}
public async void Connect(string host)
{
cancelToken = new CancellationTokenSource();
try
{
hostProductId = ProductUserId.FromString(host);
serverId = hostProductId;
connectedComplete = new TaskCompletionSource<Task>();
OnConnected += SetConnectedComplete;
SendInternal(hostProductId, socketId, InternalMessages.CONNECT);
Task connectedCompleteTask = connectedComplete.Task;
if (await Task.WhenAny(connectedCompleteTask, Task.Delay(ConnectionTimeout /*, cancelToken.Token*/)) != connectedCompleteTask)
{
SetTransportError($"Connection to {host} timed out.");
Debug.LogError($"Connection to {host} timed out.");
OnConnected -= SetConnectedComplete;
OnConnectionFailed(hostProductId);
}
OnConnected -= SetConnectedComplete;
}
catch (FormatException)
{
SetTransportError("Connection string was not in the right format. Did you enter a ProductId?");
Debug.LogError($"Connection string was not in the right format. Did you enter a ProductId?");
Error = true;
OnConnectionFailed(hostProductId);
}
catch (Exception ex)
{
SetTransportError(ex.Message);
Debug.LogError(ex.Message);
Error = true;
OnConnectionFailed(hostProductId);
}
finally
{
if (Error)
{
OnConnectionFailed(null);
}
}
}
public void Disconnect()
{
if (serverId != null)
{
CloseP2PSessionWithUser(serverId, socketId);
serverId = null;
}
else
{
return;
}
SendInternal(hostProductId, socketId, InternalMessages.DISCONNECT);
Dispose();
cancelToken?.Cancel();
WaitForClose(hostProductId, socketId);
}
private void SetConnectedComplete() => connectedComplete.SetResult(connectedComplete.Task);
protected override void OnReceiveData(byte[] data, ProductUserId clientUserId, int channel)
{
if (ignoreAllMessages)
{
return;
}
if (clientUserId != hostProductId)
{
Debug.LogError("Received a message from an unknown");
return;
}
OnReceivedData.Invoke(data, channel);
}
protected override void OnNewConnection(OnIncomingConnectionRequestInfo result)
{
if (ignoreAllMessages)
{
return;
}
if (deadSockets.Contains(result.SocketId.SocketName))
{
Debug.LogError("Received incoming connection request from dead socket");
return;
}
if (hostProductId == result.RemoteUserId)
{
EOSSDKComponent.GetP2PInterface().AcceptConnection(
new AcceptConnectionOptions()
{
LocalUserId = EOSSDKComponent.LocalUserProductId,
RemoteUserId = result.RemoteUserId,
SocketId = result.SocketId
});
}
else
{
Debug.LogError("P2P Acceptance Request from unknown host ID.");
}
}
protected override void OnReceiveInternalData(InternalMessages type, ProductUserId clientUserId, SocketId socketId)
{
if (ignoreAllMessages)
{
return;
}
switch (type)
{
case InternalMessages.ACCEPT_CONNECT:
Connected = true;
OnConnected.Invoke();
Debug.Log("Connection established.");
break;
case InternalMessages.DISCONNECT:
SetTransportError("host disconnected");
Connected = false;
Debug.Log("Disconnected.");
OnDisconnected.Invoke();
break;
default:
Debug.Log("Received unknown message type");
break;
}
}
public void Send(byte[] data, int channelId) => Send(hostProductId, socketId, data, (byte)channelId);
protected override void OnConnectionFailed(ProductUserId remoteId) => OnDisconnected.Invoke();
public void EosNotInitialized() => OnDisconnected.Invoke();
return c;
}
public async void Connect(string host)
{
cancelToken = new CancellationTokenSource();
try
{
hostProductId = ProductUserId.FromString(host);
serverId = hostProductId;
connectedComplete = new TaskCompletionSource<Task>();
OnConnected += SetConnectedComplete;
SendInternal(hostProductId, socketId, InternalMessages.CONNECT);
Task connectedCompleteTask = connectedComplete.Task;
if (await Task.WhenAny(connectedCompleteTask, Task.Delay(ConnectionTimeout /*, cancelToken.Token*/)) != connectedCompleteTask)
{
SetTransportError($"Connection to {host} timed out.");
Debug.LogError($"Connection to {host} timed out.");
OnConnected -= SetConnectedComplete;
OnConnectionFailed(hostProductId);
}
OnConnected -= SetConnectedComplete;
}
catch (FormatException)
{
SetTransportError("Connection string was not in the right format. Did you enter a ProductId?");
Debug.LogError($"Connection string was not in the right format. Did you enter a ProductId?");
Error = true;
OnConnectionFailed(hostProductId);
}
catch (Exception ex)
{
SetTransportError(ex.Message);
Debug.LogError(ex.Message);
Error = true;
OnConnectionFailed(hostProductId);
}
finally
{
if (Error)
{
OnConnectionFailed(null);
}
}
}
public void Disconnect()
{
if (serverId != null)
{
CloseP2PSessionWithUser(serverId, socketId);
serverId = null;
}
else
{
return;
}
SendInternal(hostProductId, socketId, InternalMessages.DISCONNECT);
Dispose();
cancelToken?.Cancel();
WaitForClose(hostProductId, socketId);
}
private void SetConnectedComplete() => connectedComplete.SetResult(connectedComplete.Task);
protected override void OnReceiveData(byte[] data, ProductUserId clientUserId, int channel)
{
if (ignoreAllMessages)
{
return;
}
if (clientUserId != hostProductId)
{
Debug.LogError("Received a message from an unknown");
return;
}
OnReceivedData.Invoke(data, channel);
}
protected override void OnNewConnection(OnIncomingConnectionRequestInfo result)
{
if (ignoreAllMessages)
{
return;
}
if (deadSockets.Contains(result.SocketId.SocketName))
{
Debug.LogError("Received incoming connection request from dead socket");
return;
}
if (hostProductId == result.RemoteUserId)
{
EOSSDKComponent.GetP2PInterface().AcceptConnection(
new AcceptConnectionOptions()
{
LocalUserId = EOSSDKComponent.LocalUserProductId,
RemoteUserId = result.RemoteUserId,
SocketId = result.SocketId
});
}
else
{
Debug.LogError("P2P Acceptance Request from unknown host ID.");
}
}
protected override void OnReceiveInternalData(InternalMessages type, ProductUserId clientUserId, SocketId socketId)
{
if (ignoreAllMessages)
{
return;
}
switch (type)
{
case InternalMessages.ACCEPT_CONNECT:
Connected = true;
OnConnected.Invoke();
Debug.Log("Connection established.");
break;
case InternalMessages.DISCONNECT:
SetTransportError("host disconnected");
Connected = false;
Debug.Log("Disconnected.");
OnDisconnected.Invoke();
break;
default:
Debug.Log("Received unknown message type");
break;
}
}
public void Send(byte[] data, int channelId) => Send(hostProductId, socketId, data, (byte)channelId);
protected override void OnConnectionFailed(ProductUserId remoteId) => OnDisconnected.Invoke();
public void EosNotInitialized() => OnDisconnected.Invoke();
}

View File

@ -5,331 +5,330 @@ using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace EpicTransport
namespace EpicTransport;
public abstract class Common
{
public abstract class Common
private PacketReliability[] channels;
private int internal_ch => channels.Length;
protected enum InternalMessages : byte
{
private PacketReliability[] channels;
private int internal_ch => channels.Length;
CONNECT,
ACCEPT_CONNECT,
DISCONNECT
}
protected enum InternalMessages : byte
protected struct PacketKey
{
public ProductUserId productUserId;
public byte channel;
}
private OnIncomingConnectionRequestCallback OnIncomingConnectionRequest;
private ulong incomingNotificationId = 0;
private OnRemoteConnectionClosedCallback OnRemoteConnectionClosed;
private ulong outgoingNotificationId = 0;
protected readonly EosTransport transport;
protected List<string> deadSockets;
public bool ignoreAllMessages = false;
// Mapping from PacketKey to a List of Packet Lists
protected Dictionary<PacketKey, List<List<Packet>>> incomingPackets = new();
protected Common(EosTransport transport)
{
channels = transport.Channels;
deadSockets = new List<string>();
var addNotifyPeerConnectionRequestOptions = new AddNotifyPeerConnectionRequestOptions();
addNotifyPeerConnectionRequestOptions.LocalUserId = EOSSDKComponent.LocalUserProductId;
addNotifyPeerConnectionRequestOptions.SocketId = null;
OnIncomingConnectionRequest += OnNewConnection;
OnRemoteConnectionClosed += OnConnectFail;
incomingNotificationId = EOSSDKComponent.GetP2PInterface().AddNotifyPeerConnectionRequest(addNotifyPeerConnectionRequestOptions,
null, OnIncomingConnectionRequest);
var addNotifyPeerConnectionClosedOptions = new AddNotifyPeerConnectionClosedOptions();
addNotifyPeerConnectionClosedOptions.LocalUserId = EOSSDKComponent.LocalUserProductId;
addNotifyPeerConnectionClosedOptions.SocketId = null;
outgoingNotificationId = EOSSDKComponent.GetP2PInterface().AddNotifyPeerConnectionClosed(addNotifyPeerConnectionClosedOptions,
null, OnRemoteConnectionClosed);
if (outgoingNotificationId == 0 || incomingNotificationId == 0)
{
CONNECT,
ACCEPT_CONNECT,
DISCONNECT
Debug.LogError("Couldn't bind notifications with P2P interface");
}
protected struct PacketKey
incomingPackets = new Dictionary<PacketKey, List<List<Packet>>>();
this.transport = transport;
}
protected void Dispose()
{
EOSSDKComponent.GetP2PInterface().RemoveNotifyPeerConnectionRequest(incomingNotificationId);
EOSSDKComponent.GetP2PInterface().RemoveNotifyPeerConnectionClosed(outgoingNotificationId);
transport.ResetIgnoreMessagesAtStartUpTimer();
}
protected abstract void OnNewConnection(OnIncomingConnectionRequestInfo result);
private void OnConnectFail(OnRemoteConnectionClosedInfo result)
{
if (ignoreAllMessages)
{
public ProductUserId productUserId;
public byte channel;
return;
}
private OnIncomingConnectionRequestCallback OnIncomingConnectionRequest;
private ulong incomingNotificationId = 0;
private OnRemoteConnectionClosedCallback OnRemoteConnectionClosed;
private ulong outgoingNotificationId = 0;
OnConnectionFailed(result.RemoteUserId);
protected readonly EosTransport transport;
protected List<string> deadSockets;
public bool ignoreAllMessages = false;
// Mapping from PacketKey to a List of Packet Lists
protected Dictionary<PacketKey, List<List<Packet>>> incomingPackets = new();
protected Common(EosTransport transport)
switch (result.Reason)
{
channels = transport.Channels;
case ConnectionClosedReason.ClosedByLocalUser:
throw new Exception("Connection cLosed: The Connection was gracecfully closed by the local user.");
case ConnectionClosedReason.ClosedByPeer:
throw new Exception("Connection closed: The connection was gracefully closed by remote user.");
case ConnectionClosedReason.ConnectionClosed:
throw new Exception("Connection closed: The connection was unexpectedly closed.");
case ConnectionClosedReason.ConnectionFailed:
throw new Exception("Connection failed: Failled to establish connection.");
case ConnectionClosedReason.InvalidData:
throw new Exception("Connection failed: The remote user sent us invalid data..");
case ConnectionClosedReason.InvalidMessage:
throw new Exception("Connection failed: The remote user sent us an invalid message.");
case ConnectionClosedReason.NegotiationFailed:
throw new Exception("Connection failed: Negotiation failed.");
case ConnectionClosedReason.TimedOut:
throw new Exception("Connection failed: Timeout.");
case ConnectionClosedReason.TooManyConnections:
throw new Exception("Connection failed: Too many connections.");
case ConnectionClosedReason.UnexpectedError:
throw new Exception("Unexpected Error, connection will be closed");
case ConnectionClosedReason.Unknown:
default:
throw new Exception("Unknown Error, connection has been closed.");
}
}
deadSockets = new List<string>();
protected void SendInternal(ProductUserId target, SocketId socketId, InternalMessages type)
{
EOSSDKComponent.GetP2PInterface().SendPacket(new SendPacketOptions()
{
AllowDelayedDelivery = true,
Channel = (byte)internal_ch,
Data = new byte[] { (byte)type },
LocalUserId = EOSSDKComponent.LocalUserProductId,
Reliability = PacketReliability.ReliableOrdered,
RemoteUserId = target,
SocketId = socketId
});
}
var addNotifyPeerConnectionRequestOptions = new AddNotifyPeerConnectionRequestOptions();
addNotifyPeerConnectionRequestOptions.LocalUserId = EOSSDKComponent.LocalUserProductId;
addNotifyPeerConnectionRequestOptions.SocketId = null;
protected void Send(ProductUserId host, SocketId socketId, byte[] msgBuffer, byte channel)
{
var result = EOSSDKComponent.GetP2PInterface().SendPacket(new SendPacketOptions()
{
AllowDelayedDelivery = true,
Channel = channel,
Data = msgBuffer,
LocalUserId = EOSSDKComponent.LocalUserProductId,
Reliability = channels[channel],
RemoteUserId = host,
SocketId = socketId
});
OnIncomingConnectionRequest += OnNewConnection;
OnRemoteConnectionClosed += OnConnectFail;
if (result != Result.Success)
{
Debug.LogError("Send failed " + result);
}
}
incomingNotificationId = EOSSDKComponent.GetP2PInterface().AddNotifyPeerConnectionRequest(addNotifyPeerConnectionRequestOptions,
null, OnIncomingConnectionRequest);
private bool Receive(out ProductUserId clientProductUserId, out SocketId socketId, out byte[] receiveBuffer, byte channel)
{
var result = EOSSDKComponent.GetP2PInterface().ReceivePacket(new ReceivePacketOptions()
{
LocalUserId = EOSSDKComponent.LocalUserProductId,
MaxDataSizeBytes = P2PInterface.MaxPacketSize,
RequestedChannel = channel
}, out clientProductUserId, out socketId, out channel, out receiveBuffer);
var addNotifyPeerConnectionClosedOptions = new AddNotifyPeerConnectionClosedOptions();
addNotifyPeerConnectionClosedOptions.LocalUserId = EOSSDKComponent.LocalUserProductId;
addNotifyPeerConnectionClosedOptions.SocketId = null;
outgoingNotificationId = EOSSDKComponent.GetP2PInterface().AddNotifyPeerConnectionClosed(addNotifyPeerConnectionClosedOptions,
null, OnRemoteConnectionClosed);
if (outgoingNotificationId == 0 || incomingNotificationId == 0)
{
Debug.LogError("Couldn't bind notifications with P2P interface");
}
incomingPackets = new Dictionary<PacketKey, List<List<Packet>>>();
this.transport = transport;
if (result == Result.Success)
{
return true;
}
protected void Dispose()
{
EOSSDKComponent.GetP2PInterface().RemoveNotifyPeerConnectionRequest(incomingNotificationId);
EOSSDKComponent.GetP2PInterface().RemoveNotifyPeerConnectionClosed(outgoingNotificationId);
receiveBuffer = null;
clientProductUserId = null;
return false;
}
transport.ResetIgnoreMessagesAtStartUpTimer();
protected virtual void CloseP2PSessionWithUser(ProductUserId clientUserID, SocketId socketId)
{
if (socketId == null)
{
Debug.LogWarning("Socket ID == null | " + ignoreAllMessages);
return;
}
protected abstract void OnNewConnection(OnIncomingConnectionRequestInfo result);
private void OnConnectFail(OnRemoteConnectionClosedInfo result)
if (deadSockets == null)
{
if (ignoreAllMessages)
{
return;
}
OnConnectionFailed(result.RemoteUserId);
switch (result.Reason)
{
case ConnectionClosedReason.ClosedByLocalUser:
throw new Exception("Connection cLosed: The Connection was gracecfully closed by the local user.");
case ConnectionClosedReason.ClosedByPeer:
throw new Exception("Connection closed: The connection was gracefully closed by remote user.");
case ConnectionClosedReason.ConnectionClosed:
throw new Exception("Connection closed: The connection was unexpectedly closed.");
case ConnectionClosedReason.ConnectionFailed:
throw new Exception("Connection failed: Failled to establish connection.");
case ConnectionClosedReason.InvalidData:
throw new Exception("Connection failed: The remote user sent us invalid data..");
case ConnectionClosedReason.InvalidMessage:
throw new Exception("Connection failed: The remote user sent us an invalid message.");
case ConnectionClosedReason.NegotiationFailed:
throw new Exception("Connection failed: Negotiation failed.");
case ConnectionClosedReason.TimedOut:
throw new Exception("Connection failed: Timeout.");
case ConnectionClosedReason.TooManyConnections:
throw new Exception("Connection failed: Too many connections.");
case ConnectionClosedReason.UnexpectedError:
throw new Exception("Unexpected Error, connection will be closed");
case ConnectionClosedReason.Unknown:
default:
throw new Exception("Unknown Error, connection has been closed.");
}
Debug.LogWarning("DeadSockets == null");
return;
}
protected void SendInternal(ProductUserId target, SocketId socketId, InternalMessages type)
if (deadSockets.Contains(socketId.SocketName))
{
EOSSDKComponent.GetP2PInterface().SendPacket(new SendPacketOptions()
{
AllowDelayedDelivery = true,
Channel = (byte)internal_ch,
Data = new byte[] { (byte)type },
LocalUserId = EOSSDKComponent.LocalUserProductId,
Reliability = PacketReliability.ReliableOrdered,
RemoteUserId = target,
SocketId = socketId
});
return;
}
protected void Send(ProductUserId host, SocketId socketId, byte[] msgBuffer, byte channel)
else
{
var result = EOSSDKComponent.GetP2PInterface().SendPacket(new SendPacketOptions()
{
AllowDelayedDelivery = true,
Channel = channel,
Data = msgBuffer,
LocalUserId = EOSSDKComponent.LocalUserProductId,
Reliability = channels[channel],
RemoteUserId = host,
SocketId = socketId
});
if (result != Result.Success)
{
Debug.LogError("Send failed " + result);
}
deadSockets.Add(socketId.SocketName);
}
}
private bool Receive(out ProductUserId clientProductUserId, out SocketId socketId, out byte[] receiveBuffer, byte channel)
protected void WaitForClose(ProductUserId clientUserID, SocketId socketId) => transport.StartCoroutine(DelayedClose(clientUserID, socketId));
private IEnumerator DelayedClose(ProductUserId clientUserID, SocketId socketId)
{
yield return null;
CloseP2PSessionWithUser(clientUserID, socketId);
}
public void ReceiveData()
{
try
{
var result = EOSSDKComponent.GetP2PInterface().ReceivePacket(new ReceivePacketOptions()
// Internal Channel, no fragmentation here
var socketId = new SocketId();
while (transport.enabled && Receive(out var clientUserID, out socketId, out var internalMessage, (byte)internal_ch))
{
LocalUserId = EOSSDKComponent.LocalUserProductId,
MaxDataSizeBytes = P2PInterface.MaxPacketSize,
RequestedChannel = channel
}, out clientProductUserId, out socketId, out channel, out receiveBuffer);
if (result == Result.Success)
{
return true;
}
receiveBuffer = null;
clientProductUserId = null;
return false;
}
protected virtual void CloseP2PSessionWithUser(ProductUserId clientUserID, SocketId socketId)
{
if (socketId == null)
{
Debug.LogWarning("Socket ID == null | " + ignoreAllMessages);
return;
}
if (deadSockets == null)
{
Debug.LogWarning("DeadSockets == null");
return;
}
if (deadSockets.Contains(socketId.SocketName))
{
return;
}
else
{
deadSockets.Add(socketId.SocketName);
}
}
protected void WaitForClose(ProductUserId clientUserID, SocketId socketId) => transport.StartCoroutine(DelayedClose(clientUserID, socketId));
private IEnumerator DelayedClose(ProductUserId clientUserID, SocketId socketId)
{
yield return null;
CloseP2PSessionWithUser(clientUserID, socketId);
}
public void ReceiveData()
{
try
{
// Internal Channel, no fragmentation here
var socketId = new SocketId();
while (transport.enabled && Receive(out var clientUserID, out socketId, out var internalMessage, (byte)internal_ch))
if (internalMessage.Length == 1)
{
if (internalMessage.Length == 1)
OnReceiveInternalData((InternalMessages)internalMessage[0], clientUserID, socketId);
return; // Wait one frame
}
else
{
Debug.Log("Incorrect package length on internal channel.");
}
}
// Insert new packet at the correct location in the incoming queue
for (var chNum = 0; chNum < channels.Length; chNum++)
{
while (transport.enabled && Receive(out var clientUserID, out socketId, out var receiveBuffer, (byte)chNum))
{
var incomingPacketKey = new PacketKey();
incomingPacketKey.productUserId = clientUserID;
incomingPacketKey.channel = (byte)chNum;
var packet = new Packet();
packet.FromBytes(receiveBuffer);
if (!incomingPackets.ContainsKey(incomingPacketKey))
{
OnReceiveInternalData((InternalMessages)internalMessage[0], clientUserID, socketId);
return; // Wait one frame
incomingPackets.Add(incomingPacketKey, new List<List<Packet>>());
}
var packetListIndex = incomingPackets[incomingPacketKey].Count;
for (var i = 0; i < incomingPackets[incomingPacketKey].Count; i++)
{
if (incomingPackets[incomingPacketKey][i][0].id == packet.id)
{
packetListIndex = i;
break;
}
}
if (packetListIndex == incomingPackets[incomingPacketKey].Count)
{
incomingPackets[incomingPacketKey].Add(new List<Packet>());
}
var insertionIndex = -1;
for (var i = 0; i < incomingPackets[incomingPacketKey][packetListIndex].Count; i++)
{
if (incomingPackets[incomingPacketKey][packetListIndex][i].fragment > packet.fragment)
{
insertionIndex = i;
break;
}
}
if (insertionIndex >= 0)
{
incomingPackets[incomingPacketKey][packetListIndex].Insert(insertionIndex, packet);
}
else
{
Debug.Log("Incorrect package length on internal channel.");
incomingPackets[incomingPacketKey][packetListIndex].Add(packet);
}
}
}
// Insert new packet at the correct location in the incoming queue
for (var chNum = 0; chNum < channels.Length; chNum++)
// Find fully received packets
var emptyPacketLists = new List<List<Packet>>();
foreach (var keyValuePair in incomingPackets)
{
for (var packetList = 0; packetList < keyValuePair.Value.Count; packetList++)
{
while (transport.enabled && Receive(out var clientUserID, out socketId, out var receiveBuffer, (byte)chNum))
var packetReady = true;
var packetLength = 0;
for (var packet = 0; packet < keyValuePair.Value[packetList].Count; packet++)
{
var incomingPacketKey = new PacketKey();
incomingPacketKey.productUserId = clientUserID;
incomingPacketKey.channel = (byte)chNum;
var packet = new Packet();
packet.FromBytes(receiveBuffer);
if (!incomingPackets.ContainsKey(incomingPacketKey))
var tempPacket = keyValuePair.Value[packetList][packet];
if (tempPacket.fragment != packet || packet == keyValuePair.Value[packetList].Count - 1 && tempPacket.moreFragments)
{
incomingPackets.Add(incomingPacketKey, new List<List<Packet>>());
}
var packetListIndex = incomingPackets[incomingPacketKey].Count;
for (var i = 0; i < incomingPackets[incomingPacketKey].Count; i++)
{
if (incomingPackets[incomingPacketKey][i][0].id == packet.id)
{
packetListIndex = i;
break;
}
}
if (packetListIndex == incomingPackets[incomingPacketKey].Count)
{
incomingPackets[incomingPacketKey].Add(new List<Packet>());
}
var insertionIndex = -1;
for (var i = 0; i < incomingPackets[incomingPacketKey][packetListIndex].Count; i++)
{
if (incomingPackets[incomingPacketKey][packetListIndex][i].fragment > packet.fragment)
{
insertionIndex = i;
break;
}
}
if (insertionIndex >= 0)
{
incomingPackets[incomingPacketKey][packetListIndex].Insert(insertionIndex, packet);
packetReady = false;
}
else
{
incomingPackets[incomingPacketKey][packetListIndex].Add(packet);
packetLength += tempPacket.data.Length;
}
}
}
// Find fully received packets
var emptyPacketLists = new List<List<Packet>>();
foreach (var keyValuePair in incomingPackets)
{
for (var packetList = 0; packetList < keyValuePair.Value.Count; packetList++)
if (packetReady)
{
var packetReady = true;
var packetLength = 0;
var data = new byte[packetLength];
var dataIndex = 0;
for (var packet = 0; packet < keyValuePair.Value[packetList].Count; packet++)
{
var tempPacket = keyValuePair.Value[packetList][packet];
if (tempPacket.fragment != packet || packet == keyValuePair.Value[packetList].Count - 1 && tempPacket.moreFragments)
{
packetReady = false;
}
else
{
packetLength += tempPacket.data.Length;
}
Array.Copy(keyValuePair.Value[packetList][packet].data, 0, data, dataIndex, keyValuePair.Value[packetList][packet].data.Length);
dataIndex += keyValuePair.Value[packetList][packet].data.Length;
}
if (packetReady)
{
var data = new byte[packetLength];
var dataIndex = 0;
OnReceiveData(data, keyValuePair.Key.productUserId, keyValuePair.Key.channel);
for (var packet = 0; packet < keyValuePair.Value[packetList].Count; packet++)
{
Array.Copy(keyValuePair.Value[packetList][packet].data, 0, data, dataIndex, keyValuePair.Value[packetList][packet].data.Length);
dataIndex += keyValuePair.Value[packetList][packet].data.Length;
}
OnReceiveData(data, keyValuePair.Key.productUserId, keyValuePair.Key.channel);
//keyValuePair.Value[packetList].Clear();
emptyPacketLists.Add(keyValuePair.Value[packetList]);
}
//keyValuePair.Value[packetList].Clear();
emptyPacketLists.Add(keyValuePair.Value[packetList]);
}
for (var i = 0; i < emptyPacketLists.Count; i++)
{
keyValuePair.Value.Remove(emptyPacketLists[i]);
}
emptyPacketLists.Clear();
}
}
catch (Exception e)
{
Debug.LogException(e);
for (var i = 0; i < emptyPacketLists.Count; i++)
{
keyValuePair.Value.Remove(emptyPacketLists[i]);
}
emptyPacketLists.Clear();
}
}
protected abstract void OnReceiveInternalData(InternalMessages type, ProductUserId clientUserID, SocketId socketId);
protected abstract void OnReceiveData(byte[] data, ProductUserId clientUserID, int channel);
protected abstract void OnConnectionFailed(ProductUserId remoteId);
catch (Exception e)
{
Debug.LogException(e);
}
}
protected abstract void OnReceiveInternalData(InternalMessages type, ProductUserId clientUserID, SocketId socketId);
protected abstract void OnReceiveData(byte[] data, ProductUserId clientUserID, int channel);
protected abstract void OnConnectionFailed(ProductUserId remoteId);
}

View File

@ -10,120 +10,120 @@ using UnityEngine;
/// after releasing the SDK the game has to be restarted in order to initialize the SDK again.
/// In the unity editor the OnDestroy function will not run so that we dont have to restart the editor after play.
/// </summary>
namespace EpicTransport
namespace EpicTransport;
[DefaultExecutionOrder(-32000)]
public class EOSSDKComponent : MonoBehaviour
{
[DefaultExecutionOrder(-32000)]
public class EOSSDKComponent : MonoBehaviour
// Unity Inspector shown variables
[SerializeField]
public EosApiKey apiKeys;
[Header("User Login")]
public bool authInterfaceLogin = false;
public Epic.OnlineServices.Auth.LoginCredentialType authInterfaceCredentialType = Epic.OnlineServices.Auth.LoginCredentialType.AccountPortal;
public uint devAuthToolPort = 7878;
public string devAuthToolCredentialName = "";
public ExternalCredentialType connectInterfaceCredentialType = ExternalCredentialType.DeviceidAccessToken;
public string deviceModel = "PC Windows 64bit";
[SerializeField] private string displayName = "User";
public static string DisplayName
{
// Unity Inspector shown variables
get => Instance.displayName;
set => Instance.displayName = value;
}
[SerializeField]
public EosApiKey apiKeys;
[Header("Misc")]
public LogLevel epicLoggerLevel = LogLevel.Error;
[Header("User Login")]
public bool authInterfaceLogin = false;
[SerializeField]
public bool collectPlayerMetrics = true;
public Epic.OnlineServices.Auth.LoginCredentialType authInterfaceCredentialType = Epic.OnlineServices.Auth.LoginCredentialType.AccountPortal;
public uint devAuthToolPort = 7878;
public string devAuthToolCredentialName = "";
public ExternalCredentialType connectInterfaceCredentialType = ExternalCredentialType.DeviceidAccessToken;
public string deviceModel = "PC Windows 64bit";
[SerializeField] private string displayName = "User";
public static bool CollectPlayerMetrics => Instance.collectPlayerMetrics;
public static string DisplayName
public bool checkForEpicLauncherAndRestart = false;
public bool delayedInitialization = false;
public float platformTickIntervalInSeconds = 0.0f;
private float platformTickTimer = 0f;
public uint tickBudgetInMilliseconds = 0;
// End Unity Inspector shown variables
private ulong authExpirationHandle;
private string authInterfaceLoginCredentialId = null;
public static void SetAuthInterfaceLoginCredentialId(string credentialId) => Instance.authInterfaceLoginCredentialId = credentialId;
private string authInterfaceCredentialToken = null;
public static void SetAuthInterfaceCredentialToken(string credentialToken) => Instance.authInterfaceCredentialToken = credentialToken;
private string connectInterfaceCredentialToken = null;
public static void SetConnectInterfaceCredentialToken(string credentialToken) => Instance.connectInterfaceCredentialToken = credentialToken;
private PlatformInterface EOS;
// Interfaces
public static Epic.OnlineServices.Achievements.AchievementsInterface GetAchievementsInterface() => Instance.EOS.GetAchievementsInterface();
public static Epic.OnlineServices.Auth.AuthInterface GetAuthInterface() => Instance.EOS.GetAuthInterface();
public static Epic.OnlineServices.Connect.ConnectInterface GetConnectInterface() => Instance.EOS.GetConnectInterface();
public static Epic.OnlineServices.Ecom.EcomInterface GetEcomInterface() => Instance.EOS.GetEcomInterface();
public static Epic.OnlineServices.Friends.FriendsInterface GetFriendsInterface() => Instance.EOS.GetFriendsInterface();
public static Epic.OnlineServices.Leaderboards.LeaderboardsInterface GetLeaderboardsInterface() => Instance.EOS.GetLeaderboardsInterface();
public static Epic.OnlineServices.Lobby.LobbyInterface GetLobbyInterface() => Instance.EOS.GetLobbyInterface();
public static Epic.OnlineServices.Metrics.MetricsInterface GetMetricsInterface() => Instance.EOS.GetMetricsInterface(); // Handled by the transport automatically, only use this interface if Mirror is not used for singleplayer
public static Epic.OnlineServices.Mods.ModsInterface GetModsInterface() => Instance.EOS.GetModsInterface();
public static Epic.OnlineServices.P2P.P2PInterface GetP2PInterface() => Instance.EOS.GetP2PInterface();
public static Epic.OnlineServices.PlayerDataStorage.PlayerDataStorageInterface GetPlayerDataStorageInterface() => Instance.EOS.GetPlayerDataStorageInterface();
public static Epic.OnlineServices.Presence.PresenceInterface GetPresenceInterface() => Instance.EOS.GetPresenceInterface();
public static Epic.OnlineServices.Sessions.SessionsInterface GetSessionsInterface() => Instance.EOS.GetSessionsInterface();
public static Epic.OnlineServices.TitleStorage.TitleStorageInterface GetTitleStorageInterface() => Instance.EOS.GetTitleStorageInterface();
public static Epic.OnlineServices.UI.UIInterface GetUIInterface() => Instance.EOS.GetUIInterface();
public static Epic.OnlineServices.UserInfo.UserInfoInterface GetUserInfoInterface() => Instance.EOS.GetUserInfoInterface();
protected EpicAccountId localUserAccountId;
public static EpicAccountId LocalUserAccountId => Instance.localUserAccountId;
protected string localUserAccountIdString;
public static string LocalUserAccountIdString => Instance.localUserAccountIdString;
protected ProductUserId localUserProductId;
public static ProductUserId LocalUserProductId => Instance.localUserProductId;
protected string localUserProductIdString;
public static string LocalUserProductIdString => Instance.localUserProductIdString;
protected bool initialized;
public static bool Initialized => Instance.initialized;
protected bool isConnecting;
public static bool IsConnecting => Instance.isConnecting;
protected static EOSSDKComponent instance;
protected static EOSSDKComponent Instance
{
get
{
get => Instance.displayName;
set => Instance.displayName = value;
}
[Header("Misc")]
public LogLevel epicLoggerLevel = LogLevel.Error;
[SerializeField]
public bool collectPlayerMetrics = true;
public static bool CollectPlayerMetrics => Instance.collectPlayerMetrics;
public bool checkForEpicLauncherAndRestart = false;
public bool delayedInitialization = false;
public float platformTickIntervalInSeconds = 0.0f;
private float platformTickTimer = 0f;
public uint tickBudgetInMilliseconds = 0;
// End Unity Inspector shown variables
private ulong authExpirationHandle;
private string authInterfaceLoginCredentialId = null;
public static void SetAuthInterfaceLoginCredentialId(string credentialId) => Instance.authInterfaceLoginCredentialId = credentialId;
private string authInterfaceCredentialToken = null;
public static void SetAuthInterfaceCredentialToken(string credentialToken) => Instance.authInterfaceCredentialToken = credentialToken;
private string connectInterfaceCredentialToken = null;
public static void SetConnectInterfaceCredentialToken(string credentialToken) => Instance.connectInterfaceCredentialToken = credentialToken;
private PlatformInterface EOS;
// Interfaces
public static Epic.OnlineServices.Achievements.AchievementsInterface GetAchievementsInterface() => Instance.EOS.GetAchievementsInterface();
public static Epic.OnlineServices.Auth.AuthInterface GetAuthInterface() => Instance.EOS.GetAuthInterface();
public static Epic.OnlineServices.Connect.ConnectInterface GetConnectInterface() => Instance.EOS.GetConnectInterface();
public static Epic.OnlineServices.Ecom.EcomInterface GetEcomInterface() => Instance.EOS.GetEcomInterface();
public static Epic.OnlineServices.Friends.FriendsInterface GetFriendsInterface() => Instance.EOS.GetFriendsInterface();
public static Epic.OnlineServices.Leaderboards.LeaderboardsInterface GetLeaderboardsInterface() => Instance.EOS.GetLeaderboardsInterface();
public static Epic.OnlineServices.Lobby.LobbyInterface GetLobbyInterface() => Instance.EOS.GetLobbyInterface();
public static Epic.OnlineServices.Metrics.MetricsInterface GetMetricsInterface() => Instance.EOS.GetMetricsInterface(); // Handled by the transport automatically, only use this interface if Mirror is not used for singleplayer
public static Epic.OnlineServices.Mods.ModsInterface GetModsInterface() => Instance.EOS.GetModsInterface();
public static Epic.OnlineServices.P2P.P2PInterface GetP2PInterface() => Instance.EOS.GetP2PInterface();
public static Epic.OnlineServices.PlayerDataStorage.PlayerDataStorageInterface GetPlayerDataStorageInterface() => Instance.EOS.GetPlayerDataStorageInterface();
public static Epic.OnlineServices.Presence.PresenceInterface GetPresenceInterface() => Instance.EOS.GetPresenceInterface();
public static Epic.OnlineServices.Sessions.SessionsInterface GetSessionsInterface() => Instance.EOS.GetSessionsInterface();
public static Epic.OnlineServices.TitleStorage.TitleStorageInterface GetTitleStorageInterface() => Instance.EOS.GetTitleStorageInterface();
public static Epic.OnlineServices.UI.UIInterface GetUIInterface() => Instance.EOS.GetUIInterface();
public static Epic.OnlineServices.UserInfo.UserInfoInterface GetUserInfoInterface() => Instance.EOS.GetUserInfoInterface();
protected EpicAccountId localUserAccountId;
public static EpicAccountId LocalUserAccountId => Instance.localUserAccountId;
protected string localUserAccountIdString;
public static string LocalUserAccountIdString => Instance.localUserAccountIdString;
protected ProductUserId localUserProductId;
public static ProductUserId LocalUserProductId => Instance.localUserProductId;
protected string localUserProductIdString;
public static string LocalUserProductIdString => Instance.localUserProductIdString;
protected bool initialized;
public static bool Initialized => Instance.initialized;
protected bool isConnecting;
public static bool IsConnecting => Instance.isConnecting;
protected static EOSSDKComponent instance;
protected static EOSSDKComponent Instance
{
get
if (instance == null)
{
if (instance == null)
{
return new GameObject("EOSSDKComponent").AddComponent<EOSSDKComponent>();
}
else
{
return instance;
}
return new GameObject("EOSSDKComponent").AddComponent<EOSSDKComponent>();
}
else
{
return instance;
}
}
}
public static void Tick()
{
instance.platformTickTimer -= Time.deltaTime;
instance.EOS.Tick();
}
public static void Tick()
{
instance.platformTickTimer -= Time.deltaTime;
instance.EOS.Tick();
}
// If we're in editor, we should dynamically load and unload the SDK between play sessions.
// This allows us to initialize the SDK each time the game is run in editor.
// If we're in editor, we should dynamically load and unload the SDK between play sessions.
// This allows us to initialize the SDK each time the game is run in editor.
#if UNITY_EDITOR_WIN
[DllImport("Kernel32.dll")]
private static extern IntPtr LoadLibrary(string lpLibFileName);
@ -163,27 +163,27 @@ namespace EpicTransport
private IntPtr libraryPointer;
#endif
private void Awake()
private void Awake()
{
// Initialize Java version of the SDK with a reference to the VM with JNI
// See https://eoshelp.epicgames.com/s/question/0D54z00006ufJBNCA2/cant-get-createdeviceid-to-work-in-unity-android-c-sdk?language=en_US
if (Application.platform == RuntimePlatform.Android)
{
// Initialize Java version of the SDK with a reference to the VM with JNI
// See https://eoshelp.epicgames.com/s/question/0D54z00006ufJBNCA2/cant-get-createdeviceid-to-work-in-unity-android-c-sdk?language=en_US
if (Application.platform == RuntimePlatform.Android)
{
var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
var activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
var context = activity.Call<AndroidJavaObject>("getApplicationContext");
var EOS_SDK_JAVA = new AndroidJavaClass("com.epicgames.mobile.eossdk.EOSSDK");
EOS_SDK_JAVA.CallStatic("init", context);
}
var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
var activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
var context = activity.Call<AndroidJavaObject>("getApplicationContext");
var EOS_SDK_JAVA = new AndroidJavaClass("com.epicgames.mobile.eossdk.EOSSDK");
EOS_SDK_JAVA.CallStatic("init", context);
}
// Prevent multiple instances
if (instance != null)
{
Destroy(gameObject);
return;
}
// Prevent multiple instances
if (instance != null)
{
Destroy(gameObject);
return;
}
instance = this;
instance = this;
#if UNITY_EDITOR
var libraryPath = "Assets/Mirror/Runtime/Transport/EpicOnlineTransport/EOSSDK/" + Config.LibraryName;
@ -196,260 +196,260 @@ namespace EpicTransport
Bindings.Hook(libraryPointer, GetProcAddress);
#endif
if (!delayedInitialization)
if (!delayedInitialization)
{
Initialize();
}
}
protected void InitializeImplementation()
{
isConnecting = true;
var initializeOptions = new InitializeOptions()
{
ProductName = apiKeys.epicProductName,
ProductVersion = apiKeys.epicProductVersion
};
var initializeResult = PlatformInterface.Initialize(initializeOptions);
// This code is called each time the game is run in the editor, so we catch the case where the SDK has already been initialized in the editor.
var isAlreadyConfiguredInEditor = Application.isEditor && initializeResult == Result.AlreadyConfigured;
if (initializeResult != Result.Success && !isAlreadyConfiguredInEditor)
{
throw new System.Exception("Failed to initialize platform: " + initializeResult);
}
// The SDK outputs lots of information that is useful for debugging.
// Make sure to set up the logging interface as early as possible: after initializing.
LoggingInterface.SetLogLevel(LogCategory.AllCategories, epicLoggerLevel);
LoggingInterface.SetCallback(message => Logger.EpicDebugLog(message));
var options = new Options()
{
ProductId = apiKeys.epicProductId,
SandboxId = apiKeys.epicSandboxId,
DeploymentId = apiKeys.epicDeploymentId,
ClientCredentials = new ClientCredentials()
{
Initialize();
ClientId = apiKeys.epicClientId,
ClientSecret = apiKeys.epicClientSecret
},
TickBudgetInMilliseconds = tickBudgetInMilliseconds
};
EOS = PlatformInterface.Create(options);
if (EOS == null)
{
throw new System.Exception("Failed to create platform");
}
if (checkForEpicLauncherAndRestart)
{
var result = EOS.CheckForLauncherAndRestart();
// If not started through epic launcher the app will be restarted and we can quit
if (result != Result.NoChange)
{
// Log error if launcher check failed, but still quit to prevent hacking
if (result == Result.UnexpectedError)
{
Debug.LogError("Unexpected Error while checking if app was started through epic launcher");
}
Application.Quit();
}
}
protected void InitializeImplementation()
// If we use the Auth interface then only login into the Connect interface after finishing the auth interface login
// If we don't use the Auth interface we can directly login to the Connect interface
if (authInterfaceLogin)
{
isConnecting = true;
var initializeOptions = new InitializeOptions()
if (authInterfaceCredentialType == Epic.OnlineServices.Auth.LoginCredentialType.Developer)
{
ProductName = apiKeys.epicProductName,
ProductVersion = apiKeys.epicProductVersion
};
var initializeResult = PlatformInterface.Initialize(initializeOptions);
// This code is called each time the game is run in the editor, so we catch the case where the SDK has already been initialized in the editor.
var isAlreadyConfiguredInEditor = Application.isEditor && initializeResult == Result.AlreadyConfigured;
if (initializeResult != Result.Success && !isAlreadyConfiguredInEditor)
{
throw new System.Exception("Failed to initialize platform: " + initializeResult);
authInterfaceLoginCredentialId = "localhost:" + devAuthToolPort;
authInterfaceCredentialToken = devAuthToolCredentialName;
}
// The SDK outputs lots of information that is useful for debugging.
// Make sure to set up the logging interface as early as possible: after initializing.
LoggingInterface.SetLogLevel(LogCategory.AllCategories, epicLoggerLevel);
LoggingInterface.SetCallback(message => Logger.EpicDebugLog(message));
var options = new Options()
// Login to Auth Interface
var loginOptions = new Epic.OnlineServices.Auth.LoginOptions()
{
ProductId = apiKeys.epicProductId,
SandboxId = apiKeys.epicSandboxId,
DeploymentId = apiKeys.epicDeploymentId,
ClientCredentials = new ClientCredentials()
Credentials = new Epic.OnlineServices.Auth.Credentials()
{
ClientId = apiKeys.epicClientId,
ClientSecret = apiKeys.epicClientSecret
Type = authInterfaceCredentialType,
Id = authInterfaceLoginCredentialId,
Token = authInterfaceCredentialToken
},
TickBudgetInMilliseconds = tickBudgetInMilliseconds
ScopeFlags = Epic.OnlineServices.Auth.AuthScopeFlags.BasicProfile | Epic.OnlineServices.Auth.AuthScopeFlags.FriendsList | Epic.OnlineServices.Auth.AuthScopeFlags.Presence
};
EOS = PlatformInterface.Create(options);
if (EOS == null)
EOS.GetAuthInterface().Login(loginOptions, null, OnAuthInterfaceLogin);
}
else
{
// Login to Connect Interface
if (connectInterfaceCredentialType == ExternalCredentialType.DeviceidAccessToken)
{
throw new System.Exception("Failed to create platform");
}
if (checkForEpicLauncherAndRestart)
{
var result = EOS.CheckForLauncherAndRestart();
// If not started through epic launcher the app will be restarted and we can quit
if (result != Result.NoChange)
{
// Log error if launcher check failed, but still quit to prevent hacking
if (result == Result.UnexpectedError)
{
Debug.LogError("Unexpected Error while checking if app was started through epic launcher");
}
Application.Quit();
}
}
// If we use the Auth interface then only login into the Connect interface after finishing the auth interface login
// If we don't use the Auth interface we can directly login to the Connect interface
if (authInterfaceLogin)
{
if (authInterfaceCredentialType == Epic.OnlineServices.Auth.LoginCredentialType.Developer)
{
authInterfaceLoginCredentialId = "localhost:" + devAuthToolPort;
authInterfaceCredentialToken = devAuthToolCredentialName;
}
// Login to Auth Interface
var loginOptions = new Epic.OnlineServices.Auth.LoginOptions()
{
Credentials = new Epic.OnlineServices.Auth.Credentials()
{
Type = authInterfaceCredentialType,
Id = authInterfaceLoginCredentialId,
Token = authInterfaceCredentialToken
},
ScopeFlags = Epic.OnlineServices.Auth.AuthScopeFlags.BasicProfile | Epic.OnlineServices.Auth.AuthScopeFlags.FriendsList | Epic.OnlineServices.Auth.AuthScopeFlags.Presence
};
EOS.GetAuthInterface().Login(loginOptions, null, OnAuthInterfaceLogin);
var createDeviceIdOptions = new Epic.OnlineServices.Connect.CreateDeviceIdOptions();
createDeviceIdOptions.DeviceModel = deviceModel;
EOS.GetConnectInterface().CreateDeviceId(createDeviceIdOptions, null, OnCreateDeviceId);
}
else
{
// Login to Connect Interface
if (connectInterfaceCredentialType == ExternalCredentialType.DeviceidAccessToken)
{
var createDeviceIdOptions = new Epic.OnlineServices.Connect.CreateDeviceIdOptions();
createDeviceIdOptions.DeviceModel = deviceModel;
EOS.GetConnectInterface().CreateDeviceId(createDeviceIdOptions, null, OnCreateDeviceId);
}
else
{
ConnectInterfaceLogin();
}
}
}
public static void Initialize()
{
if (Instance.initialized || Instance.isConnecting)
{
return;
}
Instance.InitializeImplementation();
}
private void OnAuthInterfaceLogin(Epic.OnlineServices.Auth.LoginCallbackInfo loginCallbackInfo)
{
if (loginCallbackInfo.ResultCode == Result.Success)
{
Debug.Log("Auth Interface Login succeeded");
string accountIdString;
var result = loginCallbackInfo.LocalUserId.ToString(out accountIdString);
if (Result.Success == result)
{
Debug.Log("EOS User ID:" + accountIdString);
localUserAccountIdString = accountIdString;
localUserAccountId = loginCallbackInfo.LocalUserId;
}
ConnectInterfaceLogin();
}
else if (Epic.OnlineServices.Common.IsOperationComplete(loginCallbackInfo.ResultCode))
{
Debug.Log("Login returned " + loginCallbackInfo.ResultCode);
}
}
}
public static void Initialize()
{
if (Instance.initialized || Instance.isConnecting)
{
return;
}
private void OnCreateDeviceId(Epic.OnlineServices.Connect.CreateDeviceIdCallbackInfo createDeviceIdCallbackInfo)
Instance.InitializeImplementation();
}
private void OnAuthInterfaceLogin(Epic.OnlineServices.Auth.LoginCallbackInfo loginCallbackInfo)
{
if (loginCallbackInfo.ResultCode == Result.Success)
{
if (createDeviceIdCallbackInfo.ResultCode == Result.Success || createDeviceIdCallbackInfo.ResultCode == Result.DuplicateNotAllowed)
{
ConnectInterfaceLogin();
}
else if (Epic.OnlineServices.Common.IsOperationComplete(createDeviceIdCallbackInfo.ResultCode))
{
Debug.Log("Device ID creation returned " + createDeviceIdCallbackInfo.ResultCode);
}
}
Debug.Log("Auth Interface Login succeeded");
private void ConnectInterfaceLogin()
{
var loginOptions = new Epic.OnlineServices.Connect.LoginOptions();
if (connectInterfaceCredentialType == ExternalCredentialType.Epic)
string accountIdString;
var result = loginCallbackInfo.LocalUserId.ToString(out accountIdString);
if (Result.Success == result)
{
Epic.OnlineServices.Auth.Token token;
var result = EOS.GetAuthInterface().CopyUserAuthToken(new Epic.OnlineServices.Auth.CopyUserAuthTokenOptions(), localUserAccountId, out token);
Debug.Log("EOS User ID:" + accountIdString);
if (result == Result.Success)
{
connectInterfaceCredentialToken = token.AccessToken;
}
else
{
Debug.LogError("Failed to retrieve User Auth Token");
}
}
else if (connectInterfaceCredentialType == ExternalCredentialType.DeviceidAccessToken)
{
loginOptions.UserLoginInfo = new Epic.OnlineServices.Connect.UserLoginInfo();
loginOptions.UserLoginInfo.DisplayName = displayName;
localUserAccountIdString = accountIdString;
localUserAccountId = loginCallbackInfo.LocalUserId;
}
loginOptions.Credentials = new Epic.OnlineServices.Connect.Credentials();
loginOptions.Credentials.Type = connectInterfaceCredentialType;
loginOptions.Credentials.Token = connectInterfaceCredentialToken;
EOS.GetConnectInterface().Login(loginOptions, null, OnConnectInterfaceLogin);
}
private void OnConnectInterfaceLogin(Epic.OnlineServices.Connect.LoginCallbackInfo loginCallbackInfo)
{
if (loginCallbackInfo.ResultCode == Result.Success)
{
Debug.Log("Connect Interface Login succeeded");
string productIdString;
var result = loginCallbackInfo.LocalUserId.ToString(out productIdString);
if (Result.Success == result)
{
Debug.Log("EOS User Product ID:" + productIdString);
localUserProductIdString = productIdString;
localUserProductId = loginCallbackInfo.LocalUserId;
}
initialized = true;
isConnecting = false;
var authExpirationOptions = new Epic.OnlineServices.Connect.AddNotifyAuthExpirationOptions();
authExpirationHandle = EOS.GetConnectInterface().AddNotifyAuthExpiration(authExpirationOptions, null, OnAuthExpiration);
}
else if (Epic.OnlineServices.Common.IsOperationComplete(loginCallbackInfo.ResultCode))
{
Debug.Log("Login returned " + loginCallbackInfo.ResultCode + "\nRetrying...");
EOS.GetConnectInterface().CreateUser(new Epic.OnlineServices.Connect.CreateUserOptions() { ContinuanceToken = loginCallbackInfo.ContinuanceToken }, null, (Epic.OnlineServices.Connect.CreateUserCallbackInfo cb) =>
{
if (cb.ResultCode != Result.Success)
{
Debug.Log(cb.ResultCode);
return;
}
localUserProductId = cb.LocalUserId;
ConnectInterfaceLogin();
});
}
}
private void OnAuthExpiration(Epic.OnlineServices.Connect.AuthExpirationCallbackInfo authExpirationCallbackInfo)
{
Debug.Log("AuthExpiration callback");
EOS.GetConnectInterface().RemoveNotifyAuthExpiration(authExpirationHandle);
ConnectInterfaceLogin();
}
// Calling tick on a regular interval is required for callbacks to work.
private void LateUpdate()
else if (Epic.OnlineServices.Common.IsOperationComplete(loginCallbackInfo.ResultCode))
{
if (EOS != null)
{
platformTickTimer += Time.deltaTime;
Debug.Log("Login returned " + loginCallbackInfo.ResultCode);
}
}
if (platformTickTimer >= platformTickIntervalInSeconds)
{
platformTickTimer = 0;
EOS.Tick();
}
private void OnCreateDeviceId(Epic.OnlineServices.Connect.CreateDeviceIdCallbackInfo createDeviceIdCallbackInfo)
{
if (createDeviceIdCallbackInfo.ResultCode == Result.Success || createDeviceIdCallbackInfo.ResultCode == Result.DuplicateNotAllowed)
{
ConnectInterfaceLogin();
}
else if (Epic.OnlineServices.Common.IsOperationComplete(createDeviceIdCallbackInfo.ResultCode))
{
Debug.Log("Device ID creation returned " + createDeviceIdCallbackInfo.ResultCode);
}
}
private void ConnectInterfaceLogin()
{
var loginOptions = new Epic.OnlineServices.Connect.LoginOptions();
if (connectInterfaceCredentialType == ExternalCredentialType.Epic)
{
Epic.OnlineServices.Auth.Token token;
var result = EOS.GetAuthInterface().CopyUserAuthToken(new Epic.OnlineServices.Auth.CopyUserAuthTokenOptions(), localUserAccountId, out token);
if (result == Result.Success)
{
connectInterfaceCredentialToken = token.AccessToken;
}
else
{
Debug.LogError("Failed to retrieve User Auth Token");
}
}
private void OnApplicationQuit()
else if (connectInterfaceCredentialType == ExternalCredentialType.DeviceidAccessToken)
{
if (EOS != null)
loginOptions.UserLoginInfo = new Epic.OnlineServices.Connect.UserLoginInfo();
loginOptions.UserLoginInfo.DisplayName = displayName;
}
loginOptions.Credentials = new Epic.OnlineServices.Connect.Credentials();
loginOptions.Credentials.Type = connectInterfaceCredentialType;
loginOptions.Credentials.Token = connectInterfaceCredentialToken;
EOS.GetConnectInterface().Login(loginOptions, null, OnConnectInterfaceLogin);
}
private void OnConnectInterfaceLogin(Epic.OnlineServices.Connect.LoginCallbackInfo loginCallbackInfo)
{
if (loginCallbackInfo.ResultCode == Result.Success)
{
Debug.Log("Connect Interface Login succeeded");
string productIdString;
var result = loginCallbackInfo.LocalUserId.ToString(out productIdString);
if (Result.Success == result)
{
EOS.Release();
EOS = null;
PlatformInterface.Shutdown();
Debug.Log("EOS User Product ID:" + productIdString);
localUserProductIdString = productIdString;
localUserProductId = loginCallbackInfo.LocalUserId;
}
// Unhook the library in the editor, this makes it possible to load the library again after stopping to play
initialized = true;
isConnecting = false;
var authExpirationOptions = new Epic.OnlineServices.Connect.AddNotifyAuthExpirationOptions();
authExpirationHandle = EOS.GetConnectInterface().AddNotifyAuthExpiration(authExpirationOptions, null, OnAuthExpiration);
}
else if (Epic.OnlineServices.Common.IsOperationComplete(loginCallbackInfo.ResultCode))
{
Debug.Log("Login returned " + loginCallbackInfo.ResultCode + "\nRetrying...");
EOS.GetConnectInterface().CreateUser(new Epic.OnlineServices.Connect.CreateUserOptions() { ContinuanceToken = loginCallbackInfo.ContinuanceToken }, null, (Epic.OnlineServices.Connect.CreateUserCallbackInfo cb) =>
{
if (cb.ResultCode != Result.Success)
{
Debug.Log(cb.ResultCode);
return;
}
localUserProductId = cb.LocalUserId;
ConnectInterfaceLogin();
});
}
}
private void OnAuthExpiration(Epic.OnlineServices.Connect.AuthExpirationCallbackInfo authExpirationCallbackInfo)
{
Debug.Log("AuthExpiration callback");
EOS.GetConnectInterface().RemoveNotifyAuthExpiration(authExpirationHandle);
ConnectInterfaceLogin();
}
// Calling tick on a regular interval is required for callbacks to work.
private void LateUpdate()
{
if (EOS != null)
{
platformTickTimer += Time.deltaTime;
if (platformTickTimer >= platformTickIntervalInSeconds)
{
platformTickTimer = 0;
EOS.Tick();
}
}
}
private void OnApplicationQuit()
{
if (EOS != null)
{
EOS.Release();
EOS = null;
PlatformInterface.Shutdown();
}
// Unhook the library in the editor, this makes it possible to load the library again after stopping to play
#if UNITY_EDITOR
if (libraryPointer != IntPtr.Zero) {
Bindings.Unhook();
@ -460,6 +460,5 @@ namespace EpicTransport
libraryPointer = IntPtr.Zero;
}
#endif
}
}
}

View File

@ -6,395 +6,394 @@ using System;
using System.Collections;
using UnityEngine;
namespace EpicTransport
namespace EpicTransport;
/// <summary>
/// EOS Transport following the Mirror transport standard
/// </summary>
public class EosTransport : Transport
{
/// <summary>
/// EOS Transport following the Mirror transport standard
/// </summary>
public class EosTransport : Transport
private const string EPIC_SCHEME = "epic";
private Client client;
private Server server;
private Common activeNode;
[SerializeField]
public PacketReliability[] Channels = new PacketReliability[2] { PacketReliability.ReliableOrdered, PacketReliability.UnreliableUnordered };
[Tooltip("Timeout for connecting in seconds.")]
public int timeout = 25;
[Tooltip("The max fragments used in fragmentation before throwing an error.")]
public int maxFragments = 55;
public float ignoreCachedMessagesAtStartUpInSeconds = 2.0f;
private float ignoreCachedMessagesTimer = 0.0f;
public RelayControl relayControl = RelayControl.AllowRelays;
[Header("Info")]
[Tooltip("This will display your Epic Account ID when you start or connect to a server.")]
public ProductUserId productUserId;
private int packetId = 0;
public Action<string> SetTransportError;
private void Awake()
{
private const string EPIC_SCHEME = "epic";
Debug.Assert(Channels != null && Channels.Length > 0, "No channel configured for EOS Transport.");
Debug.Assert(Channels.Length < byte.MaxValue, "Too many channels configured for EOS Transport");
private Client client;
private Server server;
private Common activeNode;
[SerializeField]
public PacketReliability[] Channels = new PacketReliability[2] { PacketReliability.ReliableOrdered, PacketReliability.UnreliableUnordered };
[Tooltip("Timeout for connecting in seconds.")]
public int timeout = 25;
[Tooltip("The max fragments used in fragmentation before throwing an error.")]
public int maxFragments = 55;
public float ignoreCachedMessagesAtStartUpInSeconds = 2.0f;
private float ignoreCachedMessagesTimer = 0.0f;
public RelayControl relayControl = RelayControl.AllowRelays;
[Header("Info")]
[Tooltip("This will display your Epic Account ID when you start or connect to a server.")]
public ProductUserId productUserId;
private int packetId = 0;
public Action<string> SetTransportError;
private void Awake()
if (Channels[0] != PacketReliability.ReliableOrdered)
{
Debug.Assert(Channels != null && Channels.Length > 0, "No channel configured for EOS Transport.");
Debug.Assert(Channels.Length < byte.MaxValue, "Too many channels configured for EOS Transport");
if (Channels[0] != PacketReliability.ReliableOrdered)
{
Debug.LogWarning("EOS Transport Channel[0] is not ReliableOrdered, Mirror expects Channel 0 to be ReliableOrdered, only change this if you know what you are doing.");
}
if (Channels[1] != PacketReliability.UnreliableUnordered)
{
Debug.LogWarning("EOS Transport Channel[1] is not UnreliableUnordered, Mirror expects Channel 1 to be UnreliableUnordered, only change this if you know what you are doing.");
}
StartCoroutine("FetchEpicAccountId");
StartCoroutine("ChangeRelayStatus");
Debug.LogWarning("EOS Transport Channel[0] is not ReliableOrdered, Mirror expects Channel 0 to be ReliableOrdered, only change this if you know what you are doing.");
}
public override void ClientEarlyUpdate()
if (Channels[1] != PacketReliability.UnreliableUnordered)
{
EOSSDKComponent.Tick();
if (activeNode != null)
{
ignoreCachedMessagesTimer += Time.deltaTime;
if (ignoreCachedMessagesTimer <= ignoreCachedMessagesAtStartUpInSeconds)
{
activeNode.ignoreAllMessages = true;
}
else
{
activeNode.ignoreAllMessages = false;
if (client != null && !client.isConnecting)
{
if (EOSSDKComponent.Initialized)
{
client.Connect(client.hostAddress);
}
else
{
Debug.LogError("EOS not initialized");
client.EosNotInitialized();
}
client.isConnecting = true;
}
}
}
if (enabled)
{
activeNode?.ReceiveData();
}
Debug.LogWarning("EOS Transport Channel[1] is not UnreliableUnordered, Mirror expects Channel 1 to be UnreliableUnordered, only change this if you know what you are doing.");
}
public override void ClientLateUpdate() { }
StartCoroutine("FetchEpicAccountId");
StartCoroutine("ChangeRelayStatus");
}
public override void ServerEarlyUpdate()
public override void ClientEarlyUpdate()
{
EOSSDKComponent.Tick();
if (activeNode != null)
{
EOSSDKComponent.Tick();
ignoreCachedMessagesTimer += Time.deltaTime;
if (activeNode != null)
if (ignoreCachedMessagesTimer <= ignoreCachedMessagesAtStartUpInSeconds)
{
ignoreCachedMessagesTimer += Time.deltaTime;
if (ignoreCachedMessagesTimer <= ignoreCachedMessagesAtStartUpInSeconds)
{
activeNode.ignoreAllMessages = true;
}
else
{
activeNode.ignoreAllMessages = false;
}
}
if (enabled)
{
activeNode?.ReceiveData();
}
}
public override void ServerLateUpdate() { }
public override bool ClientConnected() => ClientActive() && client.Connected;
public override void ClientConnect(string address)
{
if (!EOSSDKComponent.Initialized)
{
Debug.LogError("EOS not initialized. Client could not be started.");
OnClientDisconnected.Invoke();
return;
}
StartCoroutine("FetchEpicAccountId");
if (ServerActive())
{
Debug.LogError("Transport already running as server!");
return;
}
if (!ClientActive() || client.Error)
{
Debug.Log($"Starting client, target address {address}.");
client = Client.CreateClient(this, address);
activeNode = client;
if (EOSSDKComponent.CollectPlayerMetrics)
{
// Start Metrics colletion session
var sessionOptions = new BeginPlayerSessionOptions();
sessionOptions.AccountId = EOSSDKComponent.LocalUserAccountId;
sessionOptions.ControllerType = UserControllerType.Unknown;
sessionOptions.DisplayName = EOSSDKComponent.DisplayName;
sessionOptions.GameSessionId = null;
sessionOptions.ServerIp = null;
var result = EOSSDKComponent.GetMetricsInterface().BeginPlayerSession(sessionOptions);
if (result == Result.Success)
{
Debug.Log("Started Metric Session");
}
}
activeNode.ignoreAllMessages = true;
}
else
{
Debug.LogError("Client already running!");
}
}
activeNode.ignoreAllMessages = false;
public override void ClientConnect(Uri uri)
{
if (uri.Scheme != EPIC_SCHEME)
{
throw new ArgumentException($"Invalid url {uri}, use {EPIC_SCHEME}://EpicAccountId instead", nameof(uri));
}
ClientConnect(uri.Host);
}
public override void ClientSend(ArraySegment<byte> segment, int channelId)
{
Send(channelId, segment);
}
public override void ClientDisconnect()
{
if (ClientActive())
{
Shutdown();
}
}
public bool ClientActive() => client != null;
public override bool ServerActive() => server != null;
public override void ServerStart()
{
if (!EOSSDKComponent.Initialized)
{
Debug.LogError("EOS not initialized. Server could not be started.");
return;
}
StartCoroutine("FetchEpicAccountId");
if (ClientActive())
{
Debug.LogError("Transport already running as client!");
return;
}
if (!ServerActive())
{
Debug.Log("Starting server.");
server = Server.CreateServer(this, NetworkManager.singleton.maxConnections);
activeNode = server;
if (EOSSDKComponent.CollectPlayerMetrics)
if (client != null && !client.isConnecting)
{
// Start Metrics colletion session
var sessionOptions = new BeginPlayerSessionOptions();
sessionOptions.AccountId = EOSSDKComponent.LocalUserAccountId;
sessionOptions.ControllerType = UserControllerType.Unknown;
sessionOptions.DisplayName = EOSSDKComponent.DisplayName;
sessionOptions.GameSessionId = null;
sessionOptions.ServerIp = null;
var result = EOSSDKComponent.GetMetricsInterface().BeginPlayerSession(sessionOptions);
if (result == Result.Success)
if (EOSSDKComponent.Initialized)
{
Debug.Log("Started Metric Session");
client.Connect(client.hostAddress);
}
else
{
Debug.LogError("EOS not initialized");
client.EosNotInitialized();
}
client.isConnecting = true;
}
}
}
if (enabled)
{
activeNode?.ReceiveData();
}
}
public override void ClientLateUpdate() { }
public override void ServerEarlyUpdate()
{
EOSSDKComponent.Tick();
if (activeNode != null)
{
ignoreCachedMessagesTimer += Time.deltaTime;
if (ignoreCachedMessagesTimer <= ignoreCachedMessagesAtStartUpInSeconds)
{
activeNode.ignoreAllMessages = true;
}
else
{
Debug.LogError("Server already started!");
activeNode.ignoreAllMessages = false;
}
}
public override Uri ServerUri()
if (enabled)
{
var epicBuilder = new UriBuilder
{
Scheme = EPIC_SCHEME,
Host = EOSSDKComponent.LocalUserProductIdString
};
activeNode?.ReceiveData();
}
}
return epicBuilder.Uri;
public override void ServerLateUpdate() { }
public override bool ClientConnected() => ClientActive() && client.Connected;
public override void ClientConnect(string address)
{
if (!EOSSDKComponent.Initialized)
{
Debug.LogError("EOS not initialized. Client could not be started.");
OnClientDisconnected.Invoke();
return;
}
public override void ServerSend(int connectionId, ArraySegment<byte> segment, int channelId)
StartCoroutine("FetchEpicAccountId");
if (ServerActive())
{
if (ServerActive())
{
Send(channelId, segment, connectionId);
}
Debug.LogError("Transport already running as server!");
return;
}
public override void ServerDisconnect(int connectionId) => server.Disconnect(connectionId);
public override string ServerGetClientAddress(int connectionId) => ServerActive() ? server.ServerGetClientAddress(connectionId) : string.Empty;
public override void ServerStop()
if (!ClientActive() || client.Error)
{
if (ServerActive())
{
Shutdown();
}
}
Debug.Log($"Starting client, target address {address}.");
private void Send(int channelId, ArraySegment<byte> segment, int connectionId = int.MinValue)
{
var packets = GetPacketArray(channelId, segment);
client = Client.CreateClient(this, address);
activeNode = client;
for (var i = 0; i < packets.Length; i++)
{
if (connectionId == int.MinValue)
{
client.Send(packets[i].ToBytes(), channelId);
}
else
{
server.SendAll(connectionId, packets[i].ToBytes(), channelId);
}
}
packetId++;
}
private Packet[] GetPacketArray(int channelId, ArraySegment<byte> segment)
{
var packetCount = Mathf.CeilToInt((float)segment.Count / (float)GetMaxSinglePacketSize(channelId));
var packets = new Packet[packetCount];
for (var i = 0; i < segment.Count; i += GetMaxSinglePacketSize(channelId))
{
var fragment = i / GetMaxSinglePacketSize(channelId);
packets[fragment] = new Packet();
packets[fragment].id = packetId;
packets[fragment].fragment = fragment;
packets[fragment].moreFragments = segment.Count - i > GetMaxSinglePacketSize(channelId);
packets[fragment].data = new byte[segment.Count - i > GetMaxSinglePacketSize(channelId) ? GetMaxSinglePacketSize(channelId) : segment.Count - i];
Array.Copy(segment.Array, i, packets[fragment].data, 0, packets[fragment].data.Length);
}
return packets;
}
public override void Shutdown()
{
if (EOSSDKComponent.CollectPlayerMetrics)
{
// Stop Metrics collection session
var endSessionOptions = new EndPlayerSessionOptions();
endSessionOptions.AccountId = EOSSDKComponent.LocalUserAccountId;
var result = EOSSDKComponent.GetMetricsInterface().EndPlayerSession(endSessionOptions);
// Start Metrics colletion session
var sessionOptions = new BeginPlayerSessionOptions();
sessionOptions.AccountId = EOSSDKComponent.LocalUserAccountId;
sessionOptions.ControllerType = UserControllerType.Unknown;
sessionOptions.DisplayName = EOSSDKComponent.DisplayName;
sessionOptions.GameSessionId = null;
sessionOptions.ServerIp = null;
var result = EOSSDKComponent.GetMetricsInterface().BeginPlayerSession(sessionOptions);
if (result == Result.Success)
{
Debug.LogError("Stopped Metric Session");
Debug.Log("Started Metric Session");
}
}
}
else
{
Debug.LogError("Client already running!");
}
}
server?.Shutdown();
client?.Disconnect();
server = null;
client = null;
activeNode = null;
Debug.Log("Transport shut down.");
public override void ClientConnect(Uri uri)
{
if (uri.Scheme != EPIC_SCHEME)
{
throw new ArgumentException($"Invalid url {uri}, use {EPIC_SCHEME}://EpicAccountId instead", nameof(uri));
}
public int GetMaxSinglePacketSize(int channelId) => P2PInterface.MaxPacketSize - 10; // 1159 bytes, we need to remove 10 bytes for the packet header (id (4 bytes) + fragment (4 bytes) + more fragments (1 byte))
ClientConnect(uri.Host);
}
public override int GetMaxPacketSize(int channelId) => P2PInterface.MaxPacketSize * maxFragments;
public override void ClientSend(ArraySegment<byte> segment, int channelId)
{
Send(channelId, segment);
}
public override int GetBatchThreshold(int channelId) => P2PInterface.MaxPacketSize; // Use P2PInterface.MaxPacketSize as everything above will get fragmentated and will be counter effective to batching
public override bool Available()
public override void ClientDisconnect()
{
if (ClientActive())
{
try
Shutdown();
}
}
public bool ClientActive() => client != null;
public override bool ServerActive() => server != null;
public override void ServerStart()
{
if (!EOSSDKComponent.Initialized)
{
Debug.LogError("EOS not initialized. Server could not be started.");
return;
}
StartCoroutine("FetchEpicAccountId");
if (ClientActive())
{
Debug.LogError("Transport already running as client!");
return;
}
if (!ServerActive())
{
Debug.Log("Starting server.");
server = Server.CreateServer(this, NetworkManager.singleton.maxConnections);
activeNode = server;
if (EOSSDKComponent.CollectPlayerMetrics)
{
return EOSSDKComponent.Initialized;
// Start Metrics colletion session
var sessionOptions = new BeginPlayerSessionOptions();
sessionOptions.AccountId = EOSSDKComponent.LocalUserAccountId;
sessionOptions.ControllerType = UserControllerType.Unknown;
sessionOptions.DisplayName = EOSSDKComponent.DisplayName;
sessionOptions.GameSessionId = null;
sessionOptions.ServerIp = null;
var result = EOSSDKComponent.GetMetricsInterface().BeginPlayerSession(sessionOptions);
if (result == Result.Success)
{
Debug.Log("Started Metric Session");
}
}
catch
}
else
{
Debug.LogError("Server already started!");
}
}
public override Uri ServerUri()
{
var epicBuilder = new UriBuilder
{
Scheme = EPIC_SCHEME,
Host = EOSSDKComponent.LocalUserProductIdString
};
return epicBuilder.Uri;
}
public override void ServerSend(int connectionId, ArraySegment<byte> segment, int channelId)
{
if (ServerActive())
{
Send(channelId, segment, connectionId);
}
}
public override void ServerDisconnect(int connectionId) => server.Disconnect(connectionId);
public override string ServerGetClientAddress(int connectionId) => ServerActive() ? server.ServerGetClientAddress(connectionId) : string.Empty;
public override void ServerStop()
{
if (ServerActive())
{
Shutdown();
}
}
private void Send(int channelId, ArraySegment<byte> segment, int connectionId = int.MinValue)
{
var packets = GetPacketArray(channelId, segment);
for (var i = 0; i < packets.Length; i++)
{
if (connectionId == int.MinValue)
{
return false;
client.Send(packets[i].ToBytes(), channelId);
}
else
{
server.SendAll(connectionId, packets[i].ToBytes(), channelId);
}
}
private IEnumerator FetchEpicAccountId()
{
while (!EOSSDKComponent.Initialized)
{
yield return null;
}
packetId++;
}
productUserId = EOSSDKComponent.LocalUserProductId;
private Packet[] GetPacketArray(int channelId, ArraySegment<byte> segment)
{
var packetCount = Mathf.CeilToInt((float)segment.Count / (float)GetMaxSinglePacketSize(channelId));
var packets = new Packet[packetCount];
for (var i = 0; i < segment.Count; i += GetMaxSinglePacketSize(channelId))
{
var fragment = i / GetMaxSinglePacketSize(channelId);
packets[fragment] = new Packet();
packets[fragment].id = packetId;
packets[fragment].fragment = fragment;
packets[fragment].moreFragments = segment.Count - i > GetMaxSinglePacketSize(channelId);
packets[fragment].data = new byte[segment.Count - i > GetMaxSinglePacketSize(channelId) ? GetMaxSinglePacketSize(channelId) : segment.Count - i];
Array.Copy(segment.Array, i, packets[fragment].data, 0, packets[fragment].data.Length);
}
private IEnumerator ChangeRelayStatus()
return packets;
}
public override void Shutdown()
{
if (EOSSDKComponent.CollectPlayerMetrics)
{
while (!EOSSDKComponent.Initialized)
// Stop Metrics collection session
var endSessionOptions = new EndPlayerSessionOptions();
endSessionOptions.AccountId = EOSSDKComponent.LocalUserAccountId;
var result = EOSSDKComponent.GetMetricsInterface().EndPlayerSession(endSessionOptions);
if (result == Result.Success)
{
yield return null;
Debug.LogError("Stopped Metric Session");
}
var setRelayControlOptions = new SetRelayControlOptions();
setRelayControlOptions.RelayControl = relayControl;
EOSSDKComponent.GetP2PInterface().SetRelayControl(setRelayControlOptions);
}
public void ResetIgnoreMessagesAtStartUpTimer()
server?.Shutdown();
client?.Disconnect();
server = null;
client = null;
activeNode = null;
Debug.Log("Transport shut down.");
}
public int GetMaxSinglePacketSize(int channelId) => P2PInterface.MaxPacketSize - 10; // 1159 bytes, we need to remove 10 bytes for the packet header (id (4 bytes) + fragment (4 bytes) + more fragments (1 byte))
public override int GetMaxPacketSize(int channelId) => P2PInterface.MaxPacketSize * maxFragments;
public override int GetBatchThreshold(int channelId) => P2PInterface.MaxPacketSize; // Use P2PInterface.MaxPacketSize as everything above will get fragmentated and will be counter effective to batching
public override bool Available()
{
try
{
ignoreCachedMessagesTimer = 0;
return EOSSDKComponent.Initialized;
}
catch
{
return false;
}
}
private IEnumerator FetchEpicAccountId()
{
while (!EOSSDKComponent.Initialized)
{
yield return null;
}
private void OnDestroy()
productUserId = EOSSDKComponent.LocalUserProductId;
}
private IEnumerator ChangeRelayStatus()
{
while (!EOSSDKComponent.Initialized)
{
if (activeNode != null)
{
Shutdown();
}
yield return null;
}
var setRelayControlOptions = new SetRelayControlOptions();
setRelayControlOptions.RelayControl = relayControl;
EOSSDKComponent.GetP2PInterface().SetRelayControl(setRelayControlOptions);
}
public void ResetIgnoreMessagesAtStartUpTimer()
{
ignoreCachedMessagesTimer = 0;
}
private void OnDestroy()
{
if (activeNode != null)
{
Shutdown();
}
}
}

View File

@ -2,30 +2,29 @@
using System;
using UnityEngine;
namespace EpicTransport
namespace EpicTransport;
public static class Logger
{
public static class Logger
public static void EpicDebugLog(LogMessage message)
{
public static void EpicDebugLog(LogMessage message)
switch (message.Level)
{
switch (message.Level)
{
case LogLevel.Info:
Debug.Log($"Epic Manager: Category - {message.Category} Message - {message.Message}");
break;
case LogLevel.Error:
Debug.LogError($"Epic Manager: Category - {message.Category} Message - {message.Message}");
break;
case LogLevel.Warning:
Debug.LogWarning($"Epic Manager: Category - {message.Category} Message - {message.Message}");
break;
case LogLevel.Fatal:
Debug.LogException(new Exception($"Epic Manager: Category - {message.Category} Message - {message.Message}"));
break;
default:
Debug.Log($"Epic Manager: Unknown log processing. Category - {message.Category} Message - {message.Message}");
break;
}
case LogLevel.Info:
Debug.Log($"Epic Manager: Category - {message.Category} Message - {message.Message}");
break;
case LogLevel.Error:
Debug.LogError($"Epic Manager: Category - {message.Category} Message - {message.Message}");
break;
case LogLevel.Warning:
Debug.LogWarning($"Epic Manager: Category - {message.Category} Message - {message.Message}");
break;
case LogLevel.Fatal:
Debug.LogException(new Exception($"Epic Manager: Category - {message.Category} Message - {message.Message}"));
break;
default:
Debug.Log($"Epic Manager: Unknown log processing. Category - {message.Category} Message - {message.Message}");
break;
}
}
}

View File

@ -1,51 +1,50 @@
using System;
namespace EpicTransport
namespace EpicTransport;
public struct Packet
{
public struct Packet
public const int headerSize = sizeof(uint) + sizeof(uint) + 1;
public int size => headerSize + data.Length;
// header
public int id;
public int fragment;
public bool moreFragments;
// body
public byte[] data;
public byte[] ToBytes()
{
public const int headerSize = sizeof(uint) + sizeof(uint) + 1;
public int size => headerSize + data.Length;
var array = new byte[size];
// header
public int id;
public int fragment;
public bool moreFragments;
// Copy id
array[0] = (byte)id;
array[1] = (byte)(id >> 8);
array[2] = (byte)(id >> 0x10);
array[3] = (byte)(id >> 0x18);
// body
public byte[] data;
// Copy fragment
array[4] = (byte)fragment;
array[5] = (byte)(fragment >> 8);
array[6] = (byte)(fragment >> 0x10);
array[7] = (byte)(fragment >> 0x18);
public byte[] ToBytes()
{
var array = new byte[size];
array[8] = moreFragments ? (byte)1 : (byte)0;
// Copy id
array[0] = (byte)id;
array[1] = (byte)(id >> 8);
array[2] = (byte)(id >> 0x10);
array[3] = (byte)(id >> 0x18);
Array.Copy(data, 0, array, 9, data.Length);
// Copy fragment
array[4] = (byte)fragment;
array[5] = (byte)(fragment >> 8);
array[6] = (byte)(fragment >> 0x10);
array[7] = (byte)(fragment >> 0x18);
return array;
}
array[8] = moreFragments ? (byte)1 : (byte)0;
public void FromBytes(byte[] array)
{
id = BitConverter.ToInt32(array, 0);
fragment = BitConverter.ToInt32(array, 4);
moreFragments = array[8] == 1;
Array.Copy(data, 0, array, 9, data.Length);
return array;
}
public void FromBytes(byte[] array)
{
id = BitConverter.ToInt32(array, 0);
fragment = BitConverter.ToInt32(array, 4);
moreFragments = array[8] == 1;
data = new byte[array.Length - 9];
Array.Copy(array, 9, data, 0, data.Length);
}
data = new byte[array.Length - 9];
Array.Copy(array, 9, data, 0, data.Length);
}
}

View File

@ -4,218 +4,217 @@ using System;
using System.Collections.Generic;
using UnityEngine;
namespace EpicTransport
namespace EpicTransport;
public class Server : Common
{
public class Server : Common
private event Action<int> OnConnected;
private event Action<int, byte[], int> OnReceivedData;
private event Action<int> OnDisconnected;
private event Action<int, Exception> OnReceivedError;
private BidirectionalDictionary<ProductUserId, int> epicToMirrorIds;
private Dictionary<ProductUserId, SocketId> epicToSocketIds;
private int maxConnections;
private int nextConnectionID;
public static Server CreateServer(EosTransport transport, int maxConnections)
{
private event Action<int> OnConnected;
private event Action<int, byte[], int> OnReceivedData;
private event Action<int> OnDisconnected;
private event Action<int, Exception> OnReceivedError;
var s = new Server(transport, maxConnections);
private BidirectionalDictionary<ProductUserId, int> epicToMirrorIds;
private Dictionary<ProductUserId, SocketId> epicToSocketIds;
private int maxConnections;
private int nextConnectionID;
s.OnConnected += (id) => transport.OnServerConnected.Invoke(id);
s.OnDisconnected += (id) => transport.OnServerDisconnected.Invoke(id);
s.OnReceivedData += (id, data, channel) => transport.OnServerDataReceived.Invoke(id, new ArraySegment<byte>(data), channel);
s.OnReceivedError += (id, exception) => transport.OnServerError.Invoke(id, exception);
public static Server CreateServer(EosTransport transport, int maxConnections)
if (!EOSSDKComponent.Initialized)
{
var s = new Server(transport, maxConnections);
s.OnConnected += (id) => transport.OnServerConnected.Invoke(id);
s.OnDisconnected += (id) => transport.OnServerDisconnected.Invoke(id);
s.OnReceivedData += (id, data, channel) => transport.OnServerDataReceived.Invoke(id, new ArraySegment<byte>(data), channel);
s.OnReceivedError += (id, exception) => transport.OnServerError.Invoke(id, exception);
if (!EOSSDKComponent.Initialized)
{
Debug.LogError("EOS not initialized.");
}
return s;
Debug.LogError("EOS not initialized.");
}
private Server(EosTransport transport, int maxConnections) : base(transport)
return s;
}
private Server(EosTransport transport, int maxConnections) : base(transport)
{
this.maxConnections = maxConnections;
epicToMirrorIds = new BidirectionalDictionary<ProductUserId, int>();
epicToSocketIds = new Dictionary<ProductUserId, SocketId>();
nextConnectionID = 1;
}
protected override void OnNewConnection(OnIncomingConnectionRequestInfo result)
{
if (ignoreAllMessages)
{
this.maxConnections = maxConnections;
epicToMirrorIds = new BidirectionalDictionary<ProductUserId, int>();
epicToSocketIds = new Dictionary<ProductUserId, SocketId>();
nextConnectionID = 1;
return;
}
protected override void OnNewConnection(OnIncomingConnectionRequestInfo result)
if (deadSockets.Contains(result.SocketId.SocketName))
{
if (ignoreAllMessages)
{
return;
}
Debug.LogError("Received incoming connection request from dead socket");
return;
}
if (deadSockets.Contains(result.SocketId.SocketName))
EOSSDKComponent.GetP2PInterface().AcceptConnection(
new AcceptConnectionOptions()
{
Debug.LogError("Received incoming connection request from dead socket");
return;
}
LocalUserId = EOSSDKComponent.LocalUserProductId,
RemoteUserId = result.RemoteUserId,
SocketId = result.SocketId
});
}
EOSSDKComponent.GetP2PInterface().AcceptConnection(
new AcceptConnectionOptions()
protected override void OnReceiveInternalData(InternalMessages type, ProductUserId clientUserId, SocketId socketId)
{
if (ignoreAllMessages)
{
return;
}
switch (type)
{
case InternalMessages.CONNECT:
if (epicToMirrorIds.Count >= maxConnections)
{
LocalUserId = EOSSDKComponent.LocalUserProductId,
RemoteUserId = result.RemoteUserId,
SocketId = result.SocketId
});
}
Debug.LogError("Reached max connections");
//CloseP2PSessionWithUser(clientUserId, socketId);
SendInternal(clientUserId, socketId, InternalMessages.DISCONNECT);
return;
}
protected override void OnReceiveInternalData(InternalMessages type, ProductUserId clientUserId, SocketId socketId)
{
if (ignoreAllMessages)
{
return;
}
SendInternal(clientUserId, socketId, InternalMessages.ACCEPT_CONNECT);
switch (type)
{
case InternalMessages.CONNECT:
if (epicToMirrorIds.Count >= maxConnections)
{
Debug.LogError("Reached max connections");
//CloseP2PSessionWithUser(clientUserId, socketId);
SendInternal(clientUserId, socketId, InternalMessages.DISCONNECT);
return;
}
var connectionId = nextConnectionID++;
epicToMirrorIds.Add(clientUserId, connectionId);
epicToSocketIds.Add(clientUserId, socketId);
OnConnected.Invoke(connectionId);
SendInternal(clientUserId, socketId, InternalMessages.ACCEPT_CONNECT);
string clientUserIdString;
clientUserId.ToString(out clientUserIdString);
Debug.Log($"Client with Product User ID {clientUserIdString} connected. Assigning connection id {connectionId}");
break;
case InternalMessages.DISCONNECT:
if (epicToMirrorIds.TryGetValue(clientUserId, out var connId))
{
OnDisconnected.Invoke(connId);
//CloseP2PSessionWithUser(clientUserId, socketId);
epicToMirrorIds.Remove(clientUserId);
epicToSocketIds.Remove(clientUserId);
Debug.Log($"Client with Product User ID {clientUserId} disconnected.");
}
else
{
OnReceivedError.Invoke(-1, new Exception("ERROR Unknown Product User ID"));
}
var connectionId = nextConnectionID++;
epicToMirrorIds.Add(clientUserId, connectionId);
epicToSocketIds.Add(clientUserId, socketId);
OnConnected.Invoke(connectionId);
string clientUserIdString;
clientUserId.ToString(out clientUserIdString);
Debug.Log($"Client with Product User ID {clientUserIdString} connected. Assigning connection id {connectionId}");
break;
case InternalMessages.DISCONNECT:
if (epicToMirrorIds.TryGetValue(clientUserId, out var connId))
{
OnDisconnected.Invoke(connId);
//CloseP2PSessionWithUser(clientUserId, socketId);
epicToMirrorIds.Remove(clientUserId);
epicToSocketIds.Remove(clientUserId);
Debug.Log($"Client with Product User ID {clientUserId} disconnected.");
}
else
{
OnReceivedError.Invoke(-1, new Exception("ERROR Unknown Product User ID"));
}
break;
default:
Debug.Log("Received unknown message type");
break;
}
}
protected override void OnReceiveData(byte[] data, ProductUserId clientUserId, int channel)
{
if (ignoreAllMessages)
{
return;
}
if (epicToMirrorIds.TryGetValue(clientUserId, out var connectionId))
{
OnReceivedData.Invoke(connectionId, data, channel);
}
else
{
SocketId socketId;
epicToSocketIds.TryGetValue(clientUserId, out socketId);
CloseP2PSessionWithUser(clientUserId, socketId);
string productId;
clientUserId.ToString(out productId);
Debug.LogError("Data received from epic client thats not known " + productId);
OnReceivedError.Invoke(-1, new Exception("ERROR Unknown product ID"));
}
}
public void Disconnect(int connectionId)
{
if (epicToMirrorIds.TryGetValue(connectionId, out var userId))
{
SocketId socketId;
epicToSocketIds.TryGetValue(userId, out socketId);
SendInternal(userId, socketId, InternalMessages.DISCONNECT);
epicToMirrorIds.Remove(userId);
epicToSocketIds.Remove(userId);
}
else
{
Debug.LogWarning("Trying to disconnect unknown connection id: " + connectionId);
}
}
public void Shutdown()
{
foreach (KeyValuePair<ProductUserId, int> client in epicToMirrorIds)
{
Disconnect(client.Value);
SocketId socketId;
epicToSocketIds.TryGetValue(client.Key, out socketId);
WaitForClose(client.Key, socketId);
}
ignoreAllMessages = true;
ReceiveData();
Dispose();
}
public void SendAll(int connectionId, byte[] data, int channelId)
{
if (epicToMirrorIds.TryGetValue(connectionId, out var userId))
{
SocketId socketId;
epicToSocketIds.TryGetValue(userId, out socketId);
Send(userId, socketId, data, (byte)channelId);
}
else
{
Debug.LogError("Trying to send on unknown connection: " + connectionId);
OnReceivedError.Invoke(connectionId, new Exception("ERROR Unknown Connection"));
}
}
public string ServerGetClientAddress(int connectionId)
{
if (epicToMirrorIds.TryGetValue(connectionId, out var userId))
{
string userIdString;
userId.ToString(out userIdString);
return userIdString;
}
else
{
Debug.LogError("Trying to get info on unknown connection: " + connectionId);
OnReceivedError.Invoke(connectionId, new Exception("ERROR Unknown Connection"));
return string.Empty;
}
}
protected override void OnConnectionFailed(ProductUserId remoteId)
{
if (ignoreAllMessages)
{
return;
}
var connectionId = epicToMirrorIds.TryGetValue(remoteId, out var connId) ? connId : nextConnectionID++;
OnDisconnected.Invoke(connectionId);
Debug.LogError("Connection Failed, removing user");
epicToMirrorIds.Remove(remoteId);
epicToSocketIds.Remove(remoteId);
break;
default:
Debug.Log("Received unknown message type");
break;
}
}
protected override void OnReceiveData(byte[] data, ProductUserId clientUserId, int channel)
{
if (ignoreAllMessages)
{
return;
}
if (epicToMirrorIds.TryGetValue(clientUserId, out var connectionId))
{
OnReceivedData.Invoke(connectionId, data, channel);
}
else
{
SocketId socketId;
epicToSocketIds.TryGetValue(clientUserId, out socketId);
CloseP2PSessionWithUser(clientUserId, socketId);
string productId;
clientUserId.ToString(out productId);
Debug.LogError("Data received from epic client thats not known " + productId);
OnReceivedError.Invoke(-1, new Exception("ERROR Unknown product ID"));
}
}
public void Disconnect(int connectionId)
{
if (epicToMirrorIds.TryGetValue(connectionId, out var userId))
{
SocketId socketId;
epicToSocketIds.TryGetValue(userId, out socketId);
SendInternal(userId, socketId, InternalMessages.DISCONNECT);
epicToMirrorIds.Remove(userId);
epicToSocketIds.Remove(userId);
}
else
{
Debug.LogWarning("Trying to disconnect unknown connection id: " + connectionId);
}
}
public void Shutdown()
{
foreach (KeyValuePair<ProductUserId, int> client in epicToMirrorIds)
{
Disconnect(client.Value);
SocketId socketId;
epicToSocketIds.TryGetValue(client.Key, out socketId);
WaitForClose(client.Key, socketId);
}
ignoreAllMessages = true;
ReceiveData();
Dispose();
}
public void SendAll(int connectionId, byte[] data, int channelId)
{
if (epicToMirrorIds.TryGetValue(connectionId, out var userId))
{
SocketId socketId;
epicToSocketIds.TryGetValue(userId, out socketId);
Send(userId, socketId, data, (byte)channelId);
}
else
{
Debug.LogError("Trying to send on unknown connection: " + connectionId);
OnReceivedError.Invoke(connectionId, new Exception("ERROR Unknown Connection"));
}
}
public string ServerGetClientAddress(int connectionId)
{
if (epicToMirrorIds.TryGetValue(connectionId, out var userId))
{
string userIdString;
userId.ToString(out userIdString);
return userIdString;
}
else
{
Debug.LogError("Trying to get info on unknown connection: " + connectionId);
OnReceivedError.Invoke(connectionId, new Exception("ERROR Unknown Connection"));
return string.Empty;
}
}
protected override void OnConnectionFailed(ProductUserId remoteId)
{
if (ignoreAllMessages)
{
return;
}
var connectionId = epicToMirrorIds.TryGetValue(remoteId, out var connId) ? connId : nextConnectionID++;
OnDisconnected.Invoke(connectionId);
Debug.LogError("Connection Failed, removing user");
epicToMirrorIds.Remove(remoteId);
epicToSocketIds.Remove(remoteId);
}
}

View File

@ -1,53 +1,52 @@
using Epic.OnlineServices;
using Epic.OnlineServices.Ecom;
namespace EpicRerouter.ExeSide
namespace EpicRerouter.ExeSide;
public static class EpicEntitlementRetriever
{
public static class EpicEntitlementRetriever
private const string _eosDlcItemID = "49a9ac61fe464cbf8c8c73f46b3f1133";
private static EcomInterface _ecomInterface;
private static OwnershipStatus _epicDlcOwnershipStatus;
private static bool _epicResultReceived;
public static void Init() =>
EpicPlatformManager.OnAuthSuccess += EOSQueryOwnership;
public static void Uninit() =>
EpicPlatformManager.OnAuthSuccess -= EOSQueryOwnership;
public static EntitlementsManager.AsyncOwnershipStatus GetOwnershipStatus()
{
private const string _eosDlcItemID = "49a9ac61fe464cbf8c8c73f46b3f1133";
private static EcomInterface _ecomInterface;
private static OwnershipStatus _epicDlcOwnershipStatus;
private static bool _epicResultReceived;
public static void Init() =>
EpicPlatformManager.OnAuthSuccess += EOSQueryOwnership;
public static void Uninit() =>
EpicPlatformManager.OnAuthSuccess -= EOSQueryOwnership;
public static EntitlementsManager.AsyncOwnershipStatus GetOwnershipStatus()
if (!_epicResultReceived)
{
if (!_epicResultReceived)
{
return EntitlementsManager.AsyncOwnershipStatus.NotReady;
}
return _epicDlcOwnershipStatus == OwnershipStatus.Owned ?
EntitlementsManager.AsyncOwnershipStatus.Owned : EntitlementsManager.AsyncOwnershipStatus.NotOwned;
return EntitlementsManager.AsyncOwnershipStatus.NotReady;
}
private static void EOSQueryOwnership()
{
Program.Log("[EOS] querying DLC ownership");
_ecomInterface = EpicPlatformManager.PlatformInterface.GetEcomInterface();
var queryOwnershipOptions = new QueryOwnershipOptions
{
LocalUserId = EpicPlatformManager.LocalUserId,
CatalogItemIds = new[] { _eosDlcItemID }
};
_ecomInterface.QueryOwnership(queryOwnershipOptions, null, OnEOSQueryOwnershipComplete);
}
return _epicDlcOwnershipStatus == OwnershipStatus.Owned ?
EntitlementsManager.AsyncOwnershipStatus.Owned : EntitlementsManager.AsyncOwnershipStatus.NotOwned;
}
private static void OnEOSQueryOwnershipComplete(QueryOwnershipCallbackInfo data)
private static void EOSQueryOwnership()
{
Program.Log("[EOS] querying DLC ownership");
_ecomInterface = EpicPlatformManager.PlatformInterface.GetEcomInterface();
var queryOwnershipOptions = new QueryOwnershipOptions
{
if (data.ResultCode == Result.Success)
{
_epicDlcOwnershipStatus = data.ItemOwnership[0].OwnershipStatus;
_epicResultReceived = true;
Program.Log($"[EOS] Query DLC ownership complete: {_epicDlcOwnershipStatus}");
}
LocalUserId = EpicPlatformManager.LocalUserId,
CatalogItemIds = new[] { _eosDlcItemID }
};
_ecomInterface.QueryOwnership(queryOwnershipOptions, null, OnEOSQueryOwnershipComplete);
}
private static void OnEOSQueryOwnershipComplete(QueryOwnershipCallbackInfo data)
{
if (data.ResultCode == Result.Success)
{
_epicDlcOwnershipStatus = data.ItemOwnership[0].OwnershipStatus;
_epicResultReceived = true;
Program.Log($"[EOS] Query DLC ownership complete: {_epicDlcOwnershipStatus}");
}
}
}

View File

@ -4,138 +4,137 @@ using Epic.OnlineServices.Platform;
using System;
using System.Threading;
namespace EpicRerouter.ExeSide
namespace EpicRerouter.ExeSide;
public static class EpicPlatformManager
{
public static class EpicPlatformManager
private const string _eosProductID = "prod-starfish";
private const string _eosSandboxID = "starfish";
private const string _eosDeploymentID = "e176ecc84fbc4dd8934664684f44dc71";
private const string _eosClientID = "5c553c6accee4111bc8ea3a3ae52229b";
private const string _eosClientSecret = "k87Nfp75BzPref4nJFnnbNjYXQQR";
private const float _tickInterval = 0.1f;
public static PlatformInterface PlatformInterface;
public static EpicAccountId LocalUserId;
public static OWEvent OnAuthSuccess = new(1);
public static void Init()
{
private const string _eosProductID = "prod-starfish";
private const string _eosSandboxID = "starfish";
private const string _eosDeploymentID = "e176ecc84fbc4dd8934664684f44dc71";
private const string _eosClientID = "5c553c6accee4111bc8ea3a3ae52229b";
private const string _eosClientSecret = "k87Nfp75BzPref4nJFnnbNjYXQQR";
private const float _tickInterval = 0.1f;
public static PlatformInterface PlatformInterface;
public static EpicAccountId LocalUserId;
public static OWEvent OnAuthSuccess = new(1);
public static void Init()
if (PlatformInterface == null)
{
if (PlatformInterface == null)
try
{
try
InitPlatform();
}
catch (EOSInitializeException ex)
{
if (ex.Result == Result.AlreadyConfigured)
{
InitPlatform();
}
catch (EOSInitializeException ex)
{
if (ex.Result == Result.AlreadyConfigured)
{
throw new Exception("[EOS] platform already configured!");
}
throw new Exception("[EOS] platform already configured!");
}
}
Auth();
}
public static void Tick()
Auth();
}
public static void Tick()
{
PlatformInterface.Tick();
Thread.Sleep(TimeSpan.FromSeconds(_tickInterval));
}
public static void Uninit()
{
PlatformInterface.Release();
PlatformInterface = null;
PlatformInterface.Shutdown();
}
private static void InitPlatform()
{
var result = PlatformInterface.Initialize(new InitializeOptions
{
PlatformInterface.Tick();
Thread.Sleep(TimeSpan.FromSeconds(_tickInterval));
ProductName = Program.ProductName,
ProductVersion = Program.Version
});
if (result != Result.Success)
{
throw new EOSInitializeException("Failed to initialize Epic Online Services platform: ", result);
}
public static void Uninit()
var options = new Options
{
PlatformInterface.Release();
PlatformInterface = null;
PlatformInterface.Shutdown();
}
private static void InitPlatform()
{
var result = PlatformInterface.Initialize(new InitializeOptions
ProductId = _eosProductID,
SandboxId = _eosSandboxID,
ClientCredentials = new ClientCredentials
{
ProductName = Program.ProductName,
ProductVersion = Program.Version
});
if (result != Result.Success)
ClientId = _eosClientID,
ClientSecret = _eosClientSecret
},
DeploymentId = _eosDeploymentID
};
PlatformInterface = PlatformInterface.Create(options);
Program.Log("[EOS] Platform interface has been created");
}
private static void Auth()
{
Program.Log("[EOS] Authenticating...");
var loginOptions = new LoginOptions
{
Credentials = new Credentials
{
throw new EOSInitializeException("Failed to initialize Epic Online Services platform: ", result);
Type = LoginCredentialType.ExchangeCode,
Id = null,
Token = GetPasswordFromCommandLine()
},
ScopeFlags = 0
};
if (PlatformInterface == null)
{
throw new Exception("[EOS] Platform interface is null!");
}
PlatformInterface.GetAuthInterface().Login(loginOptions, null, OnLogin);
}
private static string GetPasswordFromCommandLine()
{
var commandLineArgs = Environment.GetCommandLineArgs();
foreach (var arg in commandLineArgs)
{
if (arg.Contains("AUTH_PASSWORD"))
{
return arg.Split('=')[1];
}
var options = new Options
{
ProductId = _eosProductID,
SandboxId = _eosSandboxID,
ClientCredentials = new ClientCredentials
{
ClientId = _eosClientID,
ClientSecret = _eosClientSecret
},
DeploymentId = _eosDeploymentID
};
PlatformInterface = PlatformInterface.Create(options);
Program.Log("[EOS] Platform interface has been created");
}
private static void Auth()
return null;
}
private static void OnLogin(LoginCallbackInfo loginCallbackInfo)
{
if (loginCallbackInfo.ResultCode == Result.Success)
{
Program.Log("[EOS] Authenticating...");
var loginOptions = new LoginOptions
{
Credentials = new Credentials
{
Type = LoginCredentialType.ExchangeCode,
Id = null,
Token = GetPasswordFromCommandLine()
},
ScopeFlags = 0
};
if (PlatformInterface == null)
{
throw new Exception("[EOS] Platform interface is null!");
}
PlatformInterface.GetAuthInterface().Login(loginOptions, null, OnLogin);
LocalUserId = loginCallbackInfo.LocalUserId;
LocalUserId.ToString(out var s);
Program.Log($"[EOS SDK] login success! user ID: {s}");
OnAuthSuccess.Invoke();
return;
}
private static string GetPasswordFromCommandLine()
{
var commandLineArgs = Environment.GetCommandLineArgs();
foreach (var arg in commandLineArgs)
{
if (arg.Contains("AUTH_PASSWORD"))
{
return arg.Split('=')[1];
}
}
throw new Exception("[EOS SDK] Login failed");
}
return null;
}
private class EOSInitializeException : Exception
{
public readonly Result Result;
private static void OnLogin(LoginCallbackInfo loginCallbackInfo)
{
if (loginCallbackInfo.ResultCode == Result.Success)
{
LocalUserId = loginCallbackInfo.LocalUserId;
LocalUserId.ToString(out var s);
Program.Log($"[EOS SDK] login success! user ID: {s}");
OnAuthSuccess.Invoke();
return;
}
throw new Exception("[EOS SDK] Login failed");
}
private class EOSInitializeException : Exception
{
public readonly Result Result;
public EOSInitializeException(string msg, Result initResult) :
base(msg) =>
Result = initResult;
}
public EOSInitializeException(string msg, Result initResult) :
base(msg) =>
Result = initResult;
}
}

View File

@ -3,55 +3,54 @@ using System.IO;
using System.Linq;
using System.Reflection;
namespace EpicRerouter.ExeSide
namespace EpicRerouter.ExeSide;
public static class Program
{
public static class Program
public static string ProductName;
public static string Version;
private static void Main(string[] args)
{
public static string ProductName;
public static string Version;
ProductName = args[0];
Log($"product name = {ProductName}");
Version = args[1];
Log($"version = {Version}");
var managedDir = args[2];
Log($"managed dir = {managedDir}");
var gameArgs = args.Skip(3).ToArray();
Log($"game args = {string.Join(", ", gameArgs)}");
private static void Main(string[] args)
AppDomain.CurrentDomain.AssemblyResolve += (_, e) =>
{
ProductName = args[0];
Log($"product name = {ProductName}");
Version = args[1];
Log($"version = {Version}");
var managedDir = args[2];
Log($"managed dir = {managedDir}");
var gameArgs = args.Skip(3).ToArray();
Log($"game args = {string.Join(", ", gameArgs)}");
var name = new AssemblyName(e.Name).Name + ".dll";
var path = Path.Combine(managedDir, name);
return File.Exists(path) ? Assembly.LoadFile(path) : null;
};
AppDomain.CurrentDomain.AssemblyResolve += (_, e) =>
{
var name = new AssemblyName(e.Name).Name + ".dll";
var path = Path.Combine(managedDir, name);
return File.Exists(path) ? Assembly.LoadFile(path) : null;
};
Go();
}
private static void Go()
{
try
{
EpicPlatformManager.Init();
EpicEntitlementRetriever.Init();
while (EpicEntitlementRetriever.GetOwnershipStatus() == EntitlementsManager.AsyncOwnershipStatus.NotReady)
{
EpicPlatformManager.Tick();
}
}
finally
{
EpicEntitlementRetriever.Uninit();
EpicPlatformManager.Uninit();
Environment.Exit((int)EpicEntitlementRetriever.GetOwnershipStatus());
}
}
public static void Log(object msg) => Console.Error.WriteLine(msg);
Go();
}
private static void Go()
{
try
{
EpicPlatformManager.Init();
EpicEntitlementRetriever.Init();
while (EpicEntitlementRetriever.GetOwnershipStatus() == EntitlementsManager.AsyncOwnershipStatus.NotReady)
{
EpicPlatformManager.Tick();
}
}
finally
{
EpicEntitlementRetriever.Uninit();
EpicPlatformManager.Uninit();
Environment.Exit((int)EpicEntitlementRetriever.GetOwnershipStatus());
}
}
public static void Log(object msg) => Console.Error.WriteLine(msg);
}

View File

@ -7,63 +7,62 @@ using System.Reflection;
using UnityEngine;
using Debug = UnityEngine.Debug;
namespace EpicRerouter.ModSide
namespace EpicRerouter.ModSide;
public static class Interop
{
public static class Interop
public static EntitlementsManager.AsyncOwnershipStatus OwnershipStatus = EntitlementsManager.AsyncOwnershipStatus.NotReady;
public static void Go()
{
public static EntitlementsManager.AsyncOwnershipStatus OwnershipStatus = EntitlementsManager.AsyncOwnershipStatus.NotReady;
public static void Go()
if (typeof(EpicPlatformManager).GetField("_platformInterface", BindingFlags.NonPublic | BindingFlags.Instance) == null)
{
if (typeof(EpicPlatformManager).GetField("_platformInterface", BindingFlags.NonPublic | BindingFlags.Instance) == null)
{
Log("not epic. don't reroute");
return;
}
Log("go");
Patches.Apply();
var processPath = Path.Combine(
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!,
"EpicRerouter.exe"
);
Log($"process path = {processPath}");
var gamePath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(typeof(EpicPlatformManager).Assembly.Location)!, ".."));
Log($"game path = {gamePath}");
var workingDirectory = Path.Combine(gamePath, "Plugins", "x86_64");
Log($"working dir = {workingDirectory}");
var args = new[]
{
Application.productName,
Application.version,
Path.Combine(gamePath, "Managed")
};
Log($"args = {args.Join()}");
var gameArgs = Environment.GetCommandLineArgs();
Log($"game args = {gameArgs.Join()}");
var process = Process.Start(new ProcessStartInfo
{
FileName = processPath,
WorkingDirectory = workingDirectory,
Arguments = args
.Concat(gameArgs)
.Join(x => $"\"{x}\"", " "),
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
});
process!.WaitForExit();
OwnershipStatus = (EntitlementsManager.AsyncOwnershipStatus)process.ExitCode;
Log($"ownership status = {OwnershipStatus}");
Log($"output:\n{process.StandardOutput.ReadToEnd()}");
Log($"error:\n{process.StandardError.ReadToEnd()}");
Log("not epic. don't reroute");
return;
}
public static void Log(object msg) => Debug.Log($"[EpicRerouter] {msg}");
Log("go");
Patches.Apply();
var processPath = Path.Combine(
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!,
"EpicRerouter.exe"
);
Log($"process path = {processPath}");
var gamePath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(typeof(EpicPlatformManager).Assembly.Location)!, ".."));
Log($"game path = {gamePath}");
var workingDirectory = Path.Combine(gamePath, "Plugins", "x86_64");
Log($"working dir = {workingDirectory}");
var args = new[]
{
Application.productName,
Application.version,
Path.Combine(gamePath, "Managed")
};
Log($"args = {args.Join()}");
var gameArgs = Environment.GetCommandLineArgs();
Log($"game args = {gameArgs.Join()}");
var process = Process.Start(new ProcessStartInfo
{
FileName = processPath,
WorkingDirectory = workingDirectory,
Arguments = args
.Concat(gameArgs)
.Join(x => $"\"{x}\"", " "),
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
});
process!.WaitForExit();
OwnershipStatus = (EntitlementsManager.AsyncOwnershipStatus)process.ExitCode;
Log($"ownership status = {OwnershipStatus}");
Log($"output:\n{process.StandardOutput.ReadToEnd()}");
Log($"error:\n{process.StandardError.ReadToEnd()}");
}
public static void Log(object msg) => Debug.Log($"[EpicRerouter] {msg}");
}

View File

@ -2,65 +2,64 @@
using UnityEngine;
using static EntitlementsManager;
namespace EpicRerouter.ModSide
namespace EpicRerouter.ModSide;
[HarmonyPatch(typeof(EpicPlatformManager))]
public static class Patches
{
[HarmonyPatch(typeof(EpicPlatformManager))]
public static class Patches
public static void Apply()
{
public static void Apply()
var harmony = new Harmony(typeof(Patches).FullName);
harmony.PatchAll(typeof(EntitlementsManagerPatches));
harmony.PatchAll(typeof(EpicPlatformManagerPatches));
}
[HarmonyPatch(typeof(EntitlementsManager))]
private static class EntitlementsManagerPatches
{
[HarmonyPrefix]
[HarmonyPatch(nameof(EntitlementsManager.InitializeOnAwake))]
private static bool InitializeOnAwake(EntitlementsManager __instance)
{
var harmony = new Harmony(typeof(Patches).FullName);
harmony.PatchAll(typeof(EntitlementsManagerPatches));
harmony.PatchAll(typeof(EpicPlatformManagerPatches));
Object.Destroy(__instance);
return false;
}
[HarmonyPatch(typeof(EntitlementsManager))]
private static class EntitlementsManagerPatches
[HarmonyPrefix]
[HarmonyPatch(nameof(EntitlementsManager.Start))]
private static bool Start() => false;
[HarmonyPrefix]
[HarmonyPatch(nameof(EntitlementsManager.OnDestroy))]
private static bool OnDestroy() => false;
[HarmonyPrefix]
[HarmonyPatch(nameof(EntitlementsManager.IsDlcOwned))]
private static bool IsDlcOwned(out AsyncOwnershipStatus __result)
{
[HarmonyPrefix]
[HarmonyPatch(nameof(EntitlementsManager.InitializeOnAwake))]
private static bool InitializeOnAwake(EntitlementsManager __instance)
{
Object.Destroy(__instance);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(EntitlementsManager.Start))]
private static bool Start() => false;
[HarmonyPrefix]
[HarmonyPatch(nameof(EntitlementsManager.OnDestroy))]
private static bool OnDestroy() => false;
[HarmonyPrefix]
[HarmonyPatch(nameof(EntitlementsManager.IsDlcOwned))]
private static bool IsDlcOwned(out AsyncOwnershipStatus __result)
{
__result = Interop.OwnershipStatus;
Interop.Log($"ownership status = {__result}");
return false;
}
}
[HarmonyPatch(typeof(EpicPlatformManager))]
private static class EpicPlatformManagerPatches
{
[HarmonyPrefix]
[HarmonyPatch("Awake")]
private static bool Awake(EpicPlatformManager __instance)
{
Object.Destroy(__instance);
return false;
}
[HarmonyPrefix]
[HarmonyPatch("Start")]
private static bool Start() => false;
[HarmonyPrefix]
[HarmonyPatch("OnDestroy")]
private static bool OnDestroy() => false;
__result = Interop.OwnershipStatus;
Interop.Log($"ownership status = {__result}");
return false;
}
}
[HarmonyPatch(typeof(EpicPlatformManager))]
private static class EpicPlatformManagerPatches
{
[HarmonyPrefix]
[HarmonyPatch("Awake")]
private static bool Awake(EpicPlatformManager __instance)
{
Object.Destroy(__instance);
return false;
}
[HarmonyPrefix]
[HarmonyPatch("Start")]
private static bool Start() => false;
[HarmonyPrefix]
[HarmonyPatch("OnDestroy")]
private static bool OnDestroy() => false;
}
}

View File

@ -1,41 +1,40 @@
using Mono.Cecil;
using System.Collections.Generic;
namespace MirrorWeaver
namespace MirrorWeaver;
public static class ImprovedExtensions
{
public static class ImprovedExtensions
/// <summary>
/// filters ONLY public fields instead of all non-private <br/>
/// replaces generic parameter fields with their corresponding argument
/// </summary>
public static IEnumerable<FieldDefinition> FindAllPublicFields_Improved(this TypeReference tr)
{
/// <summary>
/// filters ONLY public fields instead of all non-private <br/>
/// replaces generic parameter fields with their corresponding argument
/// </summary>
public static IEnumerable<FieldDefinition> FindAllPublicFields_Improved(this TypeReference tr)
while (tr != null)
{
while (tr != null)
var td = tr.Resolve();
foreach (var fd in td.Fields)
{
var td = tr.Resolve();
foreach (var fd in td.Fields)
if (fd.IsStatic || !fd.IsPublic)
{
if (fd.IsStatic || !fd.IsPublic)
{
continue;
}
if (fd.IsNotSerialized)
{
continue;
}
if (fd.FieldType is GenericParameter gp && gp.Owner == td)
{
fd.FieldType = ((GenericInstanceType)tr).GenericArguments[gp.Position];
}
yield return fd;
continue;
}
tr = td.BaseType;
if (fd.IsNotSerialized)
{
continue;
}
if (fd.FieldType is GenericParameter gp && gp.Owner == td)
{
fd.FieldType = ((GenericInstanceType)tr).GenericArguments[gp.Position];
}
yield return fd;
}
tr = td.BaseType;
}
}
}
}

View File

@ -4,58 +4,57 @@ using Mono.Cecil.Cil;
using System;
using System.IO;
namespace MirrorWeaver
namespace MirrorWeaver;
public class ConsoleLogger : Logger
{
public class ConsoleLogger : Logger
public void Warning(string message) => Warning(message, null);
public void Warning(string message, MemberReference mr)
{
public void Warning(string message) => Warning(message, null);
public void Warning(string message, MemberReference mr)
if (mr != null)
{
if (mr != null)
{
message = $"{message} (at {mr})";
}
Console.WriteLine(message);
message = $"{message} (at {mr})";
}
public void Error(string message) => Error(message, null);
public void Error(string message, MemberReference mr)
{
if (mr != null)
{
message = $"{message} (at {mr})";
}
Console.Error.WriteLine(message);
}
Console.WriteLine(message);
}
public static class Program
public void Error(string message) => Error(message, null);
public void Error(string message, MemberReference mr)
{
public static void Main(string[] args)
if (mr != null)
{
var qsbDll = args[0];
var resolver = new DefaultAssemblyResolver();
resolver.AddSearchDirectory(Path.GetDirectoryName(qsbDll));
var assembly = AssemblyDefinition.ReadAssembly(qsbDll, new ReaderParameters
{
ReadWrite = true,
AssemblyResolver = resolver,
SymbolReaderProvider = new DefaultSymbolReaderProvider(false)
});
var log = new ConsoleLogger();
var weaver = new Weaver(log);
if (!weaver.Weave(assembly, resolver, out _))
{
Environment.Exit(1);
}
assembly.Write(new WriterParameters { WriteSymbols = assembly.MainModule.HasSymbols });
message = $"{message} (at {mr})";
}
Console.Error.WriteLine(message);
}
}
public static class Program
{
public static void Main(string[] args)
{
var qsbDll = args[0];
var resolver = new DefaultAssemblyResolver();
resolver.AddSearchDirectory(Path.GetDirectoryName(qsbDll));
var assembly = AssemblyDefinition.ReadAssembly(qsbDll, new ReaderParameters
{
ReadWrite = true,
AssemblyResolver = resolver,
SymbolReaderProvider = new DefaultSymbolReaderProvider(false)
});
var log = new ConsoleLogger();
var weaver = new Weaver(log);
if (!weaver.Weave(assembly, resolver, out _))
{
Environment.Exit(1);
}
assembly.Write(new WriterParameters { WriteSymbols = assembly.MainModule.HasSymbols });
}
}

View File

@ -4,83 +4,82 @@ using Mono.Cecil;
using System;
using System.Diagnostics;
namespace MirrorWeaver
namespace MirrorWeaver;
public static class QSBReaderWriterProcessor
{
public static class QSBReaderWriterProcessor
/// <summary>
/// finds usages of the generic read/write methods and generates read/write functions for them.
/// <para/>
/// traverses from non generic classes up thru base types
/// in order to replace generic parameters with their corresponding generic arguments.
/// </summary>
public static void Process(ModuleDefinition module, Writers writers, Readers readers, ref bool weavingFailed)
{
/// <summary>
/// finds usages of the generic read/write methods and generates read/write functions for them.
/// <para/>
/// traverses from non generic classes up thru base types
/// in order to replace generic parameters with their corresponding generic arguments.
/// </summary>
public static void Process(ModuleDefinition module, Writers writers, Readers readers, ref bool weavingFailed)
var sw = Stopwatch.StartNew();
var NetworkWriter_Write = typeof(NetworkWriter).GetMethod(nameof(NetworkWriter.Write));
var NetworkReader_Read = typeof(NetworkReader).GetMethod(nameof(NetworkReader.Read));
var count = 0;
foreach (var type in module.GetTypes())
{
var sw = Stopwatch.StartNew();
var NetworkWriter_Write = typeof(NetworkWriter).GetMethod(nameof(NetworkWriter.Write));
var NetworkReader_Read = typeof(NetworkReader).GetMethod(nameof(NetworkReader.Read));
var count = 0;
foreach (var type in module.GetTypes())
if (type.HasGenericParameters)
{
if (type.HasGenericParameters)
{
continue;
}
continue;
}
TypeReference currentType = type;
while (currentType != null)
TypeReference currentType = type;
while (currentType != null)
{
var currentTypeDef = currentType.Resolve();
foreach (var method in currentTypeDef.Methods)
{
var currentTypeDef = currentType.Resolve();
foreach (var method in currentTypeDef.Methods)
if (!method.HasBody)
{
if (!method.HasBody)
continue;
}
foreach (var instruction in method.Body.Instructions)
{
if (instruction.Operand is not GenericInstanceMethod calledMethod)
{
continue;
}
foreach (var instruction in method.Body.Instructions)
if (calledMethod.DeclaringType.FullName == NetworkWriter_Write.DeclaringType.FullName &&
calledMethod.Name == NetworkWriter_Write.Name)
{
if (instruction.Operand is not GenericInstanceMethod calledMethod)
var argType = calledMethod.GenericArguments[0];
if (argType is GenericParameter genericParameter && genericParameter.Owner == currentTypeDef)
{
continue;
argType = ((GenericInstanceType)currentType).GenericArguments[genericParameter.Position];
}
if (calledMethod.DeclaringType.FullName == NetworkWriter_Write.DeclaringType.FullName &&
calledMethod.Name == NetworkWriter_Write.Name)
writers.GetWriteFunc(argType, ref weavingFailed);
count++;
}
else if (calledMethod.DeclaringType.FullName == NetworkReader_Read.DeclaringType.FullName &&
calledMethod.Name == NetworkReader_Read.Name)
{
var argType = calledMethod.GenericArguments[0];
if (argType is GenericParameter genericParameter && genericParameter.Owner == currentTypeDef)
{
var argType = calledMethod.GenericArguments[0];
if (argType is GenericParameter genericParameter && genericParameter.Owner == currentTypeDef)
{
argType = ((GenericInstanceType)currentType).GenericArguments[genericParameter.Position];
}
writers.GetWriteFunc(argType, ref weavingFailed);
count++;
argType = ((GenericInstanceType)currentType).GenericArguments[genericParameter.Position];
}
else if (calledMethod.DeclaringType.FullName == NetworkReader_Read.DeclaringType.FullName &&
calledMethod.Name == NetworkReader_Read.Name)
{
var argType = calledMethod.GenericArguments[0];
if (argType is GenericParameter genericParameter && genericParameter.Owner == currentTypeDef)
{
argType = ((GenericInstanceType)currentType).GenericArguments[genericParameter.Position];
}
readers.GetReadFunc(argType, ref weavingFailed);
count++;
}
readers.GetReadFunc(argType, ref weavingFailed);
count++;
}
}
currentType = currentTypeDef.BaseType;
}
}
Console.WriteLine($"got/generated {count} read/write funcs in {sw.ElapsedMilliseconds} ms");
currentType = currentTypeDef.BaseType;
}
}
Console.WriteLine($"got/generated {count} read/write funcs in {sw.ElapsedMilliseconds} ms");
}
}

View File

@ -5,19 +5,18 @@ using QSB.WorldSync;
using System.Collections.Generic;
using System.Threading;
namespace QSB.Anglerfish
namespace QSB.Anglerfish;
public class AnglerManager : WorldObjectManager
{
public class AnglerManager : WorldObjectManager
public override WorldObjectScene WorldObjectScene => WorldObjectScene.SolarSystem;
public static readonly List<AnglerfishController> Anglers = new();
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct)
{
public override WorldObjectScene WorldObjectScene => WorldObjectScene.SolarSystem;
public static readonly List<AnglerfishController> Anglers = new();
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct)
{
Anglers.Clear();
Anglers.AddRange(QSBWorldSync.GetUnityObjects<AnglerfishController>().SortDeterministic());
QSBWorldSync.Init<QSBAngler, AnglerfishController>(Anglers);
}
Anglers.Clear();
Anglers.AddRange(QSBWorldSync.GetUnityObjects<AnglerfishController>().SortDeterministic());
QSBWorldSync.Init<QSBAngler, AnglerfishController>(Anglers);
}
}

View File

@ -4,72 +4,71 @@ using QSB.Messaging;
using QSB.Player;
using UnityEngine;
namespace QSB.Anglerfish.Messages
namespace QSB.Anglerfish.Messages;
/// <summary>
/// angler state, target transform, and local disturbance pos
/// </summary>
public class AnglerDataMessage : QSBWorldObjectMessage<QSBAngler, AnglerfishController.AnglerState>
{
/// <summary>
/// angler state, target transform, and local disturbance pos
/// </summary>
public class AnglerDataMessage : QSBWorldObjectMessage<QSBAngler, AnglerfishController.AnglerState>
private uint TargetId;
private Vector3 LocalDisturbancePos;
public AnglerDataMessage(QSBAngler qsbAngler)
{
private uint TargetId;
private Vector3 LocalDisturbancePos;
Data = qsbAngler.AttachedObject._currentState;
TargetId = TargetToId(qsbAngler.TargetTransform);
LocalDisturbancePos = qsbAngler.AttachedObject._localDisturbancePos;
}
public AnglerDataMessage(QSBAngler qsbAngler)
public override void Serialize(NetworkWriter writer)
{
base.Serialize(writer);
writer.Write(TargetId);
writer.Write(LocalDisturbancePos);
}
public override void Deserialize(NetworkReader reader)
{
base.Deserialize(reader);
TargetId = reader.Read<uint>();
LocalDisturbancePos = reader.ReadVector3();
}
public override void OnReceiveRemote()
{
WorldObject.TargetTransform = IdToTarget(TargetId);
WorldObject.AttachedObject._localDisturbancePos = LocalDisturbancePos;
WorldObject.AttachedObject.ChangeState(Data);
}
private static uint TargetToId(Transform transform)
{
if (transform == null)
{
Data = qsbAngler.AttachedObject._currentState;
TargetId = TargetToId(qsbAngler.TargetTransform);
LocalDisturbancePos = qsbAngler.AttachedObject._localDisturbancePos;
return uint.MaxValue;
}
public override void Serialize(NetworkWriter writer)
if (transform == Locator.GetShipTransform())
{
base.Serialize(writer);
writer.Write(TargetId);
writer.Write(LocalDisturbancePos);
return uint.MaxValue - 1;
}
public override void Deserialize(NetworkReader reader)
return QSBPlayerManager.LocalPlayerId;
}
private static Transform IdToTarget(uint id)
{
if (id == uint.MaxValue)
{
base.Deserialize(reader);
TargetId = reader.Read<uint>();
LocalDisturbancePos = reader.ReadVector3();
return null;
}
public override void OnReceiveRemote()
if (id == uint.MaxValue - 1)
{
WorldObject.TargetTransform = IdToTarget(TargetId);
WorldObject.AttachedObject._localDisturbancePos = LocalDisturbancePos;
WorldObject.AttachedObject.ChangeState(Data);
return Locator.GetShipTransform();
}
private static uint TargetToId(Transform transform)
{
if (transform == null)
{
return uint.MaxValue;
}
if (transform == Locator.GetShipTransform())
{
return uint.MaxValue - 1;
}
return QSBPlayerManager.LocalPlayerId;
}
private static Transform IdToTarget(uint id)
{
if (id == uint.MaxValue)
{
return null;
}
if (id == uint.MaxValue - 1)
{
return Locator.GetShipTransform();
}
return QSBPlayerManager.GetPlayer(id).Body.transform;
}
return QSBPlayerManager.GetPlayer(id).Body.transform;
}
}

View File

@ -6,69 +6,88 @@ using QSB.Patches;
using QSB.WorldSync;
using UnityEngine;
namespace QSB.Anglerfish.Patches
namespace QSB.Anglerfish.Patches;
public class AnglerPatches : QSBPatch
{
public class AnglerPatches : QSBPatch
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
[HarmonyPrefix]
[HarmonyPatch(typeof(AnglerfishController), nameof(AnglerfishController.GetTargetPosition))]
public static bool GetTargetPosition(AnglerfishController __instance, out Vector3 __result)
{
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
var qsbAngler = __instance.GetWorldObject<QSBAngler>();
[HarmonyPrefix]
[HarmonyPatch(typeof(AnglerfishController), nameof(AnglerfishController.GetTargetPosition))]
public static bool GetTargetPosition(AnglerfishController __instance, out Vector3 __result)
if (qsbAngler.TargetTransform != null)
{
var qsbAngler = __instance.GetWorldObject<QSBAngler>();
if (qsbAngler.TargetTransform != null)
{
__result = qsbAngler.TargetTransform.position;
return false;
}
__result = __instance._brambleBody.transform.TransformPoint(__instance._localDisturbancePos);
__result = qsbAngler.TargetTransform.position;
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(AnglerfishController), nameof(AnglerfishController.OnSectorOccupantRemoved))]
public static bool OnSectorOccupantRemoved(AnglerfishController __instance,
SectorDetector sectorDetector)
__result = __instance._brambleBody.transform.TransformPoint(__instance._localDisturbancePos);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(AnglerfishController), nameof(AnglerfishController.OnSectorOccupantRemoved))]
public static bool OnSectorOccupantRemoved(AnglerfishController __instance,
SectorDetector sectorDetector)
{
var qsbAngler = __instance.GetWorldObject<QSBAngler>();
if (qsbAngler.TargetTransform != null && sectorDetector.GetAttachedOWRigidbody().transform == qsbAngler.TargetTransform)
{
var qsbAngler = __instance.GetWorldObject<QSBAngler>();
if (qsbAngler.TargetTransform != null && sectorDetector.GetAttachedOWRigidbody().transform == qsbAngler.TargetTransform)
{
qsbAngler.TargetTransform = null;
__instance.ChangeState(AnglerfishController.AnglerState.Lurking);
qsbAngler.SendMessage(new AnglerDataMessage(qsbAngler));
}
return false;
qsbAngler.TargetTransform = null;
__instance.ChangeState(AnglerfishController.AnglerState.Lurking);
qsbAngler.SendMessage(new AnglerDataMessage(qsbAngler));
}
[HarmonyPrefix]
[HarmonyPatch(typeof(AnglerfishController), nameof(AnglerfishController.UpdateState))]
public static bool UpdateState(AnglerfishController __instance)
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(AnglerfishController), nameof(AnglerfishController.UpdateState))]
public static bool UpdateState(AnglerfishController __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
return true;
}
var qsbAngler = __instance.GetWorldObject<QSBAngler>();
var qsbAngler = __instance.GetWorldObject<QSBAngler>();
switch (__instance._currentState)
{
case AnglerfishController.AnglerState.Investigating:
if ((__instance._brambleBody.transform.TransformPoint(__instance._localDisturbancePos) - __instance._anglerBody.GetPosition()).sqrMagnitude < __instance._arrivalDistance * __instance._arrivalDistance)
{
__instance.ChangeState(AnglerfishController.AnglerState.Lurking);
qsbAngler.SendMessage(new AnglerDataMessage(qsbAngler));
return false;
}
switch (__instance._currentState)
{
case AnglerfishController.AnglerState.Investigating:
if ((__instance._brambleBody.transform.TransformPoint(__instance._localDisturbancePos) - __instance._anglerBody.GetPosition()).sqrMagnitude < __instance._arrivalDistance * __instance._arrivalDistance)
{
__instance.ChangeState(AnglerfishController.AnglerState.Lurking);
qsbAngler.SendMessage(new AnglerDataMessage(qsbAngler));
return false;
}
break;
case AnglerfishController.AnglerState.Chasing:
break;
case AnglerfishController.AnglerState.Chasing:
if (qsbAngler.TargetTransform == null)
{
__instance.ChangeState(AnglerfishController.AnglerState.Lurking);
qsbAngler.SendMessage(new AnglerDataMessage(qsbAngler));
return false;
}
if ((qsbAngler.TargetTransform.position - __instance._anglerBody.GetPosition()).sqrMagnitude > __instance._escapeDistance * __instance._escapeDistance)
{
qsbAngler.TargetTransform = null;
__instance.ChangeState(AnglerfishController.AnglerState.Lurking);
qsbAngler.SendMessage(new AnglerDataMessage(qsbAngler));
return false;
}
break;
case AnglerfishController.AnglerState.Consuming:
if (!__instance._consumeComplete)
{
if (qsbAngler.TargetTransform == null)
{
__instance.ChangeState(AnglerfishController.AnglerState.Lurking);
@ -76,257 +95,237 @@ namespace QSB.Anglerfish.Patches
return false;
}
if ((qsbAngler.TargetTransform.position - __instance._anglerBody.GetPosition()).sqrMagnitude > __instance._escapeDistance * __instance._escapeDistance)
var num = Time.time - __instance._consumeStartTime;
if (qsbAngler.TargetTransform.CompareTag("Player") && num > __instance._consumeDeathDelay)
{
qsbAngler.TargetTransform = null;
__instance.ChangeState(AnglerfishController.AnglerState.Lurking);
Locator.GetDeathManager().KillPlayer(DeathType.Digestion);
__instance._consumeComplete = true;
return false;
}
if (qsbAngler.TargetTransform.CompareTag("Ship"))
{
if (num > __instance._consumeShipCrushDelay)
{
qsbAngler.TargetTransform.GetComponentInChildren<ShipDamageController>().TriggerSystemFailure();
}
if (num > __instance._consumeDeathDelay)
{
if (PlayerState.IsInsideShip())
{
Locator.GetDeathManager().KillPlayer(DeathType.Digestion);
}
__instance._consumeComplete = true;
return false;
}
}
}
else
{
qsbAngler.TargetTransform = null;
__instance.ChangeState(AnglerfishController.AnglerState.Lurking);
qsbAngler.SendMessage(new AnglerDataMessage(qsbAngler));
}
break;
case AnglerfishController.AnglerState.Stunned:
__instance._stunTimer -= Time.deltaTime;
if (__instance._stunTimer <= 0f)
{
if (qsbAngler.TargetTransform != null)
{
__instance.ChangeState(AnglerfishController.AnglerState.Chasing);
qsbAngler.SendMessage(new AnglerDataMessage(qsbAngler));
return false;
}
break;
case AnglerfishController.AnglerState.Consuming:
if (!__instance._consumeComplete)
{
if (qsbAngler.TargetTransform == null)
{
__instance.ChangeState(AnglerfishController.AnglerState.Lurking);
qsbAngler.SendMessage(new AnglerDataMessage(qsbAngler));
return false;
}
var num = Time.time - __instance._consumeStartTime;
if (qsbAngler.TargetTransform.CompareTag("Player") && num > __instance._consumeDeathDelay)
{
Locator.GetDeathManager().KillPlayer(DeathType.Digestion);
__instance._consumeComplete = true;
return false;
}
if (qsbAngler.TargetTransform.CompareTag("Ship"))
{
if (num > __instance._consumeShipCrushDelay)
{
qsbAngler.TargetTransform.GetComponentInChildren<ShipDamageController>().TriggerSystemFailure();
}
if (num > __instance._consumeDeathDelay)
{
if (PlayerState.IsInsideShip())
{
Locator.GetDeathManager().KillPlayer(DeathType.Digestion);
}
__instance._consumeComplete = true;
return false;
}
}
}
else
{
qsbAngler.TargetTransform = null;
__instance.ChangeState(AnglerfishController.AnglerState.Lurking);
qsbAngler.SendMessage(new AnglerDataMessage(qsbAngler));
}
break;
case AnglerfishController.AnglerState.Stunned:
__instance._stunTimer -= Time.deltaTime;
if (__instance._stunTimer <= 0f)
{
if (qsbAngler.TargetTransform != null)
{
__instance.ChangeState(AnglerfishController.AnglerState.Chasing);
qsbAngler.SendMessage(new AnglerDataMessage(qsbAngler));
return false;
}
__instance.ChangeState(AnglerfishController.AnglerState.Lurking);
qsbAngler.SendMessage(new AnglerDataMessage(qsbAngler));
}
break;
default:
return false;
}
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(AnglerfishController), nameof(AnglerfishController.UpdateMovement))]
public static bool UpdateMovement(AnglerfishController __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
var qsbAngler = __instance.GetWorldObject<QSBAngler>();
qsbAngler.UpdateTargetVelocity();
if (__instance._anglerBody.GetVelocity().sqrMagnitude > Mathf.Pow(__instance._chaseSpeed * 1.5f, 2f))
{
__instance.ApplyDrag(10f);
}
switch (__instance._currentState)
{
case AnglerfishController.AnglerState.Lurking:
__instance.ApplyDrag(1f);
return false;
case AnglerfishController.AnglerState.Investigating:
{
var targetPos = __instance._brambleBody.transform.TransformPoint(__instance._localDisturbancePos);
__instance.RotateTowardsTarget(targetPos, __instance._turnSpeed, __instance._turnSpeed);
if (!__instance._turningInPlace)
{
__instance.MoveTowardsTarget(targetPos, __instance._investigateSpeed, __instance._acceleration);
return false;
}
break;
}
case AnglerfishController.AnglerState.Chasing:
{
var velocity = qsbAngler.TargetVelocity;
var normalized = velocity.normalized;
var from = __instance._anglerBody.GetPosition() + __instance.transform.TransformDirection(__instance._mouthOffset) - qsbAngler.TargetTransform.position;
var magnitude = velocity.magnitude;
var num = Vector3.Angle(from, normalized);
var num2 = magnitude * 2f;
var d = num2;
if (num < 90f)
{
var magnitude2 = from.magnitude;
var num3 = magnitude2 * Mathf.Sin(num * 0.017453292f);
var num4 = magnitude2 * Mathf.Cos(num * 0.017453292f);
var magnitude3 = __instance._anglerBody.GetVelocity().magnitude;
var num5 = num4 / Mathf.Max(magnitude, 0.0001f);
var num6 = num3 / Mathf.Max(magnitude3, 0.0001f);
var num7 = num5 / num6;
if (num7 <= 1f)
{
var t = Mathf.Clamp01(num7);
d = Mathf.Lerp(num2, num4, t);
}
else
{
var num8 = Mathf.InverseLerp(1f, 4f, num7);
d = Mathf.Lerp(num4, 0f, num8 * num8);
}
}
__instance._targetPos = qsbAngler.TargetTransform.position + normalized * d;
__instance.RotateTowardsTarget(__instance._targetPos, __instance._turnSpeed, __instance._quickTurnSpeed);
if (!__instance._turningInPlace)
{
__instance.MoveTowardsTarget(__instance._targetPos, __instance._chaseSpeed, __instance._acceleration);
return false;
}
break;
}
case AnglerfishController.AnglerState.Consuming:
__instance.ApplyDrag(1f);
return false;
case AnglerfishController.AnglerState.Stunned:
__instance.ApplyDrag(0.5f);
break;
default:
return false;
}
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(AnglerfishController), nameof(AnglerfishController.OnImpact))]
public static bool OnImpact(AnglerfishController __instance,
ImpactData impact)
{
var qsbAngler = __instance.GetWorldObject<QSBAngler>();
var attachedOWRigidbody = impact.otherCollider.GetAttachedOWRigidbody();
if ((attachedOWRigidbody.CompareTag("Player") || attachedOWRigidbody.CompareTag("Ship"))
&& __instance._currentState != AnglerfishController.AnglerState.Consuming
&& __instance._currentState != AnglerfishController.AnglerState.Stunned)
{
qsbAngler.TargetTransform = attachedOWRigidbody.transform;
__instance.ChangeState(AnglerfishController.AnglerState.Chasing);
qsbAngler.SendMessage(new AnglerDataMessage(qsbAngler));
return false;
}
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(AnglerfishController), nameof(AnglerfishController.OnClosestAudibleNoise))]
public static bool OnClosestAudibleNoise(AnglerfishController __instance,
NoiseMaker noiseMaker)
{
var qsbAngler = __instance.GetWorldObject<QSBAngler>();
if (__instance._currentState is AnglerfishController.AnglerState.Consuming or AnglerfishController.AnglerState.Stunned)
{
return false;
}
if ((noiseMaker.GetNoiseOrigin() - __instance.transform.position).sqrMagnitude < __instance._pursueDistance * __instance._pursueDistance)
{
if (qsbAngler.TargetTransform != noiseMaker.GetAttachedBody().transform)
{
qsbAngler.TargetTransform = noiseMaker.GetAttachedBody().transform;
if (__instance._currentState != AnglerfishController.AnglerState.Chasing)
{
__instance.ChangeState(AnglerfishController.AnglerState.Chasing);
}
__instance.ChangeState(AnglerfishController.AnglerState.Lurking);
qsbAngler.SendMessage(new AnglerDataMessage(qsbAngler));
return false;
}
}
else if (__instance._currentState is AnglerfishController.AnglerState.Lurking or AnglerfishController.AnglerState.Investigating)
{
__instance._localDisturbancePos = __instance._brambleBody.transform.InverseTransformPoint(noiseMaker.GetNoiseOrigin());
if (__instance._currentState != AnglerfishController.AnglerState.Investigating)
break;
default:
return false;
}
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(AnglerfishController), nameof(AnglerfishController.UpdateMovement))]
public static bool UpdateMovement(AnglerfishController __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
var qsbAngler = __instance.GetWorldObject<QSBAngler>();
qsbAngler.UpdateTargetVelocity();
if (__instance._anglerBody.GetVelocity().sqrMagnitude > Mathf.Pow(__instance._chaseSpeed * 1.5f, 2f))
{
__instance.ApplyDrag(10f);
}
switch (__instance._currentState)
{
case AnglerfishController.AnglerState.Lurking:
__instance.ApplyDrag(1f);
return false;
case AnglerfishController.AnglerState.Investigating:
{
__instance.ChangeState(AnglerfishController.AnglerState.Investigating);
var targetPos = __instance._brambleBody.transform.TransformPoint(__instance._localDisturbancePos);
__instance.RotateTowardsTarget(targetPos, __instance._turnSpeed, __instance._turnSpeed);
if (!__instance._turningInPlace)
{
__instance.MoveTowardsTarget(targetPos, __instance._investigateSpeed, __instance._acceleration);
return false;
}
break;
}
case AnglerfishController.AnglerState.Chasing:
{
var velocity = qsbAngler.TargetVelocity;
var normalized = velocity.normalized;
var from = __instance._anglerBody.GetPosition() + __instance.transform.TransformDirection(__instance._mouthOffset) - qsbAngler.TargetTransform.position;
var magnitude = velocity.magnitude;
var num = Vector3.Angle(from, normalized);
var num2 = magnitude * 2f;
var d = num2;
if (num < 90f)
{
var magnitude2 = from.magnitude;
var num3 = magnitude2 * Mathf.Sin(num * 0.017453292f);
var num4 = magnitude2 * Mathf.Cos(num * 0.017453292f);
var magnitude3 = __instance._anglerBody.GetVelocity().magnitude;
var num5 = num4 / Mathf.Max(magnitude, 0.0001f);
var num6 = num3 / Mathf.Max(magnitude3, 0.0001f);
var num7 = num5 / num6;
if (num7 <= 1f)
{
var t = Mathf.Clamp01(num7);
d = Mathf.Lerp(num2, num4, t);
}
else
{
var num8 = Mathf.InverseLerp(1f, 4f, num7);
d = Mathf.Lerp(num4, 0f, num8 * num8);
}
}
qsbAngler.SendMessage(new AnglerDataMessage(qsbAngler));
}
__instance._targetPos = qsbAngler.TargetTransform.position + normalized * d;
__instance.RotateTowardsTarget(__instance._targetPos, __instance._turnSpeed, __instance._quickTurnSpeed);
if (!__instance._turningInPlace)
{
__instance.MoveTowardsTarget(__instance._targetPos, __instance._chaseSpeed, __instance._acceleration);
return false;
}
break;
}
case AnglerfishController.AnglerState.Consuming:
__instance.ApplyDrag(1f);
return false;
case AnglerfishController.AnglerState.Stunned:
__instance.ApplyDrag(0.5f);
break;
default:
return false;
}
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(AnglerfishController), nameof(AnglerfishController.OnImpact))]
public static bool OnImpact(AnglerfishController __instance,
ImpactData impact)
{
var qsbAngler = __instance.GetWorldObject<QSBAngler>();
var attachedOWRigidbody = impact.otherCollider.GetAttachedOWRigidbody();
if ((attachedOWRigidbody.CompareTag("Player") || attachedOWRigidbody.CompareTag("Ship"))
&& __instance._currentState != AnglerfishController.AnglerState.Consuming
&& __instance._currentState != AnglerfishController.AnglerState.Stunned)
{
qsbAngler.TargetTransform = attachedOWRigidbody.transform;
__instance.ChangeState(AnglerfishController.AnglerState.Chasing);
qsbAngler.SendMessage(new AnglerDataMessage(qsbAngler));
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(AnglerfishController), nameof(AnglerfishController.OnCaughtObject))]
public static bool OnCaughtObject(AnglerfishController __instance,
OWRigidbody caughtBody)
{
var qsbAngler = __instance.GetWorldObject<QSBAngler>();
return false;
}
if (__instance._currentState == AnglerfishController.AnglerState.Consuming)
[HarmonyPrefix]
[HarmonyPatch(typeof(AnglerfishController), nameof(AnglerfishController.OnClosestAudibleNoise))]
public static bool OnClosestAudibleNoise(AnglerfishController __instance,
NoiseMaker noiseMaker)
{
var qsbAngler = __instance.GetWorldObject<QSBAngler>();
if (__instance._currentState is AnglerfishController.AnglerState.Consuming or AnglerfishController.AnglerState.Stunned)
{
return false;
}
if ((noiseMaker.GetNoiseOrigin() - __instance.transform.position).sqrMagnitude < __instance._pursueDistance * __instance._pursueDistance)
{
if (qsbAngler.TargetTransform != noiseMaker.GetAttachedBody().transform)
{
if (!qsbAngler.TargetTransform.CompareTag("Player") && caughtBody.CompareTag("Player"))
qsbAngler.TargetTransform = noiseMaker.GetAttachedBody().transform;
if (__instance._currentState != AnglerfishController.AnglerState.Chasing)
{
Locator.GetDeathManager().KillPlayer(DeathType.Digestion);
__instance.ChangeState(AnglerfishController.AnglerState.Chasing);
}
qsbAngler.SendMessage(new AnglerDataMessage(qsbAngler));
return false;
}
if (caughtBody.CompareTag("Player") || caughtBody.CompareTag("Ship"))
}
else if (__instance._currentState is AnglerfishController.AnglerState.Lurking or AnglerfishController.AnglerState.Investigating)
{
__instance._localDisturbancePos = __instance._brambleBody.transform.InverseTransformPoint(noiseMaker.GetNoiseOrigin());
if (__instance._currentState != AnglerfishController.AnglerState.Investigating)
{
qsbAngler.TargetTransform = caughtBody.transform;
__instance._consumeStartTime = Time.time;
__instance.ChangeState(AnglerfishController.AnglerState.Consuming);
qsbAngler.SendMessage(new AnglerDataMessage(qsbAngler));
__instance.ChangeState(AnglerfishController.AnglerState.Investigating);
}
qsbAngler.SendMessage(new AnglerDataMessage(qsbAngler));
}
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(AnglerfishController), nameof(AnglerfishController.OnCaughtObject))]
public static bool OnCaughtObject(AnglerfishController __instance,
OWRigidbody caughtBody)
{
var qsbAngler = __instance.GetWorldObject<QSBAngler>();
if (__instance._currentState == AnglerfishController.AnglerState.Consuming)
{
if (!qsbAngler.TargetTransform.CompareTag("Player") && caughtBody.CompareTag("Player"))
{
Locator.GetDeathManager().KillPlayer(DeathType.Digestion);
}
return false;
}
if (caughtBody.CompareTag("Player") || caughtBody.CompareTag("Ship"))
{
qsbAngler.TargetTransform = caughtBody.transform;
__instance._consumeStartTime = Time.time;
__instance.ChangeState(AnglerfishController.AnglerState.Consuming);
qsbAngler.SendMessage(new AnglerDataMessage(qsbAngler));
}
return false;
}
}

View File

@ -5,95 +5,94 @@ using QSB.WorldSync;
using System.Collections.Generic;
using UnityEngine;
namespace QSB.Anglerfish.TransformSync
namespace QSB.Anglerfish.TransformSync;
public class AnglerTransformSync : UnsectoredRigidbodySync
{
public class AnglerTransformSync : UnsectoredRigidbodySync
protected override bool UseInterpolation => false;
protected override bool AllowInactiveAttachedObject => true; // since they deactivate when suspended
private QSBAngler _qsbAngler;
private static readonly List<AnglerTransformSync> _instances = new();
protected override OWRigidbody InitAttachedRigidbody()
=> _qsbAngler.AttachedObject._anglerBody;
public override void OnStartClient()
{
protected override bool UseInterpolation => false;
protected override bool AllowInactiveAttachedObject => true; // since they deactivate when suspended
private QSBAngler _qsbAngler;
private static readonly List<AnglerTransformSync> _instances = new();
protected override OWRigidbody InitAttachedRigidbody()
=> _qsbAngler.AttachedObject._anglerBody;
public override void OnStartClient()
_instances.Add(this);
if (QSBCore.IsHost)
{
_instances.Add(this);
if (QSBCore.IsHost)
{
netIdentity.RegisterAuthQueue();
}
base.OnStartClient();
netIdentity.RegisterAuthQueue();
}
public override void OnStopClient()
{
_instances.Remove(this);
if (QSBCore.IsHost)
{
netIdentity.UnregisterAuthQueue();
}
base.OnStartClient();
}
base.OnStopClient();
public override void OnStopClient()
{
_instances.Remove(this);
if (QSBCore.IsHost)
{
netIdentity.UnregisterAuthQueue();
}
protected override float SendInterval => 1;
protected override bool UseReliableRpc => true;
base.OnStopClient();
}
protected override void Init()
protected override float SendInterval => 1;
protected override bool UseReliableRpc => true;
protected override void Init()
{
_qsbAngler = AnglerManager.Anglers[_instances.IndexOf(this)].GetWorldObject<QSBAngler>();
_qsbAngler.TransformSync = this;
base.Init();
SetReferenceTransform(_qsbAngler.AttachedObject._brambleBody.transform);
AttachedRigidbody.OnUnsuspendOWRigidbody += OnUnsuspend;
AttachedRigidbody.OnSuspendOWRigidbody += OnSuspend;
netIdentity.UpdateAuthQueue(AttachedRigidbody.IsSuspended() ? AuthQueueAction.Remove : AuthQueueAction.Add);
}
protected override void Uninit()
{
base.Uninit();
AttachedRigidbody.OnUnsuspendOWRigidbody -= OnUnsuspend;
AttachedRigidbody.OnSuspendOWRigidbody -= OnSuspend;
}
private void OnUnsuspend(OWRigidbody suspendedBody) => netIdentity.UpdateAuthQueue(AuthQueueAction.Add);
private void OnSuspend(OWRigidbody suspendedBody) => netIdentity.UpdateAuthQueue(AuthQueueAction.Remove);
protected override void OnRenderObject()
{
if (!QSBCore.DebugSettings.DrawLines
|| !IsValid
|| !ReferenceTransform
|| !AttachedTransform.gameObject.activeInHierarchy)
{
_qsbAngler = AnglerManager.Anglers[_instances.IndexOf(this)].GetWorldObject<QSBAngler>();
_qsbAngler.TransformSync = this;
base.Init();
SetReferenceTransform(_qsbAngler.AttachedObject._brambleBody.transform);
AttachedRigidbody.OnUnsuspendOWRigidbody += OnUnsuspend;
AttachedRigidbody.OnSuspendOWRigidbody += OnSuspend;
netIdentity.UpdateAuthQueue(AttachedRigidbody.IsSuspended() ? AuthQueueAction.Remove : AuthQueueAction.Add);
return;
}
protected override void Uninit()
{
base.Uninit();
base.OnRenderObject();
AttachedRigidbody.OnUnsuspendOWRigidbody -= OnUnsuspend;
AttachedRigidbody.OnSuspendOWRigidbody -= OnSuspend;
Popcron.Gizmos.Sphere(AttachedRigidbody.GetPosition(), _qsbAngler.AttachedObject._arrivalDistance, Color.blue);
Popcron.Gizmos.Sphere(AttachedRigidbody.GetPosition(), _qsbAngler.AttachedObject._pursueDistance, Color.red);
Popcron.Gizmos.Sphere(AttachedRigidbody.GetPosition(), _qsbAngler.AttachedObject._escapeDistance, Color.yellow);
Popcron.Gizmos.Sphere(AttachedRigidbody.GetPosition()
+ AttachedRigidbody.transform.TransformDirection(_qsbAngler.AttachedObject._mouthOffset), 3, Color.grey);
if (_qsbAngler.TargetTransform)
{
Popcron.Gizmos.Line(_qsbAngler.TargetTransform.position, AttachedRigidbody.GetPosition(), Color.gray);
Popcron.Gizmos.Line(_qsbAngler.TargetTransform.position, _qsbAngler.TargetTransform.position + _qsbAngler.TargetVelocity, Color.green);
Popcron.Gizmos.Line(AttachedRigidbody.GetPosition(), _qsbAngler.AttachedObject._targetPos, Color.red);
Popcron.Gizmos.Sphere(_qsbAngler.AttachedObject._targetPos, 5, Color.red);
}
private void OnUnsuspend(OWRigidbody suspendedBody) => netIdentity.UpdateAuthQueue(AuthQueueAction.Add);
private void OnSuspend(OWRigidbody suspendedBody) => netIdentity.UpdateAuthQueue(AuthQueueAction.Remove);
protected override void OnRenderObject()
{
if (!QSBCore.DebugSettings.DrawLines
|| !IsValid
|| !ReferenceTransform
|| !AttachedTransform.gameObject.activeInHierarchy)
{
return;
}
base.OnRenderObject();
Popcron.Gizmos.Sphere(AttachedRigidbody.GetPosition(), _qsbAngler.AttachedObject._arrivalDistance, Color.blue);
Popcron.Gizmos.Sphere(AttachedRigidbody.GetPosition(), _qsbAngler.AttachedObject._pursueDistance, Color.red);
Popcron.Gizmos.Sphere(AttachedRigidbody.GetPosition(), _qsbAngler.AttachedObject._escapeDistance, Color.yellow);
Popcron.Gizmos.Sphere(AttachedRigidbody.GetPosition()
+ AttachedRigidbody.transform.TransformDirection(_qsbAngler.AttachedObject._mouthOffset), 3, Color.grey);
if (_qsbAngler.TargetTransform)
{
Popcron.Gizmos.Line(_qsbAngler.TargetTransform.position, AttachedRigidbody.GetPosition(), Color.gray);
Popcron.Gizmos.Line(_qsbAngler.TargetTransform.position, _qsbAngler.TargetTransform.position + _qsbAngler.TargetVelocity, Color.green);
Popcron.Gizmos.Line(AttachedRigidbody.GetPosition(), _qsbAngler.AttachedObject._targetPos, Color.red);
Popcron.Gizmos.Sphere(_qsbAngler.AttachedObject._targetPos, 5, Color.red);
}
// Popcron.Gizmos.Line(AttachedObject.GetPosition(), _qsbAngler.AttachedObject.GetTargetPosition(), Color.white);
}
// Popcron.Gizmos.Line(AttachedObject.GetPosition(), _qsbAngler.AttachedObject.GetTargetPosition(), Color.white);
}
}

View File

@ -7,48 +7,47 @@ using QSB.WorldSync;
using System.Threading;
using UnityEngine;
namespace QSB.Anglerfish.WorldObjects
namespace QSB.Anglerfish.WorldObjects;
public class QSBAngler : WorldObject<AnglerfishController>
{
public class QSBAngler : WorldObject<AnglerfishController>
public override bool ShouldDisplayDebug() => false;
public AnglerTransformSync TransformSync;
public Transform TargetTransform;
public Vector3 TargetVelocity { get; private set; }
private Vector3 _lastTargetPosition;
public override async UniTask Init(CancellationToken ct)
{
public override bool ShouldDisplayDebug() => false;
public AnglerTransformSync TransformSync;
public Transform TargetTransform;
public Vector3 TargetVelocity { get; private set; }
private Vector3 _lastTargetPosition;
public override async UniTask Init(CancellationToken ct)
if (QSBCore.IsHost)
{
if (QSBCore.IsHost)
{
NetworkServer.Spawn(Object.Instantiate(QSBNetworkManager.singleton.AnglerPrefab));
}
await UniTask.WaitUntil(() => TransformSync, cancellationToken: ct);
NetworkServer.Spawn(Object.Instantiate(QSBNetworkManager.singleton.AnglerPrefab));
}
public override void OnRemoval()
await UniTask.WaitUntil(() => TransformSync, cancellationToken: ct);
}
public override void OnRemoval()
{
if (QSBCore.IsHost)
{
if (QSBCore.IsHost)
{
NetworkServer.Destroy(TransformSync.gameObject);
}
}
public override void SendInitialState(uint to) =>
this.SendMessage(new AnglerDataMessage(this) { To = to });
public void UpdateTargetVelocity()
{
if (TargetTransform == null)
{
return;
}
TargetVelocity = TargetTransform.position - _lastTargetPosition;
_lastTargetPosition = TargetTransform.position;
NetworkServer.Destroy(TransformSync.gameObject);
}
}
public override void SendInitialState(uint to) =>
this.SendMessage(new AnglerDataMessage(this) { To = to });
public void UpdateTargetVelocity()
{
if (TargetTransform == null)
{
return;
}
TargetVelocity = TargetTransform.position - _lastTargetPosition;
_lastTargetPosition = TargetTransform.position;
}
}

View File

@ -3,21 +3,20 @@ using QSB.Animation.NPC.WorldObjects;
using QSB.WorldSync;
using System.Threading;
namespace QSB.Animation.NPC
{
internal class CharacterAnimManager : WorldObjectManager
{
// im assuming this is used in the eye as well
public override WorldObjectScene WorldObjectScene => WorldObjectScene.Both;
namespace QSB.Animation.NPC;
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct)
{
QSBWorldSync.Init<QSBCharacterAnimController, CharacterAnimController>();
QSBWorldSync.Init<QSBTravelerController, TravelerController>();
QSBWorldSync.Init<QSBSolanumController, NomaiConversationManager>();
QSBWorldSync.Init<QSBSolanumAnimController, SolanumAnimController>();
QSBWorldSync.Init<QSBHearthianRecorderEffects, HearthianRecorderEffects>();
QSBWorldSync.Init<QSBTravelerEyeController, TravelerEyeController>();
}
internal class CharacterAnimManager : WorldObjectManager
{
// im assuming this is used in the eye as well
public override WorldObjectScene WorldObjectScene => WorldObjectScene.Both;
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct)
{
QSBWorldSync.Init<QSBCharacterAnimController, CharacterAnimController>();
QSBWorldSync.Init<QSBTravelerController, TravelerController>();
QSBWorldSync.Init<QSBSolanumController, NomaiConversationManager>();
QSBWorldSync.Init<QSBSolanumAnimController, SolanumAnimController>();
QSBWorldSync.Init<QSBHearthianRecorderEffects, HearthianRecorderEffects>();
QSBWorldSync.Init<QSBTravelerEyeController, TravelerEyeController>();
}
}

View File

@ -1,22 +1,21 @@
using QSB.Animation.NPC.WorldObjects;
using QSB.Messaging;
namespace QSB.Animation.NPC.Messages
{
internal class NpcAnimationMessage : QSBWorldObjectMessage<INpcAnimController, bool>
{
public NpcAnimationMessage(bool start) => Data = start;
namespace QSB.Animation.NPC.Messages;
public override void OnReceiveRemote()
internal class NpcAnimationMessage : QSBWorldObjectMessage<INpcAnimController, bool>
{
public NpcAnimationMessage(bool start) => Data = start;
public override void OnReceiveRemote()
{
if (Data)
{
if (Data)
{
WorldObject.StartConversation();
}
else
{
WorldObject.EndConversation();
}
WorldObject.StartConversation();
}
else
{
WorldObject.EndConversation();
}
}
}

View File

@ -12,158 +12,157 @@ using QSB.WorldSync;
using System.Linq;
using UnityEngine;
namespace QSB.Animation.NPC.Patches
namespace QSB.Animation.NPC.Patches;
[HarmonyPatch]
public class CharacterAnimationPatches : QSBPatch
{
[HarmonyPatch]
public class CharacterAnimationPatches : QSBPatch
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
[HarmonyPrefix]
[HarmonyPatch(typeof(CharacterAnimController), nameof(CharacterAnimController.OnAnimatorIK))]
public static bool AnimatorIKReplacement(
CharacterAnimController __instance)
{
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
[HarmonyPrefix]
[HarmonyPatch(typeof(CharacterAnimController), nameof(CharacterAnimController.OnAnimatorIK))]
public static bool AnimatorIKReplacement(
CharacterAnimController __instance)
if (!QSBWorldSync.AllObjectsReady || ConversationManager.Instance == null)
{
if (!QSBWorldSync.AllObjectsReady || ConversationManager.Instance == null)
{
return true;
}
return true;
}
var playerId = ConversationManager.Instance.GetPlayerTalkingToTree(__instance._dialogueTree);
var player = QSBPlayerManager.GetPlayer(playerId);
var playerId = ConversationManager.Instance.GetPlayerTalkingToTree(__instance._dialogueTree);
var player = QSBPlayerManager.GetPlayer(playerId);
if (__instance.playerTrackingZone == null)
{
return true;
}
if (__instance.playerTrackingZone == null)
{
return true;
}
var qsbObj = __instance.playerTrackingZone.GetWorldObject<QSBCharacterTrigger>(); // OPTIMIZE : maybe cache this somewhere... or assess how slow this is
var qsbObj = __instance.playerTrackingZone.GetWorldObject<QSBCharacterTrigger>(); // OPTIMIZE : maybe cache this somewhere... or assess how slow this is
PlayerInfo playerToUse = null;
if (__instance._inConversation)
PlayerInfo playerToUse = null;
if (__instance._inConversation)
{
if (playerId == uint.MaxValue)
{
if (playerId == uint.MaxValue)
{
DebugLog.ToConsole($"Error - {__instance.name} is in conversation with a null player! Defaulting to active camera.", MessageType.Error);
playerToUse = QSBPlayerManager.LocalPlayer;
}
else
{
playerToUse = player.CameraBody == null
? QSBPlayerManager.LocalPlayer
: player;
}
}
else if (!__instance.lookOnlyWhenTalking && qsbObj.Occupants.Count != 0) // IDEA : maybe this would be more fun if characters looked between players at random times? :P
{
playerToUse = QSBPlayerManager.GetClosestPlayerToWorldPoint(qsbObj.Occupants, __instance.transform.position);
}
else if (QSBPlayerManager.PlayerList.Count != 0)
{
playerToUse = QSBPlayerManager.GetClosestPlayerToWorldPoint(__instance.transform.position, true);
}
var localPosition = playerToUse != null
? __instance._animator.transform.InverseTransformPoint(playerToUse.CameraBody.transform.position)
: Vector3.zero;
var targetWeight = __instance.headTrackingWeight;
if (__instance.lookOnlyWhenTalking)
{
if (!__instance._inConversation
|| qsbObj.Occupants.Count == 0
|| !qsbObj.Occupants.Contains(playerToUse))
{
targetWeight *= 0;
}
DebugLog.ToConsole($"Error - {__instance.name} is in conversation with a null player! Defaulting to active camera.", MessageType.Error);
playerToUse = QSBPlayerManager.LocalPlayer;
}
else
{
if (qsbObj.Occupants.Count == 0
|| !qsbObj.Occupants.Contains(playerToUse))
{
targetWeight *= 0;
}
playerToUse = player.CameraBody == null
? QSBPlayerManager.LocalPlayer
: player;
}
__instance._currentLookWeight = Mathf.Lerp(__instance._currentLookWeight, targetWeight, Time.deltaTime * 2f);
__instance._currentLookTarget = __instance.lookSpring.Update(__instance._currentLookTarget, localPosition, Time.deltaTime);
__instance._animator.SetLookAtPosition(__instance._animator.transform.TransformPoint(__instance._currentLookTarget));
__instance._animator.SetLookAtWeight(__instance._currentLookWeight);
return false;
}
else if (!__instance.lookOnlyWhenTalking && qsbObj.Occupants.Count != 0) // IDEA : maybe this would be more fun if characters looked between players at random times? :P
{
playerToUse = QSBPlayerManager.GetClosestPlayerToWorldPoint(qsbObj.Occupants, __instance.transform.position);
}
else if (QSBPlayerManager.PlayerList.Count != 0)
{
playerToUse = QSBPlayerManager.GetClosestPlayerToWorldPoint(__instance.transform.position, true);
}
[HarmonyPrefix]
[HarmonyPatch(typeof(FacePlayerWhenTalking), nameof(FacePlayerWhenTalking.OnStartConversation))]
public static bool OnStartConversation(FacePlayerWhenTalking __instance)
var localPosition = playerToUse != null
? __instance._animator.transform.InverseTransformPoint(playerToUse.CameraBody.transform.position)
: Vector3.zero;
var targetWeight = __instance.headTrackingWeight;
if (__instance.lookOnlyWhenTalking)
{
var playerId = ConversationManager.Instance.GetPlayerTalkingToTree(__instance._dialogueTree);
if (playerId == uint.MaxValue)
if (!__instance._inConversation
|| qsbObj.Occupants.Count == 0
|| !qsbObj.Occupants.Contains(playerToUse))
{
DebugLog.ToConsole($"Error - No player talking to {__instance._dialogueTree.name}!", MessageType.Error);
return false;
targetWeight *= 0;
}
}
else
{
if (qsbObj.Occupants.Count == 0
|| !qsbObj.Occupants.Contains(playerToUse))
{
targetWeight *= 0;
}
}
var player = QSBPlayerManager.GetPlayer(playerId);
__instance._currentLookWeight = Mathf.Lerp(__instance._currentLookWeight, targetWeight, Time.deltaTime * 2f);
__instance._currentLookTarget = __instance.lookSpring.Update(__instance._currentLookTarget, localPosition, Time.deltaTime);
__instance._animator.SetLookAtPosition(__instance._animator.transform.TransformPoint(__instance._currentLookTarget));
__instance._animator.SetLookAtWeight(__instance._currentLookWeight);
return false;
var distance = player.Body.transform.position - __instance.transform.position;
var vector2 = distance - Vector3.Project(distance, __instance.transform.up);
var angle = Vector3.Angle(__instance.transform.forward, vector2) * Mathf.Sign(Vector3.Dot(vector2, __instance.transform.right));
var axis = __instance.transform.parent.InverseTransformDirection(__instance.transform.up);
var lhs = Quaternion.AngleAxis(angle, axis);
__instance.FaceLocalRotation(lhs * __instance.transform.localRotation);
}
[HarmonyPrefix]
[HarmonyPatch(typeof(FacePlayerWhenTalking), nameof(FacePlayerWhenTalking.OnStartConversation))]
public static bool OnStartConversation(FacePlayerWhenTalking __instance)
{
var playerId = ConversationManager.Instance.GetPlayerTalkingToTree(__instance._dialogueTree);
if (playerId == uint.MaxValue)
{
DebugLog.ToConsole($"Error - No player talking to {__instance._dialogueTree.name}!", MessageType.Error);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(CharacterDialogueTree), nameof(CharacterDialogueTree.StartConversation))]
public static bool StartConversation(CharacterDialogueTree __instance)
{
var allNpcAnimControllers = QSBWorldSync.GetWorldObjects<INpcAnimController>();
var ownerOfThis = allNpcAnimControllers.FirstOrDefault(x => x.GetDialogueTree() == __instance);
if (ownerOfThis == default)
{
return true;
}
var player = QSBPlayerManager.GetPlayer(playerId);
ownerOfThis.SendMessage(new NpcAnimationMessage(true));
var distance = player.Body.transform.position - __instance.transform.position;
var vector2 = distance - Vector3.Project(distance, __instance.transform.up);
var angle = Vector3.Angle(__instance.transform.forward, vector2) * Mathf.Sign(Vector3.Dot(vector2, __instance.transform.right));
var axis = __instance.transform.parent.InverseTransformDirection(__instance.transform.up);
var lhs = Quaternion.AngleAxis(angle, axis);
__instance.FaceLocalRotation(lhs * __instance.transform.localRotation);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(CharacterDialogueTree), nameof(CharacterDialogueTree.StartConversation))]
public static bool StartConversation(CharacterDialogueTree __instance)
{
var allNpcAnimControllers = QSBWorldSync.GetWorldObjects<INpcAnimController>();
var ownerOfThis = allNpcAnimControllers.FirstOrDefault(x => x.GetDialogueTree() == __instance);
if (ownerOfThis == default)
{
return true;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(CharacterDialogueTree), nameof(CharacterDialogueTree.EndConversation))]
public static bool EndConversation(CharacterDialogueTree __instance)
{
var allNpcAnimControllers = QSBWorldSync.GetWorldObjects<INpcAnimController>();
var ownerOfThis = allNpcAnimControllers.FirstOrDefault(x => x.GetDialogueTree() == __instance);
if (ownerOfThis == default)
{
return true;
}
ownerOfThis.SendMessage(new NpcAnimationMessage(true));
return true;
}
ownerOfThis.SendMessage(new NpcAnimationMessage(false));
[HarmonyPrefix]
[HarmonyPatch(typeof(CharacterDialogueTree), nameof(CharacterDialogueTree.EndConversation))]
public static bool EndConversation(CharacterDialogueTree __instance)
{
var allNpcAnimControllers = QSBWorldSync.GetWorldObjects<INpcAnimController>();
var ownerOfThis = allNpcAnimControllers.FirstOrDefault(x => x.GetDialogueTree() == __instance);
if (ownerOfThis == default)
{
return true;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(KidRockController), nameof(KidRockController.Update))]
public static bool UpdateReplacement(KidRockController __instance)
ownerOfThis.SendMessage(new NpcAnimationMessage(false));
return true;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(KidRockController), nameof(KidRockController.Update))]
public static bool UpdateReplacement(KidRockController __instance)
{
if (!QSBWorldSync.AllObjectsReady)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
var qsbObj = QSBWorldSync.GetWorldObjects<QSBCharacterAnimController>().First(x => x.GetDialogueTree() == __instance._dialogueTree);
if (!__instance._throwingRock && !qsbObj.InConversation() && Time.time > __instance._nextThrowTime)
{
__instance.StartRockThrow();
}
return false;
return true;
}
var qsbObj = QSBWorldSync.GetWorldObjects<QSBCharacterAnimController>().First(x => x.GetDialogueTree() == __instance._dialogueTree);
if (!__instance._throwingRock && !qsbObj.InConversation() && Time.time > __instance._nextThrowTime)
{
__instance.StartRockThrow();
}
return false;
}
}

View File

@ -6,205 +6,204 @@ using QSB.WorldSync;
using System.Linq;
using UnityEngine;
namespace QSB.Animation.NPC.Patches
namespace QSB.Animation.NPC.Patches;
[HarmonyPatch]
public class SolanumPatches : QSBPatch
{
[HarmonyPatch]
public class SolanumPatches : QSBPatch
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
[HarmonyPrefix]
[HarmonyPatch(typeof(SolanumAnimController), nameof(SolanumAnimController.LateUpdate))]
public static bool SolanumLateUpdateReplacement(SolanumAnimController __instance)
{
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
[HarmonyPrefix]
[HarmonyPatch(typeof(SolanumAnimController), nameof(SolanumAnimController.LateUpdate))]
public static bool SolanumLateUpdateReplacement(SolanumAnimController __instance)
if (__instance._animatorStateEvents == null)
{
if (__instance._animatorStateEvents == null)
{
__instance._animatorStateEvents = __instance._animator.GetBehaviour<AnimatorStateEvents>();
__instance._animatorStateEvents.OnEnterState += __instance.OnEnterAnimatorState;
}
var qsbObj = __instance.GetWorldObject<QSBSolanumAnimController>();
var playersInHeadZone = qsbObj.Trigger.Occupants;
var targetCamera = playersInHeadZone == null || playersInHeadZone.Count == 0
? __instance._playerCameraTransform
: QSBPlayerManager.GetClosestPlayerToWorldPoint(playersInHeadZone, __instance.transform.position).CameraBody.transform;
var targetValue = Quaternion.LookRotation(targetCamera.position - __instance._headBoneTransform.position, __instance.transform.up);
__instance._currentLookRotation = __instance._lookSpring.Update(__instance._currentLookRotation, targetValue, Time.deltaTime);
var position = __instance._headBoneTransform.position + (__instance._currentLookRotation * Vector3.forward);
__instance._localLookPosition = __instance.transform.InverseTransformPoint(position);
return false;
__instance._animatorStateEvents = __instance._animator.GetBehaviour<AnimatorStateEvents>();
__instance._animatorStateEvents.OnEnterState += __instance.OnEnterAnimatorState;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(NomaiConversationManager), nameof(NomaiConversationManager.Update))]
public static bool ReplacementUpdate(NomaiConversationManager __instance)
var qsbObj = __instance.GetWorldObject<QSBSolanumAnimController>();
var playersInHeadZone = qsbObj.Trigger.Occupants;
var targetCamera = playersInHeadZone == null || playersInHeadZone.Count == 0
? __instance._playerCameraTransform
: QSBPlayerManager.GetClosestPlayerToWorldPoint(playersInHeadZone, __instance.transform.position).CameraBody.transform;
var targetValue = Quaternion.LookRotation(targetCamera.position - __instance._headBoneTransform.position, __instance.transform.up);
__instance._currentLookRotation = __instance._lookSpring.Update(__instance._currentLookRotation, targetValue, Time.deltaTime);
var position = __instance._headBoneTransform.position + (__instance._currentLookRotation * Vector3.forward);
__instance._localLookPosition = __instance.transform.InverseTransformPoint(position);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(NomaiConversationManager), nameof(NomaiConversationManager.Update))]
public static bool ReplacementUpdate(NomaiConversationManager __instance)
{
var qsbObj = __instance._solanumAnimController.GetWorldObject<QSBSolanumAnimController>();
__instance._playerInWatchVolume = qsbObj.Trigger.Occupants.Any();
if (!__instance._initialized)
{
var qsbObj = __instance._solanumAnimController.GetWorldObject<QSBSolanumAnimController>();
__instance._playerInWatchVolume = qsbObj.Trigger.Occupants.Any();
__instance.InitializeNomaiText();
}
if (!__instance._initialized)
{
__instance.InitializeNomaiText();
}
//var heldItem = Locator.GetToolModeSwapper().GetItemCarryTool().GetHeldItem();
//var holdingConversationStone = heldItem != null && heldItem is NomaiConversationStone;
var holdingConversationStone = QSBPlayerManager.GetPlayerCarryItems().Any(x => x.Item2 != null && x.Item2.GetItemType() == ItemType.ConversationStone);
//var heldItem = Locator.GetToolModeSwapper().GetItemCarryTool().GetHeldItem();
//var holdingConversationStone = heldItem != null && heldItem is NomaiConversationStone;
var holdingConversationStone = QSBPlayerManager.GetPlayerCarryItems().Any(x => x.Item2 != null && x.Item2.GetItemType() == ItemType.ConversationStone);
switch (__instance._state)
{
case NomaiConversationManager.State.WatchingSky:
if (__instance._playerInWatchVolume)
{
__instance._state = NomaiConversationManager.State.WatchingPlayer;
__instance._solanumAnimController.StartWatchingPlayer();
}
switch (__instance._state)
{
case NomaiConversationManager.State.WatchingSky:
if (__instance._playerInWatchVolume)
break;
case NomaiConversationManager.State.WatchingPlayer:
if (!__instance._solanumAnimController.isPerformingAction)
{
// player left watch zone
if (!__instance._playerInWatchVolume)
{
__instance._state = NomaiConversationManager.State.WatchingPlayer;
__instance._solanumAnimController.StartWatchingPlayer();
__instance._state = NomaiConversationManager.State.WatchingSky;
__instance._solanumAnimController.StopWatchingPlayer();
}
break;
case NomaiConversationManager.State.WatchingPlayer:
if (!__instance._solanumAnimController.isPerformingAction)
else if (__instance._dialogueComplete)
{
// player left watch zone
if (!__instance._playerInWatchVolume)
// create conversation stones
if (__instance._dialogueComplete && !__instance._conversationStonesCreated)
{
__instance._state = NomaiConversationManager.State.WatchingSky;
__instance._solanumAnimController.StopWatchingPlayer();
__instance._stoneCreationTimer -= Time.deltaTime;
if (__instance._stoneCreationTimer <= 0f)
{
__instance._state = NomaiConversationManager.State.CreatingStones;
__instance._solanumAnimController.PlayCreateWordStones();
}
}
else if (__instance._dialogueComplete)
else if (__instance._conversationStonesCreated && !__instance._cairnRaised)
{
// create conversation stones
if (__instance._dialogueComplete && !__instance._conversationStonesCreated)
if (!holdingConversationStone)
{
__instance._stoneCreationTimer -= Time.deltaTime;
if (__instance._stoneCreationTimer <= 0f)
{
__instance._state = NomaiConversationManager.State.CreatingStones;
__instance._solanumAnimController.PlayCreateWordStones();
}
}
else if (__instance._conversationStonesCreated && !__instance._cairnRaised)
{
if (!holdingConversationStone)
{
__instance._stoneGestureTimer -= Time.deltaTime;
if (__instance._stoneGestureTimer <= 0f)
{
__instance._solanumAnimController.PlayGestureToWordStones();
__instance._stoneGestureTimer = UnityEngine.Random.Range(8f, 16f);
}
}
// raise cairns
else if (__instance._solanumAnimController.IsPlayerLooking())
{
__instance._state = NomaiConversationManager.State.RaisingCairns;
__instance._solanumAnimController.PlayRaiseCairns();
__instance._cairnAnimator.SetTrigger("Raise");
__instance._cairnCollision.SetActivation(true);
}
}
else if (__instance._activeResponseText == null && __instance._hasValidSocketedStonePair)
{
__instance._activeResponseText = __instance._pendingResponseText;
__instance._pendingResponseText = null;
__instance._state = NomaiConversationManager.State.WritingResponse;
__instance._solanumAnimController.StartWritingMessage();
}
else if (__instance._activeResponseText != null && (!__instance._hasValidSocketedStonePair || __instance._pendingResponseText != null))
{
__instance._state = NomaiConversationManager.State.ErasingResponse;
__instance._solanumAnimController.StartWritingMessage();
}
else if (!holdingConversationStone)
{
if (__instance._playerWasHoldingStone)
{
__instance.ResetStoneGestureTimer();
}
__instance._stoneGestureTimer -= Time.deltaTime;
if (__instance._stoneGestureTimer < 0f)
if (__instance._stoneGestureTimer <= 0f)
{
__instance._solanumAnimController.PlayGestureToWordStones();
__instance.ResetStoneGestureTimer();
__instance._stoneGestureTimer = UnityEngine.Random.Range(8f, 16f);
}
}
else
// raise cairns
else if (__instance._solanumAnimController.IsPlayerLooking())
{
if (!__instance._playerWasHoldingStone)
{
__instance.ResetCairnGestureTimer();
}
__instance._cairnGestureTimer -= Time.deltaTime;
if (__instance._cairnGestureTimer < 0f)
{
__instance._solanumAnimController.PlayGestureToCairns();
__instance.ResetCairnGestureTimer();
}
__instance._state = NomaiConversationManager.State.RaisingCairns;
__instance._solanumAnimController.PlayRaiseCairns();
__instance._cairnAnimator.SetTrigger("Raise");
__instance._cairnCollision.SetActivation(true);
}
}
}
break;
case NomaiConversationManager.State.CreatingStones:
if (!__instance._solanumAnimController.isPerformingAction)
{
__instance._state = NomaiConversationManager.State.WatchingPlayer;
__instance._conversationStonesCreated = true;
}
break;
case NomaiConversationManager.State.RaisingCairns:
if (!__instance._solanumAnimController.isPerformingAction)
{
__instance._state = NomaiConversationManager.State.WatchingPlayer;
__instance._cairnRaised = true;
__instance._stoneSocketATrigger.SetActivation(true);
__instance._stoneSocketBTrigger.SetActivation(true);
}
break;
case NomaiConversationManager.State.ErasingResponse:
if (!__instance._solanumAnimController.isStartingWrite && !__instance._activeResponseText.IsAnimationPlaying())
{
__instance._activeResponseText = null;
if (__instance._pendingResponseText == null)
{
__instance._state = NomaiConversationManager.State.WatchingPlayer;
__instance._solanumAnimController.StopWritingMessage(false);
}
else
else if (__instance._activeResponseText == null && __instance._hasValidSocketedStonePair)
{
__instance._activeResponseText = __instance._pendingResponseText;
__instance._pendingResponseText = null;
__instance._state = NomaiConversationManager.State.WritingResponse;
__instance._activeResponseText.Show();
__instance._solanumAnimController.StartWritingMessage();
}
else if (__instance._activeResponseText != null && (!__instance._hasValidSocketedStonePair || __instance._pendingResponseText != null))
{
__instance._state = NomaiConversationManager.State.ErasingResponse;
__instance._solanumAnimController.StartWritingMessage();
}
else if (!holdingConversationStone)
{
if (__instance._playerWasHoldingStone)
{
__instance.ResetStoneGestureTimer();
}
__instance._stoneGestureTimer -= Time.deltaTime;
if (__instance._stoneGestureTimer < 0f)
{
__instance._solanumAnimController.PlayGestureToWordStones();
__instance.ResetStoneGestureTimer();
}
}
else
{
if (!__instance._playerWasHoldingStone)
{
__instance.ResetCairnGestureTimer();
}
__instance._cairnGestureTimer -= Time.deltaTime;
if (__instance._cairnGestureTimer < 0f)
{
__instance._solanumAnimController.PlayGestureToCairns();
__instance.ResetCairnGestureTimer();
}
}
}
}
break;
break;
case NomaiConversationManager.State.WritingResponse:
if (!__instance._solanumAnimController.isStartingWrite && !__instance._activeResponseText.IsAnimationPlaying())
case NomaiConversationManager.State.CreatingStones:
if (!__instance._solanumAnimController.isPerformingAction)
{
__instance._state = NomaiConversationManager.State.WatchingPlayer;
__instance._conversationStonesCreated = true;
}
break;
case NomaiConversationManager.State.RaisingCairns:
if (!__instance._solanumAnimController.isPerformingAction)
{
__instance._state = NomaiConversationManager.State.WatchingPlayer;
__instance._cairnRaised = true;
__instance._stoneSocketATrigger.SetActivation(true);
__instance._stoneSocketBTrigger.SetActivation(true);
}
break;
case NomaiConversationManager.State.ErasingResponse:
if (!__instance._solanumAnimController.isStartingWrite && !__instance._activeResponseText.IsAnimationPlaying())
{
__instance._activeResponseText = null;
if (__instance._pendingResponseText == null)
{
__instance._state = NomaiConversationManager.State.WatchingPlayer;
__instance._solanumAnimController.StopWritingMessage(true);
__instance._solanumAnimController.StopWritingMessage(false);
}
else
{
__instance._activeResponseText = __instance._pendingResponseText;
__instance._pendingResponseText = null;
__instance._state = NomaiConversationManager.State.WritingResponse;
__instance._activeResponseText.Show();
}
}
break;
}
break;
__instance._playerWasHoldingStone = holdingConversationStone;
case NomaiConversationManager.State.WritingResponse:
if (!__instance._solanumAnimController.isStartingWrite && !__instance._activeResponseText.IsAnimationPlaying())
{
__instance._state = NomaiConversationManager.State.WatchingPlayer;
__instance._solanumAnimController.StopWritingMessage(true);
}
return false;
break;
}
__instance._playerWasHoldingStone = holdingConversationStone;
return false;
}
}

View File

@ -4,188 +4,187 @@ using QSB.Utility;
using System.Linq;
using UnityEngine;
namespace QSB.Animation.NPC.Patches
namespace QSB.Animation.NPC.Patches;
public class TravelerControllerPatches : QSBPatch
{
public class TravelerControllerPatches : QSBPatch
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
[HarmonyPrefix]
[HarmonyPatch(typeof(TravelerController), nameof(TravelerController.OnStartConversation))]
public static bool OnStartConversation(TravelerController __instance)
{
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
__instance._talking = true;
// call directly instead of firing event
__instance.StartConversation();
[HarmonyPrefix]
[HarmonyPatch(typeof(TravelerController), nameof(TravelerController.OnStartConversation))]
public static bool OnStartConversation(TravelerController __instance)
{
__instance._talking = true;
// call directly instead of firing event
__instance.StartConversation();
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(TravelerController), nameof(TravelerController.OnEndConversation))]
public static bool OnEndConversation(TravelerController __instance)
{
// call directly instead of firing event
__instance.EndConversation(__instance._delayToRestartAudio);
__instance._talking = false;
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(TravelerController), nameof(TravelerController.StartConversation))]
public static bool StartConversation(TravelerController __instance)
{
if (__instance._animator != null && __instance._animator.enabled)
{
__instance._playingAnimID = __instance._animator.IsInTransition(0)
? __instance._animator.GetNextAnimatorStateInfo(0).fullPathHash
: __instance._animator.GetCurrentAnimatorStateInfo(0).fullPathHash;
__instance._animator.SetTrigger("Talking");
}
Locator.GetTravelerAudioManager().StopTravelerAudio(__instance);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(GabbroTravelerController), nameof(GabbroTravelerController.StartConversation))]
public static bool StartConversation(GabbroTravelerController __instance)
{
if (__instance._animator.enabled)
{
__instance._animator.CrossFadeInFixedTime("Gabbro_Talking", 1.8f);
__instance._hammockAnimator.CrossFadeInFixedTime("GabbroHammock_Talking", 1.8f);
}
Locator.GetTravelerAudioManager().StopTravelerAudio(__instance);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(TravelerController), nameof(TravelerController.EndConversation))]
public static bool EndConversation(TravelerController __instance, float audioDelay)
{
if (__instance._animator != null && __instance._animator.enabled)
{
if (audioDelay > 0f)
{
__instance._animator.CrossFadeInFixedTime(__instance._playingAnimID, audioDelay, -1, -audioDelay);
}
else
{
__instance._animator.SetTrigger("Playing");
}
}
Locator.GetTravelerAudioManager().PlayTravelerAudio(__instance, audioDelay);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(GabbroTravelerController), nameof(GabbroTravelerController.EndConversation))]
public static bool EndConversation(GabbroTravelerController __instance, float audioDelay)
{
if (__instance._animator.enabled)
{
__instance._animator.CrossFadeInFixedTime("Gabbro_Playing", audioDelay, -1, -audioDelay);
__instance._hammockAnimator.CrossFadeInFixedTime("GabbroHammock_Playing", audioDelay, -1, -audioDelay);
}
Locator.GetTravelerAudioManager().PlayTravelerAudio(__instance, audioDelay);
if (DialogueConditionManager.SharedInstance.GetConditionState("MAP_PROMPT_REMINDER") || DialogueConditionManager.SharedInstance.GetConditionState("MAP_PROMPT_ATTENTION"))
{
var conditionState = DialogueConditionManager.SharedInstance.GetConditionState("MAP_PROMPT_ATTENTION");
DialogueConditionManager.SharedInstance.SetConditionState("MAP_PROMPT_REMINDER");
DialogueConditionManager.SharedInstance.SetConditionState("MAP_PROMPT_ATTENTION");
GlobalMessenger<bool>.FireEvent("TriggerMapPromptReminder", conditionState);
}
return false;
}
return false;
}
internal static class TravelerAudioManagerExtensions
[HarmonyPrefix]
[HarmonyPatch(typeof(TravelerController), nameof(TravelerController.OnEndConversation))]
public static bool OnEndConversation(TravelerController __instance)
{
/// bad, but works great
private static SignalName? TravelerToSignalName(TravelerController traveler)
// call directly instead of firing event
__instance.EndConversation(__instance._delayToRestartAudio);
__instance._talking = false;
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(TravelerController), nameof(TravelerController.StartConversation))]
public static bool StartConversation(TravelerController __instance)
{
if (__instance._animator != null && __instance._animator.enabled)
{
var name = traveler.name;
if (name.Contains("Esker"))
{
return SignalName.Traveler_Esker;
}
if (name.Contains("Chert"))
{
return SignalName.Traveler_Chert;
}
if (name.Contains("Riebeck"))
{
return SignalName.Traveler_Riebeck;
}
if (name.Contains("Gabbro"))
{
return SignalName.Traveler_Gabbro;
}
if (name.Contains("Feldspar"))
{
return SignalName.Traveler_Feldspar;
}
if (name.Contains("Nomai"))
{
return SignalName.Traveler_Nomai;
}
if (name.Contains("Prisoner"))
{
return SignalName.Traveler_Prisoner;
}
return null;
__instance._playingAnimID = __instance._animator.IsInTransition(0)
? __instance._animator.GetNextAnimatorStateInfo(0).fullPathHash
: __instance._animator.GetCurrentAnimatorStateInfo(0).fullPathHash;
__instance._animator.SetTrigger("Talking");
}
internal static void StopTravelerAudio(this TravelerAudioManager manager, TravelerController traveler)
Locator.GetTravelerAudioManager().StopTravelerAudio(__instance);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(GabbroTravelerController), nameof(GabbroTravelerController.StartConversation))]
public static bool StartConversation(GabbroTravelerController __instance)
{
if (__instance._animator.enabled)
{
var signalName = TravelerToSignalName(traveler);
if (signalName == null)
{
return;
}
var signal = manager._signals.First(x => x.GetName() == signalName);
signal.GetOWAudioSource().FadeOut(0.5f);
__instance._animator.CrossFadeInFixedTime("Gabbro_Talking", 1.8f);
__instance._hammockAnimator.CrossFadeInFixedTime("GabbroHammock_Talking", 1.8f);
}
internal static void PlayTravelerAudio(this TravelerAudioManager manager, TravelerController traveler, float audioDelay)
Locator.GetTravelerAudioManager().StopTravelerAudio(__instance);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(TravelerController), nameof(TravelerController.EndConversation))]
public static bool EndConversation(TravelerController __instance, float audioDelay)
{
if (__instance._animator != null && __instance._animator.enabled)
{
var signalName = TravelerToSignalName(traveler);
if (signalName == null)
if (audioDelay > 0f)
{
return;
__instance._animator.CrossFadeInFixedTime(__instance._playingAnimID, audioDelay, -1, -audioDelay);
}
var signal = manager._signals.First(x => x.GetName() == signalName);
manager._playAfterDelay = false;
manager._playAudioTime = Time.time + audioDelay;
Delay.RunWhen(() => Time.time >= manager._playAudioTime, () =>
else
{
if (!signal.IsOnlyAudibleToScope() || signal.GetOWAudioSource().isPlaying)
{
signal.GetOWAudioSource().FadeIn(0.5f);
signal.GetOWAudioSource().timeSamples = 0;
}
});
__instance._animator.SetTrigger("Playing");
}
}
Locator.GetTravelerAudioManager().PlayTravelerAudio(__instance, audioDelay);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(GabbroTravelerController), nameof(GabbroTravelerController.EndConversation))]
public static bool EndConversation(GabbroTravelerController __instance, float audioDelay)
{
if (__instance._animator.enabled)
{
__instance._animator.CrossFadeInFixedTime("Gabbro_Playing", audioDelay, -1, -audioDelay);
__instance._hammockAnimator.CrossFadeInFixedTime("GabbroHammock_Playing", audioDelay, -1, -audioDelay);
}
Locator.GetTravelerAudioManager().PlayTravelerAudio(__instance, audioDelay);
if (DialogueConditionManager.SharedInstance.GetConditionState("MAP_PROMPT_REMINDER") || DialogueConditionManager.SharedInstance.GetConditionState("MAP_PROMPT_ATTENTION"))
{
var conditionState = DialogueConditionManager.SharedInstance.GetConditionState("MAP_PROMPT_ATTENTION");
DialogueConditionManager.SharedInstance.SetConditionState("MAP_PROMPT_REMINDER");
DialogueConditionManager.SharedInstance.SetConditionState("MAP_PROMPT_ATTENTION");
GlobalMessenger<bool>.FireEvent("TriggerMapPromptReminder", conditionState);
}
return false;
}
}
internal static class TravelerAudioManagerExtensions
{
/// bad, but works great
private static SignalName? TravelerToSignalName(TravelerController traveler)
{
var name = traveler.name;
if (name.Contains("Esker"))
{
return SignalName.Traveler_Esker;
}
if (name.Contains("Chert"))
{
return SignalName.Traveler_Chert;
}
if (name.Contains("Riebeck"))
{
return SignalName.Traveler_Riebeck;
}
if (name.Contains("Gabbro"))
{
return SignalName.Traveler_Gabbro;
}
if (name.Contains("Feldspar"))
{
return SignalName.Traveler_Feldspar;
}
if (name.Contains("Nomai"))
{
return SignalName.Traveler_Nomai;
}
if (name.Contains("Prisoner"))
{
return SignalName.Traveler_Prisoner;
}
return null;
}
internal static void StopTravelerAudio(this TravelerAudioManager manager, TravelerController traveler)
{
var signalName = TravelerToSignalName(traveler);
if (signalName == null)
{
return;
}
var signal = manager._signals.First(x => x.GetName() == signalName);
signal.GetOWAudioSource().FadeOut(0.5f);
}
internal static void PlayTravelerAudio(this TravelerAudioManager manager, TravelerController traveler, float audioDelay)
{
var signalName = TravelerToSignalName(traveler);
if (signalName == null)
{
return;
}
var signal = manager._signals.First(x => x.GetName() == signalName);
manager._playAfterDelay = false;
manager._playAudioTime = Time.time + audioDelay;
Delay.RunWhen(() => Time.time >= manager._playAudioTime, () =>
{
if (!signal.IsOnlyAudibleToScope() || signal.GetOWAudioSource().isPlaying)
{
signal.GetOWAudioSource().FadeIn(0.5f);
signal.GetOWAudioSource().timeSamples = 0;
}
});
}
}

View File

@ -1,11 +1,10 @@
using QSB.WorldSync;
namespace QSB.Animation.NPC.WorldObjects
namespace QSB.Animation.NPC.WorldObjects;
public interface INpcAnimController : IWorldObject
{
public interface INpcAnimController : IWorldObject
{
CharacterDialogueTree GetDialogueTree();
void StartConversation();
void EndConversation();
}
CharacterDialogueTree GetDialogueTree();
void StartConversation();
void EndConversation();
}

View File

@ -2,17 +2,16 @@
using QSB.WorldSync;
using UnityEngine;
namespace QSB.Animation.NPC.WorldObjects
namespace QSB.Animation.NPC.WorldObjects;
internal abstract class NpcAnimController<T> : WorldObject<T>, INpcAnimController
where T : MonoBehaviour
{
internal abstract class NpcAnimController<T> : WorldObject<T>, INpcAnimController
where T : MonoBehaviour
{
public abstract CharacterDialogueTree GetDialogueTree();
public abstract CharacterDialogueTree GetDialogueTree();
public void StartConversation()
=> GetDialogueTree().RaiseEvent(nameof(CharacterDialogueTree.OnStartConversation));
public void StartConversation()
=> GetDialogueTree().RaiseEvent(nameof(CharacterDialogueTree.OnStartConversation));
public void EndConversation()
=> GetDialogueTree().RaiseEvent(nameof(CharacterDialogueTree.OnEndConversation));
}
public void EndConversation()
=> GetDialogueTree().RaiseEvent(nameof(CharacterDialogueTree.OnEndConversation));
}

View File

@ -1,16 +1,15 @@
namespace QSB.Animation.NPC.WorldObjects
namespace QSB.Animation.NPC.WorldObjects;
internal class QSBCharacterAnimController : NpcAnimController<CharacterAnimController>
{
internal class QSBCharacterAnimController : NpcAnimController<CharacterAnimController>
public override void SendInitialState(uint to)
{
public override void SendInitialState(uint to)
{
// todo SendInitialState
}
public override CharacterDialogueTree GetDialogueTree()
=> AttachedObject._dialogueTree;
public bool InConversation()
=> AttachedObject._inConversation;
// todo SendInitialState
}
public override CharacterDialogueTree GetDialogueTree()
=> AttachedObject._dialogueTree;
public bool InConversation()
=> AttachedObject._inConversation;
}

View File

@ -1,13 +1,12 @@
namespace QSB.Animation.NPC.WorldObjects
{
internal class QSBHearthianRecorderEffects : NpcAnimController<HearthianRecorderEffects>
{
public override void SendInitialState(uint to)
{
// todo SendInitialState
}
namespace QSB.Animation.NPC.WorldObjects;
public override CharacterDialogueTree GetDialogueTree()
=> AttachedObject._characterDialogueTree;
internal class QSBHearthianRecorderEffects : NpcAnimController<HearthianRecorderEffects>
{
public override void SendInitialState(uint to)
{
// todo SendInitialState
}
public override CharacterDialogueTree GetDialogueTree()
=> AttachedObject._characterDialogueTree;
}

View File

@ -2,16 +2,15 @@
using QSB.WorldSync;
using System.Linq;
namespace QSB.Animation.NPC.WorldObjects
{
/// <summary>
/// only used to get QSBSolanumTrigger from SolanumAnimController
/// </summary>
internal class QSBSolanumAnimController : WorldObject<SolanumAnimController>
{
private QSBSolanumTrigger _trigger;
public QSBSolanumTrigger Trigger => _trigger ??= QSBWorldSync.GetWorldObjects<QSBSolanumTrigger>().First();
namespace QSB.Animation.NPC.WorldObjects;
public override void SendInitialState(uint to) { }
}
/// <summary>
/// only used to get QSBSolanumTrigger from SolanumAnimController
/// </summary>
internal class QSBSolanumAnimController : WorldObject<SolanumAnimController>
{
private QSBSolanumTrigger _trigger;
public QSBSolanumTrigger Trigger => _trigger ??= QSBWorldSync.GetWorldObjects<QSBSolanumTrigger>().First();
public override void SendInitialState(uint to) { }
}

View File

@ -1,13 +1,12 @@
namespace QSB.Animation.NPC.WorldObjects
{
internal class QSBSolanumController : NpcAnimController<NomaiConversationManager>
{
public override void SendInitialState(uint to)
{
// todo SendInitialState
}
namespace QSB.Animation.NPC.WorldObjects;
public override CharacterDialogueTree GetDialogueTree()
=> AttachedObject._characterDialogueTree;
internal class QSBSolanumController : NpcAnimController<NomaiConversationManager>
{
public override void SendInitialState(uint to)
{
// todo SendInitialState
}
public override CharacterDialogueTree GetDialogueTree()
=> AttachedObject._characterDialogueTree;
}

View File

@ -1,13 +1,12 @@
namespace QSB.Animation.NPC.WorldObjects
{
internal class QSBTravelerController : NpcAnimController<TravelerController>
{
public override void SendInitialState(uint to)
{
// todo SendInitialState
}
namespace QSB.Animation.NPC.WorldObjects;
public override CharacterDialogueTree GetDialogueTree()
=> AttachedObject._dialogueSystem;
internal class QSBTravelerController : NpcAnimController<TravelerController>
{
public override void SendInitialState(uint to)
{
// todo SendInitialState
}
public override CharacterDialogueTree GetDialogueTree()
=> AttachedObject._dialogueSystem;
}

View File

@ -1,13 +1,12 @@
namespace QSB.Animation.NPC.WorldObjects
{
internal class QSBTravelerEyeController : NpcAnimController<TravelerEyeController>
{
public override void SendInitialState(uint to)
{
// todo SendInitialState
}
namespace QSB.Animation.NPC.WorldObjects;
public override CharacterDialogueTree GetDialogueTree()
=> AttachedObject._dialogueTree;
internal class QSBTravelerEyeController : NpcAnimController<TravelerEyeController>
{
public override void SendInitialState(uint to)
{
// todo SendInitialState
}
public override CharacterDialogueTree GetDialogueTree()
=> AttachedObject._dialogueTree;
}

View File

@ -1,18 +1,17 @@
using UnityEngine;
namespace QSB.Animation.Player
namespace QSB.Animation.Player;
public class AnimFloatParam
{
public class AnimFloatParam
public float Current { get; private set; }
public float Target { get; set; }
private float _velocity;
public float Smooth(float smoothTime)
{
public float Current { get; private set; }
public float Target { get; set; }
private float _velocity;
public float Smooth(float smoothTime)
{
Current = Mathf.SmoothDamp(Current, Target, ref _velocity, smoothTime);
return Current;
}
Current = Mathf.SmoothDamp(Current, Target, ref _velocity, smoothTime);
return Current;
}
}

View File

@ -9,192 +9,191 @@ using QSB.Utility;
using System;
using UnityEngine;
namespace QSB.Animation.Player
namespace QSB.Animation.Player;
public class AnimationSync : PlayerSyncObject
{
public class AnimationSync : PlayerSyncObject
private RuntimeAnimatorController _suitedAnimController;
private AnimatorOverrideController _unsuitedAnimController;
private GameObject _suitedGraphics;
private GameObject _unsuitedGraphics;
private PlayerCharacterController _playerController;
private CrouchSync _crouchSync;
public AnimatorMirror Mirror { get; private set; }
public bool InSuitedUpState { get; set; }
public Animator VisibleAnimator { get; private set; }
public Animator InvisibleAnimator { get; private set; }
public NetworkAnimator NetworkAnimator { get; private set; }
protected void Awake()
{
private RuntimeAnimatorController _suitedAnimController;
private AnimatorOverrideController _unsuitedAnimController;
private GameObject _suitedGraphics;
private GameObject _unsuitedGraphics;
private PlayerCharacterController _playerController;
private CrouchSync _crouchSync;
InvisibleAnimator = gameObject.GetRequiredComponent<Animator>();
NetworkAnimator = gameObject.GetRequiredComponent<NetworkAnimator>();
NetworkAnimator.enabled = false;
}
public AnimatorMirror Mirror { get; private set; }
public bool InSuitedUpState { get; set; }
public Animator VisibleAnimator { get; private set; }
public Animator InvisibleAnimator { get; private set; }
public NetworkAnimator NetworkAnimator { get; private set; }
protected void Awake()
private void InitCommon(Transform modelRoot)
{
try
{
InvisibleAnimator = gameObject.GetRequiredComponent<Animator>();
NetworkAnimator = gameObject.GetRequiredComponent<NetworkAnimator>();
NetworkAnimator.enabled = false;
}
private void InitCommon(Transform modelRoot)
{
try
{
if (modelRoot == null)
{
DebugLog.ToConsole($"Error - Trying to InitCommon with null body!", MessageType.Error);
return;
}
VisibleAnimator = modelRoot.GetComponent<Animator>();
Mirror = modelRoot.gameObject.AddComponent<AnimatorMirror>();
if (isLocalPlayer)
{
Mirror.Init(VisibleAnimator, InvisibleAnimator);
}
else
{
Mirror.Init(InvisibleAnimator, VisibleAnimator);
}
NetworkAnimator.enabled = true;
NetworkAnimator.Invoke("Awake");
_suitedAnimController = Instantiate(QSBCore.NetworkAssetBundle.LoadAsset<RuntimeAnimatorController>("Assets/GameAssets/AnimatorController/Player.controller"));
_unsuitedAnimController = Instantiate(QSBCore.NetworkAssetBundle.LoadAsset<AnimatorOverrideController>("Assets/GameAssets/AnimatorOverrideController/PlayerUnsuitedOverride.overrideController"));
_suitedGraphics = modelRoot.GetChild(1).gameObject;
_unsuitedGraphics = modelRoot.GetChild(0).gameObject;
VisibleAnimator.SetLayerWeight(2, 1f);
}
catch (Exception ex)
{
DebugLog.ToConsole($"Exception thrown when running InitCommon on {(modelRoot != null ? modelRoot.name : "NULL BODY")}. {ex.Message} Stacktrace: {ex.StackTrace}", MessageType.Error);
}
}
public void InitLocal(Transform body)
{
InitCommon(body);
_playerController = body.parent.GetComponent<PlayerCharacterController>();
InitCrouchSync();
InitAccelerationSync();
}
public void InitRemote(Transform body)
{
InitCommon(body);
SetSuitState(QSBSceneManager.CurrentScene == OWScene.EyeOfTheUniverse);
InitCrouchSync();
InitAccelerationSync();
ThrusterManager.CreateRemotePlayerVFX(Player);
Delay.RunWhen(() => Player.CameraBody != null,
() => body.GetComponent<PlayerHeadRotationSync>().Init(Player.CameraBody.transform));
}
private void InitAccelerationSync()
{
Player.JetpackAcceleration = GetComponent<JetpackAccelerationSync>();
var thrusterModel = hasAuthority ? Locator.GetPlayerBody().GetComponent<ThrusterModel>() : null;
Player.JetpackAcceleration.Init(thrusterModel);
}
private void InitCrouchSync()
{
_crouchSync = GetComponent<CrouchSync>();
_crouchSync.Init(_playerController, VisibleAnimator);
}
public void SetSuitState(bool suitedUp)
{
if (!Player.IsReady)
if (modelRoot == null)
{
DebugLog.ToConsole($"Error - Trying to InitCommon with null body!", MessageType.Error);
return;
}
if (Player == QSBPlayerManager.LocalPlayer)
VisibleAnimator = modelRoot.GetComponent<Animator>();
Mirror = modelRoot.gameObject.AddComponent<AnimatorMirror>();
if (isLocalPlayer)
{
new PlayerSuitMessage(suitedUp).Send();
}
if (InSuitedUpState == suitedUp)
{
return;
}
InSuitedUpState = suitedUp;
if (_unsuitedAnimController == null)
{
DebugLog.ToConsole($"Error - Unsuited controller is null. ({PlayerId})", MessageType.Error);
}
if (_suitedAnimController == null)
{
DebugLog.ToConsole($"Error - Suited controller is null. ({PlayerId})", MessageType.Error);
}
if (_unsuitedGraphics == null)
{
DebugLog.ToConsole($"Warning - _unsuitedGraphics is null! ({PlayerId})", MessageType.Warning);
}
if (_suitedGraphics == null)
{
DebugLog.ToConsole($"Warning - _suitedGraphics is null! ({PlayerId})", MessageType.Warning);
}
var controller = suitedUp ? _suitedAnimController : _unsuitedAnimController;
if (_unsuitedGraphics != null)
{
_unsuitedGraphics?.SetActive(!suitedUp);
}
if (_suitedGraphics != null)
{
_suitedGraphics?.SetActive(suitedUp);
}
if (InvisibleAnimator == null)
{
DebugLog.ToConsole($"Error - InvisibleAnimator is null. ({PlayerId})", MessageType.Error);
Mirror.Init(VisibleAnimator, InvisibleAnimator);
}
else
{
InvisibleAnimator.runtimeAnimatorController = controller;
Mirror.Init(InvisibleAnimator, VisibleAnimator);
}
if (VisibleAnimator == null)
{
DebugLog.ToConsole($"Error - VisibleAnimator is null. ({PlayerId})", MessageType.Error);
}
else
{
VisibleAnimator.runtimeAnimatorController = controller;
}
// Avoids "jumping" when putting on suit
if (VisibleAnimator != null)
{
VisibleAnimator.SetTrigger("Grounded");
}
if (InvisibleAnimator != null)
{
InvisibleAnimator.SetTrigger("Grounded");
}
if (NetworkAnimator == null)
{
DebugLog.ToConsole($"Error - NetworkAnimator is null. ({PlayerId})", MessageType.Error);
}
else if (Mirror == null)
{
DebugLog.ToConsole($"Error - Mirror is null. ({PlayerId})", MessageType.Error);
}
Mirror.RebuildFloatParams();
NetworkAnimator.enabled = true;
NetworkAnimator.Invoke("Awake");
_suitedAnimController = Instantiate(QSBCore.NetworkAssetBundle.LoadAsset<RuntimeAnimatorController>("Assets/GameAssets/AnimatorController/Player.controller"));
_unsuitedAnimController = Instantiate(QSBCore.NetworkAssetBundle.LoadAsset<AnimatorOverrideController>("Assets/GameAssets/AnimatorOverrideController/PlayerUnsuitedOverride.overrideController"));
_suitedGraphics = modelRoot.GetChild(1).gameObject;
_unsuitedGraphics = modelRoot.GetChild(0).gameObject;
VisibleAnimator.SetLayerWeight(2, 1f);
}
catch (Exception ex)
{
DebugLog.ToConsole($"Exception thrown when running InitCommon on {(modelRoot != null ? modelRoot.name : "NULL BODY")}. {ex.Message} Stacktrace: {ex.StackTrace}", MessageType.Error);
}
}
public void InitLocal(Transform body)
{
InitCommon(body);
_playerController = body.parent.GetComponent<PlayerCharacterController>();
InitCrouchSync();
InitAccelerationSync();
}
public void InitRemote(Transform body)
{
InitCommon(body);
SetSuitState(QSBSceneManager.CurrentScene == OWScene.EyeOfTheUniverse);
InitCrouchSync();
InitAccelerationSync();
ThrusterManager.CreateRemotePlayerVFX(Player);
Delay.RunWhen(() => Player.CameraBody != null,
() => body.GetComponent<PlayerHeadRotationSync>().Init(Player.CameraBody.transform));
}
private void InitAccelerationSync()
{
Player.JetpackAcceleration = GetComponent<JetpackAccelerationSync>();
var thrusterModel = hasAuthority ? Locator.GetPlayerBody().GetComponent<ThrusterModel>() : null;
Player.JetpackAcceleration.Init(thrusterModel);
}
private void InitCrouchSync()
{
_crouchSync = GetComponent<CrouchSync>();
_crouchSync.Init(_playerController, VisibleAnimator);
}
public void SetSuitState(bool suitedUp)
{
if (!Player.IsReady)
{
return;
}
if (Player == QSBPlayerManager.LocalPlayer)
{
new PlayerSuitMessage(suitedUp).Send();
}
if (InSuitedUpState == suitedUp)
{
return;
}
InSuitedUpState = suitedUp;
if (_unsuitedAnimController == null)
{
DebugLog.ToConsole($"Error - Unsuited controller is null. ({PlayerId})", MessageType.Error);
}
if (_suitedAnimController == null)
{
DebugLog.ToConsole($"Error - Suited controller is null. ({PlayerId})", MessageType.Error);
}
if (_unsuitedGraphics == null)
{
DebugLog.ToConsole($"Warning - _unsuitedGraphics is null! ({PlayerId})", MessageType.Warning);
}
if (_suitedGraphics == null)
{
DebugLog.ToConsole($"Warning - _suitedGraphics is null! ({PlayerId})", MessageType.Warning);
}
var controller = suitedUp ? _suitedAnimController : _unsuitedAnimController;
if (_unsuitedGraphics != null)
{
_unsuitedGraphics?.SetActive(!suitedUp);
}
if (_suitedGraphics != null)
{
_suitedGraphics?.SetActive(suitedUp);
}
if (InvisibleAnimator == null)
{
DebugLog.ToConsole($"Error - InvisibleAnimator is null. ({PlayerId})", MessageType.Error);
}
else
{
InvisibleAnimator.runtimeAnimatorController = controller;
}
if (VisibleAnimator == null)
{
DebugLog.ToConsole($"Error - VisibleAnimator is null. ({PlayerId})", MessageType.Error);
}
else
{
VisibleAnimator.runtimeAnimatorController = controller;
}
// Avoids "jumping" when putting on suit
if (VisibleAnimator != null)
{
VisibleAnimator.SetTrigger("Grounded");
}
if (InvisibleAnimator != null)
{
InvisibleAnimator.SetTrigger("Grounded");
}
if (NetworkAnimator == null)
{
DebugLog.ToConsole($"Error - NetworkAnimator is null. ({PlayerId})", MessageType.Error);
}
else if (Mirror == null)
{
DebugLog.ToConsole($"Error - Mirror is null. ({PlayerId})", MessageType.Error);
}
Mirror.RebuildFloatParams();
NetworkAnimator.Invoke("Awake");
}
}

View File

@ -4,99 +4,98 @@ using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace QSB.Animation.Player
namespace QSB.Animation.Player;
public class AnimatorMirror : MonoBehaviour
{
public class AnimatorMirror : MonoBehaviour
private const float SmoothTime = 0.05f;
private Animator _from;
private Animator _to;
private readonly Dictionary<string, AnimFloatParam> _floatParams = new();
public void Init(Animator from, Animator to)
{
private const float SmoothTime = 0.05f;
private Animator _from;
private Animator _to;
private readonly Dictionary<string, AnimFloatParam> _floatParams = new();
public void Init(Animator from, Animator to)
if (from == null)
{
if (from == null)
{
DebugLog.ToConsole($"Error - Trying to init AnimatorMirror with null \"from\".", MessageType.Error);
}
DebugLog.ToConsole($"Error - Trying to init AnimatorMirror with null \"from\".", MessageType.Error);
}
if (to == null)
{
DebugLog.ToConsole($"Error - Trying to init AnimatorMirror with null \"to\".", MessageType.Error);
}
if (to == null)
{
DebugLog.ToConsole($"Error - Trying to init AnimatorMirror with null \"to\".", MessageType.Error);
}
if (to == null || from == null)
{
// Doing the return this way so you can see if one or both are null
return;
}
if (to == null || from == null)
{
// Doing the return this way so you can see if one or both are null
return;
}
_from = from;
_to = to;
if (_from.runtimeAnimatorController == null)
{
_from.runtimeAnimatorController = _to.runtimeAnimatorController;
}
else if (_to.runtimeAnimatorController == null)
{
_to.runtimeAnimatorController = _from.runtimeAnimatorController;
}
_from = from;
_to = to;
if (_from.runtimeAnimatorController == null)
{
_from.runtimeAnimatorController = _to.runtimeAnimatorController;
}
else if (_to.runtimeAnimatorController == null)
{
_to.runtimeAnimatorController = _from.runtimeAnimatorController;
}
RebuildFloatParams();
}
public void Update()
{
if (_to == null || _from == null)
{
return;
}
if (_to.runtimeAnimatorController != _from.runtimeAnimatorController)
{
_to.runtimeAnimatorController = _from.runtimeAnimatorController;
RebuildFloatParams();
}
public void Update()
SyncParams();
SmoothFloats();
}
private void SyncParams()
{
foreach (var fromParam in _from.parameters)
{
if (_to == null || _from == null)
switch (fromParam.type)
{
return;
}
case AnimatorControllerParameterType.Float:
_floatParams[fromParam.name].Target = _from.GetFloat(fromParam.name);
break;
if (_to.runtimeAnimatorController != _from.runtimeAnimatorController)
{
_to.runtimeAnimatorController = _from.runtimeAnimatorController;
RebuildFloatParams();
}
SyncParams();
SmoothFloats();
}
private void SyncParams()
{
foreach (var fromParam in _from.parameters)
{
switch (fromParam.type)
{
case AnimatorControllerParameterType.Float:
_floatParams[fromParam.name].Target = _from.GetFloat(fromParam.name);
break;
case AnimatorControllerParameterType.Bool:
_to.SetBool(fromParam.name, _from.GetBool(fromParam.name));
break;
}
}
}
private void SmoothFloats()
{
foreach (var floatParam in _floatParams)
{
var current = floatParam.Value.Smooth(SmoothTime);
_to.SetFloat(floatParam.Key, current);
}
}
public void RebuildFloatParams()
{
_floatParams.Clear();
foreach (var param in _from.parameters.Where(p => p.type == AnimatorControllerParameterType.Float))
{
_floatParams.Add(param.name, new AnimFloatParam());
case AnimatorControllerParameterType.Bool:
_to.SetBool(fromParam.name, _from.GetBool(fromParam.name));
break;
}
}
}
private void SmoothFloats()
{
foreach (var floatParam in _floatParams)
{
var current = floatParam.Value.Smooth(SmoothTime);
_to.SetFloat(floatParam.Key, current);
}
}
public void RebuildFloatParams()
{
_floatParams.Clear();
foreach (var param in _from.parameters.Where(p => p.type == AnimatorControllerParameterType.Float))
{
_floatParams.Add(param.name, new AnimFloatParam());
}
}
}

View File

@ -2,59 +2,58 @@
using QSB.Utility.VariableSync;
using UnityEngine;
namespace QSB.Animation.Player
namespace QSB.Animation.Player;
public class CrouchSync : NetworkBehaviour
{
public class CrouchSync : NetworkBehaviour
public AnimFloatParam CrouchParam { get; } = new AnimFloatParam();
private const float CrouchSmoothTime = 0.05f;
public const int CrouchLayerIndex = 1;
private PlayerCharacterController _playerController;
private Animator _bodyAnim;
public FloatVariableSyncer CrouchVariableSyncer;
public void Init(PlayerCharacterController playerController, Animator bodyAnim)
{
public AnimFloatParam CrouchParam { get; } = new AnimFloatParam();
_playerController = playerController;
_bodyAnim = bodyAnim;
}
private const float CrouchSmoothTime = 0.05f;
public const int CrouchLayerIndex = 1;
private PlayerCharacterController _playerController;
private Animator _bodyAnim;
public FloatVariableSyncer CrouchVariableSyncer;
public void Init(PlayerCharacterController playerController, Animator bodyAnim)
public void Update()
{
if (isLocalPlayer)
{
_playerController = playerController;
_bodyAnim = bodyAnim;
SyncLocalCrouch();
return;
}
public void Update()
{
if (isLocalPlayer)
{
SyncLocalCrouch();
return;
}
SyncRemoteCrouch();
}
SyncRemoteCrouch();
private void SyncLocalCrouch()
{
if (_playerController == null)
{
return;
}
private void SyncLocalCrouch()
{
if (_playerController == null)
{
return;
}
var jumpChargeFraction = _playerController.GetJumpCrouchFraction();
CrouchVariableSyncer.Value = jumpChargeFraction;
}
var jumpChargeFraction = _playerController.GetJumpCrouchFraction();
CrouchVariableSyncer.Value = jumpChargeFraction;
private void SyncRemoteCrouch()
{
if (_bodyAnim == null)
{
return;
}
private void SyncRemoteCrouch()
{
if (_bodyAnim == null)
{
return;
}
CrouchParam.Target = CrouchVariableSyncer.Value;
CrouchParam.Smooth(CrouchSmoothTime);
var jumpChargeFraction = CrouchParam.Current;
_bodyAnim.SetLayerWeight(CrouchLayerIndex, jumpChargeFraction);
}
CrouchParam.Target = CrouchVariableSyncer.Value;
CrouchParam.Smooth(CrouchSmoothTime);
var jumpChargeFraction = CrouchParam.Current;
_bodyAnim.SetLayerWeight(CrouchLayerIndex, jumpChargeFraction);
}
}

View File

@ -2,28 +2,27 @@
using QSB.Player;
using QSB.WorldSync;
namespace QSB.Animation.Player.Messages
namespace QSB.Animation.Player.Messages;
internal class AnimationTriggerMessage : QSBMessage<string>
{
internal class AnimationTriggerMessage : QSBMessage<string>
public AnimationTriggerMessage(string name) => Data = name;
public override bool ShouldReceive => QSBWorldSync.AllObjectsReady;
public override void OnReceiveRemote()
{
public AnimationTriggerMessage(string name) => Data = name;
public override bool ShouldReceive => QSBWorldSync.AllObjectsReady;
public override void OnReceiveRemote()
var animationSync = QSBPlayerManager.GetPlayer(From).AnimationSync;
if (animationSync == null)
{
var animationSync = QSBPlayerManager.GetPlayer(From).AnimationSync;
if (animationSync == null)
{
return;
}
if (animationSync.VisibleAnimator == null)
{
return;
}
animationSync.VisibleAnimator.SetTrigger(Data);
return;
}
if (animationSync.VisibleAnimator == null)
{
return;
}
animationSync.VisibleAnimator.SetTrigger(Data);
}
}

View File

@ -3,47 +3,46 @@ using QSB.Player;
using QSB.Player.TransformSync;
using QSB.WorldSync;
namespace QSB.Animation.Player.Messages
namespace QSB.Animation.Player.Messages;
public class PlayerSuitMessage : QSBMessage<bool>
{
public class PlayerSuitMessage : QSBMessage<bool>
static PlayerSuitMessage()
{
static PlayerSuitMessage()
GlobalMessenger.AddListener(OWEvents.SuitUp, () => Handle(true));
GlobalMessenger.AddListener(OWEvents.RemoveSuit, () => Handle(false));
}
private static void Handle(bool on)
{
if (PlayerTransformSync.LocalInstance)
{
GlobalMessenger.AddListener(OWEvents.SuitUp, () => Handle(true));
GlobalMessenger.AddListener(OWEvents.RemoveSuit, () => Handle(false));
}
private static void Handle(bool on)
{
if (PlayerTransformSync.LocalInstance)
{
new PlayerSuitMessage(on).Send();
}
}
public PlayerSuitMessage(bool on) => Data = on;
public override bool ShouldReceive => QSBWorldSync.AllObjectsReady;
public override void OnReceiveRemote()
{
var player = QSBPlayerManager.GetPlayer(From);
player.SuitedUp = Data;
if (!player.IsReady)
{
return;
}
var animator = player.AnimationSync;
animator.SetSuitState(Data);
}
public override void OnReceiveLocal()
{
QSBPlayerManager.LocalPlayer.SuitedUp = Data;
var animator = QSBPlayerManager.LocalPlayer.AnimationSync;
animator.InSuitedUpState = Data;
new PlayerSuitMessage(on).Send();
}
}
public PlayerSuitMessage(bool on) => Data = on;
public override bool ShouldReceive => QSBWorldSync.AllObjectsReady;
public override void OnReceiveRemote()
{
var player = QSBPlayerManager.GetPlayer(From);
player.SuitedUp = Data;
if (!player.IsReady)
{
return;
}
var animator = player.AnimationSync;
animator.SetSuitState(Data);
}
public override void OnReceiveLocal()
{
QSBPlayerManager.LocalPlayer.SuitedUp = Data;
var animator = QSBPlayerManager.LocalPlayer.AnimationSync;
animator.InSuitedUpState = Data;
}
}

View File

@ -5,129 +5,128 @@ using QSB.Patches;
using QSB.Utility;
using UnityEngine;
namespace QSB.Animation.Player.Patches
namespace QSB.Animation.Player.Patches;
[HarmonyPatch]
internal class PlayerAnimationPatches : QSBPatch
{
[HarmonyPatch]
internal class PlayerAnimationPatches : QSBPatch
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
[HarmonyPrefix]
[HarmonyPatch(typeof(PlayerAnimController), nameof(PlayerAnimController.LateUpdate))]
public static bool LateUpdateReplacement(
PlayerAnimController __instance)
{
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
[HarmonyPrefix]
[HarmonyPatch(typeof(PlayerAnimController), nameof(PlayerAnimController.LateUpdate))]
public static bool LateUpdateReplacement(
PlayerAnimController __instance)
var isGrounded = __instance._playerController.IsGrounded();
var isAttached = PlayerState.IsAttached();
var isInZeroG = PlayerState.InZeroG();
var isFlying = __instance._playerJetpack.GetLocalAcceleration().y > 0f;
var movementVector = Vector3.zero;
if (!isAttached)
{
var isGrounded = __instance._playerController.IsGrounded();
var isAttached = PlayerState.IsAttached();
var isInZeroG = PlayerState.InZeroG();
var isFlying = __instance._playerJetpack.GetLocalAcceleration().y > 0f;
var movementVector = Vector3.zero;
if (!isAttached)
{
movementVector = __instance._playerController.GetRelativeGroundVelocity();
}
if (Mathf.Abs(movementVector.x) < 0.05f)
{
movementVector.x = 0f;
}
if (Mathf.Abs(movementVector.z) < 0.05f)
{
movementVector.z = 0f;
}
if (isFlying)
{
__instance._ungroundedTime = Time.time;
}
var freefallMagnitude = 0f;
var timeInFreefall = 0f;
var lastGroundBody = __instance._playerController.GetLastGroundBody();
if (!isGrounded && !isAttached && !isInZeroG && lastGroundBody != null)
{
freefallMagnitude = (__instance._playerController.GetAttachedOWRigidbody().GetVelocity() - lastGroundBody.GetPointVelocity(__instance._playerController.transform.position)).magnitude;
timeInFreefall = Time.time - __instance._ungroundedTime;
}
__instance._animator.SetFloat("RunSpeedX", movementVector.x / 3f);
__instance._animator.SetFloat("RunSpeedY", movementVector.z / 3f);
__instance._animator.SetFloat("TurnSpeed", __instance._playerController.GetTurning());
__instance._animator.SetBool("Grounded", isGrounded || isAttached || PlayerState.IsRecentlyDetached());
__instance._animator.SetLayerWeight(CrouchSync.CrouchLayerIndex, __instance._playerController.GetJumpCrouchFraction());
__instance._animator.SetFloat("FreefallSpeed", freefallMagnitude / 15f * (timeInFreefall / 3f));
__instance._animator.SetBool("InZeroG", isInZeroG || isFlying);
__instance._animator.SetBool("UsingJetpack", isInZeroG && PlayerState.IsWearingSuit());
if (__instance._justBecameGrounded)
{
if (__instance._justTookFallDamage)
{
__instance._animator.SetTrigger("LandHard");
new AnimationTriggerMessage("LandHard").Send();
}
else
{
__instance._animator.SetTrigger("Land");
new AnimationTriggerMessage("Land").Send();
}
}
if (isGrounded)
{
var leftFootLift = __instance._animator.GetFloat("LeftFootLift");
if (!__instance._leftFootGrounded && leftFootLift < 0.333f)
{
__instance._leftFootGrounded = true;
__instance.RaiseEvent(nameof(__instance.OnLeftFootGrounded));
}
else if (__instance._leftFootGrounded && leftFootLift > 0.666f)
{
__instance._leftFootGrounded = false;
__instance.RaiseEvent(nameof(__instance.OnLeftFootLift));
}
var rightFootLift = __instance._animator.GetFloat("RightFootLift");
if (!__instance._rightFootGrounded && rightFootLift < 0.333f)
{
__instance._rightFootGrounded = true;
__instance.RaiseEvent(nameof(__instance.OnRightFootGrounded));
}
else if (__instance._rightFootGrounded && rightFootLift > 0.666f)
{
__instance._rightFootGrounded = false;
__instance.RaiseEvent(nameof(__instance.OnRightFootLift));
}
}
__instance._justBecameGrounded = false;
__instance._justTookFallDamage = false;
var usingTool = Locator.GetToolModeSwapper().GetToolMode() != ToolMode.None;
if ((usingTool && !__instance._rightArmHidden) || (!usingTool && __instance._rightArmHidden))
{
__instance._rightArmHidden = usingTool;
for (var i = 0; i < __instance._rightArmObjects.Length; i++)
{
__instance._rightArmObjects[i].layer = (!__instance._rightArmHidden) ? __instance._defaultLayer : __instance._probeOnlyLayer;
}
}
return false;
movementVector = __instance._playerController.GetRelativeGroundVelocity();
}
[HarmonyPrefix]
[HarmonyPatch(typeof(PlayerAnimController), nameof(PlayerAnimController.OnPlayerJump))]
public static bool OnPlayerJumpReplacement(PlayerAnimController __instance)
if (Mathf.Abs(movementVector.x) < 0.05f)
{
movementVector.x = 0f;
}
if (Mathf.Abs(movementVector.z) < 0.05f)
{
movementVector.z = 0f;
}
if (isFlying)
{
__instance._ungroundedTime = Time.time;
if (!__instance.isActiveAndEnabled)
}
var freefallMagnitude = 0f;
var timeInFreefall = 0f;
var lastGroundBody = __instance._playerController.GetLastGroundBody();
if (!isGrounded && !isAttached && !isInZeroG && lastGroundBody != null)
{
freefallMagnitude = (__instance._playerController.GetAttachedOWRigidbody().GetVelocity() - lastGroundBody.GetPointVelocity(__instance._playerController.transform.position)).magnitude;
timeInFreefall = Time.time - __instance._ungroundedTime;
}
__instance._animator.SetFloat("RunSpeedX", movementVector.x / 3f);
__instance._animator.SetFloat("RunSpeedY", movementVector.z / 3f);
__instance._animator.SetFloat("TurnSpeed", __instance._playerController.GetTurning());
__instance._animator.SetBool("Grounded", isGrounded || isAttached || PlayerState.IsRecentlyDetached());
__instance._animator.SetLayerWeight(CrouchSync.CrouchLayerIndex, __instance._playerController.GetJumpCrouchFraction());
__instance._animator.SetFloat("FreefallSpeed", freefallMagnitude / 15f * (timeInFreefall / 3f));
__instance._animator.SetBool("InZeroG", isInZeroG || isFlying);
__instance._animator.SetBool("UsingJetpack", isInZeroG && PlayerState.IsWearingSuit());
if (__instance._justBecameGrounded)
{
if (__instance._justTookFallDamage)
{
return false;
__instance._animator.SetTrigger("LandHard");
new AnimationTriggerMessage("LandHard").Send();
}
else
{
__instance._animator.SetTrigger("Land");
new AnimationTriggerMessage("Land").Send();
}
}
if (isGrounded)
{
var leftFootLift = __instance._animator.GetFloat("LeftFootLift");
if (!__instance._leftFootGrounded && leftFootLift < 0.333f)
{
__instance._leftFootGrounded = true;
__instance.RaiseEvent(nameof(__instance.OnLeftFootGrounded));
}
else if (__instance._leftFootGrounded && leftFootLift > 0.666f)
{
__instance._leftFootGrounded = false;
__instance.RaiseEvent(nameof(__instance.OnLeftFootLift));
}
__instance._animator.SetTrigger("Jump");
new AnimationTriggerMessage("Jump").Send();
var rightFootLift = __instance._animator.GetFloat("RightFootLift");
if (!__instance._rightFootGrounded && rightFootLift < 0.333f)
{
__instance._rightFootGrounded = true;
__instance.RaiseEvent(nameof(__instance.OnRightFootGrounded));
}
else if (__instance._rightFootGrounded && rightFootLift > 0.666f)
{
__instance._rightFootGrounded = false;
__instance.RaiseEvent(nameof(__instance.OnRightFootLift));
}
}
__instance._justBecameGrounded = false;
__instance._justTookFallDamage = false;
var usingTool = Locator.GetToolModeSwapper().GetToolMode() != ToolMode.None;
if ((usingTool && !__instance._rightArmHidden) || (!usingTool && __instance._rightArmHidden))
{
__instance._rightArmHidden = usingTool;
for (var i = 0; i < __instance._rightArmObjects.Length; i++)
{
__instance._rightArmObjects[i].layer = (!__instance._rightArmHidden) ? __instance._defaultLayer : __instance._probeOnlyLayer;
}
}
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(PlayerAnimController), nameof(PlayerAnimController.OnPlayerJump))]
public static bool OnPlayerJumpReplacement(PlayerAnimController __instance)
{
__instance._ungroundedTime = Time.time;
if (!__instance.isActiveAndEnabled)
{
return false;
}
__instance._animator.SetTrigger("Jump");
new AnimationTriggerMessage("Jump").Send();
return false;
}
}

View File

@ -2,44 +2,43 @@
using QSB.Utility;
using UnityEngine;
namespace QSB.Animation.Player
namespace QSB.Animation.Player;
public class PlayerHeadRotationSync : MonoBehaviour
{
public class PlayerHeadRotationSync : MonoBehaviour
private Animator _attachedAnimator;
private Transform _lookBase;
private bool _isSetUp;
public void Init(Transform lookBase)
{
private Animator _attachedAnimator;
private Transform _lookBase;
private bool _isSetUp;
_attachedAnimator = GetComponent<Animator>();
_lookBase = lookBase;
_isSetUp = true;
}
public void Init(Transform lookBase)
private void LateUpdate()
{
if (!_isSetUp)
{
_attachedAnimator = GetComponent<Animator>();
_lookBase = lookBase;
_isSetUp = true;
return;
}
private void LateUpdate()
if (_attachedAnimator == null)
{
if (!_isSetUp)
{
return;
}
if (_attachedAnimator == null)
{
DebugLog.ToConsole($"Error - _attachedAnimator is null!", MessageType.Error);
return;
}
if (_lookBase == null)
{
DebugLog.ToConsole($"Error - _lookBase is null!", MessageType.Error);
return;
}
var bone = _attachedAnimator.GetBoneTransform(HumanBodyBones.Head);
// Get the camera's local rotation with respect to the player body
var lookLocalRotation = Quaternion.Inverse(_attachedAnimator.transform.rotation) * _lookBase.rotation;
bone.localRotation = Quaternion.Euler(0f, 0f, lookLocalRotation.eulerAngles.x);
DebugLog.ToConsole($"Error - _attachedAnimator is null!", MessageType.Error);
return;
}
if (_lookBase == null)
{
DebugLog.ToConsole($"Error - _lookBase is null!", MessageType.Error);
return;
}
var bone = _attachedAnimator.GetBoneTransform(HumanBodyBones.Head);
// Get the camera's local rotation with respect to the player body
var lookLocalRotation = Quaternion.Inverse(_attachedAnimator.transform.rotation) * _lookBase.rotation;
bone.localRotation = Quaternion.Euler(0f, 0f, lookLocalRotation.eulerAngles.x);
}
}

View File

@ -1,32 +1,31 @@
using Mirror;
using QSB.Utility.VariableSync;
namespace QSB.Animation.Player.Thrusters
namespace QSB.Animation.Player.Thrusters;
public class JetpackAccelerationSync : NetworkBehaviour
{
public class JetpackAccelerationSync : NetworkBehaviour
public Vector3VariableSyncer AccelerationVariableSyncer;
public BoolVariableSyncer ThrustingVariableSyncer;
private ThrusterModel _thrusterModel;
public void Init(ThrusterModel model) => _thrusterModel = model;
public void Update()
{
public Vector3VariableSyncer AccelerationVariableSyncer;
public BoolVariableSyncer ThrustingVariableSyncer;
private ThrusterModel _thrusterModel;
public void Init(ThrusterModel model) => _thrusterModel = model;
public void Update()
if (isLocalPlayer)
{
if (isLocalPlayer)
{
SyncLocalAccel();
}
SyncLocalAccel();
}
}
private void SyncLocalAccel()
private void SyncLocalAccel()
{
if (_thrusterModel != null)
{
if (_thrusterModel != null)
{
AccelerationVariableSyncer.Value = _thrusterModel.GetLocalAcceleration();
ThrustingVariableSyncer.Value = _thrusterModel.IsTranslationalThrusterFiring();
}
AccelerationVariableSyncer.Value = _thrusterModel.GetLocalAcceleration();
ThrustingVariableSyncer.Value = _thrusterModel.IsTranslationalThrusterFiring();
}
}
}

View File

@ -2,92 +2,91 @@
using QSB.WorldSync;
using UnityEngine;
namespace QSB.Animation.Player.Thrusters
namespace QSB.Animation.Player.Thrusters;
internal class RemoteThrusterFlameController : MonoBehaviour
{
internal class RemoteThrusterFlameController : MonoBehaviour
[SerializeField]
private Thruster _thruster;
[SerializeField]
private Light _light;
[SerializeField]
private AnimationCurve _scaleByThrust = AnimationCurve.Linear(0f, 0f, 1f, 1f);
[SerializeField]
private DampedSpring _scaleSpring = new();
[SerializeField]
private float _belowMaxThrustScalar = 1f;
private MeshRenderer _thrusterRenderer;
private Vector3 _thrusterFilter;
private float _baseLightRadius;
private float _currentScale;
private bool _initialized;
private PlayerInfo _attachedPlayer;
// TODO : Make flames not appear underwater (Check original code!)
public void Init(PlayerInfo player)
{
[SerializeField]
private Thruster _thruster;
_attachedPlayer = player;
[SerializeField]
private Light _light;
_thrusterRenderer = GetComponent<MeshRenderer>();
_thrusterFilter = OWUtilities.GetShipThrusterFilter(_thruster);
_baseLightRadius = _light.range;
_currentScale = 0f;
_thrusterRenderer.enabled = false;
_light.enabled = false;
_light.shadows = LightShadows.Soft;
_initialized = true;
}
[SerializeField]
private AnimationCurve _scaleByThrust = AnimationCurve.Linear(0f, 0f, 1f, 1f);
[SerializeField]
private DampedSpring _scaleSpring = new();
[SerializeField]
private float _belowMaxThrustScalar = 1f;
private MeshRenderer _thrusterRenderer;
private Vector3 _thrusterFilter;
private float _baseLightRadius;
private float _currentScale;
private bool _initialized;
private PlayerInfo _attachedPlayer;
// TODO : Make flames not appear underwater (Check original code!)
public void Init(PlayerInfo player)
private void Update()
{
if (!_initialized)
{
_attachedPlayer = player;
return;
}
_thrusterRenderer = GetComponent<MeshRenderer>();
_thrusterFilter = OWUtilities.GetShipThrusterFilter(_thruster);
_baseLightRadius = _light.range;
var num = _scaleByThrust.Evaluate(GetThrustFraction());
if (_belowMaxThrustScalar < 1f)
{
num *= _belowMaxThrustScalar;
}
_currentScale = _scaleSpring.Update(_currentScale, num, Time.deltaTime);
if (_currentScale < 0f)
{
_currentScale = 0f;
_thrusterRenderer.enabled = false;
_light.enabled = false;
_light.shadows = LightShadows.Soft;
_initialized = true;
_scaleSpring.ResetVelocity();
}
private void Update()
if (_currentScale <= 0.001f)
{
if (!_initialized)
{
return;
}
var num = _scaleByThrust.Evaluate(GetThrustFraction());
if (_belowMaxThrustScalar < 1f)
{
num *= _belowMaxThrustScalar;
}
_currentScale = _scaleSpring.Update(_currentScale, num, Time.deltaTime);
if (_currentScale < 0f)
{
_currentScale = 0f;
_scaleSpring.ResetVelocity();
}
if (_currentScale <= 0.001f)
{
_currentScale = 0f;
_scaleSpring.ResetVelocity();
}
transform.localScale = Vector3.one * _currentScale;
_light.range = _baseLightRadius * _currentScale;
_thrusterRenderer.enabled = _currentScale > 0f;
_light.enabled = _currentScale > 0f;
_currentScale = 0f;
_scaleSpring.ResetVelocity();
}
private float GetThrustFraction() => Vector3.Dot(_attachedPlayer.JetpackAcceleration.AccelerationVariableSyncer.Value, _thrusterFilter);
transform.localScale = Vector3.one * _currentScale;
_light.range = _baseLightRadius * _currentScale;
_thrusterRenderer.enabled = _currentScale > 0f;
_light.enabled = _currentScale > 0f;
}
private void OnRenderObject()
private float GetThrustFraction() => Vector3.Dot(_attachedPlayer.JetpackAcceleration.AccelerationVariableSyncer.Value, _thrusterFilter);
private void OnRenderObject()
{
if (!QSBCore.DebugSettings.DrawLines || !QSBWorldSync.AllObjectsReady)
{
if (!QSBCore.DebugSettings.DrawLines || !QSBWorldSync.AllObjectsReady)
{
return;
}
Popcron.Gizmos.Sphere(_light.transform.position, 0.05f, Color.yellow, 4);
Popcron.Gizmos.Line(_light.transform.position, _light.transform.parent.position, Color.yellow);
return;
}
Popcron.Gizmos.Sphere(_light.transform.position, 0.05f, Color.yellow, 4);
Popcron.Gizmos.Line(_light.transform.position, _light.transform.parent.position, Color.yellow);
}
}

View File

@ -2,87 +2,86 @@
using QSB.Utility;
using UnityEngine;
namespace QSB.Animation.Player.Thrusters
namespace QSB.Animation.Player.Thrusters;
internal class RemoteThrusterWashController : MonoBehaviour
{
internal class RemoteThrusterWashController : MonoBehaviour
[SerializeField]
private float _raycastDistance = 10f;
[SerializeField]
private AnimationCurve _emissionDistanceScale;
[SerializeField]
private AnimationCurve _emissionThrusterScale;
[SerializeField]
private ParticleSystem _defaultParticleSystem;
private ParticleSystem.MainModule _defaultMainModule;
private ParticleSystem.EmissionModule _defaultEmissionModule;
private float _baseDefaultEmissionRate;
private PlayerInfo _attachedPlayer;
private bool _initialised;
public void Init(PlayerInfo player)
{
[SerializeField]
private float _raycastDistance = 10f;
_attachedPlayer = player;
[SerializeField]
private AnimationCurve _emissionDistanceScale;
[SerializeField]
private AnimationCurve _emissionThrusterScale;
[SerializeField]
private ParticleSystem _defaultParticleSystem;
private ParticleSystem.MainModule _defaultMainModule;
private ParticleSystem.EmissionModule _defaultEmissionModule;
private float _baseDefaultEmissionRate;
private PlayerInfo _attachedPlayer;
private bool _initialised;
public void Init(PlayerInfo player)
if (_defaultParticleSystem == null)
{
_attachedPlayer = player;
if (_defaultParticleSystem == null)
{
DebugLog.ToConsole($"Error - DefaultParticleSystem is null!", OWML.Common.MessageType.Error);
return;
}
_defaultMainModule = _defaultParticleSystem.main;
_defaultEmissionModule = _defaultParticleSystem.emission;
_baseDefaultEmissionRate = _defaultEmissionModule.rateOverTime.constant;
_initialised = true;
DebugLog.ToConsole($"Error - DefaultParticleSystem is null!", OWML.Common.MessageType.Error);
return;
}
private void Update()
_defaultMainModule = _defaultParticleSystem.main;
_defaultEmissionModule = _defaultParticleSystem.emission;
_baseDefaultEmissionRate = _defaultEmissionModule.rateOverTime.constant;
_initialised = true;
}
private void Update()
{
if (!_initialised)
{
if (!_initialised)
return;
}
RaycastHit hitInfo = default;
var aboveSurface = false;
var emissionThrusterScale = _emissionThrusterScale.Evaluate(_attachedPlayer.JetpackAcceleration.AccelerationVariableSyncer.Value.y);
if (emissionThrusterScale > 0f)
{
aboveSurface = Physics.Raycast(transform.position, transform.forward, out hitInfo, _raycastDistance, OWLayerMask.physicalMask);
}
emissionThrusterScale = (!aboveSurface) ? 0f : (emissionThrusterScale * _emissionDistanceScale.Evaluate(hitInfo.distance));
if (emissionThrusterScale > 0f)
{
var position = hitInfo.point + (hitInfo.normal * 0.25f);
var rotation = Quaternion.LookRotation(hitInfo.normal);
if (!_defaultParticleSystem.isPlaying)
{
return;
_defaultParticleSystem.Play();
}
RaycastHit hitInfo = default;
var aboveSurface = false;
var emissionThrusterScale = _emissionThrusterScale.Evaluate(_attachedPlayer.JetpackAcceleration.AccelerationVariableSyncer.Value.y);
if (emissionThrusterScale > 0f)
_defaultEmissionModule.rateOverTimeMultiplier = _baseDefaultEmissionRate * emissionThrusterScale;
_defaultParticleSystem.transform.SetPositionAndRotation(position, rotation);
if (_defaultMainModule.customSimulationSpace != hitInfo.transform)
{
aboveSurface = Physics.Raycast(transform.position, transform.forward, out hitInfo, _raycastDistance, OWLayerMask.physicalMask);
_defaultMainModule.customSimulationSpace = hitInfo.transform;
_defaultParticleSystem.Clear();
}
emissionThrusterScale = (!aboveSurface) ? 0f : (emissionThrusterScale * _emissionDistanceScale.Evaluate(hitInfo.distance));
if (emissionThrusterScale > 0f)
}
else
{
if (_defaultParticleSystem.isPlaying)
{
var position = hitInfo.point + (hitInfo.normal * 0.25f);
var rotation = Quaternion.LookRotation(hitInfo.normal);
if (!_defaultParticleSystem.isPlaying)
{
_defaultParticleSystem.Play();
}
_defaultEmissionModule.rateOverTimeMultiplier = _baseDefaultEmissionRate * emissionThrusterScale;
_defaultParticleSystem.transform.SetPositionAndRotation(position, rotation);
if (_defaultMainModule.customSimulationSpace != hitInfo.transform)
{
_defaultMainModule.customSimulationSpace = hitInfo.transform;
_defaultParticleSystem.Clear();
}
}
else
{
if (_defaultParticleSystem.isPlaying)
{
_defaultParticleSystem.Stop(false, ParticleSystemStopBehavior.StopEmitting);
}
_defaultParticleSystem.Stop(false, ParticleSystemStopBehavior.StopEmitting);
}
}
}

View File

@ -1,31 +1,30 @@
using QSB.Player;
using UnityEngine;
namespace QSB.Animation.Player.Thrusters
namespace QSB.Animation.Player.Thrusters;
internal static class ThrusterManager
{
internal static class ThrusterManager
public static void CreateRemotePlayerVFX(PlayerInfo player)
{
public static void CreateRemotePlayerVFX(PlayerInfo player)
{
var newVfx = player.Body.transform.Find("REMOTE_PlayerVFX").gameObject;
var newVfx = player.Body.transform.Find("REMOTE_PlayerVFX").gameObject;
CreateThrusterWashController(newVfx.transform.Find("ThrusterWash").gameObject, player);
CreateThrusterFlameController(newVfx, player);
}
CreateThrusterWashController(newVfx.transform.Find("ThrusterWash").gameObject, player);
CreateThrusterFlameController(newVfx, player);
}
private static void CreateThrusterFlameController(GameObject root, PlayerInfo player)
private static void CreateThrusterFlameController(GameObject root, PlayerInfo player)
{
var existingControllers = root.GetComponentsInChildren<RemoteThrusterFlameController>(true);
foreach (var controller in existingControllers)
{
var existingControllers = root.GetComponentsInChildren<RemoteThrusterFlameController>(true);
foreach (var controller in existingControllers)
{
controller.Init(player);
}
}
private static void CreateThrusterWashController(GameObject root, PlayerInfo player)
{
var newObj = root.GetComponent<RemoteThrusterWashController>();
newObj.Init(player);
controller.Init(player);
}
}
private static void CreateThrusterWashController(GameObject root, PlayerInfo player)
{
var newObj = root.GetComponent<RemoteThrusterWashController>();
newObj.Init(player);
}
}

View File

@ -1,9 +1,8 @@
namespace QSB.Audio
namespace QSB.Audio;
internal class QSBJetpackThrusterAudio : QSBThrusterAudio
{
internal class QSBJetpackThrusterAudio : QSBThrusterAudio
{
public OWAudioSource _underwaterSource;
public OWAudioSource _oxygenSource;
public OWAudioSource _boostSource;
}
public OWAudioSource _underwaterSource;
public OWAudioSource _oxygenSource;
public OWAudioSource _boostSource;
}

View File

@ -1,22 +1,21 @@
using UnityEngine;
namespace QSB.Audio
namespace QSB.Audio;
public class QSBPlayerAudioController : MonoBehaviour
{
public class QSBPlayerAudioController : MonoBehaviour
{
public OWAudioSource _oneShotExternalSource;
public OWAudioSource _repairToolSource;
public OWAudioSource _oneShotExternalSource;
public OWAudioSource _repairToolSource;
public void PlayEquipTool()
=> _oneShotExternalSource?.PlayOneShot(AudioType.ToolTranslatorEquip);
public void PlayEquipTool()
=> _oneShotExternalSource?.PlayOneShot(AudioType.ToolTranslatorEquip);
public void PlayUnequipTool()
=> _oneShotExternalSource?.PlayOneShot(AudioType.ToolTranslatorUnequip);
public void PlayUnequipTool()
=> _oneShotExternalSource?.PlayOneShot(AudioType.ToolTranslatorUnequip);
public void PlayTurnOnFlashlight()
=> _oneShotExternalSource?.PlayOneShot(AudioType.ToolFlashlightOn);
public void PlayTurnOnFlashlight()
=> _oneShotExternalSource?.PlayOneShot(AudioType.ToolFlashlightOn);
public void PlayTurnOffFlashlight()
=> _oneShotExternalSource?.PlayOneShot(AudioType.ToolFlashlightOff);
}
public void PlayTurnOffFlashlight()
=> _oneShotExternalSource?.PlayOneShot(AudioType.ToolFlashlightOff);
}

View File

@ -1,10 +1,9 @@
using UnityEngine;
namespace QSB.Audio
namespace QSB.Audio;
internal class QSBThrusterAudio : MonoBehaviour
{
internal class QSBThrusterAudio : MonoBehaviour
{
public OWAudioSource _translationalSource;
public OWAudioSource _rotationalSource;
}
public OWAudioSource _translationalSource;
public OWAudioSource _rotationalSource;
}

View File

@ -2,38 +2,37 @@
using QSB.Messaging;
using QSB.WorldSync;
namespace QSB.AuthoritySync
namespace QSB.AuthoritySync;
/// <summary>
/// always sent to host
/// </summary>
public class AuthQueueMessage : QSBMessage<(uint NetId, AuthQueueAction Action)>
{
public AuthQueueMessage(uint netId, AuthQueueAction action)
{
To = 0;
Data.NetId = netId;
Data.Action = action;
}
public override bool ShouldReceive => QSBWorldSync.AllObjectsReady;
public override void OnReceiveLocal() => OnReceiveRemote();
public override void OnReceiveRemote() => NetworkServer.spawned[Data.NetId].ServerUpdateAuthQueue(From, Data.Action);
}
public enum AuthQueueAction
{
/// <summary>
/// always sent to host
/// add player to the queue
/// </summary>
public class AuthQueueMessage : QSBMessage<(uint NetId, AuthQueueAction Action)>
{
public AuthQueueMessage(uint netId, AuthQueueAction action)
{
To = 0;
Data.NetId = netId;
Data.Action = action;
}
public override bool ShouldReceive => QSBWorldSync.AllObjectsReady;
public override void OnReceiveLocal() => OnReceiveRemote();
public override void OnReceiveRemote() => NetworkServer.spawned[Data.NetId].ServerUpdateAuthQueue(From, Data.Action);
}
public enum AuthQueueAction
{
/// <summary>
/// add player to the queue
/// </summary>
Add,
/// <summary>
/// remove player from the queue
/// </summary>
Remove,
/// <summary>
/// add player to the queue and force them to the front
/// </summary>
Force
}
}
Add,
/// <summary>
/// remove player from the queue
/// </summary>
Remove,
/// <summary>
/// add player to the queue and force them to the front
/// </summary>
Force
}

View File

@ -3,95 +3,94 @@ using QSB.Messaging;
using QSB.Utility;
using System.Collections.Generic;
namespace QSB.AuthoritySync
namespace QSB.AuthoritySync;
public static class AuthorityManager
{
public static class AuthorityManager
#region host only
/// <summary>
/// whoever is first gets authority
/// </summary>
private static readonly Dictionary<NetworkIdentity, List<uint>> _authQueue = new();
public static void RegisterAuthQueue(this NetworkIdentity identity) => _authQueue.Add(identity, new List<uint>());
public static void UnregisterAuthQueue(this NetworkIdentity identity) => _authQueue.Remove(identity);
public static void ServerUpdateAuthQueue(this NetworkIdentity identity, uint id, AuthQueueAction action)
{
#region host only
var authQueue = _authQueue[identity];
var oldOwner = authQueue.Count != 0 ? authQueue[0] : uint.MaxValue;
/// <summary>
/// whoever is first gets authority
/// </summary>
private static readonly Dictionary<NetworkIdentity, List<uint>> _authQueue = new();
public static void RegisterAuthQueue(this NetworkIdentity identity) => _authQueue.Add(identity, new List<uint>());
public static void UnregisterAuthQueue(this NetworkIdentity identity) => _authQueue.Remove(identity);
public static void ServerUpdateAuthQueue(this NetworkIdentity identity, uint id, AuthQueueAction action)
switch (action)
{
var authQueue = _authQueue[identity];
var oldOwner = authQueue.Count != 0 ? authQueue[0] : uint.MaxValue;
case AuthQueueAction.Add:
authQueue.SafeAdd(id);
break;
switch (action)
{
case AuthQueueAction.Add:
authQueue.SafeAdd(id);
break;
case AuthQueueAction.Remove:
authQueue.Remove(id);
break;
case AuthQueueAction.Remove:
authQueue.Remove(id);
break;
case AuthQueueAction.Force:
authQueue.Remove(id);
authQueue.Insert(0, id);
break;
}
var newOwner = authQueue.Count != 0 ? authQueue[0] : uint.MaxValue;
if (oldOwner != newOwner)
{
SetAuthority(identity, newOwner);
}
case AuthQueueAction.Force:
authQueue.Remove(id);
authQueue.Insert(0, id);
break;
}
/// <summary>
/// transfer authority to a different client
/// </summary>
public static void OnDisconnect(NetworkConnectionToClient conn)
var newOwner = authQueue.Count != 0 ? authQueue[0] : uint.MaxValue;
if (oldOwner != newOwner)
{
var id = conn.GetPlayerId();
foreach (var identity in _authQueue.Keys)
{
identity.ServerUpdateAuthQueue(id, AuthQueueAction.Remove);
}
SetAuthority(identity, newOwner);
}
public static void SetAuthority(this NetworkIdentity identity, uint id)
{
var oldConn = identity.connectionToClient;
var newConn = id != uint.MaxValue ? id.GetNetworkConnection() : null;
if (oldConn == newConn)
{
return;
}
identity.RemoveClientAuthority();
if (newConn != null)
{
identity.AssignClientAuthority(newConn);
}
// DebugLog.DebugWrite($"{identity.NetId}:{identity.gameObject.name} - "
// + $"set authority to {id}");
}
#endregion
#region any client
public static void UpdateAuthQueue(this NetworkIdentity identity, AuthQueueAction action)
{
if (action == AuthQueueAction.Force && identity.hasAuthority)
{
return;
}
new AuthQueueMessage(identity.netId, action).Send();
}
#endregion
}
/// <summary>
/// transfer authority to a different client
/// </summary>
public static void OnDisconnect(NetworkConnectionToClient conn)
{
var id = conn.GetPlayerId();
foreach (var identity in _authQueue.Keys)
{
identity.ServerUpdateAuthQueue(id, AuthQueueAction.Remove);
}
}
public static void SetAuthority(this NetworkIdentity identity, uint id)
{
var oldConn = identity.connectionToClient;
var newConn = id != uint.MaxValue ? id.GetNetworkConnection() : null;
if (oldConn == newConn)
{
return;
}
identity.RemoveClientAuthority();
if (newConn != null)
{
identity.AssignClientAuthority(newConn);
}
// DebugLog.DebugWrite($"{identity.NetId}:{identity.gameObject.name} - "
// + $"set authority to {id}");
}
#endregion
#region any client
public static void UpdateAuthQueue(this NetworkIdentity identity, AuthQueueAction action)
{
if (action == AuthQueueAction.Force && identity.hasAuthority)
{
return;
}
new AuthQueueMessage(identity.netId, action).Send();
}
#endregion
}

View File

@ -3,13 +3,12 @@ using QSB.CampfireSync.WorldObjects;
using QSB.WorldSync;
using System.Threading;
namespace QSB.CampfireSync
{
internal class CampfireManager : WorldObjectManager
{
public override WorldObjectScene WorldObjectScene => WorldObjectScene.Both;
namespace QSB.CampfireSync;
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct)
=> QSBWorldSync.Init<QSBCampfire, Campfire>();
}
internal class CampfireManager : WorldObjectManager
{
public override WorldObjectScene WorldObjectScene => WorldObjectScene.Both;
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct)
=> QSBWorldSync.Init<QSBCampfire, Campfire>();
}

View File

@ -1,12 +1,11 @@
using QSB.CampfireSync.WorldObjects;
using QSB.Messaging;
namespace QSB.CampfireSync.Messages
{
internal class CampfireStateMessage : QSBWorldObjectMessage<QSBCampfire, Campfire.State>
{
public CampfireStateMessage(Campfire.State state) => Data = state;
namespace QSB.CampfireSync.Messages;
public override void OnReceiveRemote() => WorldObject.SetState(Data);
}
internal class CampfireStateMessage : QSBWorldObjectMessage<QSBCampfire, Campfire.State>
{
public CampfireStateMessage(Campfire.State state) => Data = state;
public override void OnReceiveRemote() => WorldObject.SetState(Data);
}

View File

@ -6,80 +6,79 @@ using QSB.Patches;
using QSB.WorldSync;
using UnityEngine;
namespace QSB.CampfireSync.Patches
namespace QSB.CampfireSync.Patches;
[HarmonyPatch]
internal class CampfirePatches : QSBPatch
{
[HarmonyPatch]
internal class CampfirePatches : QSBPatch
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
[HarmonyPrefix]
[HarmonyPatch(typeof(Campfire), nameof(Campfire.OnPressInteract))]
public static bool LightCampfireEvent(Campfire __instance)
{
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
[HarmonyPrefix]
[HarmonyPatch(typeof(Campfire), nameof(Campfire.OnPressInteract))]
public static bool LightCampfireEvent(Campfire __instance)
var qsbCampfire = __instance.GetWorldObject<QSBCampfire>();
if (__instance._state == Campfire.State.LIT)
{
var qsbCampfire = __instance.GetWorldObject<QSBCampfire>();
if (__instance._state == Campfire.State.LIT)
{
qsbCampfire.StartRoasting();
}
else
{
qsbCampfire.SetState(Campfire.State.LIT);
qsbCampfire.SendMessage(new CampfireStateMessage(Campfire.State.LIT));
Locator.GetFlashlight().TurnOff(false);
}
return false;
qsbCampfire.StartRoasting();
}
else
{
qsbCampfire.SetState(Campfire.State.LIT);
qsbCampfire.SendMessage(new CampfireStateMessage(Campfire.State.LIT));
Locator.GetFlashlight().TurnOff(false);
}
[HarmonyPrefix]
[HarmonyPatch(typeof(Campfire), nameof(Campfire.Update))]
public static bool UpdateReplacement(Campfire __instance)
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(Campfire), nameof(Campfire.Update))]
public static bool UpdateReplacement(Campfire __instance)
{
var targetLitFraction = 0f;
switch (__instance._state)
{
var targetLitFraction = 0f;
switch (__instance._state)
{
case Campfire.State.UNLIT:
targetLitFraction = 0f;
break;
case Campfire.State.LIT:
targetLitFraction = 1f;
break;
case Campfire.State.SMOLDERING:
targetLitFraction = 0.4f;
break;
}
if (__instance._litFraction != targetLitFraction)
{
__instance.SetLitFraction(Mathf.MoveTowards(__instance._litFraction, targetLitFraction, Time.deltaTime));
}
if (__instance._canSleepHere)
{
__instance._sleepPrompt.SetVisibility(false);
if (__instance._interactVolumeFocus && !__instance._isPlayerSleeping && !__instance._isPlayerRoasting && OWInput.IsInputMode(InputMode.Character))
{
__instance._sleepPrompt.SetVisibility(true);
__instance._sleepPrompt.SetDisplayState(__instance.CanSleepHereNow() ? ScreenPrompt.DisplayState.Normal : ScreenPrompt.DisplayState.GrayedOut);
if (OWInput.IsNewlyPressed(InputLibrary.interactSecondary, InputMode.All) && __instance.CanSleepHereNow())
{
__instance.StartSleeping();
}
}
}
if (__instance._isPlayerSleeping)
{
__instance._wakePrompt.SetVisibility(OWInput.IsInputMode(InputMode.None) && Time.timeSinceLevelLoad - __instance._fastForwardStartTime > __instance.GetWakePromptDelay());
if (__instance.ShouldWakeUp())
{
__instance.StopSleeping(false);
return false;
}
}
return false;
case Campfire.State.UNLIT:
targetLitFraction = 0f;
break;
case Campfire.State.LIT:
targetLitFraction = 1f;
break;
case Campfire.State.SMOLDERING:
targetLitFraction = 0.4f;
break;
}
if (__instance._litFraction != targetLitFraction)
{
__instance.SetLitFraction(Mathf.MoveTowards(__instance._litFraction, targetLitFraction, Time.deltaTime));
}
if (__instance._canSleepHere)
{
__instance._sleepPrompt.SetVisibility(false);
if (__instance._interactVolumeFocus && !__instance._isPlayerSleeping && !__instance._isPlayerRoasting && OWInput.IsInputMode(InputMode.Character))
{
__instance._sleepPrompt.SetVisibility(true);
__instance._sleepPrompt.SetDisplayState(__instance.CanSleepHereNow() ? ScreenPrompt.DisplayState.Normal : ScreenPrompt.DisplayState.GrayedOut);
if (OWInput.IsNewlyPressed(InputLibrary.interactSecondary, InputMode.All) && __instance.CanSleepHereNow())
{
__instance.StartSleeping();
}
}
}
if (__instance._isPlayerSleeping)
{
__instance._wakePrompt.SetVisibility(OWInput.IsInputMode(InputMode.None) && Time.timeSinceLevelLoad - __instance._fastForwardStartTime > __instance.GetWakePromptDelay());
if (__instance.ShouldWakeUp())
{
__instance.StopSleeping(false);
return false;
}
}
return false;
}
}

View File

@ -2,20 +2,19 @@
using QSB.Messaging;
using QSB.WorldSync;
namespace QSB.CampfireSync.WorldObjects
namespace QSB.CampfireSync.WorldObjects;
public class QSBCampfire : WorldObject<Campfire>
{
public class QSBCampfire : WorldObject<Campfire>
{
public override void SendInitialState(uint to) =>
this.SendMessage(new CampfireStateMessage(GetState()) { To = to });
public override void SendInitialState(uint to) =>
this.SendMessage(new CampfireStateMessage(GetState()) { To = to });
public void StartRoasting()
=> AttachedObject.StartRoasting();
public void StartRoasting()
=> AttachedObject.StartRoasting();
public Campfire.State GetState()
=> AttachedObject.GetState();
public Campfire.State GetState()
=> AttachedObject.GetState();
public void SetState(Campfire.State newState)
=> AttachedObject.SetState(newState);
}
public void SetState(Campfire.State newState)
=> AttachedObject.SetState(newState);
}

View File

@ -1,14 +1,13 @@
namespace QSB.ClientServerStateSync
namespace QSB.ClientServerStateSync;
public enum ClientState
{
public enum ClientState
{
NotLoaded,
InTitleScreen,
AliveInSolarSystem,
DeadInSolarSystem,
AliveInEye,
WaitingForOthersToBeReady,
WatchingLongCredits,
WatchingShortCredits
}
NotLoaded,
InTitleScreen,
AliveInSolarSystem,
DeadInSolarSystem,
AliveInEye,
WaitingForOthersToBeReady,
WatchingLongCredits,
WatchingShortCredits
}

View File

@ -5,182 +5,181 @@ using QSB.Player.TransformSync;
using QSB.Utility;
using UnityEngine;
namespace QSB.ClientServerStateSync
namespace QSB.ClientServerStateSync;
internal class ClientStateManager : MonoBehaviour
{
internal class ClientStateManager : MonoBehaviour
public static ClientStateManager Instance { get; private set; }
public event ChangeStateEvent OnChangeState;
public delegate void ChangeStateEvent(ClientState newState);
private void Awake()
=> Instance = this;
private void Start()
{
public static ClientStateManager Instance { get; private set; }
QSBSceneManager.OnSceneLoaded += OnSceneLoaded;
Delay.RunWhen(() => PlayerTransformSync.LocalInstance != null,
() => new ClientStateMessage(ForceGetCurrentState()).Send());
}
public event ChangeStateEvent OnChangeState;
public delegate void ChangeStateEvent(ClientState newState);
private void OnDestroy() =>
QSBSceneManager.OnSceneLoaded -= OnSceneLoaded;
private void Awake()
=> Instance = this;
private void Start()
public void ChangeClientState(ClientState newState)
{
if (PlayerTransformSync.LocalInstance == null || QSBPlayerManager.LocalPlayer.State == newState)
{
QSBSceneManager.OnSceneLoaded += OnSceneLoaded;
Delay.RunWhen(() => PlayerTransformSync.LocalInstance != null,
() => new ClientStateMessage(ForceGetCurrentState()).Send());
return;
}
private void OnDestroy() =>
QSBSceneManager.OnSceneLoaded -= OnSceneLoaded;
QSBPlayerManager.LocalPlayer.State = newState;
OnChangeState?.Invoke(newState);
}
public void ChangeClientState(ClientState newState)
private void OnSceneLoaded(OWScene oldScene, OWScene newScene, bool inUniverse)
{
var serverState = ServerStateManager.Instance.GetServerState();
ClientState newState;
if (QSBCore.IsHost)
{
if (PlayerTransformSync.LocalInstance == null || QSBPlayerManager.LocalPlayer.State == newState)
{
return;
}
QSBPlayerManager.LocalPlayer.State = newState;
OnChangeState?.Invoke(newState);
switch (newScene)
{
case OWScene.TitleScreen:
newState = ClientState.InTitleScreen;
break;
case OWScene.Credits_Fast:
newState = ClientState.WatchingShortCredits;
break;
case OWScene.Credits_Final:
case OWScene.PostCreditsScene:
newState = ClientState.WatchingLongCredits;
break;
case OWScene.SolarSystem:
if (oldScene == OWScene.SolarSystem)
{
// reloading scene
newState = ClientState.WaitingForOthersToBeReady;
}
else
{
// loading in from title screen
newState = ClientState.AliveInSolarSystem;
}
break;
case OWScene.EyeOfTheUniverse:
newState = ClientState.AliveInEye;
break;
default:
newState = ClientState.NotLoaded;
break;
}
}
private void OnSceneLoaded(OWScene oldScene, OWScene newScene, bool inUniverse)
else
{
var serverState = ServerStateManager.Instance.GetServerState();
ClientState newState;
if (QSBCore.IsHost)
switch (newScene)
{
case OWScene.TitleScreen:
newState = ClientState.InTitleScreen;
break;
case OWScene.Credits_Fast:
newState = ClientState.WatchingShortCredits;
break;
case OWScene.Credits_Final:
case OWScene.PostCreditsScene:
newState = ClientState.WatchingLongCredits;
break;
case OWScene.SolarSystem:
if (serverState == ServerState.WaitingForAllPlayersToDie)
{
newState = ClientState.WaitingForOthersToBeReady;
break;
}
switch (newScene)
{
case OWScene.TitleScreen:
newState = ClientState.InTitleScreen;
break;
case OWScene.Credits_Fast:
newState = ClientState.WatchingShortCredits;
break;
case OWScene.Credits_Final:
case OWScene.PostCreditsScene:
newState = ClientState.WatchingLongCredits;
break;
case OWScene.SolarSystem:
if (oldScene == OWScene.SolarSystem)
{
// reloading scene
newState = ClientState.WaitingForOthersToBeReady;
}
else
{
// loading in from title screen
newState = ClientState.AliveInSolarSystem;
}
break;
case OWScene.EyeOfTheUniverse:
newState = ClientState.AliveInEye;
break;
default:
newState = ClientState.NotLoaded;
break;
}
}
else
{
switch (newScene)
{
case OWScene.TitleScreen:
newState = ClientState.InTitleScreen;
break;
case OWScene.Credits_Fast:
newState = ClientState.WatchingShortCredits;
break;
case OWScene.Credits_Final:
case OWScene.PostCreditsScene:
newState = ClientState.WatchingLongCredits;
break;
case OWScene.SolarSystem:
if (serverState == ServerState.WaitingForAllPlayersToDie)
{
newState = ClientState.WaitingForOthersToBeReady;
break;
}
if (oldScene == OWScene.SolarSystem)
{
// reloading scene
newState = ClientState.WaitingForOthersToBeReady;
}
else
{
// loading in from title screen
if (serverState == ServerState.WaitingForAllPlayersToReady)
{
newState = ClientState.WaitingForOthersToBeReady;
}
else
{
newState = ClientState.AliveInSolarSystem;
}
}
break;
case OWScene.EyeOfTheUniverse:
if (oldScene == OWScene.SolarSystem)
{
// reloading scene
newState = ClientState.WaitingForOthersToBeReady;
}
else
{
// loading in from title screen
if (serverState == ServerState.WaitingForAllPlayersToReady)
{
newState = ClientState.WaitingForOthersToBeReady;
}
else
{
newState = ClientState.AliveInEye;
newState = ClientState.AliveInSolarSystem;
}
}
break;
default:
newState = ClientState.NotLoaded;
break;
}
}
break;
case OWScene.EyeOfTheUniverse:
if (serverState == ServerState.WaitingForAllPlayersToReady)
{
newState = ClientState.WaitingForOthersToBeReady;
}
else
{
newState = ClientState.AliveInEye;
}
new ClientStateMessage(newState).Send();
}
public void OnDeath()
{
var currentScene = QSBSceneManager.CurrentScene;
if (currentScene == OWScene.SolarSystem)
{
new ClientStateMessage(ClientState.DeadInSolarSystem).Send();
}
else if (currentScene == OWScene.EyeOfTheUniverse)
{
DebugLog.ToConsole($"Error - You died in the Eye? HOW DID YOU DO THAT?!", OWML.Common.MessageType.Error);
}
else
{
// whaaaaaaaaa
DebugLog.ToConsole($"Error - You died... in a menu? In the credits? In any case, you should never see this. :P", OWML.Common.MessageType.Error);
break;
default:
newState = ClientState.NotLoaded;
break;
}
}
public void OnRespawn()
{
var currentScene = QSBSceneManager.CurrentScene;
if (currentScene == OWScene.SolarSystem)
{
DebugLog.DebugWrite($"RESPAWN!");
new ClientStateMessage(ClientState.AliveInSolarSystem).Send();
}
else
{
DebugLog.ToConsole($"Error - Player tried to respawn in scene {currentScene}", OWML.Common.MessageType.Error);
}
}
private static ClientState ForceGetCurrentState()
=> QSBSceneManager.CurrentScene switch
{
OWScene.TitleScreen => ClientState.InTitleScreen,
OWScene.Credits_Fast => ClientState.WatchingShortCredits,
OWScene.Credits_Final or OWScene.PostCreditsScene => ClientState.WatchingLongCredits,
OWScene.SolarSystem => ClientState.AliveInSolarSystem,
OWScene.EyeOfTheUniverse => ClientState.AliveInEye,
_ => ClientState.NotLoaded
};
new ClientStateMessage(newState).Send();
}
public void OnDeath()
{
var currentScene = QSBSceneManager.CurrentScene;
if (currentScene == OWScene.SolarSystem)
{
new ClientStateMessage(ClientState.DeadInSolarSystem).Send();
}
else if (currentScene == OWScene.EyeOfTheUniverse)
{
DebugLog.ToConsole($"Error - You died in the Eye? HOW DID YOU DO THAT?!", OWML.Common.MessageType.Error);
}
else
{
// whaaaaaaaaa
DebugLog.ToConsole($"Error - You died... in a menu? In the credits? In any case, you should never see this. :P", OWML.Common.MessageType.Error);
}
}
public void OnRespawn()
{
var currentScene = QSBSceneManager.CurrentScene;
if (currentScene == OWScene.SolarSystem)
{
DebugLog.DebugWrite($"RESPAWN!");
new ClientStateMessage(ClientState.AliveInSolarSystem).Send();
}
else
{
DebugLog.ToConsole($"Error - Player tried to respawn in scene {currentScene}", OWML.Common.MessageType.Error);
}
}
private static ClientState ForceGetCurrentState()
=> QSBSceneManager.CurrentScene switch
{
OWScene.TitleScreen => ClientState.InTitleScreen,
OWScene.Credits_Fast => ClientState.WatchingShortCredits,
OWScene.Credits_Final or OWScene.PostCreditsScene => ClientState.WatchingLongCredits,
OWScene.SolarSystem => ClientState.AliveInSolarSystem,
OWScene.EyeOfTheUniverse => ClientState.AliveInEye,
_ => ClientState.NotLoaded
};
}

View File

@ -3,28 +3,27 @@ using QSB.Messaging;
using QSB.Player;
using QSB.Utility;
namespace QSB.ClientServerStateSync.Messages
namespace QSB.ClientServerStateSync.Messages;
/// <summary>
/// sets the state both locally and remotely
/// </summary>
internal class ClientStateMessage : QSBMessage<ClientState>
{
/// <summary>
/// sets the state both locally and remotely
/// </summary>
internal class ClientStateMessage : QSBMessage<ClientState>
public ClientStateMessage(ClientState state) => Data = state;
public override void OnReceiveLocal()
=> ClientStateManager.Instance.ChangeClientState(Data);
public override void OnReceiveRemote()
{
public ClientStateMessage(ClientState state) => Data = state;
public override void OnReceiveLocal()
=> ClientStateManager.Instance.ChangeClientState(Data);
public override void OnReceiveRemote()
if (From == uint.MaxValue)
{
if (From == uint.MaxValue)
{
DebugLog.ToConsole($"Error - ID is uint.MaxValue!", MessageType.Error);
return;
}
var player = QSBPlayerManager.GetPlayer(From);
player.State = Data;
DebugLog.ToConsole($"Error - ID is uint.MaxValue!", MessageType.Error);
return;
}
var player = QSBPlayerManager.GetPlayer(From);
player.State = Data;
}
}

View File

@ -1,16 +1,15 @@
using QSB.Messaging;
namespace QSB.ClientServerStateSync.Messages
{
/// <summary>
/// sets the state both locally and remotely
/// </summary>
internal class ServerStateMessage : QSBMessage<ServerState>
{
public ServerStateMessage(ServerState state) => Data = state;
namespace QSB.ClientServerStateSync.Messages;
public override void OnReceiveLocal() => OnReceiveRemote();
public override void OnReceiveRemote()
=> ServerStateManager.Instance.ChangeServerState(Data);
}
/// <summary>
/// sets the state both locally and remotely
/// </summary>
internal class ServerStateMessage : QSBMessage<ServerState>
{
public ServerStateMessage(ServerState state) => Data = state;
public override void OnReceiveLocal() => OnReceiveRemote();
public override void OnReceiveRemote()
=> ServerStateManager.Instance.ChangeServerState(Data);
}

View File

@ -1,26 +1,25 @@
namespace QSB.ClientServerStateSync
namespace QSB.ClientServerStateSync;
public enum ServerState
{
public enum ServerState
{
// When in menus
NotLoaded,
// When in menus
NotLoaded,
// When in any credits
Credits,
// When in any credits
Credits,
// For normal play in SolarSystem
InSolarSystem,
// For normal play in SolarSystem
InSolarSystem,
// For normal play in EyeOfTheUniverse
InEye,
// For normal play in EyeOfTheUniverse
InEye,
// At end of loop, waiting for everyone to be ready to reload the scene
WaitingForAllPlayersToDie,
// At end of loop, waiting for everyone to be ready to reload the scene
WaitingForAllPlayersToDie,
// At start of loop, waiting for everybody to be ready to start playing
WaitingForAllPlayersToReady,
// At start of loop, waiting for everybody to be ready to start playing
WaitingForAllPlayersToReady,
// When the statue has been activated
InStatueCutscene
}
// When the statue has been activated
InStatueCutscene
}

View File

@ -7,155 +7,154 @@ using QSB.Utility;
using System.Linq;
using UnityEngine;
namespace QSB.ClientServerStateSync
namespace QSB.ClientServerStateSync;
internal class ServerStateManager : MonoBehaviour
{
internal class ServerStateManager : MonoBehaviour
public static ServerStateManager Instance { get; private set; }
public event ChangeStateEvent OnChangeState;
public delegate void ChangeStateEvent(ServerState newState);
private ServerState _currentState;
private bool _blockNextCheck;
private void Awake()
=> Instance = this;
private void Start()
{
public static ServerStateManager Instance { get; private set; }
public event ChangeStateEvent OnChangeState;
public delegate void ChangeStateEvent(ServerState newState);
private ServerState _currentState;
private bool _blockNextCheck;
private void Awake()
=> Instance = this;
private void Start()
if (!QSBCore.IsHost)
{
if (!QSBCore.IsHost)
{
return;
}
QSBSceneManager.OnSceneLoaded += OnSceneLoaded;
GlobalMessenger.AddListener("TriggerSupernova", OnTriggerSupernova);
Delay.RunWhen(() => PlayerTransformSync.LocalInstance != null,
() => new ServerStateMessage(ForceGetCurrentState()).Send());
return;
}
private void OnDestroy()
QSBSceneManager.OnSceneLoaded += OnSceneLoaded;
GlobalMessenger.AddListener("TriggerSupernova", OnTriggerSupernova);
Delay.RunWhen(() => PlayerTransformSync.LocalInstance != null,
() => new ServerStateMessage(ForceGetCurrentState()).Send());
}
private void OnDestroy()
{
QSBSceneManager.OnSceneLoaded -= OnSceneLoaded;
GlobalMessenger.RemoveListener("TriggerSupernova", OnTriggerSupernova);
}
public void ChangeServerState(ServerState newState)
{
if (_currentState == newState)
{
QSBSceneManager.OnSceneLoaded -= OnSceneLoaded;
GlobalMessenger.RemoveListener("TriggerSupernova", OnTriggerSupernova);
return;
}
public void ChangeServerState(ServerState newState)
_currentState = newState;
OnChangeState?.Invoke(newState);
}
public ServerState GetServerState()
=> _currentState;
private void OnSceneLoaded(OWScene oldScene, OWScene newScene, bool inUniverse)
{
switch (newScene)
{
if (_currentState == newState)
{
return;
}
case OWScene.Credits_Fast:
case OWScene.Credits_Final:
case OWScene.PostCreditsScene:
new ServerStateMessage(ServerState.Credits).Send();
break;
_currentState = newState;
OnChangeState?.Invoke(newState);
}
case OWScene.TitleScreen:
new ServerStateMessage(ServerState.NotLoaded).Send();
break;
public ServerState GetServerState()
=> _currentState;
private void OnSceneLoaded(OWScene oldScene, OWScene newScene, bool inUniverse)
{
switch (newScene)
{
case OWScene.Credits_Fast:
case OWScene.Credits_Final:
case OWScene.PostCreditsScene:
new ServerStateMessage(ServerState.Credits).Send();
break;
case OWScene.TitleScreen:
new ServerStateMessage(ServerState.NotLoaded).Send();
break;
case OWScene.SolarSystem:
if (oldScene == OWScene.SolarSystem)
{
new ServerStateMessage(ServerState.WaitingForAllPlayersToReady).Send();
}
else
{
new ServerStateMessage(ServerState.InSolarSystem).Send();
}
break;
case OWScene.EyeOfTheUniverse:
new ServerStateMessage(ServerState.WaitingForAllPlayersToReady).Send();
break;
case OWScene.None:
case OWScene.Undefined:
default:
DebugLog.ToConsole($"Warning - newScene is {newScene}!", OWML.Common.MessageType.Warning);
new ServerStateMessage(ServerState.NotLoaded).Send();
break;
}
}
private void OnTriggerSupernova()
{
if (QSBSceneManager.CurrentScene == OWScene.SolarSystem)
{
new ServerStateMessage(ServerState.WaitingForAllPlayersToDie).Send();
}
}
private ServerState ForceGetCurrentState()
{
var currentScene = LoadManager.GetCurrentScene();
switch (currentScene)
{
case OWScene.SolarSystem:
return ServerState.InSolarSystem;
case OWScene.EyeOfTheUniverse:
return ServerState.InEye;
default:
return ServerState.NotLoaded;
}
}
private void Update()
{
if (!QSBCore.IsHost)
{
return;
}
if (_blockNextCheck)
{
_blockNextCheck = false;
return;
}
if (_currentState == ServerState.WaitingForAllPlayersToReady)
{
if (QSBPlayerManager.PlayerList.All(x
=> x.State is ClientState.WaitingForOthersToBeReady
or ClientState.AliveInSolarSystem
or ClientState.AliveInEye))
case OWScene.SolarSystem:
if (oldScene == OWScene.SolarSystem)
{
DebugLog.DebugWrite($"All ready!!");
new StartLoopMessage().Send();
if (QSBSceneManager.CurrentScene == OWScene.SolarSystem)
{
new ServerStateMessage(ServerState.InSolarSystem).Send();
}
else if (QSBSceneManager.CurrentScene == OWScene.EyeOfTheUniverse)
{
new ServerStateMessage(ServerState.InEye).Send();
}
else
{
DebugLog.ToConsole($"Error - All players were ready in non-universe scene!?", OWML.Common.MessageType.Error);
new ServerStateMessage(ServerState.NotLoaded).Send();
}
_blockNextCheck = true;
new ServerStateMessage(ServerState.WaitingForAllPlayersToReady).Send();
}
else
{
new ServerStateMessage(ServerState.InSolarSystem).Send();
}
break;
case OWScene.EyeOfTheUniverse:
new ServerStateMessage(ServerState.WaitingForAllPlayersToReady).Send();
break;
case OWScene.None:
case OWScene.Undefined:
default:
DebugLog.ToConsole($"Warning - newScene is {newScene}!", OWML.Common.MessageType.Warning);
new ServerStateMessage(ServerState.NotLoaded).Send();
break;
}
}
private void OnTriggerSupernova()
{
if (QSBSceneManager.CurrentScene == OWScene.SolarSystem)
{
new ServerStateMessage(ServerState.WaitingForAllPlayersToDie).Send();
}
}
private ServerState ForceGetCurrentState()
{
var currentScene = LoadManager.GetCurrentScene();
switch (currentScene)
{
case OWScene.SolarSystem:
return ServerState.InSolarSystem;
case OWScene.EyeOfTheUniverse:
return ServerState.InEye;
default:
return ServerState.NotLoaded;
}
}
private void Update()
{
if (!QSBCore.IsHost)
{
return;
}
if (_blockNextCheck)
{
_blockNextCheck = false;
return;
}
if (_currentState == ServerState.WaitingForAllPlayersToReady)
{
if (QSBPlayerManager.PlayerList.All(x
=> x.State is ClientState.WaitingForOthersToBeReady
or ClientState.AliveInSolarSystem
or ClientState.AliveInEye))
{
DebugLog.DebugWrite($"All ready!!");
new StartLoopMessage().Send();
if (QSBSceneManager.CurrentScene == OWScene.SolarSystem)
{
new ServerStateMessage(ServerState.InSolarSystem).Send();
}
else if (QSBSceneManager.CurrentScene == OWScene.EyeOfTheUniverse)
{
new ServerStateMessage(ServerState.InEye).Send();
}
else
{
DebugLog.ToConsole($"Error - All players were ready in non-universe scene!?", OWML.Common.MessageType.Error);
new ServerStateMessage(ServerState.NotLoaded).Send();
}
_blockNextCheck = true;
}
}
}

View File

@ -1,33 +1,32 @@
using UnityEngine;
namespace QSB.ConversationSync
namespace QSB.ConversationSync;
public class CameraFacingBillboard : MonoBehaviour
{
public class CameraFacingBillboard : MonoBehaviour
private OWCamera _activeCam;
private void Awake()
=> GlobalMessenger<OWCamera>.AddListener("SwitchActiveCamera", OnSwitchActiveCamera);
private void Start()
{
private OWCamera _activeCam;
private void Awake()
=> GlobalMessenger<OWCamera>.AddListener("SwitchActiveCamera", OnSwitchActiveCamera);
private void Start()
{
_activeCam = Locator.GetActiveCamera();
UpdateRotation();
}
private void OnDestroy()
=> GlobalMessenger<OWCamera>.RemoveListener("SwitchActiveCamera", OnSwitchActiveCamera);
private void OnSwitchActiveCamera(OWCamera activeCamera)
{
_activeCam = activeCamera;
UpdateRotation();
}
private void LateUpdate()
=> UpdateRotation();
private void UpdateRotation()
=> transform.LookAt(transform.position + (_activeCam.transform.rotation * Vector3.forward), _activeCam.transform.rotation * Vector3.up);
_activeCam = Locator.GetActiveCamera();
UpdateRotation();
}
private void OnDestroy()
=> GlobalMessenger<OWCamera>.RemoveListener("SwitchActiveCamera", OnSwitchActiveCamera);
private void OnSwitchActiveCamera(OWCamera activeCamera)
{
_activeCam = activeCamera;
UpdateRotation();
}
private void LateUpdate()
=> UpdateRotation();
private void UpdateRotation()
=> transform.LookAt(transform.position + (_activeCam.transform.rotation * Vector3.forward), _activeCam.transform.rotation * Vector3.up);
}

View File

@ -12,124 +12,123 @@ using System.Threading;
using UnityEngine;
using UnityEngine.UI;
namespace QSB.ConversationSync
namespace QSB.ConversationSync;
public class ConversationManager : WorldObjectManager
{
public class ConversationManager : WorldObjectManager
public override WorldObjectScene WorldObjectScene => WorldObjectScene.Both;
public static ConversationManager Instance { get; private set; }
public Dictionary<CharacterDialogueTree, GameObject> BoxMappings { get; } = new Dictionary<CharacterDialogueTree, GameObject>();
private GameObject _boxPrefab;
public void Start()
{
public override WorldObjectScene WorldObjectScene => WorldObjectScene.Both;
Instance = this;
public static ConversationManager Instance { get; private set; }
public Dictionary<CharacterDialogueTree, GameObject> BoxMappings { get; } = new Dictionary<CharacterDialogueTree, GameObject>();
_boxPrefab = QSBCore.ConversationAssetBundle.LoadAsset<GameObject>("assets/Prefabs/dialoguebubble.prefab");
private GameObject _boxPrefab;
public void Start()
var font = (Font)Resources.Load(@"fonts\english - latin\HVD Fonts - BrandonGrotesque-Bold_Dynamic");
if (font == null)
{
Instance = this;
_boxPrefab = QSBCore.ConversationAssetBundle.LoadAsset<GameObject>("assets/Prefabs/dialoguebubble.prefab");
var font = (Font)Resources.Load(@"fonts\english - latin\HVD Fonts - BrandonGrotesque-Bold_Dynamic");
if (font == null)
{
DebugLog.ToConsole("Error - Font is null!", MessageType.Error);
}
_boxPrefab.GetComponent<Text>().font = font;
_boxPrefab.GetComponent<Text>().color = Color.white;
DebugLog.ToConsole("Error - Font is null!", MessageType.Error);
}
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct)
=> QSBWorldSync.Init<QSBRemoteDialogueTrigger, RemoteDialogueTrigger>();
_boxPrefab.GetComponent<Text>().font = font;
_boxPrefab.GetComponent<Text>().color = Color.white;
}
public uint GetPlayerTalkingToTree(CharacterDialogueTree tree)
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct)
=> QSBWorldSync.Init<QSBRemoteDialogueTrigger, RemoteDialogueTrigger>();
public uint GetPlayerTalkingToTree(CharacterDialogueTree tree)
{
var treeIndex = QSBWorldSync.OldDialogueTrees.IndexOf(tree);
return QSBPlayerManager.PlayerList.All(x => x.CurrentCharacterDialogueTreeId != treeIndex)
? uint.MaxValue
: QSBPlayerManager.PlayerList.First(x => x.CurrentCharacterDialogueTreeId == treeIndex).PlayerId;
}
public void SendPlayerOption(string text) =>
new ConversationMessage(ConversationType.Player, (int)QSBPlayerManager.LocalPlayerId, text).Send();
public void SendCharacterDialogue(int id, string text)
{
if (id == -1)
{
var treeIndex = QSBWorldSync.OldDialogueTrees.IndexOf(tree);
return QSBPlayerManager.PlayerList.All(x => x.CurrentCharacterDialogueTreeId != treeIndex)
? uint.MaxValue
: QSBPlayerManager.PlayerList.First(x => x.CurrentCharacterDialogueTreeId == treeIndex).PlayerId;
DebugLog.ToConsole("Warning - Tried to send conv. event with char id -1.", MessageType.Warning);
return;
}
public void SendPlayerOption(string text) =>
new ConversationMessage(ConversationType.Player, (int)QSBPlayerManager.LocalPlayerId, text).Send();
new ConversationMessage(ConversationType.Character, id, text).Send();
}
public void SendCharacterDialogue(int id, string text)
public void CloseBoxPlayer() =>
new ConversationMessage(ConversationType.ClosePlayer, (int)QSBPlayerManager.LocalPlayerId).Send();
public void CloseBoxCharacter(int id) =>
new ConversationMessage(ConversationType.CloseCharacter, id).Send();
public void SendConvState(int charId, bool state)
{
if (charId == -1)
{
if (id == -1)
{
DebugLog.ToConsole("Warning - Tried to send conv. event with char id -1.", MessageType.Warning);
return;
}
new ConversationMessage(ConversationType.Character, id, text).Send();
DebugLog.ToConsole("Warning - Tried to send conv. start/end event with char id -1.", MessageType.Warning);
return;
}
public void CloseBoxPlayer() =>
new ConversationMessage(ConversationType.ClosePlayer, (int)QSBPlayerManager.LocalPlayerId).Send();
new ConversationStartEndMessage(charId, state).Send();
}
public void CloseBoxCharacter(int id) =>
new ConversationMessage(ConversationType.CloseCharacter, id).Send();
public void SendConvState(int charId, bool state)
public void DisplayPlayerConversationBox(uint playerId, string text)
{
if (playerId == QSBPlayerManager.LocalPlayerId)
{
if (charId == -1)
{
DebugLog.ToConsole("Warning - Tried to send conv. start/end event with char id -1.", MessageType.Warning);
return;
}
new ConversationStartEndMessage(charId, state).Send();
DebugLog.ToConsole("Error - Cannot display conversation box for local player!", MessageType.Error);
return;
}
public void DisplayPlayerConversationBox(uint playerId, string text)
var player = QSBPlayerManager.GetPlayer(playerId);
// Destroy old box if it exists
var playerBox = player.CurrentDialogueBox;
if (playerBox != null)
{
if (playerId == QSBPlayerManager.LocalPlayerId)
{
DebugLog.ToConsole("Error - Cannot display conversation box for local player!", MessageType.Error);
return;
}
var player = QSBPlayerManager.GetPlayer(playerId);
// Destroy old box if it exists
var playerBox = player.CurrentDialogueBox;
if (playerBox != null)
{
Destroy(playerBox);
}
QSBPlayerManager.GetPlayer(playerId).CurrentDialogueBox = CreateBox(player.Body.transform, 2, text);
Destroy(playerBox);
}
public void DisplayCharacterConversationBox(int index, string text)
QSBPlayerManager.GetPlayer(playerId).CurrentDialogueBox = CreateBox(player.Body.transform, 2, text);
}
public void DisplayCharacterConversationBox(int index, string text)
{
if (QSBWorldSync.OldDialogueTrees.ElementAtOrDefault(index) == null)
{
if (QSBWorldSync.OldDialogueTrees.ElementAtOrDefault(index) == null)
{
DebugLog.ToConsole($"Error - Tried to display character conversation box for id {index}! (Doesn't exist!)", MessageType.Error);
return;
}
// Remove old box if it exists
var oldDialogueTree = QSBWorldSync.OldDialogueTrees[index];
if (BoxMappings.ContainsKey(oldDialogueTree))
{
Destroy(BoxMappings[oldDialogueTree]);
BoxMappings.Remove(oldDialogueTree);
}
BoxMappings.Add(oldDialogueTree, CreateBox(oldDialogueTree.gameObject.transform, 2, text));
DebugLog.ToConsole($"Error - Tried to display character conversation box for id {index}! (Doesn't exist!)", MessageType.Error);
return;
}
private GameObject CreateBox(Transform parent, float vertOffset, string text)
// Remove old box if it exists
var oldDialogueTree = QSBWorldSync.OldDialogueTrees[index];
if (BoxMappings.ContainsKey(oldDialogueTree))
{
var newBox = Instantiate(_boxPrefab);
newBox.SetActive(false);
newBox.transform.SetParent(parent);
newBox.transform.localPosition = new Vector3(0, vertOffset, 0);
newBox.transform.rotation = parent.rotation;
newBox.GetComponent<Text>().text = text;
newBox.SetActive(true);
return newBox;
Destroy(BoxMappings[oldDialogueTree]);
BoxMappings.Remove(oldDialogueTree);
}
BoxMappings.Add(oldDialogueTree, CreateBox(oldDialogueTree.gameObject.transform, 2, text));
}
private GameObject CreateBox(Transform parent, float vertOffset, string text)
{
var newBox = Instantiate(_boxPrefab);
newBox.SetActive(false);
newBox.transform.SetParent(parent);
newBox.transform.localPosition = new Vector3(0, vertOffset, 0);
newBox.transform.rotation = parent.rotation;
newBox.GetComponent<Text>().text = text;
newBox.SetActive(true);
return newBox;
}
}

View File

@ -1,10 +1,9 @@
namespace QSB.ConversationSync
namespace QSB.ConversationSync;
public enum ConversationType
{
public enum ConversationType
{
Character,
Player,
CloseCharacter,
ClosePlayer
}
Character,
Player,
CloseCharacter,
ClosePlayer
}

View File

@ -4,47 +4,46 @@ using QSB.WorldSync;
using System.Text.RegularExpressions;
using UnityEngine;
namespace QSB.ConversationSync.Messages
namespace QSB.ConversationSync.Messages;
public class ConversationMessage : QSBMessage<(ConversationType Type, int Id, string Message)>
{
public class ConversationMessage : QSBMessage<(ConversationType Type, int Id, string Message)>
public ConversationMessage(ConversationType type, int id, string message = "")
{
public ConversationMessage(ConversationType type, int id, string message = "")
Data.Type = type;
Data.Id = id;
Data.Message = message;
}
public override bool ShouldReceive => QSBWorldSync.AllObjectsReady;
public override void OnReceiveRemote()
{
switch (Data.Type)
{
Data.Type = type;
Data.Id = id;
Data.Message = message;
}
case ConversationType.Character:
var translated = TextTranslation.Translate(Data.Message).Trim();
translated = Regex.Replace(translated, @"<[Pp]ause=?\d*\.?\d*\s?\/?>", "");
ConversationManager.Instance.DisplayCharacterConversationBox(Data.Id, translated);
break;
public override bool ShouldReceive => QSBWorldSync.AllObjectsReady;
case ConversationType.Player:
ConversationManager.Instance.DisplayPlayerConversationBox((uint)Data.Id, Data.Message);
break;
public override void OnReceiveRemote()
{
switch (Data.Type)
{
case ConversationType.Character:
var translated = TextTranslation.Translate(Data.Message).Trim();
translated = Regex.Replace(translated, @"<[Pp]ause=?\d*\.?\d*\s?\/?>", "");
ConversationManager.Instance.DisplayCharacterConversationBox(Data.Id, translated);
case ConversationType.CloseCharacter:
if (Data.Id == -1)
{
break;
}
case ConversationType.Player:
ConversationManager.Instance.DisplayPlayerConversationBox((uint)Data.Id, Data.Message);
break;
var tree = QSBWorldSync.OldDialogueTrees[Data.Id];
Object.Destroy(ConversationManager.Instance.BoxMappings[tree]);
break;
case ConversationType.CloseCharacter:
if (Data.Id == -1)
{
break;
}
var tree = QSBWorldSync.OldDialogueTrees[Data.Id];
Object.Destroy(ConversationManager.Instance.BoxMappings[tree]);
break;
case ConversationType.ClosePlayer:
Object.Destroy(QSBPlayerManager.GetPlayer((uint)Data.Id).CurrentDialogueBox);
break;
}
case ConversationType.ClosePlayer:
Object.Destroy(QSBPlayerManager.GetPlayer((uint)Data.Id).CurrentDialogueBox);
break;
}
}
}
}

View File

@ -4,53 +4,52 @@ using QSB.Player;
using QSB.Utility;
using QSB.WorldSync;
namespace QSB.ConversationSync.Messages
namespace QSB.ConversationSync.Messages;
public class ConversationStartEndMessage : QSBMessage<(int TreeId, bool Start)>
{
public class ConversationStartEndMessage : QSBMessage<(int TreeId, bool Start)>
public ConversationStartEndMessage(int treeId, bool start)
{
public ConversationStartEndMessage(int treeId, bool start)
Data.TreeId = treeId;
Data.Start = start;
}
public override bool ShouldReceive => QSBWorldSync.AllObjectsReady;
public override void OnReceiveRemote()
{
if (Data.TreeId == -1)
{
Data.TreeId = treeId;
Data.Start = start;
DebugLog.ToConsole("Warning - Received conv. start/end event with char id -1.", MessageType.Warning);
return;
}
public override bool ShouldReceive => QSBWorldSync.AllObjectsReady;
var dialogueTree = QSBWorldSync.OldDialogueTrees[Data.TreeId];
public override void OnReceiveRemote()
if (Data.Start)
{
if (Data.TreeId == -1)
{
DebugLog.ToConsole("Warning - Received conv. start/end event with char id -1.", MessageType.Warning);
return;
}
var dialogueTree = QSBWorldSync.OldDialogueTrees[Data.TreeId];
if (Data.Start)
{
StartConversation(From, Data.TreeId, dialogueTree);
}
else
{
EndConversation(From, dialogueTree);
}
StartConversation(From, Data.TreeId, dialogueTree);
}
private static void StartConversation(
uint playerId,
int treeId,
CharacterDialogueTree tree)
else
{
QSBPlayerManager.GetPlayer(playerId).CurrentCharacterDialogueTreeId = treeId;
tree.GetInteractVolume().DisableInteraction();
}
private static void EndConversation(
uint playerId,
CharacterDialogueTree tree)
{
QSBPlayerManager.GetPlayer(playerId).CurrentCharacterDialogueTreeId = -1;
tree.GetInteractVolume().EnableInteraction();
EndConversation(From, dialogueTree);
}
}
}
private static void StartConversation(
uint playerId,
int treeId,
CharacterDialogueTree tree)
{
QSBPlayerManager.GetPlayer(playerId).CurrentCharacterDialogueTreeId = treeId;
tree.GetInteractVolume().DisableInteraction();
}
private static void EndConversation(
uint playerId,
CharacterDialogueTree tree)
{
QSBPlayerManager.GetPlayer(playerId).CurrentCharacterDialogueTreeId = -1;
tree.GetInteractVolume().EnableInteraction();
}
}

View File

@ -1,57 +1,56 @@
using QSB.Messaging;
using QSB.WorldSync;
namespace QSB.ConversationSync.Messages
namespace QSB.ConversationSync.Messages;
public class DialogueConditionMessage : QSBMessage<(string Name, bool State)>
{
public class DialogueConditionMessage : QSBMessage<(string Name, bool State)>
public DialogueConditionMessage(string name, bool state)
{
public DialogueConditionMessage(string name, bool state)
Data.Name = name;
Data.State = state;
}
public override void OnReceiveRemote()
{
if (QSBCore.IsHost)
{
Data.Name = name;
Data.State = state;
QSBWorldSync.SetDialogueCondition(Data.Name, Data.State);
}
public override void OnReceiveRemote()
var sharedInstance = DialogueConditionManager.SharedInstance;
var flag = true;
if (sharedInstance.ConditionExists(Data.Name))
{
if (QSBCore.IsHost)
if (sharedInstance._dictConditions[Data.Name] == Data.State)
{
QSBWorldSync.SetDialogueCondition(Data.Name, Data.State);
flag = false;
}
var sharedInstance = DialogueConditionManager.SharedInstance;
var flag = true;
if (sharedInstance.ConditionExists(Data.Name))
{
if (sharedInstance._dictConditions[Data.Name] == Data.State)
{
flag = false;
}
sharedInstance._dictConditions[Data.Name] = Data.State;
}
else
{
sharedInstance.AddCondition(Data.Name, Data.State);
}
if (flag)
{
GlobalMessenger<string, bool>.FireEvent("DialogueConditionChanged", Data.Name, Data.State);
}
if (Data.Name == "LAUNCH_CODES_GIVEN")
{
PlayerData.LearnLaunchCodes();
}
sharedInstance._dictConditions[Data.Name] = Data.State;
}
else
{
sharedInstance.AddCondition(Data.Name, Data.State);
}
public override void OnReceiveLocal()
if (flag)
{
if (QSBCore.IsHost)
{
QSBWorldSync.SetDialogueCondition(Data.Name, Data.State);
}
GlobalMessenger<string, bool>.FireEvent("DialogueConditionChanged", Data.Name, Data.State);
}
if (Data.Name == "LAUNCH_CODES_GIVEN")
{
PlayerData.LearnLaunchCodes();
}
}
}
public override void OnReceiveLocal()
{
if (QSBCore.IsHost)
{
QSBWorldSync.SetDialogueCondition(Data.Name, Data.State);
}
}
}

View File

@ -1,14 +1,13 @@
using QSB.ConversationSync.WorldObjects;
using QSB.Messaging;
namespace QSB.ConversationSync.Messages
{
internal class EnterRemoteDialogueMessage : QSBWorldObjectMessage<QSBRemoteDialogueTrigger, int>
{
public EnterRemoteDialogueMessage(int dialogueIndex)
=> Data = dialogueIndex;
namespace QSB.ConversationSync.Messages;
public override void OnReceiveRemote()
=> WorldObject.RemoteEnterDialogue(Data);
}
internal class EnterRemoteDialogueMessage : QSBWorldObjectMessage<QSBRemoteDialogueTrigger, int>
{
public EnterRemoteDialogueMessage(int dialogueIndex)
=> Data = dialogueIndex;
public override void OnReceiveRemote()
=> WorldObject.RemoteEnterDialogue(Data);
}

View File

@ -1,49 +1,48 @@
using QSB.Messaging;
using QSB.WorldSync;
namespace QSB.ConversationSync.Messages
namespace QSB.ConversationSync.Messages;
internal class PersistentConditionMessage : QSBMessage<(string Condition, bool State)>
{
internal class PersistentConditionMessage : QSBMessage<(string Condition, bool State)>
public PersistentConditionMessage(string condition, bool state)
{
public PersistentConditionMessage(string condition, bool state)
Data.Condition = condition;
Data.State = state;
}
public override void OnReceiveRemote()
{
if (QSBCore.IsHost)
{
Data.Condition = condition;
Data.State = state;
QSBWorldSync.SetPersistentCondition(Data.Condition, Data.State);
}
public override void OnReceiveRemote()
var gameSave = PlayerData._currentGameSave;
if (gameSave.dictConditions.ContainsKey(Data.Condition))
{
if (QSBCore.IsHost)
{
QSBWorldSync.SetPersistentCondition(Data.Condition, Data.State);
}
var gameSave = PlayerData._currentGameSave;
if (gameSave.dictConditions.ContainsKey(Data.Condition))
{
gameSave.dictConditions[Data.Condition] = Data.State;
}
else
{
gameSave.dictConditions.Add(Data.Condition, Data.State);
}
if (Data.Condition
is not "LAUNCH_CODES_GIVEN"
and not "PLAYER_ENTERED_TIMELOOPCORE"
and not "PROBE_ENTERED_TIMELOOPCORE"
and not "PLAYER_ENTERED_TIMELOOPCORE_MULTIPLE")
{
PlayerData.SaveCurrentGame();
}
gameSave.dictConditions[Data.Condition] = Data.State;
}
else
{
gameSave.dictConditions.Add(Data.Condition, Data.State);
}
public override void OnReceiveLocal()
if (Data.Condition
is not "LAUNCH_CODES_GIVEN"
and not "PLAYER_ENTERED_TIMELOOPCORE"
and not "PROBE_ENTERED_TIMELOOPCORE"
and not "PLAYER_ENTERED_TIMELOOPCORE_MULTIPLE")
{
if (QSBCore.IsHost)
{
QSBWorldSync.SetPersistentCondition(Data.Condition, Data.State);
}
PlayerData.SaveCurrentGame();
}
}
}
public override void OnReceiveLocal()
{
if (QSBCore.IsHost)
{
QSBWorldSync.SetPersistentCondition(Data.Condition, Data.State);
}
}
}

View File

@ -3,49 +3,48 @@ using QSB.ConversationSync.WorldObjects;
using QSB.Messaging;
using System.Linq;
namespace QSB.ConversationSync.Messages
namespace QSB.ConversationSync.Messages;
internal class RemoteDialogueInitialStateMessage : QSBWorldObjectMessage<QSBRemoteDialogueTrigger>
{
internal class RemoteDialogueInitialStateMessage : QSBWorldObjectMessage<QSBRemoteDialogueTrigger>
private bool _inRemoteDialogue;
private bool[] _activatedDialogues;
private int _dialogueIndex;
private bool _colliderEnabled;
public RemoteDialogueInitialStateMessage(RemoteDialogueTrigger trigger)
{
private bool _inRemoteDialogue;
private bool[] _activatedDialogues;
private int _dialogueIndex;
private bool _colliderEnabled;
_inRemoteDialogue = trigger._inRemoteDialogue;
_activatedDialogues = trigger._activatedDialogues;
_dialogueIndex = trigger._listDialogues
.IndexOf(x => x.dialogue == trigger._activeRemoteDialogue);
_colliderEnabled = trigger._collider.enabled;
}
public RemoteDialogueInitialStateMessage(RemoteDialogueTrigger trigger)
{
_inRemoteDialogue = trigger._inRemoteDialogue;
_activatedDialogues = trigger._activatedDialogues;
_dialogueIndex = trigger._listDialogues
.IndexOf(x => x.dialogue == trigger._activeRemoteDialogue);
_colliderEnabled = trigger._collider.enabled;
}
public override void Serialize(NetworkWriter writer)
{
base.Serialize(writer);
writer.Write(_inRemoteDialogue);
writer.Write(_activatedDialogues);
writer.Write(_dialogueIndex);
writer.Write(_colliderEnabled);
}
public override void Serialize(NetworkWriter writer)
{
base.Serialize(writer);
writer.Write(_inRemoteDialogue);
writer.Write(_activatedDialogues);
writer.Write(_dialogueIndex);
writer.Write(_colliderEnabled);
}
public override void Deserialize(NetworkReader reader)
{
base.Deserialize(reader);
_inRemoteDialogue = reader.Read<bool>();
_activatedDialogues = reader.Read<bool[]>();
_dialogueIndex = reader.Read<int>();
_colliderEnabled = reader.Read<bool>();
}
public override void Deserialize(NetworkReader reader)
{
base.Deserialize(reader);
_inRemoteDialogue = reader.Read<bool>();
_activatedDialogues = reader.Read<bool[]>();
_dialogueIndex = reader.Read<int>();
_colliderEnabled = reader.Read<bool>();
}
public override void OnReceiveRemote()
{
var trigger = WorldObject.AttachedObject;
trigger._activatedDialogues = _activatedDialogues;
trigger._inRemoteDialogue = _inRemoteDialogue;
trigger._activeRemoteDialogue = trigger._listDialogues.ElementAtOrDefault(_dialogueIndex).dialogue;
trigger._collider.enabled = _colliderEnabled;
}
public override void OnReceiveRemote()
{
var trigger = WorldObject.AttachedObject;
trigger._activatedDialogues = _activatedDialogues;
trigger._inRemoteDialogue = _inRemoteDialogue;
trigger._activeRemoteDialogue = trigger._listDialogues.ElementAtOrDefault(_dialogueIndex).dialogue;
trigger._collider.enabled = _colliderEnabled;
}
}

View File

@ -9,170 +9,169 @@ using QSB.Utility;
using QSB.WorldSync;
using System.Linq;
namespace QSB.ConversationSync.Patches
namespace QSB.ConversationSync.Patches;
[HarmonyPatch]
public class ConversationPatches : QSBPatch
{
[HarmonyPatch]
public class ConversationPatches : QSBPatch
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
public static readonly string[] PersistentConditionsToSync =
{
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
"MET_SOLANUM",
"MET_PRISONER",
"TALKED_TO_GABBRO",
"GABBRO_MERGE_TRIGGERED",
"KNOWS_MEDITATION"
};
public static readonly string[] PersistentConditionsToSync =
{
"MET_SOLANUM",
"MET_PRISONER",
"TALKED_TO_GABBRO",
"GABBRO_MERGE_TRIGGERED",
"KNOWS_MEDITATION"
};
[HarmonyPrefix]
[HarmonyPatch(typeof(DialogueConditionManager), nameof(DialogueConditionManager.SetConditionState))]
public static bool SetConditionState(string conditionName, bool conditionState)
{
new DialogueConditionMessage(conditionName, conditionState).Send();
return true;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(DialogueConditionManager), nameof(DialogueConditionManager.SetConditionState))]
public static bool SetConditionState(string conditionName, bool conditionState)
[HarmonyPrefix]
[HarmonyPatch(typeof(CharacterDialogueTree), nameof(CharacterDialogueTree.StartConversation))]
public static void CharacterDialogueTree_StartConversation(CharacterDialogueTree __instance)
{
var index = QSBWorldSync.OldDialogueTrees.FindIndex(x => x == __instance);
if (index == -1)
{
new DialogueConditionMessage(conditionName, conditionState).Send();
DebugLog.ToConsole($"Warning - Index for tree {__instance.name} was -1.", MessageType.Warning);
}
QSBPlayerManager.LocalPlayer.CurrentCharacterDialogueTreeId = index;
ConversationManager.Instance.SendConvState(index, true);
}
[HarmonyPrefix]
[HarmonyPatch(typeof(CharacterDialogueTree), nameof(CharacterDialogueTree.EndConversation))]
public static bool CharacterDialogueTree_EndConversation(CharacterDialogueTree __instance)
{
if (!__instance.enabled)
{
return false;
}
if (QSBPlayerManager.LocalPlayer.CurrentCharacterDialogueTreeId == -1)
{
DebugLog.ToConsole($"Warning - Ending conversation with CurrentDialogueId of -1! Called from {__instance.name}", MessageType.Warning);
return true;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(CharacterDialogueTree), nameof(CharacterDialogueTree.StartConversation))]
public static void CharacterDialogueTree_StartConversation(CharacterDialogueTree __instance)
ConversationManager.Instance.SendConvState(QSBPlayerManager.LocalPlayer.CurrentCharacterDialogueTreeId, false);
ConversationManager.Instance.CloseBoxCharacter(QSBPlayerManager.LocalPlayer.CurrentCharacterDialogueTreeId);
QSBPlayerManager.LocalPlayer.CurrentCharacterDialogueTreeId = -1;
ConversationManager.Instance.CloseBoxPlayer();
return true;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(CharacterDialogueTree), nameof(CharacterDialogueTree.InputDialogueOption))]
public static bool CharacterDialogueTree_InputDialogueOption(CharacterDialogueTree __instance, int optionIndex)
{
if (optionIndex < 0)
{
var index = QSBWorldSync.OldDialogueTrees.FindIndex(x => x == __instance);
if (index == -1)
{
DebugLog.ToConsole($"Warning - Index for tree {__instance.name} was -1.", MessageType.Warning);
}
QSBPlayerManager.LocalPlayer.CurrentCharacterDialogueTreeId = index;
ConversationManager.Instance.SendConvState(index, true);
}
[HarmonyPrefix]
[HarmonyPatch(typeof(CharacterDialogueTree), nameof(CharacterDialogueTree.EndConversation))]
public static bool CharacterDialogueTree_EndConversation(CharacterDialogueTree __instance)
{
if (!__instance.enabled)
{
return false;
}
if (QSBPlayerManager.LocalPlayer.CurrentCharacterDialogueTreeId == -1)
{
DebugLog.ToConsole($"Warning - Ending conversation with CurrentDialogueId of -1! Called from {__instance.name}", MessageType.Warning);
return true;
}
ConversationManager.Instance.SendConvState(QSBPlayerManager.LocalPlayer.CurrentCharacterDialogueTreeId, false);
ConversationManager.Instance.CloseBoxCharacter(QSBPlayerManager.LocalPlayer.CurrentCharacterDialogueTreeId);
QSBPlayerManager.LocalPlayer.CurrentCharacterDialogueTreeId = -1;
// in a page where there is no selectable options
ConversationManager.Instance.CloseBoxPlayer();
return true;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(CharacterDialogueTree), nameof(CharacterDialogueTree.InputDialogueOption))]
public static bool CharacterDialogueTree_InputDialogueOption(CharacterDialogueTree __instance, int optionIndex)
var selectedOption = __instance._currentDialogueBox.OptionFromUIIndex(optionIndex);
ConversationManager.Instance.SendPlayerOption(selectedOption.Text);
return true;
}
[HarmonyPostfix]
[HarmonyPatch(typeof(DialogueNode), nameof(DialogueNode.GetNextPage))]
public static void DialogueNode_GetNextPage(DialogueNode __instance)
{
var key = __instance._name + __instance._listPagesToDisplay[__instance._currentPage];
// Sending key so translation can be done on client side - should make different language-d clients compatible
Delay.RunWhen(() => QSBPlayerManager.LocalPlayer.CurrentCharacterDialogueTreeId != -1,
() => ConversationManager.Instance.SendCharacterDialogue(QSBPlayerManager.LocalPlayer.CurrentCharacterDialogueTreeId, key));
}
[HarmonyPrefix]
[HarmonyPatch(typeof(RemoteDialogueTrigger), nameof(RemoteDialogueTrigger.ConversationTriggered))]
public static bool ConversationTriggeredReplacement(RemoteDialogueTrigger __instance, out bool __result, out RemoteDialogueTrigger.RemoteDialogueCondition dialogue)
{
dialogue = default;
var dialogueIndex = -1;
for (var i = 0; i < __instance._listDialogues.Length; i++)
{
if (optionIndex < 0)
if (!__instance._activatedDialogues[i])
{
// in a page where there is no selectable options
ConversationManager.Instance.CloseBoxPlayer();
return true;
}
var allConditionsMet = true;
var anyConditionsMet = __instance._listDialogues[i].prereqConditions.Length == 0;
var selectedOption = __instance._currentDialogueBox.OptionFromUIIndex(optionIndex);
ConversationManager.Instance.SendPlayerOption(selectedOption.Text);
return true;
}
[HarmonyPostfix]
[HarmonyPatch(typeof(DialogueNode), nameof(DialogueNode.GetNextPage))]
public static void DialogueNode_GetNextPage(DialogueNode __instance)
{
var key = __instance._name + __instance._listPagesToDisplay[__instance._currentPage];
// Sending key so translation can be done on client side - should make different language-d clients compatible
Delay.RunWhen(() => QSBPlayerManager.LocalPlayer.CurrentCharacterDialogueTreeId != -1,
() => ConversationManager.Instance.SendCharacterDialogue(QSBPlayerManager.LocalPlayer.CurrentCharacterDialogueTreeId, key));
}
[HarmonyPrefix]
[HarmonyPatch(typeof(RemoteDialogueTrigger), nameof(RemoteDialogueTrigger.ConversationTriggered))]
public static bool ConversationTriggeredReplacement(RemoteDialogueTrigger __instance, out bool __result, out RemoteDialogueTrigger.RemoteDialogueCondition dialogue)
{
dialogue = default;
var dialogueIndex = -1;
for (var i = 0; i < __instance._listDialogues.Length; i++)
{
if (!__instance._activatedDialogues[i])
foreach (var prereqCondition in __instance._listDialogues[i].prereqConditions)
{
var allConditionsMet = true;
var anyConditionsMet = __instance._listDialogues[i].prereqConditions.Length == 0;
foreach (var prereqCondition in __instance._listDialogues[i].prereqConditions)
if (DialogueConditionManager.SharedInstance.GetConditionState(prereqCondition))
{
if (DialogueConditionManager.SharedInstance.GetConditionState(prereqCondition))
{
anyConditionsMet = true;
}
else
{
allConditionsMet = false;
}
anyConditionsMet = true;
}
var conditionsMet = false;
var prereqConditionType = __instance._listDialogues[i].prereqConditionType;
if (prereqConditionType != RemoteDialogueTrigger.MultiConditionType.OR)
else
{
if (prereqConditionType == RemoteDialogueTrigger.MultiConditionType.AND && allConditionsMet)
{
conditionsMet = true;
}
allConditionsMet = false;
}
else if (anyConditionsMet)
}
var conditionsMet = false;
var prereqConditionType = __instance._listDialogues[i].prereqConditionType;
if (prereqConditionType != RemoteDialogueTrigger.MultiConditionType.OR)
{
if (prereqConditionType == RemoteDialogueTrigger.MultiConditionType.AND && allConditionsMet)
{
conditionsMet = true;
}
}
else if (anyConditionsMet)
{
conditionsMet = true;
}
if (conditionsMet && __instance._listDialogues[i].priority < int.MaxValue)
{
dialogue = __instance._listDialogues[i];
dialogueIndex = i;
}
if (conditionsMet && __instance._listDialogues[i].priority < int.MaxValue)
{
dialogue = __instance._listDialogues[i];
dialogueIndex = i;
}
}
}
if (dialogueIndex == -1)
{
__result = false;
return false;
}
__instance._activatedDialogues[dialogueIndex] = true;
__result = true;
__instance.GetWorldObject<QSBRemoteDialogueTrigger>()
.SendMessage(new EnterRemoteDialogueMessage(dialogueIndex));
if (dialogueIndex == -1)
{
__result = false;
return false;
}
[HarmonyPostfix]
[HarmonyPatch(typeof(GameSave), nameof(GameSave.SetPersistentCondition))]
public static void SetPersistentCondition(string condition, bool state)
{
if (PersistentConditionsToSync.Contains(condition))
{
new PersistentConditionMessage(condition, state).Send();
}
}
__instance._activatedDialogues[dialogueIndex] = true;
__result = true;
[HarmonyPrefix]
[HarmonyPatch(typeof(DialogueConditionManager), nameof(DialogueConditionManager.AddCondition))]
public static bool AddCondition(string conditionName, bool conditionState)
__instance.GetWorldObject<QSBRemoteDialogueTrigger>()
.SendMessage(new EnterRemoteDialogueMessage(dialogueIndex));
return false;
}
[HarmonyPostfix]
[HarmonyPatch(typeof(GameSave), nameof(GameSave.SetPersistentCondition))]
public static void SetPersistentCondition(string condition, bool state)
{
if (PersistentConditionsToSync.Contains(condition))
{
new DialogueConditionMessage(conditionName, conditionState).Send();
return true;
new PersistentConditionMessage(condition, state).Send();
}
}
[HarmonyPrefix]
[HarmonyPatch(typeof(DialogueConditionManager), nameof(DialogueConditionManager.AddCondition))]
public static bool AddCondition(string conditionName, bool conditionState)
{
new DialogueConditionMessage(conditionName, conditionState).Send();
return true;
}
}

View File

@ -2,20 +2,19 @@
using QSB.Messaging;
using QSB.WorldSync;
namespace QSB.ConversationSync.WorldObjects
namespace QSB.ConversationSync.WorldObjects;
internal class QSBRemoteDialogueTrigger : WorldObject<RemoteDialogueTrigger>
{
internal class QSBRemoteDialogueTrigger : WorldObject<RemoteDialogueTrigger>
public override void SendInitialState(uint to) =>
this.SendMessage(new RemoteDialogueInitialStateMessage(AttachedObject) { To = to });
public void RemoteEnterDialogue(int dialogueIndex)
{
public override void SendInitialState(uint to) =>
this.SendMessage(new RemoteDialogueInitialStateMessage(AttachedObject) { To = to });
var dialogueCondition = AttachedObject._listDialogues[dialogueIndex];
AttachedObject._activeRemoteDialogue = dialogueCondition.dialogue;
AttachedObject._inRemoteDialogue = true;
public void RemoteEnterDialogue(int dialogueIndex)
{
var dialogueCondition = AttachedObject._listDialogues[dialogueIndex];
AttachedObject._activeRemoteDialogue = dialogueCondition.dialogue;
AttachedObject._inRemoteDialogue = true;
AttachedObject._activatedDialogues[dialogueIndex] = true;
}
AttachedObject._activatedDialogues[dialogueIndex] = true;
}
}

View File

@ -4,28 +4,27 @@ using QSB.Messaging;
using QSB.Patches;
using QSB.Utility;
namespace QSB.DeathSync.Messages
namespace QSB.DeathSync.Messages;
// when all players die
internal class EndLoopMessage : QSBMessage
{
// when all players die
internal class EndLoopMessage : QSBMessage
public override void OnReceiveLocal() => OnReceiveRemote();
public override void OnReceiveRemote()
{
public override void OnReceiveLocal() => OnReceiveRemote();
public override void OnReceiveRemote()
DebugLog.DebugWrite($" ~~~~ END LOOP - all players are dead ~~~~ ");
if (ServerStateManager.Instance.GetServerState() == ServerState.WaitingForAllPlayersToDie)
{
DebugLog.DebugWrite($" ~~~~ END LOOP - all players are dead ~~~~ ");
if (ServerStateManager.Instance.GetServerState() == ServerState.WaitingForAllPlayersToDie)
{
return;
}
return;
}
QSBPatchManager.DoUnpatchType(QSBPatchTypes.RespawnTime);
QSBPatchManager.DoUnpatchType(QSBPatchTypes.RespawnTime);
Locator.GetDeathManager().KillPlayer(DeathType.TimeLoop);
if (QSBCore.IsHost)
{
new ServerStateMessage(ServerState.WaitingForAllPlayersToDie).Send();
}
Locator.GetDeathManager().KillPlayer(DeathType.TimeLoop);
if (QSBCore.IsHost)
{
new ServerStateMessage(ServerState.WaitingForAllPlayersToDie).Send();
}
}
}

View File

@ -5,48 +5,47 @@ using QSB.Player;
using QSB.RespawnSync;
using QSB.Utility;
namespace QSB.DeathSync.Messages
namespace QSB.DeathSync.Messages;
public class PlayerDeathMessage : QSBMessage<DeathType>
{
public class PlayerDeathMessage : QSBMessage<DeathType>
private int NecronomiconIndex;
public PlayerDeathMessage(DeathType type)
{
private int NecronomiconIndex;
Data = type;
NecronomiconIndex = Necronomicon.GetRandomIndex(type);
}
public PlayerDeathMessage(DeathType type)
public override void Serialize(NetworkWriter writer)
{
base.Serialize(writer);
writer.Write(NecronomiconIndex);
}
public override void Deserialize(NetworkReader reader)
{
base.Deserialize(reader);
NecronomiconIndex = reader.Read<int>();
}
public override void OnReceiveLocal()
{
var player = QSBPlayerManager.GetPlayer(From);
RespawnManager.Instance.OnPlayerDeath(player);
ClientStateManager.Instance.OnDeath();
}
public override void OnReceiveRemote()
{
var player = QSBPlayerManager.GetPlayer(From);
var playerName = player.Name;
var deathMessage = Necronomicon.GetPhrase(Data, NecronomiconIndex);
if (deathMessage != null)
{
Data = type;
NecronomiconIndex = Necronomicon.GetRandomIndex(type);
DebugLog.ToAll(string.Format(deathMessage, playerName));
}
public override void Serialize(NetworkWriter writer)
{
base.Serialize(writer);
writer.Write(NecronomiconIndex);
}
public override void Deserialize(NetworkReader reader)
{
base.Deserialize(reader);
NecronomiconIndex = reader.Read<int>();
}
public override void OnReceiveLocal()
{
var player = QSBPlayerManager.GetPlayer(From);
RespawnManager.Instance.OnPlayerDeath(player);
ClientStateManager.Instance.OnDeath();
}
public override void OnReceiveRemote()
{
var player = QSBPlayerManager.GetPlayer(From);
var playerName = player.Name;
var deathMessage = Necronomicon.GetPhrase(Data, NecronomiconIndex);
if (deathMessage != null)
{
DebugLog.ToAll(string.Format(deathMessage, playerName));
}
RespawnManager.Instance.OnPlayerDeath(player);
}
RespawnManager.Instance.OnPlayerDeath(player);
}
}

View File

@ -4,28 +4,27 @@ using QSB.ClientServerStateSync.Messages;
using QSB.Messaging;
using QSB.Utility;
namespace QSB.DeathSync.Messages
{
internal class StartLoopMessage : QSBMessage
{
public override void OnReceiveLocal() => OnReceiveRemote();
namespace QSB.DeathSync.Messages;
public override void OnReceiveRemote()
internal class StartLoopMessage : QSBMessage
{
public override void OnReceiveLocal() => OnReceiveRemote();
public override void OnReceiveRemote()
{
DebugLog.DebugWrite($" ~~~ LOOP START ~~~");
if (QSBSceneManager.CurrentScene == OWScene.SolarSystem)
{
DebugLog.DebugWrite($" ~~~ LOOP START ~~~");
if (QSBSceneManager.CurrentScene == OWScene.SolarSystem)
{
new ClientStateMessage(ClientState.AliveInSolarSystem).Send();
}
else if (QSBSceneManager.CurrentScene == OWScene.EyeOfTheUniverse)
{
new ClientStateMessage(ClientState.AliveInEye).Send();
}
else
{
DebugLog.ToConsole($"Error - Got StartLoop event when not in universe!", MessageType.Error);
new ClientStateMessage(ClientState.NotLoaded).Send();
}
new ClientStateMessage(ClientState.AliveInSolarSystem).Send();
}
else if (QSBSceneManager.CurrentScene == OWScene.EyeOfTheUniverse)
{
new ClientStateMessage(ClientState.AliveInEye).Send();
}
else
{
DebugLog.ToConsole($"Error - Got StartLoop event when not in universe!", MessageType.Error);
new ClientStateMessage(ClientState.NotLoaded).Send();
}
}
}

View File

@ -1,156 +1,155 @@
using System;
using System.Collections.Generic;
namespace QSB.DeathSync
namespace QSB.DeathSync;
public static class Necronomicon
{
public static class Necronomicon
private static readonly Dictionary<DeathType, string[]> Darkhold = new()
{
private static readonly Dictionary<DeathType, string[]> Darkhold = new()
{
DeathType.Default,
new[] // Running out of health
{
DeathType.Default,
new[] // Running out of health
{
"{0} died",
"{0} was killed"
}
},
"{0} died",
"{0} was killed"
}
},
{
DeathType.Impact,
new[] // Hitting the ground/wall/object
{
DeathType.Impact,
new[] // Hitting the ground/wall/object
{
"{0} forgot to use retro-rockets",
"{0} bonked into the ground too hard",
"{0} hit the ground too hard",
"{0} went splat",
"{0} died",
"{0} was killed",
"{0} died due to impact",
"{0} impacted the ground too hard"
}
},
"{0} forgot to use retro-rockets",
"{0} bonked into the ground too hard",
"{0} hit the ground too hard",
"{0} went splat",
"{0} died",
"{0} was killed",
"{0} died due to impact",
"{0} impacted the ground too hard"
}
},
{
DeathType.Asphyxiation,
new[] // Running out of oxygen
{
DeathType.Asphyxiation,
new[] // Running out of oxygen
{
"{0} forgot to breathe",
"{0} asphyxiated",
"{0} died due to asphyxiation",
"{0} forgot how to breathe",
"{0} forgot to check their oxygen",
"{0} ran out of air",
"{0} ran out of oxygen",
"{0} didn't need air anyway"
}
},
"{0} forgot to breathe",
"{0} asphyxiated",
"{0} died due to asphyxiation",
"{0} forgot how to breathe",
"{0} forgot to check their oxygen",
"{0} ran out of air",
"{0} ran out of oxygen",
"{0} didn't need air anyway"
}
},
{
DeathType.Energy,
new[] // Electricity, sun, etc.
{
DeathType.Energy,
new[] // Electricity, sun, etc.
{
"{0} was cooked",
"{0} died",
"{0} was killed"
}
},
"{0} was cooked",
"{0} died",
"{0} was killed"
}
},
{
DeathType.Supernova,
new[] // Supernova
{
DeathType.Supernova,
new[] // Supernova
{
"{0} ran out of time",
"{0} burnt up",
"{0} got vaporized",
"{0} lost track of time",
"{0} got front row seats to the supernova",
"{0} heard the music",
"{0} watched the sun go kaboom",
"{0} became cosmic marshmallow",
"{0} photosynthesized too much",
"{0} died due to the supernova"
}
},
"{0} ran out of time",
"{0} burnt up",
"{0} got vaporized",
"{0} lost track of time",
"{0} got front row seats to the supernova",
"{0} heard the music",
"{0} watched the sun go kaboom",
"{0} became cosmic marshmallow",
"{0} photosynthesized too much",
"{0} died due to the supernova"
}
},
{
DeathType.Digestion,
new[] // Anglerfish
{
DeathType.Digestion,
new[] // Anglerfish
{
"{0} was eaten",
"{0} found a fish",
"{0} encountered an evil creature",
"{0} messed with the wrong fish",
"{0} was digested",
"{0} died due to digestion"
}
},
"{0} was eaten",
"{0} found a fish",
"{0} encountered an evil creature",
"{0} messed with the wrong fish",
"{0} was digested",
"{0} died due to digestion"
}
},
{
DeathType.Crushed,
new[] // Crushed in sand
{
DeathType.Crushed,
new[] // Crushed in sand
{
"{0} went through the tunnel too slow",
"{0} didn't make it out in time",
"{0} was squished",
"{0} was crushed",
"{0} was buried",
"{0} went swimming in the sand",
"{0} underestimated the danger of sand",
"{0} died due to being crushed"
}
},
"{0} went through the tunnel too slow",
"{0} didn't make it out in time",
"{0} was squished",
"{0} was crushed",
"{0} was buried",
"{0} went swimming in the sand",
"{0} underestimated the danger of sand",
"{0} died due to being crushed"
}
},
{
DeathType.Lava,
new[] // Lava
{
DeathType.Lava,
new[] // Lava
{
"{0} died in lava",
"{0} was melted",
"{0} tried to swim in lava",
"{0} didn't know what the glowy orange liquid was",
"{0} fell into lava",
"{0} became one with the glowing gooey rock",
"{0} died due to lava",
"{0} got burnt in the lava"
}
},
"{0} died in lava",
"{0} was melted",
"{0} tried to swim in lava",
"{0} didn't know what the glowy orange liquid was",
"{0} fell into lava",
"{0} became one with the glowing gooey rock",
"{0} died due to lava",
"{0} got burnt in the lava"
}
},
{
DeathType.BlackHole,
new[] // ATP core black hole
{
DeathType.BlackHole,
new[] // ATP core black hole
{
"{0} should visit the Ash Twin Project again",
"{0} waited inside the Ash Twin Project",
"{0} chased their memories"
}
},
"{0} should visit the Ash Twin Project again",
"{0} waited inside the Ash Twin Project",
"{0} chased their memories"
}
},
{
DeathType.DreamExplosion,
new[] // using the prototype
{
DeathType.DreamExplosion,
new[] // using the prototype
{
"{0} exploded",
"{0} was an early adopter",
"{0} went kaboom",
"{0} was fried",
"{0} died due to explosion",
"{0} used the wrong artifact"
}
},
"{0} exploded",
"{0} was an early adopter",
"{0} went kaboom",
"{0} was fried",
"{0} died due to explosion",
"{0} used the wrong artifact"
}
},
{
DeathType.CrushedByElevator,
new[] // elevator-induced pancakeness
{
DeathType.CrushedByElevator,
new[] // elevator-induced pancakeness
{
"{0} was crushed",
"{0} was squished",
"{0} was crushed by an elevator",
"{0} stood under an elevator",
"{0} became a flat-hearther",
"{0} was squished by an elevator"
}
},
};
"{0} was crushed",
"{0} was squished",
"{0} was crushed by an elevator",
"{0} stood under an elevator",
"{0} became a flat-hearther",
"{0} was squished by an elevator"
}
},
};
public static string GetPhrase(DeathType deathType, int index)
=> Darkhold.ContainsKey(deathType)
? Darkhold[deathType][index]
: null;
public static string GetPhrase(DeathType deathType, int index)
=> Darkhold.ContainsKey(deathType)
? Darkhold[deathType][index]
: null;
public static int GetRandomIndex(DeathType deathType)
=> Darkhold.ContainsKey(deathType)
? new Random().Next(0, Darkhold[deathType].Length)
: -1;
}
public static int GetRandomIndex(DeathType deathType)
=> Darkhold.ContainsKey(deathType)
? new Random().Next(0, Darkhold[deathType].Length)
: -1;
}

View File

@ -8,220 +8,219 @@ using QSB.Utility;
using System.Linq;
using UnityEngine;
namespace QSB.DeathSync.Patches
namespace QSB.DeathSync.Patches;
[HarmonyPatch]
public class DeathPatches : QSBPatch
{
[HarmonyPatch]
public class DeathPatches : QSBPatch
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
// TODO : Remove with future functionality.
[HarmonyPrefix]
[HarmonyPatch(typeof(ShipEjectionSystem), nameof(ShipEjectionSystem.OnPressInteract))]
public static bool DisableEjection()
=> false;
// TODO : Remove with future functionality.
[HarmonyPrefix]
[HarmonyPatch(typeof(ShipDetachableLeg), nameof(ShipDetachableLeg.Detach))]
public static bool ShipDetachableLeg_Detach(out OWRigidbody __result)
{
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
__result = null;
return false;
}
// TODO : Remove with future functionality.
[HarmonyPrefix]
[HarmonyPatch(typeof(ShipEjectionSystem), nameof(ShipEjectionSystem.OnPressInteract))]
public static bool DisableEjection()
=> false;
// TODO : Remove with future functionality.
[HarmonyPrefix]
[HarmonyPatch(typeof(ShipDetachableModule), nameof(ShipDetachableModule.Detach))]
public static bool ShipDetachableModule_Detach(out OWRigidbody __result)
{
__result = null;
return false;
}
// TODO : Remove with future functionality.
[HarmonyPrefix]
[HarmonyPatch(typeof(ShipDetachableLeg), nameof(ShipDetachableLeg.Detach))]
public static bool ShipDetachableLeg_Detach(out OWRigidbody __result)
[HarmonyPrefix]
[HarmonyPatch(typeof(PlayerResources), nameof(PlayerResources.OnImpact))]
public static bool PlayerResources_OnImpact(PlayerResources __instance, ImpactData impact)
{
if (PlayerState.IsInsideShip())
{
__result = null;
return false;
}
// TODO : Remove with future functionality.
[HarmonyPrefix]
[HarmonyPatch(typeof(ShipDetachableModule), nameof(ShipDetachableModule.Detach))]
public static bool ShipDetachableModule_Detach(out OWRigidbody __result)
var speed = Mathf.Clamp01((impact.speed - __instance.GetMinImpactSpeed()) / (__instance.GetMaxImpactSpeed() - __instance.GetMinImpactSpeed()));
var tookDamage = __instance.ApplyInstantDamage(100f * speed, InstantDamageType.Impact);
if (tookDamage && __instance._currentHealth <= 0f && !PlayerState.IsDead())
{
Locator.GetDeathManager().SetImpactDeathSpeed(impact.speed);
Locator.GetDeathManager().KillPlayer(DeathType.Impact);
}
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(HighSpeedImpactSensor), nameof(HighSpeedImpactSensor.FixedUpdate))]
public static bool HighSpeedImpactSensor_FixedUpdate(
HighSpeedImpactSensor __instance
)
{
if (__instance._isPlayer && (PlayerState.IsAttached() || PlayerState.IsInsideShuttle() || PlayerState.UsingNomaiRemoteCamera()))
{
__result = null;
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(PlayerResources), nameof(PlayerResources.OnImpact))]
public static bool PlayerResources_OnImpact(PlayerResources __instance, ImpactData impact)
if (__instance._dieNextUpdate && !__instance._dead)
{
if (PlayerState.IsInsideShip())
__instance._dead = true;
__instance._dieNextUpdate = false;
if (__instance.gameObject.CompareTag("Player"))
{
return false;
}
var speed = Mathf.Clamp01((impact.speed - __instance.GetMinImpactSpeed()) / (__instance.GetMaxImpactSpeed() - __instance.GetMinImpactSpeed()));
var tookDamage = __instance.ApplyInstantDamage(100f * speed, InstantDamageType.Impact);
if (tookDamage && __instance._currentHealth <= 0f && !PlayerState.IsDead())
{
Locator.GetDeathManager().SetImpactDeathSpeed(impact.speed);
Locator.GetDeathManager().SetImpactDeathSpeed(__instance._impactSpeed);
Locator.GetDeathManager().KillPlayer(DeathType.Impact);
}
else if (__instance.gameObject.CompareTag("Ship"))
{
__instance.GetComponent<ShipDamageController>().Explode();
}
}
if (__instance._isPlayer && PlayerState.IsInsideShip())
{
var shipCenter = Locator.GetShipTransform().position + (Locator.GetShipTransform().up * 2f);
var distanceFromShip = Vector3.Distance(__instance._body.GetPosition(), shipCenter);
if (distanceFromShip > 8f)
{
__instance._body.SetPosition(shipCenter);
}
if (!__instance._dead)
{
var a = __instance._body.GetVelocity() - Locator.GetShipBody().GetPointVelocity(__instance._body.GetPosition());
if (a.sqrMagnitude > __instance._sqrCheckSpeedThreshold)
{
__instance._impactSpeed = a.magnitude;
__instance._body.AddVelocityChange(-a);
}
}
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(HighSpeedImpactSensor), nameof(HighSpeedImpactSensor.FixedUpdate))]
public static bool HighSpeedImpactSensor_FixedUpdate(
HighSpeedImpactSensor __instance
)
var passiveReferenceFrame = __instance._sectorDetector.GetPassiveReferenceFrame();
if (!__instance._dead && passiveReferenceFrame != null)
{
if (__instance._isPlayer && (PlayerState.IsAttached() || PlayerState.IsInsideShuttle() || PlayerState.UsingNomaiRemoteCamera()))
var relativeVelocity = __instance._body.GetVelocity() - passiveReferenceFrame.GetOWRigidBody().GetPointVelocity(__instance._body.GetPosition());
if (relativeVelocity.sqrMagnitude > __instance._sqrCheckSpeedThreshold)
{
return false;
}
if (__instance._dieNextUpdate && !__instance._dead)
{
__instance._dead = true;
__instance._dieNextUpdate = false;
if (__instance.gameObject.CompareTag("Player"))
var hitCount = Physics.RaycastNonAlloc(__instance.transform.TransformPoint(__instance._localOffset), relativeVelocity, __instance._raycastHits, (relativeVelocity.magnitude * Time.deltaTime) + __instance._radius, OWLayerMask.physicalMask, QueryTriggerInteraction.Ignore);
for (var i = 0; i < hitCount; i++)
{
Locator.GetDeathManager().SetImpactDeathSpeed(__instance._impactSpeed);
Locator.GetDeathManager().KillPlayer(DeathType.Impact);
}
else if (__instance.gameObject.CompareTag("Ship"))
{
__instance.GetComponent<ShipDamageController>().Explode();
}
}
if (__instance._isPlayer && PlayerState.IsInsideShip())
{
var shipCenter = Locator.GetShipTransform().position + (Locator.GetShipTransform().up * 2f);
var distanceFromShip = Vector3.Distance(__instance._body.GetPosition(), shipCenter);
if (distanceFromShip > 8f)
{
__instance._body.SetPosition(shipCenter);
}
if (!__instance._dead)
{
var a = __instance._body.GetVelocity() - Locator.GetShipBody().GetPointVelocity(__instance._body.GetPosition());
if (a.sqrMagnitude > __instance._sqrCheckSpeedThreshold)
if (__instance._raycastHits[i].rigidbody.mass > 10f && !__instance._raycastHits[i].rigidbody.Equals(__instance._body.GetRigidbody()))
{
__instance._impactSpeed = a.magnitude;
__instance._body.AddVelocityChange(-a);
}
}
return false;
}
var passiveReferenceFrame = __instance._sectorDetector.GetPassiveReferenceFrame();
if (!__instance._dead && passiveReferenceFrame != null)
{
var relativeVelocity = __instance._body.GetVelocity() - passiveReferenceFrame.GetOWRigidBody().GetPointVelocity(__instance._body.GetPosition());
if (relativeVelocity.sqrMagnitude > __instance._sqrCheckSpeedThreshold)
{
var hitCount = Physics.RaycastNonAlloc(__instance.transform.TransformPoint(__instance._localOffset), relativeVelocity, __instance._raycastHits, (relativeVelocity.magnitude * Time.deltaTime) + __instance._radius, OWLayerMask.physicalMask, QueryTriggerInteraction.Ignore);
for (var i = 0; i < hitCount; i++)
{
if (__instance._raycastHits[i].rigidbody.mass > 10f && !__instance._raycastHits[i].rigidbody.Equals(__instance._body.GetRigidbody()))
var owRigidbody = __instance._raycastHits[i].rigidbody.GetComponent<OWRigidbody>();
if (owRigidbody == null)
{
var owRigidbody = __instance._raycastHits[i].rigidbody.GetComponent<OWRigidbody>();
if (owRigidbody == null)
DebugLog.ToConsole("Rigidbody does not have attached OWRigidbody!!!", OWML.Common.MessageType.Error);
Debug.Break();
}
else
{
relativeVelocity = __instance._body.GetVelocity() - owRigidbody.GetPointVelocity(__instance._body.GetPosition());
var a2 = Vector3.Project(relativeVelocity, __instance._raycastHits[i].normal);
if (a2.sqrMagnitude > __instance._sqrCheckSpeedThreshold)
{
DebugLog.ToConsole("Rigidbody does not have attached OWRigidbody!!!", OWML.Common.MessageType.Error);
Debug.Break();
}
else
{
relativeVelocity = __instance._body.GetVelocity() - owRigidbody.GetPointVelocity(__instance._body.GetPosition());
var a2 = Vector3.Project(relativeVelocity, __instance._raycastHits[i].normal);
if (a2.sqrMagnitude > __instance._sqrCheckSpeedThreshold)
__instance._body.AddVelocityChange(-a2);
__instance._impactSpeed = a2.magnitude;
if (!PlayerState.IsInsideTheEye())
{
__instance._body.AddVelocityChange(-a2);
__instance._impactSpeed = a2.magnitude;
if (!PlayerState.IsInsideTheEye())
{
__instance._dieNextUpdate = true;
}
break;
__instance._dieNextUpdate = true;
}
break;
}
}
}
}
}
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(DeathManager), nameof(DeathManager.KillPlayer))]
public static bool DeathManager_KillPlayer_Prefix(DeathType deathType)
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(DeathManager), nameof(DeathManager.KillPlayer))]
public static bool DeathManager_KillPlayer_Prefix(DeathType deathType)
{
if (RespawnOnDeath.Instance == null)
{
if (RespawnOnDeath.Instance == null)
{
return true;
}
if (RespawnOnDeath.Instance.AllowedDeathTypes.Contains(deathType))
{
return true;
}
if (QSBPlayerManager.LocalPlayer.IsDead)
{
return false;
}
var deadPlayersCount = QSBPlayerManager.PlayerList.Count(x => x.IsDead);
if (deadPlayersCount == QSBPlayerManager.PlayerList.Count - 1)
{
new EndLoopMessage().Send();
return true;
}
RespawnOnDeath.Instance.ResetPlayer();
return false;
}
[HarmonyPostfix]
[HarmonyPatch(typeof(DeathManager), nameof(DeathManager.KillPlayer))]
public static void DeathManager_KillPlayer_Postfix(DeathType deathType)
{
if (QSBPlayerManager.LocalPlayer.IsDead)
{
return;
}
QSBPlayerManager.LocalPlayer.IsDead = true;
new PlayerDeathMessage(deathType).Send();
if (PlayerAttachWatcher.Current)
{
PlayerAttachWatcher.Current.DetachPlayer();
}
}
[HarmonyPostfix]
[HarmonyPatch(typeof(ShipDamageController), nameof(ShipDamageController.Awake))]
public static void ShipDamageController_Awake(ShipDamageController __instance)
=> __instance._exploded = true;
[HarmonyPrefix]
[HarmonyPatch(typeof(DestructionVolume), nameof(DestructionVolume.VanishShip))]
public static bool DestructionVolume_VanishShip(DestructionVolume __instance)
{
if (RespawnOnDeath.Instance == null)
{
return true;
}
if (!ShipTransformSync.LocalInstance.hasAuthority)
{
return false;
}
if (PlayerState.IsInsideShip() || PlayerState.UsingShipComputer() || PlayerState.AtFlightConsole())
{
Locator.GetDeathManager().KillPlayer(__instance._deathType);
}
return true;
}
if (RespawnOnDeath.Instance.AllowedDeathTypes.Contains(deathType))
{
return true;
}
if (QSBPlayerManager.LocalPlayer.IsDead)
{
return false;
}
var deadPlayersCount = QSBPlayerManager.PlayerList.Count(x => x.IsDead);
if (deadPlayersCount == QSBPlayerManager.PlayerList.Count - 1)
{
new EndLoopMessage().Send();
return true;
}
RespawnOnDeath.Instance.ResetPlayer();
return false;
}
[HarmonyPostfix]
[HarmonyPatch(typeof(DeathManager), nameof(DeathManager.KillPlayer))]
public static void DeathManager_KillPlayer_Postfix(DeathType deathType)
{
if (QSBPlayerManager.LocalPlayer.IsDead)
{
return;
}
QSBPlayerManager.LocalPlayer.IsDead = true;
new PlayerDeathMessage(deathType).Send();
if (PlayerAttachWatcher.Current)
{
PlayerAttachWatcher.Current.DetachPlayer();
}
}
[HarmonyPostfix]
[HarmonyPatch(typeof(ShipDamageController), nameof(ShipDamageController.Awake))]
public static void ShipDamageController_Awake(ShipDamageController __instance)
=> __instance._exploded = true;
[HarmonyPrefix]
[HarmonyPatch(typeof(DestructionVolume), nameof(DestructionVolume.VanishShip))]
public static bool DestructionVolume_VanishShip(DestructionVolume __instance)
{
if (RespawnOnDeath.Instance == null)
{
return true;
}
if (!ShipTransformSync.LocalInstance.hasAuthority)
{
return false;
}
if (PlayerState.IsInsideShip() || PlayerState.UsingShipComputer() || PlayerState.AtFlightConsole())
{
Locator.GetDeathManager().KillPlayer(__instance._deathType);
}
return true;
}
}

View File

@ -2,182 +2,181 @@
using QSB.Patches;
using UnityEngine;
namespace QSB.DeathSync.Patches
namespace QSB.DeathSync.Patches;
[HarmonyPatch]
internal class MapPatches : QSBPatch
{
[HarmonyPatch]
internal class MapPatches : QSBPatch
public override QSBPatchTypes Type => QSBPatchTypes.RespawnTime;
[HarmonyPrefix]
[HarmonyPatch(typeof(MapController), nameof(MapController.EnterMapView))]
public static bool MapController_EnterMapView(
MapController __instance
)
{
public override QSBPatchTypes Type => QSBPatchTypes.RespawnTime;
[HarmonyPrefix]
[HarmonyPatch(typeof(MapController), nameof(MapController.EnterMapView))]
public static bool MapController_EnterMapView(
MapController __instance
)
if (__instance._isMapMode)
{
if (__instance._isMapMode)
{
return false;
}
return false;
}
__instance._mapMarkerManager.SetVisible(true);
GlobalMessenger.FireEvent("EnterMapView");
GlobalMessenger<OWCamera>.FireEvent("SwitchActiveCamera", __instance._mapCamera);
if (__instance._audioSource.isPlaying)
{
__instance._audioSource.Stop();
__instance._audioSource.SetLocalVolume(1f);
__instance._audioSource.Play();
}
else
{
__instance._audioSource.SetLocalVolume(1f);
__instance._audioSource.Play();
}
__instance._mapMarkerManager.SetVisible(true);
GlobalMessenger.FireEvent("EnterMapView");
GlobalMessenger<OWCamera>.FireEvent("SwitchActiveCamera", __instance._mapCamera);
if (__instance._audioSource.isPlaying)
{
__instance._audioSource.Stop();
__instance._audioSource.SetLocalVolume(1f);
__instance._audioSource.Play();
}
else
{
__instance._audioSource.SetLocalVolume(1f);
__instance._audioSource.Play();
}
Locator.GetAudioMixer().MixMap();
__instance._activeCam.enabled = false;
__instance._mapCamera.enabled = true;
__instance._gridRenderer.enabled = false;
__instance._targetTransform = null;
__instance._lockedToTargetTransform = false;
__instance._position = RespawnOnDeath.Instance.DeathPositionWorld - Locator.GetCenterOfTheUniverse().GetStaticReferenceFrame().GetPosition();
Locator.GetAudioMixer().MixMap();
__instance._activeCam.enabled = false;
__instance._mapCamera.enabled = true;
__instance._gridRenderer.enabled = false;
__instance._targetTransform = null;
__instance._lockedToTargetTransform = false;
__instance._position = RespawnOnDeath.Instance.DeathPositionWorld - Locator.GetCenterOfTheUniverse().GetStaticReferenceFrame().GetPosition();
__instance._position.y = 0f;
__instance._yaw = __instance._defaultYawAngle;
__instance._pitch = __instance._initialPitchAngle;
__instance._zoom = __instance._initialZoomDist;
__instance._targetZoom = __instance._defaultZoomDist;
__instance.transform.rotation = Quaternion.LookRotation(-RespawnOnDeath.Instance.DeathPlayerUpVector, RespawnOnDeath.Instance.DeathPlayerForwardVector);
__instance.transform.position = RespawnOnDeath.Instance.DeathPositionWorld;
__instance._interpPosition = true;
__instance._interpPitch = true;
__instance._interpZoom = true;
__instance._framingPlayer = __instance._lockedToTargetTransform;
__instance._lockTimer = __instance._lockOnMoveLength;
__instance._gridOverrideSize = (__instance._currentRFrame == null) ? 0f : __instance._currentRFrame.GetAutopilotArrivalDistance();
__instance._gridOverride = __instance._gridOverrideSize > 0f;
__instance._gridTimer = (!__instance._gridOverride) ? 0f : __instance._gridLockOnLength;
__instance._revealLength = 20f;
__instance._revealTimer = 0f;
__instance._isMapMode = true;
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(MapController), nameof(MapController.LateUpdate))]
public static bool MapController_LateUpdate(
MapController __instance
)
{
__instance._lockTimer = Mathf.Min(__instance._lockTimer + Time.deltaTime, __instance._lockOnMoveLength);
__instance._revealTimer = Mathf.Min(__instance._revealTimer + Time.deltaTime, __instance._revealLength);
var revealFraction = Mathf.Clamp01(__instance._revealTimer / __instance._revealLength);
var smoothedRevealFraction = Mathf.SmoothStep(0f, 1f, revealFraction);
var canInteractWith = __instance._revealTimer > 18f;
if (__instance._screenPromptsVisible && __instance._isPaused)
{
__instance._closePrompt.SetVisibility(false);
__instance._panPrompt.SetVisibility(false);
__instance._rotatePrompt.SetVisibility(false);
__instance._zoomPrompt.SetVisibility(false);
__instance._screenPromptsVisible = false;
}
else if (!__instance._screenPromptsVisible && canInteractWith && !__instance._isPaused)
{
__instance._closePrompt.SetVisibility(false);
__instance._panPrompt.SetVisibility(true);
__instance._rotatePrompt.SetVisibility(true);
__instance._zoomPrompt.SetVisibility(true);
__instance._screenPromptsVisible = true;
}
var XZinput = Vector2.zero;
var lookInput = Vector2.zero;
var zoomInput = 0f;
if (canInteractWith)
{
XZinput = OWInput.GetAxisValue(InputLibrary.moveXZ);
lookInput = InputLibrary.look.GetAxisValue(false);
zoomInput = OWInput.GetValue(InputLibrary.mapZoomIn) - OWInput.GetValue(InputLibrary.mapZoomOut);
lookInput.y *= -1f;
zoomInput *= -1f;
}
__instance._lockedToTargetTransform &= XZinput.sqrMagnitude < 0.01f;
__instance._interpPosition &= XZinput.sqrMagnitude < 0.01f;
__instance._interpPitch &= Mathf.Abs(lookInput.y) < 0.1f;
__instance._interpZoom &= Mathf.Abs(zoomInput) < 0.1f;
if (__instance._interpPosition)
{
var a = __instance._activeCam.transform.position - Locator.GetCenterOfTheUniverse().GetOffsetPosition();
var b = Vector3.zero;
__instance._position = Vector3.Lerp(a, b, smoothedRevealFraction);
}
else
{
var normalized = Vector3.Scale(__instance.transform.forward + __instance.transform.up, new Vector3(1f, 0f, 1f)).normalized;
var a2 = (__instance.transform.right * XZinput.x) + (normalized * XZinput.y);
__instance._position += a2 * __instance._panSpeed * __instance._zoom * Time.deltaTime;
__instance._position.y = 0f;
__instance._yaw = __instance._defaultYawAngle;
__instance._pitch = __instance._initialPitchAngle;
__instance._zoom = __instance._initialZoomDist;
__instance._targetZoom = __instance._defaultZoomDist;
__instance.transform.rotation = Quaternion.LookRotation(-RespawnOnDeath.Instance.DeathPlayerUpVector, RespawnOnDeath.Instance.DeathPlayerForwardVector);
__instance.transform.position = RespawnOnDeath.Instance.DeathPositionWorld;
__instance._interpPosition = true;
__instance._interpPitch = true;
__instance._interpZoom = true;
__instance._framingPlayer = __instance._lockedToTargetTransform;
__instance._lockTimer = __instance._lockOnMoveLength;
__instance._gridOverrideSize = (__instance._currentRFrame == null) ? 0f : __instance._currentRFrame.GetAutopilotArrivalDistance();
__instance._gridOverride = __instance._gridOverrideSize > 0f;
__instance._gridTimer = (!__instance._gridOverride) ? 0f : __instance._gridLockOnLength;
__instance._revealLength = 20f;
__instance._revealTimer = 0f;
__instance._isMapMode = true;
return false;
if (__instance._position.sqrMagnitude > __instance._maxPanDistance * __instance._maxPanDistance)
{
__instance._position = __instance._position.normalized * __instance._maxPanDistance;
}
}
[HarmonyPrefix]
[HarmonyPatch(typeof(MapController), nameof(MapController.LateUpdate))]
public static bool MapController_LateUpdate(
MapController __instance
)
__instance._yaw += lookInput.x * __instance._yawSpeed * Time.deltaTime;
__instance._yaw = OWMath.WrapAngle(__instance._yaw);
if (__instance._interpPitch)
{
__instance._lockTimer = Mathf.Min(__instance._lockTimer + Time.deltaTime, __instance._lockOnMoveLength);
__instance._revealTimer = Mathf.Min(__instance._revealTimer + Time.deltaTime, __instance._revealLength);
var revealFraction = Mathf.Clamp01(__instance._revealTimer / __instance._revealLength);
var smoothedRevealFraction = Mathf.SmoothStep(0f, 1f, revealFraction);
var canInteractWith = __instance._revealTimer > 18f;
if (__instance._screenPromptsVisible && __instance._isPaused)
{
__instance._closePrompt.SetVisibility(false);
__instance._panPrompt.SetVisibility(false);
__instance._rotatePrompt.SetVisibility(false);
__instance._zoomPrompt.SetVisibility(false);
__instance._screenPromptsVisible = false;
}
else if (!__instance._screenPromptsVisible && canInteractWith && !__instance._isPaused)
{
__instance._closePrompt.SetVisibility(false);
__instance._panPrompt.SetVisibility(true);
__instance._rotatePrompt.SetVisibility(true);
__instance._zoomPrompt.SetVisibility(true);
__instance._screenPromptsVisible = true;
}
var XZinput = Vector2.zero;
var lookInput = Vector2.zero;
var zoomInput = 0f;
if (canInteractWith)
{
XZinput = OWInput.GetAxisValue(InputLibrary.moveXZ);
lookInput = InputLibrary.look.GetAxisValue(false);
zoomInput = OWInput.GetValue(InputLibrary.mapZoomIn) - OWInput.GetValue(InputLibrary.mapZoomOut);
lookInput.y *= -1f;
zoomInput *= -1f;
}
__instance._lockedToTargetTransform &= XZinput.sqrMagnitude < 0.01f;
__instance._interpPosition &= XZinput.sqrMagnitude < 0.01f;
__instance._interpPitch &= Mathf.Abs(lookInput.y) < 0.1f;
__instance._interpZoom &= Mathf.Abs(zoomInput) < 0.1f;
if (__instance._interpPosition)
{
var a = __instance._activeCam.transform.position - Locator.GetCenterOfTheUniverse().GetOffsetPosition();
var b = Vector3.zero;
__instance._position = Vector3.Lerp(a, b, smoothedRevealFraction);
}
else
{
var normalized = Vector3.Scale(__instance.transform.forward + __instance.transform.up, new Vector3(1f, 0f, 1f)).normalized;
var a2 = (__instance.transform.right * XZinput.x) + (normalized * XZinput.y);
__instance._position += a2 * __instance._panSpeed * __instance._zoom * Time.deltaTime;
__instance._position.y = 0f;
if (__instance._position.sqrMagnitude > __instance._maxPanDistance * __instance._maxPanDistance)
{
__instance._position = __instance._position.normalized * __instance._maxPanDistance;
}
}
__instance._yaw += lookInput.x * __instance._yawSpeed * Time.deltaTime;
__instance._yaw = OWMath.WrapAngle(__instance._yaw);
if (__instance._interpPitch)
{
__instance._pitch = Mathf.Lerp(__instance._initialPitchAngle, __instance._defaultPitchAngle, smoothedRevealFraction);
}
else
{
__instance._pitch += lookInput.y * __instance._pitchSpeed * Time.deltaTime;
__instance._pitch = Mathf.Clamp(__instance._pitch, __instance._minPitchAngle, __instance._maxPitchAngle);
}
if (__instance._interpZoom)
{
__instance._zoom = Mathf.Lerp(__instance._initialZoomDist, __instance._targetZoom, smoothedRevealFraction);
}
else
{
__instance._zoom += zoomInput * __instance._zoomSpeed * Time.deltaTime;
__instance._zoom = Mathf.Clamp(__instance._zoom, __instance._minZoomDistance, __instance._maxZoomDistance);
}
__instance._mapCamera.nearClipPlane = Mathf.Lerp(0.1f, 1f, smoothedRevealFraction);
var finalRotation = Quaternion.Euler(__instance._pitch, __instance._yaw, 0f);
var num4 = revealFraction * (2f - revealFraction);
var num5 = Mathf.SmoothStep(0f, 1f, num4);
// Create rotation that's looking down at the player from above
var lookingDownAtPlayer = Quaternion.LookRotation(-RespawnOnDeath.Instance.DeathPlayerUpVector, Vector3.up);
// Get starting position - distance above player
var startingPosition = RespawnOnDeath.Instance.DeathPositionWorld;
startingPosition += RespawnOnDeath.Instance.DeathPlayerUpVector * num5 * __instance._observatoryRevealDist;
// Lerp to final rotation
__instance.transform.rotation = Quaternion.Lerp(lookingDownAtPlayer, finalRotation, num5);
// Lerp reveal twist
__instance.transform.rotation *= Quaternion.AngleAxis(Mathf.Lerp(__instance._observatoryRevealTwist, 0f, num4), Vector3.forward);
var endPosition = __instance._position + (-__instance.transform.forward * __instance._zoom) + Locator.GetCenterOfTheUniverse().GetStaticReferenceFrame().GetPosition();
// Lerp to final position
__instance.transform.position = Vector3.Lerp(startingPosition, endPosition, num5);
return false;
__instance._pitch = Mathf.Lerp(__instance._initialPitchAngle, __instance._defaultPitchAngle, smoothedRevealFraction);
}
else
{
__instance._pitch += lookInput.y * __instance._pitchSpeed * Time.deltaTime;
__instance._pitch = Mathf.Clamp(__instance._pitch, __instance._minPitchAngle, __instance._maxPitchAngle);
}
if (__instance._interpZoom)
{
__instance._zoom = Mathf.Lerp(__instance._initialZoomDist, __instance._targetZoom, smoothedRevealFraction);
}
else
{
__instance._zoom += zoomInput * __instance._zoomSpeed * Time.deltaTime;
__instance._zoom = Mathf.Clamp(__instance._zoom, __instance._minZoomDistance, __instance._maxZoomDistance);
}
__instance._mapCamera.nearClipPlane = Mathf.Lerp(0.1f, 1f, smoothedRevealFraction);
var finalRotation = Quaternion.Euler(__instance._pitch, __instance._yaw, 0f);
var num4 = revealFraction * (2f - revealFraction);
var num5 = Mathf.SmoothStep(0f, 1f, num4);
// Create rotation that's looking down at the player from above
var lookingDownAtPlayer = Quaternion.LookRotation(-RespawnOnDeath.Instance.DeathPlayerUpVector, Vector3.up);
// Get starting position - distance above player
var startingPosition = RespawnOnDeath.Instance.DeathPositionWorld;
startingPosition += RespawnOnDeath.Instance.DeathPlayerUpVector * num5 * __instance._observatoryRevealDist;
// Lerp to final rotation
__instance.transform.rotation = Quaternion.Lerp(lookingDownAtPlayer, finalRotation, num5);
// Lerp reveal twist
__instance.transform.rotation *= Quaternion.AngleAxis(Mathf.Lerp(__instance._observatoryRevealTwist, 0f, num4), Vector3.forward);
var endPosition = __instance._position + (-__instance.transform.forward * __instance._zoom) + Locator.GetCenterOfTheUniverse().GetStaticReferenceFrame().GetPosition();
// Lerp to final position
__instance.transform.position = Vector3.Lerp(startingPosition, endPosition, num5);
return false;
}
}

View File

@ -7,118 +7,117 @@ using QSB.WorldSync;
using System.Linq;
using UnityEngine;
namespace QSB.DeathSync
namespace QSB.DeathSync;
public class RespawnOnDeath : MonoBehaviour
{
public class RespawnOnDeath : MonoBehaviour
public static RespawnOnDeath Instance;
public readonly DeathType[] AllowedDeathTypes = {
DeathType.BigBang,
DeathType.Supernova,
DeathType.TimeLoop
};
private SpawnPoint _playerSpawnPoint;
private PlayerSpawner _playerSpawner;
private FluidDetector _fluidDetector;
private PlayerResources _playerResources;
private PlayerSpacesuit _spaceSuit;
private SuitPickupVolume[] _suitPickupVolumes;
private Vector3 _deathPositionRelative;
public Transform DeathClosestAstroObject { get; private set; }
public Vector3 DeathPositionWorld
=> DeathClosestAstroObject == null
? Vector3.zero
: DeathClosestAstroObject.TransformPoint(_deathPositionRelative);
public Vector3 DeathPlayerUpVector { get; private set; }
public Vector3 DeathPlayerForwardVector { get; private set; }
public void Awake() => Instance = this;
public void Init()
{
public static RespawnOnDeath Instance;
DebugLog.DebugWrite($"INIT");
var playerTransform = Locator.GetPlayerTransform();
_playerResources = playerTransform.GetComponent<PlayerResources>();
_spaceSuit = Locator.GetPlayerSuit();
_playerSpawner = FindObjectOfType<PlayerSpawner>();
_suitPickupVolumes = FindObjectsOfType<SuitPickupVolume>();
_fluidDetector = Locator.GetPlayerCamera().GetComponentInChildren<FluidDetector>();
_playerSpawnPoint = GetSpawnPoint();
}
public readonly DeathType[] AllowedDeathTypes = {
DeathType.BigBang,
DeathType.Supernova,
DeathType.TimeLoop
};
private SpawnPoint _playerSpawnPoint;
private PlayerSpawner _playerSpawner;
private FluidDetector _fluidDetector;
private PlayerResources _playerResources;
private PlayerSpacesuit _spaceSuit;
private SuitPickupVolume[] _suitPickupVolumes;
private Vector3 _deathPositionRelative;
public Transform DeathClosestAstroObject { get; private set; }
public Vector3 DeathPositionWorld
=> DeathClosestAstroObject == null
? Vector3.zero
: DeathClosestAstroObject.TransformPoint(_deathPositionRelative);
public Vector3 DeathPlayerUpVector { get; private set; }
public Vector3 DeathPlayerForwardVector { get; private set; }
public void Awake() => Instance = this;
public void Init()
public void ResetPlayer()
{
DebugLog.DebugWrite($"RESET PLAYER");
if (_playerSpawnPoint == null)
{
DebugLog.DebugWrite($"INIT");
var playerTransform = Locator.GetPlayerTransform();
_playerResources = playerTransform.GetComponent<PlayerResources>();
_spaceSuit = Locator.GetPlayerSuit();
_playerSpawner = FindObjectOfType<PlayerSpawner>();
_suitPickupVolumes = FindObjectsOfType<SuitPickupVolume>();
_fluidDetector = Locator.GetPlayerCamera().GetComponentInChildren<FluidDetector>();
_playerSpawnPoint = GetSpawnPoint();
DebugLog.ToConsole("Warning - _playerSpawnPoint is null!", MessageType.Warning);
Init();
}
public void ResetPlayer()
RespawnManager.Instance.TriggerRespawnMap();
var inSpace = PlayerTransformSync.LocalInstance.SectorDetector.SectorList.Count == 0;
if (inSpace)
{
DebugLog.DebugWrite($"RESET PLAYER");
if (_playerSpawnPoint == null)
DeathClosestAstroObject = Locator.GetAstroObject(AstroObject.Name.Sun).transform;
}
else
{
var allAstroobjects = QSBWorldSync.GetUnityObjects<AstroObject>().Where(x => x.GetAstroObjectName() != AstroObject.Name.None && x.GetAstroObjectType() != AstroObject.Type.Satellite);
var closest = allAstroobjects.MinBy(x => Vector3.SqrMagnitude(x.transform.position));
DeathClosestAstroObject = closest.transform;
}
var deathPosition = Locator.GetPlayerTransform().position;
_deathPositionRelative = DeathClosestAstroObject.InverseTransformPoint(deathPosition);
DeathPlayerUpVector = Locator.GetPlayerTransform().up;
DeathPlayerForwardVector = Locator.GetPlayerTransform().forward;
var playerBody = Locator.GetPlayerBody();
playerBody.WarpToPositionRotation(_playerSpawnPoint.transform.position, _playerSpawnPoint.transform.rotation);
playerBody.SetVelocity(_playerSpawnPoint.GetPointVelocity());
_playerSpawnPoint.AddObjectToTriggerVolumes(Locator.GetPlayerDetector().gameObject);
_playerSpawnPoint.AddObjectToTriggerVolumes(_fluidDetector.gameObject);
_playerSpawnPoint.OnSpawnPlayer();
_playerResources._isSuffocating = false;
_playerResources.DebugRefillResources();
_spaceSuit.RemoveSuit(true);
foreach (var pickupVolume in _suitPickupVolumes)
{
if (!pickupVolume._containsSuit && pickupVolume._allowSuitReturn)
{
DebugLog.ToConsole("Warning - _playerSpawnPoint is null!", MessageType.Warning);
Init();
}
RespawnManager.Instance.TriggerRespawnMap();
var inSpace = PlayerTransformSync.LocalInstance.SectorDetector.SectorList.Count == 0;
if (inSpace)
{
DeathClosestAstroObject = Locator.GetAstroObject(AstroObject.Name.Sun).transform;
}
else
{
var allAstroobjects = QSBWorldSync.GetUnityObjects<AstroObject>().Where(x => x.GetAstroObjectName() != AstroObject.Name.None && x.GetAstroObjectType() != AstroObject.Type.Satellite);
var closest = allAstroobjects.MinBy(x => Vector3.SqrMagnitude(x.transform.position));
DeathClosestAstroObject = closest.transform;
}
var deathPosition = Locator.GetPlayerTransform().position;
_deathPositionRelative = DeathClosestAstroObject.InverseTransformPoint(deathPosition);
DeathPlayerUpVector = Locator.GetPlayerTransform().up;
DeathPlayerForwardVector = Locator.GetPlayerTransform().forward;
var playerBody = Locator.GetPlayerBody();
playerBody.WarpToPositionRotation(_playerSpawnPoint.transform.position, _playerSpawnPoint.transform.rotation);
playerBody.SetVelocity(_playerSpawnPoint.GetPointVelocity());
_playerSpawnPoint.AddObjectToTriggerVolumes(Locator.GetPlayerDetector().gameObject);
_playerSpawnPoint.AddObjectToTriggerVolumes(_fluidDetector.gameObject);
_playerSpawnPoint.OnSpawnPlayer();
_playerResources._isSuffocating = false;
_playerResources.DebugRefillResources();
_spaceSuit.RemoveSuit(true);
foreach (var pickupVolume in _suitPickupVolumes)
{
if (!pickupVolume._containsSuit && pickupVolume._allowSuitReturn)
pickupVolume._containsSuit = true;
pickupVolume._interactVolume.ChangePrompt(UITextType.SuitUpPrompt, pickupVolume._pickupSuitCommandIndex);
pickupVolume._suitGeometry.SetActive(true);
pickupVolume._suitOWCollider.SetActivation(true);
foreach (var geo in pickupVolume._toolGeometry)
{
pickupVolume._containsSuit = true;
pickupVolume._interactVolume.ChangePrompt(UITextType.SuitUpPrompt, pickupVolume._pickupSuitCommandIndex);
pickupVolume._suitGeometry.SetActive(true);
pickupVolume._suitOWCollider.SetActivation(true);
foreach (var geo in pickupVolume._toolGeometry)
{
geo.SetActive(true);
}
geo.SetActive(true);
}
}
QSBPlayerManager.LocalPlayer.LocalFlashlight.TurnOff(false);
}
private SpawnPoint GetSpawnPoint()
QSBPlayerManager.LocalPlayer.LocalFlashlight.TurnOff(false);
}
private SpawnPoint GetSpawnPoint()
{
var spawnList = _playerSpawner._spawnList;
if (spawnList == null)
{
var spawnList = _playerSpawner._spawnList;
if (spawnList == null)
{
DebugLog.ToConsole($"Warning - _spawnList was null for player spawner!", MessageType.Warning);
return null;
}
return spawnList.FirstOrDefault(spawnPoint =>
spawnPoint.GetSpawnLocation() == SpawnLocation.TimberHearth
&& spawnPoint.IsShipSpawn() == false);
DebugLog.ToConsole($"Warning - _spawnList was null for player spawner!", MessageType.Warning);
return null;
}
return spawnList.FirstOrDefault(spawnPoint =>
spawnPoint.GetSpawnLocation() == SpawnLocation.TimberHearth
&& spawnPoint.IsShipSpawn() == false);
}
}

View File

@ -3,13 +3,12 @@ using QSB.EchoesOfTheEye.AirlockSync.WorldObjects;
using QSB.WorldSync;
using System.Threading;
namespace QSB.EchoesOfTheEye.AirlockSync
{
internal class AirlockManager : WorldObjectManager
{
// is this used in the prisoner sequence in the eye?
public override WorldObjectScene WorldObjectScene => WorldObjectScene.SolarSystem;
namespace QSB.EchoesOfTheEye.AirlockSync;
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct) => QSBWorldSync.Init<QSBGhostAirlock, GhostAirlock>();
}
internal class AirlockManager : WorldObjectManager
{
// is this used in the prisoner sequence in the eye?
public override WorldObjectScene WorldObjectScene => WorldObjectScene.SolarSystem;
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct) => QSBWorldSync.Init<QSBGhostAirlock, GhostAirlock>();
}

View File

@ -1,10 +1,9 @@
using QSB.WorldSync;
namespace QSB.EchoesOfTheEye.AirlockSync.WorldObjects
namespace QSB.EchoesOfTheEye.AirlockSync.WorldObjects;
// will be implemented when eote
internal class QSBGhostAirlock : WorldObject<GhostAirlock>
{
// will be implemented when eote
internal class QSBGhostAirlock : WorldObject<GhostAirlock>
{
public override void SendInitialState(uint to) { }
}
public override void SendInitialState(uint to) { }
}

View File

@ -3,13 +3,12 @@ using QSB.EchoesOfTheEye.LightSensorSync.WorldObjects;
using QSB.WorldSync;
using System.Threading;
namespace QSB.EchoesOfTheEye.LightSensorSync
{
internal class LightSensorManager : WorldObjectManager
{
// see AirlockManager question
public override WorldObjectScene WorldObjectScene => WorldObjectScene.SolarSystem;
namespace QSB.EchoesOfTheEye.LightSensorSync;
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct) => QSBWorldSync.Init<QSBLightSensor, SingleLightSensor>();
}
internal class LightSensorManager : WorldObjectManager
{
// see AirlockManager question
public override WorldObjectScene WorldObjectScene => WorldObjectScene.SolarSystem;
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct) => QSBWorldSync.Init<QSBLightSensor, SingleLightSensor>();
}

View File

@ -7,158 +7,157 @@ using QSB.WorldSync;
using System.Linq;
using UnityEngine;
namespace QSB.EchoesOfTheEye.LightSensorSync.Patches
namespace QSB.EchoesOfTheEye.LightSensorSync.Patches;
[HarmonyPatch]
internal class LightSensorPatches : QSBPatch
{
[HarmonyPatch]
internal class LightSensorPatches : QSBPatch
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
[HarmonyPrefix]
[HarmonyPatch(typeof(SingleLightSensor), nameof(SingleLightSensor.UpdateIllumination))]
public static bool UpdateIlluminationReplacement(SingleLightSensor __instance)
{
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
[HarmonyPrefix]
[HarmonyPatch(typeof(SingleLightSensor), nameof(SingleLightSensor.UpdateIllumination))]
public static bool UpdateIlluminationReplacement(SingleLightSensor __instance)
if (!QSBWorldSync.AllObjectsReady)
{
if (!QSBWorldSync.AllObjectsReady)
{
return true;
}
return true;
}
var qsbLightSensor = __instance.GetWorldObject<QSBLightSensor>();
qsbLightSensor._illuminatedByLocal = false;
__instance._illuminated = false;
if (__instance._illuminatingDreamLanternList != null)
{
__instance._illuminatingDreamLanternList.Clear();
}
var qsbLightSensor = __instance.GetWorldObject<QSBLightSensor>();
qsbLightSensor._illuminatedByLocal = false;
__instance._illuminated = false;
if (__instance._illuminatingDreamLanternList != null)
{
__instance._illuminatingDreamLanternList.Clear();
}
var vector = __instance.transform.TransformPoint(__instance._localSensorOffset);
var sensorWorldDir = Vector3.zero;
if (__instance._directionalSensor)
{
sensorWorldDir = __instance.transform.TransformDirection(__instance._localDirection).normalized;
}
var vector = __instance.transform.TransformPoint(__instance._localSensorOffset);
var sensorWorldDir = Vector3.zero;
if (__instance._directionalSensor)
{
sensorWorldDir = __instance.transform.TransformDirection(__instance._localDirection).normalized;
}
if (__instance._lightSources == null)
{
return false;
}
if (__instance._lightSources == null)
{
return false;
}
for (var i = 0; i < __instance._lightSources.Count; i++)
{
var source = __instance._lightSources[i];
for (var i = 0; i < __instance._lightSources.Count; i++)
{
var source = __instance._lightSources[i];
if ((__instance._lightSourceMask & source.GetLightSourceType()) == source.GetLightSourceType()
&& source.CheckIlluminationAtPoint(vector, __instance._sensorRadius, __instance._maxDistance))
if ((__instance._lightSourceMask & source.GetLightSourceType()) == source.GetLightSourceType()
&& source.CheckIlluminationAtPoint(vector, __instance._sensorRadius, __instance._maxDistance))
{
var lightSourceType = source.GetLightSourceType();
switch (lightSourceType)
{
var lightSourceType = source.GetLightSourceType();
switch (lightSourceType)
{
case LightSourceType.UNDEFINED:
{
var light = source as OWLight2;
var occludableLight = light.GetLight().shadows != LightShadows.None
&& light.GetLight().shadowStrength > 0.5f;
case LightSourceType.UNDEFINED:
{
var light = source as OWLight2;
var occludableLight = light.GetLight().shadows != LightShadows.None
&& light.GetLight().shadowStrength > 0.5f;
if (light.CheckIlluminationAtPoint(vector, __instance._sensorRadius, __instance._maxDistance)
&& !__instance.CheckOcclusion(light.transform.position, vector, sensorWorldDir, occludableLight))
{
__instance._illuminated = true;
qsbLightSensor._illuminatedByLocal = true;
}
break;
}
case LightSourceType.FLASHLIGHT:
{
if (source is Flashlight light && light == Locator.GetFlashlight())
{
var position = Locator.GetPlayerCamera().transform.position;
var to = __instance.transform.position - position;
if (Vector3.Angle(Locator.GetPlayerCamera().transform.forward, to) <= __instance._maxSpotHalfAngle
&& !__instance.CheckOcclusion(position, vector, sensorWorldDir))
{
__instance._illuminated = true;
qsbLightSensor._illuminatedByLocal = true;
}
}
else
{
var player = QSBPlayerManager.PlayerList.First(x => x.FlashLight == source as QSBFlashlight);
var position = player.Camera.transform.position;
var to = __instance.transform.position - position;
if (Vector3.Angle(player.Camera.transform.forward, to) <= __instance._maxSpotHalfAngle
&& !__instance.CheckOcclusion(position, vector, sensorWorldDir))
{
__instance._illuminated = true;
}
}
break;
}
case LightSourceType.PROBE:
{
var probe = Locator.GetProbe();
if (probe != null
&& probe.IsLaunched()
&& !probe.IsRetrieving()
&& probe.CheckIlluminationAtPoint(vector, __instance._sensorRadius, __instance._maxDistance)
&& !__instance.CheckOcclusion(probe.GetLightSourcePosition(), vector, sensorWorldDir))
{
__instance._illuminated = true;
qsbLightSensor._illuminatedByLocal = true;
}
break;
}
case LightSourceType.FLASHLIGHT | LightSourceType.PROBE:
case LightSourceType.FLASHLIGHT | LightSourceType.DREAM_LANTERN:
case LightSourceType.PROBE | LightSourceType.DREAM_LANTERN:
case LightSourceType.FLASHLIGHT | LightSourceType.PROBE | LightSourceType.DREAM_LANTERN:
break;
case LightSourceType.DREAM_LANTERN:
{
var dreamLanternController = __instance._lightSources[i] as DreamLanternController;
if (dreamLanternController.IsLit()
&& dreamLanternController.IsFocused(__instance._lanternFocusThreshold)
&& dreamLanternController.CheckIlluminationAtPoint(vector, __instance._sensorRadius, __instance._maxDistance)
&& !__instance.CheckOcclusion(dreamLanternController.GetLightPosition(), vector, sensorWorldDir))
{
__instance._illuminatingDreamLanternList.Add(dreamLanternController);
__instance._illuminated = true;
qsbLightSensor._illuminatedByLocal = true;
}
break;
}
case LightSourceType.SIMPLE_LANTERN:
foreach (var light in __instance._lightSources[i].GetLights())
{
var occludableLight = light.GetLight().shadows != LightShadows.None
&& light.GetLight().shadowStrength > 0.5f;
var maxDistance = Mathf.Min(__instance._maxSimpleLanternDistance, __instance._maxDistance);
if (light.CheckIlluminationAtPoint(vector, __instance._sensorRadius, maxDistance) && !__instance.CheckOcclusion(light.transform.position, vector, sensorWorldDir, occludableLight))
{
__instance._illuminated = true;
qsbLightSensor._illuminatedByLocal = true;
break;
}
}
break;
default:
if (lightSourceType == LightSourceType.VOLUME_ONLY)
if (light.CheckIlluminationAtPoint(vector, __instance._sensorRadius, __instance._maxDistance)
&& !__instance.CheckOcclusion(light.transform.position, vector, sensorWorldDir, occludableLight))
{
__instance._illuminated = true;
qsbLightSensor._illuminatedByLocal = true;
}
break;
}
}
case LightSourceType.FLASHLIGHT:
{
if (source is Flashlight light && light == Locator.GetFlashlight())
{
var position = Locator.GetPlayerCamera().transform.position;
var to = __instance.transform.position - position;
if (Vector3.Angle(Locator.GetPlayerCamera().transform.forward, to) <= __instance._maxSpotHalfAngle
&& !__instance.CheckOcclusion(position, vector, sensorWorldDir))
{
__instance._illuminated = true;
qsbLightSensor._illuminatedByLocal = true;
}
}
else
{
var player = QSBPlayerManager.PlayerList.First(x => x.FlashLight == source as QSBFlashlight);
var position = player.Camera.transform.position;
var to = __instance.transform.position - position;
if (Vector3.Angle(player.Camera.transform.forward, to) <= __instance._maxSpotHalfAngle
&& !__instance.CheckOcclusion(position, vector, sensorWorldDir))
{
__instance._illuminated = true;
}
}
break;
}
case LightSourceType.PROBE:
{
var probe = Locator.GetProbe();
if (probe != null
&& probe.IsLaunched()
&& !probe.IsRetrieving()
&& probe.CheckIlluminationAtPoint(vector, __instance._sensorRadius, __instance._maxDistance)
&& !__instance.CheckOcclusion(probe.GetLightSourcePosition(), vector, sensorWorldDir))
{
__instance._illuminated = true;
qsbLightSensor._illuminatedByLocal = true;
}
break;
}
case LightSourceType.FLASHLIGHT | LightSourceType.PROBE:
case LightSourceType.FLASHLIGHT | LightSourceType.DREAM_LANTERN:
case LightSourceType.PROBE | LightSourceType.DREAM_LANTERN:
case LightSourceType.FLASHLIGHT | LightSourceType.PROBE | LightSourceType.DREAM_LANTERN:
break;
case LightSourceType.DREAM_LANTERN:
{
var dreamLanternController = __instance._lightSources[i] as DreamLanternController;
if (dreamLanternController.IsLit()
&& dreamLanternController.IsFocused(__instance._lanternFocusThreshold)
&& dreamLanternController.CheckIlluminationAtPoint(vector, __instance._sensorRadius, __instance._maxDistance)
&& !__instance.CheckOcclusion(dreamLanternController.GetLightPosition(), vector, sensorWorldDir))
{
__instance._illuminatingDreamLanternList.Add(dreamLanternController);
__instance._illuminated = true;
qsbLightSensor._illuminatedByLocal = true;
}
break;
}
case LightSourceType.SIMPLE_LANTERN:
foreach (var light in __instance._lightSources[i].GetLights())
{
var occludableLight = light.GetLight().shadows != LightShadows.None
&& light.GetLight().shadowStrength > 0.5f;
var maxDistance = Mathf.Min(__instance._maxSimpleLanternDistance, __instance._maxDistance);
if (light.CheckIlluminationAtPoint(vector, __instance._sensorRadius, maxDistance) && !__instance.CheckOcclusion(light.transform.position, vector, sensorWorldDir, occludableLight))
{
__instance._illuminated = true;
qsbLightSensor._illuminatedByLocal = true;
break;
}
}
break;
default:
if (lightSourceType == LightSourceType.VOLUME_ONLY)
{
__instance._illuminated = true;
qsbLightSensor._illuminatedByLocal = true;
}
break;
}
}
return false;
}
return false;
}
}

View File

@ -3,44 +3,43 @@ using QSB.WorldSync;
using System;
using System.Threading;
namespace QSB.EchoesOfTheEye.LightSensorSync.WorldObjects
namespace QSB.EchoesOfTheEye.LightSensorSync.WorldObjects;
// will be implemented when eote
internal class QSBLightSensor : WorldObject<SingleLightSensor>
{
// will be implemented when eote
internal class QSBLightSensor : WorldObject<SingleLightSensor>
internal bool _illuminatedByLocal;
public event Action OnDetectLocalLight;
public event Action OnDetectLocalDarkness;
public override async UniTask Init(CancellationToken ct)
{
internal bool _illuminatedByLocal;
public event Action OnDetectLocalLight;
public event Action OnDetectLocalDarkness;
public override async UniTask Init(CancellationToken ct)
{
AttachedObject.OnDetectLight += OnDetectLight;
AttachedObject.OnDetectDarkness += OnDetectDarkness;
}
public override void OnRemoval()
{
AttachedObject.OnDetectLight -= OnDetectLight;
AttachedObject.OnDetectDarkness -= OnDetectDarkness;
}
private void OnDetectLight()
{
if (_illuminatedByLocal)
{
OnDetectLocalLight?.Invoke();
}
}
private void OnDetectDarkness()
{
if (_illuminatedByLocal)
{
OnDetectLocalDarkness?.Invoke();
}
}
public override void SendInitialState(uint to) { }
AttachedObject.OnDetectLight += OnDetectLight;
AttachedObject.OnDetectDarkness += OnDetectDarkness;
}
public override void OnRemoval()
{
AttachedObject.OnDetectLight -= OnDetectLight;
AttachedObject.OnDetectDarkness -= OnDetectDarkness;
}
private void OnDetectLight()
{
if (_illuminatedByLocal)
{
OnDetectLocalLight?.Invoke();
}
}
private void OnDetectDarkness()
{
if (_illuminatedByLocal)
{
OnDetectLocalDarkness?.Invoke();
}
}
public override void SendInitialState(uint to) { }
}

View File

@ -1,10 +1,9 @@
using QSB.EchoesOfTheEye.RaftSync.WorldObjects;
using QSB.Messaging;
namespace QSB.EchoesOfTheEye.RaftSync.Messages
namespace QSB.EchoesOfTheEye.RaftSync.Messages;
public class RaftDockOnPressInteractMessage : QSBWorldObjectMessage<QSBRaftDock>
{
public class RaftDockOnPressInteractMessage : QSBWorldObjectMessage<QSBRaftDock>
{
public override void OnReceiveRemote() => WorldObject.OnPressInteract();
}
}
public override void OnReceiveRemote() => WorldObject.OnPressInteract();
}

View File

@ -8,57 +8,56 @@ using QSB.Utility;
using QSB.WorldSync;
using UnityEngine;
namespace QSB.EchoesOfTheEye.RaftSync.Patches
namespace QSB.EchoesOfTheEye.RaftSync.Patches;
public class RaftPatches : QSBPatch
{
public class RaftPatches : QSBPatch
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
[HarmonyPrefix]
[HarmonyPatch(typeof(RaftController), nameof(RaftController.OnPressInteract))]
private static bool OnPressInteract(RaftController __instance)
{
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
__instance._interactReceiver.SetInteractionEnabled(false);
[HarmonyPrefix]
[HarmonyPatch(typeof(RaftController), nameof(RaftController.OnPressInteract))]
private static bool OnPressInteract(RaftController __instance)
var qsbRaft = __instance.GetWorldObject<QSBRaft>();
qsbRaft.TransformSync.netIdentity.UpdateAuthQueue(AuthQueueAction.Force);
Delay.RunWhen(() => qsbRaft.TransformSync.hasAuthority, () =>
{
__instance._interactReceiver.SetInteractionEnabled(false);
var normalized = Vector3.ProjectOnPlane(Locator.GetPlayerCamera().transform.forward, __instance.transform.up).normalized;
__instance._raftBody.AddVelocityChange(normalized * 5f);
__instance._effectsController.PlayRaftPush();
__instance._pushTime = Time.time;
});
var qsbRaft = __instance.GetWorldObject<QSBRaft>();
qsbRaft.TransformSync.netIdentity.UpdateAuthQueue(AuthQueueAction.Force);
Delay.RunWhen(() => qsbRaft.TransformSync.hasAuthority, () =>
{
var normalized = Vector3.ProjectOnPlane(Locator.GetPlayerCamera().transform.forward, __instance.transform.up).normalized;
__instance._raftBody.AddVelocityChange(normalized * 5f);
__instance._effectsController.PlayRaftPush();
__instance._pushTime = Time.time;
});
return false;
}
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(RaftDock), nameof(RaftDock.OnPressInteract))]
private static bool OnPressInteract(RaftDock __instance)
[HarmonyPrefix]
[HarmonyPatch(typeof(RaftDock), nameof(RaftDock.OnPressInteract))]
private static bool OnPressInteract(RaftDock __instance)
{
if (__instance._raft != null && __instance._state == RaftCarrier.DockState.Docked)
{
if (__instance._raft != null && __instance._state == RaftCarrier.DockState.Docked)
{
__instance._raftUndockCountDown = __instance._raft.dropDelay;
__instance._state = RaftCarrier.DockState.WaitForExit;
__instance._raft.SetRailingRaised(true);
if (__instance._gearInterface != null)
{
__instance._gearInterface.AddRotation(90f);
}
__instance.enabled = true;
__instance.GetWorldObject<QSBRaftDock>().SendMessage(new RaftDockOnPressInteractMessage());
return false;
}
__instance._raftUndockCountDown = __instance._raft.dropDelay;
__instance._state = RaftCarrier.DockState.WaitForExit;
__instance._raft.SetRailingRaised(true);
if (__instance._gearInterface != null)
{
__instance._gearInterface.PlayFailure();
__instance._gearInterface.AddRotation(90f);
}
__instance.enabled = true;
__instance.GetWorldObject<QSBRaftDock>().SendMessage(new RaftDockOnPressInteractMessage());
return false;
}
if (__instance._gearInterface != null)
{
__instance._gearInterface.PlayFailure();
}
return false;
}
}
}

View File

@ -5,22 +5,21 @@ using QSB.WorldSync;
using System.Collections.Generic;
using System.Threading;
namespace QSB.EchoesOfTheEye.RaftSync
namespace QSB.EchoesOfTheEye.RaftSync;
public class RaftManager : WorldObjectManager
{
public class RaftManager : WorldObjectManager
public override WorldObjectScene WorldObjectScene => WorldObjectScene.SolarSystem;
public static readonly List<RaftController> Rafts = new();
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct)
{
public override WorldObjectScene WorldObjectScene => WorldObjectScene.SolarSystem;
Rafts.Clear();
Rafts.AddRange(QSBWorldSync.GetUnityObjects<RaftController>().SortDeterministic());
QSBWorldSync.Init<QSBRaft, RaftController>(Rafts);
public static readonly List<RaftController> Rafts = new();
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct)
{
Rafts.Clear();
Rafts.AddRange(QSBWorldSync.GetUnityObjects<RaftController>().SortDeterministic());
QSBWorldSync.Init<QSBRaft, RaftController>(Rafts);
QSBWorldSync.Init<QSBRaftDock, RaftDock>();
QSBWorldSync.Init<QSBDamRaftLift, DamRaftLift>();
}
QSBWorldSync.Init<QSBRaftDock, RaftDock>();
QSBWorldSync.Init<QSBDamRaftLift, DamRaftLift>();
}
}
}

View File

@ -5,83 +5,82 @@ using QSB.Utility;
using QSB.WorldSync;
using System.Collections.Generic;
namespace QSB.EchoesOfTheEye.RaftSync.TransformSync
namespace QSB.EchoesOfTheEye.RaftSync.TransformSync;
public class RaftTransformSync : UnsectoredRigidbodySync
{
public class RaftTransformSync : UnsectoredRigidbodySync
protected override bool UseInterpolation => false;
private QSBRaft _qsbRaft;
private static readonly List<RaftTransformSync> _instances = new();
protected override OWRigidbody InitAttachedRigidbody() => _qsbRaft.AttachedObject._raftBody;
public override void OnStartClient()
{
protected override bool UseInterpolation => false;
private QSBRaft _qsbRaft;
private static readonly List<RaftTransformSync> _instances = new();
protected override OWRigidbody InitAttachedRigidbody() => _qsbRaft.AttachedObject._raftBody;
public override void OnStartClient()
_instances.Add(this);
if (QSBCore.IsHost)
{
_instances.Add(this);
if (QSBCore.IsHost)
{
netIdentity.RegisterAuthQueue();
}
base.OnStartClient();
netIdentity.RegisterAuthQueue();
}
public override void OnStopClient()
{
_instances.Remove(this);
if (QSBCore.IsHost)
{
netIdentity.UnregisterAuthQueue();
}
base.OnStartClient();
}
base.OnStopClient();
public override void OnStopClient()
{
_instances.Remove(this);
if (QSBCore.IsHost)
{
netIdentity.UnregisterAuthQueue();
}
protected override void Init()
{
_qsbRaft = RaftManager.Rafts[_instances.IndexOf(this)].GetWorldObject<QSBRaft>();
_qsbRaft.TransformSync = this;
base.OnStopClient();
}
base.Init();
SetReferenceTransform(AttachedRigidbody.GetOrigParent());
protected override void Init()
{
_qsbRaft = RaftManager.Rafts[_instances.IndexOf(this)].GetWorldObject<QSBRaft>();
_qsbRaft.TransformSync = this;
AttachedRigidbody.OnUnsuspendOWRigidbody += OnUnsuspend;
AttachedRigidbody.OnSuspendOWRigidbody += OnSuspend;
netIdentity.UpdateAuthQueue(AttachedRigidbody.IsSuspended() ? AuthQueueAction.Remove : AuthQueueAction.Add);
}
base.Init();
SetReferenceTransform(AttachedRigidbody.GetOrigParent());
protected override void Uninit()
{
base.Uninit();
AttachedRigidbody.OnUnsuspendOWRigidbody += OnUnsuspend;
AttachedRigidbody.OnSuspendOWRigidbody += OnSuspend;
netIdentity.UpdateAuthQueue(AttachedRigidbody.IsSuspended() ? AuthQueueAction.Remove : AuthQueueAction.Add);
}
AttachedRigidbody.OnUnsuspendOWRigidbody -= OnUnsuspend;
AttachedRigidbody.OnSuspendOWRigidbody -= OnSuspend;
}
protected override void Uninit()
{
base.Uninit();
private void OnUnsuspend(OWRigidbody suspendedBody) => netIdentity.UpdateAuthQueue(AuthQueueAction.Add);
private void OnSuspend(OWRigidbody suspendedBody) => netIdentity.UpdateAuthQueue(AuthQueueAction.Remove);
AttachedRigidbody.OnUnsuspendOWRigidbody -= OnUnsuspend;
AttachedRigidbody.OnSuspendOWRigidbody -= OnSuspend;
}
public override void OnStartAuthority() => DebugLog.DebugWrite($"{this} - authority = true");
public override void OnStopAuthority() => DebugLog.DebugWrite($"{this} - authority = false");
private void OnUnsuspend(OWRigidbody suspendedBody) => netIdentity.UpdateAuthQueue(AuthQueueAction.Add);
private void OnSuspend(OWRigidbody suspendedBody) => netIdentity.UpdateAuthQueue(AuthQueueAction.Remove);
/// <summary>
/// replacement for base method
/// using SetPos/Rot instead of Move
/// </summary>
protected override void ApplyToAttached()
{
var targetPos = ReferenceTransform.FromRelPos(transform.position);
var targetRot = ReferenceTransform.FromRelRot(transform.rotation);
public override void OnStartAuthority() => DebugLog.DebugWrite($"{this} - authority = true");
public override void OnStopAuthority() => DebugLog.DebugWrite($"{this} - authority = false");
AttachedRigidbody.SetPosition(targetPos);
AttachedRigidbody.SetRotation(targetRot);
/// <summary>
/// replacement for base method
/// using SetPos/Rot instead of Move
/// </summary>
protected override void ApplyToAttached()
{
var targetPos = ReferenceTransform.FromRelPos(transform.position);
var targetRot = ReferenceTransform.FromRelRot(transform.rotation);
var targetVelocity = ReferenceRigidbody.FromRelVel(Velocity, targetPos);
var targetAngularVelocity = ReferenceRigidbody.FromRelAngVel(AngularVelocity);
AttachedRigidbody.SetPosition(targetPos);
AttachedRigidbody.SetRotation(targetRot);
AttachedRigidbody.SetVelocity(targetVelocity);
AttachedRigidbody.SetAngularVelocity(targetAngularVelocity);
}
var targetVelocity = ReferenceRigidbody.FromRelVel(Velocity, targetPos);
var targetAngularVelocity = ReferenceRigidbody.FromRelAngVel(AngularVelocity);
AttachedRigidbody.SetVelocity(targetVelocity);
AttachedRigidbody.SetAngularVelocity(targetAngularVelocity);
}
}

View File

@ -1,12 +1,11 @@
using QSB.WorldSync;
namespace QSB.EchoesOfTheEye.RaftSync.WorldObjects
namespace QSB.EchoesOfTheEye.RaftSync.WorldObjects;
public class QSBDamRaftLift : WorldObject<DamRaftLift>
{
public class QSBDamRaftLift : WorldObject<DamRaftLift>
public override void SendInitialState(uint to)
{
public override void SendInitialState(uint to)
{
// todo SendInitialState
}
// todo SendInitialState
}
}
}

View File

@ -8,58 +8,57 @@ using System.Linq;
using System.Threading;
using UnityEngine;
namespace QSB.EchoesOfTheEye.RaftSync.WorldObjects
namespace QSB.EchoesOfTheEye.RaftSync.WorldObjects;
public class QSBRaft : WorldObject<RaftController>
{
public class QSBRaft : WorldObject<RaftController>
public override bool ShouldDisplayDebug() => false;
public RaftTransformSync TransformSync;
private QSBLightSensor[] _lightSensors;
public override async UniTask Init(CancellationToken ct)
{
public override bool ShouldDisplayDebug() => false;
public RaftTransformSync TransformSync;
private QSBLightSensor[] _lightSensors;
public override async UniTask Init(CancellationToken ct)
if (QSBCore.IsHost)
{
if (QSBCore.IsHost)
{
NetworkServer.Spawn(Object.Instantiate(QSBNetworkManager.singleton.RaftPrefab));
}
await UniTask.WaitUntil(() => TransformSync, cancellationToken: ct);
await UniTask.WaitUntil(() => QSBWorldSync.AllObjectsAdded, cancellationToken: ct);
_lightSensors = AttachedObject._lightSensors.Select(x => x.GetWorldObject<QSBLightSensor>()).ToArray();
foreach (var lightSensor in _lightSensors)
{
lightSensor.OnDetectLocalLight += OnDetectLocalLight;
}
NetworkServer.Spawn(Object.Instantiate(QSBNetworkManager.singleton.RaftPrefab));
}
public override void OnRemoval()
{
if (QSBCore.IsHost)
{
NetworkServer.Destroy(TransformSync.gameObject);
}
await UniTask.WaitUntil(() => TransformSync, cancellationToken: ct);
foreach (var lightSensor in _lightSensors)
{
lightSensor.OnDetectLocalLight -= OnDetectLocalLight;
}
}
await UniTask.WaitUntil(() => QSBWorldSync.AllObjectsAdded, cancellationToken: ct);
_lightSensors = AttachedObject._lightSensors.Select(x => x.GetWorldObject<QSBLightSensor>()).ToArray();
private void OnDetectLocalLight()
foreach (var lightSensor in _lightSensors)
{
if (AttachedObject.IsPlayerRiding())
{
TransformSync.netIdentity.UpdateAuthQueue(AuthQueueAction.Force);
}
}
public override void SendInitialState(uint to)
{
// todo?? SendInitialState
lightSensor.OnDetectLocalLight += OnDetectLocalLight;
}
}
public override void OnRemoval()
{
if (QSBCore.IsHost)
{
NetworkServer.Destroy(TransformSync.gameObject);
}
foreach (var lightSensor in _lightSensors)
{
lightSensor.OnDetectLocalLight -= OnDetectLocalLight;
}
}
private void OnDetectLocalLight()
{
if (AttachedObject.IsPlayerRiding())
{
TransformSync.netIdentity.UpdateAuthQueue(AuthQueueAction.Force);
}
}
public override void SendInitialState(uint to)
{
// todo?? SendInitialState
}
}

View File

@ -1,34 +1,33 @@
using QSB.WorldSync;
namespace QSB.EchoesOfTheEye.RaftSync.WorldObjects
namespace QSB.EchoesOfTheEye.RaftSync.WorldObjects;
public class QSBRaftDock : WorldObject<RaftDock>
{
public class QSBRaftDock : WorldObject<RaftDock>
public override void SendInitialState(uint to)
{
public override void SendInitialState(uint to)
// todo SendInitialState
}
public void OnPressInteract()
{
if (AttachedObject._raft != null && AttachedObject._state == RaftCarrier.DockState.Docked)
{
// todo SendInitialState
}
public void OnPressInteract()
{
if (AttachedObject._raft != null && AttachedObject._state == RaftCarrier.DockState.Docked)
{
AttachedObject._raftUndockCountDown = AttachedObject._raft.dropDelay;
AttachedObject._state = RaftCarrier.DockState.WaitForExit;
AttachedObject._raft.SetRailingRaised(true);
if (AttachedObject._gearInterface != null)
{
AttachedObject._gearInterface.AddRotation(90f);
}
AttachedObject.enabled = true;
return;
}
AttachedObject._raftUndockCountDown = AttachedObject._raft.dropDelay;
AttachedObject._state = RaftCarrier.DockState.WaitForExit;
AttachedObject._raft.SetRailingRaised(true);
if (AttachedObject._gearInterface != null)
{
AttachedObject._gearInterface.PlayFailure();
AttachedObject._gearInterface.AddRotation(90f);
}
AttachedObject.enabled = true;
return;
}
if (AttachedObject._gearInterface != null)
{
AttachedObject._gearInterface.PlayFailure();
}
}
}
}

View File

@ -1,10 +1,9 @@
using QSB.EchoesOfTheEye.SlideProjectors.WorldObjects;
using QSB.Messaging;
namespace QSB.EchoesOfTheEye.SlideProjectors.Messages
namespace QSB.EchoesOfTheEye.SlideProjectors.Messages;
internal class NextSlideMessage : QSBWorldObjectMessage<QSBSlideProjector>
{
internal class NextSlideMessage : QSBWorldObjectMessage<QSBSlideProjector>
{
public override void OnReceiveRemote() => WorldObject.NextSlide();
}
public override void OnReceiveRemote() => WorldObject.NextSlide();
}

View File

@ -1,10 +1,9 @@
using QSB.EchoesOfTheEye.SlideProjectors.WorldObjects;
using QSB.Messaging;
namespace QSB.EchoesOfTheEye.SlideProjectors.Messages
namespace QSB.EchoesOfTheEye.SlideProjectors.Messages;
internal class PreviousSlideMessage : QSBWorldObjectMessage<QSBSlideProjector>
{
internal class PreviousSlideMessage : QSBWorldObjectMessage<QSBSlideProjector>
{
public override void OnReceiveRemote() => WorldObject.PreviousSlide();
}
public override void OnReceiveRemote() => WorldObject.PreviousSlide();
}

View File

@ -2,54 +2,53 @@
using QSB.EchoesOfTheEye.SlideProjectors.WorldObjects;
using QSB.Messaging;
namespace QSB.EchoesOfTheEye.SlideProjectors.Messages
namespace QSB.EchoesOfTheEye.SlideProjectors.Messages;
public class ProjectorAuthorityMessage : QSBWorldObjectMessage<QSBSlideProjector>
{
public class ProjectorAuthorityMessage : QSBWorldObjectMessage<QSBSlideProjector>
private uint AuthorityOwner;
public ProjectorAuthorityMessage(uint authorityOwner) => AuthorityOwner = authorityOwner;
public override void Serialize(NetworkWriter writer)
{
private uint AuthorityOwner;
base.Serialize(writer);
writer.Write(AuthorityOwner);
}
public ProjectorAuthorityMessage(uint authorityOwner) => AuthorityOwner = authorityOwner;
public override void Deserialize(NetworkReader reader)
{
base.Deserialize(reader);
AuthorityOwner = reader.Read<uint>();
}
public override void Serialize(NetworkWriter writer)
public override bool ShouldReceive
{
get
{
base.Serialize(writer);
writer.Write(AuthorityOwner);
}
public override void Deserialize(NetworkReader reader)
{
base.Deserialize(reader);
AuthorityOwner = reader.Read<uint>();
}
public override bool ShouldReceive
{
get
if (!base.ShouldReceive)
{
if (!base.ShouldReceive)
{
return false;
}
// Deciding if to change the object's owner
// Message
// | = 0 | > 0 |
// = 0 | No | Yes |
// > 0 | Yes | No |
// if Obj==Message then No
// Obj
return (WorldObject.ControllingPlayer == 0 || AuthorityOwner == 0)
&& WorldObject.ControllingPlayer != AuthorityOwner;
return false;
}
}
public override void OnReceiveLocal() => OnReceiveRemote();
// Deciding if to change the object's owner
// Message
// | = 0 | > 0 |
// = 0 | No | Yes |
// > 0 | Yes | No |
// if Obj==Message then No
// Obj
public override void OnReceiveRemote()
{
WorldObject.ControllingPlayer = AuthorityOwner;
WorldObject.OnChangeAuthority(AuthorityOwner);
return (WorldObject.ControllingPlayer == 0 || AuthorityOwner == 0)
&& WorldObject.ControllingPlayer != AuthorityOwner;
}
}
public override void OnReceiveLocal() => OnReceiveRemote();
public override void OnReceiveRemote()
{
WorldObject.ControllingPlayer = AuthorityOwner;
WorldObject.OnChangeAuthority(AuthorityOwner);
}
}

View File

@ -6,34 +6,33 @@ using QSB.Patches;
using QSB.Player;
using QSB.WorldSync;
namespace QSB.EchoesOfTheEye.SlideProjectors.Patches
namespace QSB.EchoesOfTheEye.SlideProjectors.Patches;
internal class ProjectorPatches : QSBPatch
{
internal class ProjectorPatches : QSBPatch
{
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
[HarmonyPostfix]
[HarmonyPatch(typeof(SlideProjector), nameof(SlideProjector.OnPressInteract))]
public static void Interact(SlideProjector __instance) =>
__instance.GetWorldObject<QSBSlideProjector>()
.SendMessage(new ProjectorAuthorityMessage(QSBPlayerManager.LocalPlayerId));
[HarmonyPostfix]
[HarmonyPatch(typeof(SlideProjector), nameof(SlideProjector.OnPressInteract))]
public static void Interact(SlideProjector __instance) =>
__instance.GetWorldObject<QSBSlideProjector>()
.SendMessage(new ProjectorAuthorityMessage(QSBPlayerManager.LocalPlayerId));
[HarmonyPostfix]
[HarmonyPatch(typeof(SlideProjector), nameof(SlideProjector.CancelInteraction))]
public static void CancelInteract(SlideProjector __instance) =>
__instance.GetWorldObject<QSBSlideProjector>()
.SendMessage(new ProjectorAuthorityMessage(0));
[HarmonyPostfix]
[HarmonyPatch(typeof(SlideProjector), nameof(SlideProjector.CancelInteraction))]
public static void CancelInteract(SlideProjector __instance) =>
__instance.GetWorldObject<QSBSlideProjector>()
.SendMessage(new ProjectorAuthorityMessage(0));
[HarmonyPostfix]
[HarmonyPatch(typeof(SlideProjector), nameof(SlideProjector.NextSlide))]
public static void NextSlide(SlideProjector __instance) =>
__instance.GetWorldObject<QSBSlideProjector>()
.SendMessage(new NextSlideMessage());
[HarmonyPostfix]
[HarmonyPatch(typeof(SlideProjector), nameof(SlideProjector.NextSlide))]
public static void NextSlide(SlideProjector __instance) =>
__instance.GetWorldObject<QSBSlideProjector>()
.SendMessage(new NextSlideMessage());
[HarmonyPostfix]
[HarmonyPatch(typeof(SlideProjector), nameof(SlideProjector.PreviousSlide))]
public static void PreviousSlide(SlideProjector __instance) =>
__instance.GetWorldObject<QSBSlideProjector>()
.SendMessage(new PreviousSlideMessage());
}
[HarmonyPostfix]
[HarmonyPatch(typeof(SlideProjector), nameof(SlideProjector.PreviousSlide))]
public static void PreviousSlide(SlideProjector __instance) =>
__instance.GetWorldObject<QSBSlideProjector>()
.SendMessage(new PreviousSlideMessage());
}

View File

@ -3,12 +3,11 @@ using QSB.EchoesOfTheEye.SlideProjectors.WorldObjects;
using QSB.WorldSync;
using System.Threading;
namespace QSB.EchoesOfTheEye.SlideProjectors
{
internal class SlideProjectorManager : WorldObjectManager
{
public override WorldObjectScene WorldObjectScene => WorldObjectScene.SolarSystem;
namespace QSB.EchoesOfTheEye.SlideProjectors;
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct) => QSBWorldSync.Init<QSBSlideProjector, SlideProjector>();
}
internal class SlideProjectorManager : WorldObjectManager
{
public override WorldObjectScene WorldObjectScene => WorldObjectScene.SolarSystem;
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct) => QSBWorldSync.Init<QSBSlideProjector, SlideProjector>();
}

View File

@ -3,81 +3,80 @@ using QSB.Utility;
using QSB.WorldSync;
using System.Threading;
namespace QSB.EchoesOfTheEye.SlideProjectors.WorldObjects
namespace QSB.EchoesOfTheEye.SlideProjectors.WorldObjects;
public class QSBSlideProjector : WorldObject<SlideProjector>
{
public class QSBSlideProjector : WorldObject<SlideProjector>
public uint ControllingPlayer;
public override async UniTask Init(CancellationToken ct)
{
public uint ControllingPlayer;
DebugLog.DebugWrite($"Init {this}");
}
public override async UniTask Init(CancellationToken ct)
{
DebugLog.DebugWrite($"Init {this}");
}
public override void SendInitialState(uint to)
{
// todo SendInitialState
}
public override void SendInitialState(uint to)
{
// todo SendInitialState
}
public void OnChangeAuthority(uint newOwner)
{
DebugLog.DebugWrite($"{this} change ControllingPlayer to {newOwner}");
}
public void OnChangeAuthority(uint newOwner)
public void NextSlide()
{
var hasChangedSlide = false;
if (AttachedObject._slideItem != null && AttachedObject._slideItem.slidesContainer.NextSlideAvailable())
{
DebugLog.DebugWrite($"{this} change ControllingPlayer to {newOwner}");
}
public void NextSlide()
{
var hasChangedSlide = false;
if (AttachedObject._slideItem != null && AttachedObject._slideItem.slidesContainer.NextSlideAvailable())
hasChangedSlide = AttachedObject._slideItem.slidesContainer.IncreaseSlideIndex();
if (hasChangedSlide)
{
hasChangedSlide = AttachedObject._slideItem.slidesContainer.IncreaseSlideIndex();
if (hasChangedSlide)
if (AttachedObject._oneShotSource != null)
{
if (AttachedObject._oneShotSource != null)
{
AttachedObject._oneShotSource.PlayOneShot(AudioType.Projector_Next);
}
AttachedObject._oneShotSource.PlayOneShot(AudioType.Projector_Next);
}
if (AttachedObject.IsProjectorFullyLit())
{
AttachedObject._slideItem.slidesContainer.SetCurrentRead();
AttachedObject._slideItem.slidesContainer.TryPlayMusicForCurrentSlideTransition(true);
}
if (AttachedObject.IsProjectorFullyLit())
{
AttachedObject._slideItem.slidesContainer.SetCurrentRead();
AttachedObject._slideItem.slidesContainer.TryPlayMusicForCurrentSlideTransition(true);
}
}
}
if (AttachedObject._gearInterface != null)
if (AttachedObject._gearInterface != null)
{
var audioVolume = hasChangedSlide ? 0f : 0.5f;
AttachedObject._gearInterface.AddRotation(45f, audioVolume);
}
}
public void PreviousSlide()
{
var hasChangedSlide = false;
if (AttachedObject._slideItem != null && AttachedObject._slideItem.slidesContainer.PrevSlideAvailable())
{
hasChangedSlide = AttachedObject._slideItem.slidesContainer.DecreaseSlideIndex();
if (hasChangedSlide)
{
var audioVolume = hasChangedSlide ? 0f : 0.5f;
AttachedObject._gearInterface.AddRotation(45f, audioVolume);
if (AttachedObject._oneShotSource != null)
{
AttachedObject._oneShotSource.PlayOneShot(AudioType.Projector_Prev);
}
if (AttachedObject.IsProjectorFullyLit())
{
AttachedObject._slideItem.slidesContainer.SetCurrentRead();
AttachedObject._slideItem.slidesContainer.TryPlayMusicForCurrentSlideTransition(false);
}
}
}
public void PreviousSlide()
if (AttachedObject._gearInterface != null)
{
var hasChangedSlide = false;
if (AttachedObject._slideItem != null && AttachedObject._slideItem.slidesContainer.PrevSlideAvailable())
{
hasChangedSlide = AttachedObject._slideItem.slidesContainer.DecreaseSlideIndex();
if (hasChangedSlide)
{
if (AttachedObject._oneShotSource != null)
{
AttachedObject._oneShotSource.PlayOneShot(AudioType.Projector_Prev);
}
if (AttachedObject.IsProjectorFullyLit())
{
AttachedObject._slideItem.slidesContainer.SetCurrentRead();
AttachedObject._slideItem.slidesContainer.TryPlayMusicForCurrentSlideTransition(false);
}
}
}
if (AttachedObject._gearInterface != null)
{
var audioVolume = hasChangedSlide ? 0f : 0.5f;
AttachedObject._gearInterface.AddRotation(-45f, audioVolume);
}
var audioVolume = hasChangedSlide ? 0f : 0.5f;
AttachedObject._gearInterface.AddRotation(-45f, audioVolume);
}
}
}

View File

@ -3,13 +3,12 @@ using QSB.ElevatorSync.WorldObjects;
using QSB.WorldSync;
using System.Threading;
namespace QSB.ElevatorSync
{
public class ElevatorManager : WorldObjectManager
{
public override WorldObjectScene WorldObjectScene => WorldObjectScene.SolarSystem;
namespace QSB.ElevatorSync;
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct)
=> QSBWorldSync.Init<QSBElevator, Elevator>();
}
public class ElevatorManager : WorldObjectManager
{
public override WorldObjectScene WorldObjectScene => WorldObjectScene.SolarSystem;
public override async UniTask BuildWorldObjects(OWScene scene, CancellationToken ct)
=> QSBWorldSync.Init<QSBElevator, Elevator>();
}

View File

@ -1,12 +1,11 @@
using QSB.ElevatorSync.WorldObjects;
using QSB.Messaging;
namespace QSB.ElevatorSync.Messages
{
public class ElevatorMessage : QSBWorldObjectMessage<QSBElevator, bool>
{
public ElevatorMessage(bool isGoingUp) => Data = isGoingUp;
namespace QSB.ElevatorSync.Messages;
public override void OnReceiveRemote() => WorldObject.RemoteCall(Data);
}
public class ElevatorMessage : QSBWorldObjectMessage<QSBElevator, bool>
{
public ElevatorMessage(bool isGoingUp) => Data = isGoingUp;
public override void OnReceiveRemote() => WorldObject.RemoteCall(Data);
}

View File

@ -5,20 +5,19 @@ using QSB.Messaging;
using QSB.Patches;
using QSB.WorldSync;
namespace QSB.ElevatorSync.Patches
{
[HarmonyPatch]
public class ElevatorPatches : QSBPatch
{
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
namespace QSB.ElevatorSync.Patches;
[HarmonyPostfix]
[HarmonyPatch(typeof(Elevator), nameof(Elevator.StartLift))]
public static void Elevator_StartLift(Elevator __instance)
{
var isGoingUp = __instance._goingToTheEnd;
var qsbElevator = __instance.GetWorldObject<QSBElevator>();
qsbElevator.SendMessage(new ElevatorMessage(isGoingUp));
}
[HarmonyPatch]
public class ElevatorPatches : QSBPatch
{
public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect;
[HarmonyPostfix]
[HarmonyPatch(typeof(Elevator), nameof(Elevator.StartLift))]
public static void Elevator_StartLift(Elevator __instance)
{
var isGoingUp = __instance._goingToTheEnd;
var qsbElevator = __instance.GetWorldObject<QSBElevator>();
qsbElevator.SendMessage(new ElevatorMessage(isGoingUp));
}
}

Some files were not shown because too many files have changed in this diff Show More