2f107db...5202af9

This commit is contained in:
Joonas Rikkonen
2019-03-18 21:42:26 +02:00
parent 58c92888b7
commit 044fd3344b
395 changed files with 27417 additions and 19754 deletions
@@ -0,0 +1,315 @@
using Lidgren.Network;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
namespace Barotrauma.Networking
{
partial class BannedPlayer
{
private static UInt16 LastIdentifier = 0;
public BannedPlayer(string name, string ip, string reason, DateTime? expirationTime)
{
this.Name = name;
this.IP = ip;
this.Reason = reason;
this.ExpirationTime = expirationTime;
this.UniqueIdentifier = LastIdentifier; LastIdentifier++;
this.IsRangeBan = IP.IndexOf(".x")>-1;
}
public BannedPlayer(string name, ulong steamID, string reason, DateTime? expirationTime)
{
this.Name = name;
this.SteamID = steamID;
this.Reason = reason;
this.ExpirationTime = expirationTime;
this.UniqueIdentifier = LastIdentifier; LastIdentifier++;
this.IsRangeBan = false;
}
public bool CompareTo(string ipCompare)
{
if (!IsRangeBan)
{
return ipCompare == IP;
}
else
{
int rangeBanIndex = IP.IndexOf(".x");
if (ipCompare.Length < rangeBanIndex) return false;
return ipCompare.Substring(0, rangeBanIndex) == IP.Substring(0, rangeBanIndex);
}
}
public bool CompareTo(IPAddress ipCompare)
{
if (ipCompare.IsIPv4MappedToIPv6 && CompareTo(ipCompare.MapToIPv4().ToString()))
{
return true;
}
return CompareTo(ipCompare.ToString());
}
}
partial class BanList
{
partial void InitProjectSpecific()
{
if (!File.Exists(SavePath)) { return; }
string[] lines;
try
{
lines = File.ReadAllLines(SavePath);
}
catch (Exception e)
{
DebugConsole.ThrowError("Failed to open the list of banned players in " + SavePath, e);
return;
}
foreach (string line in lines)
{
string[] separatedLine = line.Split(',');
if (separatedLine.Length < 2) continue;
string name = separatedLine[0];
string identifier = separatedLine[1];
DateTime? expirationTime = null;
if (separatedLine.Length > 2 && !string.IsNullOrEmpty(separatedLine[2]))
{
if (DateTime.TryParse(separatedLine[2], out DateTime parsedTime))
{
expirationTime = parsedTime;
}
}
string reason = separatedLine.Length > 3 ? string.Join(",", separatedLine.Skip(3)) : "";
if (expirationTime.HasValue && DateTime.Now > expirationTime.Value) continue;
if (identifier.Contains(".") || identifier.Contains(":"))
{
//identifier is an ip
bannedPlayers.Add(new BannedPlayer(name, identifier, reason, expirationTime));
}
else
{
//identifier should be a steam id
if (ulong.TryParse(identifier, out ulong steamID))
{
bannedPlayers.Add(new BannedPlayer(name, steamID, reason, expirationTime));
}
else
{
DebugConsole.ThrowError("Error in banlist: \"" + identifier + "\" is not a valid IP or a Steam ID");
}
}
}
}
public bool IsBanned(IPAddress IP, ulong steamID)
{
bannedPlayers.RemoveAll(bp => bp.ExpirationTime.HasValue && DateTime.Now > bp.ExpirationTime.Value);
return bannedPlayers.Any(bp => bp.CompareTo(IP) || (steamID > 0 && bp.SteamID == steamID));
}
public void BanPlayer(string name, IPAddress ip, string reason, TimeSpan? duration)
{
string ipStr = ip.IsIPv4MappedToIPv6 ? ip.MapToIPv4().ToString() : ip.ToString();
BanPlayer(name, ipStr, 0, reason, duration);
}
public void BanPlayer(string name, string ip, string reason, TimeSpan? duration)
{
BanPlayer(name, ip, 0, reason, duration);
}
public void BanPlayer(string name, ulong steamID, string reason, TimeSpan? duration)
{
BanPlayer(name, "", steamID, reason, duration);
}
private void BanPlayer(string name, string ip, ulong steamID, string reason, TimeSpan? duration)
{
var existingBan = bannedPlayers.Find(bp => bp.IP == ip && bp.SteamID == steamID);
if (existingBan != null)
{
if (!duration.HasValue) return;
DebugConsole.Log("Set \"" + name + "\"'s ban duration to " + duration.Value);
existingBan.ExpirationTime = DateTime.Now + duration.Value;
Save();
return;
}
System.Diagnostics.Debug.Assert(!name.Contains(','));
string logMsg = "Banned " + name;
if (!string.IsNullOrEmpty(reason)) logMsg += ", reason: " + reason;
if (duration.HasValue) logMsg += ", duration: " + duration.Value.ToString();
DebugConsole.Log(logMsg);
DateTime? expirationTime = null;
if (duration.HasValue)
{
expirationTime = DateTime.Now + duration.Value;
}
bannedPlayers.Add(new BannedPlayer(name, ip, reason, expirationTime));
Save();
}
public void UnbanPlayer(string name)
{
var player = bannedPlayers.Find(bp => bp.Name == name);
if (player == null)
{
DebugConsole.Log("Could not unban player \"" + name + "\". Matching player not found.");
}
else
{
RemoveBan(player);
}
}
public void UnbanIP(string ip)
{
var player = bannedPlayers.Find(bp => bp.IP == ip);
if (player == null)
{
DebugConsole.Log("Could not unban IP \"" + ip + "\". Matching player not found.");
}
else
{
RemoveBan(player);
}
}
private void RemoveBan(BannedPlayer banned)
{
DebugConsole.Log("Removing ban from " + banned.Name);
GameServer.Log("Removing ban from " + banned.Name, ServerLog.MessageType.ServerMessage);
bannedPlayers.Remove(banned);
Save();
}
private void RangeBan(BannedPlayer banned)
{
banned.IP = ToRange(banned.IP);
BannedPlayer bp;
while ((bp = bannedPlayers.Find(x => banned.CompareTo(x.IP))) != null)
{
//remove all specific bans that are now covered by the rangeban
bannedPlayers.Remove(bp);
}
bannedPlayers.Add(banned);
Save();
}
public void Save()
{
GameServer.Log("Saving banlist", ServerLog.MessageType.ServerMessage);
bannedPlayers.RemoveAll(bp => bp.ExpirationTime.HasValue && DateTime.Now > bp.ExpirationTime.Value);
List<string> lines = new List<string>();
foreach (BannedPlayer banned in bannedPlayers)
{
string line = banned.Name;
line += "," + ((banned.SteamID > 0) ? banned.SteamID.ToString() : banned.IP);
line += "," + (banned.ExpirationTime.HasValue ? banned.ExpirationTime.Value.ToString() : "");
if (!string.IsNullOrWhiteSpace(banned.Reason)) line += "," + banned.Reason;
lines.Add(line);
}
try
{
File.WriteAllLines(SavePath, lines);
}
catch (Exception e)
{
DebugConsole.ThrowError("Saving the list of banned players to " + SavePath + " failed", e);
}
}
public void ServerAdminWrite(NetBuffer outMsg, Client c)
{
if (!c.HasPermission(ClientPermissions.Ban))
{
outMsg.Write(false); outMsg.WritePadBits();
return;
}
outMsg.Write(true);
outMsg.Write(c.Connection == GameMain.Server.OwnerConnection);
outMsg.WritePadBits();
outMsg.WriteVariableInt32(bannedPlayers.Count);
for (int i = 0; i < bannedPlayers.Count; i++)
{
BannedPlayer bannedPlayer = bannedPlayers[i];
outMsg.Write(bannedPlayer.Name);
outMsg.Write(bannedPlayer.UniqueIdentifier);
outMsg.Write(bannedPlayer.IsRangeBan); outMsg.WritePadBits();
if (c.Connection == GameMain.Server.OwnerConnection)
{
outMsg.Write(bannedPlayer.IP);
outMsg.Write(bannedPlayer.SteamID);
}
}
}
public bool ServerAdminRead(NetBuffer incMsg, Client c)
{
if (!c.HasPermission(ClientPermissions.Ban))
{
UInt16 removeCount = incMsg.ReadUInt16();
incMsg.Position += removeCount * 4 * 8;
UInt16 rangeBanCount = incMsg.ReadUInt16();
incMsg.Position += rangeBanCount * 4 * 8;
return false;
}
else
{
UInt16 removeCount = incMsg.ReadUInt16();
for (int i = 0; i < removeCount; i++)
{
UInt16 id = incMsg.ReadUInt16();
BannedPlayer bannedPlayer = bannedPlayers.Find(p => p.UniqueIdentifier == id);
if (bannedPlayer != null)
{
GameServer.Log(c.Name + " unbanned " + bannedPlayer.Name + " (" + bannedPlayer.IP + ")", ServerLog.MessageType.ConsoleUsage);
RemoveBan(bannedPlayer);
}
}
Int16 rangeBanCount = incMsg.ReadInt16();
for (int i = 0; i < rangeBanCount; i++)
{
UInt16 id = incMsg.ReadUInt16();
BannedPlayer bannedPlayer = bannedPlayers.Find(p => p.UniqueIdentifier == id);
if (bannedPlayer != null)
{
GameServer.Log(c.Name + " rangebanned " + bannedPlayer.Name + " (" + bannedPlayer.IP + ")", ServerLog.MessageType.ConsoleUsage);
RangeBan(bannedPlayer);
}
}
return removeCount > 0 || rangeBanCount > 0;
}
}
}
}
@@ -0,0 +1,180 @@
using Barotrauma.Items.Components;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
using System.Text;
namespace Barotrauma.Networking
{
partial class ChatMessage
{
public static void ServerRead(NetIncomingMessage msg, Client c)
{
c.KickAFKTimer = 0.0f;
UInt16 ID = msg.ReadUInt16();
ChatMessageType type = (ChatMessageType)msg.ReadByte();
string txt = "";
int orderIndex = -1;
Character orderTargetCharacter = null;
Entity orderTargetEntity = null;
int orderOptionIndex = -1;
OrderChatMessage orderMsg = null;
if (type == ChatMessageType.Order)
{
orderIndex = msg.ReadByte();
orderTargetCharacter = Entity.FindEntityByID(msg.ReadUInt16()) as Character;
orderTargetEntity = Entity.FindEntityByID(msg.ReadUInt16()) as Entity;
orderOptionIndex = msg.ReadByte();
if (orderIndex < 0 || orderIndex >= Order.PrefabList.Count)
{
DebugConsole.ThrowError("Invalid order message from client \"" + c.Name + "\" - order index out of bounds.");
return;
}
Order order = Order.PrefabList[orderIndex];
string orderOption = orderOptionIndex < 0 || orderOptionIndex >= order.Options.Length ? "" : order.Options[orderOptionIndex];
orderMsg = new OrderChatMessage(order, orderOption, orderTargetEntity, orderTargetCharacter, c.Character);
txt = orderMsg.Text;
}
else
{
txt = msg.ReadString() ?? "";
}
if (!NetIdUtils.IdMoreRecent(ID, c.LastSentChatMsgID)) return;
c.LastSentChatMsgID = ID;
if (txt.Length > MaxLength)
{
txt = txt.Substring(0, MaxLength);
}
c.LastSentChatMessages.Add(txt);
if (c.LastSentChatMessages.Count > 10)
{
c.LastSentChatMessages.RemoveRange(0, c.LastSentChatMessages.Count - 10);
}
float similarity = 0.0f;
//don't do message similarity checks on order messages
if (orderMsg == null)
{
for (int i = 0; i < c.LastSentChatMessages.Count; i++)
{
float closeFactor = 1.0f / (c.LastSentChatMessages.Count - i);
if (string.IsNullOrEmpty(txt))
{
similarity += closeFactor;
}
else
{
int levenshteinDist = ToolBox.LevenshteinDistance(txt, c.LastSentChatMessages[i]);
similarity += Math.Max((txt.Length - levenshteinDist) / (float)txt.Length * closeFactor, 0.0f);
}
}
}
bool isOwner = GameMain.Server.OwnerConnection != null && c.Connection == GameMain.Server.OwnerConnection;
if (similarity + c.ChatSpamSpeed > 5.0f && !isOwner)
{
c.ChatSpamCount++;
if (c.ChatSpamCount > 3)
{
//kick for spamming too much
GameMain.Server.KickClient(c, TextManager.Get("SpamFilterKicked"));
}
else
{
ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked"), ChatMessageType.Server, null);
c.ChatSpamTimer = 10.0f;
GameMain.Server.SendDirectChatMessage(denyMsg, c);
}
return;
}
c.ChatSpamSpeed += similarity + 0.5f;
if (c.ChatSpamTimer > 0.0f && !isOwner)
{
ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked"), ChatMessageType.Server, null);
c.ChatSpamTimer = 10.0f;
GameMain.Server.SendDirectChatMessage(denyMsg, c);
return;
}
if (type == ChatMessageType.Order)
{
if (c.Character == null || c.Character.SpeechImpediment >= 100.0f || c.Character.IsDead) return;
ChatMessageType messageType = CanUseRadio(orderMsg.Sender) ? ChatMessageType.Radio : ChatMessageType.Default;
if (orderMsg.Order.TargetAllCharacters)
{
#if CLIENT
//add the order to the crewmanager only if the host is not controlling a character
//OR the character is close enough to hear it
if (Character.Controlled == null ||
!string.IsNullOrEmpty(ApplyDistanceEffect(orderMsg.Text, messageType, orderMsg.Sender, Character.Controlled)))
{
GameMain.GameSession?.CrewManager?.AddOrder(
new Order(orderMsg.Order.Prefab, orderTargetEntity, (orderTargetEntity as Item)?.GetComponent<ItemComponent>()),
orderMsg.Order.Prefab.FadeOutTime);
}
#endif
}
else if (orderTargetCharacter != null)
{
orderTargetCharacter.SetOrder(
new Order(orderMsg.Order.Prefab, orderTargetEntity, (orderTargetEntity as Item)?.GetComponent<ItemComponent>()),
orderMsg.OrderOption, orderMsg.Sender);
}
GameMain.Server.SendOrderChatMessage(orderMsg);
}
else
{
GameMain.Server.SendChatMessage(txt, null, c);
}
}
public int EstimateLengthBytesServer(Client c)
{
int length = 1 + //(byte)ServerNetObject.CHAT_MESSAGE
2 + //(UInt16)NetStateID
1 + //(byte)Type
Encoding.UTF8.GetBytes(Text).Length + 2;
if (Sender != null && c.InGame)
{
length += 2; //sender ID (UInt16)
}
else if (SenderName != null)
{
length += Encoding.UTF8.GetBytes(SenderName).Length + 2;
}
return length;
}
public virtual void ServerWrite(NetOutgoingMessage msg, Client c)
{
msg.Write((byte)ServerNetObject.CHAT_MESSAGE);
msg.Write(NetStateID);
msg.Write((byte)Type);
msg.Write(Text);
msg.Write(SenderName);
msg.Write(Sender != null && c.InGame);
if (Sender != null && c.InGame)
{
msg.Write(Sender.ID);
}
}
}
}
@@ -0,0 +1,172 @@
using Lidgren.Network;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma.Networking
{
partial class Client : IDisposable
{
public ulong SteamID;
public bool VoiceEnabled = true;
public UInt16 LastRecvClientListUpdate = 0;
public UInt16 LastRecvLobbyUpdate = 0;
public UInt16 LastSentChatMsgID = 0; //last msg this client said
public UInt16 LastRecvChatMsgID = 0; //last msg this client knows about
public UInt16 LastSentEntityEventID = 0;
public UInt16 LastRecvEntityEventID = 0;
public UInt16 LastRecvCampaignUpdate = 0;
public UInt16 LastRecvCampaignSave = 0;
public Pair<UInt16, float> LastCampaignSaveSendTime;
public readonly List<ChatMessage> ChatMsgQueue = new List<ChatMessage>();
public UInt16 LastChatMsgQueueID;
//latest chat messages sent by this client
public readonly List<string> LastSentChatMessages = new List<string>();
public float ChatSpamSpeed;
public float ChatSpamTimer;
public int ChatSpamCount;
public float KickAFKTimer;
public double MidRoundSyncTimeOut;
public bool NeedsMidRoundSync;
//how many unique events the client missed before joining the server
public UInt16 UnreceivedEntityEventCount;
public UInt16 FirstNewEventID;
//when was a specific entity event last sent to the client
// key = event id, value = NetTime.Now when sending
public readonly Dictionary<UInt16, float> EntityEventLastSent = new Dictionary<UInt16, float>();
//when was a position update for a given entity last sent to the client
// key = entity id, value = NetTime.Now when sending
public readonly Dictionary<UInt16, float> PositionUpdateLastSent = new Dictionary<UInt16, float>();
public readonly Queue<Entity> PendingPositionUpdates = new Queue<Entity>();
public bool ReadyToStart;
public List<JobPrefab> JobPreferences;
public JobPrefab AssignedJob;
public float DeleteDisconnectedTimer;
public CharacterInfo CharacterInfo;
public NetConnection Connection { get; set; }
public bool SpectateOnly;
private float karma = 1.0f;
public float Karma
{
get
{
if (GameMain.Server == null) return 1.0f;
if (!GameMain.Server.ServerSettings.KarmaEnabled) return 1.0f;
return karma;
}
set
{
if (GameMain.Server == null) return;
if (!GameMain.Server.ServerSettings.KarmaEnabled) return;
karma = Math.Min(Math.Max(value, 0.0f), 1.0f);
}
}
partial void InitProjSpecific()
{
JobPreferences = new List<JobPrefab>(JobPrefab.List.GetRange(0, Math.Min(JobPrefab.List.Count, 3)));
VoipQueue = new VoipQueue(ID, true, true);
GameMain.Server.VoipServer.RegisterQueue(VoipQueue);
}
partial void DisposeProjSpecific()
{
GameMain.Server.VoipServer.UnregisterQueue(VoipQueue);
VoipQueue.Dispose();
}
public void InitClientSync()
{
LastSentChatMsgID = 0;
LastRecvChatMsgID = ChatMessage.LastID;
LastRecvLobbyUpdate = 0;
LastRecvEntityEventID = 0;
UnreceivedEntityEventCount = 0;
NeedsMidRoundSync = false;
}
public static bool IsValidName(string name, GameServer server)
{
char[] disallowedChars = new char[] { ';', ',', '<', '>', '/', '\\', '[', ']', '"', '?' };
if (name.Any(c => disallowedChars.Contains(c))) return false;
foreach (char character in name)
{
if (!server.ServerSettings.AllowedClientNameChars.Any(charRange => (int)character >= charRange.First && (int)character <= charRange.Second)) return false;
}
return true;
}
public bool IPMatches(string ip)
{
if (Connection?.RemoteEndPoint == null) { return false; }
if (Connection.RemoteEndPoint.Address.IsIPv4MappedToIPv6 &&
Connection.RemoteEndPoint.Address.MapToIPv4().ToString() == ip)
{
return true;
}
return Connection.RemoteEndPoint.Address.ToString() == ip;
}
public void SetPermissions(ClientPermissions permissions, List<DebugConsole.Command> permittedConsoleCommands)
{
this.Permissions = permissions;
this.PermittedConsoleCommands = new List<DebugConsole.Command>(permittedConsoleCommands);
}
public void GivePermission(ClientPermissions permission)
{
if (!this.Permissions.HasFlag(permission)) this.Permissions |= permission;
}
public void RemovePermission(ClientPermissions permission)
{
if (this.Permissions.HasFlag(permission)) this.Permissions &= ~permission;
}
public bool HasPermission(ClientPermissions permission)
{
return this.Permissions.HasFlag(permission);
}
public static string SanitizeName(string name)
{
name = name.Trim();
if (name.Length > 20)
{
name = name.Substring(0, 20);
}
string rName = "";
for (int i = 0; i < name.Length; i++)
{
rName += name[i] < 32 ? '?' : name[i];
}
return rName;
}
}
}
@@ -0,0 +1,44 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
partial class EntitySpawner : Entity, IServerSerializable
{
public void CreateNetworkEvent(Entity entity, bool remove)
{
if (GameMain.Server != null && entity != null)
{
GameMain.Server.CreateEntityEvent(this, new object[] { new SpawnOrRemove(entity, remove) });
}
}
public void ServerWrite(Lidgren.Network.NetBuffer message, Client client, object[] extraData = null)
{
if (GameMain.Server == null) return;
SpawnOrRemove entities = (SpawnOrRemove)extraData[0];
message.Write(entities.Remove);
if (entities.Remove)
{
message.Write(entities.Entity.ID);
}
else
{
if (entities.Entity is Item)
{
message.Write((byte)SpawnableType.Item);
((Item)entities.Entity).WriteSpawnData(message);
}
else if (entities.Entity is Character)
{
message.Write((byte)SpawnableType.Character);
DebugConsole.NewMessage("WRITING CHARACTER DATA: " + (entities.Entity).ToString() + " (ID: " + entities.Entity.ID + ")", Color.Cyan);
((Character)entities.Entity).WriteSpawnData(message);
}
}
}
}
}
@@ -0,0 +1,285 @@
using Lidgren.Network;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Barotrauma.Networking
{
class FileSender
{
public class FileTransferOut
{
private byte[] data;
private DateTime startingTime;
private NetConnection connection;
public FileTransferStatus Status;
public string FileName
{
get;
private set;
}
public string FilePath
{
get;
private set;
}
public FileTransferType FileType
{
get;
private set;
}
public float Progress
{
get { return SentOffset / (float)Data.Length; }
}
public float WaitTimer
{
get;
set;
}
public byte[] Data
{
get { return data; }
}
public int SentOffset
{
get;
set;
}
public NetConnection Connection
{
get { return connection; }
}
public int SequenceChannel;
public FileTransferOut(NetConnection recipient, FileTransferType fileType, string filePath)
{
connection = recipient;
FileType = fileType;
FilePath = filePath;
FileName = Path.GetFileName(filePath);
Status = FileTransferStatus.NotStarted;
startingTime = DateTime.Now;
data = File.ReadAllBytes(filePath);
}
}
const int MaxTransferCount = 16;
const int MaxTransferCountPerRecipient = 5;
public static TimeSpan MaxTransferDuration = new TimeSpan(0, 2, 0);
public delegate void FileTransferDelegate(FileTransferOut fileStreamReceiver);
public FileTransferDelegate OnStarted;
public FileTransferDelegate OnEnded;
private List<FileTransferOut> activeTransfers;
private int chunkLen;
private NetPeer peer;
public List<FileTransferOut> ActiveTransfers
{
get { return activeTransfers; }
}
public FileSender(NetworkMember networkMember)
{
peer = networkMember.NetPeer;
chunkLen = peer.Configuration.MaximumTransmissionUnit - 100;
activeTransfers = new List<FileTransferOut>();
}
public FileTransferOut StartTransfer(NetConnection recipient, FileTransferType fileType, string filePath)
{
if (activeTransfers.Count >= MaxTransferCount)
{
return null;
}
if (activeTransfers.Count(t => t.Connection == recipient) > MaxTransferCountPerRecipient)
{
return null;
}
if (!File.Exists(filePath))
{
DebugConsole.ThrowError("Failed to initiate file transfer (file \"" + filePath + "\" not found.");
return null;
}
FileTransferOut transfer = null;
try
{
transfer = new FileTransferOut(recipient, fileType, filePath)
{
SequenceChannel = 1
};
while (activeTransfers.Any(t => t.Connection == recipient && t.SequenceChannel == transfer.SequenceChannel))
{
transfer.SequenceChannel++;
}
activeTransfers.Add(transfer);
}
catch (Exception e)
{
DebugConsole.ThrowError("Failed to initiate file transfer", e);
return null;
}
OnStarted(transfer);
return transfer;
}
public void Update(float deltaTime)
{
activeTransfers.RemoveAll(t => t.Connection.Status != NetConnectionStatus.Connected);
var endedTransfers = activeTransfers.FindAll(t =>
t.Connection.Status != NetConnectionStatus.Connected ||
t.Status == FileTransferStatus.Finished ||
t.Status == FileTransferStatus.Canceled ||
t.Status == FileTransferStatus.Error);
foreach (FileTransferOut transfer in endedTransfers)
{
activeTransfers.Remove(transfer);
OnEnded(transfer);
}
foreach (FileTransferOut transfer in activeTransfers)
{
transfer.WaitTimer -= deltaTime;
if (transfer.WaitTimer > 0.0f) continue;
if (!transfer.Connection.CanSendImmediately(NetDeliveryMethod.ReliableOrdered, 1)) continue;
transfer.WaitTimer = 0.05f;// transfer.Connection.AverageRoundtripTime;
// send another part of the file
long remaining = transfer.Data.Length - transfer.SentOffset;
int sendByteCount = (remaining > chunkLen ? chunkLen : (int)remaining);
NetOutgoingMessage message;
//first message; send length, chunk length, file name etc
if (transfer.SentOffset == 0)
{
message = peer.CreateMessage();
message.Write((byte)ServerPacketHeader.FILE_TRANSFER);
message.Write((byte)FileTransferMessageType.Initiate);
message.Write((byte)transfer.FileType);
message.Write((ushort)chunkLen);
message.Write((ulong)transfer.Data.Length);
message.Write(transfer.FileName);
GameMain.Server.CompressOutgoingMessage(message);
transfer.Connection.SendMessage(message, NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel);
transfer.Status = FileTransferStatus.Sending;
if (GameSettings.VerboseLogging)
{
DebugConsole.Log("Sending file transfer initiation message: ");
DebugConsole.Log(" File: " + transfer.FileName);
DebugConsole.Log(" Size: " + transfer.Data.Length);
DebugConsole.Log(" Sequence channel: " + transfer.SequenceChannel);
}
}
message = peer.CreateMessage(1 + 1 + sendByteCount);
message.Write((byte)ServerPacketHeader.FILE_TRANSFER);
message.Write((byte)FileTransferMessageType.Data);
byte[] sendBytes = new byte[sendByteCount];
Array.Copy(transfer.Data, transfer.SentOffset, sendBytes, 0, sendByteCount);
message.Write(sendBytes);
GameMain.Server.CompressOutgoingMessage(message);
transfer.Connection.SendMessage(message, NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel);
transfer.SentOffset += sendByteCount;
if (GameSettings.VerboseLogging)
{
DebugConsole.Log("Sending " + sendByteCount + " bytes of the file " + transfer.FileName + " (" + transfer.SentOffset + "/" + transfer.Data.Length + " sent)");
}
if (remaining - sendByteCount <= 0)
{
transfer.Status = FileTransferStatus.Finished;
}
}
}
public void CancelTransfer(FileTransferOut transfer)
{
transfer.Status = FileTransferStatus.Canceled;
activeTransfers.Remove(transfer);
OnEnded(transfer);
GameMain.Server.SendCancelTransferMsg(transfer);
}
public void ReadFileRequest(NetIncomingMessage inc, Client client)
{
byte messageType = inc.ReadByte();
if (messageType == (byte)FileTransferMessageType.Cancel)
{
byte sequenceChannel = inc.ReadByte();
var matchingTransfer = activeTransfers.Find(t => t.Connection == inc.SenderConnection && t.SequenceChannel == sequenceChannel);
if (matchingTransfer != null) CancelTransfer(matchingTransfer);
return;
}
byte fileType = inc.ReadByte();
switch (fileType)
{
case (byte)FileTransferType.Submarine:
string fileName = inc.ReadString();
string fileHash = inc.ReadString();
var requestedSubmarine = Submarine.SavedSubmarines.FirstOrDefault(s => s.Name == fileName && s.MD5Hash.Hash == fileHash);
if (requestedSubmarine != null)
{
StartTransfer(inc.SenderConnection, FileTransferType.Submarine, requestedSubmarine.FilePath);
}
break;
case (byte)FileTransferType.CampaignSave:
if (GameMain.GameSession != null &&
!ActiveTransfers.Any(t => t.Connection == inc.SenderConnection && t.FileType == FileTransferType.CampaignSave))
{
StartTransfer(inc.SenderConnection, FileTransferType.CampaignSave, GameMain.GameSession.SavePath);
if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign)
{
client.LastCampaignSaveSendTime = new Pair<ushort, float>(campaign.LastSaveID, (float)NetTime.Now);
}
}
break;
}
}
}
}
@@ -1,12 +0,0 @@
using System;
namespace Barotrauma.Networking
{
class GameClient : NetworkMember
{
public GameClient(string newName)
{
throw new Exception("Tried to create GameClient in dedicated server build");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,504 @@
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma.Networking
{
class ServerEntityEvent : NetEntityEvent
{
private IServerSerializable serializable;
public bool Sent;
#if DEBUG
public string StackTrace;
#endif
private double createTime;
public double CreateTime
{
get { return createTime; }
}
public ServerEntityEvent(IServerSerializable entity, UInt16 id)
: base(entity, id)
{
serializable = entity;
createTime = Timing.TotalTime;
#if DEBUG
StackTrace = Environment.StackTrace.ToString();
#endif
}
public void Write(NetBuffer msg, Client recipient)
{
serializable.ServerWrite(msg, recipient, Data);
}
}
class ServerEntityEventManager : NetEntityEventManager
{
private List<ServerEntityEvent> events;
//list of unique events (i.e. !IsDuplicate) created during the round
//used for syncing clients who join mid-round
private List<ServerEntityEvent> uniqueEvents;
private UInt16 lastSentToAll;
public List<ServerEntityEvent> Events
{
get { return events; }
}
public List<ServerEntityEvent> UniqueEvents
{
get { return uniqueEvents; }
}
private class BufferedEvent
{
public readonly Client Sender;
public readonly UInt16 CharacterStateID;
public readonly NetBuffer Data;
public readonly Character Character;
public readonly IClientSerializable TargetEntity;
public bool IsProcessed;
public BufferedEvent(Client sender, Character senderCharacter, UInt16 characterStateID, IClientSerializable targetEntity, NetBuffer data)
{
this.Sender = sender;
this.Character = senderCharacter;
this.CharacterStateID = characterStateID;
this.TargetEntity = targetEntity;
this.Data = data;
}
}
private List<BufferedEvent> bufferedEvents;
private UInt16 ID;
private GameServer server;
public ServerEntityEventManager(GameServer server)
{
events = new List<ServerEntityEvent>();
this.server = server;
bufferedEvents = new List<BufferedEvent>();
uniqueEvents = new List<ServerEntityEvent>();
}
public void CreateEvent(IServerSerializable entity, object[] extraData = null)
{
if (entity == null || !(entity is Entity))
{
DebugConsole.ThrowError("Can't create an entity event for " + entity + "!");
return;
}
if (((Entity)entity).Removed && !(entity is Level))
{
DebugConsole.ThrowError("Can't create an entity event for " + entity + " - the entity has been removed.\n"+Environment.StackTrace);
return;
}
if (((Entity)entity).IdFreed)
{
DebugConsole.ThrowError("Can't create an entity event for " + entity + " - the ID of the entity has been freed.\n"+Environment.StackTrace);
return;
}
var newEvent = new ServerEntityEvent(entity, (UInt16)(ID + 1));
if (extraData != null) newEvent.SetData(extraData);
//remove events that have been sent to all clients, they are redundant now
//keep at least one event in the list (lastSentToAll == e.ID) so we can use it to keep track of the latest ID
events.RemoveAll(e => NetIdUtils.IdMoreRecent(lastSentToAll, e.ID));
if (server.ConnectedClients.Count(c => c.InGame) == 0 && events.Count > 1)
{
events.RemoveRange(0, events.Count - 1);
}
for (int i = events.Count - 1; i >= 0; i--)
{
//we already have an identical event that's waiting to be sent
// -> no need to add a new one
if (events[i].IsDuplicate(newEvent) && !events[i].Sent) return;
}
ID++;
events.Add(newEvent);
if (!uniqueEvents.Any(e => e.IsDuplicate(newEvent)))
{
//create a copy of the event and give it a new ID
var uniqueEvent = new ServerEntityEvent(entity, (UInt16)(uniqueEvents.Count + 1));
uniqueEvent.SetData(extraData);
uniqueEvents.Add(uniqueEvent);
}
}
public void Update(List<Client> clients)
{
foreach (BufferedEvent bufferedEvent in bufferedEvents)
{
if (bufferedEvent.Character == null || bufferedEvent.Character.IsDead)
{
bufferedEvent.IsProcessed = true;
continue;
}
//delay reading the events until the inputs for the corresponding frame have been processed
//UNLESS the character is unconscious, in which case we'll read the messages immediately (because further inputs will be ignored)
//atm the "give in" command is the only thing unconscious characters can do, other types of events are ignored
if (!bufferedEvent.Character.IsUnconscious &&
NetIdUtils.IdMoreRecent(bufferedEvent.CharacterStateID, bufferedEvent.Character.LastProcessedID))
{
continue;
}
try
{
ReadEvent(bufferedEvent.Data, bufferedEvent.TargetEntity, bufferedEvent.Sender);
}
catch (Exception e)
{
string entityName = bufferedEvent.TargetEntity == null ? "null" : bufferedEvent.TargetEntity.ToString();
if (GameSettings.VerboseLogging)
{
DebugConsole.ThrowError("Failed to read server event for entity \"" + entityName + "\"!", e);
}
GameAnalyticsManager.AddErrorEventOnce("ServerEntityEventManager.Read:ReadFailed" + entityName,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Failed to read server event for entity \"" + entityName + "\"!\n" + e.StackTrace);
}
bufferedEvent.IsProcessed = true;
}
var inGameClients = clients.FindAll(c => c.InGame && !c.NeedsMidRoundSync);
if (inGameClients.Count > 0)
{
lastSentToAll = inGameClients[0].LastRecvEntityEventID;
if (server.OwnerConnection != null)
{
var owner = clients.Find(c => c.Connection == server.OwnerConnection);
if (owner != null)
{
lastSentToAll = owner.LastRecvEntityEventID;
}
}
inGameClients.ForEach(c => { if (NetIdUtils.IdMoreRecent(lastSentToAll, c.LastRecvEntityEventID)) lastSentToAll = c.LastRecvEntityEventID; });
ServerEntityEvent firstEventToResend = events.Find(e => e.ID == (ushort)(lastSentToAll + 1));
if (firstEventToResend != null && (Timing.TotalTime - firstEventToResend.CreateTime) > 10.0f)
{
//it's been 10 seconds since this event was created
//kick everyone that hasn't received it yet, this is way too old
List<Client> toKick = inGameClients.FindAll(c => NetIdUtils.IdMoreRecent((UInt16)(lastSentToAll + 1), c.LastRecvEntityEventID));
toKick.ForEach(c =>
{
DebugConsole.NewMessage(c.Name + " was kicked due to excessive desync (expected old event " + (c.LastRecvEntityEventID + 1).ToString() + ")", Color.Red);
GameServer.Log("Disconnecting client " + c.Name + " due to excessive desync (expected old event "
+ (c.LastRecvEntityEventID + 1).ToString() +
" (created " + (Timing.TotalTime - firstEventToResend.CreateTime).ToString("0.##") + " s ago)" +
" Events queued: " + events.Count + ", last sent to all: " + lastSentToAll, ServerLog.MessageType.ServerMessage);
server.DisconnectClient(c, "", "ServerMessage.ExcessiveDesync");
}
);
}
if (events.Count > 0)
{
//the client is waiting for an event that we don't have anymore
//(the ID they're expecting is smaller than the ID of the first event in our list)
List<Client> toKick = inGameClients.FindAll(c => NetIdUtils.IdMoreRecent(events[0].ID, (UInt16)(c.LastRecvEntityEventID + 1)));
toKick.ForEach(c =>
{
DebugConsole.NewMessage(c.Name + " was kicked due to excessive desync (expected removed event " + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", Color.Red);
GameServer.Log("Disconnecting client " + c.Name + " due to excessive desync (expected removed event" + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", ServerLog.MessageType.ServerMessage);
server.DisconnectClient(c, "", "ServerMessage.ExcessiveDesync");
});
}
}
var timedOutClients = clients.FindAll(c => c.InGame && c.NeedsMidRoundSync && Timing.TotalTime > c.MidRoundSyncTimeOut);
foreach (Client timedOutClient in timedOutClients)
{
GameServer.Log("Disconnecting client " + timedOutClient.Name + ". Syncing the client with the server took too long.", ServerLog.MessageType.ServerMessage);
GameMain.Server.DisconnectClient(timedOutClient, "", "ServerMessage.SyncTimeout");
}
bufferedEvents.RemoveAll(b => b.IsProcessed);
}
private void BufferEvent(BufferedEvent bufferedEvent)
{
if (bufferedEvents.Count > 512)
{
//should normally never happen
//a client could potentially spam events with a much higher character state ID
//than the state of their character and/or stop sending character inputs,
//so we'll drop some events to make sure no-one blows up our buffer
bufferedEvents.RemoveRange(0, 256);
}
bufferedEvents.Add(bufferedEvent);
}
/// <summary>
/// Writes all the events that the client hasn't received yet into the outgoing message
/// </summary>
public void Write(Client client, NetOutgoingMessage msg)
{
Write(client, msg, out _);
}
/// <summary>
/// Writes all the events that the client hasn't received yet into the outgoing message
/// </summary>
public void Write(Client client, NetOutgoingMessage msg, out List<NetEntityEvent> sentEvents)
{
List<NetEntityEvent> eventsToSync = null;
if (client.NeedsMidRoundSync)
{
eventsToSync = GetEventsToSync(client, uniqueEvents);
}
else
{
eventsToSync = GetEventsToSync(client, events);
}
if (eventsToSync.Count == 0)
{
sentEvents = eventsToSync;
return;
}
//too many events for one packet
if (eventsToSync.Count > MaxEventsPerWrite)
{
if (eventsToSync.Count > MaxEventsPerWrite * 3 && !client.NeedsMidRoundSync)
{
Color color = eventsToSync.Count > MaxEventsPerWrite * 20 ? Color.Red : Color.Orange;
if (eventsToSync.Count < MaxEventsPerWrite * 5) { color = Color.Yellow; }
DebugConsole.NewMessage("WARNING: event count very high: " + eventsToSync.Count + "/" + MaxEventsPerWrite, color);
}
eventsToSync.RemoveRange(MaxEventsPerWrite, eventsToSync.Count - MaxEventsPerWrite);
}
foreach (NetEntityEvent entityEvent in eventsToSync)
{
(entityEvent as ServerEntityEvent).Sent = true;
client.EntityEventLastSent[entityEvent.ID] = (float)NetTime.Now;
}
if (client.NeedsMidRoundSync)
{
msg.Write((byte)ServerNetObject.ENTITY_EVENT_INITIAL);
//how many (unique) events the clients had missed before joining
client.UnreceivedEntityEventCount = (UInt16)uniqueEvents.Count;
//ID of the first event sent after the client joined
//(after the client has been synced they'll switch their lastReceivedID
//to the one before this, and the eventmanagers will start to function "normally")
client.FirstNewEventID = events.Count == 0 ? (UInt16)0 : events[events.Count - 1].ID;
msg.Write(client.UnreceivedEntityEventCount);
msg.Write(client.FirstNewEventID);
Write(msg, eventsToSync, client);
}
else
{
msg.Write((byte)ServerNetObject.ENTITY_EVENT);
Write(msg, eventsToSync, client);
}
sentEvents = eventsToSync;
}
/// <summary>
/// Returns a list of events that should be sent to the client from the eventList
/// </summary>
/// <param name="client"></param>
/// <param name="eventList"></param>
/// <returns></returns>
private List<NetEntityEvent> GetEventsToSync(Client client, List<ServerEntityEvent> eventList)
{
List<NetEntityEvent> eventsToSync = new List<NetEntityEvent>();
if (eventList.Count == 0) return eventsToSync;
//find the index of the first event the client hasn't received
int startIndex = eventList.Count;
while (startIndex > 0 &&
NetIdUtils.IdMoreRecent(eventList[startIndex - 1].ID,client.LastRecvEntityEventID))
{
startIndex--;
}
for (int i = startIndex; i < eventList.Count; i++)
{
//find the first event that hasn't been sent in 1.5 * roundtriptime or at all
client.EntityEventLastSent.TryGetValue(eventList[i].ID, out float lastSent);
float minInterval = Math.Max(client.Connection.AverageRoundtripTime * 1.5f, (float)server.UpdateInterval.TotalSeconds * 2);
if (lastSent > NetTime.Now - Math.Min(minInterval, 0.5f))
{
continue;
}
eventsToSync.AddRange(eventList.GetRange(i, eventList.Count - i));
break;
}
return eventsToSync;
}
public void InitClientMidRoundSync(Client client)
{
//no need for midround syncing if no events have been created,
//or if the first created unique event is still in the event list
if (uniqueEvents.Count == 0 || (events.Count > 0 && events[0].ID == uniqueEvents[0].ID))
{
client.UnreceivedEntityEventCount = 0;
client.FirstNewEventID = 0;
client.NeedsMidRoundSync = false;
}
else
{
double midRoundSyncTimeOut = uniqueEvents.Count / MaxEventsPerWrite * server.UpdateInterval.TotalSeconds;
midRoundSyncTimeOut = Math.Max(10.0f, midRoundSyncTimeOut * 10.0f);
client.UnreceivedEntityEventCount = (UInt16)uniqueEvents.Count;
client.FirstNewEventID = 0;
client.NeedsMidRoundSync = true;
client.MidRoundSyncTimeOut = Timing.TotalTime + midRoundSyncTimeOut;
}
}
/// <summary>
/// Read the events from the message, ignoring ones we've already received
/// </summary>
public void Read(NetIncomingMessage msg, Client sender = null)
{
UInt16 firstEventID = msg.ReadUInt16();
int eventCount = msg.ReadByte();
for (int i = 0; i < eventCount; i++)
{
UInt16 thisEventID = (UInt16)(firstEventID + (UInt16)i);
UInt16 entityID = msg.ReadUInt16();
if (entityID == Entity.NullEntityID)
{
msg.ReadPadBits();
if (thisEventID == (UInt16)(sender.LastSentEntityEventID + 1)) sender.LastSentEntityEventID++;
continue;
}
byte msgLength = msg.ReadByte();
IClientSerializable entity = Entity.FindEntityByID(entityID) as IClientSerializable;
//skip the event if we've already received it
if (thisEventID != (UInt16)(sender.LastSentEntityEventID + 1))
{
if (GameSettings.VerboseLogging)
{
DebugConsole.NewMessage("received msg " + thisEventID, Microsoft.Xna.Framework.Color.Red);
}
msg.Position += msgLength * 8;
}
else if (entity == null)
{
//entity not found -> consider the even read and skip over it
//(can happen, for example, when a client uses a medical item repeatedly
//and creates an event for it before receiving the event about it being removed)
if (GameSettings.VerboseLogging)
{
DebugConsole.NewMessage(
"Received msg " + thisEventID + ", entity " + entityID + " not found",
Microsoft.Xna.Framework.Color.Orange);
}
sender.LastSentEntityEventID++;
msg.Position += msgLength * 8;
}
else
{
if (GameSettings.VerboseLogging)
{
DebugConsole.NewMessage("Received msg " + thisEventID, Microsoft.Xna.Framework.Color.Green);
}
UInt16 characterStateID = msg.ReadUInt16();
NetBuffer buffer = new NetBuffer();
buffer.Write(msg.ReadBytes(msgLength - 2));
BufferEvent(new BufferedEvent(sender, sender.Character, characterStateID, entity, buffer));
sender.LastSentEntityEventID++;
}
msg.ReadPadBits();
}
}
protected override void WriteEvent(NetBuffer buffer, NetEntityEvent entityEvent, Client recipient = null)
{
var serverEvent = entityEvent as ServerEntityEvent;
if (serverEvent == null) return;
serverEvent.Write(buffer, recipient);
}
protected void ReadEvent(NetBuffer buffer, INetSerializable entity, Client sender = null)
{
var clientEntity = entity as IClientSerializable;
if (clientEntity == null) return;
clientEntity.ServerRead(ClientNetObject.ENTITY_STATE, buffer, sender);
}
public void Clear()
{
ID = 0;
events.Clear();
bufferedEvents.Clear();
lastSentToAll = 0;
uniqueEvents.Clear();
foreach (Client c in server.ConnectedClients)
{
c.PositionUpdateLastSent.Clear();
c.EntityEventLastSent.Clear();
c.LastRecvEntityEventID = 0;
c.LastSentEntityEventID = 0;
}
}
}
}
@@ -2,27 +2,9 @@
{
abstract partial class NetworkMember
{
protected const CharacterInfo characterInfo = null;
protected const Character myCharacter = null;
public CharacterInfo CharacterInfo
{
get { return null; }
set
{
//do nothing
}
}
public Character Character
{
get { return null; }
}
private void InitProjSpecific()
{
//do nothing
}
}
}
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Text;
using Lidgren.Network;
namespace Barotrauma.Networking
{
partial class OrderChatMessage : ChatMessage
{
public override void ServerWrite(NetOutgoingMessage msg, Client c)
{
msg.Write((byte)ServerNetObject.CHAT_MESSAGE);
msg.Write(NetStateID);
msg.Write((byte)ChatMessageType.Order);
msg.Write(SenderName);
msg.Write(Sender != null && c.InGame);
if (Sender != null && c.InGame)
{
msg.Write(Sender.ID);
}
msg.Write((byte)Order.PrefabList.IndexOf(Order.Prefab));
msg.Write(TargetCharacter == null ? (UInt16)0 : TargetCharacter.ID);
msg.Write(TargetEntity == null ? (UInt16)0 : TargetEntity.ID);
msg.Write((byte)Array.IndexOf(Order.Prefab.Options, OrderOption));
}
}
}
@@ -0,0 +1,235 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma.Networking
{
partial class RespawnManager : Entity, IServerSerializable
{
private List<Client> GetClientsToRespawn()
{
return networkMember.ConnectedClients.FindAll(c =>
c.InGame &&
(!c.SpectateOnly || !((GameServer)networkMember).ServerSettings.AllowSpectating) &&
(c.Character == null || c.Character.IsDead));
}
private List<CharacterInfo> GetBotsToRespawn()
{
GameServer server = networkMember as GameServer;
if (server.ServerSettings.BotSpawnMode == BotSpawnMode.Normal)
{
return Character.CharacterList
.FindAll(c => c.TeamID == Character.TeamType.Team1 && c.AIController != null && c.Info != null && c.IsDead)
.Select(c => c.Info)
.ToList();
}
int currPlayerCount = server.ConnectedClients.Count(c => c.InGame && (!c.SpectateOnly || !server.ServerSettings.AllowSpectating));
//if (server.CharacterInfo != null) currPlayerCount++;
var existingBots = Character.CharacterList
.FindAll(c => c.TeamID == Character.TeamType.Team1 && c.AIController != null && c.Info != null);
int requiredBots = server.ServerSettings.BotCount - currPlayerCount;
requiredBots -= existingBots.Count(b => !b.IsDead);
List<CharacterInfo> botsToRespawn = new List<CharacterInfo>();
for (int i = 0; i < requiredBots; i++)
{
CharacterInfo botToRespawn = existingBots.Find(b => b.IsDead)?.Info;
if (botToRespawn == null)
{
botToRespawn = new CharacterInfo(Character.HumanConfigFile);
}
else
{
existingBots.Remove(botToRespawn.Character);
}
botsToRespawn.Add(botToRespawn);
}
return botsToRespawn;
}
partial void UpdateWaiting(float deltaTime)
{
var server = networkMember as GameServer;
int characterToRespawnCount = GetClientsToRespawn().Count;
int totalCharacterCount = server.ConnectedClients.Count;
/*if (server.Character != null)
{
totalCharacterCount++;
if (server.Character.IsDead) characterToRespawnCount++;
}*/
bool startCountdown = (float)characterToRespawnCount >= Math.Max((float)totalCharacterCount * server.ServerSettings.MinRespawnRatio, 1.0f);
if (startCountdown != CountdownStarted)
{
CountdownStarted = startCountdown;
server.CreateEntityEvent(this);
}
if (!CountdownStarted) return;
respawnTimer -= deltaTime;
if (respawnTimer <= 0.0f)
{
respawnTimer = server.ServerSettings.RespawnInterval;
DispatchShuttle();
}
if (respawnShuttle == null) return;
respawnShuttle.Velocity = Vector2.Zero;
if (shuttleSteering != null)
{
shuttleSteering.AutoPilot = false;
shuttleSteering.MaintainPos = false;
}
}
partial void DispatchShuttle()
{
var server = networkMember as GameServer;
if (server == null) return;
if (respawnShuttle != null)
{
state = State.Transporting;
server.CreateEntityEvent(this);
ResetShuttle();
if (shuttleSteering != null)
{
shuttleSteering.TargetVelocity = Vector2.Zero;
}
GameServer.Log("Dispatching the respawn shuttle.", ServerLog.MessageType.Spawning);
RespawnCharacters();
CoroutineManager.StopCoroutines("forcepos");
CoroutineManager.StartCoroutine(ForceShuttleToPos(Level.Loaded.StartPosition - Vector2.UnitY * Level.ShaftHeight, 100.0f), "forcepos");
}
else
{
state = State.Waiting;
GameServer.Log("Respawning everyone in main sub.", ServerLog.MessageType.Spawning);
server.CreateEntityEvent(this);
RespawnCharacters();
}
}
partial void RespawnCharactersProjSpecific()
{
var server = networkMember as GameServer;
if (server == null) return;
var respawnSub = respawnShuttle ?? Submarine.MainSub;
var clients = GetClientsToRespawn();
foreach (Client c in clients)
{
//all characters are in Team 1 in game modes/missions with only one team.
//if at some point we add a game mode with multiple teams where respawning is possible, this needs to be reworked
c.TeamID = Character.TeamType.Team1;
if (c.CharacterInfo == null) c.CharacterInfo = new CharacterInfo(Character.HumanConfigFile, c.Name);
}
List<CharacterInfo> characterInfos = clients.Select(c => c.CharacterInfo).ToList();
var botsToSpawn = GetBotsToRespawn();
characterInfos.AddRange(botsToSpawn);
server.AssignJobs(clients);
foreach (Client c in clients)
{
c.CharacterInfo.Job = new Job(c.AssignedJob);
}
//the spawnpoints where the characters will spawn
var shuttleSpawnPoints = WayPoint.SelectCrewSpawnPoints(characterInfos, respawnSub);
//the spawnpoints where they would spawn if they were spawned inside the main sub
//(in order to give them appropriate ID card tags)
var mainSubSpawnPoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSub);
ItemPrefab divingSuitPrefab = MapEntityPrefab.Find(null, "divingsuit") as ItemPrefab;
ItemPrefab oxyPrefab = MapEntityPrefab.Find(null, "oxygentank") as ItemPrefab;
ItemPrefab scooterPrefab = MapEntityPrefab.Find(null, "underwaterscooter") as ItemPrefab;
ItemPrefab batteryPrefab = MapEntityPrefab.Find(null, "batterycell") as ItemPrefab;
var cargoSp = WayPoint.WayPointList.Find(wp => wp.Submarine == respawnSub && wp.SpawnType == SpawnType.Cargo);
for (int i = 0; i < characterInfos.Count; i++)
{
bool bot = i >= clients.Count;
var character = Character.Create(characterInfos[i], shuttleSpawnPoints[i].WorldPosition, characterInfos[i].Name, !bot, bot);
character.TeamID = Character.TeamType.Team1;
if (bot)
{
GameServer.Log(string.Format("Respawning bot {0} as {1}", character.Info.Name, characterInfos[i].Job.Name), ServerLog.MessageType.Spawning);
}
else
{
clients[i].Character = character;
character.OwnerClientIP = clients[i].Connection.RemoteEndPoint.Address.ToString();
character.OwnerClientName = clients[i].Name;
GameServer.Log(string.Format("Respawning {0} ({1}) as {2}", clients[i].Name, clients[i].Connection?.RemoteEndPoint?.Address, characterInfos[i].Job.Name), ServerLog.MessageType.Spawning);
}
if (divingSuitPrefab != null && oxyPrefab != null && respawnShuttle != null)
{
Vector2 pos = cargoSp == null ? character.Position : cargoSp.Position;
if (divingSuitPrefab != null && oxyPrefab != null)
{
var divingSuit = new Item(divingSuitPrefab, pos, respawnSub);
Spawner.CreateNetworkEvent(divingSuit, false);
respawnItems.Add(divingSuit);
var oxyTank = new Item(oxyPrefab, pos, respawnSub);
Spawner.CreateNetworkEvent(oxyTank, false);
divingSuit.Combine(oxyTank);
respawnItems.Add(oxyTank);
}
if (scooterPrefab != null && batteryPrefab != null)
{
var scooter = new Item(scooterPrefab, pos, respawnSub);
Spawner.CreateNetworkEvent(scooter, false);
var battery = new Item(batteryPrefab, pos, respawnSub);
Spawner.CreateNetworkEvent(battery, false);
scooter.Combine(battery);
respawnItems.Add(scooter);
respawnItems.Add(battery);
}
}
//give the character the items they would've gotten if they had spawned in the main sub
character.GiveJobItems(mainSubSpawnPoints[i]);
//add the ID card tags they should've gotten when spawning in the shuttle
foreach (Item item in character.Inventory.Items)
{
if (item == null || item.Prefab.Name != "ID Card") continue;
foreach (string s in shuttleSpawnPoints[i].IdCardTags)
{
item.AddTag(s);
}
if (!string.IsNullOrWhiteSpace(shuttleSpawnPoints[i].IdCardDesc))
item.Description = shuttleSpawnPoints[i].IdCardDesc;
}
}
}
}
}
@@ -0,0 +1,103 @@
using Facepunch.Steamworks;
using System.Linq;
namespace Barotrauma.Steam
{
partial class SteamManager
{
#region Server
public static bool CreateServer(Networking.GameServer server, bool isPublic)
{
Instance.isInitialized = true;
ServerInit options = new ServerInit("Barotrauma", "Barotrauma")
{
GamePort = (ushort)server.Port,
QueryPort = (ushort)server.QueryPort
};
//options.QueryShareGamePort();
instance.server = new Server(AppID, options, isPublic);
if (!instance.server.IsValid)
{
instance.server.Dispose();
instance.server = null;
DebugConsole.ThrowError("Initializing Steam server failed.");
return false;
}
RefreshServerDetails(server);
instance.server.Auth.OnAuthChange = server.OnAuthChange;
Instance.server.LogOnAnonymous();
return true;
}
public static bool RefreshServerDetails(Networking.GameServer server)
{
if (instance == null || !instance.isInitialized)
{
return false;
}
// These server state variables may be changed at any time. Note that there is no longer a mechanism
// to send the player count. The player count is maintained by steam and you should use the player
// creation/authentication functions to maintain your player count.
instance.server.ServerName = server.Name;
instance.server.MaxPlayers = server.ServerSettings.MaxPlayers;
instance.server.Passworded = server.ServerSettings.HasPassword;
Instance.server.SetKey("message", GameMain.Server.ServerSettings.ServerMessageText);
Instance.server.SetKey("version", GameMain.Version.ToString());
Instance.server.SetKey("contentpackage", string.Join(",", GameMain.Config.SelectedContentPackages.Select(cp => cp.Name)));
Instance.server.SetKey("contentpackagehash", string.Join(",", GameMain.Config.SelectedContentPackages.Select(cp => cp.MD5hash.Hash)));
Instance.server.SetKey("contentpackageurl", string.Join(",", GameMain.Config.SelectedContentPackages.Select(cp => cp.SteamWorkshopUrl ?? "")));
Instance.server.SetKey("usingwhitelist", (server.ServerSettings.Whitelist != null && server.ServerSettings.Whitelist.Enabled).ToString());
Instance.server.SetKey("modeselectionmode", server.ServerSettings.ModeSelectionMode.ToString());
Instance.server.SetKey("subselectionmode", server.ServerSettings.SubSelectionMode.ToString());
Instance.server.SetKey("allowspectating", server.ServerSettings.AllowSpectating.ToString());
Instance.server.SetKey("allowrespawn", server.ServerSettings.AllowRespawn.ToString());
Instance.server.SetKey("traitors", server.ServerSettings.TraitorsEnabled.ToString());
Instance.server.SetKey("gamestarted", server.GameStarted.ToString());
Instance.server.SetKey("gamemode", server.ServerSettings.GameModeIdentifier);
instance.server.DedicatedServer = true;
return true;
}
public static bool StartAuthSession(byte[] authTicketData, ulong clientSteamID)
{
if (instance == null || !instance.isInitialized || instance.server == null) return false;
DebugConsole.Log("SteamManager authenticating Steam client " + clientSteamID);
if (!instance.server.Auth.StartSession(authTicketData, clientSteamID))
{
DebugConsole.Log("Authentication failed");
return false;
}
return true;
}
public static void StopAuthSession(ulong clientSteamID)
{
if (instance == null || !instance.isInitialized || instance.server == null) return;
DebugConsole.Log("SteamManager ending auth session with Steam client " + clientSteamID);
instance.server.Auth.EndSession(clientSteamID);
}
public static bool CloseServer()
{
if (instance == null || !instance.isInitialized || instance.server == null) return false;
instance.server.Dispose();
instance.server = null;
return true;
}
#endregion
}
}
@@ -0,0 +1,100 @@
using Barotrauma.Items.Components;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Text;
namespace Barotrauma.Networking
{
class VoipServer
{
private NetServer netServer;
private List<VoipQueue> queues;
private Dictionary<VoipQueue,DateTime> lastSendTime;
public VoipServer(NetServer server)
{
this.netServer = server;
queues = new List<VoipQueue>();
lastSendTime = new Dictionary<VoipQueue, DateTime>();
}
public void RegisterQueue(VoipQueue queue)
{
if (!queues.Contains(queue)) queues.Add(queue);
}
public void UnregisterQueue(VoipQueue queue)
{
if (queues.Contains(queue)) queues.Remove(queue);
}
public void SendToClients(List<Client> clients)
{
foreach (VoipQueue queue in queues)
{
if (queue.LastReadTime < DateTime.Now - VoipConfig.SEND_INTERVAL) { continue; }
if (lastSendTime.ContainsKey(queue))
{
if ((lastSendTime[queue] + VoipConfig.SEND_INTERVAL) > DateTime.Now) { continue; }
lastSendTime[queue] = DateTime.Now;
}
else
{
lastSendTime.Add(queue, DateTime.Now);
}
Client sender = clients.Find(c => c.VoipQueue == queue);
foreach (Client recipient in clients)
{
if (recipient == sender) { continue; }
if (!CanReceive(sender, recipient)) { continue; }
NetOutgoingMessage msg = netServer.CreateMessage();
msg.Write((byte)ServerPacketHeader.VOICE);
msg.Write((byte)queue.QueueID);
queue.Write(msg);
GameMain.Server.CompressOutgoingMessage(msg);
netServer.SendMessage(msg, recipient.Connection, NetDeliveryMethod.Unreliable);
}
}
}
private bool CanReceive(Client sender, Client recipient)
{
if (Screen.Selected != GameMain.GameScreen) { return true; }
//no-one can hear muted players
if (sender.Muted) { return false; }
bool recipientSpectating = recipient.Character == null || recipient.Character.IsDead;
bool senderSpectating = sender.Character == null || sender.Character.IsDead;
//spectators cannot speak with in-game players or vice versa
if (recipientSpectating != senderSpectating) { return false; }
//both spectating, no need to do radio/distance checks
if (recipientSpectating && senderSpectating) { return true; }
//sender can't speak
if (sender.Character != null && sender.Character.SpeechImpediment >= 100.0f) { return false; }
//check if the message can be sent via radio
if (ChatMessage.CanUseRadio(sender.Character, out WifiComponent senderRadio) &&
ChatMessage.CanUseRadio(recipient.Character, out WifiComponent recipientRadio))
{
if (recipientRadio.CanReceive(senderRadio)) { return true; }
}
//otherwise do a distance check
return ChatMessage.GetGarbleAmount(recipient.Character, sender.Character, ChatMessage.SpeakRange) < 1.0f;
}
}
}
@@ -1,4 +1,10 @@
namespace Barotrauma
using Barotrauma.Networking;
using Lidgren.Network;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Voting
{
@@ -12,6 +18,118 @@
get { return allowModeVoting; }
set { allowModeVoting = value; }
}
public void ServerRead(NetIncomingMessage inc, Client sender)
{
if (GameMain.Server == null || sender == null) return;
byte voteTypeByte = inc.ReadByte();
VoteType voteType = VoteType.Unknown;
try
{
voteType = (VoteType)voteTypeByte;
}
catch (Exception e)
{
DebugConsole.ThrowError("Failed to cast vote type \"" + voteTypeByte + "\"", e);
return;
}
switch (voteType)
{
case VoteType.Sub:
string subName = inc.ReadString();
Submarine sub = Submarine.SavedSubmarines.FirstOrDefault(s => s.Name == subName);
sender.SetVote(voteType, sub);
break;
case VoteType.Mode:
string modeIdentifier = inc.ReadString();
GameModePreset mode = GameModePreset.List.Find(gm => gm.Identifier == modeIdentifier);
if (!mode.Votable) break;
sender.SetVote(voteType, mode);
break;
case VoteType.EndRound:
if (!sender.HasSpawned) return;
sender.SetVote(voteType, inc.ReadBoolean());
GameMain.NetworkMember.EndVoteCount = GameMain.Server.ConnectedClients.Count(c => c.HasSpawned && c.GetVote<bool>(VoteType.EndRound));
GameMain.NetworkMember.EndVoteMax = GameMain.Server.ConnectedClients.Count(c => c.HasSpawned);
break;
case VoteType.Kick:
byte kickedClientID = inc.ReadByte();
Client kicked = GameMain.Server.ConnectedClients.Find(c => c.ID == kickedClientID);
if (kicked != null && !kicked.HasKickVoteFrom(sender))
{
kicked.AddKickVote(sender);
Client.UpdateKickVotes(GameMain.Server.ConnectedClients);
GameMain.Server.SendChatMessage($"ServerMessage.HasVotedToKick_[initiator]={sender.Name}_[target]={kicked.Name}", ChatMessageType.Server, null);
}
break;
case VoteType.StartRound:
bool ready = inc.ReadBoolean();
if (ready != sender.GetVote<bool>(VoteType.StartRound))
{
sender.SetVote(VoteType.StartRound, ready);
GameServer.Log(sender.Name + (ready ? " is ready to start the game." : " is not ready to start the game."), ServerLog.MessageType.ServerMessage);
}
break;
}
inc.ReadPadBits();
GameMain.Server.UpdateVoteStatus();
}
public void ServerWrite(NetBuffer msg)
{
if (GameMain.Server == null) return;
msg.Write(allowSubVoting);
if (allowSubVoting)
{
List<Pair<object, int>> voteList = GetVoteList(VoteType.Sub, GameMain.Server.ConnectedClients);
msg.Write((byte)voteList.Count);
foreach (Pair<object, int> vote in voteList)
{
msg.Write((byte)vote.Second);
msg.Write(((Submarine)vote.First).Name);
}
}
msg.Write(AllowModeVoting);
if (allowModeVoting)
{
List<Pair<object, int>> voteList = GetVoteList(VoteType.Mode, GameMain.Server.ConnectedClients);
msg.Write((byte)voteList.Count);
foreach (Pair<object, int> vote in voteList)
{
msg.Write((byte)vote.Second);
msg.Write(((GameModePreset)vote.First).Identifier);
}
}
msg.Write(AllowEndVoting);
if (AllowEndVoting)
{
msg.Write((byte)GameMain.Server.ConnectedClients.Count(v => v.GetVote<bool>(VoteType.EndRound)));
msg.Write((byte)GameMain.Server.ConnectedClients.Count);
}
msg.Write(AllowVoteKick);
var readyClients = GameMain.Server.ConnectedClients.FindAll(c => c.GetVote<bool>(VoteType.StartRound));
msg.Write((byte)readyClients.Count);
foreach (Client c in readyClients)
{
msg.Write(c.ID);
}
msg.WritePadBits();
}
}
}
@@ -0,0 +1,209 @@
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
namespace Barotrauma.Networking
{
partial class WhiteListedPlayer
{
private static UInt16 LastIdentifier = 0;
public WhiteListedPlayer(string name,string ip)
{
Name = name;
IP = ip;
UniqueIdentifier = LastIdentifier; LastIdentifier++;
}
}
partial class WhiteList
{
partial void InitProjSpecific()
{
if (File.Exists(SavePath))
{
string[] lines;
try
{
lines = File.ReadAllLines(SavePath);
}
catch (Exception e)
{
DebugConsole.ThrowError("Failed to open whitelist in " + SavePath, e);
return;
}
foreach (string line in lines)
{
if (line[0] == '#')
{
string lineval = line.Substring(1, line.Length - 1);
int intVal = 0;
Int32.TryParse(lineval, out intVal);
if (lineval.ToLower() == "true" || intVal != 0)
{
Enabled = true;
}
else
{
Enabled = false;
}
}
else
{
string[] separatedLine = line.Split(',');
if (separatedLine.Length < 2) continue;
string name = String.Join(",", separatedLine.Take(separatedLine.Length - 1));
string ip = separatedLine.Last();
whitelistedPlayers.Add(new WhiteListedPlayer(name, ip));
}
}
}
}
public void Save()
{
GameServer.Log("Saving whitelist", ServerLog.MessageType.ServerMessage);
List<string> lines = new List<string>();
if (Enabled)
{
lines.Add("#true");
}
else
{
lines.Add("#false");
}
foreach (WhiteListedPlayer wlp in whitelistedPlayers)
{
lines.Add(wlp.Name + "," + wlp.IP);
}
try
{
File.WriteAllLines(SavePath, lines);
}
catch (Exception e)
{
DebugConsole.ThrowError("Saving the whitelist to " + SavePath + " failed", e);
}
}
public bool IsWhiteListed(string name, IPAddress address)
{
if (!Enabled) return true;
WhiteListedPlayer wlp = whitelistedPlayers.Find(p => p.Name == name);
if (wlp == null) return false;
if (!string.IsNullOrWhiteSpace(wlp.IP))
{
if (address.IsIPv4MappedToIPv6 && wlp.IP == address.MapToIPv4().ToString())
{
return true;
}
else
{
return wlp.IP == address.ToString();
}
}
return true;
}
private void RemoveFromWhiteList(WhiteListedPlayer wlp)
{
GameServer.Log("Removing " + wlp.Name + " from whitelist", ServerLog.MessageType.ServerMessage);
whitelistedPlayers.Remove(wlp);
Save();
}
private void AddToWhiteList(string name, string ip)
{
if (string.IsNullOrWhiteSpace(name)) return;
if (whitelistedPlayers.Any(x => x.Name.ToLower() == name.ToLower() && x.IP == ip)) return;
whitelistedPlayers.Add(new WhiteListedPlayer(name, ip));
Save();
}
public void ServerAdminWrite(NetBuffer outMsg, Client c)
{
if (!c.HasPermission(ClientPermissions.ManageSettings))
{
outMsg.Write(false); outMsg.WritePadBits();
return;
}
outMsg.Write(true);
outMsg.Write(c.Connection == GameMain.Server.OwnerConnection);
outMsg.Write(Enabled);
outMsg.WritePadBits();
outMsg.WriteVariableInt32(whitelistedPlayers.Count);
for (int i = 0; i < whitelistedPlayers.Count; i++)
{
WhiteListedPlayer whitelistedPlayer = whitelistedPlayers[i];
outMsg.Write(whitelistedPlayer.Name);
outMsg.Write(whitelistedPlayer.UniqueIdentifier);
if (c.Connection == GameMain.Server.OwnerConnection)
{
outMsg.Write(whitelistedPlayer.IP);
//outMsg.Write(whitelistedPlayer.SteamID); //TODO: add steamid to whitelisted players
}
}
}
public bool ServerAdminRead(NetBuffer incMsg, Client c)
{
if (!c.HasPermission(ClientPermissions.ManageSettings))
{
bool enabled = incMsg.ReadBoolean(); incMsg.ReadPadBits();
UInt16 removeCount = incMsg.ReadUInt16();
incMsg.Position += removeCount * 4 * 8;
UInt16 addCount = incMsg.ReadUInt16();
for (int i = 0; i < addCount; i++)
{
incMsg.ReadString(); //skip name
incMsg.ReadString(); //skip ip
}
return false;
}
else
{
bool prevEnabled = Enabled;
bool enabled = incMsg.ReadBoolean(); incMsg.ReadPadBits();
Enabled = enabled;
UInt16 removeCount = incMsg.ReadUInt16();
for (int i = 0; i < removeCount; i++)
{
UInt16 id = incMsg.ReadUInt16();
WhiteListedPlayer whitelistedPlayer = whitelistedPlayers.Find(p => p.UniqueIdentifier == id);
if (whitelistedPlayer != null)
{
GameServer.Log(c.Name + " removed " + whitelistedPlayer.Name + " from whitelist (" + whitelistedPlayer.IP + ")", ServerLog.MessageType.ConsoleUsage);
RemoveFromWhiteList(whitelistedPlayer);
}
}
UInt16 addCount = incMsg.ReadUInt16();
for (int i = 0; i < addCount; i++)
{
string name = incMsg.ReadString();
string ip = incMsg.ReadString();
GameServer.Log(c.Name + " added " + name + " to whitelist (" + ip + ")", ServerLog.MessageType.ConsoleUsage);
AddToWhiteList(name, ip);
}
return removeCount > 0 || addCount > 0 || prevEnabled!=enabled;
}
}
}
}