(61d00a474) v0.9.7.1

This commit is contained in:
Regalis
2020-03-04 13:04:10 +01:00
parent 3c50efa5c9
commit 3c09ebe02f
5086 changed files with 786063 additions and 295871 deletions
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma.Networking
{
partial class BannedPlayer
{
public string Name;
public string IP; public bool IsRangeBan;
public UInt64 SteamID;
public string Reason;
public DateTime? ExpirationTime;
public UInt16 UniqueIdentifier;
}
partial class BanList
{
private readonly List<BannedPlayer> bannedPlayers;
public IEnumerable<string> BannedNames
{
get { return bannedPlayers.Select(bp => bp.Name); }
}
public IEnumerable<string> BannedIPs
{
get { return bannedPlayers.Select(bp => bp.IP); }
}
partial void InitProjectSpecific();
public BanList()
{
bannedPlayers = new List<BannedPlayer>();
InitProjectSpecific();
}
public static string ToRange(string ip)
{
for (int i = ip.Length - 1; i > 0; i--)
{
if (ip[i] == '.')
{
ip = ip.Substring(0, i) + ".x";
break;
}
}
return ip;
}
}
}
@@ -0,0 +1,249 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
using System.Text;
namespace Barotrauma.Networking
{
public enum ChatMessageType
{
Default, Error, Dead, Server, Radio, Private, Console, MessageBox, Order, ServerLog, ServerMessageBox
}
partial class ChatMessage
{
public const int MaxLength = 150;
public const int MaxMessagesPerPacket = 10;
public const float SpeakRange = 2000.0f;
private static readonly string dateTimeFormatLongTimePattern = System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern;
public static Color[] MessageColor =
{
new Color(190, 198, 205), //default
new Color(204, 74, 78), //error
new Color(136, 177, 255), //dead
new Color(157, 225, 160), //server
new Color(238, 208, 0), //radio
new Color(64, 240, 89), //private
new Color(255, 255, 255), //console
new Color(255, 255, 255), //messagebox
new Color(255, 128, 0) //order
};
public readonly string Text;
private string translatedText;
public string TranslatedText
{
get
{
if (Type.HasFlag(ChatMessageType.Server) || Type.HasFlag(ChatMessageType.Error) || Type.HasFlag(ChatMessageType.ServerLog))
{
if (translatedText == null || translatedText.Length == 0)
{
translatedText = TextManager.GetServerMessage(Text);
}
return translatedText;
}
else
{
return Text;
}
}
}
public ChatMessageType Type;
public readonly Character Sender;
public readonly string SenderName;
public Color Color
{
get { return MessageColor[(int)Type]; }
}
public static string GetTimeStamp()
{
return $"[{DateTime.Now.ToString(dateTimeFormatLongTimePattern)}] ";
}
public string TextWithSender
{
get
{
return string.IsNullOrWhiteSpace(SenderName) ? TranslatedText : SenderName + ": " + TranslatedText;
}
}
public static UInt16 LastID = 0;
public UInt16 NetStateID
{
get;
set;
}
protected ChatMessage(string senderName, string text, ChatMessageType type, Character sender)
{
Text = text;
Type = type;
Sender = sender;
SenderName = senderName;
}
public static ChatMessage Create(string senderName, string text, ChatMessageType type, Character sender)
{
return new ChatMessage(senderName, text, type, sender);
}
public static string GetChatMessageCommand(string message, out string messageWithoutCommand)
{
messageWithoutCommand = message;
int separatorIndex = message.IndexOf(";");
if (separatorIndex == -1) return "";
//int colonIndex = message.IndexOf(":");
string command = "";
try
{
command = message.Substring(0, separatorIndex);
command = command.Trim();
}
catch
{
return command;
}
messageWithoutCommand = message.Substring(separatorIndex + 1, message.Length - separatorIndex - 1).TrimStart();
return command;
}
public static float GetGarbleAmount(Entity listener, Entity sender, float range, float obstructionmult = 2.0f)
{
if (listener == null || sender == null)
{
return 0.0f;
}
if (listener.WorldPosition == sender.WorldPosition) { return 0.0f; }
float dist = Vector2.Distance(listener.WorldPosition, sender.WorldPosition);
if (dist > range) { return 1.0f; }
Hull listenerHull = listener == null ? null : Hull.FindHull(listener.WorldPosition);
Hull sourceHull = sender == null ? null : Hull.FindHull(sender.WorldPosition);
if (sourceHull != listenerHull)
{
if (Submarine.CheckVisibility(listener.SimPosition, sender.SimPosition) != null) dist = (dist + 100f) * obstructionmult;
}
if (dist > range) { return 1.0f; }
return dist / range;
}
public string ApplyDistanceEffect(Character listener)
{
if (Sender == null) return Text;
return ApplyDistanceEffect(listener, Sender, Text, SpeakRange);
}
public static string ApplyDistanceEffect(Entity listener, Entity sender, string text, float range, float obstructionmult = 2.0f)
{
return ApplyDistanceEffect(text, GetGarbleAmount(listener, sender, range, obstructionmult));
}
public static string ApplyDistanceEffect(string text, float garbleAmount)
{
if (garbleAmount < 0.3f) return text;
if (garbleAmount >= 1.0f) return "";
int startIndex = Math.Max(text.IndexOf(':') + 1, 1);
StringBuilder sb = new StringBuilder(text.Length);
for (int i = 0; i < text.Length; i++)
{
sb.Append((i > startIndex && Rand.Range(0.0f, 1.0f) < garbleAmount) ? '-' : text[i]);
}
return sb.ToString();
}
public static string ApplyDistanceEffect(string message, ChatMessageType type, Character sender, Character receiver)
{
if (sender == null) return "";
switch (type)
{
case ChatMessageType.Default:
if (receiver != null && !receiver.IsDead)
{
return ApplyDistanceEffect(receiver, sender, message, SpeakRange * (1.0f - sender.SpeechImpediment / 100.0f), 3.0f);
}
break;
case ChatMessageType.Radio:
case ChatMessageType.Order:
if (receiver != null && !receiver.IsDead)
{
var receiverItem = receiver.Inventory?.Items.FirstOrDefault(i => i?.GetComponent<WifiComponent>() != null);
//character doesn't have a radio -> don't send
if (receiverItem == null || !receiver.HasEquippedItem(receiverItem)) return "";
var senderItem = sender.Inventory?.Items.FirstOrDefault(i => i?.GetComponent<WifiComponent>() != null);
if (senderItem == null || !sender.HasEquippedItem(senderItem)) return "";
var receiverRadio = receiverItem.GetComponent<WifiComponent>();
var senderRadio = senderItem.GetComponent<WifiComponent>();
if (!receiverRadio.CanReceive(senderRadio)) return "";
string msg = ApplyDistanceEffect(receiverItem, senderItem, message, senderRadio.Range);
if (sender.SpeechImpediment > 0.0f)
{
//speech impediment doesn't reduce the range when using a radio, but adds extra garbling
msg = ApplyDistanceEffect(msg, sender.SpeechImpediment / 100.0f);
}
return msg;
}
break;
}
return message;
}
public int EstimateLengthBytesClient()
{
int length = 1 + //(byte)ServerNetObject.CHAT_MESSAGE
2 + //(UInt16)NetStateID
Encoding.UTF8.GetBytes(Text).Length + 2;
return length;
}
public static bool CanUseRadio(Character sender)
{
return CanUseRadio(sender, out _);
}
public static bool CanUseRadio(Character sender, out WifiComponent radio)
{
radio = null;
if (sender?.Inventory == null || sender.Removed) { return false; }
radio = sender.Inventory.Items.FirstOrDefault(i => i?.GetComponent<WifiComponent>() != null)?.GetComponent<WifiComponent>();
if (radio?.Item == null) { return false; }
return sender.HasEquippedItem(radio.Item) && radio.CanTransmit();
}
}
}
@@ -0,0 +1,298 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Barotrauma.Networking
{
static partial class ChildServerRelay
{
private static Stream writeStream;
private static Stream readStream;
private static volatile bool shutDown;
public static bool HasShutDown
{
get { return shutDown; }
}
private static ManualResetEvent writeManualResetEvent;
private static byte[] tempBytes;
private enum ReadState
{
WaitingForPacketStart,
WaitingForPacketEnd
};
private static ReadState readState;
private static byte[] readIncBuf;
private static int readIncOffset;
private static int readIncTotal;
private static Queue<byte[]> msgsToWrite;
private static Queue<byte[]> msgsToRead;
private static Thread readThread;
private static Thread writeThread;
private static CancellationTokenSource readCancellationToken;
private static void PrivateStart()
{
readState = ReadState.WaitingForPacketStart;
readIncOffset = 0;
readIncTotal = 0;
tempBytes = new byte[MsgConstants.MTU * 2];
msgsToWrite = new Queue<byte[]>();
msgsToRead = new Queue<byte[]>();
shutDown = false;
readCancellationToken = new CancellationTokenSource();
writeManualResetEvent = new ManualResetEvent(false);
readThread = new Thread(UpdateRead)
{
Name = "ChildServerRelay.ReadThread",
IsBackground = true
};
writeThread = new Thread(UpdateWrite)
{
Name = "ChildServerRelay.WriteThread",
IsBackground = true
};
readThread.Start();
writeThread.Start();
}
private static void PrivateShutDown()
{
shutDown = true;
writeManualResetEvent?.Set();
readCancellationToken?.Cancel();
readThread?.Join(); readThread = null;
writeThread?.Join(); writeThread = null;
readCancellationToken?.Dispose();
readCancellationToken = null;
readStream?.Dispose(); readStream = null;
writeStream?.Dispose(); writeStream = null;
msgsToRead?.Clear(); msgsToWrite?.Clear();
}
private static int ReadIncomingMsgs()
{
Task<int> readTask = readStream?.ReadAsync(tempBytes, 0, tempBytes.Length, readCancellationToken.Token);
TimeSpan ts = TimeSpan.FromMilliseconds(100);
for (int i = 0; i < 150; i++)
{
if (shutDown)
{
readCancellationToken?.Cancel();
shutDown = true;
return -1;
}
if ((readTask?.IsCompleted ?? true) || (readTask?.Wait(ts) ?? true))
{
break;
}
}
if (readTask == null || !readTask.IsCompleted)
{
readCancellationToken?.Cancel();
shutDown = true;
return -1;
}
if (readTask.Status != TaskStatus.RanToCompletion)
{
shutDown = true;
return -1;
}
return readTask.Result;
}
private static void UpdateRead()
{
while (!shutDown)
{
#if SERVER
if (!((readStream as AnonymousPipeClientStream)?.IsConnected ?? false))
{
shutDown = true;
return;
}
#else
if (!((readStream as AnonymousPipeServerStream)?.IsConnected ?? false))
{
shutDown = true;
return;
}
#endif
int readLen = ReadIncomingMsgs();
if (readLen < 0) { shutDown = true; return; }
int procIndex = 0;
while (procIndex < readLen)
{
if (readState == ReadState.WaitingForPacketStart)
{
readIncTotal = tempBytes[procIndex];
procIndex++;
if (procIndex >= readLen)
{
readLen = ReadIncomingMsgs();
if (readLen < 0) { shutDown = true; return; }
procIndex = 0;
}
readIncTotal |= (tempBytes[procIndex] << 8);
procIndex++;
if (readIncTotal <= 0) { continue; }
readIncOffset = 0;
readIncBuf = new byte[readIncTotal];
readState = ReadState.WaitingForPacketEnd;
}
else if (readState == ReadState.WaitingForPacketEnd)
{
if ((readIncTotal - readIncOffset) > (readLen - procIndex))
{
Array.Copy(tempBytes, procIndex, readIncBuf, readIncOffset, readLen - procIndex);
readIncOffset += (readLen - procIndex);
procIndex = readLen;
}
else
{
Array.Copy(tempBytes, procIndex, readIncBuf, readIncOffset, readIncTotal - readIncOffset);
procIndex += (readIncTotal - readIncOffset);
readIncOffset = readIncTotal;
lock (msgsToRead)
{
msgsToRead.Enqueue(readIncBuf);
}
readIncBuf = null;
readState = ReadState.WaitingForPacketStart;
}
}
if (shutDown) { break; }
}
Thread.Yield();
}
}
private static void UpdateWrite()
{
while (!shutDown)
{
#if SERVER
if (!((writeStream as AnonymousPipeClientStream)?.IsConnected ?? false))
{
shutDown = true;
return;
}
#else
if (!((writeStream as AnonymousPipeServerStream)?.IsConnected ?? false))
{
shutDown = true;
return;
}
#endif
bool msgAvailable; byte[] msg;
lock (msgsToWrite)
{
msgAvailable = msgsToWrite.TryDequeue(out msg);
}
while (msgAvailable)
{
byte[] lengthBytes = new byte[2];
lengthBytes[0] = (byte)(msg.Length & 0xFF);
lengthBytes[1] = (byte)((msg.Length >> 8) & 0xFF);
msg = lengthBytes.Concat(msg).ToArray();
try
{
writeStream?.Write(msg, 0, msg.Length);
}
catch (IOException e)
{
shutDown = true;
break;
}
if (shutDown) { break; }
lock (msgsToWrite)
{
msgAvailable = msgsToWrite.TryDequeue(out msg);
}
}
if (!shutDown)
{
writeManualResetEvent.Reset();
if (!writeManualResetEvent.WaitOne(1000))
{
if (shutDown)
{
return;
}
try
{
//heartbeat to keep the other end alive
byte[] lengthBytes = new byte[2];
lengthBytes[0] = (byte)0;
lengthBytes[1] = (byte)0;
writeStream?.Write(lengthBytes, 0, 2);
}
catch (IOException e)
{
shutDown = true;
break;
}
}
}
}
}
public static void Write(byte[] msg)
{
if (shutDown) { return; }
lock (msgsToWrite)
{
msgsToWrite.Enqueue(msg);
writeManualResetEvent.Set();
}
}
public static bool Read(out byte[] msg)
{
if (shutDown) { msg = null; return false; }
lock (msgsToRead)
{
return msgsToRead.TryDequeue(out msg);
}
}
}
}
@@ -0,0 +1,261 @@
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma.Networking
{
partial class Client : IDisposable
{
public const int MaxNameLength = 20;
public string Name; public UInt16 NameID;
public byte ID;
public UInt64 SteamID;
public string PreferredJob;
public Character.TeamType TeamID;
private Character character;
public Character Character
{
get { return character; }
set
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
GameMain.NetworkMember.LastClientListUpdateID++;
}
else
{
if (value!=null)
{
DebugConsole.NewMessage(value.Name, Microsoft.Xna.Framework.Color.Yellow);
}
}
character = value;
if (character != null)
{
HasSpawned = true;
#if CLIENT
GameMain.GameSession?.CrewManager?.SetPlayerVoiceIconState(this, muted, mutedLocally);
if (character == GameMain.Client.Character && GameMain.Client.SpawnAsTraitor)
{
character.IsTraitor = true;
character.TraitorCurrentObjective = GameMain.Client.TraitorFirstObjective;
}
#endif
}
}
}
private Vector2 spectate_position;
public Vector2? SpectatePos
{
get
{
if (character == null || character.IsDead)
{
return spectate_position;
}
else
{
return null;
}
}
set
{
spectate_position = value.Value;
}
}
private bool muted;
public bool Muted
{
get { return muted; }
set
{
if (muted == value) { return; }
muted = value;
#if CLIENT
GameMain.NetLobbyScreen.SetPlayerVoiceIconState(this, muted, mutedLocally);
GameMain.GameSession?.CrewManager?.SetPlayerVoiceIconState(this, muted, mutedLocally);
#endif
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
GameMain.NetworkMember.LastClientListUpdateID++;
}
}
}
public VoipQueue VoipQueue
{
get;
private set;
}
public bool InGame;
public bool HasSpawned; //has the client spawned as a character during the current round
private List<Client> kickVoters;
public HashSet<string> GivenAchievements = new HashSet<string>();
public ClientPermissions Permissions = ClientPermissions.None;
public List<DebugConsole.Command> PermittedConsoleCommands
{
get;
private set;
}
private object[] votes;
public int KickVoteCount
{
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)
{
this.Name = name;
this.ID = ID;
PermittedConsoleCommands = new List<DebugConsole.Command>();
kickVoters = new List<Client>();
votes = new object[Enum.GetNames(typeof(VoteType)).Length];
InitProjSpecific();
}
public T GetVote<T>(VoteType voteType)
{
return (votes[(int)voteType] is T) ? (T)votes[(int)voteType] : default(T);
}
public void SetVote(VoteType voteType, object value)
{
votes[(int)voteType] = value;
}
public void ResetVotes()
{
for (int i = 0; i < votes.Length; i++)
{
votes[i] = null;
}
}
public void AddKickVote(Client voter)
{
if (!kickVoters.Contains(voter)) kickVoters.Add(voter);
}
public void RemoveKickVote(Client voter)
{
kickVoters.Remove(voter);
}
public bool HasKickVoteFrom(Client voter)
{
return kickVoters.Contains(voter);
}
public bool HasKickVoteFromID(int id)
{
return kickVoters.Any(k => k.ID == id);
}
public static void UpdateKickVotes(List<Client> connectedClients)
{
foreach (Client client in connectedClients)
{
client.kickVoters.RemoveAll(voter => !connectedClients.Contains(voter));
}
}
public void WritePermissions(IWriteMessage msg)
{
msg.Write(ID);
msg.Write((UInt16)Permissions);
if (HasPermission(ClientPermissions.ConsoleCommands))
{
msg.Write((UInt16)PermittedConsoleCommands.Count);
foreach (DebugConsole.Command command in PermittedConsoleCommands)
{
msg.Write(command.names[0]);
}
}
}
public static void ReadPermissions(IReadMessage inc, out ClientPermissions permissions, out List<DebugConsole.Command> permittedCommands)
{
UInt16 permissionsInt = inc.ReadUInt16();
permissions = ClientPermissions.None;
permittedCommands = new List<DebugConsole.Command>();
try
{
permissions = (ClientPermissions)permissionsInt;
}
catch (InvalidCastException)
{
return;
}
if (permissions.HasFlag(ClientPermissions.ConsoleCommands))
{
UInt16 commandCount = inc.ReadUInt16();
for (int i = 0; i < commandCount; i++)
{
string commandName = inc.ReadString();
var consoleCommand = DebugConsole.Commands.Find(c => c.names.Contains(commandName));
if (consoleCommand != null)
{
permittedCommands.Add(consoleCommand);
}
}
}
}
public void ReadPermissions(IReadMessage inc)
{
ClientPermissions permissions = ClientPermissions.None;
List<DebugConsole.Command> permittedCommands = new List<DebugConsole.Command>();
ReadPermissions(inc, out permissions, out permittedCommands);
SetPermissions(permissions, permittedCommands);
}
public static string SanitizeName(string name)
{
name = name.Trim();
if (name.Length > MaxNameLength)
{
name = name.Substring(0, MaxNameLength);
}
string rName = "";
for (int i = 0; i < name.Length; i++)
{
rName += name[i] < 32 ? '?' : name[i];
}
return rName;
}
public void Dispose()
{
DisposeProjSpecific();
}
}
}
@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Networking
{
[Flags]
public enum ClientPermissions
{
None = 0x0,
ManageRound = 0x1,
Kick = 0x2,
Ban = 0x4,
Unban = 0x8,
SelectSub = 0x10,
SelectMode = 0x20,
ManageCampaign = 0x40,
ConsoleCommands = 0x80,
ServerLog = 0x100,
ManageSettings = 0x200,
ManagePermissions = 0x400,
KarmaImmunity = 0x800,
All = 0xFFF
}
class PermissionPreset
{
public static List<PermissionPreset> List = new List<PermissionPreset>();
public readonly string Name;
public readonly string 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", "");
string permissionsStr = element.GetAttributeString("permissions", "");
if (!Enum.TryParse(permissionsStr, out Permissions))
{
DebugConsole.ThrowError("Error in permission preset \"" + Name + "\" - " + permissionsStr + " is not a valid permission!");
}
PermittedCommands = new List<DebugConsole.Command>();
if (Permissions.HasFlag(ClientPermissions.ConsoleCommands))
{
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("command", StringComparison.OrdinalIgnoreCase)) { continue; }
string commandName = subElement.GetAttributeString("name", "");
DebugConsole.Command command = DebugConsole.FindCommand(commandName);
if (command == null)
{
#if SERVER
DebugConsole.ThrowError("Error in permission preset \"" + Name + "\" - " + commandName + "\" is not a valid console command.");
#endif
continue;
}
PermittedCommands.Add(command);
}
}
}
public static void LoadAll(string file)
{
if (!File.Exists(file)) { return; }
XDocument doc = XMLExtensions.TryLoadXml(file);
if (doc == null) { return; }
List.Clear();
foreach (XElement element in doc.Root.Elements())
{
List.Add(new PermissionPreset(element));
}
}
public bool MatchesPermissions(ClientPermissions permissions, List<DebugConsole.Command> permittedConsoleCommands)
{
return permissions == this.Permissions && PermittedCommands.SequenceEqual(permittedConsoleCommands);
}
}
}
@@ -0,0 +1,288 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
namespace Barotrauma
{
partial class EntitySpawner : Entity, IServerSerializable
{
private enum SpawnableType { Item, Character };
interface IEntitySpawnInfo
{
Entity Spawn();
void OnSpawned(Entity entity);
}
class ItemSpawnInfo : IEntitySpawnInfo
{
public readonly ItemPrefab Prefab;
public readonly Vector2 Position;
public readonly Inventory Inventory;
public readonly Submarine Submarine;
public readonly float Condition;
private readonly Action<Item> onSpawned;
public ItemSpawnInfo(ItemPrefab prefab, Vector2 worldPosition, Action<Item> onSpawned, float? condition = null)
{
Prefab = prefab ?? throw new ArgumentException("ItemSpawnInfo prefab cannot be null.");
Position = worldPosition;
Condition = condition ?? prefab.Health;
this.onSpawned = onSpawned;
}
public ItemSpawnInfo(ItemPrefab prefab, Vector2 position, Submarine sub, Action<Item> onSpawned, float? condition = null)
{
Prefab = prefab ?? throw new ArgumentException("ItemSpawnInfo prefab cannot be null.");
Position = position;
Submarine = sub;
Condition = condition ?? prefab.Health;
this.onSpawned = onSpawned;
}
public ItemSpawnInfo(ItemPrefab prefab, Inventory inventory, Action<Item> onSpawned, float? condition = null)
{
Prefab = prefab ?? throw new ArgumentException("ItemSpawnInfo prefab cannot be null.");
Inventory = inventory;
Condition = condition ?? prefab.Health;
this.onSpawned = onSpawned;
}
public Entity Spawn()
{
if (Prefab == null)
{
return null;
}
Item spawnedItem;
if (Inventory != null)
{
spawnedItem = new Item(Prefab, Vector2.Zero, null);
Inventory.TryPutItem(spawnedItem, null, spawnedItem.AllowedSlots);
}
else
{
spawnedItem = new Item(Prefab, Position, Submarine);
}
return spawnedItem;
}
public void OnSpawned(Entity spawnedItem)
{
if (!(spawnedItem is Item item)) { throw new ArgumentException($"The entity passed to ItemSpawnInfo.OnSpawned must be an Item (value was {spawnedItem?.ToString() ?? "null"})."); }
onSpawned?.Invoke(item);
}
}
class CharacterSpawnInfo : IEntitySpawnInfo
{
public readonly string identifier;
public readonly Vector2 Position;
public readonly Submarine Submarine;
private readonly Action<Character> onSpawned;
public CharacterSpawnInfo(string identifier, Vector2 worldPosition, Action<Character> onSpawn = null)
{
this.identifier = identifier ?? throw new ArgumentException("ItemSpawnInfo prefab cannot be null.");
Position = worldPosition;
this.onSpawned = onSpawn;
}
public CharacterSpawnInfo(string identifier, Vector2 position, Submarine sub, Action<Character> onSpawn = null)
{
this.identifier = identifier ?? throw new ArgumentException("ItemSpawnInfo prefab cannot be null.");
Position = position;
Submarine = sub;
this.onSpawned = onSpawn;
}
public Entity Spawn()
{
var character = string.IsNullOrEmpty(identifier) ? null :
Character.Create(identifier,
Submarine == null ? Position : Submarine.Position + Position,
ToolBox.RandomSeed(8), createNetworkEvent: false);
return character;
}
public void OnSpawned(Entity spawnedCharacter)
{
if (!(spawnedCharacter is Character character)) { throw new ArgumentException($"The entity passed to CharacterSpawnInfo.OnSpawned must be a Character (value was {spawnedCharacter?.ToString() ?? "null"})."); }
onSpawned?.Invoke(character);
}
}
private readonly Queue<IEntitySpawnInfo> spawnQueue;
private readonly Queue<Entity> removeQueue;
public class SpawnOrRemove
{
public readonly Entity Entity;
public readonly UInt16 OriginalID;
public readonly bool Remove = false;
public SpawnOrRemove(Entity entity, bool remove)
{
Entity = entity;
OriginalID = entity.ID;
Remove = remove;
}
}
public EntitySpawner()
: base(null)
{
spawnQueue = new Queue<IEntitySpawnInfo>();
removeQueue = new Queue<Entity>();
}
public override string ToString()
{
return "EntitySpawner";
}
public void AddToSpawnQueue(ItemPrefab itemPrefab, Vector2 worldPosition, float? condition = null, Action<Item> onSpawned = null)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (itemPrefab == null)
{
string errorMsg = "Attempted to add a null item to entity spawn queue.\n" + Environment.StackTrace;
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue1:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
}
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, worldPosition, onSpawned, condition));
}
public void AddToSpawnQueue(ItemPrefab itemPrefab, Vector2 position, Submarine sub, float? condition = null, Action<Item> onSpawned = null)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (itemPrefab == null)
{
string errorMsg = "Attempted to add a null item to entity spawn queue.\n" + Environment.StackTrace;
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue2:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
}
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, position, sub, onSpawned, condition));
}
public void AddToSpawnQueue(ItemPrefab itemPrefab, Inventory inventory, float? condition = null, Action<Item> onSpawned = null)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (itemPrefab == null)
{
string errorMsg = "Attempted to add a null item to entity spawn queue.\n" + Environment.StackTrace;
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue3:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
}
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, inventory, onSpawned, condition));
}
public void AddToSpawnQueue(string speciesName, Vector2 worldPosition, Action<Character> onSpawn = null)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (string.IsNullOrEmpty(speciesName))
{
string errorMsg = "Attempted to add an empty/null species name to entity spawn queue.\n" + Environment.StackTrace;
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue4:SpeciesNameNullOrEmpty", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
}
spawnQueue.Enqueue(new CharacterSpawnInfo(speciesName, worldPosition, onSpawn));
}
public void AddToSpawnQueue(string speciesName, Vector2 position, Submarine sub, Action<Character> onSpawn = null)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (string.IsNullOrEmpty(speciesName))
{
string errorMsg = "Attempted to add an empty/null species name to entity spawn queue.\n" + Environment.StackTrace;
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue5:SpeciesNameNullOrEmpty", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
}
spawnQueue.Enqueue(new CharacterSpawnInfo(speciesName, position, sub, onSpawn));
}
public void AddToRemoveQueue(Entity entity)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (removeQueue.Contains(entity) || entity.Removed || entity == null) { return; }
if (entity is Character)
{
Character character = entity as Character;
#if SERVER
if (GameMain.Server != null)
{
Client client = GameMain.Server.ConnectedClients.Find(c => c.Character == character);
if (client != null) GameMain.Server.SetClientCharacter(client, null);
}
#endif
}
removeQueue.Enqueue(entity);
}
public void AddToRemoveQueue(Item item)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (removeQueue.Contains(item) || item.Removed) { return; }
removeQueue.Enqueue(item);
if (item.ContainedItems == null) return;
foreach (Item containedItem in item.ContainedItems)
{
if (containedItem != null) AddToRemoveQueue(containedItem);
}
}
public void Update()
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
while (spawnQueue.Count > 0)
{
var entitySpawnInfo = spawnQueue.Dequeue();
var spawnedEntity = entitySpawnInfo.Spawn();
if (spawnedEntity != null)
{
CreateNetworkEventProjSpecific(spawnedEntity, false);
if (spawnedEntity is Item)
{
((Item)spawnedEntity).Condition = ((ItemSpawnInfo)entitySpawnInfo).Condition;
}
entitySpawnInfo.OnSpawned(spawnedEntity);
}
}
while (removeQueue.Count > 0)
{
var removedEntity = removeQueue.Dequeue();
if (removedEntity is Item item)
{
item.SendPendingNetworkUpdates();
}
CreateNetworkEventProjSpecific(removedEntity, true);
removedEntity.Remove();
}
}
partial void CreateNetworkEventProjSpecific(Entity entity, bool remove);
public void Reset()
{
removeQueue.Clear();
spawnQueue.Clear();
}
}
}
@@ -0,0 +1,17 @@
namespace Barotrauma.Networking
{
enum FileTransferStatus
{
NotStarted, Sending, Receiving, Finished, Canceled, Error
}
enum FileTransferMessageType
{
Unknown, Initiate, Data, TransferOnSameMachine, Cancel
}
enum FileTransferType
{
Submarine, CampaignSave
}
}
@@ -0,0 +1,30 @@
namespace Barotrauma.Networking
{
interface INetSerializable { }
/// <summary>
/// Interface for entities that the clients can send information of to the server
/// </summary>
interface IClientSerializable : INetSerializable
{
#if CLIENT
void ClientWrite(IWriteMessage msg, object[] extraData = null);
#endif
#if SERVER
void ServerRead(ClientNetObject type, IReadMessage msg, Client c);
#endif
}
/// <summary>
/// Interface for entities that the server can send information of to the clients
/// </summary>
interface IServerSerializable : INetSerializable
{
#if SERVER
void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null);
#endif
#if CLIENT
void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime);
#endif
}
}
@@ -0,0 +1,190 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace Barotrauma
{
partial class KarmaManager : ISerializableEntity
{
public static readonly string ConfigFile = "Data" + Path.DirectorySeparatorChar + "karmasettings.xml";
public string Name => "KarmaManager";
public Dictionary<string, SerializableProperty> SerializableProperties { get; private set; }
[Serialize(true, true)]
public bool ResetKarmaBetweenRounds { get; set; }
[Serialize(0.1f, true)]
public float KarmaDecay { get; set; }
[Serialize(50.0f, true)]
public float KarmaDecayThreshold { get; set; }
[Serialize(0.15f, true)]
public float KarmaIncrease { get; set; }
[Serialize(50.0f, true)]
public float KarmaIncreaseThreshold { get; set; }
[Serialize(0.05f, true)]
public float StructureRepairKarmaIncrease { get; set; }
[Serialize(0.1f, true)]
public float StructureDamageKarmaDecrease { get; set; }
[Serialize(30.0f, true)]
public float MaxStructureDamageKarmaDecreasePerSecond { get; set; }
[Serialize(0.03f, true)]
public float ItemRepairKarmaIncrease { get; set; }
[Serialize(0.5f, true)]
public float ReactorOverheatKarmaDecrease { get; set; }
[Serialize(30.0f, true)]
public float ReactorMeltdownKarmaDecrease { get; set; }
[Serialize(0.1f, true)]
public float DamageEnemyKarmaIncrease { get; set; }
[Serialize(0.2f, true)]
public float HealFriendlyKarmaIncrease { get; set; }
[Serialize(0.25f, true)]
public float DamageFriendlyKarmaDecrease { get; set; }
[Serialize(1.0f, true)]
public float ExtinguishFireKarmaIncrease { get; set; }
private int allowedWireDisconnectionsPerMinute;
[Serialize(5, true)]
public int AllowedWireDisconnectionsPerMinute
{
get { return allowedWireDisconnectionsPerMinute; }
set { allowedWireDisconnectionsPerMinute = Math.Max(0, value); }
}
[Serialize(6.0f, true)]
public float WireDisconnectionKarmaDecrease { get; set; }
[Serialize(0.15f, true)]
public float SteerSubKarmaIncrease { get; set; }
[Serialize(15.0f, true)]
public float SpamFilterKarmaDecrease { get; set; }
[Serialize(40.0f, true)]
public float HerpesThreshold { get; set; }
[Serialize(1.0f, true)]
public float KickBanThreshold { get; set; }
[Serialize(0, true)]
public int KicksBeforeBan { get; set; }
[Serialize(10.0f, true)]
public float KarmaNotificationInterval { get; set; }
[Serialize(120.0f, true)]
public float AllowedRetaliationTime { get; set; }
private readonly AfflictionPrefab herpesAffliction;
public Dictionary<string, XElement> Presets = new Dictionary<string, XElement>();
public KarmaManager()
{
XDocument doc = null;
int maxLoadRetries = 4;
for (int i = 0; i <= maxLoadRetries; i++)
{
try
{
doc = XMLExtensions.TryLoadXml(ConfigFile);
break;
}
catch (IOException)
{
if (i == maxLoadRetries) { break; }
DebugConsole.NewMessage("Opening karma settings file \"" + ConfigFile + "\" failed, retrying in 250 ms...");
System.Threading.Thread.Sleep(250);
}
}
SerializableProperties = SerializableProperty.DeserializeProperties(this, doc?.Root);
if (doc?.Root != null)
{
Presets["custom"] = doc.Root;
foreach (XElement subElement in doc.Root.Elements())
{
string presetName = subElement.GetAttributeString("name", "");
Presets[presetName.ToLowerInvariant()] = subElement;
}
SelectPreset(GameMain.NetworkMember?.ServerSettings?.KarmaPreset ?? "default");
}
herpesAffliction = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier == "spaceherpes");
}
public void SelectPreset(string presetName)
{
if (string.IsNullOrEmpty(presetName)) { return; }
presetName = presetName.ToLowerInvariant();
if (Presets.ContainsKey(presetName))
{
SerializableProperty.DeserializeProperties(this, Presets[presetName]);
}
else if (Presets.ContainsKey("custom"))
{
SerializableProperty.DeserializeProperties(this, Presets["custom"]);
}
}
public void SaveCustomPreset()
{
if (Presets.ContainsKey("custom"))
{
SerializableProperty.SerializeProperties(this, Presets["custom"], saveIfDefault: true);
}
}
public void Save()
{
XDocument doc = new XDocument(new XElement(Name));
foreach (KeyValuePair<string, XElement> preset in Presets)
{
doc.Root.Add(preset.Value);
}
XmlWriterSettings settings = new XmlWriterSettings
{
Indent = true,
NewLineOnAttributes = true
};
int maxLoadRetries = 4;
for (int i = 0; i <= maxLoadRetries; i++)
{
try
{
using (var writer = XmlWriter.Create(ConfigFile, settings))
{
doc.Save(writer);
}
break;
}
catch (IOException)
{
if (i == maxLoadRetries) { throw; }
DebugConsole.NewMessage("Saving karma settings file file \"" + ConfigFile + "\" failed, retrying in 250 ms...");
System.Threading.Thread.Sleep(250);
continue;
}
}
}
}
}
@@ -0,0 +1,15 @@
namespace Barotrauma.Networking
{
static class NetBufferExtensions
{
//public static void WriteEnum(this NetBuffer buffer, Enum value)
//{
// buffer.WriteRangedInteger(0, Enum.GetValues(value.GetType()).Length - 1, Convert.ToInt32(value));
//}
//public static TEnum ReadEnum<TEnum>(this NetBuffer buffer)
//{
// return (TEnum)(object)buffer.ReadRangedInteger(0, Enum.GetValues(typeof(TEnum)).Length - 1);
//}
}
}
@@ -0,0 +1,104 @@
using Microsoft.Xna.Framework;
using System;
namespace Barotrauma.Networking
{
static class NetConfig
{
public const int DefaultPort = 27015;
public const int DefaultQueryPort = 27016;
public const int MaxPlayers = 16;
public const int ServerNameMaxLength = 60;
public static string MasterServerUrl = GameMain.Config.MasterServerUrl;
//if a Character is further than this from the sub and the players, the server will disable it
//(in display units)
public const float DisableCharacterDist = 22000.0f;
public const float DisableCharacterDistSqr = DisableCharacterDist * DisableCharacterDist;
//the character needs to get this close to be re-enabled
public const float EnableCharacterDist = 20000.0f;
public const float EnableCharacterDistSqr = EnableCharacterDist * EnableCharacterDist;
public const float MaxPhysicsBodyVelocity = 64.0f;
public const float MaxPhysicsBodyAngularVelocity = 16.0f;
public const float MaxHealthUpdateInterval = 2.0f;
public const float MaxHealthUpdateIntervalDead = 10.0f;
public const float HighPrioCharacterPositionUpdateDistance = 1000.0f;
public const float LowPrioCharacterPositionUpdateDistance = 10000.0f;
public const float HighPrioCharacterPositionUpdateInterval = 0.0f;
public const float LowPrioCharacterPositionUpdateInterval = 1.0f;
//this should be higher than LowPrioCharacterPositionUpdateInterval,
//otherwise the clients may freeze characters even though the server hasn't actually stopped sending position updates
public const float FreezeCharacterIfPositionDataMissingDelay = 2.0f;
public const float DisableCharacterIfPositionDataMissingDelay = 3.5f;
public const float DeleteDisconnectedTime = 20.0f;
public const float ItemConditionUpdateInterval = 0.15f;
public const float LevelObjectUpdateInterval = 0.5f;
public const float HullUpdateInterval = 0.5f;
public const float SparseHullUpdateInterval = 5.0f;
public const float HullUpdateDistance = 20000.0f;
public const int MaxEventPacketsPerUpdate = 4;
/// <summary>
/// Interpolates the positional error of a physics body towards zero.
/// </summary>
public static Vector2 InterpolateSimPositionError(Vector2 simPositionError, float? smoothingFactor = null)
{
float lengthSqr = simPositionError.LengthSquared();
//correct immediately if the error is very large
if (lengthSqr > 100.0f) { return Vector2.Zero; }
float positionSmoothingFactor = smoothingFactor ?? MathHelper.Lerp(0.95f, 0.8f, MathHelper.Clamp(lengthSqr, 0.0f, 1.0f));
return simPositionError *= positionSmoothingFactor;
}
/// <summary>
/// Interpolates the rotational error of a physics body towards zero.
/// </summary>
public static float InterpolateRotationError(float rotationError)
{
//correct immediately if the error is very large
if (rotationError > MathHelper.TwoPi) { return 0.0f; }
float rotationSmoothingFactor = MathHelper.Lerp(0.95f, 0.8f, Math.Min(Math.Abs(rotationError), 1.0f));
return rotationError *= rotationSmoothingFactor;
}
/// <summary>
/// Interpolates the cursor position error towards zero.
/// </summary>
public static Vector2 InterpolateCursorPositionError(Vector2 cursorPositionError)
{
float lengthSqr = cursorPositionError.LengthSquared();
//correct immediately if the error is very large
if (lengthSqr > 1000.0f) { return Vector2.Zero; }
return cursorPositionError *= 0.7f;
}
public static Vector2 Quantize(Vector2 value, float min, float max, int numberOfBits)
{
return new Vector2(
Quantize(value.X, min, max, numberOfBits),
Quantize(value.Y, min, max, numberOfBits));
}
public static float Quantize(float value, float min, float max, int numberOfBits)
{
float step = (max - min) / (1 << (numberOfBits + 1));
if (Math.Abs(value) < step + 0.00001f)
{
return 0.0f;
}
return MathUtils.RoundTowardsClosest(MathHelper.Clamp(value, min, max), step);
}
}
}
@@ -0,0 +1,83 @@
using System;
namespace Barotrauma.Networking
{
abstract class NetEntityEvent
{
public enum Type
{
Invalid,
ComponentState,
InventoryState,
Status,
Treatment,
ApplyStatusEffect,
ChangeProperty,
Control,
UpdateSkills,
Combine
}
public readonly Entity Entity;
public readonly UInt16 ID;
public UInt16 EntityID
{
get;
private set;
}
//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 bool Sent;
protected NetEntityEvent(INetSerializable serializableEntity, UInt16 id)
{
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)
{
this.Data = data;
}
public bool IsDuplicate(NetEntityEvent other)
{
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;
}
}
}
@@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
namespace Barotrauma.Networking
{
abstract class NetEntityEventManager
{
public const int MaxEventBufferLength = 1024;
/// <summary>
/// Write the events to the outgoing message. The recipient parameter is only needed for ServerEntityEventManager
/// </summary>
protected void Write(IWriteMessage msg, List<NetEntityEvent> eventsToSync, out List<NetEntityEvent> sentEvents, Client recipient = null)
{
//write into a temporary buffer so we can write the number of events before the actual data
IWriteMessage tempBuffer = new WriteOnlyMessage();
sentEvents = new List<NetEntityEvent>();
int eventCount = 0;
foreach (NetEntityEvent e in eventsToSync)
{
//write into a temporary buffer so we can write the length before the actual data
IWriteMessage tempEventBuffer = new WriteOnlyMessage();
try
{
WriteEvent(tempEventBuffer, e, recipient);
}
catch (Exception exception)
{
DebugConsole.ThrowError("Failed to write an event for the entity \"" + e.Entity + "\"", exception);
GameAnalyticsManager.AddErrorEventOnce("NetEntityEventManager.Write:WriteFailed" + e.Entity.ToString(),
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Failed to write an event for the entity \"" + e.Entity + "\"\n" + exception.StackTrace);
//write an empty event to avoid messing up IDs
//(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;
}
//the length of the data is written as a byte, so the data needs to be less than 255 bytes long
if (tempEventBuffer.LengthBytes > 255)
{
DebugConsole.ThrowError("Too much data in network event for entity \"" + e.Entity.ToString() + "\" (" + tempEventBuffer.LengthBytes + " bytes, event ID " + e.ID + ")");
GameAnalyticsManager.AddErrorEventOnce("NetEntityEventManager.Write:TooLong" + e.Entity.ToString(),
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Too much data in network event for entity \"" + e.Entity.ToString() + "\" (" + tempEventBuffer.LengthBytes + " bytes, event ID " + e.ID + ")");
//write an empty event to prevent breaking the event syncing
tempBuffer.Write(Entity.NullEntityID);
tempBuffer.WritePadBits();
eventCount++;
continue;
}
if (msg.LengthBytes + tempBuffer.LengthBytes + tempEventBuffer.LengthBytes > MaxEventBufferLength)
{
//no more room in this packet
break;
}
tempBuffer.Write(e.EntityID);
tempBuffer.Write((byte)tempEventBuffer.LengthBytes);
tempBuffer.Write(tempEventBuffer.Buffer, 0, tempEventBuffer.LengthBytes);
tempBuffer.WritePadBits();
sentEvents.Add(e);
eventCount++;
}
if (eventCount > 0)
{
msg.Write(eventsToSync[0].ID);
msg.Write((byte)eventCount);
msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes);
}
}
protected abstract void WriteEvent(IWriteMessage buffer, NetEntityEvent entityEvent, Client recipient = null);
}
}
@@ -0,0 +1,80 @@
using System;
using System.Diagnostics;
namespace Barotrauma.Networking
{
/// <summary>
/// Helper class for dealing with 16-bit IDs that wrap around ushort.MaxValue
/// </summary>
static class NetIdUtils
{
/// <summary>
/// Is newID more recent than oldID
/// </summary>
public static bool IdMoreRecent(ushort newID, ushort oldID)
{
uint id1 = newID;
uint id2 = oldID;
return
(id1 > id2) && (id1 - id2 <= ushort.MaxValue / 2)
||
(id2 > id1) && (id2 - id1 > ushort.MaxValue / 2);
}
public static ushort Clamp(ushort id, ushort min, ushort max)
{
if (IdMoreRecent(min, max))
{
throw new ArgumentException($"Min cannot be larger than max ({min}, {max})");
}
if (!IdMoreRecent(id, min))
{
return min;
}
else if (IdMoreRecent(id, max))
{
return max;
}
return id;
}
/// <summary>
/// Is the current ID valid given the previous ID and latest possible ID (not smaller than the previous ID or larger than the latest ID)
/// </summary>
public static bool IsValidId(ushort currentId, ushort previousId, ushort latestPossibleId)
{
//cannot be valid if more recent than the latest Id
if (IdMoreRecent(currentId, latestPossibleId)) { return false; }
//normally the id needs to be more recent than the previous id,
//but there's a special case when the previous id was 0:
// if a client reconnects mid-round and tries to jump from the unitialized state (0) back to some previous high id (> ushort.MaxValue / 2),
// this would normally get interpreted as trying to jump backwards, but in this case we'll allow it
return IdMoreRecent(currentId, previousId) || (previousId == 0 && currentId > ushort.MaxValue / 2);
}
#if DEBUG
public static void Test()
{
Debug.Assert(IdMoreRecent((ushort)2, (ushort)1));
Debug.Assert(IdMoreRecent((ushort)2, (ushort)(ushort.MaxValue - 5)));
Debug.Assert(!IdMoreRecent((ushort)ushort.MaxValue, (ushort)5));
Debug.Assert(Clamp((ushort)5, (ushort)1, (ushort)10) == 5);
Debug.Assert(Clamp((ushort)(ushort.MaxValue - 5), (ushort)(ushort.MaxValue - 2), (ushort)3) == (ushort)(ushort.MaxValue - 2));
Debug.Assert(IsValidId((ushort)10, (ushort)1, (ushort)10));
Debug.Assert(!IsValidId((ushort)11, (ushort)1, (ushort)10));
Debug.Assert(IsValidId((ushort)1, (ushort)(ushort.MaxValue - 5), (ushort)10));
Debug.Assert(!IsValidId((ushort)(ushort.MaxValue - 6), (ushort)(ushort.MaxValue - 5), (ushort)10));
Debug.Assert(IsValidId((ushort)0, (ushort)ushort.MaxValue - 100, (ushort)ushort.MaxValue));
Debug.Assert(!IsValidId((ushort)(ushort.MaxValue - 101), (ushort)ushort.MaxValue - 100, (ushort)ushort.MaxValue));
}
#endif
}
}
@@ -0,0 +1,267 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma.Networking
{
enum ClientPacketHeader
{
UPDATE_LOBBY, //update state in lobby
UPDATE_INGAME, //update state ingame
SERVER_SETTINGS, //change server settings
CAMPAIGN_SETUP_INFO,
FILE_REQUEST, //request a (submarine) file from the server
VOICE,
RESPONSE_STARTGAME, //tell the server whether you're ready to start
SERVER_COMMAND, //tell the server to end a round or kick/ban someone (special permissions required)
REQUEST_STARTGAMEFINALIZE, //tell the server you're ready to finalize round initialization
ERROR //tell the server that an error occurred
}
enum ClientNetObject
{
END_OF_MESSAGE, //self-explanatory
SYNC_IDS, //ids of the last changes the client knows about
CHAT_MESSAGE, //also self-explanatory
VOTE, //you get the idea
CHARACTER_INPUT,
ENTITY_STATE,
SPECTATING_POS
}
enum ClientNetError
{
MISSING_EVENT, //client was expecting a previous event
MISSING_ENTITY //client can't find an entity of a certain ID
}
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
UPDATE_LOBBY, //update state in lobby (votes and chat messages)
UPDATE_INGAME, //update state ingame (character input and chat messages)
PERMISSIONS, //tell the client which special permissions they have (if any)
ACHIEVEMENT, //give the client a steam achievement
CHEATS_ENABLED, //tell the clients whether cheats are on or off
CAMPAIGN_SETUP_INFO,
FILE_TRANSFER,
VOICE,
QUERY_STARTGAME, //ask the clients whether they're ready to start
STARTGAME, //start a new round
STARTGAMEFINALIZE, //finalize round initialization
ENDGAME,
TRAITOR_MESSAGE,
MISSION
}
enum ServerNetObject
{
END_OF_MESSAGE,
SYNC_IDS,
CHAT_MESSAGE,
VOTE,
CLIENT_LIST,
ENTITY_POSITION,
ENTITY_EVENT,
ENTITY_EVENT_INITIAL,
}
enum TraitorMessageType
{
Server,
ServerMessageBox,
Objective,
Console
}
enum VoteType
{
Unknown,
Sub,
Mode,
EndRound,
Kick,
StartRound
}
enum DisconnectReason
{
Unknown,
Banned,
Kicked,
ServerShutdown,
ServerCrashed,
ServerFull,
AuthenticationRequired,
SteamAuthenticationRequired,
SteamAuthenticationFailed,
SessionTaken,
TooManyFailedLogins,
NoName,
InvalidName,
NameTaken,
InvalidVersion,
MissingContentPackage,
IncompatibleContentPackage,
NotOnWhitelist,
ExcessiveDesyncOldEvent,
ExcessiveDesyncRemovedEvent,
SyncTimeout
}
abstract partial class NetworkMember
{
public UInt16 LastClientListUpdateID
{
get;
set;
}
public virtual bool IsServer
{
get { return false; }
}
public virtual bool IsClient
{
get { return false; }
}
public abstract void CreateEntityEvent(INetSerializable entity, object[] extraData = null);
#if DEBUG
public Dictionary<string, long> messageCount = new Dictionary<string, long>();
#endif
protected ServerSettings serverSettings;
protected TimeSpan updateInterval;
protected DateTime updateTimer;
public int EndVoteCount, EndVoteMax;
protected bool gameStarted;
protected RespawnManager respawnManager;
public bool ShowNetStats;
public float SimulatedRandomLatency, SimulatedMinimumLatency;
public float SimulatedLoss;
public float SimulatedDuplicatesChance;
public int TickRate
{
get { return serverSettings.TickRate; }
set
{
serverSettings.TickRate = MathHelper.Clamp(value, 1, 60);
updateInterval = new TimeSpan(0, 0, 0, 0, MathHelper.Clamp(1000 / serverSettings.TickRate, 1, 500));
}
}
public KarmaManager KarmaManager
{
get;
private set;
} = new KarmaManager();
public bool GameStarted
{
get { return gameStarted; }
}
public virtual List<Client> ConnectedClients
{
get { return null; }
}
public RespawnManager RespawnManager
{
get { return respawnManager; }
}
public ServerSettings ServerSettings
{
get { return serverSettings; }
}
public bool CanUseRadio(Character sender)
{
if (sender == null) return false;
var radio = sender.Inventory.Items.FirstOrDefault(i => i != null && i.GetComponent<WifiComponent>() != null);
if (radio == null || !sender.HasEquippedItem(radio)) return false;
var radioComponent = radio.GetComponent<WifiComponent>();
if (radioComponent == null) return false;
return radioComponent.HasRequiredContainedItems(sender, addMessage: false);
}
public void AddChatMessage(string message, ChatMessageType type, string senderName = "", Character senderCharacter = null)
{
AddChatMessage(ChatMessage.Create(senderName, message, type, senderCharacter));
}
public virtual void AddChatMessage(ChatMessage message)
{
if (string.IsNullOrEmpty(message.Text)) { return; }
if (message.Sender != null && !message.Sender.IsDead)
{
message.Sender.ShowSpeechBubble(2.0f, ChatMessage.MessageColor[(int)message.Type]);
}
}
public virtual void KickPlayer(string kickedName, string reason) { }
public virtual void BanPlayer(string kickedName, string reason, bool range = false, TimeSpan? duration = null) { }
public virtual void UnbanPlayer(string playerName, string playerIP) { }
public virtual void Update(float deltaTime) { }
public virtual void Disconnect() { }
/// <summary>
/// Check if the two version are compatible (= if they can play together in multiplayer).
/// Returns null if compatibility could not be determined (invalid/unknown version number).
/// </summary>
public static bool? IsCompatible(string myVersion, string remoteVersion)
{
if (string.IsNullOrEmpty(myVersion) || string.IsNullOrEmpty(remoteVersion)) { return null; }
if (!Version.TryParse(myVersion, out Version myVersionNumber)) { return null; }
if (!Version.TryParse(remoteVersion, out Version remoteVersionNumber)) { return null; }
return IsCompatible(myVersionNumber, remoteVersionNumber);
}
/// <summary>
/// Check if the two version are compatible (= if they can play together in multiplayer).
/// </summary>
public static bool IsCompatible(Version myVersion, Version remoteVersion)
{
//major.minor.build.revision
//revision number is ignored, other values have to match
return
myVersion.Major == remoteVersion.Major &&
myVersion.Minor == remoteVersion.Minor &&
myVersion.Build == remoteVersion.Build;
}
}
}
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Barotrauma.Networking
{
partial class OrderChatMessage : ChatMessage
{
public readonly Order Order;
//who was this order given to
public readonly Character TargetCharacter;
//which entity is this order referring to (hull, reactor, railgun controller, etc)
public readonly Entity TargetEntity;
//additional instructions (power up, fire at will, etc)
public readonly string OrderOption;
public OrderChatMessage(Order order, string orderOption, Entity targetEntity, Character targetCharacter, Character sender)
: this(order, orderOption,
order?.GetChatMessage(targetCharacter?.Name, sender?.CurrentHull?.DisplayName, givingOrderToSelf: targetCharacter == sender, orderOption: orderOption),
targetEntity, targetCharacter, sender)
{
}
public OrderChatMessage(Order order, string orderOption, string text, Entity targetEntity, Character targetCharacter, Character sender)
: base(sender?.Name, text, ChatMessageType.Order, sender)
{
Order = order;
OrderOption = orderOption;
TargetCharacter = targetCharacter;
TargetEntity = targetEntity;
}
}
}
@@ -0,0 +1,38 @@
using System;
namespace Barotrauma.Networking
{
public enum DeliveryMethod : byte
{
Unreliable = 0x0,
Reliable = 0x1,
ReliableOrdered = 0x2
}
public enum ConnectionInitialization : byte
{
//used by all peer implementations
SteamTicketAndVersion = 0x1,
ContentPackageOrder = 0x2,
Password = 0x3,
Success = 0x0,
//used only by SteamP2P implementations
ConnectionStarted = 0x4
}
[Flags]
public enum PacketHeader : byte
{
//used by all peer implementations
None = 0x0,
IsCompressed = 0x1,
IsConnectionInitializationStep = 0x2,
//used only by SteamP2P implementations
IsDisconnectMessage = 0x4,
IsServerMessage = 0x8,
IsHeartbeatMessage = 0x10
}
}
@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Barotrauma.Networking
{
public interface IReadMessage
{
bool ReadBoolean();
void ReadPadBits();
byte ReadByte();
UInt16 ReadUInt16();
Int16 ReadInt16();
UInt32 ReadUInt32();
Int32 ReadInt32();
UInt64 ReadUInt64();
Int64 ReadInt64();
Single ReadSingle();
Double ReadDouble();
UInt32 ReadVariableUInt32();
String ReadString();
int ReadRangedInteger(int min, int max);
Single ReadRangedSingle(Single min, Single max, int bitCount);
byte[] ReadBytes(int numberOfBytes);
int BitPosition { get; set; }
int BytePosition { get; }
byte[] Buffer { get; }
int LengthBits { get; set; }
int LengthBytes { get; }
NetworkConnection Sender { get; }
}
}
@@ -0,0 +1,32 @@
using System;
namespace Barotrauma.Networking
{
public interface IWriteMessage
{
void Write(bool val);
void WritePadBits();
void Write(byte val);
void Write(Int16 val);
void Write(UInt16 val);
void Write(Int32 val);
void Write(UInt32 val);
void Write(Int64 val);
void Write(UInt64 val);
void Write(Single val);
void Write(Double val);
void WriteVariableUInt32(UInt32 val);
void Write(string 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);
int BitPosition { get; set; }
int BytePosition { get; }
byte[] Buffer { get; }
int LengthBits { get; set; }
int LengthBytes { get; }
}
}
@@ -0,0 +1,946 @@
using Lidgren.Network;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Runtime.InteropServices;
using System.Text;
namespace Barotrauma.Networking
{
public static class MsgConstants
{
public const int MTU = 1200;
public const int CompressionThreshold = 1000;
public const int InitialBufferSize = 256;
public const int BufferOverAllocateAmount = 4;
}
/// <summary>
/// Utility struct for writing Singles
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct SingleUIntUnion
{
/// <summary>
/// Value as a 32 bit float
/// </summary>
[FieldOffset(0)]
public float SingleValue;
/// <summary>
/// Value as an unsigned 32 bit integer
/// </summary>
[FieldOffset(0)]
public uint UIntValue;
}
internal static class MsgWriter
{
internal static void Write(ref byte[] buf, ref int bitPos, bool val)
{
#if DEBUG
int resetPos = bitPos;
#endif
EnsureBufferSize(ref buf, bitPos + 1);
int bytePos = bitPos / 8;
int bitOffset = bitPos % 8;
byte bitFlag = (byte)(1 << bitOffset);
byte bitMask = (byte)((~bitFlag) & 0xff);
buf[bytePos] &= bitMask;
if (val) buf[bytePos] |= bitFlag;
bitPos++;
#if DEBUG
bool testVal = MsgReader.ReadBoolean(buf, ref resetPos);
if (testVal != val || resetPos != bitPos)
{
DebugConsole.ThrowError("Boolean written incorrectly! " + testVal + ", " + val + "; " + resetPos + ", " + bitPos);
}
#endif
}
internal static void WritePadBits(ref byte[] buf, ref int bitPos)
{
int bitOffset = bitPos % 8;
bitPos += ((8 - bitOffset) % 8);
EnsureBufferSize(ref buf, bitPos);
}
internal static void Write(ref byte[] buf, ref int bitPos, byte val)
{
EnsureBufferSize(ref buf, bitPos + 8);
NetBitWriter.WriteByte(val, 8, buf, bitPos);
bitPos += 8;
}
internal static void Write(ref byte[] buf, ref int bitPos, UInt16 val)
{
EnsureBufferSize(ref buf, bitPos + 16);
NetBitWriter.WriteUInt16(val, 16, buf, bitPos);
bitPos += 16;
}
internal static void Write(ref byte[] buf, ref int bitPos, Int16 val)
{
EnsureBufferSize(ref buf, bitPos + 16);
NetBitWriter.WriteUInt16((UInt16)val, 16, buf, bitPos);
bitPos += 16;
}
internal static void Write(ref byte[] buf, ref int bitPos, UInt32 val)
{
EnsureBufferSize(ref buf, bitPos + 32);
NetBitWriter.WriteUInt32(val, 32, buf, bitPos);
bitPos += 32;
}
internal static void Write(ref byte[] buf, ref int bitPos, Int32 val)
{
EnsureBufferSize(ref buf, bitPos + 32);
NetBitWriter.WriteUInt32((UInt32)val, 32, buf, bitPos);
bitPos += 32;
}
internal static void Write(ref byte[] buf, ref int bitPos, UInt64 val)
{
EnsureBufferSize(ref buf, bitPos + 64);
NetBitWriter.WriteUInt64(val, 64, buf, bitPos);
bitPos += 64;
}
internal static void Write(ref byte[] buf, ref int bitPos, Int64 val)
{
EnsureBufferSize(ref buf, bitPos + 64);
NetBitWriter.WriteUInt64((UInt64)val, 64, buf, bitPos);
bitPos += 64;
}
internal static void Write(ref byte[] buf, ref int bitPos, Single val)
{
// Use union to avoid BitConverter.GetBytes() which allocates memory on the heap
SingleUIntUnion su;
su.UIntValue = 0; // must initialize every member of the union to avoid warning
su.SingleValue = val;
EnsureBufferSize(ref buf, bitPos + 32);
NetBitWriter.WriteUInt32(su.UIntValue, 32, buf, bitPos);
bitPos += 32;
}
internal static void Write(ref byte[] buf, ref int bitPos, Double val)
{
EnsureBufferSize(ref buf, bitPos + 64);
byte[] bytes = BitConverter.GetBytes(val);
WriteBytes(ref buf, ref bitPos, bytes, 0, bytes.Length);
bitPos += 64;
}
internal static void Write(ref byte[] buf, ref int bitPos, string val)
{
if (string.IsNullOrEmpty(val))
{
WriteVariableUInt32(ref buf, ref bitPos, (uint)0);
return;
}
byte[] bytes = Encoding.UTF8.GetBytes(val);
WriteVariableUInt32(ref buf, ref bitPos, (uint)bytes.Length);
WriteBytes(ref buf, ref bitPos, bytes, 0, bytes.Length);
}
internal static int WriteVariableUInt32(ref byte[] buf, ref int bitPos, uint value)
{
int retval = 1;
uint num1 = (uint)value;
while (num1 >= 0x80)
{
Write(ref buf, ref bitPos, (byte)(num1 | 0x80));
num1 = num1 >> 7;
retval++;
}
Write(ref buf, ref bitPos, (byte)num1);
return retval;
}
internal static void WriteRangedInteger(ref byte[] buf, ref int bitPos, int val, int min, int max)
{
uint range = (uint)(max - min);
int numberOfBits = NetUtility.BitsToHoldUInt(range);
EnsureBufferSize(ref buf, bitPos + numberOfBits);
uint rvalue = (uint)(val - min);
NetBitWriter.WriteUInt32(rvalue, numberOfBits, buf, bitPos);
bitPos += numberOfBits;
}
internal static void WriteRangedSingle(ref byte[] buf, ref int bitPos, Single val, Single min, Single max, int numberOfBits)
{
float range = max - min;
float unit = ((val - min) / range);
int maxVal = (1 << numberOfBits) - 1;
EnsureBufferSize(ref buf, bitPos + numberOfBits);
NetBitWriter.WriteUInt32((UInt32)((float)maxVal * unit), numberOfBits, buf, bitPos);
bitPos += numberOfBits;
}
internal static void WriteBytes(ref byte[] buf, ref int bitPos, byte[] val, int pos, int length)
{
EnsureBufferSize(ref buf, bitPos + length * 8);
NetBitWriter.WriteBytes(val, pos, length, buf, bitPos);
bitPos += length * 8;
}
internal static void EnsureBufferSize(ref byte[] buf, int numberOfBits)
{
int byteLen = ((numberOfBits + 7) >> 3);
if (buf == null)
{
buf = new byte[byteLen + MsgConstants.BufferOverAllocateAmount];
return;
}
if (buf.Length < byteLen)
{
Array.Resize<byte>(ref buf, byteLen + MsgConstants.BufferOverAllocateAmount);
}
}
}
internal static class MsgReader
{
internal static bool ReadBoolean(byte[] buf, ref int bitPos)
{
byte retval = NetBitWriter.ReadByte(buf, 1, bitPos);
bitPos++;
return (retval > 0 ? true : false);
}
internal static void ReadPadBits(byte[] buf, ref int bitPos)
{
int bitOffset = bitPos % 8;
bitPos += (8 - bitOffset) % 8;
}
internal static byte ReadByte(byte[] buf, ref int bitPos)
{
byte retval = NetBitWriter.ReadByte(buf, 8, bitPos);
bitPos += 8;
return retval;
}
internal static UInt16 ReadUInt16(byte[] buf, ref int bitPos)
{
uint retval = NetBitWriter.ReadUInt16(buf, 16, bitPos);
bitPos += 16;
return (ushort)retval;
}
internal static Int16 ReadInt16(byte[] buf, ref int bitPos)
{
return (Int16)ReadUInt16(buf, ref bitPos);
}
internal static UInt32 ReadUInt32(byte[] buf, ref int bitPos)
{
uint retval = NetBitWriter.ReadUInt32(buf, 32, bitPos);
bitPos += 32;
return retval;
}
internal static Int32 ReadInt32(byte[] buf, ref int bitPos)
{
return (Int32)ReadUInt32(buf, ref bitPos);
}
internal static UInt64 ReadUInt64(byte[] buf, ref int bitPos)
{
ulong low = NetBitWriter.ReadUInt32(buf, 32, bitPos);
bitPos += 32;
ulong high = NetBitWriter.ReadUInt32(buf, 32, bitPos);
ulong retval = low + (high << 32);
bitPos += 32;
return retval;
}
internal static Int64 ReadInt64(byte[] buf, ref int bitPos)
{
return (Int64)ReadUInt64(buf, ref bitPos);
}
internal static Single ReadSingle(byte[] buf, ref int bitPos)
{
if ((bitPos & 7) == 0) // read directly
{
float retval = BitConverter.ToSingle(buf, bitPos >> 3);
bitPos += 32;
return retval;
}
byte[] bytes = ReadBytes(buf, ref bitPos, 4);
return BitConverter.ToSingle(bytes, 0);
}
internal static Double ReadDouble(byte[] buf, ref int bitPos)
{
if ((bitPos & 7) == 0) // read directly
{
// read directly
double retval = BitConverter.ToDouble(buf, bitPos >> 3);
bitPos += 64;
return retval;
}
byte[] bytes = ReadBytes(buf, ref bitPos, 8);
return BitConverter.ToDouble(bytes, 0);
}
internal static UInt32 ReadVariableUInt32(byte[] buf, ref int bitPos)
{
int bitLength = buf.Length * 8;
int num1 = 0;
int num2 = 0;
while (bitLength - bitPos >= 8)
{
byte num3 = ReadByte(buf, ref bitPos);
num1 |= (num3 & 0x7f) << num2;
num2 += 7;
if ((num3 & 0x80) == 0)
return (uint)num1;
}
// ouch; failed to find enough bytes; malformed variable length number?
return (uint)num1;
}
internal static String ReadString(byte[] buf, ref int bitPos)
{
int bitLength = buf.Length * 8;
int byteLen = (int)ReadVariableUInt32(buf, ref bitPos);
if (byteLen <= 0) { return String.Empty; }
if ((ulong)(bitLength - bitPos) < ((ulong)byteLen * 8))
{
// not enough data
return null;
}
if ((bitPos & 7) == 0)
{
// read directly
string retval = System.Text.Encoding.UTF8.GetString(buf, bitPos >> 3, byteLen);
bitPos += (8 * byteLen);
return retval;
}
byte[] bytes = ReadBytes(buf, ref bitPos, byteLen);
return System.Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length);
}
internal static int ReadRangedInteger(byte[] buf, ref int bitPos, int min, int max)
{
uint range = (uint)(max - min);
int numBits = NetUtility.BitsToHoldUInt(range);
uint rvalue = NetBitWriter.ReadUInt32(buf, numBits, bitPos);
bitPos += numBits;
return (int)(min + rvalue);
}
internal static Single ReadRangedSingle(byte[] buf, ref int bitPos, Single min, Single max, int bitCount)
{
int maxInt = (1 << bitCount) - 1;
int intVal = ReadRangedInteger(buf, ref bitPos, 0, maxInt);
Single range = max - min;
return min + (range * ((Single)intVal) / ((Single)maxInt));
}
internal static byte[] ReadBytes(byte[] buf, ref int bitPos, int numberOfBytes)
{
byte[] retval = new byte[numberOfBytes];
NetBitWriter.ReadBytes(buf, numberOfBytes, bitPos, retval, 0);
bitPos += (8 * numberOfBytes);
return retval;
}
}
public class WriteOnlyMessage : IWriteMessage
{
private byte[] buf = new byte[MsgConstants.InitialBufferSize];
private int seekPos = 0;
private int lengthBits = 0;
public int BitPosition
{
get
{
return seekPos;
}
set
{
seekPos = value;
}
}
public int BytePosition
{
get
{
return seekPos / 8;
}
}
public byte[] Buffer
{
get
{
return buf;
}
}
public int LengthBits
{
get
{
lengthBits = seekPos > lengthBits ? seekPos : lengthBits;
return lengthBits;
}
set
{
lengthBits = value;
seekPos = seekPos > lengthBits ? lengthBits : seekPos;
}
}
public int LengthBytes
{
get
{
return (LengthBits + ((8 - (LengthBits % 8)) % 8)) / 8;
}
}
public void Write(bool val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void WritePadBits()
{
MsgWriter.WritePadBits(ref buf, ref seekPos);
}
public void Write(byte val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(UInt16 val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(Int16 val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(UInt32 val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(Int32 val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(UInt64 val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(Int64 val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(Single val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(Double val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void WriteVariableUInt32(UInt32 val)
{
MsgWriter.WriteVariableUInt32(ref buf, ref seekPos, val);
}
public void Write(String val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void WriteRangedInteger(int val, int min, int max)
{
MsgWriter.WriteRangedInteger(ref buf, ref seekPos, val, min, max);
}
public void WriteRangedSingle(Single val, Single min, Single max, int bitCount)
{
MsgWriter.WriteRangedSingle(ref buf, ref seekPos, val, min, max, bitCount);
}
public void Write(byte[] val, int startPos, int length)
{
MsgWriter.WriteBytes(ref buf, ref seekPos, val, startPos, length);
}
public void PrepareForSending(ref byte[] outBuf, out bool isCompressed, out int length)
{
if (LengthBytes <= MsgConstants.CompressionThreshold)
{
isCompressed = false;
if (LengthBytes > outBuf.Length) { Array.Resize(ref outBuf, LengthBytes); }
Array.Copy(buf, outBuf, LengthBytes);
length = LengthBytes;
}
else
{
using (MemoryStream output = new MemoryStream())
{
using (DeflateStream dstream = new DeflateStream(output, CompressionLevel.Fastest))
{
dstream.Write(buf, 0, LengthBytes);
}
byte[] compressedBuf = output.ToArray();
//don't send the data as compressed if the data takes up more space after compression
//(which may happen when sending a sub/save file that's already been compressed with a better compression ratio)
if (compressedBuf.Length >= outBuf.Length)
{
isCompressed = false;
if (LengthBytes > outBuf.Length) { Array.Resize(ref outBuf, LengthBytes); }
Array.Copy(buf, outBuf, LengthBytes);
length = LengthBytes;
}
else
{
isCompressed = true;
if (compressedBuf.Length > outBuf.Length) { Array.Resize(ref outBuf, compressedBuf.Length); }
Array.Copy(compressedBuf, outBuf, compressedBuf.Length);
length = compressedBuf.Length;
DebugConsole.Log("Compressed message: " + LengthBytes + " to " + length);
}
}
}
}
}
public class ReadOnlyMessage : IReadMessage
{
private byte[] buf;
private int seekPos = 0;
private int lengthBits = 0;
public int BitPosition
{
get
{
return seekPos;
}
set
{
seekPos = value;
}
}
public int BytePosition
{
get
{
return seekPos / 8;
}
}
public byte[] Buffer
{
get
{
return buf;
}
}
public int LengthBits
{
get
{
lengthBits = seekPos > lengthBits ? seekPos : lengthBits;
return lengthBits;
}
set
{
lengthBits = value;
seekPos = seekPos > lengthBits ? lengthBits : seekPos;
}
}
public int LengthBytes
{
get
{
return lengthBits / 8;
}
}
public NetworkConnection Sender { get; private set; }
public ReadOnlyMessage(byte[] inBuf, bool isCompressed, int startPos, int inLength, NetworkConnection sender)
{
Sender = sender;
if (isCompressed)
{
byte[] decompressedData;
using (MemoryStream input = new MemoryStream(inBuf, startPos, inLength))
{
using (MemoryStream output = new MemoryStream())
{
using (DeflateStream dstream = new DeflateStream(input, CompressionMode.Decompress))
{
dstream.CopyTo(output);
}
decompressedData = output.ToArray();
}
}
buf = new byte[decompressedData.Length];
Array.Copy(decompressedData, 0, buf, 0, decompressedData.Length);
lengthBits = decompressedData.Length * 8;
DebugConsole.Log("Decompressing message: " + inLength + " to " + LengthBytes);
}
else
{
buf = new byte[inBuf.Length];
Array.Copy(inBuf, startPos, buf, 0, inLength);
lengthBits = inLength * 8;
}
seekPos = 0;
}
public bool ReadBoolean()
{
return MsgReader.ReadBoolean(buf, ref seekPos);
}
public void ReadPadBits()
{
MsgReader.ReadPadBits(buf, ref seekPos);
}
public byte ReadByte()
{
return MsgReader.ReadByte(buf, ref seekPos);
}
public UInt16 ReadUInt16()
{
return MsgReader.ReadUInt16(buf, ref seekPos);
}
public Int16 ReadInt16()
{
return MsgReader.ReadInt16(buf, ref seekPos);
}
public UInt32 ReadUInt32()
{
return MsgReader.ReadUInt32(buf, ref seekPos);
}
public Int32 ReadInt32()
{
return MsgReader.ReadInt32(buf, ref seekPos);
}
public UInt64 ReadUInt64()
{
return MsgReader.ReadUInt64(buf, ref seekPos);
}
public Int64 ReadInt64()
{
return MsgReader.ReadInt64(buf, ref seekPos);
}
public Single ReadSingle()
{
return MsgReader.ReadSingle(buf, ref seekPos);
}
public Double ReadDouble()
{
return MsgReader.ReadDouble(buf, ref seekPos);
}
public UInt32 ReadVariableUInt32()
{
return MsgReader.ReadVariableUInt32(buf, ref seekPos);
}
public String ReadString()
{
return MsgReader.ReadString(buf, ref seekPos);
}
public int ReadRangedInteger(int min, int max)
{
return MsgReader.ReadRangedInteger(buf, ref seekPos, min, max);
}
public Single ReadRangedSingle(Single min, Single max, int bitCount)
{
return MsgReader.ReadRangedSingle(buf, ref seekPos, min, max, bitCount);
}
public byte[] ReadBytes(int numberOfBytes)
{
return MsgReader.ReadBytes(buf, ref seekPos, numberOfBytes);
}
}
public class ReadWriteMessage : IWriteMessage, IReadMessage
{
private byte[] buf = new byte[MsgConstants.InitialBufferSize];
private int seekPos = 0;
private int lengthBits = 0;
public int BitPosition
{
get
{
return seekPos;
}
set
{
seekPos = value;
}
}
public int BytePosition
{
get
{
return seekPos / 8;
}
}
public byte[] Buffer
{
get
{
return buf;
}
}
public int LengthBits
{
get
{
lengthBits = seekPos > lengthBits ? seekPos : lengthBits;
return lengthBits;
}
set
{
lengthBits = value;
seekPos = seekPos > lengthBits ? lengthBits : seekPos;
}
}
public int LengthBytes
{
get
{
return (LengthBits + ((8 - (LengthBits % 8)) % 8)) / 8;
}
}
public NetworkConnection Sender { get { return null; } }
public void Write(bool val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void WritePadBits()
{
MsgWriter.WritePadBits(ref buf, ref seekPos);
}
public void Write(byte val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(UInt16 val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(Int16 val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(UInt32 val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(Int32 val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(UInt64 val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(Int64 val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(Single val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void Write(Double val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void WriteVariableUInt32(UInt32 val)
{
MsgWriter.WriteVariableUInt32(ref buf, ref seekPos, val);
}
public void Write(String val)
{
MsgWriter.Write(ref buf, ref seekPos, val);
}
public void WriteRangedInteger(int val, int min, int max)
{
MsgWriter.WriteRangedInteger(ref buf, ref seekPos, val, min, max);
}
public void WriteRangedSingle(Single val, Single min, Single max, int bitCount)
{
MsgWriter.WriteRangedSingle(ref buf, ref seekPos, val, min, max, bitCount);
}
public void Write(byte[] val, int startPos, int length)
{
MsgWriter.WriteBytes(ref buf, ref seekPos, val, startPos, length);
}
public bool ReadBoolean()
{
return MsgReader.ReadBoolean(buf, ref seekPos);
}
public void ReadPadBits()
{
MsgReader.ReadPadBits(buf, ref seekPos);
}
public byte ReadByte()
{
return MsgReader.ReadByte(buf, ref seekPos);
}
public UInt16 ReadUInt16()
{
return MsgReader.ReadUInt16(buf, ref seekPos);
}
public Int16 ReadInt16()
{
return MsgReader.ReadInt16(buf, ref seekPos);
}
public UInt32 ReadUInt32()
{
return MsgReader.ReadUInt32(buf, ref seekPos);
}
public Int32 ReadInt32()
{
return MsgReader.ReadInt32(buf, ref seekPos);
}
public UInt64 ReadUInt64()
{
return MsgReader.ReadUInt64(buf, ref seekPos);
}
public Int64 ReadInt64()
{
return MsgReader.ReadInt64(buf, ref seekPos);
}
public Single ReadSingle()
{
return MsgReader.ReadSingle(buf, ref seekPos);
}
public Double ReadDouble()
{
return MsgReader.ReadDouble(buf, ref seekPos);
}
public UInt32 ReadVariableUInt32()
{
return MsgReader.ReadVariableUInt32(buf, ref seekPos);
}
public String ReadString()
{
return MsgReader.ReadString(buf, ref seekPos);
}
public int ReadRangedInteger(int min, int max)
{
return MsgReader.ReadRangedInteger(buf, ref seekPos, min, max);
}
public Single ReadRangedSingle(Single min, Single max, int bitCount)
{
return MsgReader.ReadRangedSingle(buf, ref seekPos, min, max, bitCount);
}
public byte[] ReadBytes(int numberOfBytes)
{
return MsgReader.ReadBytes(buf, ref seekPos, numberOfBytes);
}
public void PrepareForSending(ref byte[] outBuf, out bool isCompressed, out int outLength)
{
throw new InvalidOperationException("ReadWriteMessages are not to be sent");
}
}
}
@@ -0,0 +1,37 @@
using System;
using System.Net;
using Lidgren.Network;
namespace Barotrauma.Networking
{
public class LidgrenConnection : NetworkConnection
{
public NetConnection NetConnection { get; private set; }
public IPEndPoint IPEndPoint => NetConnection.RemoteEndPoint;
public string IPString
{
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;
}
}
}
@@ -0,0 +1,32 @@
using System;
namespace Barotrauma.Networking
{
public enum NetworkConnectionStatus
{
Connected = 0x1,
Disconnected = 0x2
}
public 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 UInt64 SteamID
{
get;
protected set;
}
public string EndPointString
{
get;
protected set;
}
public NetworkConnectionStatus Status = NetworkConnectionStatus.Disconnected;
}
}
@@ -0,0 +1,13 @@
using System;
namespace Barotrauma.Networking
{
public class PipeConnection : NetworkConnection
{
public PipeConnection()
{
EndPointString = "PIPE";
}
}
}
@@ -0,0 +1,27 @@
using System;
namespace Barotrauma.Networking
{
public class SteamP2PConnection : NetworkConnection
{
public double Timeout = 0.0;
public SteamP2PConnection(string name, UInt64 steamId)
{
SteamID = steamId;
EndPointString = SteamID.ToString();
Name = name;
Heartbeat();
}
public void Decay(float deltaTime)
{
Timeout -= deltaTime;
}
public void Heartbeat()
{
Timeout = TimeoutThreshold;
}
}
}
@@ -0,0 +1,385 @@
using Barotrauma.Items.Components;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma.Networking
{
partial class RespawnManager : Entity, IServerSerializable
{
public enum State
{
Waiting,
Transporting,
Returning
}
private NetworkMember networkMember;
private Steering shuttleSteering;
private List<Door> shuttleDoors;
//items created during respawn
//any respawn items left in the shuttle are removed when the shuttle despawns
private List<Item> respawnItems = new List<Item>();
public bool UsingShuttle
{
get { return RespawnShuttle != null; }
}
/// <summary>
/// When will the shuttle be dispatched with respawned characters
/// </summary>
public DateTime RespawnTime { get; private set; }
/// <summary>
/// When will the sub start heading back out of the level
/// </summary>
public DateTime ReturnTime { get; private set; }
public bool RespawnCountdownStarted
{
get;
private set;
}
public bool ReturnCountdownStarted
{
get;
private set;
}
public State CurrentState { get; private set; }
private DateTime despawnTime;
private float maxTransportTime;
private float updateReturnTimer;
public Submarine RespawnShuttle { get; private set; }
public RespawnManager(NetworkMember networkMember, Submarine shuttle)
: base(shuttle)
{
this.networkMember = networkMember;
if (shuttle != null)
{
RespawnShuttle = new Submarine(shuttle.FilePath, shuttle.MD5Hash.Hash, true);
RespawnShuttle.Load(false);
RespawnShuttle.PhysicsBody.FarseerBody.OnCollision += OnShuttleCollision;
//prevent wifi components from communicating between the respawn shuttle and other subs
List<WifiComponent> wifiComponents = new List<WifiComponent>();
foreach (Item item in Item.ItemList)
{
if (item.Submarine == RespawnShuttle) { wifiComponents.AddRange(item.GetComponents<WifiComponent>()); }
}
foreach (WifiComponent wifiComponent in wifiComponents)
{
wifiComponent.TeamID = Character.TeamType.FriendlyNPC;
}
ResetShuttle();
shuttleDoors = new List<Door>();
foreach (Item item in Item.ItemList)
{
if (item.Submarine != RespawnShuttle) continue;
var steering = item.GetComponent<Steering>();
if (steering != null) shuttleSteering = steering;
var door = item.GetComponent<Door>();
if (door != null) shuttleDoors.Add(door);
//lock all wires to prevent the players from messing up the electronics
var connectionPanel = item.GetComponent<ConnectionPanel>();
if (connectionPanel != null)
{
foreach (Connection connection in connectionPanel.Connections)
{
foreach (Wire wire in connection.Wires)
{
if (wire != null) wire.Locked = true;
}
}
}
}
}
else
{
RespawnShuttle = null;
}
#if SERVER
if (networkMember is GameServer server)
{
maxTransportTime = server.ServerSettings.MaxTransportTime;
}
#endif
}
private bool OnShuttleCollision(Fixture sender, Fixture other, Contact contact)
{
//ignore collisions with the top barrier when returning
return CurrentState != State.Returning || other?.Body != Level.Loaded?.TopBarrier;
}
public void Update(float deltaTime)
{
if (RespawnShuttle == null)
{
if (CurrentState != State.Waiting)
{
CurrentState = State.Waiting;
}
}
switch (CurrentState)
{
case State.Waiting:
UpdateWaiting(deltaTime);
break;
case State.Transporting:
UpdateTransporting(deltaTime);
break;
case State.Returning:
UpdateReturning(deltaTime);
break;
}
}
partial void UpdateWaiting(float deltaTime);
private void UpdateTransporting(float deltaTime)
{
//infinite transport time -> shuttle wont return
if (maxTransportTime <= 0.0f) return;
UpdateTransportingProjSpecific(deltaTime);
}
partial void UpdateTransportingProjSpecific(float deltaTime);
public void ForceRespawn()
{
ResetShuttle();
RespawnTime = DateTime.Now;
CurrentState = State.Waiting;
}
private void UpdateReturning(float deltaTime)
{
updateReturnTimer += deltaTime;
if (updateReturnTimer > 1.0f)
{
updateReturnTimer = 0.0f;
if (shuttleSteering != null)
{
shuttleSteering.SetDestinationLevelStart();
}
UpdateReturningProjSpecific();
}
}
partial void DispatchShuttle();
partial void UpdateReturningProjSpecific();
private IEnumerable<object> ForceShuttleToPos(Vector2 position, float speed)
{
if (RespawnShuttle == null)
{
yield return CoroutineStatus.Success;
}
while (Math.Abs(position.Y - RespawnShuttle.WorldPosition.Y) > 100.0f)
{
Vector2 diff = position - RespawnShuttle.WorldPosition;
if (diff.LengthSquared() > 0.01f)
{
Vector2 displayVel = Vector2.Normalize(diff) * speed;
RespawnShuttle.SubBody.Body.LinearVelocity = ConvertUnits.ToSimUnits(displayVel);
}
yield return CoroutineStatus.Running;
if (RespawnShuttle.SubBody == null) yield return CoroutineStatus.Success;
}
yield return CoroutineStatus.Success;
}
private void ResetShuttle()
{
ReturnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(maxTransportTime * 1000));
despawnTime = ReturnTime + new TimeSpan(0, 0, seconds: 30);
if (RespawnShuttle == null) return;
foreach (Item item in Item.ItemList)
{
if (item.Submarine != RespawnShuttle) continue;
//remove respawn items that have been left in the shuttle
if (respawnItems.Contains(item))
{
Spawner.AddToRemoveQueue(item);
continue;
}
//restore other items to full condition and recharge batteries
item.Condition = item.Prefab.Health;
item.GetComponent<Repairable>()?.ResetDeterioration();
var powerContainer = item.GetComponent<PowerContainer>();
if (powerContainer != null)
{
powerContainer.Charge = powerContainer.Capacity;
}
}
foreach (Structure wall in Structure.WallList)
{
if (wall.Submarine != RespawnShuttle) continue;
for (int i = 0; i < wall.SectionCount; i++)
{
wall.AddDamage(i, -100000.0f);
}
}
var shuttleGaps = Gap.GapList.FindAll(g => g.Submarine == RespawnShuttle && g.ConnectedWall != null);
shuttleGaps.ForEach(g => Spawner.AddToRemoveQueue(g));
foreach (Hull hull in Hull.hullList)
{
if (hull.Submarine != RespawnShuttle) continue;
hull.OxygenPercentage = 100.0f;
hull.WaterVolume = 0.0f;
}
foreach (Character c in Character.CharacterList)
{
if (c.Submarine != RespawnShuttle) continue;
#if CLIENT
if (Character.Controlled == c) Character.Controlled = null;
#endif
c.Kill(CauseOfDeathType.Unknown, null, true);
c.Enabled = false;
Spawner.AddToRemoveQueue(c);
if (c.Inventory != null)
{
foreach (Item item in c.Inventory.Items)
{
if (item == null) continue;
Spawner.AddToRemoveQueue(item);
}
}
}
RespawnShuttle.SetPosition(new Vector2(Level.Loaded.StartPosition.X, Level.Loaded.Size.Y + RespawnShuttle.Borders.Height));
RespawnShuttle.Velocity = Vector2.Zero;
}
partial void RespawnCharactersProjSpecific();
public void RespawnCharacters()
{
RespawnCharactersProjSpecific();
}
public Vector2 FindSpawnPos()
{
if (Level.Loaded == null || Submarine.MainSub == null) { return Vector2.Zero; }
Rectangle dockedBorders = RespawnShuttle.GetDockedBorders();
Vector2 diffFromDockedBorders =
new Vector2(dockedBorders.Center.X, dockedBorders.Y - dockedBorders.Height / 2)
- new Vector2(RespawnShuttle.Borders.Center.X, RespawnShuttle.Borders.Y - RespawnShuttle.Borders.Height / 2);
int minWidth = Math.Max(dockedBorders.Width, 1000);
int minHeight = Math.Max(dockedBorders.Height, 1000);
List<Level.InterestingPosition> potentialSpawnPositions = new List<Level.InterestingPosition>();
foreach (Level.InterestingPosition potentialSpawnPos in Level.Loaded.PositionsOfInterest.Where(p => p.PositionType == Level.PositionType.MainPath))
{
bool invalid = false;
//make sure the shuttle won't overlap with any ruins
foreach (var ruin in Level.Loaded.Ruins)
{
if (Math.Abs(ruin.Area.Center.X - potentialSpawnPos.Position.X) < (minWidth + ruin.Area.Width) / 2) { invalid = true; break; }
if (Math.Abs(ruin.Area.Center.Y - potentialSpawnPos.Position.Y) < (minHeight + ruin.Area.Height) / 2) { invalid = true; break; }
}
if (invalid) { continue; }
//make sure there aren't any walls too close
var tooCloseCells = Level.Loaded.GetTooCloseCells(potentialSpawnPos.Position.ToVector2(), Math.Max(minWidth, minHeight));
if (tooCloseCells.Any()) { continue; }
//make sure the spawnpoint is far enough from other subs
foreach (Submarine sub in Submarine.Loaded)
{
if (sub == RespawnShuttle || RespawnShuttle.DockedTo.Contains(sub)) { continue; }
float minDist = Math.Max(Math.Max(minWidth, minHeight) + Math.Max(sub.Borders.Width, sub.Borders.Height), 10000.0f);
if (Vector2.DistanceSquared(sub.WorldPosition, potentialSpawnPos.Position.ToVector2()) < minDist * minDist)
{
invalid = true;
break;
}
}
if (invalid) { continue; }
foreach (Character character in Character.CharacterList)
{
if (character.IsDead)
{
//cannot spawn directly over dead bodies
if (Math.Abs(character.WorldPosition.X - potentialSpawnPos.Position.X) < minWidth) { invalid = true; break; }
if (Math.Abs(character.WorldPosition.Y - potentialSpawnPos.Position.Y) < minHeight) { invalid = true; break; }
}
else
{
//cannot spawn near alive characters (to prevent other players from seeing the shuttle
//appear out of nowhere, or monsters from immediatelly wrecking the shuttle)
if (Vector2.DistanceSquared(character.WorldPosition, potentialSpawnPos.Position.ToVector2()) < 5000.0f * 5000.0f)
{
invalid = true;
break;
}
}
}
if (invalid) { continue; }
potentialSpawnPositions.Add(potentialSpawnPos);
}
Vector2 bestSpawnPos = new Vector2(Level.Loaded.StartPosition.X, Level.Loaded.Size.Y + RespawnShuttle.Borders.Height);
float bestSpawnPosValue = 0.0f;
foreach (var potentialSpawnPos in potentialSpawnPositions)
{
//the closer the spawnpos is to the main sub, the better
float spawnPosValue = 100000.0f / Math.Max(Vector2.Distance(potentialSpawnPos.Position.ToVector2(), Submarine.MainSub.WorldPosition), 1.0f);
//prefer spawnpoints that are at the left side of the sub (so the shuttle doesn't have to go backwards)
if (potentialSpawnPos.Position.X > Submarine.MainSub.WorldPosition.X)
{
spawnPosValue *= 0.1f;
}
if (spawnPosValue > bestSpawnPosValue)
{
bestSpawnPos = potentialSpawnPos.Position.ToVector2();
bestSpawnPosValue = spawnPosValue;
}
}
return bestSpawnPos;
}
}
}
@@ -0,0 +1,179 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Barotrauma.Networking
{
public partial class ServerLog
{
private struct LogMessage
{
public readonly string Text;
public readonly MessageType Type;
public LogMessage(string text, MessageType type)
{
if (type.HasFlag(MessageType.Chat))
{
Text = $"[{DateTime.Now.ToString()}]\n {text}";
}
else
{
Text = $"[{DateTime.Now.ToString()}]\n {TextManager.GetServerMessage(text)}";
}
Type = type;
}
}
public enum MessageType
{
Chat,
ItemInteraction,
Inventory,
Attack,
Spawning,
Wiring,
ServerMessage,
ConsoleUsage,
Error,
}
private readonly Dictionary<MessageType, Color> messageColor = new Dictionary<MessageType, Color>
{
{ MessageType.Chat, Color.LightBlue },
{ MessageType.ItemInteraction, new Color(205, 205, 180) },
{ MessageType.Inventory, new Color(255, 234, 85) },
{ MessageType.Attack, new Color(204, 74, 78) },
{ MessageType.Spawning, new Color(163, 73, 164) },
{ MessageType.Wiring, new Color(255, 157, 85) },
{ MessageType.ServerMessage, new Color(157, 225, 160) },
{ MessageType.ConsoleUsage, new Color(0, 162, 232) },
{ MessageType.Error, Color.Red },
};
private readonly Dictionary<MessageType, string> messageTypeName = new Dictionary<MessageType, string>
{
{ MessageType.Chat, "ChatMessage" },
{ MessageType.ItemInteraction, "ItemInteraction" },
{ MessageType.Inventory, "InventoryUsage" },
{ MessageType.Attack, "AttackDeath" },
{ MessageType.Spawning, "Spawning" },
{ MessageType.Wiring, "Wiring" },
{ MessageType.ServerMessage, "ServerMessage" },
{ MessageType.ConsoleUsage, "ConsoleUsage" },
{ MessageType.Error, "Error" }
};
private int linesPerFile = 800;
public const string SavePath = "ServerLogs";
private readonly Queue<LogMessage> lines;
private int unsavedLineCount;
private readonly bool[] msgTypeHidden = new bool[Enum.GetValues(typeof(MessageType)).Length];
public int LinesPerFile
{
get { return linesPerFile; }
set { linesPerFile = Math.Max(value, 10); }
}
public string ServerName;
public ServerLog(string serverName)
{
ServerName = serverName;
lines = new Queue<LogMessage>();
foreach (MessageType messageType in Enum.GetValues(typeof(MessageType)))
{
System.Diagnostics.Debug.Assert(messageColor.ContainsKey(messageType));
System.Diagnostics.Debug.Assert(messageTypeName.ContainsKey(messageType));
}
}
public void WriteLine(string line, MessageType messageType)
{
//string logLine = "[" + DateTime.Now.ToLongTimeString() + "] " + line;
#if SERVER
DebugConsole.NewMessage(line, messageColor[messageType]); //TODO: REMOVE
#endif
var newText = new LogMessage(line, messageType);
lines.Enqueue(newText);
#if CLIENT
if (listBox != null)
{
AddLine(newText);
listBox.UpdateScrollBarSize();
}
#endif
unsavedLineCount++;
if (unsavedLineCount >= LinesPerFile)
{
Save();
unsavedLineCount = 0;
}
while (lines.Count > LinesPerFile)
{
lines.Dequeue();
}
#if CLIENT
while (listBox != null && listBox.Content.CountChildren > LinesPerFile)
{
listBox.RemoveChild(listBox.Content.Children.First());
}
#endif
}
public void Save()
{
if (!Directory.Exists(SavePath))
{
try
{
Directory.CreateDirectory(SavePath);
}
catch (Exception e)
{
DebugConsole.ThrowError("Failed to create a folder for server logs", e);
return;
}
}
string fileName = ServerName + "_" + DateTime.Now.ToString("yyyy-MM-dd_HH:mm");
fileName = ToolBox.RemoveInvalidFileNameChars(fileName);
string filePath = Path.Combine(SavePath, fileName + ".txt");
int i = 2;
while (File.Exists(filePath))
{
filePath = Path.Combine(SavePath, fileName + " (" + i + ").txt");
i++;
}
try
{
File.WriteAllLines(filePath, lines.Select(l => l.Text));
}
catch (Exception e)
{
DebugConsole.ThrowError("Saving the server log to " + filePath + " failed", e);
return;
}
}
}
}
@@ -0,0 +1,967 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace Barotrauma.Networking
{
public enum SelectionMode
{
Manual = 0, Random = 1, Vote = 2
}
public enum YesNoMaybe
{
No = 0, Maybe = 1, Yes = 2
}
public enum BotSpawnMode
{
Normal, Fill
}
public enum PlayStyle
{
Serious = 0,
Casual = 1,
Roleplay = 2,
Rampage = 3,
SomethingDifferent = 4
}
partial class ServerSettings : ISerializableEntity
{
public const string SettingsFile = "serversettings.xml";
[Flags]
public enum NetFlags : byte
{
Name = 0x1,
Message = 0x2,
Properties = 0x4,
Misc = 0x8,
LevelSeed = 0x10
}
public static readonly string PermissionPresetFile = "Data" + Path.DirectorySeparatorChar + "permissionpresets.xml";
public string Name
{
get { return "ServerSettings"; }
}
/// <summary>
/// Have some of the properties listed in the server list changed
/// </summary>
public bool ServerDetailsChanged;
public class SavedClientPermission
{
public readonly string EndPoint;
public readonly ulong SteamID;
public readonly string Name;
public List<DebugConsole.Command> PermittedCommands;
public ClientPermissions Permissions;
public SavedClientPermission(string name, string endpoint, ClientPermissions permissions, List<DebugConsole.Command> permittedCommands)
{
this.Name = name;
this.EndPoint = endpoint;
this.Permissions = permissions;
this.PermittedCommands = permittedCommands;
}
public SavedClientPermission(string name, ulong steamID, ClientPermissions permissions, List<DebugConsole.Command> permittedCommands)
{
this.Name = name;
this.SteamID = steamID;
this.Permissions = permissions;
this.PermittedCommands = permittedCommands;
}
}
partial class NetPropertyData
{
private readonly SerializableProperty property;
private readonly string typeString;
private readonly object parentObject;
public string Name
{
get { return property.Name; }
}
public object Value
{
get { return property.GetValue(parentObject); }
set { property.SetValue(parentObject, value); }
}
public NetPropertyData(object parentObject, SerializableProperty property, string typeString)
{
this.property = property;
this.typeString = typeString;
this.parentObject = parentObject;
}
public bool PropEquals(object a, object b)
{
switch (typeString)
{
case "float":
if (!(a is float?)) return false;
if (!(b is float?)) return false;
return MathUtils.NearlyEqual((float)a, (float)b);
case "int":
if (!(a is int?)) return false;
if (!(b is int?)) return false;
return (int)a == (int)b;
case "bool":
if (!(a is bool?)) return false;
if (!(b is bool?)) return false;
return (bool)a == (bool)b;
case "Enum":
if (!(a is Enum)) return false;
if (!(b is Enum)) return false;
return ((Enum)a).Equals((Enum)b);
default:
if (a == null || b == null)
{
return (a == null) == (b == null);
}
return a.ToString().Equals(b.ToString(), StringComparison.OrdinalIgnoreCase);
}
}
public void Read(IReadMessage msg)
{
int oldPos = msg.BitPosition;
UInt32 size = msg.ReadVariableUInt32();
float x; float y; float z; float w;
byte r; byte g; byte b; byte a;
int ix; int iy; int width; int height;
switch (typeString)
{
case "float":
if (size != 4) break;
property.SetValue(parentObject, msg.ReadSingle());
return;
case "int":
if (size != 4) break;
property.SetValue(parentObject, msg.ReadInt32());
return;
case "vector2":
if (size != 8) break;
x = msg.ReadSingle();
y = msg.ReadSingle();
property.SetValue(parentObject, new Vector2(x, y));
return;
case "vector3":
if (size != 12) break;
x = msg.ReadSingle();
y = msg.ReadSingle();
z = msg.ReadSingle();
property.SetValue(parentObject, new Vector3(x, y, z));
return;
case "vector4":
if (size != 16) break;
x = msg.ReadSingle();
y = msg.ReadSingle();
z = msg.ReadSingle();
w = msg.ReadSingle();
property.SetValue(parentObject, new Vector4(x, y, z, w));
return;
case "color":
if (size != 4) break;
r = msg.ReadByte();
g = msg.ReadByte();
b = msg.ReadByte();
a = msg.ReadByte();
property.SetValue(parentObject, new Color(r, g, b, a));
return;
case "rectangle":
if (size != 16) break;
ix = msg.ReadInt32();
iy = msg.ReadInt32();
width = msg.ReadInt32();
height = msg.ReadInt32();
property.SetValue(parentObject, new Rectangle(ix, iy, width, height));
return;
default:
msg.BitPosition = oldPos; //reset position to properly read the string
string incVal = msg.ReadString();
property.TrySetValue(parentObject, incVal);
return;
}
//size didn't match: skip this
msg.BitPosition += (int)(8 * size);
}
public void Write(IWriteMessage msg, object overrideValue = null)
{
if (overrideValue == null) overrideValue = property.GetValue(parentObject);
switch (typeString)
{
case "float":
msg.WriteVariableUInt32(4);
msg.Write((float)overrideValue);
break;
case "int":
msg.WriteVariableUInt32(4);
msg.Write((int)overrideValue);
break;
case "vector2":
msg.WriteVariableUInt32(8);
msg.Write(((Vector2)overrideValue).X);
msg.Write(((Vector2)overrideValue).Y);
break;
case "vector3":
msg.WriteVariableUInt32(12);
msg.Write(((Vector3)overrideValue).X);
msg.Write(((Vector3)overrideValue).Y);
msg.Write(((Vector3)overrideValue).Z);
break;
case "vector4":
msg.WriteVariableUInt32(16);
msg.Write(((Vector4)overrideValue).X);
msg.Write(((Vector4)overrideValue).Y);
msg.Write(((Vector4)overrideValue).Z);
msg.Write(((Vector4)overrideValue).W);
break;
case "color":
msg.WriteVariableUInt32(4);
msg.Write(((Color)overrideValue).R);
msg.Write(((Color)overrideValue).G);
msg.Write(((Color)overrideValue).B);
msg.Write(((Color)overrideValue).A);
break;
case "rectangle":
msg.WriteVariableUInt32(16);
msg.Write(((Rectangle)overrideValue).X);
msg.Write(((Rectangle)overrideValue).Y);
msg.Write(((Rectangle)overrideValue).Width);
msg.Write(((Rectangle)overrideValue).Height);
break;
default:
string strVal = overrideValue.ToString();
msg.Write(strVal);
break;
}
}
};
public Dictionary<string, SerializableProperty> SerializableProperties
{
get;
private set;
}
private readonly Dictionary<UInt32, NetPropertyData> netProperties;
partial void InitProjSpecific();
public ServerSettings(NetworkMember networkMember, string serverName, int port, int queryPort, int maxPlayers, bool isPublic, bool enableUPnP)
{
ServerLog = new ServerLog(serverName);
Voting = new Voting();
Whitelist = new WhiteList();
BanList = new BanList();
ExtraCargo = new Dictionary<ItemPrefab, int>();
PermissionPreset.LoadAll(PermissionPresetFile);
InitProjSpecific();
ServerName = serverName;
Port = port;
QueryPort = queryPort;
EnableUPnP = enableUPnP;
MaxPlayers = maxPlayers;
IsPublic = isPublic;
netProperties = new Dictionary<UInt32, NetPropertyData>();
using (MD5 md5 = MD5.Create())
{
var saveProperties = SerializableProperty.GetProperties<Serialize>(this);
foreach (var property in saveProperties)
{
object value = property.GetValue(this);
if (value == null) { continue; }
string typeName = SerializableProperty.GetSupportedTypeName(value.GetType());
if (typeName != null || property.PropertyType.IsEnum)
{
NetPropertyData netPropertyData = new NetPropertyData(this, property, typeName);
UInt32 key = ToolBox.StringToUInt32Hash(property.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);
}
}
var karmaProperties = SerializableProperty.GetProperties<Serialize>(networkMember.KarmaManager);
foreach (var property in karmaProperties)
{
object value = property.GetValue(networkMember.KarmaManager);
if (value == null) { continue; }
string typeName = SerializableProperty.GetSupportedTypeName(value.GetType());
if (typeName != null || property.PropertyType.IsEnum)
{
NetPropertyData netPropertyData = new NetPropertyData(networkMember.KarmaManager, property, typeName);
UInt32 key = ToolBox.StringToUInt32Hash(property.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);
}
}
}
}
private string serverName;
public string ServerName
{
get { return serverName; }
set
{
serverName = value;
if (serverName.Length > NetConfig.ServerNameMaxLength) { ServerName = ServerName.Substring(0, NetConfig.ServerNameMaxLength); }
}
}
private string serverMessageText;
public string ServerMessageText
{
get { return serverMessageText; }
set
{
if (serverMessageText == value) { return; }
serverMessageText = value;
ServerDetailsChanged = true;
}
}
public int Port;
public int QueryPort;
public bool EnableUPnP;
public ServerLog ServerLog;
public Voting Voting;
public Dictionary<string, bool> MonsterEnabled { get; private set; }
public Dictionary<ItemPrefab, int> ExtraCargo { get; private set; }
private float selectedLevelDifficulty;
private string password;
public float AutoRestartTimer;
private bool autoRestart;
public bool IsPublic;
private int maxPlayers;
public List<SavedClientPermission> ClientPermissions { get; private set; } = new List<SavedClientPermission>();
public WhiteList Whitelist { get; private set; }
[Serialize(20, true)]
public int TickRate
{
get;
set;
}
[Serialize(true, true)]
public bool RandomizeSeed
{
get;
set;
}
[Serialize(true, true)]
public bool UseRespawnShuttle
{
get;
private set;
}
[Serialize(300.0f, true)]
public float RespawnInterval
{
get;
private set;
}
[Serialize(180.0f, true)]
public float MaxTransportTime
{
get;
private set;
}
[Serialize(0.2f, true)]
public float MinRespawnRatio
{
get;
private set;
}
[Serialize(60.0f, true)]
public float AutoRestartInterval
{
get;
set;
}
[Serialize(false, true)]
public bool StartWhenClientsReady
{
get;
set;
}
[Serialize(0.8f, true)]
public float StartWhenClientsReadyRatio
{
get;
private set;
}
private bool allowSpectating;
[Serialize(true, true)]
public bool AllowSpectating
{
get { return allowSpectating; }
private set
{
if (allowSpectating == value) { return; }
allowSpectating = value;
ServerDetailsChanged = true;
}
}
[Serialize(true, true)]
public bool EndRoundAtLevelEnd
{
get;
private set;
}
[Serialize(true, true)]
public bool SaveServerLogs
{
get;
private set;
}
[Serialize(true, true)]
public bool AllowRagdollButton
{
get;
set;
}
[Serialize(true, true)]
public bool AllowFileTransfers
{
get;
private set;
}
private bool voiceChatEnabled;
[Serialize(true, true)]
public bool VoiceChatEnabled
{
get { return voiceChatEnabled; }
set
{
if (voiceChatEnabled == value) { return; }
voiceChatEnabled = value;
ServerDetailsChanged = true;
}
}
private PlayStyle playstyleSelection;
[Serialize(PlayStyle.Casual, true)]
public PlayStyle PlayStyle
{
get { return playstyleSelection; }
set
{
playstyleSelection = value;
ServerDetailsChanged = true;
}
}
[Serialize(800, true)]
private int LinesPerLogFile
{
get
{
return ServerLog.LinesPerFile;
}
set
{
ServerLog.LinesPerFile = value;
}
}
public bool AutoRestart
{
get { return autoRestart; }
set
{
autoRestart = value;
AutoRestartTimer = autoRestart ? AutoRestartInterval : 0.0f;
}
}
public bool HasPassword
{
get { return password != null; }
#if CLIENT
set
{
password = value ? (password ?? "_") : null;
}
#endif
}
[Serialize(true, true)]
public bool AllowVoteKick
{
get
{
return Voting.AllowVoteKick;
}
set
{
Voting.AllowVoteKick = value;
}
}
[Serialize(true, true)]
public bool AllowEndVoting
{
get
{
return Voting.AllowEndVoting;
}
set
{
Voting.AllowEndVoting = value;
}
}
private bool allowRespawn;
[Serialize(true, true)]
public bool AllowRespawn
{
get { return allowRespawn; ; }
set
{
if (allowRespawn == value) { return; }
allowRespawn = value;
ServerDetailsChanged = true;
}
}
[Serialize(0, true)]
public int BotCount
{
get;
set;
}
[Serialize(16, true)]
public int MaxBotCount
{
get;
set;
}
[Serialize(BotSpawnMode.Normal, true)]
public BotSpawnMode BotSpawnMode
{
get;
set;
}
public float SelectedLevelDifficulty
{
get { return selectedLevelDifficulty; }
set { selectedLevelDifficulty = MathHelper.Clamp(value, 0.0f, 100.0f); }
}
[Serialize(true, true)]
public bool AllowDisguises
{
get;
set;
}
[Serialize(true, true)]
public bool AllowRewiring
{
get;
set;
}
[Serialize(true, true)]
public bool AllowFriendlyFire
{
get;
set;
}
[Serialize(true, true)]
public bool BanAfterWrongPassword
{
get;
set;
}
[Serialize(3, true)]
public int MaxPasswordRetriesBeforeBan
{
get;
private set;
}
[Serialize("", true)]
public string SelectedSubmarine
{
get;
set;
}
[Serialize("", true)]
public string SelectedShuttle
{
get;
set;
}
private YesNoMaybe traitorsEnabled;
[Serialize(YesNoMaybe.No, true)]
public YesNoMaybe TraitorsEnabled
{
get { return traitorsEnabled; }
set
{
if (traitorsEnabled == value) { return; }
traitorsEnabled = value;
ServerDetailsChanged = true;
}
}
[Serialize(defaultValue: 1, isSaveable: true)]
public int TraitorsMinPlayerCount
{
get;
set;
}
[Serialize(defaultValue: 90.0f, isSaveable: true)]
public float TraitorsMinStartDelay
{
get;
set;
}
[Serialize(defaultValue: 180.0f, isSaveable: true)]
public float TraitorsMaxStartDelay
{
get;
set;
}
[Serialize(defaultValue: 30.0f, isSaveable: true)]
public float TraitorsMinRestartDelay
{
get;
set;
}
[Serialize(defaultValue: 90.0f, isSaveable: true)]
public float TraitorsMaxRestartDelay
{
get;
set;
}
private SelectionMode subSelectionMode;
[Serialize(SelectionMode.Manual, true)]
public SelectionMode SubSelectionMode
{
get { return subSelectionMode; }
set
{
subSelectionMode = value;
Voting.AllowSubVoting = subSelectionMode == SelectionMode.Vote;
ServerDetailsChanged = true;
}
}
private SelectionMode modeSelectionMode;
[Serialize(SelectionMode.Manual, true)]
public SelectionMode ModeSelectionMode
{
get { return modeSelectionMode; }
set
{
modeSelectionMode = value;
Voting.AllowModeVoting = modeSelectionMode == SelectionMode.Vote;
ServerDetailsChanged = true;
}
}
public BanList BanList { get; private set; }
[Serialize(0.6f, true)]
public float EndVoteRequiredRatio
{
get;
private set;
}
[Serialize(0.6f, true)]
public float KickVoteRequiredRatio
{
get;
private set;
}
[Serialize(120.0f, true)]
public float KillDisconnectedTime
{
get;
private set;
}
[Serialize(600.0f, true)]
public float KickAFKTime
{
get;
private set;
}
private bool karmaEnabled;
[Serialize(false, true)]
public bool KarmaEnabled
{
get { return karmaEnabled; }
set
{
karmaEnabled = value;
#if CLIENT
if (karmaSettingsBlocker != null) { karmaSettingsBlocker.Visible = !karmaEnabled || karmaPresetDD.SelectedData as string != "custom"; }
#endif
}
}
private string karmaPreset = "default";
[Serialize("default", true)]
public string KarmaPreset
{
get { return karmaPreset; }
set
{
if (karmaPreset == value) { return; }
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
{
GameMain.NetworkMember?.KarmaManager?.SelectPreset(value);
}
karmaPreset = value;
}
}
[Serialize("sandbox", true)]
public string GameModeIdentifier
{
get;
set;
}
[Serialize("All", true)]
public string MissionType
{
get;
set;
}
[Serialize(8, true)]
public int MaxPlayers
{
get { return maxPlayers; }
set { maxPlayers = MathHelper.Clamp(value, 1, NetConfig.MaxPlayers); }
}
public List<MissionType> AllowedRandomMissionTypes
{
get;
set;
}
[Serialize(60f * 60.0f, true)]
public float AutoBanTime
{
get;
private set;
}
[Serialize(60.0f * 60.0f * 24.0f, true)]
public float MaxAutoBanTime
{
get;
private set;
}
public void SetPassword(string password)
{
if (string.IsNullOrEmpty(password))
{
this.password = null;
}
else
{
this.password = password;
}
}
public static byte[] SaltPassword(byte[] password, int salt)
{
byte[] saltedPw = new byte[password.Length*2];
for (int i = 0; i < password.Length; i++)
{
saltedPw[(i * 2)] = password[i];
saltedPw[(i * 2) + 1] = (byte)((salt >> (8 * (i % 4))) & 0xff);
}
saltedPw = Lidgren.Network.NetUtility.ComputeSHAHash(saltedPw);
return saltedPw;
}
public bool IsPasswordCorrect(byte[] input, int salt)
{
if (!HasPassword) return true;
byte[] saltedPw = SaltPassword(Encoding.UTF8.GetBytes(password), salt);
DebugConsole.NewMessage(ToolBox.ByteArrayToString(input) + " " + ToolBox.ByteArrayToString(saltedPw));
if (input.Length != saltedPw.Length) return false;
for (int i = 0; i < input.Length; i++)
{
if (input[i] != saltedPw[i]) return false;
}
return true;
}
/// <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
{
get;
private set;
} = new List<Pair<int, int>>();
private void InitMonstersEnabled()
{
//monster spawn settings
if (MonsterEnabled == null)
{
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);
}
}
}
public void ReadMonsterEnabled(IReadMessage inc)
{
InitMonstersEnabled();
List<string> monsterNames = MonsterEnabled.Keys.ToList();
foreach (string s in monsterNames)
{
MonsterEnabled[s] = inc.ReadBoolean();
}
inc.ReadPadBits();
}
public void WriteMonsterEnabled(IWriteMessage msg, Dictionary<string, bool> monsterEnabled = null)
{
//monster spawn settings
if (monsterEnabled == null) monsterEnabled = MonsterEnabled;
List<string> monsterNames = monsterEnabled.Keys.ToList();
foreach (string s in monsterNames)
{
msg.Write(monsterEnabled[s]);
}
msg.WritePadBits();
}
public bool ReadExtraCargo(IReadMessage msg)
{
bool changed = false;
UInt32 count = msg.ReadUInt32();
if (ExtraCargo == null || count != ExtraCargo.Count) changed = true;
Dictionary<ItemPrefab, int> extraCargo = new Dictionary<ItemPrefab, int>();
for (int i = 0; i < count; i++)
{
string prefabIdentifier = msg.ReadString();
string prefabName = msg.ReadString();
byte amount = msg.ReadByte();
var itemPrefab = string.IsNullOrEmpty(prefabIdentifier) ?
MapEntityPrefab.Find(prefabName, null, showErrorMessages: false) as ItemPrefab :
MapEntityPrefab.Find(prefabName, prefabIdentifier, showErrorMessages: false) as ItemPrefab;
if (itemPrefab != null && amount > 0)
{
if (changed || !ExtraCargo.ContainsKey(itemPrefab) || ExtraCargo[itemPrefab] != amount) changed = true;
extraCargo.Add(itemPrefab, amount);
}
}
if (changed) ExtraCargo = extraCargo;
return changed;
}
public void WriteExtraCargo(IWriteMessage msg)
{
if (ExtraCargo == null)
{
msg.Write((UInt32)0);
return;
}
msg.Write((UInt32)ExtraCargo.Count);
foreach (KeyValuePair<ItemPrefab, int> kvp in ExtraCargo)
{
msg.Write(kvp.Key.Identifier ?? "");
msg.Write(kvp.Key.OriginalName ?? "");
msg.Write((byte)kvp.Value);
}
}
}
}
@@ -0,0 +1,187 @@
using System;
using System.Collections.Generic;
using System.Linq;
#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";
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;
}
public static bool OverlayCustomURL(string url)
{
if (!isInitialized || !Steamworks.SteamClient.IsValid)
{
return false;
}
Steamworks.SteamFriends.OpenWebOverlay(url);
return true;
}
public static bool UnlockAchievement(string achievementName)
{
if (!isInitialized || !Steamworks.SteamClient.IsValid)
{
return false;
}
DebugConsole.Log("Unlocked achievement \"" + achievementName + "\"");
var achievements = Steamworks.SteamUserStats.Achievements.ToList();
int achIndex = achievements.FindIndex(ach => ach.Name == achievementName);
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 \"" + achievementName + "\".");
#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
}
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
}
return success;
}
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);
UpdateProjectSpecific(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 UInt64 SteamIDStringToUInt64(string str)
{
if (string.IsNullOrWhiteSpace(str)) { return 0; }
UInt64 retVal;
if (UInt64.TryParse(str, out retVal) && retVal >(1<<52)) { return retVal; }
if (str.ToUpper().IndexOf("STEAM_") != 0) { 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;
return "STEAM_" + universe.ToString() + ":" + y.ToString() + ":" + accountNumber.ToString();
}
}
}
#endif
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Barotrauma.Networking
{
static partial class VoipConfig
{
public const int MAX_COMPRESSED_SIZE = 120; //amount of bytes we expect each 60ms of audio to fit in
public static readonly TimeSpan SEND_INTERVAL = new TimeSpan(0,0,0,0,120);
}
}
@@ -0,0 +1,160 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
namespace Barotrauma.Networking
{
public class VoipQueue : IDisposable
{
public const int BUFFER_COUNT = 8;
protected int[] bufferLengths;
protected byte[][] buffers;
protected int newestBufferInd;
protected bool firstRead;
public int EnqueuedTotalLength
{
get
{
int enqueuedTotalLength = 0;
for (int i = 0; i < BUFFER_COUNT; i++)
{
enqueuedTotalLength += bufferLengths[i];
}
return enqueuedTotalLength;
}
}
public byte[] BufferToQueue
{
get;
protected set;
}
public virtual byte QueueID
{
get;
protected set;
}
public UInt16 LatestBufferID
{
get;
protected set;
}
public bool CanSend
{
get;
protected set;
}
public bool CanReceive
{
get;
protected set;
}
public DateTime LastReadTime
{
get;
private set;
}
public VoipQueue(byte id, bool canSend, bool canReceive)
{
BufferToQueue = new byte[VoipConfig.MAX_COMPRESSED_SIZE];
newestBufferInd = BUFFER_COUNT - 1;
bufferLengths = new int[BUFFER_COUNT];
buffers = new byte[BUFFER_COUNT][];
for (int i = 0; i < BUFFER_COUNT; i++)
{
buffers[i] = new byte[VoipConfig.MAX_COMPRESSED_SIZE];
}
QueueID = id;
CanSend = canSend;
CanReceive = canReceive;
LatestBufferID = BUFFER_COUNT-1;
firstRead = true;
LastReadTime = DateTime.Now;
}
public void EnqueueBuffer(int length)
{
if (length > byte.MaxValue) return;
newestBufferInd = (newestBufferInd + 1) % BUFFER_COUNT;
int enqueuedTotalLength = EnqueuedTotalLength;
bufferLengths[newestBufferInd] = length;
BufferToQueue.CopyTo(buffers[newestBufferInd], 0);
if ((enqueuedTotalLength+length)>0) LatestBufferID++;
}
public void RetrieveBuffer(int id,out int outSize,out byte[] outBuf)
{
lock (buffers)
{
if (id >= LatestBufferID - (BUFFER_COUNT - 1) && id <= LatestBufferID)
{
int index = (newestBufferInd - (LatestBufferID - id)); if (index < 0) index += BUFFER_COUNT;
outSize = bufferLengths[index];
outBuf = buffers[index];
return;
}
}
outSize = -1;
outBuf = null;
}
public virtual void Write(IWriteMessage msg)
{
if (!CanSend) throw new Exception("Called Write on a VoipQueue not set up for sending");
msg.Write((UInt16)LatestBufferID);
for (int i = 0; i < BUFFER_COUNT; i++)
{
int index = (newestBufferInd + i + 1) % BUFFER_COUNT;
msg.Write((byte)bufferLengths[index]);
msg.Write(buffers[index], 0, bufferLengths[index]);
}
}
public virtual bool Read(IReadMessage msg)
{
if (!CanReceive) throw new Exception("Called Read on a VoipQueue not set up for receiving");
UInt16 incLatestBufferID = msg.ReadUInt16();
if (firstRead || NetIdUtils.IdMoreRecent(incLatestBufferID,LatestBufferID))
{
firstRead = false;
for (int i = 0; i < BUFFER_COUNT; i++)
{
bufferLengths[i] = msg.ReadByte();
buffers[i] = msg.ReadBytes(bufferLengths[i]);
}
newestBufferInd = BUFFER_COUNT - 1;
LatestBufferID = incLatestBufferID;
LastReadTime = DateTime.Now;
return true;
}
else
{
for (int i = 0; i < BUFFER_COUNT; i++)
{
byte len = msg.ReadByte();
msg.BitPosition += len * 8;
}
return false;
}
}
public virtual void Dispose() { }
}
}
@@ -0,0 +1,73 @@
using Barotrauma.Networking;
using System.Collections.Generic;
namespace Barotrauma
{
partial class Voting
{
private bool allowSubVoting, allowModeVoting;
public bool AllowVoteKick = true;
public bool AllowEndVoting = true;
private List<Pair<object, int>> GetVoteList(VoteType voteType, List<Client> voters)
{
List<Pair<object, int>> voteList = new List<Pair<object, int>>();
foreach (Client voter in voters)
{
object vote = voter.GetVote<object>(voteType);
if (vote == null) continue;
var existingVotable = voteList.Find(v => v.First == vote || v.First.Equals(vote));
if (existingVotable == null)
{
voteList.Add(new Pair<object, int>(vote, 1));
}
else
{
existingVotable.Second++;
}
}
return voteList;
}
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);
List<Pair<object, int>> voteList = GetVoteList(voteType, voters);
T selected = default(T);
int highestVotes = 0;
foreach (Pair<object, int> votable in voteList)
{
if (selected == null || votable.Second > highestVotes)
{
highestVotes = votable.Second;
selected = (T)votable.First;
}
}
return selected;
}
public void ResetVotes(List<Client> connectedClients)
{
foreach (Client client in connectedClients)
{
client.ResetVotes();
}
GameMain.NetworkMember.EndVoteCount = 0;
GameMain.NetworkMember.EndVoteMax = 0;
#if CLIENT
UpdateVoteTexts(connectedClients, VoteType.Mode);
UpdateVoteTexts(connectedClients, VoteType.Sub);
#endif
}
}
}
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Barotrauma.Networking
{
partial class WhiteListedPlayer
{
public string Name;
public string IP;
public UInt16 UniqueIdentifier;
}
partial class WhiteList
{
const string SavePath = "Data/whitelist.txt";
private List<WhiteListedPlayer> whitelistedPlayers;
public List<WhiteListedPlayer> WhiteListedPlayers
{
get { return whitelistedPlayers; }
}
public bool Enabled;
partial void InitProjSpecific();
public WhiteList()
{
Enabled = false;
whitelistedPlayers = new List<WhiteListedPlayer>();
InitProjSpecific();
}
}
}