This commit is contained in:
Evil Factory
2022-02-24 14:30:39 -03:00
364 changed files with 10838 additions and 3966 deletions
@@ -112,7 +112,7 @@ namespace Barotrauma.Networking
set;
}
protected ChatMessage(string senderName, string text, ChatMessageType type, Character sender, Client client, PlayerConnectionChangeType changeType = PlayerConnectionChangeType.None)
protected ChatMessage(string senderName, string text, ChatMessageType type, Character sender, Client client, PlayerConnectionChangeType changeType = PlayerConnectionChangeType.None, Color? textColor = null)
{
Text = text;
Type = type;
@@ -122,11 +122,13 @@ namespace Barotrauma.Networking
SenderName = senderName;
ChangeType = changeType;
}
public static ChatMessage Create(string senderName, string text, ChatMessageType type, Character sender, Client client = null, PlayerConnectionChangeType changeType = PlayerConnectionChangeType.None)
customTextColor = textColor;
}
public static ChatMessage Create(string senderName, string text, ChatMessageType type, Character sender, Client client = null, PlayerConnectionChangeType changeType = PlayerConnectionChangeType.None, Color? textColor = null)
{
return new ChatMessage(senderName, text, type, sender, client ?? GameMain.NetworkMember?.ConnectedClients?.Find(c => c.Character != null && c.Character == sender), changeType);
return new ChatMessage(senderName, text, type, sender, client ?? GameMain.NetworkMember?.ConnectedClients?.Find(c => c.Character != null && c.Character == sender), changeType, textColor);
}
public static string GetChatMessageCommand(string message, out string messageWithoutCommand)
@@ -225,27 +227,30 @@ namespace Barotrauma.Networking
break;
case ChatMessageType.Radio:
case ChatMessageType.Order:
if (receiver != null && !receiver.IsDead)
if (receiver?.Inventory != null && !receiver.IsDead)
{
var receiverItem = receiver.Inventory?.AllItems.FirstOrDefault(i => i.GetComponent<WifiComponent>() != null);
//character doesn't have a radio -> don't send
if (receiverItem == null || !receiver.HasEquippedItem(receiverItem)) { return spokenMsg; }
var senderItem = sender.Inventory?.AllItems.FirstOrDefault(i => i.GetComponent<WifiComponent>() != null);
if (senderItem == null || !sender.HasEquippedItem(senderItem)) { return spokenMsg; }
var receiverRadio = receiverItem.GetComponent<WifiComponent>();
var senderRadio = senderItem.GetComponent<WifiComponent>();
if (!receiverRadio.CanReceive(senderRadio)) { return spokenMsg; }
string msg = ApplyDistanceEffect(receiverItem, senderItem, message, senderRadio.Range);
if (sender.SpeechImpediment > 0.0f)
foreach (Item receiverItem in receiver.Inventory.AllItems.Where(i => i.GetComponent<WifiComponent>()?.LinkToChat ?? false))
{
//speech impediment doesn't reduce the range when using a radio, but adds extra garbling
msg = ApplyDistanceEffect(msg, sender.SpeechImpediment / 100.0f);
if (sender.Inventory == null || !receiver.HasEquippedItem(receiverItem)) { continue; }
foreach (Item senderItem in sender.Inventory.AllItems.Where(i => i.GetComponent<WifiComponent>()?.LinkToChat ?? false))
{
if (!sender.HasEquippedItem(senderItem)) { continue; }
var receiverRadio = receiverItem.GetComponent<WifiComponent>();
var senderRadio = senderItem.GetComponent<WifiComponent>();
if (!receiverRadio.CanReceive(senderRadio)) { continue; }
string msg = ApplyDistanceEffect(receiverItem, senderItem, message, senderRadio.Range);
if (sender.SpeechImpediment > 0.0f)
{
//speech impediment doesn't reduce the range when using a radio, but adds extra garbling
msg = ApplyDistanceEffect(msg, sender.SpeechImpediment / 100.0f);
}
return msg;
}
}
return msg;
return spokenMsg;
}
break;
}
@@ -275,7 +280,7 @@ namespace Barotrauma.Networking
foreach (Item item in sender.Inventory.AllItems)
{
var wifiComponent = item.GetComponent<WifiComponent>();
if (wifiComponent == null || !wifiComponent.CanTransmit() || !sender.HasEquippedItem(item)) { continue; }
if (wifiComponent == null || !wifiComponent.LinkToChat || !wifiComponent.CanTransmit() || !sender.HasEquippedItem(item)) { continue; }
if (radio == null || wifiComponent.Range > radio.Range)
{
radio = wifiComponent;
@@ -22,7 +22,11 @@ namespace Barotrauma.Networking
ManageSettings = 0x200,
ManagePermissions = 0x400,
KarmaImmunity = 0x800,
All = 0xFFF
BuyItems = 0x1000,
SellInventoryItems = 0x2000,
SellSubItems = 0x4000,
CampaignStore = 0x8000,
All = 0xFFFF
}
class PermissionPreset
@@ -0,0 +1,547 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
/// <summary>
/// Marks fields and properties as to be serialized and deserialized by <see cref="INetSerializableStruct"/>.
/// Also contains settings for some types like maximum and minimum values for numbers to reduce bits used.
/// </summary>
/// <example>
/// <code>
/// struct NetPurchasedItem : INetSerializableStruct
/// {
/// [NetworkSerialize]
/// public string Identifier;
///
/// [NetworkSerialize(ArrayMaxSize = 16)]
/// public string[] Tags;
///
/// [NetworkSerialize(MinValueInt = 0, MaxValueInt = 8)]
/// public int Amount;
/// }
/// </code>
/// </example>
/// <remarks>
/// Using the attribute on the struct will make all fields and properties serialized
/// </remarks>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Struct | AttributeTargets.Property)]
public class NetworkSerialize : Attribute
{
public int MaxValueInt = int.MaxValue;
public int MinValueInt = int.MinValue;
public float MaxValueFloat = float.MaxValue;
public float MinValueFloat = float.MinValue;
public int NumberOfBits = 8;
public bool IncludeColorAlpha = false;
public int ArrayMaxSize = ushort.MaxValue;
}
/// <summary>
/// Static class that contains serialize and deserialize functions for different types used in <see cref="INetSerializableStruct"/>
/// </summary>
public static class NetSerializableProperties
{
public readonly struct ReadWriteBehavior
{
public delegate dynamic? ReadDelegate(IReadMessage inc, Type type, NetworkSerialize attribute);
public delegate void WriteDelegate(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg);
public readonly ReadDelegate ReadAction;
public readonly WriteDelegate WriteAction;
public ReadWriteBehavior(ReadDelegate readAction, WriteDelegate writeAction)
{
ReadAction = readAction;
WriteAction = writeAction;
}
}
private static readonly ImmutableDictionary<Type, ReadWriteBehavior> TypeBehaviors = new Dictionary<Type, ReadWriteBehavior>
{
{ typeof(Boolean), new ReadWriteBehavior(ReadBoolean, WriteDynamic) },
{ typeof(Byte), new ReadWriteBehavior(ReadByte, WriteDynamic) },
{ typeof(UInt16), new ReadWriteBehavior(ReadUInt16, WriteDynamic) },
{ typeof(Int16), new ReadWriteBehavior(ReadInt16, WriteDynamic) },
{ typeof(UInt32), new ReadWriteBehavior(ReadUInt32, WriteDynamic) },
{ typeof(Int32), new ReadWriteBehavior(ReadInt32, WriteInt32) },
{ typeof(UInt64), new ReadWriteBehavior(ReadUInt64, WriteDynamic) },
{ typeof(Int64), new ReadWriteBehavior(ReadInt64, WriteDynamic) },
{ typeof(Single), new ReadWriteBehavior(ReadSingle, WriteSingle) },
{ typeof(Double), new ReadWriteBehavior(ReadDouble, WriteDynamic) },
{ typeof(String), new ReadWriteBehavior(ReadString, WriteDynamic) },
{ typeof(Color), new ReadWriteBehavior(ReadColor, WriteColor) },
{ typeof(Vector2), new ReadWriteBehavior(ReadVector2, WriteVector2) }
}.ToImmutableDictionary();
private static readonly ReadWriteBehavior InvalidReadWriteBehavior = new ReadWriteBehavior(ReadInvalid, WriteInvalid);
private static readonly ImmutableDictionary<Predicate<Type>, ReadWriteBehavior> TypePredicates = new Dictionary<Predicate<Type>, ReadWriteBehavior>
{
// Arrays
{ type => type.BaseType?.IsAssignableFrom(typeof(Array)) ?? false, new ReadWriteBehavior(ReadArray, WriteArray) },
// Nested INetSerializableStructs
{ type => typeof(INetSerializableStruct).IsAssignableFrom(type), new ReadWriteBehavior(ReadINetSerializableStruct, WriteINetSerializableStruct) },
// Enums
{ type => type.IsEnum, new ReadWriteBehavior(ReadEnum, WriteEnum) },
// Nullable / Optional types
{ type => Nullable.GetUnderlyingType(type) != null, new ReadWriteBehavior(ReadNullable, WriteNullable) }
}.ToImmutableDictionary();
private static void WriteInvalid(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg) => throw new InvalidOperationException($"Type {obj?.GetType()} cannot be serialized. Did you forget to implement INetSerializableStruct?");
private static dynamic ReadInvalid(IReadMessage inc, Type type, NetworkSerialize attribute) => throw new InvalidOperationException($"Type {type} cannot be deserialized. Did you forget to implement INetSerializableStruct?");
private static void WriteNullable(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
{
if (obj is { } notNull)
{
msg.Write(true);
if (TryFindBehavior(notNull.GetType(), out ReadWriteBehavior behavior))
{
// uh oh, something terrible has happened!
if (behavior.WriteAction == WriteNullable) { behavior = InvalidReadWriteBehavior; }
behavior.WriteAction(notNull, attribute, msg);
return;
}
}
msg.Write(false);
}
private static dynamic? ReadNullable(IReadMessage inc, Type type, NetworkSerialize attribute)
{
if (!inc.ReadBoolean()) { return null; }
Type? underlyingType = Nullable.GetUnderlyingType(type);
if (underlyingType is null) { throw new InvalidOperationException($"Could not get the underlying type of {type} in {nameof(ReadNullable)}"); }
if (TryFindBehavior(underlyingType, out ReadWriteBehavior behavior))
{
// uh oh, something terrible has happened!
if (behavior.ReadAction == ReadNullable) { behavior = InvalidReadWriteBehavior; }
return behavior.ReadAction(inc, underlyingType, attribute);
}
throw new InvalidOperationException($"Could not find suitable behavior for type {underlyingType} in {nameof(ReadNullable)}");
}
private static void WriteEnum(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
{
if (obj is null) { throw new ArgumentNullException(nameof(obj), "Tried to write 'null' into a non-nullable type"); }
Range<int> range = GetEnumRange(obj.GetType());
msg.WriteRangedInteger(Convert.ChangeType(obj, obj.GetTypeCode()), range.Start, range.End);
}
private static dynamic ReadEnum(IReadMessage inc, Type type, NetworkSerialize attribute)
{
Range<int> range = GetEnumRange(type);
int enumIndex = inc.ReadRangedInteger(range.Start, range.End);
foreach (dynamic e in Enum.GetValues(type))
{
if (Convert.ChangeType(e, e.GetTypeCode()) == enumIndex) { return e; }
}
throw new InvalidOperationException($"An enum {type} with value {enumIndex} could not be found in {nameof(ReadEnum)}");
}
private static void WriteINetSerializableStruct(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
{
if (obj is null) { throw new ArgumentNullException(nameof(obj), "Tried to write 'null' into a non-nullable type"); }
if (!(obj is INetSerializableStruct serializableStruct)) { throw new InvalidOperationException($"Object in {nameof(WriteINetSerializableStruct)} was {obj.GetType()} but expected {nameof(INetSerializableStruct)}"); }
serializableStruct.Write(msg);
}
private static dynamic ReadINetSerializableStruct(IReadMessage inc, Type type, NetworkSerialize attribute)
{
return INetSerializableStruct.ReadDynamic(type, inc);
}
private static void WriteDynamic(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
{
if (obj is null) { throw new ArgumentNullException(nameof(obj), "Tried to write 'null' into a non-nullable type"); }
msg.Write(obj);
}
private static dynamic ReadArray(IReadMessage inc, Type type, NetworkSerialize attribute)
{
Type? elementType = type.GetElementType();
if (elementType is null) { throw new InvalidOperationException($"Could not get the element type of {type} in {nameof(ReadArray)}"); }
int length = inc.ReadRangedInteger(0, attribute.ArrayMaxSize);
Array list = Array.CreateInstance(elementType, length);
for (int i = 0; i < length; i++)
{
if (TryFindBehavior(elementType, out ReadWriteBehavior behavior))
{
list.SetValue(behavior.ReadAction(inc, elementType, attribute), i);
}
else
{
throw new InvalidOperationException($"Could not find suitable behavior for type {elementType} in {nameof(ReadArray)}");
}
}
return list;
}
private static void WriteArray(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
{
if (obj is null) { throw new ArgumentNullException(nameof(obj), "Tried to write 'null' into a non-nullable type"); }
if (!(obj is Array array)) { throw new InvalidOperationException($"Object in {nameof(WriteArray)} was {obj.GetType()} but expected {nameof(Array)}"); }
msg.WriteRangedInteger(array.Length, 0, attribute.ArrayMaxSize);
foreach (dynamic o in array)
{
if (TryFindBehavior(o.GetType(), out ReadWriteBehavior behavior))
{
behavior.WriteAction(o, attribute, msg);
}
}
}
private static dynamic ReadBoolean(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadBoolean();
private static dynamic ReadByte(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadByte();
private static dynamic ReadUInt16(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadUInt16();
private static dynamic ReadInt16(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadInt16();
private static dynamic ReadUInt32(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadUInt32();
private static dynamic ReadInt32(IReadMessage inc, Type type, NetworkSerialize attribute)
{
if (IsRanged(attribute.MinValueInt, attribute.MaxValueInt))
{
return inc.ReadRangedInteger(attribute.MinValueInt, attribute.MaxValueInt);
}
return inc.ReadInt32();
}
private static void WriteInt32(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
{
if (obj is null) { throw new ArgumentNullException(nameof(obj), "Tried to write 'null' into a non-nullable type"); }
if (IsRanged(attribute.MinValueInt, attribute.MaxValueInt))
{
msg.WriteRangedInteger(obj, attribute.MinValueInt, attribute.MaxValueInt);
return;
}
msg.Write(obj);
}
private static dynamic ReadUInt64(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadUInt64();
private static dynamic ReadInt64(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadInt64();
private static dynamic ReadSingle(IReadMessage inc, Type type, NetworkSerialize attribute)
{
if (IsRanged(attribute.MinValueFloat, attribute.MaxValueFloat))
{
return inc.ReadRangedSingle(attribute.MinValueFloat, attribute.MaxValueFloat, attribute.NumberOfBits);
}
return inc.ReadSingle();
}
private static void WriteSingle(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
{
if (obj is null) { throw new ArgumentNullException(nameof(obj), "Tried to write 'null' into a non-nullable type"); }
if (IsRanged(attribute.MinValueFloat, attribute.MaxValueFloat))
{
msg.WriteRangedSingle(obj, attribute.MinValueFloat, attribute.MaxValueFloat, attribute.NumberOfBits);
return;
}
msg.Write(obj);
}
private static dynamic ReadDouble(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadDouble();
private static dynamic ReadString(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadString();
private static dynamic ReadColor(IReadMessage inc, Type type, NetworkSerialize attribute) => attribute.IncludeColorAlpha ? inc.ReadColorR8G8B8A8() : inc.ReadColorR8G8B8();
private static void WriteColor(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
{
if (obj is null) { throw new ArgumentNullException(nameof(obj), "Tried to write 'null' into a non-nullable type"); }
if (attribute.IncludeColorAlpha)
{
msg.WriteColorR8G8B8A8(obj);
return;
}
msg.WriteColorR8G8B8(obj);
}
private static dynamic ReadVector2(IReadMessage inc, Type type, NetworkSerialize attribute)
{
float x;
float y;
if (IsRanged(attribute.MinValueFloat, attribute.MaxValueFloat))
{
x = inc.ReadRangedSingle(attribute.MinValueFloat, attribute.MaxValueFloat, attribute.NumberOfBits);
y = inc.ReadRangedSingle(attribute.MinValueFloat, attribute.MaxValueFloat, attribute.NumberOfBits);
}
else
{
x = inc.ReadSingle();
y = inc.ReadSingle();
}
return new Vector2(x, y);
}
private static void WriteVector2(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
{
if (obj is null) { throw new ArgumentNullException(nameof(obj), "Tried to write 'null' into a non-nullable type"); }
var (x, y) = (Vector2)obj;
if (IsRanged(attribute.MinValueFloat, attribute.MaxValueFloat))
{
msg.WriteRangedSingle(x, attribute.MinValueFloat, attribute.MaxValueFloat, attribute.NumberOfBits);
msg.WriteRangedSingle(y, attribute.MinValueFloat, attribute.MaxValueFloat, attribute.NumberOfBits);
return;
}
msg.Write(x);
msg.Write(y);
}
private static bool IsRanged(float minValue, float maxValue) => minValue > float.MinValue || maxValue < float.MaxValue;
private static bool IsRanged(int minValue, int maxValue) => minValue > int.MinValue || maxValue < int.MaxValue;
private static Range<int> GetEnumRange(Type type)
{
ImmutableArray<int> values = Enum.GetValues(type).Cast<int>().ToImmutableArray();
return new Range<int>(values.Min(), values.Max());
}
public static bool TryFindBehavior(Type type, out ReadWriteBehavior behavior)
{
if (TypeBehaviors.TryGetValue(type, out behavior)) { return true; }
foreach (var (predicate, behavior2) in TypePredicates)
{
if (predicate(type))
{
behavior = behavior2;
return true;
}
}
behavior = InvalidReadWriteBehavior;
return false;
}
}
/// <summary>
/// Interface that allows the creation of automatically serializable and deserializable structs.
/// <br/><br/>
/// </summary>
/// <example>
/// <code>
/// public enum PurchaseResult
/// {
/// Unknown,
/// Completed,
/// Declined
/// }
///
/// [NetworkSerialize]
/// struct NetStoreTransaction : INetSerializableStruct
/// {
/// public long Timestamp { get; set; }
/// public PurchaseResult Result { get; set; }
/// public NetPurchasedItem? PurchasedItem { get; set; }
/// }
///
/// [NetworkSerialize]
/// struct NetPurchasedItem : INetSerializableStruct
/// {
/// public string Identifier;
/// public string[] Tags;
/// public int Amount;
/// }
/// </code>
/// </example>
/// <remarks>
/// Supported types are:<br/>
/// <see cref="Boolean">bool</see><br/>
/// <see cref="Byte">byte</see><br/>
/// <see cref="UInt16">ushort</see><br/>
/// <see cref="Int16">short</see><br/>
/// <see cref="UInt32">uint</see><br/>
/// <see cref="Int32">int</see><br/>
/// <see cref="UInt64">ulong</see><br/>
/// <see cref="Int64">long</see><br/>
/// <see cref="Single">float</see><br/>
/// <see cref="Double">double</see><br/>
/// <see cref="String">string</see><br/>
/// <see cref="Microsoft.Xna.Framework.Color"/><br/>
/// <see cref="Microsoft.Xna.Framework.Vector2"/><br/>
/// In addition arrays, enums and <see cref="Nullable{T}"/> are supported.<br/>
/// Using <see cref="Nullable{T}"/> will make the field or property optional
/// </remarks>
/// <seealso cref="NetworkSerialize"/>
public interface INetSerializableStruct
{
/// <summary>
/// Deserializes a network message into a struct.
/// </summary>
/// <example>
/// <code>
/// public void ClientRead(IReadMessage inc)
/// {
/// NetStoreTransaction transaction = INetSerializableStruct.Read&lt;NetStoreTransaction&gt;(inc);
/// if (transaction.Result == PurchaseResult.Declined)
/// {
/// Console.WriteLine("Purchase declined!");
/// return;
/// }
///
/// if (transaction.PurchasedItem is { } item)
/// {
/// // Purchased 3x Wrench with tags: smallitem, mechanical, tool
/// Console.WriteLine($"Purchased {item.Amount}x {item.Identifier} with tags: {string.Join(", ", item.Tags)}");
/// }
/// }
/// </code>
/// </example>
/// <param name="inc">Incoming network message</param>
/// <typeparam name="T">Type of the struct that implements <see cref="INetSerializableStruct"/></typeparam>
/// <returns>A new struct of type T with fields and properties deserialized</returns>
public static T Read<T>(IReadMessage inc) where T : INetSerializableStruct => (T)ReadDynamic(typeof(T), inc);
public static dynamic ReadDynamic(Type type, IReadMessage inc)
{
object? newObject = Activator.CreateInstance(type);
if (newObject is null) { return default!; }
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo info in properties)
{
NetworkSerialize? attribute = GetAttribute(info, newObject);
if (attribute is null) { continue; }
if (NetSerializableProperties.TryFindBehavior(info.PropertyType, out var behavior))
{
object? value = behavior.ReadAction(inc, info.PropertyType, attribute);
info.SetValue(newObject, value);
}
else
{
DebugConsole.ThrowError($"Unsupported property type \"{info.PropertyType}\" in {newObject}!");
}
}
FieldInfo[] fields = type.GetFields();
foreach (FieldInfo info in fields)
{
NetworkSerialize? attribute = GetAttribute(info, newObject);
if (attribute is null) { continue; }
if (NetSerializableProperties.TryFindBehavior(info.FieldType, out var behavior))
{
object? value = behavior.ReadAction(inc, info.FieldType, attribute);
info.SetValue(newObject, value);
}
else
{
DebugConsole.ThrowError($"Unsupported field type \"{info.FieldType}\" in {newObject}!");
}
}
return newObject;
}
/// <summary>
/// Serializes the struct into a network message
/// <example>
/// <code>
/// public void ServerWrite(IWriteMessage msg)
/// {
/// INetSerializableStruct transaction = new NetStoreTransaction
/// {
/// Result = PurchaseResult.Completed,
/// Timestamp = DateTimeOffset.Now.ToUnixTimeSeconds(),
/// PurchasedItem = new NetPurchasedItem
/// {
/// Identifier = "Wrench",
/// Amount = 3,
/// Tags = new []{ "smallitem", "mechanical", "tool" }
/// }
/// };
///
/// transaction.Write(msg);
/// }
/// </code>
/// </example>
/// </summary>
/// <param name="msg">Outgoing network message</param>
public void Write(IWriteMessage msg)
{
PropertyInfo[] properties = GetType().GetProperties();
foreach (PropertyInfo info in properties)
{
NetworkSerialize? attribute = GetAttribute(info, this);
if (attribute is null) { continue; }
if (NetSerializableProperties.TryFindBehavior(info.PropertyType, out var behavior))
{
behavior.WriteAction(info.GetValue(this), attribute, msg);
}
else
{
throw new InvalidOperationException($"Unsupported property type \"{info.PropertyType}\" in {this}");
}
}
FieldInfo[] fields = GetType().GetFields();
foreach (FieldInfo info in fields)
{
NetworkSerialize? attribute = GetAttribute(info, this);
if (attribute is null) { continue; }
if (NetSerializableProperties.TryFindBehavior(info.FieldType, out var behavior))
{
behavior.WriteAction(info.GetValue(this), attribute, msg);
}
else
{
throw new InvalidOperationException($"Unsupported field type \"{info.FieldType}\" in {this}");
}
}
}
private static NetworkSerialize? GetAttribute(MemberInfo info, object baseClass) => info.GetCustomAttribute<NetworkSerialize>() ?? baseClass.GetType().GetCustomAttribute<NetworkSerialize>();
}
}
@@ -29,7 +29,8 @@ namespace Barotrauma.Networking
REQUEST_STARTGAMEFINALIZE, //tell the server you're ready to finalize round initialization
ERROR, //tell the server that an error occurred
CREW,
CREW, //hiring UI
MEDICAL, //medical clinic
READY_CHECK,
READY_TO_SPAWN,
LUA_NET_MESSAGE
@@ -81,6 +82,8 @@ namespace Barotrauma.Networking
EVENTACTION,
CREW, //anything related to managing bots in multiplayer
READY_CHECK, //start, end and update a ready check
MEDICAL, //medical clinic
LUA_NET_MESSAGE
}
enum ServerNetObject
@@ -145,7 +148,9 @@ namespace Barotrauma.Networking
NotOnWhitelist,
ExcessiveDesyncOldEvent,
ExcessiveDesyncRemovedEvent,
SyncTimeout
SyncTimeout,
SteamP2PError,
SteamP2PTimeOut,
}
abstract partial class NetworkMember
@@ -231,24 +236,24 @@ namespace Barotrauma.Networking
var radio = sender.Inventory.AllItems.FirstOrDefault(i => i.GetComponent<WifiComponent>() != null);
if (radio == null || !sender.HasEquippedItem(radio)) { return false; }
var radioComponent = radio.GetComponent<WifiComponent>();
if (radioComponent == null) { return false; }
return radioComponent.HasRequiredContainedItems(sender, addMessage: false);
}
public void AddChatMessage(string message, ChatMessageType type, string senderName = "", Client senderClient = null, Character senderCharacter = null, PlayerConnectionChangeType changeType = PlayerConnectionChangeType.None)
public void AddChatMessage(string message, ChatMessageType type, string senderName = "", Client senderClient = null, Character senderCharacter = null, PlayerConnectionChangeType changeType = PlayerConnectionChangeType.None, Color? textColor = null)
{
AddChatMessage(ChatMessage.Create(senderName, message, type, senderCharacter, senderClient, changeType: changeType));
AddChatMessage(ChatMessage.Create(senderName, message, type, senderCharacter, senderClient, changeType: changeType, textColor: textColor));
}
public virtual void AddChatMessage(ChatMessage message)
{
if (string.IsNullOrEmpty(message.Text)) { return; }
if (message.Sender != null && !message.Sender.IsDead)
{
message.Sender.ShowSpeechBubble(2.0f, ChatMessage.MessageColor[(int)message.Type]);
message.Sender.ShowSpeechBubble(2.0f, message.Color);
}
}
@@ -23,18 +23,22 @@ namespace Barotrauma.Networking
/// </summary>
public int? WallSectionIndex { get; set; }
public bool IsNewOrder { get; }
/// <summary>
/// Same as calling <see cref="OrderChatMessage.OrderChatMessage(Order, string, int, string, ISpatialEntity, Character, Character)"/>, but the text parameter is set using <see cref="Order.GetChatMessage(string, string, bool, string)"/>
/// Same as calling <see cref="OrderChatMessage.OrderChatMessage(Order, string, int, string, ISpatialEntity, Character, Character)"/>,
/// but the text parameter is set using <see cref="Order.GetChatMessage(string, string, bool, string)"/>
/// </summary>
public OrderChatMessage(Order order, string orderOption, int priority, ISpatialEntity targetEntity, Character targetCharacter, Character sender)
public OrderChatMessage(Order order, string orderOption, int priority, ISpatialEntity targetEntity, Character targetCharacter, Character sender, bool isNewOrder = true)
: this(order, orderOption, priority,
order?.GetChatMessage(targetCharacter?.Name, sender?.CurrentHull?.DisplayName, givingOrderToSelf: targetCharacter == sender, orderOption: orderOption, priority: priority),
targetEntity, targetCharacter, sender)
order?.GetChatMessage(targetCharacter?.Name, sender?.CurrentHull?.DisplayName, targetCharacter == sender, orderOption, isNewOrder),
targetEntity, targetCharacter, sender, isNewOrder)
{
}
public OrderChatMessage(Order order, string orderOption, int priority, string text, ISpatialEntity targetEntity, Character targetCharacter, Character sender)
public OrderChatMessage(Order order, string orderOption, int priority, string text, ISpatialEntity targetEntity,
Character targetCharacter, Character sender, bool isNewOrder = true)
: base(sender?.Name, text, ChatMessageType.Order, sender, GameMain.NetworkMember.ConnectedClients.Find(c => c.Character == sender))
{
Order = order;
@@ -42,9 +46,11 @@ namespace Barotrauma.Networking
OrderPriority = priority;
TargetCharacter = targetCharacter;
TargetEntity = targetEntity;
IsNewOrder = isNewOrder;
}
public static void WriteOrder(IWriteMessage msg, Order order, Character targetCharacter, ISpatialEntity targetEntity, string orderOption, int orderPriority, int? wallSectionIndex)
public static void WriteOrder(IWriteMessage msg, Order order, Character targetCharacter, ISpatialEntity targetEntity,
string orderOption, int orderPriority, int? wallSectionIndex, bool isNewOrder)
{
msg.Write((byte)Order.PrefabList.IndexOf(order.Prefab));
msg.Write(targetCharacter == null ? (UInt16)0 : targetCharacter.ID);
@@ -100,11 +106,13 @@ namespace Barotrauma.Networking
msg.Write((byte)(wallSectionIndex ?? order.WallSectionIndex ?? 0));
}
}
msg.Write(isNewOrder);
}
private void WriteOrder(IWriteMessage msg)
{
WriteOrder(msg, Order, TargetCharacter, TargetEntity, OrderOption, OrderPriority, WallSectionIndex);
WriteOrder(msg, Order, TargetCharacter, TargetEntity, OrderOption, OrderPriority, WallSectionIndex, IsNewOrder);
}
public struct OrderMessageInfo
@@ -119,8 +127,10 @@ namespace Barotrauma.Networking
public OrderTarget TargetPosition { get; }
public int? WallSectionIndex { get; }
public int Priority { get; }
public bool IsNewOrder { get; }
public OrderMessageInfo(int orderIndex, Order orderPrefab, string orderOption, int? orderOptionIndex, Character targetCharacter, Order.OrderTargetType targetType, Entity targetEntity, OrderTarget targetPosition, int? wallSectionIndex, int orderPriority)
public OrderMessageInfo(int orderIndex, Order orderPrefab, string orderOption, int? orderOptionIndex, Character targetCharacter,
Order.OrderTargetType targetType, Entity targetEntity, OrderTarget targetPosition, int? wallSectionIndex, int orderPriority, bool isNewOrder)
{
OrderIndex = orderIndex;
OrderPrefab = orderPrefab;
@@ -132,6 +142,7 @@ namespace Barotrauma.Networking
TargetPosition = targetPosition;
WallSectionIndex = wallSectionIndex;
Priority = orderPriority;
IsNewOrder = isNewOrder;
}
}
@@ -205,7 +216,10 @@ namespace Barotrauma.Networking
wallSectionIndex = msg.ReadByte();
}
return new OrderMessageInfo(orderIndex, orderPrefab, orderOption, optionIndex, targetCharacter, orderTargetType, targetEntity, orderTargetPosition, wallSectionIndex, orderPriority);
bool isNewOrder = msg.ReadBoolean();
return new OrderMessageInfo(orderIndex, orderPrefab, orderOption, optionIndex, targetCharacter,
orderTargetType, targetEntity, orderTargetPosition, wallSectionIndex, orderPriority, isNewOrder);
}
}
}
@@ -38,19 +38,19 @@ namespace Barotrauma.Networking
public static class NetworkEnumExtensions
{
public static bool IsCompressed(this PacketHeader h)
=> h.IsBitSet(PacketHeader.IsCompressed);
=> h.HasFlag(PacketHeader.IsCompressed);
public static bool IsConnectionInitializationStep(this PacketHeader h)
=> h.IsBitSet(PacketHeader.IsConnectionInitializationStep);
=> h.HasFlag(PacketHeader.IsConnectionInitializationStep);
public static bool IsDisconnectMessage(this PacketHeader h)
=> h.IsBitSet(PacketHeader.IsDisconnectMessage);
=> h.HasFlag(PacketHeader.IsDisconnectMessage);
public static bool IsServerMessage(this PacketHeader h)
=> h.IsBitSet(PacketHeader.IsServerMessage);
=> h.HasFlag(PacketHeader.IsServerMessage);
public static bool IsHeartbeatMessage(this PacketHeader h)
=> h.IsBitSet(PacketHeader.IsHeartbeatMessage);
=> h.HasFlag(PacketHeader.IsHeartbeatMessage);
}
}
@@ -81,8 +81,7 @@ namespace Barotrauma.Networking
public const string SavePath = "ServerLogs";
private readonly Queue<LogMessage> lines;
private int unsavedLineCount;
private readonly Queue<LogMessage> unsavedLines;
private readonly bool[] msgTypeHidden = new bool[Enum.GetValues(typeof(MessageType)).Length];
@@ -98,6 +97,7 @@ namespace Barotrauma.Networking
{
ServerName = serverName;
lines = new Queue<LogMessage>();
unsavedLines = new Queue<LogMessage>();
foreach (MessageType messageType in Enum.GetValues(typeof(MessageType)))
{
@@ -117,22 +117,19 @@ namespace Barotrauma.Networking
#endif
lines.Enqueue(newText);
unsavedLines.Enqueue(newText);
#if CLIENT
if (listBox != null)
{
AddLine(newText);
listBox.UpdateScrollBarSize();
}
#endif
unsavedLineCount++;
if (unsavedLineCount >= LinesPerFile)
if (unsavedLines.Count() >= LinesPerFile)
{
Save();
unsavedLineCount = 0;
unsavedLines.Clear();
}
while (lines.Count > LinesPerFile)
@@ -176,7 +173,7 @@ namespace Barotrauma.Networking
try
{
File.WriteAllLines(filePath, lines.Select(l => l.SanitizedText));
File.WriteAllLines(filePath, unsavedLines.Select(l => l.SanitizedText));
}
catch (Exception e)
{
@@ -97,7 +97,7 @@ namespace Barotrauma.Networking
private readonly SerializableProperty property;
private readonly string typeString;
private readonly object parentObject;
public string Name
{
get { return property.Name; }
@@ -214,7 +214,7 @@ namespace Barotrauma.Networking
public void Write(IWriteMessage msg, object overrideValue = null)
{
if (overrideValue == null) overrideValue = property.GetValue(parentObject);
if (overrideValue == null) { overrideValue = Value; }
switch (typeString)
{
case "float":
@@ -315,6 +315,7 @@ namespace Barotrauma.Networking
{
NetPropertyData netPropertyData = new NetPropertyData(this, property, typeName);
UInt32 key = ToolBox.StringToUInt32Hash(property.Name, md5);
if (key == 0) { key++; } //0 is reserved to indicate the end of the netproperties section of a message
if (netProperties.ContainsKey(key)){ throw new Exception("Hashing collision in ServerSettings.netProperties: " + netProperties[key] + " has same key as " + property.Name + " (" + key.ToString() + ")"); }
netProperties.Add(key, netPropertyData);
}
@@ -720,13 +721,6 @@ namespace Barotrauma.Networking
set;
}
[Serialize("", true)]
public string CampaignSubmarines
{
get;
set;
}
private YesNoMaybe traitorsEnabled;
[Serialize(YesNoMaybe.No, true)]
public YesNoMaybe TraitorsEnabled
@@ -1070,7 +1064,6 @@ namespace Barotrauma.Networking
}
#if SERVER
MultiPlayerCampaign.UpdateCampaignSubs();
SelectNonHiddenSubmarine();
#endif
}
@@ -124,7 +124,6 @@ namespace Barotrauma.Steam
return unlocked;
}
public static bool IncrementStat(string statName, int increment)
{
if (!isInitialized || !Steamworks.SteamClient.IsValid) { return false; }
@@ -136,6 +135,10 @@ namespace Barotrauma.Steam
DebugConsole.NewMessage("Failed to increment stat \"" + statName + "\".");
#endif
}
else
{
StoreStats();
}
return success;
}
@@ -150,9 +153,19 @@ namespace Barotrauma.Steam
DebugConsole.NewMessage("Failed to increment stat \"" + statName + "\".");
#endif
}
else
{
StoreStats();
}
return success;
}
public static int GetStatInt(string statName)
{
if (!isInitialized || !Steamworks.SteamClient.IsValid) { return 0; }
return Steamworks.SteamUserStats.GetStatInt(statName);
}
public static bool StoreStats()
{
if (!isInitialized || !Steamworks.SteamClient.IsValid) { return false; }
@@ -167,6 +180,17 @@ namespace Barotrauma.Steam
return success;
}
public static bool TryGetUnlockedAchievements(out List<Steamworks.Data.Achievement> achievements)
{
if (!isInitialized || !Steamworks.SteamClient.IsValid)
{
achievements = null;
return false;
}
achievements = Steamworks.SteamUserStats.Achievements.Where(a => a.State).ToList();
return true;
}
public static void Update(float deltaTime)
{
if (!isInitialized) { return; }