Unstable v0.19.1.0

This commit is contained in:
Juan Pablo Arce
2022-08-19 13:59:08 -03:00
parent 6b55adcdd9
commit 1219615d64
192 changed files with 3875 additions and 2648 deletions
@@ -1,37 +1,34 @@
using Barotrauma.Steam;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma.Networking
{
#warning TODO: turn this into INetSerializableStruct
partial class BannedPlayer
{
public string Name;
public string EndPoint; public bool IsRangeBan;
public UInt64 SteamID;
public string Reason;
public DateTime? ExpirationTime;
public UInt16 UniqueIdentifier;
public readonly string Name;
public readonly Either<Address, AccountId> AddressOrAccountId;
private void ParseEndPointAsSteamId()
{
ulong endPointAsSteamId = SteamManager.SteamIDStringToUInt64(EndPoint);
if (endPointAsSteamId != 0 && SteamID == 0) { SteamID = endPointAsSteamId; }
}
public readonly string Reason;
public DateTime? ExpirationTime;
public readonly UInt32 UniqueIdentifier;
}
partial class BanList
{
private readonly List<BannedPlayer> bannedPlayers;
public IReadOnlyList<BannedPlayer> BannedPlayers => bannedPlayers;
public IEnumerable<string> BannedNames
{
get { return bannedPlayers.Select(bp => bp.Name); }
}
public IEnumerable<string> BannedEndPoints
public IEnumerable<Either<Address, AccountId>> BannedAddresses
{
get { return bannedPlayers.Select(bp => bp.EndPoint).Where(endPoint => !string.IsNullOrEmpty(endPoint)); }
get { return bannedPlayers.Select(bp => bp.AddressOrAccountId); }
}
partial void InitProjectSpecific();
@@ -42,19 +39,5 @@ namespace Barotrauma.Networking
bannedPlayers = new List<BannedPlayer>();
InitProjectSpecific();
}
public static string ToRange(string ip)
{
if (SteamManager.SteamIDStringToUInt64(ip) != 0) { return ip; }
for (int i = ip.Length - 1; i > 0; i--)
{
if (ip[i] == '.')
{
ip = ip.Substring(0, i) + ".x";
break;
}
}
return ip;
}
}
}
@@ -11,10 +11,10 @@ namespace Barotrauma.Networking
public string Name;
public Identifier PreferredJob;
public CharacterTeamType PreferredTeam;
public UInt16 NameID;
public UInt64 SteamID;
public byte ID;
public UInt16 CharacterID;
public UInt16 NameId;
public AccountInfo AccountInfo;
public byte SessionId;
public UInt16 CharacterId;
public float Karma;
public bool Muted;
public bool InGame;
@@ -28,10 +28,23 @@ namespace Barotrauma.Networking
{
public const int MaxNameLength = 32;
public string Name; public UInt16 NameID;
public byte ID;
public UInt64 SteamID;
public UInt64 OwnerSteamID;
public string Name; public UInt16 NameId;
/// <summary>
/// An ID for this client for the current session.
/// THIS IS NOT A PERSISTENT VALUE. DO NOT STORE THIS LONG-TERM.
/// IT CANNOT BE USED TO IDENTIFY PLAYERS ACROSS SESSIONS.
/// </summary>
public readonly byte SessionId;
public AccountInfo AccountInfo;
/// <summary>
/// The ID of the account used to authenticate this session.
/// This value can be used as a persistent value to identify
/// players in the banlist and campaign saves.
/// </summary>
public Option<AccountId> AccountId => AccountInfo.AccountId;
public LanguageIdentifier Language;
@@ -90,14 +103,14 @@ namespace Barotrauma.Networking
public UInt16 CharacterID;
private Vector2 spectate_position;
private Vector2 spectatePos;
public Vector2? SpectatePos
{
get
{
if (character == null || character.IsDead)
{
return spectate_position;
return spectatePos;
}
else
{
@@ -107,7 +120,7 @@ namespace Barotrauma.Networking
set
{
spectate_position = value.Value;
spectatePos = value.Value;
}
}
@@ -177,19 +190,13 @@ namespace Barotrauma.Networking
{
get { return kickVoters.Count; }
}
/*public Client(NetPeer server, string name, byte ID)
: this(name, ID)
{
}*/
partial void InitProjSpecific();
partial void DisposeProjSpecific();
public Client(string name, byte ID)
public Client(string name, byte sessionId)
{
this.Name = name;
this.ID = ID;
this.SessionId = sessionId;
kickVoters = new List<Client>();
@@ -234,13 +241,16 @@ namespace Barotrauma.Networking
return kickVoters.Contains(voter);
}
public bool HasKickVoteFromID(int id)
public bool HasKickVoteFromSessionId(int id)
{
return kickVoters.Any(k => k.ID == id);
return kickVoters.Any(k => k.SessionId == id);
}
public bool SessionOrAccountIdMatches(string userId)
=> (AccountId.IsSome() && Networking.AccountId.Parse(userId) == AccountId)
|| (byte.TryParse(userId, out byte sessionId) && SessionId == sessionId);
public static void UpdateKickVotes(List<Client> connectedClients)
public static void UpdateKickVotes(IReadOnlyList<Client> connectedClients)
{
foreach (Client client in connectedClients)
{
@@ -250,7 +260,7 @@ namespace Barotrauma.Networking
public void WritePermissions(IWriteMessage msg)
{
msg.Write(ID);
msg.Write(SessionId);
msg.WriteRangedInteger((int)Permissions, 0, (int)ClientPermissions.All);
if (HasPermission(ClientPermissions.ConsoleCommands))
{
@@ -32,8 +32,8 @@ namespace Barotrauma.Networking
class PermissionPreset
{
public static List<PermissionPreset> List = new List<PermissionPreset>();
public static readonly List<PermissionPreset> List = new List<PermissionPreset>();
public readonly LocalizedString Name;
public readonly LocalizedString Description;
public readonly ClientPermissions Permissions;
@@ -87,9 +87,11 @@ namespace Barotrauma.Networking
}
}
public bool MatchesPermissions(ClientPermissions permissions, HashSet<DebugConsole.Command> permittedConsoleCommands)
public bool MatchesPermissions(ClientPermissions permissions, ISet<DebugConsole.Command> permittedConsoleCommands)
{
return permissions == this.Permissions && PermittedCommands.SequenceEqual(permittedConsoleCommands);
return permissions == Permissions
&& PermittedCommands.All(permittedConsoleCommands.Contains)
&& permittedConsoleCommands.All(PermittedCommands.Contains);
}
}
}
@@ -3,10 +3,10 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
@@ -35,7 +35,7 @@ namespace Barotrauma
/// 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 sealed class NetworkSerialize : Attribute
{
public int MaxValueInt = int.MaxValue;
public int MinValueInt = int.MinValue;
@@ -56,21 +56,37 @@ namespace Barotrauma
/// <summary>
/// Static class that contains serialize and deserialize functions for different types used in <see cref="INetSerializableStruct"/>
/// </summary>
public static class NetSerializableProperties
[SuppressMessage("ReSharper", "RedundantTypeArgumentsOfMethod")]
static class NetSerializableProperties
{
public readonly struct ReadWriteBehavior
public interface IReadWriteBehavior
{
public delegate dynamic? ReadDelegate(IReadMessage inc, Type type, NetworkSerialize attribute);
public delegate object? ReadDelegate(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField);
public delegate void WriteDelegate(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg);
public delegate void WriteDelegate(object? obj, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField);
public readonly ReadDelegate ReadAction;
public readonly WriteDelegate WriteAction;
public ReadDelegate ReadAction { get; }
public WriteDelegate WriteAction { get; }
}
public readonly struct ReadWriteBehavior<T> : IReadWriteBehavior
{
public delegate T ReadDelegate(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField);
public delegate void WriteDelegate(T obj, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField);
public IReadWriteBehavior.ReadDelegate ReadAction { get; }
public IReadWriteBehavior.WriteDelegate WriteAction { get; }
public ReadDelegate ReadActionDirect { get; }
public WriteDelegate WriteActionDirect { get; }
public ReadWriteBehavior(ReadDelegate readAction, WriteDelegate writeAction)
{
ReadAction = readAction;
WriteAction = writeAction;
ReadAction = (inc, attribute, bitField) => readAction(inc, attribute, bitField);
WriteAction = (o, attribute, msg, bitField) => writeAction((T)o!, attribute, msg, bitField);
ReadActionDirect = readAction;
WriteActionDirect = writeAction;
}
}
@@ -80,17 +96,18 @@ namespace Barotrauma
public delegate void SetValueDelegate(object? obj, object? value);
public readonly string Name;
public readonly Type Type;
public readonly ReadWriteBehavior Behavior;
public readonly IReadWriteBehavior Behavior;
public readonly NetworkSerialize Attribute;
public readonly SetValueDelegate SetValue;
public readonly GetValueDelegate GetValue;
public readonly bool HasOwnAttribute;
public CachedReflectedVariable(MemberInfo info, ReadWriteBehavior behavior, Type baseClassType)
public CachedReflectedVariable(MemberInfo info, IReadWriteBehavior behavior, Type baseClassType)
{
Behavior = behavior;
Name = info.Name;
switch (info)
{
case PropertyInfo pi:
@@ -126,343 +143,368 @@ namespace Barotrauma
private static readonly Dictionary<Type, ImmutableArray<CachedReflectedVariable>> CachedVariables = new Dictionary<Type, ImmutableArray<CachedReflectedVariable>>();
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(Identifier), new ReadWriteBehavior(ReadIdentifier, WriteDynamic) },
{ typeof(Color), new ReadWriteBehavior(ReadColor, WriteColor) },
{ typeof(Vector2), new ReadWriteBehavior(ReadVector2, WriteVector2) }
}.ToImmutableDictionary();
private static readonly Dictionary<Type, IReadWriteBehavior> TypeBehaviors
= new Dictionary<Type, IReadWriteBehavior>
{
{ typeof(Boolean), new ReadWriteBehavior<Boolean>(ReadBoolean, WriteBoolean) },
{ typeof(Byte), new ReadWriteBehavior<Byte>(ReadByte, WriteByte) },
{ typeof(UInt16), new ReadWriteBehavior<UInt16>(ReadUInt16, WriteUInt16) },
{ typeof(Int16), new ReadWriteBehavior<Int16>(ReadInt16, WriteInt16) },
{ typeof(UInt32), new ReadWriteBehavior<UInt32>(ReadUInt32, WriteUInt32) },
{ typeof(Int32), new ReadWriteBehavior<Int32>(ReadInt32, WriteInt32) },
{ typeof(UInt64), new ReadWriteBehavior<UInt64>(ReadUInt64, WriteUInt64) },
{ typeof(Int64), new ReadWriteBehavior<Int64>(ReadInt64, WriteInt64) },
{ typeof(Single), new ReadWriteBehavior<Single>(ReadSingle, WriteSingle) },
{ typeof(Double), new ReadWriteBehavior<Double>(ReadDouble, WriteDouble) },
{ typeof(String), new ReadWriteBehavior<String>(ReadString, WriteString) },
{ typeof(Identifier), new ReadWriteBehavior<Identifier>(ReadIdentifier, WriteIdentifier) },
{ typeof(AccountId), new ReadWriteBehavior<AccountId>(ReadAccountId, WriteAccountId) },
{ typeof(Color), new ReadWriteBehavior<Color>(ReadColor, WriteColor) },
{ typeof(Vector2), new ReadWriteBehavior<Vector2>(ReadVector2, WriteVector2) }
};
private static readonly ImmutableDictionary<Predicate<Type>, ReadWriteBehavior> TypePredicates = new Dictionary<Predicate<Type>, ReadWriteBehavior>
private static readonly ImmutableDictionary<Predicate<Type>, Func<Type, IReadWriteBehavior>> BehaviorFactories = new Dictionary<Predicate<Type>, Func<Type, IReadWriteBehavior>>
{
// Arrays
{ type => typeof(Array).IsAssignableFrom(type.BaseType), new ReadWriteBehavior(ReadArray, WriteArray) },
{ type => type.IsArray, CreateArrayBehavior },
// Nested INetSerializableStructs
{ type => typeof(INetSerializableStruct).IsAssignableFrom(type), new ReadWriteBehavior(ReadINetSerializableStruct, WriteINetSerializableStruct) },
{ type => typeof(INetSerializableStruct).IsAssignableFrom(type), CreateINetSerializableStructBehavior },
// Enums
{ type => type.IsEnum, new ReadWriteBehavior(ReadEnum, WriteEnum) },
{ type => type.IsEnum, CreateEnumBehavior },
// Nullable
{ type => Nullable.GetUnderlyingType(type) != null, new ReadWriteBehavior(ReadNullable, WriteNullable) },
{ type => Nullable.GetUnderlyingType(type) != null, CreateNullableStructBehavior },
// ImmutableArray
{ type => IsOfGenericType(type, typeof(ImmutableArray<>)), CreateImmutableArrayBehavior },
// Option
{ type => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Option<>), new ReadWriteBehavior(ReadOption, WriteOption) }
{ type => IsOfGenericType(type, typeof(Option<>)), CreateOptionBehavior }
}.ToImmutableDictionary();
private static readonly ReadWriteBehavior InvalidReadWriteBehavior = new ReadWriteBehavior(ReadInvalid, WriteInvalid);
private static readonly Dictionary<Type, MethodInfo> cachedSomeCreateMethods = new Dictionary<Type, MethodInfo>();
private static readonly Dictionary<Type, MethodInfo> cachedNoneCreateMethod = new Dictionary<Type, MethodInfo>();
private static void WriteInvalid(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg) =>
throw new SerializationException($"Type {obj?.GetType()} cannot be serialized. Did you forget to implement {nameof(INetSerializableStruct)}?");
private static dynamic ReadInvalid(IReadMessage inc, Type type, NetworkSerialize attribute) => throw new SerializationException($"Type {type} cannot be deserialized. Did you forget to implement {nameof(INetSerializableStruct)}?");
private static void WriteOption(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
/// <param name="behaviorGenericParam">The type that the behavior handles</param>
/// <param name="funcGenericParam">The type that will be used as the generic parameter for the read/write methods</param>
/// <param name="readFunc">The read method.
/// It must have a generic parameter.
/// The return type must be such that if the generic parameter is replaced with funcGenericParam, you get behaviorGenericParam.</param>
/// <param name="writeFunc">The write method. The first parameter's type must be the same as readFunc's return type.</param>
/// <typeparam name="TDelegateBase">Ideally the least specific type possible, because it's replaced by behaviorGenericParam</typeparam>
/// <returns>A ReadWriteBehavior&lt;behaviorGenericParam&gt;</returns>
private static IReadWriteBehavior CreateBehavior<TDelegateBase>(Type behaviorGenericParam,
Type funcGenericParam,
ReadWriteBehavior<TDelegateBase>.ReadDelegate readFunc,
ReadWriteBehavior<TDelegateBase>.WriteDelegate writeFunc)
{
if (obj is null) { throw new ArgumentNullException(nameof(obj), "Tried to write 'null' into a non-nullable type"); }
var behaviorType = typeof(ReadWriteBehavior<>).MakeGenericType(behaviorGenericParam);
Type type = obj.GetType();
Type optionType = type.GetGenericTypeDefinition();
Type underlyingType = type.GetGenericArguments()[0];
var readDelegateType = typeof(ReadWriteBehavior<>.ReadDelegate).MakeGenericType(behaviorGenericParam);
var writeDelegateType = typeof(ReadWriteBehavior<>.WriteDelegate).MakeGenericType(behaviorGenericParam);
if (optionType == typeof(None<>))
var constructor = behaviorType.GetConstructor(new[]
{
msg.Write(false);
readDelegateType, writeDelegateType
});
return (constructor!.Invoke(new object[]
{
readFunc.Method.GetGenericMethodDefinition().MakeGenericMethod(funcGenericParam).CreateDelegate(readDelegateType),
writeFunc.Method.GetGenericMethodDefinition().MakeGenericMethod(funcGenericParam).CreateDelegate(writeDelegateType)
}) as IReadWriteBehavior)!;
}
private static IReadWriteBehavior CreateArrayBehavior(Type arrayType) =>
CreateBehavior(
arrayType,
arrayType.GetElementType()!,
ReadArray<object>,
WriteArray<object>);
private static IReadWriteBehavior CreateINetSerializableStructBehavior(Type structType) =>
CreateBehavior(
structType,
structType,
ReadINetSerializableStruct<INetSerializableStruct>,
WriteINetSerializableStruct<INetSerializableStruct>);
private static IReadWriteBehavior CreateEnumBehavior(Type enumType) =>
CreateBehavior(
enumType,
enumType,
ReadEnum<Enum>,
WriteEnum<Enum>);
private static IReadWriteBehavior CreateNullableStructBehavior(Type nullableType) =>
CreateBehavior(
nullableType,
Nullable.GetUnderlyingType(nullableType)!,
ReadNullable<int>,
WriteNullable<int>);
private static IReadWriteBehavior CreateOptionBehavior(Type optionType) =>
CreateBehavior(
optionType,
optionType.GetGenericArguments()[0],
ReadOption<object>,
WriteOption<object>);
private static IReadWriteBehavior CreateImmutableArrayBehavior(Type arrayType) =>
CreateBehavior(
arrayType,
arrayType.GetGenericArguments()[0],
ReadImmutableArray<object>,
WriteImmutableArray<object>);
private static ImmutableArray<T> ReadImmutableArray<T>(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) where T : notnull
{
return ReadArray<T>(inc, attribute, bitField).ToImmutableArray();
}
private static void WriteImmutableArray<T>(ImmutableArray<T> array, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) where T : notnull
{
ToolBox.ThrowIfNull(array);
WriteIReadOnlyCollection<T>(array, attribute, msg, bitField);
}
private static T[] ReadArray<T>(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) where T : notnull
{
int length = bitField.ReadInteger(0, attribute.ArrayMaxSize);
T[] array = new T[length];
if (!TryFindBehavior(out ReadWriteBehavior<T> behavior))
{
throw new InvalidOperationException($"Could not find suitable behavior for type {typeof(T)} in {nameof(ReadArray)}");
}
else if (optionType == typeof(Some<>))
for (int i = 0; i < length; i++)
{
msg.Write(true);
if (TryFindBehavior(underlyingType, out ReadWriteBehavior behavior))
{
behavior.WriteAction(obj.Value, attribute, msg);
}
array[i] = behavior.ReadActionDirect(inc, attribute, bitField);
}
else
return array;
}
private static void WriteArray<T>(T[] array, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) where T : notnull
{
ToolBox.ThrowIfNull(array);
WriteIReadOnlyCollection(array, attribute, msg, bitField);
}
private static void WriteIReadOnlyCollection<T>(IReadOnlyCollection<T> array, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) where T : notnull
{
bitField.WriteInteger(array.Count, 0, attribute.ArrayMaxSize);
if (!TryFindBehavior(out ReadWriteBehavior<T> behavior))
{
throw new ArgumentOutOfRangeException(nameof(obj), "Option type was neither None or Some");
throw new InvalidOperationException($"Could not find suitable behavior for type {typeof(T)} in {nameof(WriteArray)}");
}
foreach (T o in array)
{
behavior.WriteActionDirect(o, attribute, msg, bitField);
}
}
private static dynamic? ReadOption(IReadMessage inc, Type type, NetworkSerialize attribute)
private static T ReadINetSerializableStruct<T>(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) where T : INetSerializableStruct
{
Type underlyingType = type.GetGenericArguments()[0];
bool hasValue = inc.ReadBoolean();
if (!hasValue)
{
return GetCreateMethod(typeof(None<>), underlyingType, cachedNoneCreateMethod).Invoke(null, null);
}
if (TryFindBehavior(underlyingType, out ReadWriteBehavior behavior))
{
dynamic? value = behavior.ReadAction(inc, underlyingType, attribute);
return GetCreateMethod(typeof(Some<>), underlyingType, cachedSomeCreateMethods).Invoke(null, new[] { value });
}
throw new InvalidOperationException($"Could not find suitable behavior for type {underlyingType} in {nameof(ReadOption)}");
static MethodInfo GetCreateMethod(Type optionType, Type type, Dictionary<Type, MethodInfo> cache)
{
if (cache.TryGetValue(type, out MethodInfo? foundInfo))
{
return foundInfo;
}
Type genericType = optionType.MakeGenericType(type);
MethodInfo info = genericType.GetMethod("Create", BindingFlags.Static | BindingFlags.Public)!;
cache.Add(type, info);
return info;
}
return INetSerializableStruct.ReadInternal<T>(inc, bitField);
}
private static void WriteNullable(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
private static void WriteINetSerializableStruct<T>(T serializableStruct, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) where T : INetSerializableStruct
{
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);
ToolBox.ThrowIfNull(serializableStruct);
serializableStruct.WriteInternal(msg, bitField);
}
private static dynamic? ReadNullable(IReadMessage inc, Type type, NetworkSerialize attribute)
private static T ReadEnum<T>(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) where T : Enum
{
if (!inc.ReadBoolean()) { return null; }
var type = typeof(T);
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);
int enumIndex = bitField.ReadInteger(range.Start, range.End);
foreach (dynamic? e in Enum.GetValues(type))
foreach (T e in (T[])Enum.GetValues(type))
{
if (Convert.ChangeType(e, e!.GetTypeCode()) == enumIndex) { return e; }
if ((int)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)
private static void WriteEnum<T>(T value, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) where T : Enum
{
if (obj is null) { throw new ArgumentNullException(nameof(obj), "Tried to write 'null' into a non-nullable type"); }
ToolBox.ThrowIfNull(value);
if (!(obj is INetSerializableStruct serializableStruct)) { throw new InvalidOperationException($"Object in {nameof(WriteINetSerializableStruct)} was {obj.GetType()} but expected {nameof(INetSerializableStruct)}"); }
serializableStruct.Write(msg);
Range<int> range = GetEnumRange(typeof(T));
bitField.WriteInteger((int)Convert.ChangeType(value, value.GetTypeCode()), range.Start, range.End);
}
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++)
private static T? ReadNullable<T>(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) where T : struct =>
ReadOption<T>(inc, attribute, bitField) switch
{
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)}");
}
Some<T> { Value: var value } => value,
None<T> _ => null,
_ => throw new ArgumentOutOfRangeException()
};
private static void WriteNullable<T>(T? value, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) where T : struct =>
WriteOption<T>(value.HasValue ? Option<T>.Some(value.Value) : Option<T>.None(), attribute, msg, bitField);
private static Option<T> ReadOption<T>(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) where T : notnull
{
bool hasValue = bitField.ReadBoolean();
if (!hasValue)
{
return Option<T>.None();
}
return list;
if (TryFindBehavior(out ReadWriteBehavior<T> behavior))
{
return Option<T>.Some(behavior.ReadActionDirect(inc, attribute, bitField));
}
throw new InvalidOperationException($"Could not find suitable behavior for type {typeof(T)} in {nameof(ReadOption)}");
}
private static void WriteArray(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
private static void WriteOption<T>(Option<T> option, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) where T : notnull
{
if (obj is null) { throw new ArgumentNullException(nameof(obj), "Tried to write 'null' into a non-nullable type"); }
ToolBox.ThrowIfNull(option);
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 (option.TryUnwrap(out T value))
{
if (TryFindBehavior(o!.GetType(), out ReadWriteBehavior behavior))
bitField.WriteBoolean(true);
if (TryFindBehavior(out ReadWriteBehavior<T> behavior))
{
behavior.WriteAction(o, attribute, msg);
behavior.WriteActionDirect(value, attribute, msg, bitField);
}
}
else
{
bitField.WriteBoolean(false);
}
}
private static dynamic ReadBoolean(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadBoolean();
private static bool ReadBoolean(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => bitField.ReadBoolean();
private static void WriteBoolean(bool b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { bitField.WriteBoolean(b); }
private static dynamic ReadByte(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadByte();
private static byte ReadByte(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadByte();
private static void WriteByte(byte b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); }
private static dynamic ReadUInt16(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadUInt16();
private static ushort ReadUInt16(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadUInt16();
private static void WriteUInt16(ushort b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); }
private static dynamic ReadInt16(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadInt16();
private static short ReadInt16(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadInt16();
private static void WriteInt16(short b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); }
private static dynamic ReadUInt32(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadUInt32();
private static uint ReadUInt32(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadUInt32();
private static void WriteUInt32(uint b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); }
private static dynamic ReadInt32(IReadMessage inc, Type type, NetworkSerialize attribute)
private static int ReadInt32(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField)
{
if (IsRanged(attribute.MinValueInt, attribute.MaxValueInt))
{
return inc.ReadRangedInteger(attribute.MinValueInt, attribute.MaxValueInt);
return bitField.ReadInteger(attribute.MinValueInt, attribute.MaxValueInt);
}
return inc.ReadInt32();
}
private static void WriteInt32(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
private static void WriteInt32(int i, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField)
{
if (obj is null) { throw new ArgumentNullException(nameof(obj), "Tried to write 'null' into a non-nullable type"); }
ToolBox.ThrowIfNull(i);
if (IsRanged(attribute.MinValueInt, attribute.MaxValueInt))
{
msg.WriteRangedInteger(obj, attribute.MinValueInt, attribute.MaxValueInt);
bitField.WriteInteger(i, attribute.MinValueInt, attribute.MaxValueInt);
return;
}
msg.Write(obj);
msg.Write(i);
}
private static dynamic ReadUInt64(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadUInt64();
private static ulong ReadUInt64(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadUInt64();
private static void WriteUInt64(ulong b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); }
private static dynamic ReadInt64(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadInt64();
private static long ReadInt64(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadInt64();
private static void WriteInt64(long b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); }
private static dynamic ReadSingle(IReadMessage inc, Type type, NetworkSerialize attribute)
private static float ReadSingle(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField)
{
if (IsRanged(attribute.MinValueFloat, attribute.MaxValueFloat))
{
return inc.ReadRangedSingle(attribute.MinValueFloat, attribute.MaxValueFloat, attribute.NumberOfBits);
return bitField.ReadFloat(attribute.MinValueFloat, attribute.MaxValueFloat, attribute.NumberOfBits);
}
return inc.ReadSingle();
}
private static void WriteSingle(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
private static void WriteSingle(float f, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField)
{
if (obj is null) { throw new ArgumentNullException(nameof(obj), "Tried to write 'null' into a non-nullable type"); }
ToolBox.ThrowIfNull(f);
if (IsRanged(attribute.MinValueFloat, attribute.MaxValueFloat))
{
msg.WriteRangedSingle(obj, attribute.MinValueFloat, attribute.MaxValueFloat, attribute.NumberOfBits);
bitField.WriteFloat(f, attribute.MinValueFloat, attribute.MaxValueFloat, attribute.NumberOfBits);
return;
}
msg.Write(obj);
msg.Write(f);
}
private static dynamic ReadDouble(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadDouble();
private static double ReadDouble(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadDouble();
private static void WriteDouble(double b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); }
private static dynamic ReadString(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadString();
private static string ReadString(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadString();
private static void WriteString(string b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); }
private static dynamic ReadIdentifier(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadIdentifier();
private static Identifier ReadIdentifier(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => inc.ReadIdentifier();
private static void WriteIdentifier(Identifier b, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField) { msg.Write(b); }
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)
private static AccountId ReadAccountId(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField)
{
if (obj is null) { throw new ArgumentNullException(nameof(obj), "Tried to write 'null' into a non-nullable type"); }
string str = inc.ReadString();
return AccountId.Parse(str).TryUnwrap(out var accountId)
? accountId
: throw new InvalidCastException($"Could not parse \"{str}\" as an {nameof(AccountId)}");
}
private static void WriteAccountId(AccountId accountId, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField)
{
msg.Write(accountId.StringRepresentation);
}
private static Color ReadColor(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField) => attribute.IncludeColorAlpha ? inc.ReadColorR8G8B8A8() : inc.ReadColorR8G8B8();
private static void WriteColor(Color color, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField)
{
ToolBox.ThrowIfNull(color);
if (attribute.IncludeColorAlpha)
{
msg.WriteColorR8G8B8A8(obj);
msg.WriteColorR8G8B8A8(color);
return;
}
msg.WriteColorR8G8B8(obj);
msg.WriteColorR8G8B8(color);
}
private static dynamic ReadVector2(IReadMessage inc, Type type, NetworkSerialize attribute)
private static Vector2 ReadVector2(IReadMessage inc, NetworkSerialize attribute, IReadableBitField bitField)
{
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();
}
float x = ReadSingle(inc, attribute, bitField);
float y = ReadSingle(inc, attribute, bitField);
return new Vector2(x, y);
}
private static void WriteVector2(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
private static void WriteVector2(Vector2 vector2, NetworkSerialize attribute, IWriteMessage msg, IWritableBitField bitField)
{
if (obj is null) { throw new ArgumentNullException(nameof(obj), "Tried to write 'null' into a non-nullable type"); }
ToolBox.ThrowIfNull(vector2);
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);
var (x, y) = vector2;
WriteSingle(x, attribute, msg, bitField);
WriteSingle(y, attribute, msg, bitField);
}
private static bool IsRanged(float minValue, float maxValue) => minValue > float.MinValue || maxValue < float.MaxValue;
@@ -474,53 +516,71 @@ namespace Barotrauma
return new Range<int>(values.Min(), values.Max());
}
private static bool TryFindBehavior(Type type, out ReadWriteBehavior behavior)
private static bool TryFindBehavior<T>(out ReadWriteBehavior<T> behavior) where T : notnull
{
if (TypeBehaviors.TryGetValue(type, out behavior)) { return true; }
bool found = TryFindBehavior(typeof(T), out var bhvr);
behavior = (ReadWriteBehavior<T>)bhvr;
return found;
}
foreach (var (predicate, behavior2) in TypePredicates)
private static bool TryFindBehavior(Type type, out IReadWriteBehavior behavior)
{
if (TypeBehaviors.TryGetValue(type, out var outBehavior))
{
if (predicate(type))
{
behavior = behavior2;
return true;
}
behavior = outBehavior;
return true;
}
behavior = InvalidReadWriteBehavior;
foreach (var (predicate, factory) in BehaviorFactories)
{
if (!predicate(type)) { continue; }
behavior = factory(type);
TypeBehaviors.Add(type, behavior);
return true;
}
behavior = default!;
return false;
}
public static ImmutableArray<CachedReflectedVariable> GetPropertiesAndFields(Type type, Type baseClassType)
public static ImmutableArray<CachedReflectedVariable> GetPropertiesAndFields(Type type)
{
if (CachedVariables.TryGetValue(type, out var cached)) { return cached; }
List<CachedReflectedVariable> variables = new List<CachedReflectedVariable>();
IEnumerable<PropertyInfo> propertyInfos = type.GetProperties().Where(HasAttribute);
IEnumerable<FieldInfo> fieldInfos = type.GetFields().Where(HasAttribute);
IEnumerable<PropertyInfo> propertyInfos = type.GetProperties().Where(HasAttribute).Where(NotStatic);
IEnumerable<FieldInfo> fieldInfos = type.GetFields().Where(HasAttribute).Where(NotStatic);
foreach (PropertyInfo info in propertyInfos)
{
if (TryFindBehavior(info.PropertyType, out ReadWriteBehavior behavior))
if (info.SetMethod is null)
{
variables.Add(new CachedReflectedVariable(info, behavior, baseClassType));
//skip get-only properties, because it's
//useful to have them but their value
//cannot be set when reading a struct
continue;
}
if (TryFindBehavior(info.PropertyType, out IReadWriteBehavior behavior))
{
variables.Add(new CachedReflectedVariable(info, behavior, type));
}
else
{
throw new SerializationException($"Unable to serialize type \"{type}\".");
throw new Exception($"Unable to serialize type \"{type}\".");
}
}
foreach (FieldInfo info in fieldInfos)
{
if (TryFindBehavior(info.FieldType, out ReadWriteBehavior behavior))
if (TryFindBehavior(info.FieldType, out IReadWriteBehavior behavior))
{
variables.Add(new CachedReflectedVariable(info, behavior, baseClassType));
variables.Add(new CachedReflectedVariable(info, behavior, type));
}
else
{
throw new SerializationException($"Unable to serialize type \"{type}\".");
throw new Exception($"Unable to serialize type \"{type}\".");
}
}
@@ -528,7 +588,20 @@ namespace Barotrauma
CachedVariables.Add(type, array);
return array;
bool HasAttribute(MemberInfo info) => (info.GetCustomAttribute<NetworkSerialize>() ?? baseClassType.GetCustomAttribute<NetworkSerialize>()) != null;
bool HasAttribute(MemberInfo info) => (info.GetCustomAttribute<NetworkSerialize>() ?? type.GetCustomAttribute<NetworkSerialize>()) != null;
bool NotStatic(MemberInfo info)
=> info switch
{
PropertyInfo property => property.GetGetMethod() is { IsStatic: false },
FieldInfo field => !field.IsStatic,
_ => false
};
}
private static bool IsOfGenericType(Type type, Type comparedTo)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == comparedTo;
}
}
@@ -575,13 +648,15 @@ namespace Barotrauma
/// <see cref="Single">float</see><br/>
/// <see cref="Double">double</see><br/>
/// <see cref="String">string</see><br/>
/// <see cref="Barotrauma.Networking.AccountId"/><br/>
/// <see cref="System.Collections.Immutable.ImmutableArray{T}"></see><br/>
/// <see cref="Microsoft.Xna.Framework.Color"/><br/>
/// <see cref="Microsoft.Xna.Framework.Vector2"/><br/>
/// In addition arrays, enums, <see cref="Nullable{T}"/> and <see cref="Option{T}"/> are supported.<br/>
/// Using <see cref="Nullable{T}"/> or <see cref="Option{T}"/> will make the field or property optional.
/// </remarks>
/// <seealso cref="NetworkSerialize"/>
public interface INetSerializableStruct
interface INetSerializableStruct
{
/// <summary>
/// Deserializes a network message into a struct.
@@ -608,21 +683,34 @@ namespace Barotrauma
/// <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)
public static T Read<T>(IReadMessage inc) where T : INetSerializableStruct
{
object? newObject = Activator.CreateInstance(type);
IReadableBitField bitField = new ReadOnlyBitField(inc);
return ReadInternal<T>(inc, bitField);
}
public static T ReadInternal<T>(IReadMessage inc, IReadableBitField bitField) where T : INetSerializableStruct
{
object? newObject = Activator.CreateInstance(typeof(T));
if (newObject is null) { return default!; }
var properties = NetSerializableProperties.GetPropertiesAndFields(type, type);
var properties = NetSerializableProperties.GetPropertiesAndFields(typeof(T));
foreach (NetSerializableProperties.CachedReflectedVariable property in properties)
{
NetworkSerialize attribute = property.Attribute;
property.SetValue(newObject, property.Behavior.ReadAction(inc, property.Type, attribute));
object? value = property.Behavior.ReadAction(inc, property.Attribute, bitField);
try
{
property.SetValue(newObject, value);
}
catch (Exception exception)
{
throw new Exception($"Failed to assign" +
$" {value ?? "[NULL]"} ({value?.GetType().Name ?? "[NULL]"})" +
$" to {typeof(T).Name}.{property.Name} ({property.Type.Name})", exception);
}
}
return newObject;
return (T)newObject;
}
/// <summary>
@@ -651,17 +739,26 @@ namespace Barotrauma
/// <param name="msg">Outgoing network message</param>
public void Write(IWriteMessage msg)
{
Type type = GetType();
var properties = NetSerializableProperties.GetPropertiesAndFields(type, type);
IWritableBitField bitField = new WriteOnlyBitField();
IWriteMessage structWriteMsg = new WriteOnlyMessage();
WriteInternal(structWriteMsg, bitField);
bitField.WriteToMessage(msg);
msg.Write(structWriteMsg.Buffer, 0, structWriteMsg.LengthBytes);
}
public void WriteInternal(IWriteMessage msg, IWritableBitField bitField)
{
var properties = NetSerializableProperties.GetPropertiesAndFields(GetType());
foreach (NetSerializableProperties.CachedReflectedVariable property in properties)
{
NetworkSerialize attribute = property.Attribute;
property.Behavior.WriteAction(property.GetValue(this), attribute, msg);
object? value = property.GetValue(this);
property.Behavior.WriteAction(value!, property.Attribute, msg, bitField);
}
}
}
public static class WriteOnlyMessageExtensions
static class WriteOnlyMessageExtensions
{
#if CLIENT
public static IWriteMessage WithHeader(this IWriteMessage msg, ClientPacketHeader header)
@@ -148,7 +148,6 @@ namespace Barotrauma.Networking
InvalidVersion,
MissingContentPackage,
IncompatibleContentPackage,
NotOnWhitelist,
ExcessiveDesyncOldEvent,
ExcessiveDesyncRemovedEvent,
SyncTimeout,
@@ -218,10 +217,7 @@ namespace Barotrauma.Networking
get { return gameStarted; }
}
public virtual List<Client> ConnectedClients
{
get { return null; }
}
public abstract IReadOnlyList<Client> ConnectedClients { get; }
public RespawnManager RespawnManager
{
@@ -268,19 +264,22 @@ namespace Barotrauma.Networking
{
retVal += "color:#ff9900;";
}
retVal += "metadata:" + (client.SteamID != 0 ? client.SteamID.ToString() : client.ID.ToString()) + "‖" + (name ?? client.Name).Replace("‖", "") + "‖end‖";
retVal += "metadata:" + (client.AccountId.TryUnwrap(out var accountId) ? accountId.ToString() : client.SessionId.ToString())
+ "‖" + (name ?? client.Name).Replace("‖", "") + "‖end‖";
return retVal;
}
public virtual void KickPlayer(string kickedName, string reason) { }
public abstract void KickPlayer(string kickedName, string reason);
public virtual void BanPlayer(string kickedName, string reason, bool range = false, TimeSpan? duration = null) { }
public abstract void BanPlayer(string kickedName, string reason, TimeSpan? duration = null);
public virtual void UnbanPlayer(string playerName, string playerIP) { }
public abstract void UnbanPlayer(string playerName);
public abstract void UnbanPlayer(Endpoint endpoint);
public virtual void Update(float deltaTime) { }
public virtual void Disconnect() { }
public virtual void Quit() { }
/// <summary>
/// Check if the two version are compatible (= if they can play together in multiplayer).
@@ -0,0 +1,24 @@
#nullable enable
namespace Barotrauma.Networking
{
abstract class AccountId
{
public abstract string StringRepresentation { get; }
public static Option<AccountId> Parse(string str)
=> ReflectionUtils.ParseDerived<AccountId>(str);
public abstract override bool Equals(object? obj);
public abstract override int GetHashCode();
public override string ToString() => StringRepresentation;
public static bool operator ==(AccountId a, AccountId b)
=> a.Equals(b);
public static bool operator !=(AccountId a, AccountId b)
=> !(a == b);
}
}
@@ -0,0 +1,121 @@
#nullable enable
using System;
namespace Barotrauma.Networking
{
sealed class SteamId : AccountId
{
public readonly UInt64 Value;
public override string StringRepresentation { get; }
/// Based on information found here: https://developer.valvesoftware.com/wiki/SteamID
/// ------------------------------------------------------------------------------------
/// A SteamID is a 64-bit value (16 hexadecimal digits) that's broken up as follows:
///
/// | a | b | c | d |
/// Most significant - | 01 | 1 | 00001 | 0546779D | - Least significant
///
/// a) 8 bits representing the universe the account belongs to.
/// b) 4 bits representing the type of account. Typically 1.
/// c) 20 bits representing the instance of the account. Typically 1.
/// d) 32 bits representing the account number.
///
/// The account number is additionally broken up as follows:
///
/// | e | f |
/// Most significant - | 0000010101000110011101111001110 | 1 | - Least significant
///
/// e) These are the 31 most significant bits of the account number.
/// f) This is the least significant bit of the account number, discriminated under the name Y for some reason.
///
/// Barotrauma supports two textual representations of SteamIDs:
/// 1. STEAM40: Given this name as it represents 40 of the 64 bits in the ID. The account type and instance both
/// have an implied value of 1. The format is "STEAM_{universe}:{Y}:{restOfAccountNumber}".
/// 2. STEAM64: If STEAM40 does not suffice to represent an ID (i.e. the account type or instance were different
/// from 1), we use "STEAM64_{fullId}" where fullId is the 64-bit decimal representation of the full
/// ID.
private const string steam64Prefix = "STEAM64_";
private const string steam40Prefix = "STEAM_";
private const UInt64 usualAccountInstance = 1;
private const UInt64 usualAccountType = 1;
static UInt64 ExtractBits(UInt64 id, int offset, int numberOfBits)
=> (id >> offset) & ((1ul << numberOfBits) - 1ul);
static UInt64 ExtractY(UInt64 id)
=> ExtractBits(id, offset: 0, numberOfBits: 1);
static UInt64 ExtractAccountNumberRemainder(UInt64 id)
=> ExtractBits(id, offset: 1, numberOfBits: 31);
static UInt64 ExtractAccountInstance(UInt64 id)
=> ExtractBits(id, offset: 32, numberOfBits: 20);
static UInt64 ExtractAccountType(UInt64 id)
=> ExtractBits(id, offset: 52, numberOfBits: 4);
static UInt64 ExtractUniverse(UInt64 id)
=> ExtractBits(id, offset: 56, numberOfBits: 8);
public SteamId(UInt64 value)
{
Value = value;
if (ExtractAccountInstance(Value) == usualAccountInstance
&& ExtractAccountType(Value) == usualAccountType)
{
UInt64 y = ExtractY(Value);
UInt64 accountNumberRemainder = ExtractAccountNumberRemainder(Value);
UInt64 universe = ExtractUniverse(Value);
StringRepresentation = $"{steam40Prefix}{universe}:{y}:{accountNumberRemainder}";
}
else
{
StringRepresentation = $"{steam64Prefix}{Value}";
}
}
public override string ToString() => StringRepresentation;
public new static Option<SteamId> Parse(string str)
{
if (str.IsNullOrWhiteSpace()) { return Option<SteamId>.None(); }
if (str.StartsWith(steam64Prefix, StringComparison.InvariantCultureIgnoreCase)) { str = str[steam64Prefix.Length..]; }
if (UInt64.TryParse(str, out UInt64 retVal) && ExtractAccountInstance(retVal) > 0)
{
return Option<SteamId>.Some(new SteamId(retVal));
}
if (!str.StartsWith(steam40Prefix, StringComparison.InvariantCultureIgnoreCase)) { return Option<SteamId>.None(); }
string[] split = str[steam40Prefix.Length..].Split(':');
if (split.Length != 3) { return Option<SteamId>.None(); }
if (!UInt64.TryParse(split[0], out UInt64 universe)) { return Option<SteamId>.None(); }
if (!UInt64.TryParse(split[1], out UInt64 y)) { return Option<SteamId>.None(); }
if (!UInt64.TryParse(split[2], out UInt64 accountNumber)) { return Option<SteamId>.None(); }
return Option<SteamId>.Some(
new SteamId((universe << 56)
| usualAccountType << 52
| usualAccountInstance << 32
| (accountNumber << 1)
| y));
}
public override bool Equals(object? obj)
=> obj switch
{
SteamId otherId => this == otherId,
_ => false
};
public override int GetHashCode()
=> Value.GetHashCode();
public static bool operator ==(SteamId a, SteamId b)
=> a.Value == b.Value;
public static bool operator !=(SteamId a, SteamId b)
=> !(a == b);
}
}
@@ -0,0 +1,50 @@
#nullable enable
using System.Collections.Immutable;
using System.Linq;
namespace Barotrauma.Networking
{
[NetworkSerialize]
readonly struct AccountInfo : INetSerializableStruct
{
public static readonly AccountInfo None = new AccountInfo(Option<AccountId>.None());
/// <summary>
/// The primary ID for a given user
/// </summary>
public readonly Option<AccountId> AccountId;
/// <summary>
/// Other user IDs that this user might be closely tied to,
/// such as the owner of the current copy of Barotrauma
/// </summary>
#warning TODO: make ImmutableArray once feature/inetserializablestruct-improvements gets merged to dev
public readonly AccountId[] OtherMatchingIds;
public AccountInfo(AccountId accountId, params AccountId[] otherIds) : this(Option<AccountId>.Some(accountId), otherIds) { }
public AccountInfo(Option<AccountId> accountId, params AccountId[] otherIds)
{
AccountId = accountId;
OtherMatchingIds = otherIds.Where(id => !accountId.ValueEquals(id)).ToArray();
}
public bool Matches(AccountId accountId)
=> AccountId.ValueEquals(accountId) || OtherMatchingIds.Contains(accountId);
public override bool Equals(object? obj)
=> obj switch
{
AccountInfo otherInfo => AccountId == otherInfo.AccountId && OtherMatchingIds.All(otherInfo.OtherMatchingIds.Contains),
_ => false
};
public override int GetHashCode()
=> AccountId.GetHashCode();
public static bool operator ==(AccountInfo a, AccountInfo b)
=> a.Equals(b);
public static bool operator !=(AccountInfo a, AccountInfo b) => !(a == b);
}
}
@@ -0,0 +1,26 @@
#nullable enable
namespace Barotrauma.Networking
{
abstract class Address
{
public abstract string StringRepresentation { get; }
public static Option<Address> Parse(string str)
=> ReflectionUtils.ParseDerived<Address>(str);
public abstract bool IsLocalHost { get; }
public abstract override bool Equals(object? obj);
public abstract override int GetHashCode();
public override string ToString() => StringRepresentation;
public static bool operator ==(Address a, Address b)
=> a.Equals(b);
public static bool operator !=(Address a, Address b)
=> !(a == b);
}
}
@@ -0,0 +1,69 @@
#nullable enable
using System;
using System.Linq;
using System.Net;
using System.Net.Sockets;
namespace Barotrauma.Networking
{
sealed class LidgrenAddress : Address
{
public readonly IPAddress NetAddress;
public override string StringRepresentation
=> NetAddress.ToString();
public override bool IsLocalHost => IPAddress.IsLoopback(NetAddress);
public LidgrenAddress(IPAddress netAddress)
{
NetAddress = netAddress;
}
public new static Option<LidgrenAddress> Parse(string endpointStr)
{
if (IPAddress.TryParse(endpointStr, out IPAddress? netEndpoint))
{
return Option<LidgrenAddress>.Some(new LidgrenAddress(netEndpoint!));
}
try
{
var resolvedAddresses = Dns.GetHostAddresses(endpointStr);
return resolvedAddresses.Any()
? Option<LidgrenAddress>.Some(new LidgrenAddress(resolvedAddresses.First()))
: Option<LidgrenAddress>.None();
}
catch (SocketException)
{
return Option<LidgrenAddress>.None();
}
catch (ArgumentOutOfRangeException)
{
return Option<LidgrenAddress>.None();
}
}
public override bool Equals(object? obj)
=> obj switch
{
LidgrenAddress otherAddress => this == otherAddress,
_ => false
};
public override int GetHashCode()
=> NetAddress.GetHashCode();
public static bool operator ==(LidgrenAddress a, LidgrenAddress b)
{
var addressA = a.NetAddress.MapToIPv6();
var addressB = b.NetAddress.MapToIPv6();
if (IPAddress.IsLoopback(addressA) && IPAddress.IsLoopback(addressB)) { return true; }
return addressA.Equals(addressB);
}
public static bool operator !=(LidgrenAddress a, LidgrenAddress b)
=> !(a == b);
}
}
@@ -0,0 +1,22 @@
#nullable enable
namespace Barotrauma.Networking
{
sealed class PipeAddress : Address
{
public override string StringRepresentation => "PIPE";
public override bool IsLocalHost => true;
public override bool Equals(object? obj)
=> obj is PipeAddress;
public override int GetHashCode() => 1;
public static bool operator ==(PipeAddress a, PipeAddress b)
=> true;
public static bool operator !=(PipeAddress a, PipeAddress b)
=> !(a == b);
}
}
@@ -0,0 +1,37 @@
#nullable enable
namespace Barotrauma.Networking
{
sealed class SteamP2PAddress : Address
{
public readonly SteamId SteamId;
public override string StringRepresentation => SteamId.StringRepresentation;
public override bool IsLocalHost => false;
public SteamP2PAddress(SteamId steamId)
{
SteamId = steamId;
}
public new static Option<SteamP2PAddress> Parse(string endpointStr)
=> SteamId.Parse(endpointStr).Select(steamId => new SteamP2PAddress(steamId));
public override bool Equals(object? obj)
=> obj switch
{
SteamP2PAddress otherAddress => this == otherAddress,
_ => false
};
public override int GetHashCode()
=> SteamId.GetHashCode();
public static bool operator ==(SteamP2PAddress a, SteamP2PAddress b)
=> a.SteamId == b.SteamId;
public static bool operator !=(SteamP2PAddress a, SteamP2PAddress b)
=> !(a == b);
}
}
@@ -0,0 +1,16 @@
#nullable enable
namespace Barotrauma.Networking
{
sealed class UnknownAddress : Address
{
public override string StringRepresentation => "Hidden";
public override bool IsLocalHost => false;
public override bool Equals(object? obj)
=> ReferenceEquals(obj, this);
public override int GetHashCode() => 1;
}
}
@@ -0,0 +1,33 @@
#nullable enable
namespace Barotrauma.Networking
{
abstract class Endpoint
{
public abstract string StringRepresentation { get; }
public abstract LocalizedString ServerTypeString { get; }
public readonly Address Address;
public Endpoint(Address address)
{
Address = address;
}
public abstract override bool Equals(object? obj);
public abstract override int GetHashCode();
public override string ToString() => StringRepresentation;
public static Option<Endpoint> Parse(string str)
=> ReflectionUtils.ParseDerived<Endpoint>(str);
public static bool operator ==(Endpoint a, Endpoint b)
=> a.Equals(b);
public static bool operator !=(Endpoint a, Endpoint b)
=> !(a == b);
}
}
@@ -0,0 +1,63 @@
#nullable enable
using System.Linq;
using System.Net;
namespace Barotrauma.Networking
{
sealed class LidgrenEndpoint : Endpoint
{
public readonly IPEndPoint NetEndpoint;
public int Port => NetEndpoint.Port;
public override string StringRepresentation
=> NetEndpoint.ToString();
public override LocalizedString ServerTypeString { get; } = TextManager.Get("DedicatedServer");
public LidgrenEndpoint(IPAddress address, int port) : this(new IPEndPoint(address, port)) { }
public LidgrenEndpoint(IPEndPoint netEndpoint) : base(new LidgrenAddress(netEndpoint.Address))
{
NetEndpoint = netEndpoint;
}
public new static Option<LidgrenEndpoint> Parse(string endpointStr)
{
if (IPEndPoint.TryParse(endpointStr, out IPEndPoint? netEndpoint))
{
return Option<LidgrenEndpoint>.Some(new LidgrenEndpoint(netEndpoint!));
}
if (endpointStr.Count(c => c == ':') == 1)
{
string[] split = endpointStr.Split(':');
string hostName = split[0];
if (LidgrenAddress.Parse(hostName).TryUnwrap(out var adr)
&& int.TryParse(split[1], out var port))
{
return Option<LidgrenEndpoint>.Some(new LidgrenEndpoint(adr.NetAddress, port));
}
}
return LidgrenAddress.Parse(endpointStr)
.Select(adr => new LidgrenEndpoint(adr.NetAddress, NetConfig.DefaultPort));
}
public override bool Equals(object? obj)
=> obj switch
{
LidgrenEndpoint otherEndpoint => this == otherEndpoint,
_ => false
};
public override int GetHashCode()
=> NetEndpoint.GetHashCode();
public static bool operator ==(LidgrenEndpoint a, LidgrenEndpoint b)
=> a.Address.Equals(b.Address) && a.Port == b.Port;
public static bool operator !=(LidgrenEndpoint a, LidgrenEndpoint b)
=> !(a == b);
}
}
@@ -0,0 +1,37 @@
#nullable enable
namespace Barotrauma.Networking
{
sealed class SteamP2PEndpoint : Endpoint
{
public readonly SteamId SteamId;
public override string StringRepresentation => SteamId.StringRepresentation;
public override LocalizedString ServerTypeString { get; } = TextManager.Get("SteamP2PServer");
public SteamP2PEndpoint(SteamId steamId) : base(new SteamP2PAddress(steamId))
{
SteamId = steamId;
}
public new static Option<SteamP2PEndpoint> Parse(string endpointStr)
=> SteamId.Parse(endpointStr).Select(steamId => new SteamP2PEndpoint(steamId));
public override bool Equals(object? obj)
=> obj switch
{
SteamP2PEndpoint otherEndpoint => this == otherEndpoint,
_ => false
};
public override int GetHashCode()
=> SteamId.GetHashCode();
public static bool operator ==(SteamP2PEndpoint a, SteamP2PEndpoint b)
=> a.SteamId == b.SteamId;
public static bool operator !=(SteamP2PEndpoint a, SteamP2PEndpoint b)
=> !(a == b);
}
}
@@ -4,11 +4,12 @@ using System.Text;
namespace Barotrauma.Networking
{
public interface IReadMessage
interface IReadMessage
{
bool ReadBoolean();
void ReadPadBits();
byte ReadByte();
byte PeekByte();
UInt16 ReadUInt16();
Int16 ReadInt16();
UInt32 ReadUInt32();
@@ -2,7 +2,7 @@
namespace Barotrauma.Networking
{
public interface IWriteMessage
interface IWriteMessage
{
void Write(bool val);
void WritePadBits();
@@ -11,7 +11,7 @@ namespace Barotrauma.Networking
{
public static class MsgConstants
{
public const int MTU = 1200;
public const int MTU = 1200; //TODO: determine dynamically
public const int CompressionThreshold = 1000;
public const int InitialBufferSize = 256;
public const int BufferOverAllocateAmount = 4;
@@ -254,6 +254,12 @@ namespace Barotrauma.Networking
return retval;
}
internal static byte PeekByte(byte[] buf, ref int bitPos)
{
byte retval = NetBitWriter.ReadByte(buf, 8, bitPos);
return retval;
}
internal static UInt16 ReadUInt16(byte[] buf, ref int bitPos)
{
uint retval = NetBitWriter.ReadUInt16(buf, 16, bitPos);
@@ -409,7 +415,7 @@ namespace Barotrauma.Networking
}
}
public class WriteOnlyMessage : IWriteMessage
class WriteOnlyMessage : IWriteMessage
{
private byte[] buf = new byte[MsgConstants.InitialBufferSize];
private int seekPos = 0;
@@ -602,9 +608,9 @@ namespace Barotrauma.Networking
}
}
public class ReadOnlyMessage : IReadMessage
class ReadOnlyMessage : IReadMessage
{
private byte[] buf;
private readonly byte[] buf;
private int seekPos = 0;
private int lengthBits = 0;
@@ -720,6 +726,11 @@ namespace Barotrauma.Networking
return MsgReader.ReadByte(buf, ref seekPos);
}
public byte PeekByte()
{
return MsgReader.PeekByte(buf, ref seekPos);
}
public UInt16 ReadUInt16()
{
return MsgReader.ReadUInt16(buf, ref seekPos);
@@ -801,7 +812,7 @@ namespace Barotrauma.Networking
}
}
public class ReadWriteMessage : IWriteMessage, IReadMessage
class ReadWriteMessage : IWriteMessage, IReadMessage
{
private byte[] buf;
private int seekPos = 0;
@@ -983,6 +994,11 @@ namespace Barotrauma.Networking
return MsgReader.ReadByte(buf, ref seekPos);
}
public byte PeekByte()
{
return MsgReader.PeekByte(buf, ref seekPos);
}
public UInt16 ReadUInt16()
{
return MsgReader.ReadUInt16(buf, ref seekPos);
@@ -1067,5 +1083,6 @@ namespace Barotrauma.Networking
{
throw new InvalidOperationException("ReadWriteMessages are not to be sent");
}
}
}
@@ -1,55 +1,14 @@
using System;
using System.Net;
using Lidgren.Network;
using Lidgren.Network;
namespace Barotrauma.Networking
{
public class LidgrenConnection : NetworkConnection
sealed class LidgrenConnection : NetworkConnection
{
public NetConnection NetConnection { get; private set; }
public readonly NetConnection NetConnection;
public IPEndPoint IPEndPoint => NetConnection.RemoteEndPoint;
public string IPString
public LidgrenConnection(NetConnection netConnection) : base(new LidgrenEndpoint(netConnection.RemoteEndPoint))
{
get
{
return IPEndPoint.Address.IsIPv4MappedToIPv6 ? IPEndPoint.Address.MapToIPv4NoThrow().ToString() : IPEndPoint.Address.ToString();
}
}
public UInt16 Port
{
get
{
return (UInt16)IPEndPoint.Port;
}
}
public LidgrenConnection(string name, NetConnection netConnection, UInt64 steamId)
{
Name = name;
NetConnection = netConnection;
SteamID = steamId;
EndPointString = IPString;
}
public override bool SetSteamIDIfUnknown(UInt64 id)
{
if (SteamID != 0) { return false; } //do not allow the SteamID to be set multiple times
SteamID = id;
return true;
}
public override bool EndpointMatches(string endPoint)
{
if (IPEndPoint?.Address == null) { return false; }
if (!IPAddress.TryParse(endPoint, out IPAddress addr)) { return false; }
IPAddress ip1 = IPEndPoint.Address.IsIPv4MappedToIPv6 ? IPEndPoint.Address.MapToIPv4() : IPEndPoint.Address;
IPAddress ip2 = addr.IsIPv4MappedToIPv6 ? addr.MapToIPv4() : addr;
return ip1.ToString() == ip2.ToString();
}
}
}
@@ -8,57 +8,37 @@ namespace Barotrauma.Networking
Disconnected = 0x2
}
public abstract class NetworkConnection
abstract class NetworkConnection
{
public const double TimeoutThreshold = 60.0; //full minute for timeout because loading screens can take quite a while
public const double TimeoutThresholdInGame = 10.0;
public string Name;
public AccountInfo AccountInfo { get; private set; } = AccountInfo.None;
public UInt64 SteamID
{
get;
protected set;
}
public UInt64 OwnerSteamID
{
get;
protected set;
}
public string EndPointString
{
get;
protected set;
}
public readonly Endpoint Endpoint;
[Obsolete("TODO: this doesn't belong in layer 1")]
public LanguageIdentifier Language
{
get; set;
}
public abstract bool EndpointMatches(string endPoint);
public NetworkConnection(Endpoint endpoint)
{
Endpoint = endpoint;
}
public bool EndpointMatches(Endpoint endPoint)
=> Endpoint == endPoint;
public NetworkConnectionStatus Status = NetworkConnectionStatus.Disconnected;
public virtual bool SetSteamIDIfUnknown(UInt64 id)
public void SetAccountInfo(AccountInfo newInfo)
{
//by default, don't allow setting the ID, this is only done
//with Lidgren connections since those are initialized before
//the SteamID can be known; it's set once the Steam auth ticket
//is received by the server.
return false;
}
public bool SetOwnerSteamIDIfUnknown(UInt64 id)
{
//we know that for both Lidgren and SteamP2P, the
//owner id isn't known until the auth ticket is
//processed, so this method is the same for both
if (OwnerSteamID != 0) { return false; }
OwnerSteamID = id;
return true;
AccountInfo = newInfo;
}
public sealed override string ToString()
=> Endpoint.StringRepresentation;
}
}
@@ -1,19 +1,33 @@
using Barotrauma.Steam;
#nullable enable
using System;
namespace Barotrauma.Networking
{
public class PipeConnection : NetworkConnection
sealed class PipeEndpoint : Endpoint
{
public PipeConnection(ulong steamId)
{
EndPointString = "PIPE";
SteamID = steamId;
}
public override string StringRepresentation => "PIPE";
public override LocalizedString ServerTypeString => throw new InvalidOperationException();
public override bool EndpointMatches(string endPoint)
public PipeEndpoint() : base(new PipeAddress()) { }
public override bool Equals(object? obj)
=> obj is PipeEndpoint;
public override int GetHashCode() => 1;
public static bool operator ==(PipeEndpoint a, PipeEndpoint b)
=> true;
public static bool operator !=(PipeEndpoint a, PipeEndpoint b)
=> !(a == b);
}
sealed class PipeConnection : NetworkConnection
{
public PipeConnection(AccountId accountId) : base(new PipeEndpoint())
{
return SteamManager.SteamIDStringToUInt64(endPoint) == SteamID || endPoint == "PIPE";
SetAccountInfo(new AccountInfo(Option<AccountId>.Some(accountId)));
}
}
}
@@ -1,18 +1,13 @@
using Barotrauma.Steam;
using System;
namespace Barotrauma.Networking
namespace Barotrauma.Networking
{
public class SteamP2PConnection : NetworkConnection
sealed class SteamP2PConnection : NetworkConnection
{
public double Timeout = 0.0;
public SteamP2PConnection(string name, UInt64 steamId)
public SteamP2PConnection(SteamId steamId) : this(new SteamP2PEndpoint(steamId)) { }
public SteamP2PConnection(SteamP2PEndpoint endpoint) : base(endpoint)
{
SteamID = steamId;
OwnerSteamID = 0;
EndPointString = SteamManager.SteamIDUInt64ToString(SteamID);
Name = name;
Heartbeat();
}
@@ -25,10 +20,5 @@ namespace Barotrauma.Networking
{
Timeout = TimeoutThreshold;
}
public override bool EndpointMatches(string endPoint)
{
return SteamManager.SteamIDStringToUInt64(endPoint) == SteamID;
}
}
}
@@ -269,6 +269,7 @@ namespace Barotrauma.Networking
#endif
}
}
respawnItems.Clear();
foreach (Structure wall in Structure.WallList)
{
@@ -70,27 +70,18 @@ namespace Barotrauma.Networking
public class SavedClientPermission
{
public readonly string EndPoint;
public readonly ulong SteamID;
public readonly Either<Address, AccountId> AddressOrAccountId;
public readonly string Name;
public HashSet<DebugConsole.Command> PermittedCommands;
public readonly ImmutableHashSet<DebugConsole.Command> PermittedCommands;
public ClientPermissions Permissions;
public readonly ClientPermissions Permissions;
public SavedClientPermission(string name, string endpoint, ClientPermissions permissions, HashSet<DebugConsole.Command> permittedCommands)
public SavedClientPermission(string name, Either<Address, AccountId> addressOrAccountId, ClientPermissions permissions, IEnumerable<DebugConsole.Command> permittedCommands)
{
this.Name = name;
this.EndPoint = endpoint;
this.AddressOrAccountId = addressOrAccountId;
this.Permissions = permissions;
this.PermittedCommands = permittedCommands;
}
public SavedClientPermission(string name, ulong steamID, ClientPermissions permissions, HashSet<DebugConsole.Command> permittedCommands)
{
this.Name = name;
this.SteamID = steamID;
this.Permissions = permissions;
this.PermittedCommands = permittedCommands;
this.PermittedCommands = permittedCommands.ToImmutableHashSet();
}
}
@@ -279,7 +270,6 @@ namespace Barotrauma.Networking
{
ServerLog = new ServerLog(serverName);
Whitelist = new WhiteList();
BanList = new BanList();
ExtraCargo = new Dictionary<ItemPrefab, int>();
@@ -398,8 +388,6 @@ namespace Barotrauma.Networking
public List<SavedClientPermission> ClientPermissions { get; private set; } = new List<SavedClientPermission>();
public WhiteList Whitelist { get; private set; }
[Serialize(20, IsPropertySaveable.Yes)]
public int TickRate
{
@@ -1,12 +1,8 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
namespace Barotrauma.Networking
{
public class VoipQueue : IDisposable
class VoipQueue : IDisposable
{
public const int BUFFER_COUNT = 8;
protected int[] bufferLengths;
@@ -8,7 +8,7 @@ namespace Barotrauma
{
public enum VoteState { None = 0, Started = 1, Running = 2, Passed = 3, Failed = 4 };
private IReadOnlyDictionary<T, int> GetVoteCounts<T>(VoteType voteType, List<Client> voters)
private IReadOnlyDictionary<T, int> GetVoteCounts<T>(VoteType voteType, IEnumerable<Client> voters)
{
Dictionary<T, int> voteList = new Dictionary<T, int>();
@@ -57,7 +57,7 @@ namespace Barotrauma
return selected;
}
public void ResetVotes(List<Client> connectedClients)
public void ResetVotes(IEnumerable<Client> connectedClients)
{
foreach (Client client in connectedClients)
{