Merge branch 'dev' of https://github.com/Regalis11/Barotrauma.git into unstable-tests

This commit is contained in:
Evil Factory
2022-04-08 12:52:28 -03:00
990 changed files with 44338 additions and 38589 deletions
@@ -56,9 +56,9 @@ namespace Barotrauma.Networking
{
if (Type.HasFlag(ChatMessageType.Server) || Type.HasFlag(ChatMessageType.Error) || Type.HasFlag(ChatMessageType.ServerLog))
{
if (translatedText == null || translatedText.Length == 0)
if (translatedText.IsNullOrEmpty())
{
translatedText = TextManager.GetServerMessage(Text);
translatedText = TextManager.GetServerMessage(Text).Value;
}
return translatedText;
@@ -214,8 +214,12 @@ namespace Barotrauma.Networking
public static string ApplyDistanceEffect(string message, ChatMessageType type, Character sender, Character receiver)
{
if (sender == null) { return ""; }
string spokenMsg = ApplyDistanceEffect(receiver, sender, message, SpeakRange * (1.0f - sender.SpeechImpediment / 100.0f), 3.0f);
float range = SpeakRange;
if (type == ChatMessageType.Default && sender.SpeechImpediment > 0)
{
range *= 1.0f - sender.SpeechImpediment / 100.0f;
}
string spokenMsg = ApplyDistanceEffect(receiver, sender, message, range, 3.0f);
switch (type)
{
@@ -1,6 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Barotrauma.Extensions;
@@ -18,6 +19,13 @@ namespace Barotrauma.Networking
private static PipeType writeStream;
private static PipeType readStream;
private enum WriteStatus : byte
{
Success = 0x00,
Heartbeat = 0x01,
Crash = 0xFF
}
private static ManualResetEvent writeManualResetEvent;
private static volatile bool shutDown;
@@ -29,6 +37,8 @@ namespace Barotrauma.Networking
private static int readIncTotal;
private static ConcurrentQueue<byte[]> msgsToWrite;
private static ConcurrentQueue<string> errorsToWrite;
private static ConcurrentQueue<byte[]> msgsToRead;
private static Thread readThread;
@@ -44,6 +54,8 @@ namespace Barotrauma.Networking
readTempBytes = new byte[ReadBufferSize];
msgsToWrite = new ConcurrentQueue<byte[]>();
errorsToWrite = new ConcurrentQueue<string>();
msgsToRead = new ConcurrentQueue<byte[]>();
shutDown = false;
@@ -127,9 +139,11 @@ namespace Barotrauma.Networking
}
}
static partial void HandleCrashString(string str);
private static void UpdateRead()
{
Span<byte> msgLengthSpan = stackalloc byte[2];
Span<byte> msgLengthSpan = stackalloc byte[3];
while (!shutDown)
{
CheckPipeConnected(nameof(readStream), readStream);
@@ -154,13 +168,26 @@ namespace Barotrauma.Networking
if (!readBytes(msgLengthSpan)) { shutDown = true; break; }
int msgLength = msgLengthSpan[0] | (msgLengthSpan[1] << 8);
WriteStatus writeStatus = (WriteStatus)msgLengthSpan[2];
if (msgLength > 0)
{
byte[] msg = new byte[msgLength];
if (!readBytes(msg.AsSpan())) { shutDown = true; break; }
msgsToRead.Enqueue(msg);
switch (writeStatus)
{
case WriteStatus.Success:
msgsToRead.Enqueue(msg);
break;
case WriteStatus.Heartbeat:
//do nothing
break;
case WriteStatus.Crash:
HandleCrashString(Encoding.UTF8.GetString(msg));
shutDown = true;
break;
}
}
Thread.Yield();
@@ -173,9 +200,9 @@ namespace Barotrauma.Networking
{
CheckPipeConnected(nameof(writeStream), writeStream);
bool msgAvailable; byte[] msg;
byte[] msg;
void writeMsg()
void writeMsg(WriteStatus writeStatus)
{
// It's SUPER IMPORTANT that this stack allocation
// remains in this local function and is never inlined,
@@ -183,11 +210,12 @@ namespace Barotrauma.Networking
// when the function returns; placing it in the loop
// this method is based around would lead to a stack
// overflow real quick!
Span<byte> bytesToWrite = stackalloc byte[2 + msg.Length];
Span<byte> bytesToWrite = stackalloc byte[3 + msg.Length];
bytesToWrite[0] = (byte)(msg.Length & 0xFF);
bytesToWrite[1] = (byte)((msg.Length >> 8) & 0xFF);
Span<byte> msgSlice = bytesToWrite.Slice(2, msg.Length);
bytesToWrite[2] = (byte)writeStatus;
Span<byte> msgSlice = bytesToWrite.Slice(3, msg.Length);
msg.AsSpan().CopyTo(msgSlice);
@@ -209,15 +237,20 @@ namespace Barotrauma.Networking
}
}
msgAvailable = msgsToWrite.TryDequeue(out msg);
while (msgAvailable)
while (errorsToWrite.TryDequeue(out var error))
{
writeMsg();
msg = Encoding.UTF8.GetBytes(error);
writeMsg(WriteStatus.Crash);
shutDown = true;
}
while (msgsToWrite.TryDequeue(out msg))
{
writeMsg(WriteStatus.Success);
if (shutDown) { break; }
msgAvailable = msgsToWrite.TryDequeue(out msg);
}
if (!shutDown)
{
writeManualResetEvent.Reset();
@@ -226,7 +259,7 @@ namespace Barotrauma.Networking
if (shutDown) { return; }
//heartbeat to keep the other end alive
msg = Array.Empty<byte>(); writeMsg();
msg = Array.Empty<byte>(); writeMsg(WriteStatus.Heartbeat);
}
}
}
@@ -6,6 +6,25 @@ using System.Linq;
namespace Barotrauma.Networking
{
[NetworkSerialize]
struct TempClient : INetSerializableStruct
{
public string Name;
public Identifier PreferredJob;
public CharacterTeamType PreferredTeam;
public UInt16 NameID;
public UInt64 SteamID;
public byte ID;
public UInt16 CharacterID;
public float Karma;
public bool Muted;
public bool InGame;
public bool HasPermissions;
public bool IsOwner;
public bool AllowKicking;
public bool IsDownloading;
}
partial class Client : IDisposable
{
public const int MaxNameLength = 32;
@@ -15,11 +34,11 @@ namespace Barotrauma.Networking
public UInt64 SteamID;
public UInt64 OwnerSteamID;
public string Language;
public LanguageIdentifier Language;
public UInt16 Ping;
public string PreferredJob;
public Identifier PreferredJob;
public CharacterTeamType TeamID;
@@ -120,7 +139,7 @@ namespace Barotrauma.Networking
}
}
public bool HasPermissions = false;
public bool HasPermissions => Permissions != ClientPermissions.None;
public VoipQueue VoipQueue
{
@@ -148,7 +167,7 @@ namespace Barotrauma.Networking
private List<Client> kickVoters;
public HashSet<string> GivenAchievements = new HashSet<string>();
public HashSet<Identifier> GivenAchievements = new HashSet<Identifier>();
public ClientPermissions Permissions = ClientPermissions.None;
public List<DebugConsole.Command> PermittedConsoleCommands
@@ -22,27 +22,28 @@ namespace Barotrauma.Networking
ManageSettings = 0x200,
ManagePermissions = 0x400,
KarmaImmunity = 0x800,
BuyItems = 0x1000,
ManageMoney = 0x1000,
SellInventoryItems = 0x2000,
SellSubItems = 0x4000,
CampaignStore = 0x8000,
All = 0xFFFF
ManageMap = 0x8000,
ManageHires = 0x10000,
All = 0x1FFFF
}
class PermissionPreset
{
public static List<PermissionPreset> List = new List<PermissionPreset>();
public readonly string Name;
public readonly string Description;
public readonly LocalizedString Name;
public readonly LocalizedString Description;
public readonly ClientPermissions Permissions;
public readonly List<DebugConsole.Command> PermittedCommands;
public PermissionPreset(XElement element)
{
string name = element.GetAttributeString("name", "");
Name = TextManager.Get("permissionpresetname." + name, true) ?? name;
Description = TextManager.Get("permissionpresetdescription." + name, true) ?? element.GetAttributeString("description", "");
Name = TextManager.Get("permissionpresetname." + name).Fallback(name);
Description = TextManager.Get("permissionpresetdescription." + name) .Fallback(element.GetAttributeString("description", ""));
string permissionsStr = element.GetAttributeString("permissions", "");
if (!Enum.TryParse(permissionsStr, out Permissions))
@@ -53,7 +54,7 @@ namespace Barotrauma.Networking
PermittedCommands = new List<DebugConsole.Command>();
if (Permissions.HasFlag(ClientPermissions.ConsoleCommands))
{
foreach (XElement subElement in element.Elements())
foreach (var subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("command", StringComparison.OrdinalIgnoreCase)) { continue; }
string commandName = subElement.GetAttributeString("name", "");
@@ -5,6 +5,7 @@ using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using Steamworks.ServerList;
namespace Barotrauma
{
@@ -117,7 +118,7 @@ namespace Barotrauma
class CharacterSpawnInfo : IEntitySpawnInfo
{
public readonly string identifier;
public readonly Identifier Identifier;
public readonly CharacterInfo CharacterInfo;
public readonly Vector2 Position;
@@ -125,30 +126,32 @@ namespace Barotrauma
private readonly Action<Character> onSpawned;
public CharacterSpawnInfo(string identifier, Vector2 worldPosition, Action<Character> onSpawn = null)
public CharacterSpawnInfo(Identifier identifier, Vector2 worldPosition, Action<Character> onSpawn = null)
{
this.identifier = identifier ?? throw new ArgumentException("ItemSpawnInfo prefab cannot be null.");
this.Identifier = identifier;
if (identifier.IsEmpty) { throw new ArgumentException($"{nameof(CharacterSpawnInfo)} identifier cannot be null."); }
Position = worldPosition;
this.onSpawned = onSpawn;
}
public CharacterSpawnInfo(string identifier, Vector2 position, Submarine sub, Action<Character> onSpawn = null)
public CharacterSpawnInfo(Identifier identifier, Vector2 position, Submarine sub, Action<Character> onSpawn = null)
{
this.identifier = identifier ?? throw new ArgumentException("ItemSpawnInfo prefab cannot be null.");
this.Identifier = identifier;
if (identifier.IsEmpty) { throw new ArgumentException($"{nameof(CharacterSpawnInfo)} identifier cannot be null."); }
Position = position;
Submarine = sub;
this.onSpawned = onSpawn;
}
public CharacterSpawnInfo(string identifier, Vector2 position, CharacterInfo characterInfo, Action<Character> onSpawn = null) : this (identifier, position, onSpawn)
public CharacterSpawnInfo(Identifier identifier, Vector2 position, CharacterInfo characterInfo, Action<Character> onSpawn = null) : this (identifier, position, onSpawn)
{
CharacterInfo = characterInfo;
}
public Entity Spawn()
{
var character = string.IsNullOrEmpty(identifier) ? null :
Character.Create(identifier,
var character = Identifier.IsEmpty ? null :
Character.Create(Identifier,
Submarine == null ? Position : Submarine.Position + Position,
ToolBox.RandomSeed(8), CharacterInfo, createNetworkEvent: false);
return character;
@@ -194,49 +197,60 @@ namespace Barotrauma
private readonly Queue<IEntitySpawnInfo> spawnQueue;
private readonly Queue<Entity> removeQueue;
public class SpawnOrRemove
public abstract class SpawnOrRemove : NetEntityEvent.IData
{
public readonly Entity Entity;
public UInt16 ID => Entity.ID;
public readonly UInt16 InventoryID;
public readonly UInt16 OriginalID, OriginalInventoryID;
public readonly byte OriginalItemContainerIndex;
public readonly bool Remove = false;
public readonly byte ItemContainerIndex;
public readonly int SlotIndex;
public override string ToString()
{
return
(Remove ? "Remove" : "Spawn") + "(" +
"(" +
((Entity as MapEntity)?.Name ?? "[NULL]") +
$", {OriginalID}, {OriginalInventoryID})";
$", {ID}, {InventoryID}, {SlotIndex})";
}
public SpawnOrRemove(Entity entity, bool remove)
protected SpawnOrRemove(Entity entity)
{
Entity = entity;
OriginalID = entity.ID;
if (entity is Item item && item.ParentInventory?.Owner != null)
if (!(entity is Item { ParentInventory: { Owner: { } } } item)) { return; }
InventoryID = item.ParentInventory.Owner.ID;
SlotIndex = item.ParentInventory.FindIndex(item);
//find the index of the ItemContainer this item is inside to get the item to
//spawn in the correct inventory in multi-inventory items like fabricators
if (item.Container == null) { return; }
foreach (ItemComponent component in item.Container.Components)
{
OriginalInventoryID = item.ParentInventory.Owner.ID;
//find the index of the ItemContainer this item is inside to get the item to
//spawn in the correct inventory in multi-inventory items like fabricators
if (item.Container != null)
if (component is ItemContainer container &&
container.Inventory == item.ParentInventory)
{
foreach (ItemComponent component in item.Container.Components)
{
if (component is ItemContainer container &&
container.Inventory == item.ParentInventory)
{
OriginalItemContainerIndex = (byte)item.Container.GetComponentIndex(component);
break;
}
}
ItemContainerIndex = (byte)item.Container.GetComponentIndex(component);
break;
}
}
Remove = remove;
}
}
public sealed class SpawnEntity : SpawnOrRemove
{
public SpawnEntity(Entity entity) : base(entity) { }
public override string ToString()
=> $"Spawn {base.ToString()}";
}
public sealed class RemoveEntity : SpawnOrRemove
{
public RemoveEntity(Entity entity) : base(entity) { }
public override string ToString()
=> $"Remove {base.ToString()}";
}
public EntitySpawner()
: base(null, Entity.EntitySpawnerID)
@@ -250,7 +264,7 @@ namespace Barotrauma
return "EntitySpawner";
}
public void AddToSpawnQueue(ItemPrefab itemPrefab, Vector2 worldPosition, float? condition = null, int? quality = null, Action<Item> onSpawned = null)
public void AddItemToSpawnQueue(ItemPrefab itemPrefab, Vector2 worldPosition, float? condition = null, int? quality = null, Action<Item> onSpawned = null)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (itemPrefab == null)
@@ -263,7 +277,7 @@ namespace Barotrauma
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, worldPosition, onSpawned, condition, quality));
}
public void AddToSpawnQueue(ItemPrefab itemPrefab, Vector2 position, Submarine sub, float? condition = null, int? quality = null, Action<Item> onSpawned = null)
public void AddItemToSpawnQueue(ItemPrefab itemPrefab, Vector2 position, Submarine sub, float? condition = null, int? quality = null, Action<Item> onSpawned = null)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (itemPrefab == null)
@@ -276,7 +290,7 @@ namespace Barotrauma
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, position, sub, onSpawned, condition, quality));
}
public void AddToSpawnQueue(ItemPrefab itemPrefab, Inventory inventory, float? condition = null, int? quality = null, Action<Item> onSpawned = null, bool spawnIfInventoryFull = true, bool ignoreLimbSlots = false, InvSlotType slot = InvSlotType.None)
public void AddItemToSpawnQueue(ItemPrefab itemPrefab, Inventory inventory, float? condition = null, int? quality = null, Action<Item> onSpawned = null, bool spawnIfInventoryFull = true, bool ignoreLimbSlots = false, InvSlotType slot = InvSlotType.None)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (itemPrefab == null)
@@ -294,10 +308,10 @@ namespace Barotrauma
});
}
public void AddToSpawnQueue(string speciesName, Vector2 worldPosition, Action<Character> onSpawn = null)
public void AddCharacterToSpawnQueue(Identifier speciesName, Vector2 worldPosition, Action<Character> onSpawn = null)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (string.IsNullOrEmpty(speciesName))
if (speciesName.IsEmpty)
{
string errorMsg = "Attempted to add an empty/null species name to entity spawn queue.\n" + Environment.StackTrace.CleanupStackTrace();
DebugConsole.ThrowError(errorMsg);
@@ -307,10 +321,10 @@ namespace Barotrauma
spawnQueue.Enqueue(new CharacterSpawnInfo(speciesName, worldPosition, onSpawn));
}
public void AddToSpawnQueue(string speciesName, Vector2 position, Submarine sub, Action<Character> onSpawn = null)
public void AddCharacterToSpawnQueue(Identifier speciesName, Vector2 position, Submarine sub, Action<Character> onSpawn = null)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (string.IsNullOrEmpty(speciesName))
if (speciesName.IsEmpty)
{
string errorMsg = "Attempted to add an empty/null species name to entity spawn queue.\n" + Environment.StackTrace.CleanupStackTrace();
DebugConsole.ThrowError(errorMsg);
@@ -320,10 +334,10 @@ namespace Barotrauma
spawnQueue.Enqueue(new CharacterSpawnInfo(speciesName, position, sub, onSpawn));
}
public void AddToSpawnQueue(string speciesName, Vector2 worldPosition, CharacterInfo characterInfo, Action<Character> onSpawn = null)
public void AddCharacterToSpawnQueue(Identifier speciesName, Vector2 worldPosition, CharacterInfo characterInfo, Action<Character> onSpawn = null)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (string.IsNullOrEmpty(speciesName))
if (speciesName.IsEmpty)
{
string errorMsg = "Attempted to add an empty/null species name to entity spawn queue.\n" + Environment.StackTrace.CleanupStackTrace();
DebugConsole.ThrowError(errorMsg);
@@ -333,10 +347,11 @@ namespace Barotrauma
spawnQueue.Enqueue(new CharacterSpawnInfo(speciesName, worldPosition, characterInfo, onSpawn));
}
public void AddToRemoveQueue(Entity entity)
public void AddEntityToRemoveQueue(Entity entity)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (removeQueue.Contains(entity) || entity.Removed || entity == null || entity.IdFreed) { return; }
if (entity is Item item) { AddItemToRemoveQueue(item); return; }
if (entity is Character)
{
Character character = entity as Character;
@@ -352,7 +367,7 @@ namespace Barotrauma
removeQueue.Enqueue(entity);
}
public void AddToRemoveQueue(Item item)
public void AddItemToRemoveQueue(Item item)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (removeQueue.Contains(item) || item.Removed) { return; }
@@ -364,7 +379,7 @@ namespace Barotrauma
{
if (containedItem != null)
{
AddToRemoveQueue(containedItem);
AddItemToRemoveQueue(containedItem);
}
}
}
@@ -392,20 +407,19 @@ namespace Barotrauma
public void Update(bool createNetworkEvents = true)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (GameMain.NetworkMember is { IsClient: true }) { return; }
while (spawnQueue.Count > 0)
{
var entitySpawnInfo = spawnQueue.Dequeue();
var spawnedEntity = entitySpawnInfo.Spawn();
if (spawnedEntity != null)
{
if (createNetworkEvents)
{
CreateNetworkEventProjSpecific(spawnedEntity, false);
}
entitySpawnInfo.OnSpawned(spawnedEntity);
if (spawnedEntity == null) { continue; }
if (createNetworkEvents)
{
CreateNetworkEventProjSpecific(new SpawnEntity(spawnedEntity));
}
entitySpawnInfo.OnSpawned(spawnedEntity);
}
while (removeQueue.Count > 0)
@@ -417,13 +431,13 @@ namespace Barotrauma
}
if (createNetworkEvents)
{
CreateNetworkEventProjSpecific(removedEntity, true);
CreateNetworkEventProjSpecific(new RemoveEntity(removedEntity));
}
removedEntity.Remove();
}
}
partial void CreateNetworkEventProjSpecific(Entity entity, bool remove);
partial void CreateNetworkEventProjSpecific(SpawnOrRemove spawnOrRemove);
public void Reset()
{
@@ -12,6 +12,6 @@
enum FileTransferType
{
Submarine, CampaignSave
Submarine, CampaignSave, Mod
}
}
@@ -3,28 +3,41 @@
interface INetSerializable { }
/// <summary>
/// Interface for entities that the clients can send information of to the server
/// Interface for entities that the clients can send events to the server
/// </summary>
interface IClientSerializable : INetSerializable
{
#if CLIENT
void ClientWrite(IWriteMessage msg, object[] extraData = null);
void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null);
#endif
#if SERVER
void ServerRead(ClientNetObject type, IReadMessage msg, Client c);
void ServerEventRead(IReadMessage msg, Client c);
#endif
}
/// <summary>
/// Interface for entities that the server can send information of to the clients
/// Interface for entities that the server can send events to the clients
/// </summary>
interface IServerSerializable : INetSerializable
{
#if SERVER
void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null);
void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null);
#endif
#if CLIENT
void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime);
void ClientEventRead(IReadMessage msg, float sendingTime);
#endif
}
/// <summary>
/// Interface for entities that handle ServerNetObject.ENTITY_POSITION
/// </summary>
interface IServerPositionSync : IServerSerializable
{
#if SERVER
void ServerWritePosition(IWriteMessage msg, Client c);
#endif
#if CLIENT
void ClientReadPosition(IReadMessage msg, float sendingTime);
#endif
}
}
@@ -5,6 +5,8 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
@@ -42,6 +44,13 @@ namespace Barotrauma
public int NumberOfBits = 8;
public bool IncludeColorAlpha = false;
public int ArrayMaxSize = ushort.MaxValue;
public readonly int OrderKey;
public NetworkSerialize([CallerLineNumber] int lineNumber = 0)
{
OrderKey = lineNumber;
}
}
/// <summary>
@@ -52,6 +61,7 @@ namespace Barotrauma
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;
@@ -64,6 +74,58 @@ namespace Barotrauma
}
}
public readonly struct CachedReflectedVariable
{
public delegate object? GetValueDelegate(object? obj);
public delegate void SetValueDelegate(object? obj, object? value);
public readonly Type Type;
public readonly ReadWriteBehavior 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)
{
Behavior = behavior;
switch (info)
{
case PropertyInfo pi:
Type = pi.PropertyType;
GetValue = pi.GetValue;
SetValue = pi.SetValue;
break;
case FieldInfo fi:
Type = fi.FieldType;
GetValue = fi.GetValue;
SetValue = fi.SetValue;
break;
default:
throw new ArgumentException($"Expected {nameof(FieldInfo)} or {nameof(PropertyInfo)} but found {info.GetType()}.", nameof(info));
}
if (info.GetCustomAttribute<NetworkSerialize>() is { } ownAttriute)
{
HasOwnAttribute = true;
Attribute = ownAttriute;
}
else if (baseClassType.GetCustomAttribute<NetworkSerialize>() is { } globalAttribute)
{
HasOwnAttribute = false;
Attribute = globalAttribute;
}
else
{
throw new InvalidOperationException($"Unable to serialize \"{Type}\" in \"{baseClassType}\" because it has no {nameof(NetworkSerialize)} attribute.");
}
}
}
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) },
@@ -77,16 +139,15 @@ namespace Barotrauma
{ 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 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) },
{ type => typeof(Array).IsAssignableFrom(type.BaseType), new ReadWriteBehavior(ReadArray, WriteArray) },
// Nested INetSerializableStructs
{ type => typeof(INetSerializableStruct).IsAssignableFrom(type), new ReadWriteBehavior(ReadINetSerializableStruct, WriteINetSerializableStruct) },
@@ -94,13 +155,79 @@ namespace Barotrauma
// Enums
{ type => type.IsEnum, new ReadWriteBehavior(ReadEnum, WriteEnum) },
// Nullable / Optional types
{ type => Nullable.GetUnderlyingType(type) != null, new ReadWriteBehavior(ReadNullable, WriteNullable) }
// Nullable
{ type => Nullable.GetUnderlyingType(type) != null, new ReadWriteBehavior(ReadNullable, WriteNullable) },
// Option
{ type => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Option<>), new ReadWriteBehavior(ReadOption, WriteOption) }
}.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 readonly ReadWriteBehavior InvalidReadWriteBehavior = new ReadWriteBehavior(ReadInvalid, WriteInvalid);
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 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)
{
if (obj is null) { throw new ArgumentNullException(nameof(obj), "Tried to write 'null' into a non-nullable type"); }
Type type = obj.GetType();
Type optionType = type.GetGenericTypeDefinition();
Type underlyingType = type.GetGenericArguments()[0];
if (optionType == typeof(None<>))
{
msg.Write(false);
}
else if (optionType == typeof(Some<>))
{
msg.Write(true);
if (TryFindBehavior(underlyingType, out ReadWriteBehavior behavior))
{
behavior.WriteAction(obj.Value, attribute, msg);
}
}
else
{
throw new ArgumentOutOfRangeException(nameof(obj), "Option type was neither None or Some");
}
}
private static dynamic? ReadOption(IReadMessage inc, Type type, NetworkSerialize attribute)
{
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;
}
}
private static void WriteNullable(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
{
@@ -152,9 +279,9 @@ namespace Barotrauma
Range<int> range = GetEnumRange(type);
int enumIndex = inc.ReadRangedInteger(range.Start, range.End);
foreach (dynamic e in Enum.GetValues(type))
foreach (dynamic? e in Enum.GetValues(type))
{
if (Convert.ChangeType(e, e.GetTypeCode()) == enumIndex) { return e; }
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)}");
@@ -213,9 +340,9 @@ namespace Barotrauma
msg.WriteRangedInteger(array.Length, 0, attribute.ArrayMaxSize);
foreach (dynamic o in array)
foreach (dynamic? o in array)
{
if (TryFindBehavior(o.GetType(), out ReadWriteBehavior behavior))
if (TryFindBehavior(o!.GetType(), out ReadWriteBehavior behavior))
{
behavior.WriteAction(o, attribute, msg);
}
@@ -286,6 +413,8 @@ namespace Barotrauma
private static dynamic ReadString(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadString();
private static dynamic ReadIdentifier(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadIdentifier();
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)
@@ -345,7 +474,7 @@ namespace Barotrauma
return new Range<int>(values.Min(), values.Max());
}
public static bool TryFindBehavior(Type type, out ReadWriteBehavior behavior)
private static bool TryFindBehavior(Type type, out ReadWriteBehavior behavior)
{
if (TypeBehaviors.TryGetValue(type, out behavior)) { return true; }
@@ -361,6 +490,46 @@ namespace Barotrauma
behavior = InvalidReadWriteBehavior;
return false;
}
public static ImmutableArray<CachedReflectedVariable> GetPropertiesAndFields(Type type, Type baseClassType)
{
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);
foreach (PropertyInfo info in propertyInfos)
{
if (TryFindBehavior(info.PropertyType, out ReadWriteBehavior behavior))
{
variables.Add(new CachedReflectedVariable(info, behavior, baseClassType));
}
else
{
throw new SerializationException($"Unable to serialize type \"{type}\".");
}
}
foreach (FieldInfo info in fieldInfos)
{
if (TryFindBehavior(info.FieldType, out ReadWriteBehavior behavior))
{
variables.Add(new CachedReflectedVariable(info, behavior, baseClassType));
}
else
{
throw new SerializationException($"Unable to serialize type \"{type}\".");
}
}
ImmutableArray<CachedReflectedVariable> array = variables.All(v => v.HasOwnAttribute) ? variables.OrderBy(v => v.Attribute.OrderKey).ToImmutableArray() : variables.ToImmutableArray();
CachedVariables.Add(type, array);
return array;
bool HasAttribute(MemberInfo info) => (info.GetCustomAttribute<NetworkSerialize>() ?? baseClassType.GetCustomAttribute<NetworkSerialize>()) != null;
}
}
/// <summary>
@@ -408,8 +577,8 @@ namespace Barotrauma
/// <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
/// 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
@@ -446,38 +615,11 @@ namespace Barotrauma
object? newObject = Activator.CreateInstance(type);
if (newObject is null) { return default!; }
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo info in properties)
var properties = NetSerializableProperties.GetPropertiesAndFields(type, type);
foreach (NetSerializableProperties.CachedReflectedVariable property 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}!");
}
NetworkSerialize attribute = property.Attribute;
property.SetValue(newObject, property.Behavior.ReadAction(inc, property.Type, attribute));
}
return newObject;
@@ -509,39 +651,34 @@ namespace Barotrauma
/// <param name="msg">Outgoing network message</param>
public void Write(IWriteMessage msg)
{
PropertyInfo[] properties = GetType().GetProperties();
foreach (PropertyInfo info in properties)
Type type = GetType();
var properties = NetSerializableProperties.GetPropertiesAndFields(type, type);
foreach (NetSerializableProperties.CachedReflectedVariable property 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}");
}
NetworkSerialize attribute = property.Attribute;
property.Behavior.WriteAction(property.GetValue(this), attribute, msg);
}
}
}
private static NetworkSerialize? GetAttribute(MemberInfo info, object baseClass) => info.GetCustomAttribute<NetworkSerialize>() ?? baseClass.GetType().GetCustomAttribute<NetworkSerialize>();
public static class WriteOnlyMessageExtensions
{
#if CLIENT
public static IWriteMessage WithHeader(this IWriteMessage msg, ClientPacketHeader header)
{
msg.Write((byte)header);
return msg;
}
#elif SERVER
public static IWriteMessage WithHeader(this IWriteMessage msg, ServerPacketHeader header)
{
msg.Write((byte)header);
return msg;
}
#endif
public static void Write(this IWriteMessage msg, INetSerializableStruct serializableStruct)
{
serializableStruct.Write(msg);
}
}
}
@@ -13,105 +13,105 @@ namespace Barotrauma
public string Name => "KarmaManager";
public Dictionary<string, SerializableProperty> SerializableProperties { get; private set; }
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; }
[Serialize(true, true)]
[Serialize(true, IsPropertySaveable.Yes)]
public bool ResetKarmaBetweenRounds { get; set; }
[Serialize(0.1f, true)]
[Serialize(0.1f, IsPropertySaveable.Yes)]
public float KarmaDecay { get; set; }
[Serialize(50.0f, true)]
[Serialize(50.0f, IsPropertySaveable.Yes)]
public float KarmaDecayThreshold { get; set; }
[Serialize(0.15f, true)]
[Serialize(0.15f, IsPropertySaveable.Yes)]
public float KarmaIncrease { get; set; }
[Serialize(50.0f, true)]
[Serialize(50.0f, IsPropertySaveable.Yes)]
public float KarmaIncreaseThreshold { get; set; }
[Serialize(0.05f, true)]
[Serialize(0.05f, IsPropertySaveable.Yes)]
public float StructureRepairKarmaIncrease { get; set; }
[Serialize(0.1f, true)]
[Serialize(0.1f, IsPropertySaveable.Yes)]
public float StructureDamageKarmaDecrease { get; set; }
[Serialize(15.0f, true)]
[Serialize(15.0f, IsPropertySaveable.Yes)]
public float MaxStructureDamageKarmaDecreasePerSecond { get; set; }
[Serialize(0.03f, true)]
[Serialize(0.03f, IsPropertySaveable.Yes)]
public float ItemRepairKarmaIncrease { get; set; }
[Serialize(0.5f, true)]
[Serialize(0.5f, IsPropertySaveable.Yes)]
public float ReactorOverheatKarmaDecrease { get; set; }
[Serialize(30.0f, true)]
[Serialize(30.0f, IsPropertySaveable.Yes)]
public float ReactorMeltdownKarmaDecrease { get; set; }
[Serialize(0.1f, true)]
[Serialize(0.1f, IsPropertySaveable.Yes)]
public float DamageEnemyKarmaIncrease { get; set; }
[Serialize(0.2f, true)]
[Serialize(0.2f, IsPropertySaveable.Yes)]
public float HealFriendlyKarmaIncrease { get; set; }
[Serialize(0.25f, true)]
[Serialize(0.25f, IsPropertySaveable.Yes)]
public float DamageFriendlyKarmaDecrease { get; set; }
[Serialize(0.25f, true)]
[Serialize(0.25f, IsPropertySaveable.Yes)]
public float StunFriendlyKarmaDecrease { get; set; }
[Serialize(0.3f, true)]
[Serialize(0.3f, IsPropertySaveable.Yes)]
public float StunFriendlyKarmaDecreaseThreshold { get; set; }
[Serialize(1.0f, true)]
[Serialize(1.0f, IsPropertySaveable.Yes)]
public float ExtinguishFireKarmaIncrease { get; set; }
[Serialize(defaultValue: 15.0f, true)]
[Serialize(defaultValue: 15.0f, IsPropertySaveable.Yes)]
public float DangerousItemStealKarmaDecrease { get; set; }
[Serialize(defaultValue: false, true)]
[Serialize(defaultValue: false, IsPropertySaveable.Yes)]
public bool DangerousItemStealBots { get; set; }
[Serialize(defaultValue: 0.05f, true)]
[Serialize(defaultValue: 0.05f, IsPropertySaveable.Yes)]
public float BallastFloraKarmaIncrease { get; set; }
private int allowedWireDisconnectionsPerMinute;
[Serialize(5, true)]
[Serialize(5, IsPropertySaveable.Yes)]
public int AllowedWireDisconnectionsPerMinute
{
get { return allowedWireDisconnectionsPerMinute; }
set { allowedWireDisconnectionsPerMinute = Math.Max(0, value); }
}
[Serialize(6.0f, true)]
[Serialize(6.0f, IsPropertySaveable.Yes)]
public float WireDisconnectionKarmaDecrease { get; set; }
[Serialize(0.15f, true)]
[Serialize(0.15f, IsPropertySaveable.Yes)]
public float SteerSubKarmaIncrease { get; set; }
[Serialize(15.0f, true)]
[Serialize(15.0f, IsPropertySaveable.Yes)]
public float SpamFilterKarmaDecrease { get; set; }
[Serialize(40.0f, true)]
[Serialize(40.0f, IsPropertySaveable.Yes)]
public float HerpesThreshold { get; set; }
[Serialize(1.0f, true)]
[Serialize(1.0f, IsPropertySaveable.Yes)]
public float KickBanThreshold { get; set; }
[Serialize(0, true)]
[Serialize(0, IsPropertySaveable.Yes)]
public int KicksBeforeBan { get; set; }
[Serialize(10.0f, true)]
[Serialize(10.0f, IsPropertySaveable.Yes)]
public float KarmaNotificationInterval { get; set; }
[Serialize(120.0f, true)]
[Serialize(120.0f, IsPropertySaveable.Yes)]
public float AllowedRetaliationTime { get; set; }
[Serialize(5.0f, true)]
[Serialize(5.0f, IsPropertySaveable.Yes)]
public float DangerousItemContainKarmaDecrease { get; set; }
[Serialize(defaultValue: true, true)]
[Serialize(defaultValue: true, IsPropertySaveable.Yes)]
public bool IsDangerousItemContainKarmaDecreaseIncremental { get; set; }
[Serialize(30.0f, true)]
[Serialize(30.0f, IsPropertySaveable.Yes)]
public float MaxDangerousItemContainKarmaDecrease { get; set; }
private readonly AfflictionPrefab herpesAffliction;
@@ -141,7 +141,7 @@ namespace Barotrauma
if (doc?.Root != null)
{
Presets["custom"] = doc.Root;
foreach (XElement subElement in doc.Root.Elements())
foreach (var subElement in doc.Root.Elements())
{
string presetName = subElement.GetAttributeString("name", "");
Presets[presetName.ToLowerInvariant()] = subElement;
@@ -13,8 +13,6 @@ namespace Barotrauma.Networking
public const int ServerNameMaxLength = 60;
public const int ServerMessageMaxLength = 2000;
public static string MasterServerUrl = GameMain.Config.MasterServerUrl;
public const float MaxPhysicsBodyVelocity = 64.0f;
public const float MaxPhysicsBodyAngularVelocity = 16.0f;
@@ -4,47 +4,16 @@ namespace Barotrauma.Networking
{
abstract class NetEntityEvent
{
public enum Type
{
Invalid,
ComponentState,
InventoryState,
Status,
Treatment,
ApplyStatusEffect,
ChangeProperty,
Control,
UpdateSkills,
Combine,
SetAttackTarget,
ExecuteAttack,
Upgrade,
AssignCampaignInteraction,
TeamChange,
ObjectiveManagerState,
AddToCrew,
UpdateExperience,
UpdateTalents,
UpdateMoney,
UpdatePermanentStats,
}
public interface IData { }
public readonly Entity Entity;
public readonly UInt16 ID;
public UInt16 EntityID
{
get;
private set;
}
public UInt16 EntityID => Entity.ID;
//arbitrary extra data that will be passed to the Write method of the serializable entity
//(the index of an itemcomponent for example)
public object[] Data
{
get;
private set;
}
public IData Data { get; private set; }
public bool Sent;
@@ -52,43 +21,18 @@ namespace Barotrauma.Networking
{
this.ID = id;
this.Entity = serializableEntity as Entity;
RefreshEntityID();
}
public void RefreshEntityID()
{
this.EntityID = this.Entity is Entity entity ? entity.ID : Entity.NullEntityID;
}
public void SetData(object[] data)
public void SetData(IData data)
{
this.Data = data;
}
public bool IsDuplicate(NetEntityEvent other)
{
if (other.Entity != this.Entity) return false;
if (other.Entity != this.Entity) { return false; }
if (Data != null && other.Data != null)
{
if (Data.Length != other.Data.Length) return false;
for (int i = 0; i < Data.Length; i++)
{
if (Data[i] == null)
{
if (other.Data[i] != null) return false;
}
else
{
if (other.Data[i] == null) return false;
if (!Data[i].Equals(other.Data[i])) return false;
}
}
return true;
}
return Data == other.Data;
return Equals(Data, other.Data);
}
}
}
@@ -38,7 +38,6 @@ namespace Barotrauma.Networking
//(otherwise the clients might read the next event in the message and think its ID
//is consecutive to the previous one, even though we skipped over this broken event)
tempBuffer.Write(Entity.NullEntityID);
tempBuffer.WritePadBits();
eventCount++;
continue;
}
@@ -53,7 +52,6 @@ namespace Barotrauma.Networking
tempBuffer.Write(e.EntityID);
tempBuffer.WriteVariableUInt32((uint)tempEventBuffer.LengthBytes);
tempBuffer.Write(tempEventBuffer.Buffer, 0, tempEventBuffer.LengthBytes);
tempBuffer.WritePadBits();
sentEvents.Add(e);
eventCount++;
@@ -61,6 +59,7 @@ namespace Barotrauma.Networking
if (eventCount > 0)
{
msg.WritePadBits();
msg.Write(eventsToSync[0].ID);
msg.Write((byte)eventCount);
msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes);
@@ -6,7 +6,7 @@ using System.Linq;
namespace Barotrauma.Networking
{
enum ClientPacketHeader
public enum ClientPacketHeader
{
UPDATE_LOBBY, //update state in lobby
UPDATE_INGAME, //update state ingame
@@ -31,8 +31,11 @@ namespace Barotrauma.Networking
ERROR, //tell the server that an error occurred
CREW, //hiring UI
MEDICAL, //medical clinic
TRANSFER_MONEY, // wallet transfers
REWARD_DISTRIBUTION, // wallet reward distribution
READY_CHECK,
READY_TO_SPAWN,
LUA_NET_MESSAGE
}
enum ClientNetObject
@@ -52,7 +55,7 @@ namespace Barotrauma.Networking
MISSING_ENTITY //client can't find an entity of a certain ID
}
enum ServerPacketHeader
public enum ServerPacketHeader
{
AUTH_RESPONSE, //tell the player if they require a password to log in
AUTH_FAILURE, //the server won't authorize player yet, however connection is still alive
@@ -83,6 +86,7 @@ namespace Barotrauma.Networking
CREW, //anything related to managing bots in multiplayer
MEDICAL, //medical clinic
READY_CHECK, //start, end and update a ready check
MONEY,
LUA_NET_MESSAGE
}
@@ -116,7 +120,8 @@ namespace Barotrauma.Networking
StartRound,
PurchaseAndSwitchSub,
PurchaseSub,
SwitchSub
SwitchSub,
TransferMoney
}
public enum ReadyCheckState
@@ -171,7 +176,7 @@ namespace Barotrauma.Networking
get { return false; }
}
public abstract void CreateEntityEvent(INetSerializable entity, object[] extraData = null);
public abstract void CreateEntityEvent(INetSerializable entity, NetEntityEvent.IData extraData = null);
#if DEBUG
public Dictionary<string, long> messageCount = new Dictionary<string, long>();
@@ -179,11 +184,11 @@ namespace Barotrauma.Networking
protected ServerSettings serverSettings;
public Voting Voting { get; protected set; }
protected TimeSpan updateInterval;
protected DateTime updateTimer;
public int EndVoteCount, EndVoteMax, SubmarineVoteYesCount, SubmarineVoteNoCount, SubmarineVoteMax;
protected bool gameStarted;
protected RespawnManager respawnManager;
@@ -11,17 +11,17 @@ namespace Barotrauma.Networking
public readonly Character TargetCharacter;
//which entity is this order referring to (hull, reactor, railgun controller, etc)
public readonly ISpatialEntity TargetEntity;
public ISpatialEntity TargetEntity => Order.TargetSpatialEntity;
//additional instructions (power up, fire at will, etc)
public readonly string OrderOption;
public Identifier OrderOption => Order.Option;
public readonly int OrderPriority;
public int OrderPriority => Order.ManualPriority;
/// <summary>
/// Used when the order targets a wall
/// </summary>
public int? WallSectionIndex { get; set; }
public int? WallSectionIndex => Order.WallSectionIndex;
public bool IsNewOrder { get; }
@@ -29,55 +29,50 @@ namespace Barotrauma.Networking
/// 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, bool isNewOrder = true)
: this(order, orderOption, priority,
order?.GetChatMessage(targetCharacter?.Name, sender?.CurrentHull?.DisplayName, targetCharacter == sender, orderOption, isNewOrder),
targetEntity, targetCharacter, sender, isNewOrder)
public OrderChatMessage(Order order, Character targetCharacter, Character sender, bool isNewOrder = true)
: this(order,
order?.GetChatMessage(targetCharacter?.Name, sender?.CurrentHull?.DisplayName?.Value, givingOrderToSelf: targetCharacter == sender, orderOption: order.Option, isNewOrder: isNewOrder),
targetCharacter, sender, isNewOrder)
{
}
public OrderChatMessage(Order order, string orderOption, int priority, string text, ISpatialEntity targetEntity,
Character targetCharacter, Character sender, bool isNewOrder = true)
public OrderChatMessage(Order order, string text, Character targetCharacter, Character sender, bool isNewOrder = true)
: base(sender?.Name, text, ChatMessageType.Order, sender, GameMain.NetworkMember.ConnectedClients.Find(c => c.Character == sender))
{
Order = order;
OrderOption = orderOption;
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, bool isNewOrder)
public static void WriteOrder(IWriteMessage msg, Order order, Character targetCharacter, bool isNewOrder)
{
msg.Write((byte)Order.PrefabList.IndexOf(order.Prefab));
msg.Write(order.Prefab.Identifier);
msg.Write(targetCharacter == null ? (UInt16)0 : targetCharacter.ID);
msg.Write(targetEntity is Entity ? (targetEntity as Entity).ID : (UInt16)0);
msg.Write(order.TargetSpatialEntity is Entity ? (order.TargetEntity as Entity).ID : (UInt16)0);
// The option of a Dismiss order is written differently so we know what order we target
// now that the game supports multiple current orders simultaneously
if (order.Prefab.Identifier != "dismissed")
if (!order.IsDismissal)
{
msg.Write((byte)Array.IndexOf(order.Prefab.Options, orderOption));
msg.Write((byte)order.Options.IndexOf(order.Option));
}
else
{
if (!string.IsNullOrEmpty(orderOption))
if (order.Option != Identifier.Empty)
{
msg.Write(true);
string[] dismissedOrder = orderOption.Split('.');
string[] dismissedOrder = order.Option.Value.Split('.');
msg.Write((byte)dismissedOrder.Length);
if (dismissedOrder.Length > 0)
{
string dismissedOrderIdentifier = dismissedOrder[0];
var orderPrefab = Order.GetPrefab(dismissedOrderIdentifier);
msg.Write((byte)Order.PrefabList.IndexOf(orderPrefab));
Identifier dismissedOrderIdentifier = dismissedOrder[0].ToIdentifier();
var orderPrefab = OrderPrefab.Prefabs[dismissedOrderIdentifier];
msg.Write(dismissedOrderIdentifier);
if (dismissedOrder.Length > 1)
{
string dismissedOrderOption = dismissedOrder[1];
msg.Write((byte)Array.IndexOf(orderPrefab.Options, dismissedOrderOption));
Identifier dismissedOrderOption = dismissedOrder[1].ToIdentifier();
msg.Write((byte)orderPrefab.Options.IndexOf(dismissedOrderOption));
}
}
}
@@ -89,9 +84,9 @@ namespace Barotrauma.Networking
}
}
msg.Write((byte)orderPriority);
msg.Write((byte)order.ManualPriority);
msg.Write((byte)order.TargetType);
if (order.TargetType == Order.OrderTargetType.Position && targetEntity is OrderTarget orderTarget)
if (order.TargetType == Order.OrderTargetType.Position && order.TargetSpatialEntity is OrderTarget orderTarget)
{
msg.Write(true);
msg.Write(orderTarget.Position.X);
@@ -103,7 +98,7 @@ namespace Barotrauma.Networking
msg.Write(false);
if (order.TargetType == Order.OrderTargetType.WallSection)
{
msg.Write((byte)(wallSectionIndex ?? order.WallSectionIndex ?? 0));
msg.Write((byte)(order.WallSectionIndex ?? 0));
}
}
@@ -112,14 +107,14 @@ namespace Barotrauma.Networking
private void WriteOrder(IWriteMessage msg)
{
WriteOrder(msg, Order, TargetCharacter, TargetEntity, OrderOption, OrderPriority, WallSectionIndex, IsNewOrder);
WriteOrder(msg, Order, TargetCharacter, IsNewOrder);
}
public struct OrderMessageInfo
{
public int OrderIndex { get; }
public Order OrderPrefab { get; }
public string OrderOption { get; }
public Identifier OrderIdentifier { get; }
public OrderPrefab OrderPrefab => OrderPrefab.Prefabs[OrderIdentifier];
public Identifier OrderOption { get; }
public int? OrderOptionIndex { get; }
public Character TargetCharacter { get; }
public Order.OrderTargetType TargetType { get; }
@@ -129,11 +124,10 @@ namespace Barotrauma.Networking
public int Priority { get; }
public bool IsNewOrder { get; }
public OrderMessageInfo(int orderIndex, Order orderPrefab, string orderOption, int? orderOptionIndex, Character targetCharacter,
public OrderMessageInfo(Identifier orderIdentifier, Identifier orderOption, int? orderOptionIndex, Character targetCharacter,
Order.OrderTargetType targetType, Entity targetEntity, OrderTarget targetPosition, int? wallSectionIndex, int orderPriority, bool isNewOrder)
{
OrderIndex = orderIndex;
OrderPrefab = orderPrefab;
OrderIdentifier = orderIdentifier;
OrderOption = orderOption;
OrderOptionIndex = orderOptionIndex;
TargetCharacter = targetCharacter;
@@ -148,21 +142,20 @@ namespace Barotrauma.Networking
public static OrderMessageInfo ReadOrder(IReadMessage msg)
{
int orderIndex = msg.ReadByte();
Identifier orderIdentifier = msg.ReadIdentifier();
ushort targetCharacterId = msg.ReadUInt16();
Character targetCharacter = targetCharacterId != Entity.NullEntityID ? Entity.FindEntityByID(targetCharacterId) as Character : null;
ushort targetEntityId = msg.ReadUInt16();
Entity targetEntity = targetEntityId != Entity.NullEntityID ? Entity.FindEntityByID(targetEntityId) : null;
Order orderPrefab = null;
int? optionIndex = null;
string orderOption = null;
Identifier orderOption = Identifier.Empty;
// The option of a Dismiss order is written differently so we know what order we target
// now that the game supports multiple current orders simultaneously
if (orderIndex >= 0 && orderIndex < Order.PrefabList.Count)
if (orderIdentifier != Identifier.Empty)
{
orderPrefab = Order.PrefabList[orderIndex];
if (orderPrefab.Identifier != "dismissed")
var orderPrefab = OrderPrefab.Prefabs[orderIdentifier];
if (!orderPrefab.IsDismissal)
{
optionIndex = msg.ReadByte();
}
@@ -172,11 +165,11 @@ namespace Barotrauma.Networking
int identifierCount = msg.ReadByte();
if (identifierCount > 0)
{
int dismissedOrderIndex = msg.ReadByte();
Order dismissedOrderPrefab = null;
if (dismissedOrderIndex >= 0 && dismissedOrderIndex < Order.PrefabList.Count)
Identifier dismissedOrderIdentifier = msg.ReadIdentifier();
OrderPrefab dismissedOrderPrefab = null;
if (dismissedOrderIdentifier != Identifier.Empty)
{
dismissedOrderPrefab = Order.PrefabList[dismissedOrderIndex];
dismissedOrderPrefab = OrderPrefab.Prefabs[dismissedOrderIdentifier];
orderOption = dismissedOrderPrefab.Identifier;
}
if (identifierCount > 1)
@@ -187,7 +180,7 @@ namespace Barotrauma.Networking
var options = dismissedOrderPrefab.Options;
if (options != null && dismissedOrderOptionIndex >= 0 && dismissedOrderOptionIndex < options.Length)
{
orderOption += $".{options[dismissedOrderOptionIndex]}";
orderOption = $"{orderOption.Value}.{options[dismissedOrderOptionIndex]}".ToIdentifier();
}
}
}
@@ -217,9 +210,8 @@ namespace Barotrauma.Networking
}
bool isNewOrder = msg.ReadBoolean();
return new OrderMessageInfo(orderIndex, orderPrefab, orderOption, optionIndex, targetCharacter,
orderTargetType, targetEntity, orderTargetPosition, wallSectionIndex, orderPriority, isNewOrder);
return new OrderMessageInfo(orderIdentifier, orderOption, optionIndex, targetCharacter,
orderTargetType, targetEntity, orderTargetPosition, wallSectionIndex, orderPriority, isNewOrder);
}
}
}
@@ -19,6 +19,7 @@ namespace Barotrauma.Networking
Double ReadDouble();
UInt32 ReadVariableUInt32();
String ReadString();
Identifier ReadIdentifier();
Microsoft.Xna.Framework.Color ReadColorR8G8B8();
Microsoft.Xna.Framework.Color ReadColorR8G8B8A8();
int ReadRangedInteger(int min, int max);
@@ -19,11 +19,12 @@ namespace Barotrauma.Networking
void WriteColorR8G8B8A8(Microsoft.Xna.Framework.Color val);
void WriteVariableUInt32(UInt32 val);
void Write(string val);
void Write(Identifier val);
void WriteRangedInteger(int val, int min, int max);
void WriteRangedSingle(Single val, Single min, Single max, int bitCount);
void Write(byte[] val, int startIndex, int length);
void PrepareForSending(ref byte[] outBuf, out bool isCompressed, out int outLength);
void PrepareForSending(ref byte[] outBuf, bool compressPastThreshold, out bool isCompressed, out int outLength);
int BitPosition { get; set; }
int BytePosition { get; }
@@ -454,6 +454,7 @@ namespace Barotrauma.Networking
{
lengthBits = value;
seekPos = seekPos > lengthBits ? lengthBits : seekPos;
MsgWriter.EnsureBufferSize(ref buf, lengthBits);
}
}
@@ -540,6 +541,11 @@ namespace Barotrauma.Networking
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(Identifier val)
{
Write(val.Value);
}
public void WriteRangedInteger(int val, int min, int max)
{
MsgWriter.WriteRangedInteger(ref buf, ref seekPos, val, min, max);
@@ -555,9 +561,9 @@ namespace Barotrauma.Networking
MsgWriter.WriteBytes(ref buf, ref seekPos, val, startPos, length);
}
public void PrepareForSending(ref byte[] outBuf, out bool isCompressed, out int length)
public void PrepareForSending(ref byte[] outBuf, bool compressPastThreshold, out bool isCompressed, out int length)
{
if (LengthBytes <= MsgConstants.CompressionThreshold)
if (LengthBytes <= MsgConstants.CompressionThreshold || !compressPastThreshold)
{
isCompressed = false;
if (LengthBytes > outBuf.Length) { Array.Resize(ref outBuf, LengthBytes); }
@@ -648,7 +654,7 @@ namespace Barotrauma.Networking
{
get
{
return lengthBits / 8;
return (LengthBits + 7) / 8;
}
}
@@ -764,6 +770,11 @@ namespace Barotrauma.Networking
return MsgReader.ReadString(buf, ref seekPos);
}
public Identifier ReadIdentifier()
{
return ReadString().ToIdentifier();
}
public Color ReadColorR8G8B8()
{
return MsgReader.ReadColorR8G8B8(buf, ref seekPos);
@@ -773,7 +784,6 @@ namespace Barotrauma.Networking
{
return MsgReader.ReadColorR8G8B8A8(buf, ref seekPos);
}
public int ReadRangedInteger(int min, int max)
{
@@ -857,7 +867,7 @@ namespace Barotrauma.Networking
{
get
{
return (LengthBits + ((8 - (LengthBits % 8)) % 8)) / 8;
return (LengthBits + 7) / 8;
}
}
@@ -938,6 +948,10 @@ namespace Barotrauma.Networking
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(Identifier val)
{
Write(val.Value);
}
public void WriteRangedInteger(int val, int min, int max)
{
@@ -1019,6 +1033,11 @@ namespace Barotrauma.Networking
return MsgReader.ReadString(buf, ref seekPos);
}
public Identifier ReadIdentifier()
{
return ReadString().ToIdentifier();
}
public Color ReadColorR8G8B8()
{
return MsgReader.ReadColorR8G8B8(buf, ref seekPos);
@@ -1044,7 +1063,7 @@ namespace Barotrauma.Networking
return MsgReader.ReadBytes(buf, ref seekPos, numberOfBytes);
}
public void PrepareForSending(ref byte[] outBuf, out bool isCompressed, out int outLength)
public void PrepareForSending(ref byte[] outBuf, bool compressPastThreshold, out bool isCompressed, out int outLength)
{
throw new InvalidOperationException("ReadWriteMessages are not to be sent");
}
@@ -33,7 +33,7 @@ namespace Barotrauma.Networking
protected set;
}
public string Language
public LanguageIdentifier Language
{
get; set;
}
@@ -249,7 +249,7 @@ namespace Barotrauma.Networking
//remove respawn items that have been left in the shuttle
if (respawnItems.Contains(item))
{
Spawner.AddToRemoveQueue(item);
Spawner.AddItemToRemoveQueue(item);
continue;
}
@@ -285,7 +285,7 @@ namespace Barotrauma.Networking
}
}
foreach (Hull hull in Hull.hullList)
foreach (Hull hull in Hull.HullList)
{
if (hull.Submarine != RespawnShuttle) { continue; }
hull.OxygenPercentage = 100.0f;
@@ -308,12 +308,12 @@ namespace Barotrauma.Networking
c.Kill(CauseOfDeathType.Unknown, null, true);
c.Enabled = false;
Spawner.AddToRemoveQueue(c);
Spawner.AddEntityToRemoveQueue(c);
if (c.Inventory != null)
{
foreach (Item item in c.Inventory.AllItems)
{
Spawner.AddToRemoveQueue(item);
Spawner.AddItemToRemoveQueue(item);
}
}
}
@@ -335,7 +335,7 @@ namespace Barotrauma.Networking
public static Affliction GetRespawnPenaltyAffliction()
{
var respawnPenaltyAffliction = AfflictionPrefab.List.FirstOrDefault(a => a.AfflictionType.Equals("respawnpenalty", StringComparison.OrdinalIgnoreCase));
var respawnPenaltyAffliction = AfflictionPrefab.Prefabs.First(a => a.AfflictionType == "respawnpenalty");
return respawnPenaltyAffliction?.Instantiate(10.0f);
}
@@ -10,23 +10,20 @@ namespace Barotrauma.Networking
{
private struct LogMessage
{
public readonly string Text;
public readonly string SanitizedText;
public readonly RichString Text;
public readonly MessageType Type;
public readonly List<RichTextData> RichData;
public LogMessage(string text, MessageType type)
{
if (type.HasFlag(MessageType.Chat))
{
Text = $"[{DateTime.Now}]\n {text}";
text = $"[{DateTime.Now}]\n {text}";
}
else
{
Text = $"[{DateTime.Now}]\n {TextManager.GetServerMessage(text)}";
text = $"[{DateTime.Now}]\n {TextManager.GetServerMessage(text)}";
}
RichData = RichTextData.GetRichTextData(Text, out SanitizedText);
Text = RichString.Rich(text);
Type = type;
}
}
@@ -41,6 +38,7 @@ namespace Barotrauma.Networking
Wiring,
ServerMessage,
ConsoleUsage,
Money,
Karma,
Talent,
Error,
@@ -56,9 +54,10 @@ namespace Barotrauma.Networking
{ MessageType.Wiring, new Color(255, 157, 85) },
{ MessageType.ServerMessage, new Color(157, 225, 160) },
{ MessageType.ConsoleUsage, new Color(0, 162, 232) },
{ MessageType.Money, Color.Green },
{ MessageType.Karma, new Color(75, 88, 255) },
{ MessageType.Talent, new Color(125, 125, 255) },
{ MessageType.Error, Color.Red },
{ MessageType.Error, Color.Red }
};
private readonly Dictionary<MessageType, string> messageTypeName = new Dictionary<MessageType, string>
@@ -71,6 +70,7 @@ namespace Barotrauma.Networking
{ MessageType.Wiring, "Wiring" },
{ MessageType.ServerMessage, "ServerMessage" },
{ MessageType.ConsoleUsage, "ConsoleUsage" },
{ MessageType.Money, "Money" },
{ MessageType.Karma, "Karma" },
{ MessageType.Talent, "Talent" },
{ MessageType.Error, "Error" }
@@ -113,7 +113,7 @@ namespace Barotrauma.Networking
var newText = new LogMessage(line, messageType);
#if SERVER
DebugConsole.NewMessage(newText.SanitizedText, messageColor[messageType]); //TODO: REMOVE
DebugConsole.NewMessage(newText.Text.SanitizedValue, messageColor[messageType]); //TODO: REMOVE
#endif
lines.Enqueue(newText);
@@ -173,7 +173,7 @@ namespace Barotrauma.Networking
try
{
File.WriteAllLines(filePath, unsavedLines.Select(l => l.SanitizedText));
File.WriteAllLines(filePath, unsavedLines.Select(l => l.Text.SanitizedValue));
}
catch (Exception e)
{
@@ -1,6 +1,7 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Globalization;
using Barotrauma.IO;
@@ -97,11 +98,8 @@ namespace Barotrauma.Networking
private readonly SerializableProperty property;
private readonly string typeString;
private readonly object parentObject;
public string Name
{
get { return property.Name; }
}
public Identifier Name => property.Name.ToIdentifier();
public object Value
{
@@ -266,7 +264,7 @@ namespace Barotrauma.Networking
}
};
public Dictionary<string, SerializableProperty> SerializableProperties
public Dictionary<Identifier, SerializableProperty> SerializableProperties
{
get;
private set;
@@ -280,8 +278,6 @@ namespace Barotrauma.Networking
{
ServerLog = new ServerLog(serverName);
Voting = new Voting();
Whitelist = new WhiteList();
BanList = new BanList();
@@ -314,7 +310,7 @@ namespace Barotrauma.Networking
if (typeName != null || property.PropertyType.IsEnum)
{
NetPropertyData netPropertyData = new NetPropertyData(this, property, typeName);
UInt32 key = ToolBox.StringToUInt32Hash(property.Name, md5);
UInt32 key = ToolBox.IdentifierToUint32Hash(netPropertyData.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);
@@ -331,7 +327,7 @@ namespace Barotrauma.Networking
if (typeName != null || property.PropertyType.IsEnum)
{
NetPropertyData netPropertyData = new NetPropertyData(networkMember.KarmaManager, property, typeName);
UInt32 key = ToolBox.StringToUInt32Hash(property.Name, md5);
UInt32 key = ToolBox.IdentifierToUint32Hash(netPropertyData.Name, md5);
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);
}
@@ -383,9 +379,7 @@ namespace Barotrauma.Networking
public ServerLog ServerLog;
public Voting Voting;
public Dictionary<string, bool> MonsterEnabled { get; private set; }
public Dictionary<Identifier, bool> MonsterEnabled { get; private set; }
public const int MaxExtraCargoItemsOfType = 10;
public const int MaxExtraCargoItemTypes = 20;
@@ -408,63 +402,63 @@ namespace Barotrauma.Networking
public WhiteList Whitelist { get; private set; }
[Serialize(20, true)]
[Serialize(20, IsPropertySaveable.Yes)]
public int TickRate
{
get;
set;
}
[Serialize(true, true)]
[Serialize(true, IsPropertySaveable.Yes)]
public bool RandomizeSeed
{
get;
set;
}
[Serialize(true, true)]
[Serialize(true, IsPropertySaveable.Yes)]
public bool UseRespawnShuttle
{
get;
private set;
}
[Serialize(300.0f, true)]
[Serialize(300.0f, IsPropertySaveable.Yes)]
public float RespawnInterval
{
get;
private set;
}
[Serialize(180.0f, true)]
[Serialize(180.0f, IsPropertySaveable.Yes)]
public float MaxTransportTime
{
get;
private set;
}
[Serialize(0.2f, true)]
[Serialize(0.2f, IsPropertySaveable.Yes)]
public float MinRespawnRatio
{
get;
private set;
}
[Serialize(60.0f, true)]
[Serialize(60.0f, IsPropertySaveable.Yes)]
public float AutoRestartInterval
{
get;
set;
}
[Serialize(false, true)]
[Serialize(false, IsPropertySaveable.Yes)]
public bool StartWhenClientsReady
{
get;
set;
}
[Serialize(0.8f, true)]
[Serialize(0.8f, IsPropertySaveable.Yes)]
public float StartWhenClientsReadyRatio
{
get;
@@ -472,7 +466,7 @@ namespace Barotrauma.Networking
}
private bool allowSpectating;
[Serialize(true, true)]
[Serialize(true, IsPropertySaveable.Yes)]
public bool AllowSpectating
{
get { return allowSpectating; }
@@ -484,21 +478,28 @@ namespace Barotrauma.Networking
}
}
[Serialize(true, true)]
[Serialize(true, IsPropertySaveable.Yes)]
public bool SaveServerLogs
{
get;
private set;
}
[Serialize(true, true)]
[Serialize(true, IsPropertySaveable.Yes)]
public bool AllowModDownloads
{
get;
private set;
} = true;
[Serialize(true, IsPropertySaveable.Yes)]
public bool AllowRagdollButton
{
get;
set;
}
[Serialize(true, true)]
[Serialize(true, IsPropertySaveable.Yes)]
public bool AllowFileTransfers
{
get;
@@ -506,7 +507,7 @@ namespace Barotrauma.Networking
}
private bool voiceChatEnabled;
[Serialize(true, true)]
[Serialize(true, IsPropertySaveable.Yes)]
public bool VoiceChatEnabled
{
get { return voiceChatEnabled; }
@@ -519,7 +520,7 @@ namespace Barotrauma.Networking
}
private PlayStyle playstyleSelection;
[Serialize(PlayStyle.Casual, true)]
[Serialize(PlayStyle.Casual, IsPropertySaveable.Yes)]
public PlayStyle PlayStyle
{
get { return playstyleSelection; }
@@ -530,14 +531,14 @@ namespace Barotrauma.Networking
}
}
[Serialize(Barotrauma.LosMode.Opaque, true)]
[Serialize(Barotrauma.LosMode.Opaque, IsPropertySaveable.Yes)]
public LosMode LosMode
{
get;
set;
}
[Serialize(800, true)]
[Serialize(800, IsPropertySaveable.Yes)]
public int LinesPerLogFile
{
get
@@ -572,37 +573,23 @@ namespace Barotrauma.Networking
#endif
}
[Serialize(true, true)]
[Serialize(true, IsPropertySaveable.Yes)]
public bool AllowVoteKick
{
get
{
return Voting.AllowVoteKick;
}
set
{
Voting.AllowVoteKick = value;
}
get; set;
}
[Serialize(true, true)]
[Serialize(true, IsPropertySaveable.Yes)]
public bool AllowEndVoting
{
get
{
return Voting.AllowEndVoting;
}
set
{
Voting.AllowEndVoting = value;
}
get; set;
}
private bool allowRespawn;
[Serialize(true, true)]
[Serialize(true, IsPropertySaveable.Yes)]
public bool AllowRespawn
{
get { return allowRespawn; ; }
get { return allowRespawn; }
set
{
if (allowRespawn == value) { return; }
@@ -611,28 +598,28 @@ namespace Barotrauma.Networking
}
}
[Serialize(0, true)]
[Serialize(0, IsPropertySaveable.Yes)]
public int BotCount
{
get;
set;
}
[Serialize(16, true)]
[Serialize(16, IsPropertySaveable.Yes)]
public int MaxBotCount
{
get;
set;
}
[Serialize(BotSpawnMode.Normal, true)]
[Serialize(BotSpawnMode.Normal, IsPropertySaveable.Yes)]
public BotSpawnMode BotSpawnMode
{
get;
set;
}
[Serialize(false, true)]
[Serialize(false, IsPropertySaveable.Yes)]
public bool DisableBotConversations
{
get;
@@ -645,76 +632,76 @@ namespace Barotrauma.Networking
set { selectedLevelDifficulty = MathHelper.Clamp(value, 0.0f, 100.0f); }
}
[Serialize(true, true)]
[Serialize(true, IsPropertySaveable.Yes)]
public bool AllowDisguises
{
get;
set;
}
[Serialize(true, true)]
[Serialize(true, IsPropertySaveable.Yes)]
public bool AllowRewiring
{
get;
set;
}
[Serialize(false, true)]
[Serialize(false, IsPropertySaveable.Yes)]
public bool LockAllDefaultWires
{
get;
set;
}
[Serialize(false, true)]
[Serialize(false, IsPropertySaveable.Yes)]
public bool AllowLinkingWifiToChat
{
get;
set;
}
[Serialize(true, true)]
[Serialize(true, IsPropertySaveable.Yes)]
public bool AllowFriendlyFire
{
get;
set;
}
[Serialize(false, true)]
[Serialize(false, IsPropertySaveable.Yes)]
public bool DestructibleOutposts
{
get;
set;
}
[Serialize(true, true)]
[Serialize(true, IsPropertySaveable.Yes)]
public bool KillableNPCs
{
get;
set;
}
[Serialize(true, true)]
[Serialize(true, IsPropertySaveable.Yes)]
public bool BanAfterWrongPassword
{
get;
set;
}
[Serialize(3, true)]
[Serialize(3, IsPropertySaveable.Yes)]
public int MaxPasswordRetriesBeforeBan
{
get;
private set;
}
[Serialize("", true)]
[Serialize("", IsPropertySaveable.Yes)]
public string SelectedSubmarine
{
get;
set;
}
[Serialize("", true)]
[Serialize("", IsPropertySaveable.Yes)]
public string SelectedShuttle
{
get;
@@ -722,7 +709,7 @@ namespace Barotrauma.Networking
}
private YesNoMaybe traitorsEnabled;
[Serialize(YesNoMaybe.No, true)]
[Serialize(YesNoMaybe.No, IsPropertySaveable.Yes)]
public YesNoMaybe TraitorsEnabled
{
get { return traitorsEnabled; }
@@ -734,35 +721,35 @@ namespace Barotrauma.Networking
}
}
[Serialize(defaultValue: 1, isSaveable: true)]
[Serialize(defaultValue: 1, isSaveable: IsPropertySaveable.Yes)]
public int TraitorsMinPlayerCount
{
get;
set;
}
[Serialize(defaultValue: 90.0f, isSaveable: true)]
[Serialize(defaultValue: 90.0f, isSaveable: IsPropertySaveable.Yes)]
public float TraitorsMinStartDelay
{
get;
set;
}
[Serialize(defaultValue: 180.0f, isSaveable: true)]
[Serialize(defaultValue: 180.0f, isSaveable: IsPropertySaveable.Yes)]
public float TraitorsMaxStartDelay
{
get;
set;
}
[Serialize(defaultValue: 30.0f, isSaveable: true)]
[Serialize(defaultValue: 30.0f, isSaveable: IsPropertySaveable.Yes)]
public float TraitorsMinRestartDelay
{
get;
set;
}
[Serialize(defaultValue: 90.0f, isSaveable: true)]
[Serialize(defaultValue: 90.0f, isSaveable: IsPropertySaveable.Yes)]
public float TraitorsMaxRestartDelay
{
get;
@@ -770,69 +757,69 @@ namespace Barotrauma.Networking
}
private SelectionMode subSelectionMode;
[Serialize(SelectionMode.Manual, true)]
[Serialize(SelectionMode.Manual, IsPropertySaveable.Yes)]
public SelectionMode SubSelectionMode
{
get { return subSelectionMode; }
set
{
subSelectionMode = value;
Voting.AllowSubVoting = subSelectionMode == SelectionMode.Vote;
AllowSubVoting = subSelectionMode == SelectionMode.Vote;
ServerDetailsChanged = true;
}
}
private SelectionMode modeSelectionMode;
[Serialize(SelectionMode.Manual, true)]
[Serialize(SelectionMode.Manual, IsPropertySaveable.Yes)]
public SelectionMode ModeSelectionMode
{
get { return modeSelectionMode; }
set
{
modeSelectionMode = value;
Voting.AllowModeVoting = modeSelectionMode == SelectionMode.Vote;
AllowModeVoting = modeSelectionMode == SelectionMode.Vote;
ServerDetailsChanged = true;
}
}
public BanList BanList { get; private set; }
[Serialize(0.6f, true)]
[Serialize(0.6f, IsPropertySaveable.Yes)]
public float EndVoteRequiredRatio
{
get;
private set;
}
[Serialize(0.6f, true)]
public float SubmarineVoteRequiredRatio
[Serialize(0.6f, IsPropertySaveable.Yes)]
public float VoteRequiredRatio
{
get;
private set;
}
[Serialize(30f, true)]
public float SubmarineVoteTimeout
[Serialize(30f, IsPropertySaveable.Yes)]
public float VoteTimeout
{
get;
private set;
}
[Serialize(0.6f, true)]
[Serialize(0.6f, IsPropertySaveable.Yes)]
public float KickVoteRequiredRatio
{
get;
private set;
}
[Serialize(300.0f, true)]
[Serialize(300.0f, IsPropertySaveable.Yes)]
public float KillDisconnectedTime
{
get;
private set;
}
[Serialize(600.0f, true)]
[Serialize(600.0f, IsPropertySaveable.Yes)]
public float KickAFKTime
{
get;
@@ -840,7 +827,7 @@ namespace Barotrauma.Networking
}
private bool karmaEnabled;
[Serialize(false, true)]
[Serialize(false, IsPropertySaveable.Yes)]
public bool KarmaEnabled
{
get { return karmaEnabled; }
@@ -854,7 +841,7 @@ namespace Barotrauma.Networking
}
private string karmaPreset = "default";
[Serialize("default", true)]
[Serialize("default", IsPropertySaveable.Yes)]
public string KarmaPreset
{
get { return karmaPreset; }
@@ -869,21 +856,21 @@ namespace Barotrauma.Networking
}
}
[Serialize("sandbox", true)]
public string GameModeIdentifier
[Serialize("sandbox", IsPropertySaveable.Yes)]
public Identifier GameModeIdentifier
{
get;
set;
}
[Serialize("All", true)]
[Serialize("All", IsPropertySaveable.Yes)]
public string MissionType
{
get;
set;
}
[Serialize(8, true)]
[Serialize(8, IsPropertySaveable.Yes)]
public int MaxPlayers
{
get { return maxPlayers; }
@@ -896,21 +883,21 @@ namespace Barotrauma.Networking
set;
}
[Serialize(60f * 60.0f, true)]
[Serialize(60f * 60.0f, IsPropertySaveable.Yes)]
public float AutoBanTime
{
get;
private set;
}
[Serialize(60.0f * 60.0f * 24.0f, true)]
[Serialize(60.0f * 60.0f * 24.0f, IsPropertySaveable.Yes)]
public float MaxAutoBanTime
{
get;
private set;
}
[Serialize(true, true)]
[Serialize(true, IsPropertySaveable.Yes)]
public bool RadiationEnabled
{
get;
@@ -919,13 +906,66 @@ namespace Barotrauma.Networking
private int maxMissionCount = CampaignSettings.DefaultMaxMissionCount;
[Serialize(CampaignSettings.DefaultMaxMissionCount, true)]
[Serialize(CampaignSettings.DefaultMaxMissionCount, IsPropertySaveable.Yes)]
public int MaxMissionCount
{
get { return maxMissionCount; }
set { maxMissionCount = MathHelper.Clamp(value, CampaignSettings.MinMissionCountLimit, CampaignSettings.MaxMissionCountLimit); }
}
private bool allowSubVoting;
//Don't serialize: the value is set based on SubSelectionMode
public bool AllowSubVoting
{
get { return allowSubVoting; }
set
{
if (value == allowSubVoting) { return; }
allowSubVoting = value;
#if CLIENT
GameMain.NetLobbyScreen.SubList.Enabled = value ||
(GameMain.Client != null && GameMain.Client.HasPermission(Networking.ClientPermissions.SelectSub));
var subVotesLabel = GameMain.NetLobbyScreen.Frame.FindChild("subvotes", true) as GUITextBlock;
subVotesLabel.Visible = value;
var subVisButton = GameMain.NetLobbyScreen.SubVisibilityButton;
subVisButton.RectTransform.AbsoluteOffset
= new Point(value ? (int)(subVotesLabel.TextSize.X + subVisButton.Rect.Width) : 0, 0);
GameMain.Client?.Voting.UpdateVoteTexts(null, VoteType.Sub);
GameMain.NetLobbyScreen.SubList.Deselect();
#endif
}
}
private bool allowModeVoting;
//Don't serialize: the value is set based on ModeSelectionMode
public bool AllowModeVoting
{
get { return allowModeVoting; }
set
{
if (value == allowModeVoting) { return; }
allowModeVoting = value;
#if CLIENT
GameMain.NetLobbyScreen.ModeList.Enabled =
value ||
(GameMain.Client != null && GameMain.Client.HasPermission(Networking.ClientPermissions.SelectMode));
GameMain.NetLobbyScreen.Frame.FindChild("modevotes", true).Visible = value;
// Disable modes that cannot be voted on
foreach (var guiComponent in GameMain.NetLobbyScreen.ModeList.Content.Children)
{
if (guiComponent is GUIFrame frame)
{
frame.CanBeFocused = !allowModeVoting || ((GameModePreset)frame.UserData).Votable;
}
}
GameMain.Client?.Voting.UpdateVoteTexts(null, VoteType.Mode);
GameMain.NetLobbyScreen.ModeList.Deselect();
#endif
}
}
public void SetPassword(string password)
{
if (string.IsNullOrEmpty(password))
@@ -965,45 +1005,54 @@ namespace Barotrauma.Networking
/// <summary>
/// A list of int pairs that represent the ranges of UTF-16 codes allowed in client names
/// </summary>
public List<Pair<int, int>> AllowedClientNameChars
public List<Range<int>> AllowedClientNameChars
{
get;
private set;
} = new List<Pair<int, int>>();
} = new List<Range<int>>();
private void InitMonstersEnabled()
{
//monster spawn settings
if (MonsterEnabled == null)
if (MonsterEnabled is null || MonsterEnabled.Count != CharacterPrefab.Prefabs.Count())
{
List<string> monsterNames1 = CharacterPrefab.Prefabs.Select(p => p.Identifier).ToList();
MonsterEnabled = new Dictionary<string, bool>();
foreach (string s in monsterNames1)
{
if (!MonsterEnabled.ContainsKey(s)) MonsterEnabled.Add(s, true);
}
MonsterEnabled = CharacterPrefab.Prefabs.Select(p => (p.Identifier, true)).ToDictionary();
}
}
private static IReadOnlyList<Identifier> ExtractAndSortKeys(IReadOnlyDictionary<Identifier, bool> monsterEnabled)
=> monsterEnabled.Keys
.OrderBy(k => CharacterPrefab.Prefabs[k].UintIdentifier)
.ToImmutableArray();
public void ReadMonsterEnabled(IReadMessage inc)
{
InitMonstersEnabled();
List<string> monsterNames = MonsterEnabled.Keys.ToList();
foreach (string s in monsterNames)
var monsterNames = ExtractAndSortKeys(MonsterEnabled);
uint receivedMonsterCount = inc.ReadVariableUInt32();
if (monsterNames.Count != receivedMonsterCount)
{
MonsterEnabled[s] = inc.ReadBoolean();
inc.BitPosition += (int)receivedMonsterCount;
DebugConsole.AddWarning($"Expected monster count {monsterNames.Count}, got {receivedMonsterCount}");
}
else
{
foreach (Identifier s in monsterNames)
{
MonsterEnabled[s] = inc.ReadBoolean();
}
}
inc.ReadPadBits();
}
public void WriteMonsterEnabled(IWriteMessage msg, Dictionary<string, bool> monsterEnabled = null)
public void WriteMonsterEnabled(IWriteMessage msg, Dictionary<Identifier, bool> monsterEnabled = null)
{
//monster spawn settings
if (monsterEnabled == null) monsterEnabled = MonsterEnabled;
List<string> monsterNames = monsterEnabled.Keys.ToList();
foreach (string s in monsterNames)
InitMonstersEnabled();
monsterEnabled ??= MonsterEnabled;
var monsterNames = ExtractAndSortKeys(monsterEnabled);
msg.WriteVariableUInt32((uint)monsterNames.Count);
foreach (Identifier s in monsterNames)
{
msg.Write(monsterEnabled[s]);
}
@@ -1018,7 +1067,7 @@ namespace Barotrauma.Networking
Dictionary<ItemPrefab, int> extraCargo = new Dictionary<ItemPrefab, int>();
for (int i = 0; i < count; i++)
{
string prefabIdentifier = msg.ReadString();
Identifier prefabIdentifier = msg.ReadIdentifier();
byte amount = msg.ReadByte();
if (MapEntityPrefab.Find(null, prefabIdentifier, showErrorMessages: false) is ItemPrefab itemPrefab && amount > 0)
@@ -1044,7 +1093,7 @@ namespace Barotrauma.Networking
msg.Write((UInt32)ExtraCargo.Count);
foreach (KeyValuePair<ItemPrefab, int> kvp in ExtraCargo)
{
msg.Write(kvp.Key.Identifier ?? "");
msg.Write(kvp.Key.Identifier);
msg.Write((byte)kvp.Value);
}
}
@@ -1,296 +0,0 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
#if USE_STEAM
namespace Barotrauma.Steam
{
static partial class SteamManager
{
public const int STEAMP2P_OWNER_PORT = 30000;
public const uint AppID = 602960;
private static readonly List<string> initializationErrors = new List<string>();
public static IEnumerable<string> InitializationErrors
{
get { return initializationErrors; }
}
public const string MetadataFileName = "filelist.xml";
public const string CopyIndicatorFileName = ".copying";
private static readonly Dictionary<string, int> tagCommonness = new Dictionary<string, int>()
{
{ "submarine", 10 },
{ "item", 10 },
{ "monster", 8 },
{ "art", 8 },
{ "mission", 8 },
{ "event set", 8 },
{ "total conversion", 5 },
{ "environment", 5 },
{ "item assembly", 5 },
{ "language", 5 }
};
private static readonly List<string> popularTags = new List<string>();
public static IEnumerable<string> PopularTags
{
get
{
if (!isInitialized) { return Enumerable.Empty<string>(); }
return popularTags;
}
}
private static bool isInitialized;
public static bool IsInitialized => isInitialized;
public static void Initialize()
{
InitializeProjectSpecific();
}
public static ulong GetSteamID()
{
if (!isInitialized || !Steamworks.SteamClient.IsValid)
{
return 0;
}
return Steamworks.SteamClient.SteamId;
}
public static string GetUsername()
{
if (!isInitialized || !Steamworks.SteamClient.IsValid)
{
return "";
}
return Steamworks.SteamClient.Name;
}
private static Steamworks.AuthTicket currentTicket = null;
public static Steamworks.AuthTicket GetAuthSessionTicket()
{
if (!isInitialized)
{
return null;
}
currentTicket?.Cancel();
currentTicket = Steamworks.SteamUser.GetAuthSessionTicket();
return currentTicket;
}
public static bool OverlayCustomURL(string url)
{
if (!isInitialized || !Steamworks.SteamClient.IsValid)
{
return false;
}
Steamworks.SteamFriends.OpenWebOverlay(url);
return true;
}
public static bool UnlockAchievement(string achievementIdentifier)
{
if (!isInitialized || !Steamworks.SteamClient.IsValid)
{
return false;
}
DebugConsole.Log("Unlocked achievement \"" + achievementIdentifier + "\"");
var achievements = Steamworks.SteamUserStats.Achievements.ToList();
int achIndex = achievements.FindIndex(ach => ach.Identifier == achievementIdentifier);
bool unlocked = achIndex >= 0 ? achievements[achIndex].Trigger() : false;
if (!unlocked)
{
//can be caused by an incorrect identifier, but also happens during normal gameplay:
//SteamAchievementManager tries to unlock achievements that may or may not exist
//(discovered[whateverbiomewasentered], kill[withwhateveritem], kill[somemonster] etc) so that we can add
//some types of new achievements without the need for client-side changes.
#if DEBUG
DebugConsole.NewMessage("Failed to unlock achievement \"" + achievementIdentifier + "\".");
#endif
}
return unlocked;
}
public static bool IncrementStat(string statName, int increment)
{
if (!isInitialized || !Steamworks.SteamClient.IsValid) { return false; }
DebugConsole.Log("Incremented stat \"" + statName + "\" by " + increment);
bool success = Steamworks.SteamUserStats.AddStat(statName, increment);
if (!success)
{
#if DEBUG
DebugConsole.NewMessage("Failed to increment stat \"" + statName + "\".");
#endif
}
else
{
StoreStats();
}
return success;
}
public static bool IncrementStat(string statName, float increment)
{
if (!isInitialized || !Steamworks.SteamClient.IsValid) { return false; }
DebugConsole.Log("Incremented stat \"" + statName + "\" by " + increment);
bool success = Steamworks.SteamUserStats.AddStat(statName, increment);
if (!success)
{
#if DEBUG
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; }
DebugConsole.Log("Storing Steam stats...");
bool success = Steamworks.SteamUserStats.StoreStats();
if (!success)
{
#if DEBUG
DebugConsole.NewMessage("Failed to store Steam stats.");
#endif
}
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; }
if (Steamworks.SteamClient.IsValid) { Steamworks.SteamClient.RunCallbacks(); }
if (Steamworks.SteamServer.IsValid) { Steamworks.SteamServer.RunCallbacks(); }
SteamAchievementManager.Update(deltaTime);
}
public static void ShutDown()
{
if (!isInitialized) { return; }
if (Steamworks.SteamClient.IsValid) { Steamworks.SteamClient.Shutdown(); }
if (Steamworks.SteamServer.IsValid) { Steamworks.SteamServer.Shutdown(); }
isInitialized = false;
}
public static IEnumerable<ulong> ParseWorkshopIds(string workshopIdData)
{
string[] workshopIds = workshopIdData.Split(',');
foreach (string id in workshopIds)
{
if (ulong.TryParse(id, out ulong idCast))
{
yield return idCast;
}
else
{
yield return 0;
}
}
}
public static IEnumerable<ulong> WorkshopUrlsToIds(IEnumerable<string> urls)
{
return urls.Select((u) =>
{
if (string.IsNullOrEmpty(u))
{
return (ulong)0;
}
else
{
return GetWorkshopItemIDFromUrl(u);
}
});
}
public static ulong GetWorkshopItemIDFromUrl(string url)
{
try
{
Uri uri = new Uri(url);
string idStr = HttpUtility.ParseQueryString(uri.Query)["id"];
if (ulong.TryParse(idStr, out ulong id))
{
return id;
}
}
catch (Exception e)
{
DebugConsole.ThrowError("Failed to get Workshop item ID from the url \"" + url + "\"!", e);
}
return 0;
}
public static UInt64 SteamIDStringToUInt64(string str)
{
if (string.IsNullOrWhiteSpace(str)) { return 0; }
UInt64 retVal;
if (str.StartsWith("STEAM64_", StringComparison.InvariantCultureIgnoreCase)) { str = str.Substring(8); }
if (UInt64.TryParse(str, out retVal) && retVal >(1<<52)) { return retVal; }
if (!str.StartsWith("STEAM_", StringComparison.InvariantCultureIgnoreCase)) { return 0; }
string[] split = str.Substring(6).Split(':');
if (split.Length != 3) { return 0; }
if (!UInt64.TryParse(split[0], out UInt64 universe)) { return 0; }
if (!UInt64.TryParse(split[1], out UInt64 y)) { return 0; }
if (!UInt64.TryParse(split[2], out UInt64 accountNumber)) { return 0; }
UInt64 accountInstance = 1; UInt64 accountType = 1;
return (universe << 56) | (accountType << 52) | (accountInstance << 32) | (accountNumber << 1) | y;
}
public static string SteamIDUInt64ToString(UInt64 uint64)
{
UInt64 y = uint64 & 0x1;
UInt64 accountNumber = (uint64 >> 1) & 0x7fffffff;
UInt64 universe = (uint64 >> 56) & 0xff;
string retVal = "STEAM_" + universe.ToString() + ":" + y.ToString() + ":" + accountNumber.ToString();
if (SteamIDStringToUInt64(retVal) != uint64) { return "STEAM64_" + uint64.ToString(); }
return retVal;
}
}
}
#endif
@@ -1,19 +1,11 @@
using Barotrauma.Networking;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Voting
{
private bool allowSubVoting, allowModeVoting;
public bool AllowVoteKick = true;
public bool AllowEndVoting = true;
public bool VoteRunning = false;
public enum VoteState { None = 0, Started = 1, Running = 2, Passed = 3, Failed = 4 };
private IReadOnlyDictionary<T, int> GetVoteCounts<T>(VoteType voteType, List<Client> voters)
@@ -39,12 +31,12 @@ namespace Barotrauma
public T HighestVoted<T>(VoteType voteType, List<Client> voters)
{
if (voteType == VoteType.Sub && !AllowSubVoting) return default(T);
if (voteType == VoteType.Mode && !AllowModeVoting) return default(T);
if (voteType == VoteType.Sub && !GameMain.NetworkMember.ServerSettings.AllowSubVoting) { return default; }
if (voteType == VoteType.Mode && !GameMain.NetworkMember.ServerSettings.AllowModeVoting) { return default; }
IReadOnlyDictionary<T, int> voteList = GetVoteCounts<T>(voteType, voters);
T selected = default(T);
T selected = default;
int highestVotes = 0;
foreach (KeyValuePair<T, int> votable in voteList)
{
@@ -71,11 +63,13 @@ namespace Barotrauma
{
client.ResetVotes();
}
GameMain.NetworkMember.EndVoteCount = 0;
GameMain.NetworkMember.EndVoteMax = 0;
#if CLIENT
foreach (VoteType voteType in Enum.GetValues(typeof(VoteType)))
{
SetVoteCountYes(voteType, 0);
SetVoteCountNo(voteType, 0);
SetVoteCountMax(voteType, 0);
}
UpdateVoteTexts(connectedClients, VoteType.Mode);
UpdateVoteTexts(connectedClients, VoteType.Sub);
#endif