Merge branch 'dev' into steamworks-v3

This commit is contained in:
_nebula 2023-08-04 15:27:40 +01:00
commit b4763c835e
19 changed files with 195 additions and 84 deletions

View File

@ -4,11 +4,27 @@ using UnityEngine.Events;
public interface IQSBAPI public interface IQSBAPI
{ {
#region General
/// <summary> /// <summary>
/// If called, all players connected to YOUR hosted game must have this mod installed. /// If called, all players connected to YOUR hosted game must have this mod installed.
/// </summary> /// </summary>
void RegisterRequiredForAllPlayers(IModBehaviour mod); void RegisterRequiredForAllPlayers(IModBehaviour mod);
/// <summary>
/// Returns if the current player is the host.
/// </summary>
bool GetIsHost();
/// <summary>
/// Returns if the current player is in multiplayer.
/// </summary>
bool GetIsInMultiplayer();
#endregion
#region Player
/// <summary> /// <summary>
/// Returns the player ID of the current player. /// Returns the player ID of the current player.
/// </summary> /// </summary>
@ -22,16 +38,18 @@ public interface IQSBAPI
/// <summary> /// <summary>
/// Returns the list of IDs of all connected players. /// Returns the list of IDs of all connected players.
///
/// The first player in the list is the host.
/// </summary> /// </summary>
uint[] GetPlayerIDs(); uint[] GetPlayerIDs();
/// <summary> /// <summary>
/// Invoked when a player joins the game. /// Invoked when any player (local or remote) joins the game.
/// </summary> /// </summary>
UnityEvent<uint> OnPlayerJoin(); UnityEvent<uint> OnPlayerJoin();
/// <summary> /// <summary>
/// Invoked when a player leaves the game. /// Invoked when any player (local or remote) leaves the game.
/// </summary> /// </summary>
UnityEvent<uint> OnPlayerLeave(); UnityEvent<uint> OnPlayerLeave();
@ -53,8 +71,14 @@ public interface IQSBAPI
/// <returns>The data requested. If key is not valid, returns default.</returns> /// <returns>The data requested. If key is not valid, returns default.</returns>
T GetCustomData<T>(uint playerId, string key); T GetCustomData<T>(uint playerId, string key);
#endregion
#region Messaging
/// <summary> /// <summary>
/// Sends a message containing arbitrary data to every player. /// Sends a message containing arbitrary data to every player.
///
/// Keep your messages under around 1100 bytes.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the data being sent. This type must be serializable.</typeparam> /// <typeparam name="T">The type of the data being sent. This type must be serializable.</typeparam>
/// <param name="messageType">The unique key of the message.</param> /// <param name="messageType">The unique key of the message.</param>
@ -70,4 +94,6 @@ public interface IQSBAPI
/// <param name="messageType">The unique key of the message.</param> /// <param name="messageType">The unique key of the message.</param>
/// <param name="handler">The action to be ran when the message is received. The uint is the player ID that sent the messsage.</param> /// <param name="handler">The action to be ran when the message is received. The uint is the player ID that sent the messsage.</param>
void RegisterHandler<T>(string messageType, Action<uint, T> handler); void RegisterHandler<T>(string messageType, Action<uint, T> handler);
#endregion
} }

View File

@ -8,6 +8,8 @@
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_USING/@EntryValue">Required</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_USING/@EntryValue">Required</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_WHILE/@EntryValue">Required</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_WHILE/@EntryValue">Required</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/CASE_BLOCK_BRACES/@EntryValue">NEXT_LINE_SHIFTED_2</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/CASE_BLOCK_BRACES/@EntryValue">NEXT_LINE_SHIFTED_2</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=API/@EntryIndexedValue">API</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ID/@EntryIndexedValue">ID</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=QSB/@EntryIndexedValue">QSB</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=QSB/@EntryIndexedValue">QSB</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UWP/@EntryIndexedValue">UWP</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UWP/@EntryIndexedValue">UWP</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>

View File

@ -1,27 +1,28 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using OWML.Common;
using QSB.Utility; using QSB.Utility;
namespace QSB.API; namespace QSB.API;
public static class AddonDataManager public static class AddonDataManager
{ {
private static readonly Dictionary<string, Action<uint, object>> _handlers = new(); private static readonly Dictionary<int, Action<uint, object>> _handlers = new();
public static void OnReceiveDataMessage(string messageType, object data, uint from) public static void OnReceiveDataMessage(int hash, object data, uint from)
{ {
DebugLog.DebugWrite($"Received data message of message type \"{messageType}\" from {from}!"); DebugLog.DebugWrite($"Received addon message of with hash {hash} from {from}!");
if (!_handlers.TryGetValue(messageType, out var handler)) if (!_handlers.TryGetValue(hash, out var handler))
{ {
DebugLog.DebugWrite($"unknown addon message type with hash {hash}", MessageType.Error);
return; return;
} }
handler(from, data); handler(from, data);
} }
public static void RegisterHandler<T>(string messageType, Action<uint, T> handler) public static void RegisterHandler<T>(int hash, Action<uint, T> handler)
{ {
DebugLog.DebugWrite($"Registering handler for \"{messageType}\" with type of {typeof(T).Name}"); DebugLog.DebugWrite($"Registering addon message handler for hash {hash} with type {typeof(T).Name}");
_handlers.Add(messageType, (from, data) => handler(from, (T)data)); _handlers.Add(hash, (from, data) => handler(from, (T)data));
} }
} }

View File

@ -4,11 +4,27 @@ using UnityEngine.Events;
public interface IQSBAPI public interface IQSBAPI
{ {
#region General
/// <summary> /// <summary>
/// If called, all players connected to YOUR hosted game must have this mod installed. /// If called, all players connected to YOUR hosted game must have this mod installed.
/// </summary> /// </summary>
void RegisterRequiredForAllPlayers(IModBehaviour mod); void RegisterRequiredForAllPlayers(IModBehaviour mod);
/// <summary>
/// Returns if the current player is the host.
/// </summary>
bool GetIsHost();
/// <summary>
/// Returns if the current player is in multiplayer.
/// </summary>
bool GetIsInMultiplayer();
#endregion
#region Player
/// <summary> /// <summary>
/// Returns the player ID of the current player. /// Returns the player ID of the current player.
/// </summary> /// </summary>
@ -22,21 +38,25 @@ public interface IQSBAPI
/// <summary> /// <summary>
/// Returns the list of IDs of all connected players. /// Returns the list of IDs of all connected players.
///
/// The first player in the list is the host.
/// </summary> /// </summary>
uint[] GetPlayerIDs(); uint[] GetPlayerIDs();
/// <summary> /// <summary>
/// Invoked when a player joins the game. /// Invoked when any player (local or remote) joins the game.
/// </summary> /// </summary>
UnityEvent<uint> OnPlayerJoin(); UnityEvent<uint> OnPlayerJoin();
/// <summary> /// <summary>
/// Invoked when a player leaves the game. /// Invoked when any player (local or remote) leaves the game.
/// </summary> /// </summary>
UnityEvent<uint> OnPlayerLeave(); UnityEvent<uint> OnPlayerLeave();
/// <summary> /// <summary>
/// Sets some arbitrary data for a given player. /// Sets some arbitrary data for a given player.
///
/// Not synced.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the data.</typeparam> /// <typeparam name="T">The type of the data.</typeparam>
/// <param name="playerId">The ID of the player.</param> /// <param name="playerId">The ID of the player.</param>
@ -46,15 +66,23 @@ public interface IQSBAPI
/// <summary> /// <summary>
/// Returns some arbitrary data from a given player. /// Returns some arbitrary data from a given player.
///
/// Not synced.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the data.</typeparam> /// <typeparam name="T">The type of the data.</typeparam>
/// <param name="playerId">The ID of the player.</param> /// <param name="playerId">The ID of the player.</param>
/// <param name="key">The unique key of the data you want to access.</param> /// <param name="key">The unique key of the data you want to access.</param>
/// <returns>The data requested. If key is not valid, returns default.</returns> /// <returns>The data requested. If key is not valid, returns default.</returns>
T GetCustomData<T>(uint playerId, string key); T GetCustomData<T>(uint playerId, string key);
#endregion
#region Messaging
/// <summary> /// <summary>
/// Sends a message containing arbitrary data to every player. /// Sends a message containing arbitrary data to every player.
///
/// Keep your messages under around 1100 bytes.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the data being sent. This type must be serializable.</typeparam> /// <typeparam name="T">The type of the data being sent. This type must be serializable.</typeparam>
/// <param name="messageType">The unique key of the message.</param> /// <param name="messageType">The unique key of the message.</param>
@ -70,4 +98,6 @@ public interface IQSBAPI
/// <param name="messageType">The unique key of the message.</param> /// <param name="messageType">The unique key of the message.</param>
/// <param name="handler">The action to be ran when the message is received. The uint is the player ID that sent the messsage.</param> /// <param name="handler">The action to be ran when the message is received. The uint is the player ID that sent the messsage.</param>
void RegisterHandler<T>(string messageType, Action<uint, T> handler); void RegisterHandler<T>(string messageType, Action<uint, T> handler);
#endregion
} }

View File

@ -0,0 +1,11 @@
using QSB.Messaging;
using QSB.Player;
using QSB.Utility;
namespace QSB.API.Messages;
public class AddonCustomDataSyncMessage : QSBMessage<(uint playerId, string key, byte[] data)>
{
public AddonCustomDataSyncMessage(uint playerId, string key, object data) : base((playerId, key, data.ToBytes())) { }
public override void OnReceiveRemote() => QSBPlayerManager.GetPlayer(Data.playerId).SetCustomData(Data.key, Data.data.ToObject());
}

View File

@ -1,29 +1,11 @@
using System.IO; using QSB.Messaging;
using System.Runtime.Serialization.Formatters.Binary; using QSB.Utility;
using QSB.Messaging;
namespace QSB.API.Messages; namespace QSB.API.Messages;
public class AddonDataMessage : QSBMessage<(string messageType, byte[] data, bool receiveLocally)> public class AddonDataMessage : QSBMessage<(int hash, byte[] data, bool receiveLocally)>
{ {
public AddonDataMessage(string messageType, object data, bool receiveLocally) : base((messageType, Obj2Bytes(data), receiveLocally)) { } public AddonDataMessage(int hash, object data, bool receiveLocally) : base((hash, data.ToBytes(), receiveLocally)) { }
private static byte[] Obj2Bytes(object obj)
{
using var ms = new MemoryStream();
var bf = new BinaryFormatter();
bf.Serialize(ms, obj);
var bytes = ms.ToArray();
return bytes;
}
private static object Bytes2Obj(byte[] bytes)
{
using var ms = new MemoryStream(bytes);
var bf = new BinaryFormatter();
var obj = bf.Deserialize(ms);
return obj;
}
public override void OnReceiveLocal() public override void OnReceiveLocal()
{ {
@ -35,7 +17,7 @@ public class AddonDataMessage : QSBMessage<(string messageType, byte[] data, boo
public override void OnReceiveRemote() public override void OnReceiveRemote()
{ {
var obj = Bytes2Obj(Data.data); var obj = Data.data.ToObject();
AddonDataManager.OnReceiveDataMessage(Data.messageType, obj, From); AddonDataManager.OnReceiveDataMessage(Data.hash, obj, From);
} }
} }

View File

@ -1,9 +1,10 @@
using System; using Mirror;
using System.Linq;
using OWML.Common; using OWML.Common;
using QSB.API.Messages; using QSB.API.Messages;
using QSB.Messaging; using QSB.Messaging;
using QSB.Player; using QSB.Player;
using System;
using System.Linq;
using UnityEngine.Events; using UnityEngine.Events;
namespace QSB.API; namespace QSB.API;
@ -16,22 +17,24 @@ public class QSBAPI : IQSBAPI
QSBCore.Addons.Add(uniqueName, mod); QSBCore.Addons.Add(uniqueName, mod);
} }
public bool GetIsHost() => QSBCore.IsHost;
public bool GetIsInMultiplayer() => QSBCore.IsInMultiplayer;
public uint GetLocalPlayerID() => QSBPlayerManager.LocalPlayerId; public uint GetLocalPlayerID() => QSBPlayerManager.LocalPlayerId;
public string GetPlayerName(uint playerId) => QSBPlayerManager.GetPlayer(playerId).Name; public string GetPlayerName(uint playerId) => QSBPlayerManager.GetPlayer(playerId).Name;
public uint[] GetPlayerIDs() => QSBPlayerManager.PlayerList.Select(x => x.PlayerId).ToArray(); public uint[] GetPlayerIDs() => QSBPlayerManager.PlayerList.Select(x => x.PlayerId).ToArray();
public UnityEvent<uint> OnPlayerJoin() => QSBAPIEvents.OnPlayerJoinEvent; public UnityEvent<uint> OnPlayerJoin() => QSBAPIEvents.OnPlayerJoinEvent;
public UnityEvent<uint> OnPlayerLeave() => QSBAPIEvents.OnPlayerLeaveEvent; public UnityEvent<uint> OnPlayerLeave() => QSBAPIEvents.OnPlayerLeaveEvent;
public void SetCustomData<T>(uint playerId, string key, T data) => QSBPlayerManager.GetPlayer(playerId).SetCustomData(key, data); public void SetCustomData<T>(uint playerId, string key, T data) => QSBPlayerManager.GetPlayer(playerId).SetCustomData(key, data);
public T GetCustomData<T>(uint playerId, string key) => QSBPlayerManager.GetPlayer(playerId).GetCustomData<T>(key); public T GetCustomData<T>(uint playerId, string key) => QSBPlayerManager.GetPlayer(playerId).GetCustomData<T>(key);
public void SendMessage<T>(string messageType, T data, uint to = uint.MaxValue, bool receiveLocally = false) public void SendMessage<T>(string messageType, T data, uint to = uint.MaxValue, bool receiveLocally = false)
=> new AddonDataMessage(messageType, data, receiveLocally) {To = to} .Send(); => new AddonDataMessage(messageType.GetStableHashCode(), data, receiveLocally) { To = to }.Send();
public void RegisterHandler<T>(string messageType, Action<uint, T> handler) public void RegisterHandler<T>(string messageType, Action<uint, T> handler)
=> AddonDataManager.RegisterHandler(messageType, handler); => AddonDataManager.RegisterHandler(messageType.GetStableHashCode(), handler);
} }
internal static class QSBAPIEvents internal static class QSBAPIEvents
@ -43,6 +46,7 @@ internal static class QSBAPIEvents
} }
internal class PlayerEvent : UnityEvent<uint> { } internal class PlayerEvent : UnityEvent<uint> { }
internal static PlayerEvent OnPlayerJoinEvent = new PlayerEvent();
internal static PlayerEvent OnPlayerLeaveEvent = new PlayerEvent(); internal static readonly PlayerEvent OnPlayerJoinEvent = new();
internal static readonly PlayerEvent OnPlayerLeaveEvent = new();
} }

View File

@ -257,7 +257,10 @@ public class RespawnOnDeath : MonoBehaviour
var sectorList = PlayerTransformSync.LocalInstance.SectorDetector.SectorList; var sectorList = PlayerTransformSync.LocalInstance.SectorDetector.SectorList;
if (sectorList.All(x => x.Type != Sector.Name.TimberHearth)) if (sectorList.All(x => x.Type != Sector.Name.TimberHearth))
{ {
// stops sectors from breaking when you die on TH?? // Spooky scary legacy code?
// Original comment was "stops sectors from breaking when you die on TH??"
// I think dying on TH used to break all the sectors. Something about you not technically re-entering TH when dying inside it.
// I commented out these lines, and everything seemed fine. But I'm not gonna touch them just in case. :P
Locator.GetPlayerSectorDetector().RemoveFromAllSectors(); Locator.GetPlayerSectorDetector().RemoveFromAllSectors();
Locator.GetPlayerCameraDetector().GetComponent<AudioDetector>().DeactivateAllVolumes(0f); Locator.GetPlayerCameraDetector().GetComponent<AudioDetector>().DeactivateAllVolumes(0f);
} }

View File

@ -645,6 +645,7 @@ public class MenuManager : MonoBehaviour, IAddComponentOnStart
} }
LoadGame(PlayerData.GetWarpedToTheEye()); LoadGame(PlayerData.GetWarpedToTheEye());
// wait until scene load and then wait until Start has ran
Delay.RunWhen(() => TimeLoop._initialized, QSBNetworkManager.singleton.StartHost); Delay.RunWhen(() => TimeLoop._initialized, QSBNetworkManager.singleton.StartHost);
}; };
@ -655,6 +656,7 @@ public class MenuManager : MonoBehaviour, IAddComponentOnStart
else else
{ {
LoadGame(PlayerData.GetWarpedToTheEye()); LoadGame(PlayerData.GetWarpedToTheEye());
// wait until scene load and then wait until Start has ran
Delay.RunWhen(() => TimeLoop._initialized, QSBNetworkManager.singleton.StartHost); Delay.RunWhen(() => TimeLoop._initialized, QSBNetworkManager.singleton.StartHost);
} }
} }

View File

@ -19,7 +19,6 @@ using QSB.WorldSync;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.Serialization; using System.Runtime.Serialization;
@ -29,20 +28,19 @@ public static class QSBMessageManager
{ {
#region inner workings #region inner workings
internal static readonly Type[] _types; internal static readonly Dictionary<int, Type> _types = new();
internal static readonly Dictionary<Type, ushort> _typeToId = new();
private static string _rxPath; private static string _rxPath;
private static string _txPath; private static string _txPath;
static QSBMessageManager() static QSBMessageManager()
{ {
_types = typeof(QSBMessage).GetDerivedTypes().ToArray(); foreach (var type in typeof(QSBMessage).GetDerivedTypes())
for (ushort i = 0; i < _types.Length; i++)
{ {
_typeToId.Add(_types[i], i); var hash = type.FullName.GetStableHashCode();
_types.Add(hash, type);
// call static constructor of message if needed // call static constructor of message if needed
RuntimeHelpers.RunClassConstructor(_types[i].TypeHandle); RuntimeHelpers.RunClassConstructor(type.TypeHandle);
} }
} }
@ -66,6 +64,11 @@ public static class QSBMessageManager
private static void OnServerReceive(QSBMessage msg) private static void OnServerReceive(QSBMessage msg)
{ {
if (msg == null)
{
return;
}
if (msg.To == uint.MaxValue) if (msg.To == uint.MaxValue)
{ {
NetworkServer.SendToAll<Wrapper>(msg); NetworkServer.SendToAll<Wrapper>(msg);
@ -101,9 +104,9 @@ public static class QSBMessageManager
var player = QSBPlayerManager.GetPlayer(msg.From); var player = QSBPlayerManager.GetPlayer(msg.From);
if (!player.IsReady if (!player.IsReady
&& player.PlayerId != QSBPlayerManager.LocalPlayerId && player.PlayerId != QSBPlayerManager.LocalPlayerId
&& player.State is ClientState.AliveInSolarSystem or ClientState.AliveInEye or ClientState.DeadInSolarSystem && player.State is ClientState.AliveInSolarSystem or ClientState.AliveInEye or ClientState.DeadInSolarSystem
&& msg is not (PlayerInformationMessage or PlayerReadyMessage or RequestStateResyncMessage or ServerStateMessage)) && msg is not (PlayerInformationMessage or PlayerReadyMessage or RequestStateResyncMessage or ServerStateMessage))
{ {
//DebugLog.ToConsole($"Warning - Got message {msg} from player {msg.From}, but they were not ready. Asking for state resync, just in case.", MessageType.Warning); //DebugLog.ToConsole($"Warning - Got message {msg} from player {msg.From}, but they were not ready. Asking for state resync, just in case.", MessageType.Warning);
new RequestStateResyncMessage().Send(); new RequestStateResyncMessage().Send();
@ -208,8 +211,12 @@ public static class ReaderWriterExtensions
{ {
private static QSBMessage ReadQSBMessage(this NetworkReader reader) private static QSBMessage ReadQSBMessage(this NetworkReader reader)
{ {
var id = reader.ReadUShort(); var hash = reader.ReadInt();
var type = QSBMessageManager._types[id]; if (!QSBMessageManager._types.TryGetValue(hash, out var type))
{
DebugLog.DebugWrite($"unknown QSBMessage type with hash {hash}", MessageType.Error);
return null;
}
var msg = (QSBMessage)FormatterServices.GetUninitializedObject(type); var msg = (QSBMessage)FormatterServices.GetUninitializedObject(type);
msg.Deserialize(reader); msg.Deserialize(reader);
return msg; return msg;
@ -218,8 +225,8 @@ public static class ReaderWriterExtensions
private static void WriteQSBMessage(this NetworkWriter writer, QSBMessage msg) private static void WriteQSBMessage(this NetworkWriter writer, QSBMessage msg)
{ {
var type = msg.GetType(); var type = msg.GetType();
var id = QSBMessageManager._typeToId[type]; var hash = type.FullName.GetStableHashCode();
writer.Write(id); writer.WriteInt(hash);
msg.Serialize(writer); msg.Serialize(writer);
} }
} }

View File

@ -11,7 +11,7 @@ public abstract class QSBPatch
public void DoPatches(Harmony instance) => instance.PatchAll(GetType()); public void DoPatches(Harmony instance) => instance.PatchAll(GetType());
/// <summary> /// <summary>
/// this is true when a message is received remotely (OnReceiveRemote) or a player leaves (OnRemovePlayer) /// this is true when a message is received remotely (OnReceiveRemote) or a remote player joins/leaves (OnAddPlayer/OnRemovePlayer)
/// </summary> /// </summary>
public static bool Remote; public static bool Remote;
} }

View File

@ -1,4 +1,5 @@
using OWML.Common; using OWML.Common;
using QSB.API.Messages;
using QSB.ClientServerStateSync; using QSB.ClientServerStateSync;
using QSB.ClientServerStateSync.Messages; using QSB.ClientServerStateSync.Messages;
using QSB.Messaging; using QSB.Messaging;
@ -50,5 +51,11 @@ public class RequestStateResyncMessage : QSBMessage
} }
new PlayerInformationMessage { To = From }.Send(); new PlayerInformationMessage { To = From }.Send();
// Initial sync of all custom data from APIs
foreach (var kvp in QSBPlayerManager.LocalPlayer._customData)
{
new AddonCustomDataSyncMessage(QSBPlayerManager.LocalPlayerId, kvp.Key, kvp.Value) { To = From }.Send();
}
} }
} }

View File

@ -1,10 +1,12 @@
using OWML.Common; using OWML.Common;
using QSB.Animation.Player; using QSB.Animation.Player;
using QSB.API.Messages;
using QSB.Audio; using QSB.Audio;
using QSB.ClientServerStateSync; using QSB.ClientServerStateSync;
using QSB.HUD; using QSB.HUD;
using QSB.Messaging; using QSB.Messaging;
using QSB.ModelShip; using QSB.ModelShip;
using QSB.Patches;
using QSB.Player.Messages; using QSB.Player.Messages;
using QSB.Player.TransformSync; using QSB.Player.TransformSync;
using QSB.QuantumSync.WorldObjects; using QSB.QuantumSync.WorldObjects;
@ -179,10 +181,18 @@ public partial class PlayerInfo
HUDBox.OnRespawn(); HUDBox.OnRespawn();
} }
private Dictionary<string, object> _customData = new(); // internal for RequestStateResyncMessage
internal readonly Dictionary<string, object> _customData = new();
public void SetCustomData<T>(string key, T data) public void SetCustomData<T>(string key, T data)
=> _customData[key] = data; {
_customData[key] = data;
if (!QSBPatch.Remote)
{
new AddonCustomDataSyncMessage(PlayerId, key, data).Send();
}
}
public T GetCustomData<T>(string key) public T GetCustomData<T>(string key)
{ {

View File

@ -36,7 +36,9 @@ public class PlayerTransformSync : SectoredTransformSync
var player = new PlayerInfo(this); var player = new PlayerInfo(this);
QSBPlayerManager.PlayerList.SafeAdd(player); QSBPlayerManager.PlayerList.SafeAdd(player);
base.OnStartClient(); base.OnStartClient();
QSBPatch.Remote = !isLocalPlayer;
QSBPlayerManager.OnAddPlayer?.SafeInvoke(Player); QSBPlayerManager.OnAddPlayer?.SafeInvoke(Player);
QSBPatch.Remote = false;
DebugLog.DebugWrite($"Create Player : {Player}", MessageType.Info); DebugLog.DebugWrite($"Create Player : {Player}", MessageType.Info);
JoinLeaveSingularity.Create(Player, true); JoinLeaveSingularity.Create(Player, true);
@ -49,7 +51,7 @@ public class PlayerTransformSync : SectoredTransformSync
JoinLeaveSingularity.Create(Player, false); JoinLeaveSingularity.Create(Player, false);
// TODO : Maybe move this to a leave event...? Would ensure everything could finish up before removing the player // TODO : Maybe move this to a leave event...? Would ensure everything could finish up before removing the player
QSBPatch.Remote = true; QSBPatch.Remote = !isLocalPlayer;
QSBPlayerManager.OnRemovePlayer?.SafeInvoke(Player); QSBPlayerManager.OnRemovePlayer?.SafeInvoke(Player);
QSBPatch.Remote = false; QSBPatch.Remote = false;
base.OnStopClient(); base.OnStopClient();

View File

@ -301,9 +301,9 @@ public class QSBCore : ModBehaviour
foreach (var type in addonAssembly.GetTypes()) foreach (var type in addonAssembly.GetTypes())
{ {
if (typeof(QSBMessage).IsAssignableFrom(type) || typeof(WorldObjectManager).IsAssignableFrom(type) || typeof(IWorldObject).IsAssignableFrom(type)) if (typeof(WorldObjectManager).IsAssignableFrom(type) || typeof(IWorldObject).IsAssignableFrom(type))
{ {
DebugLog.ToConsole($"Addon \"{uniqueName}\" cannot be cosmetic, as it creates networking events or objects.", MessageType.Error); DebugLog.ToConsole($"Addon \"{uniqueName}\" cannot be cosmetic, as it creates networking objects.", MessageType.Error);
return; return;
} }
} }

View File

@ -36,6 +36,7 @@ using QSB.WorldSync;
using System; using System;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using QSB.API;
using UnityEngine; using UnityEngine;
namespace QSB; namespace QSB;

View File

@ -21,26 +21,24 @@ public static class DebugLog
message = $"[{ProcessInstanceId}] " + message; message = $"[{ProcessInstanceId}] " + message;
} }
var @this = QSBCore.Helper != null ? QSBCore.Helper.Console : ModConsole.OwmlConsole;
var Logger = @this.GetValue<IModLogger>("Logger");
var _socket = @this.GetValue<IModSocket>("_socket");
// copied from https://github.com/ow-mods/owml/blob/master/src/OWML.Logging/ModSocketOutput.cs#L33 // copied from https://github.com/ow-mods/owml/blob/master/src/OWML.Logging/ModSocketOutput.cs#L33
Logger?.Log($"{type}: {message}");
_socket.WriteToSocket(new ModSocketMessage
{ {
var Logger = ModConsole.OwmlConsole.GetValue<IModLogger>("Logger"); SenderName = "QSB",
var _socket = ModConsole.OwmlConsole.GetValue<IModSocket>("_socket"); SenderType = GetCallingType(),
Type = type,
Message = message
});
Logger?.Log($"{type}: {message}"); if (type == MessageType.Fatal)
{
_socket.WriteToSocket(new ModSocketMessage _socket.Close();
{ Process.GetCurrentProcess().Kill();
SenderName = "QSB",
SenderType = GetCallingType(),
Type = type,
Message = message
});
if (type == MessageType.Fatal)
{
_socket.Close();
Process.GetCurrentProcess().Kill();
}
} }
} }

View File

@ -4,8 +4,10 @@ using OWML.Common;
using QSB.Player; using QSB.Player;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text; using System.Text;
using UnityEngine; using UnityEngine;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
@ -237,5 +239,28 @@ public static class Extensions
return sb.ToString(); return sb.ToString();
} }
/// <summary>
/// only works for c# serializable objects
/// </summary>
public static byte[] ToBytes(this object obj)
{
using var ms = new MemoryStream();
var bf = new BinaryFormatter();
bf.Serialize(ms, obj);
var bytes = ms.ToArray();
return bytes;
}
/// <summary>
/// only works for c# serializable objects
/// </summary>
public static object ToObject(this byte[] bytes)
{
using var ms = new MemoryStream(bytes);
var bf = new BinaryFormatter();
var obj = bf.Deserialize(ms);
return obj;
}
#endregion #endregion
} }

View File

@ -7,7 +7,7 @@
"body": "- Disable *all* other mods. (Can heavily affect performance)\n- Make sure you are not running any other network-intensive applications." "body": "- Disable *all* other mods. (Can heavily affect performance)\n- Make sure you are not running any other network-intensive applications."
}, },
"uniqueName": "Raicuparta.QuantumSpaceBuddies", "uniqueName": "Raicuparta.QuantumSpaceBuddies",
"version": "0.30.1", "version": "0.30.2",
"owmlVersion": "2.9.5", "owmlVersion": "2.9.5",
"dependencies": [ "_nebula.MenuFramework", "JohnCorby.VanillaFix" ], "dependencies": [ "_nebula.MenuFramework", "JohnCorby.VanillaFix" ],
"pathsToPreserve": [ "debugsettings.json" ], "pathsToPreserve": [ "debugsettings.json" ],