(61d00a474) v0.9.7.1
This commit is contained in:
@@ -0,0 +1,370 @@
|
||||
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;
|
||||
|
||||
this.IP = "";
|
||||
}
|
||||
|
||||
public bool CompareTo(string ipCompare)
|
||||
{
|
||||
if (string.IsNullOrEmpty(IP) || string.IsNullOrEmpty(IP)) { return false; }
|
||||
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 (string.IsNullOrEmpty(IP) || ipCompare == null) { return false; }
|
||||
if (ipCompare.IsIPv4MappedToIPv6 && CompareTo(ipCompare.MapToIPv4NoThrow().ToString()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return CompareTo(ipCompare.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
partial class BanList
|
||||
{
|
||||
const string SavePath = "Data/bannedplayers.txt";
|
||||
|
||||
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, out string reason)
|
||||
{
|
||||
reason = string.Empty;
|
||||
if (IPAddress.IsLoopback(IP)) { return false; }
|
||||
var bannedPlayer = bannedPlayers.Find(bp => bp.CompareTo(IP) || (steamID > 0 && bp.SteamID == steamID));
|
||||
reason = bannedPlayer?.Reason;
|
||||
return bannedPlayer != null;
|
||||
}
|
||||
|
||||
public bool IsBanned(IPAddress IP, out string reason)
|
||||
{
|
||||
reason = string.Empty;
|
||||
if (IPAddress.IsLoopback(IP)) { return false; }
|
||||
bannedPlayers.RemoveAll(bp => bp.ExpirationTime.HasValue && DateTime.Now > bp.ExpirationTime.Value);
|
||||
var bannedPlayer = bannedPlayers.Find(bp => bp.CompareTo(IP));
|
||||
reason = bannedPlayer?.Reason;
|
||||
return bannedPlayer != null;
|
||||
}
|
||||
|
||||
public bool IsBanned(ulong steamID, out string reason)
|
||||
{
|
||||
reason = string.Empty;
|
||||
bannedPlayers.RemoveAll(bp => bp.ExpirationTime.HasValue && DateTime.Now > bp.ExpirationTime.Value);
|
||||
var bannedPlayer = bannedPlayers.Find(bp => steamID > 0 && bp.SteamID == steamID);
|
||||
reason = bannedPlayer?.Reason;
|
||||
return bannedPlayer != null;
|
||||
}
|
||||
|
||||
public void BanPlayer(string name, IPAddress ip, string reason, TimeSpan? duration)
|
||||
{
|
||||
string ipStr = ip.IsIPv4MappedToIPv6 ? ip.MapToIPv4NoThrow().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;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(ip))
|
||||
{
|
||||
bannedPlayers.Add(new BannedPlayer(name, ip, reason, expirationTime));
|
||||
}
|
||||
else if (steamID > 0)
|
||||
{
|
||||
bannedPlayers.Add(new BannedPlayer(name, steamID, reason, expirationTime));
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to ban a client (no valid IP or Steam ID given)");
|
||||
return;
|
||||
}
|
||||
|
||||
Save();
|
||||
}
|
||||
|
||||
public void UnbanPlayer(string name)
|
||||
{
|
||||
name = name.ToLower();
|
||||
var player = bannedPlayers.Find(bp => bp.Name.ToLower() == 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(IWriteMessage outMsg, Client c)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (outMsg == null) { throw new ArgumentException("OutMsg was null"); }
|
||||
if (GameMain.Server == null) { throw new Exception("GameMain.Server was null"); }
|
||||
|
||||
if (!c.HasPermission(ClientPermissions.Ban))
|
||||
{
|
||||
outMsg.Write(false); outMsg.WritePadBits();
|
||||
return;
|
||||
}
|
||||
|
||||
outMsg.Write(true);
|
||||
outMsg.Write(c.Connection == GameMain.Server.OwnerConnection);
|
||||
|
||||
outMsg.WritePadBits();
|
||||
outMsg.WriteVariableUInt32((UInt32)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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
string errorMsg = "Error while writing banlist. {" + e + "}\n" + e.StackTrace;
|
||||
GameAnalyticsManager.AddErrorEventOnce("Banlist.ServerAdminWrite", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ServerAdminRead(IReadMessage incMsg, Client c)
|
||||
{
|
||||
if (!c.HasPermission(ClientPermissions.Ban))
|
||||
{
|
||||
UInt16 removeCount = incMsg.ReadUInt16();
|
||||
incMsg.BitPosition += removeCount * 4 * 8;
|
||||
UInt16 rangeBanCount = incMsg.ReadUInt16();
|
||||
incMsg.BitPosition += 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,172 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
partial class ChatMessage
|
||||
{
|
||||
public static void ServerRead(IReadMessage 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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
//order/report messages can be sent a little faster than normal messages without triggering the spam filter
|
||||
if (orderMsg != null)
|
||||
{
|
||||
similarity *= 0.25f;
|
||||
}
|
||||
|
||||
bool isOwner = GameMain.Server.OwnerConnection != null && c.Connection == GameMain.Server.OwnerConnection;
|
||||
|
||||
if (similarity + c.ChatSpamSpeed > 5.0f && !isOwner)
|
||||
{
|
||||
GameMain.Server.KarmaManager.OnSpamFilterTriggered(c);
|
||||
|
||||
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)
|
||||
{
|
||||
//do nothing
|
||||
}
|
||||
else if (orderTargetCharacter != null)
|
||||
{
|
||||
orderTargetCharacter.SetOrder(
|
||||
new Order(orderMsg.Order.Prefab, orderTargetEntity, (orderTargetEntity as Item)?.Components.FirstOrDefault(ic => ic.GetType() == orderMsg.Order.ItemComponentType)),
|
||||
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(IWriteMessage 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,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
static partial class ChildServerRelay
|
||||
{
|
||||
public static void Start(string writeHandle, string readHandle)
|
||||
{
|
||||
var writePipe = new AnonymousPipeClientStream(PipeDirection.Out, writeHandle);
|
||||
var readPipe = new AnonymousPipeClientStream(PipeDirection.In, readHandle);
|
||||
|
||||
writeStream = writePipe; readStream = readPipe;
|
||||
|
||||
PrivateStart();
|
||||
}
|
||||
|
||||
public static void ShutDown()
|
||||
{
|
||||
PrivateShutDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
partial class Client : IDisposable
|
||||
{
|
||||
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 int RoundsSincePlayedAsTraitor;
|
||||
|
||||
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, double> EntityEventLastSent = new Dictionary<UInt16, double>();
|
||||
|
||||
//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<Pair<JobPrefab, int>> JobPreferences;
|
||||
public Pair<JobPrefab, int> AssignedJob;
|
||||
|
||||
public float DeleteDisconnectedTimer;
|
||||
|
||||
private CharacterInfo characterInfo;
|
||||
public CharacterInfo CharacterInfo
|
||||
{
|
||||
get { return characterInfo; }
|
||||
set
|
||||
{
|
||||
if (characterInfo == value) { return; }
|
||||
characterInfo?.Remove();
|
||||
characterInfo = value;
|
||||
}
|
||||
}
|
||||
public NetworkConnection Connection { get; set; }
|
||||
|
||||
public bool SpectateOnly;
|
||||
|
||||
public int KarmaKickCount;
|
||||
|
||||
private float karma = 100.0f;
|
||||
public float Karma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (GameMain.Server == null || !GameMain.Server.ServerSettings.KarmaEnabled) { return 100.0f; }
|
||||
if (HasPermission(ClientPermissions.KarmaImmunity)) { return 100.0f; }
|
||||
return karma;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (GameMain.Server == null || !GameMain.Server.ServerSettings.KarmaEnabled) { return; }
|
||||
karma = Math.Min(Math.Max(value, 0.0f), 100.0f);
|
||||
}
|
||||
}
|
||||
|
||||
partial void InitProjSpecific()
|
||||
{
|
||||
var jobs = JobPrefab.Prefabs.ToList();
|
||||
// TODO: modding support?
|
||||
JobPreferences = new List<Pair<JobPrefab, int>>(jobs.GetRange(0, Math.Min(jobs.Count, 3)).Select(j => new Pair<JobPrefab, int>(j, 0)));
|
||||
|
||||
VoipQueue = new VoipQueue(ID, true, true);
|
||||
GameMain.Server.VoipServer.RegisterQueue(VoipQueue);
|
||||
|
||||
//initialize to infinity, gets set to a proper value when initializing midround syncing
|
||||
MidRoundSyncTimeOut = double.PositiveInfinity;
|
||||
}
|
||||
|
||||
partial void DisposeProjSpecific()
|
||||
{
|
||||
GameMain.Server.VoipServer.UnregisterQueue(VoipQueue);
|
||||
VoipQueue.Dispose();
|
||||
characterInfo?.Remove();
|
||||
characterInfo = null;
|
||||
}
|
||||
|
||||
public void InitClientSync()
|
||||
{
|
||||
LastSentChatMsgID = 0;
|
||||
LastRecvChatMsgID = ChatMessage.LastID;
|
||||
|
||||
LastRecvLobbyUpdate = 0;
|
||||
|
||||
LastRecvEntityEventID = 0;
|
||||
|
||||
UnreceivedEntityEventCount = 0;
|
||||
NeedsMidRoundSync = false;
|
||||
}
|
||||
|
||||
public static bool IsValidName(string name, ServerSettings serverSettings)
|
||||
{
|
||||
char[] disallowedChars = new char[] { ';', ',', '<', '>', '/', '\\', '[', ']', '"', '?' };
|
||||
if (name.Any(c => disallowedChars.Contains(c))) return false;
|
||||
|
||||
foreach (char character in name)
|
||||
{
|
||||
if (!serverSettings.AllowedClientNameChars.Any(charRange => (int)character >= charRange.First && (int)character <= charRange.Second)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool EndpointMatches(string endpoint)
|
||||
{
|
||||
if (Connection is LidgrenConnection lidgrenConn)
|
||||
{
|
||||
if (lidgrenConn.IPEndPoint?.Address == null) { return false; }
|
||||
if ((lidgrenConn.IPEndPoint?.Address.IsIPv4MappedToIPv6 ?? false) &&
|
||||
lidgrenConn.IPEndPoint?.Address.MapToIPv4NoThrow().ToString() == endpoint)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return Connection.EndPointString == endpoint;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class EntitySpawner : Entity, IServerSerializable
|
||||
{
|
||||
public void CreateNetworkEvent(Entity entity, bool remove)
|
||||
{
|
||||
CreateNetworkEventProjSpecific(entity, remove);
|
||||
}
|
||||
|
||||
partial void CreateNetworkEventProjSpecific(Entity entity, bool remove)
|
||||
{
|
||||
if (GameMain.Server != null && entity != null)
|
||||
{
|
||||
GameMain.Server.CreateEntityEvent(this, new object[] { new SpawnOrRemove(entity, remove) });
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerWrite(IWriteMessage 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.OriginalID);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (entities.Entity is Item)
|
||||
{
|
||||
message.Write((byte)SpawnableType.Item);
|
||||
DebugConsole.Log("Writing item spawn data " + entities.Entity.ToString() + " (original ID: " + entities.OriginalID + ", current ID: " + entities.Entity.ID + ")");
|
||||
((Item)entities.Entity).WriteSpawnData(message, entities.OriginalID);
|
||||
}
|
||||
else if (entities.Entity is Character)
|
||||
{
|
||||
message.Write((byte)SpawnableType.Character);
|
||||
DebugConsole.Log("Writing character spawn data: " + entities.Entity.ToString() + " (original ID: " + entities.OriginalID + ", current ID: " + entities.Entity.ID + ")");
|
||||
((Character)entities.Entity).WriteSpawnData(message, entities.OriginalID, restrictMessageSize: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class FileSender
|
||||
{
|
||||
public class FileTransferOut
|
||||
{
|
||||
private readonly byte[] data;
|
||||
|
||||
private readonly DateTime startingTime;
|
||||
|
||||
private readonly NetworkConnection 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 KnownReceivedOffset / (float)Data.Length; }
|
||||
}
|
||||
|
||||
public float WaitTimer
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public byte[] Data
|
||||
{
|
||||
get { return data; }
|
||||
}
|
||||
|
||||
public bool Acknowledged;
|
||||
|
||||
public int SentOffset
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public int KnownReceivedOffset;
|
||||
|
||||
public NetworkConnection Connection
|
||||
{
|
||||
get { return connection; }
|
||||
}
|
||||
|
||||
public int ID;
|
||||
|
||||
public FileTransferOut(NetworkConnection recipient, FileTransferType fileType, string filePath)
|
||||
{
|
||||
connection = recipient;
|
||||
|
||||
FileType = fileType;
|
||||
FilePath = filePath;
|
||||
FileName = Path.GetFileName(filePath);
|
||||
|
||||
Acknowledged = false;
|
||||
SentOffset = 0;
|
||||
KnownReceivedOffset = 0;
|
||||
|
||||
Status = FileTransferStatus.NotStarted;
|
||||
|
||||
startingTime = DateTime.Now;
|
||||
|
||||
int maxRetries = 4;
|
||||
for (int i = 0; i <= maxRetries; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
data = File.ReadAllBytes(filePath);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
if (i >= maxRetries) { throw; }
|
||||
DebugConsole.NewMessage("Failed to initiate a file transfer {" + e.Message + "}, retrying in 250 ms...", Color.Red);
|
||||
Thread.Sleep(250);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 readonly List<FileTransferOut> activeTransfers;
|
||||
|
||||
private readonly int chunkLen;
|
||||
|
||||
private readonly ServerPeer peer;
|
||||
|
||||
public List<FileTransferOut> ActiveTransfers
|
||||
{
|
||||
get { return activeTransfers; }
|
||||
}
|
||||
|
||||
public FileSender(ServerPeer serverPeer, int mtu)
|
||||
{
|
||||
peer = serverPeer;
|
||||
chunkLen = mtu - 100;
|
||||
|
||||
activeTransfers = new List<FileTransferOut>();
|
||||
}
|
||||
|
||||
public FileTransferOut StartTransfer(NetworkConnection 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)
|
||||
{
|
||||
ID = 1
|
||||
};
|
||||
while (activeTransfers.Any(t => t.Connection == recipient && t.ID == transfer.ID))
|
||||
{
|
||||
transfer.ID++;
|
||||
}
|
||||
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 != NetworkConnectionStatus.Connected);
|
||||
|
||||
var endedTransfers = activeTransfers.FindAll(t =>
|
||||
t.Connection.Status != NetworkConnectionStatus.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;
|
||||
|
||||
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);
|
||||
|
||||
IWriteMessage message;
|
||||
|
||||
try
|
||||
{
|
||||
//first message; send length, file name etc
|
||||
//wait for acknowledgement before sending data
|
||||
if (!transfer.Acknowledged)
|
||||
{
|
||||
message = new WriteOnlyMessage();
|
||||
message.Write((byte)ServerPacketHeader.FILE_TRANSFER);
|
||||
|
||||
//if the recipient is the owner of the server (= a client running the server from the main exe)
|
||||
//we don't need to send anything, the client can just read the file directly
|
||||
if (transfer.Connection == GameMain.Server.OwnerConnection)
|
||||
{
|
||||
message.Write((byte)FileTransferMessageType.TransferOnSameMachine);
|
||||
message.Write((byte)transfer.ID);
|
||||
message.Write((byte)transfer.FileType);
|
||||
message.Write(transfer.FilePath);
|
||||
peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable);
|
||||
transfer.Status = FileTransferStatus.Finished;
|
||||
}
|
||||
else
|
||||
{
|
||||
message.Write((byte)FileTransferMessageType.Initiate);
|
||||
message.Write((byte)transfer.ID);
|
||||
message.Write((byte)transfer.FileType);
|
||||
//message.Write((ushort)chunkLen);
|
||||
message.Write(transfer.Data.Length);
|
||||
message.Write(transfer.FileName);
|
||||
peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable);
|
||||
|
||||
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(" ID: " + transfer.ID);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
message = new WriteOnlyMessage();
|
||||
message.Write((byte)ServerPacketHeader.FILE_TRANSFER);
|
||||
message.Write((byte)FileTransferMessageType.Data);
|
||||
|
||||
message.Write((byte)transfer.ID);
|
||||
message.Write(transfer.SentOffset);
|
||||
|
||||
byte[] sendBytes = new byte[sendByteCount];
|
||||
Array.Copy(transfer.Data, transfer.SentOffset, sendBytes, 0, sendByteCount);
|
||||
|
||||
message.Write((ushort)sendByteCount);
|
||||
message.Write(sendBytes, 0, sendByteCount);
|
||||
|
||||
transfer.SentOffset += sendByteCount;
|
||||
if (transfer.SentOffset > transfer.KnownReceivedOffset + chunkLen * 5 ||
|
||||
transfer.SentOffset >= transfer.Data.Length)
|
||||
{
|
||||
transfer.SentOffset = transfer.KnownReceivedOffset;
|
||||
}
|
||||
|
||||
peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable);
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("FileSender threw an exception when trying to send data", e);
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"FileSender.Update:Exception",
|
||||
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
||||
"FileSender threw an exception when trying to send data:\n" + e.Message + "\n" + e.StackTrace);
|
||||
transfer.Status = FileTransferStatus.Error;
|
||||
break;
|
||||
}
|
||||
|
||||
if (GameSettings.VerboseLogging)
|
||||
{
|
||||
DebugConsole.Log("Sending " + sendByteCount + " bytes of the file " + transfer.FileName + " (" + transfer.SentOffset + "/" + transfer.Data.Length + " sent)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CancelTransfer(FileTransferOut transfer)
|
||||
{
|
||||
transfer.Status = FileTransferStatus.Canceled;
|
||||
activeTransfers.Remove(transfer);
|
||||
|
||||
OnEnded(transfer);
|
||||
|
||||
GameMain.Server.SendCancelTransferMsg(transfer);
|
||||
}
|
||||
|
||||
public void ReadFileRequest(IReadMessage inc, Client client)
|
||||
{
|
||||
byte messageType = inc.ReadByte();
|
||||
|
||||
if (messageType == (byte)FileTransferMessageType.Cancel)
|
||||
{
|
||||
byte transferId = inc.ReadByte();
|
||||
var matchingTransfer = activeTransfers.Find(t => t.Connection == inc.Sender && t.ID == transferId);
|
||||
if (matchingTransfer != null) CancelTransfer(matchingTransfer);
|
||||
|
||||
return;
|
||||
}
|
||||
else if (messageType == (byte)FileTransferMessageType.Data)
|
||||
{
|
||||
byte transferId = inc.ReadByte();
|
||||
var matchingTransfer = activeTransfers.Find(t => t.Connection == inc.Sender && t.ID == transferId);
|
||||
if (matchingTransfer != null)
|
||||
{
|
||||
matchingTransfer.Acknowledged = true;
|
||||
int offset = inc.ReadInt32();
|
||||
matchingTransfer.KnownReceivedOffset = offset > matchingTransfer.KnownReceivedOffset ? offset : matchingTransfer.KnownReceivedOffset;
|
||||
if (matchingTransfer.SentOffset < matchingTransfer.KnownReceivedOffset) { matchingTransfer.SentOffset = matchingTransfer.KnownReceivedOffset; }
|
||||
|
||||
if (matchingTransfer.KnownReceivedOffset >= matchingTransfer.Data.Length)
|
||||
{
|
||||
matchingTransfer.Status = FileTransferStatus.Finished;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.Sender, FileTransferType.Submarine, requestedSubmarine.FilePath);
|
||||
}
|
||||
break;
|
||||
case (byte)FileTransferType.CampaignSave:
|
||||
if (GameMain.GameSession != null &&
|
||||
!ActiveTransfers.Any(t => t.Connection == inc.Sender && t.FileType == FileTransferType.CampaignSave))
|
||||
{
|
||||
StartTransfer(inc.Sender, FileTransferType.CampaignSave, GameMain.GameSession.SavePath);
|
||||
if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign)
|
||||
{
|
||||
client.LastCampaignSaveSendTime = new Pair<ushort, float>(campaign.LastSaveID, (float)Lidgren.Network.NetTime.Now);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,438 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class KarmaManager : ISerializableEntity
|
||||
{
|
||||
private class ClientMemory
|
||||
{
|
||||
public List<Pair<Wire, float>> WireDisconnectTime = new List<Pair<Wire, float>>();
|
||||
|
||||
public float PreviousNotifiedKarma;
|
||||
|
||||
public float StructureDamageAccumulator;
|
||||
|
||||
private float structureDamagePerSecond;
|
||||
public float StructureDamagePerSecond
|
||||
{
|
||||
get { return Math.Max(StructureDamageAccumulator, structureDamagePerSecond); }
|
||||
set { structureDamagePerSecond = value; }
|
||||
}
|
||||
|
||||
//when did a given character last attack this one
|
||||
public Dictionary<Character, double> LastAttackTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new Dictionary<Character, double>();
|
||||
}
|
||||
|
||||
public bool TestMode = false;
|
||||
|
||||
private readonly Dictionary<Client, ClientMemory> clientMemories = new Dictionary<Client, ClientMemory>();
|
||||
private readonly List<Client> bannedClients = new List<Client>();
|
||||
|
||||
private DateTime perSecondUpdate;
|
||||
|
||||
public void UpdateClients(IEnumerable<Client> clients, float deltaTime)
|
||||
{
|
||||
if (!GameMain.Server.GameStarted) { return; }
|
||||
|
||||
bannedClients.Clear();
|
||||
foreach (Client client in clients)
|
||||
{
|
||||
UpdateClient(client, deltaTime);
|
||||
|
||||
if (perSecondUpdate < DateTime.Now)
|
||||
{
|
||||
var clientMemory = GetClientMemory(client);
|
||||
clientMemory.StructureDamagePerSecond = clientMemory.StructureDamageAccumulator;
|
||||
clientMemory.StructureDamageAccumulator = 0.0f;
|
||||
|
||||
var toRemove = clientMemory.LastAttackTime.Where(pair => pair.Value < Timing.TotalTime - AllowedRetaliationTime).Select(pair => pair.Key).ToList();
|
||||
foreach (var lastAttacker in toRemove)
|
||||
{
|
||||
clientMemory.LastAttackTime.Remove(lastAttacker);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (perSecondUpdate < DateTime.Now)
|
||||
{
|
||||
foreach (Client client in clients)
|
||||
{
|
||||
SendKarmaNotifications(client);
|
||||
}
|
||||
perSecondUpdate = DateTime.Now + new TimeSpan(0, 0, 1);
|
||||
}
|
||||
|
||||
foreach (Client bannedClient in bannedClients)
|
||||
{
|
||||
bannedClient.KarmaKickCount++;
|
||||
if (bannedClient.KarmaKickCount <= KicksBeforeBan)
|
||||
{
|
||||
GameMain.Server.KickClient(bannedClient, $"KarmaKicked~[banthreshold]={(int)KickBanThreshold}", resetKarma: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.Server.BanClient(bannedClient, $"KarmaBanned~[banthreshold]={(int)KickBanThreshold}", duration: TimeSpan.FromSeconds(GameMain.Server.ServerSettings.AutoBanTime));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SendKarmaNotifications(Client client, string debugKarmaChangeReason = "")
|
||||
{
|
||||
//send a notification about karma changing if the karma has changed by x%
|
||||
|
||||
var clientMemory = GetClientMemory(client);
|
||||
float karmaChange = client.Karma - clientMemory.PreviousNotifiedKarma;
|
||||
if (Math.Abs(karmaChange) > 1.0f &&
|
||||
(TestMode || Math.Abs(karmaChange) / clientMemory.PreviousNotifiedKarma > KarmaNotificationInterval / 100.0f))
|
||||
{
|
||||
if (TestMode)
|
||||
{
|
||||
string msg =
|
||||
karmaChange < 0 ? $"Your karma has decreased to {client.Karma}" : $"Your karma has increased to {client.Karma}";
|
||||
if (!string.IsNullOrEmpty(debugKarmaChangeReason))
|
||||
{
|
||||
msg += $". Reason: {debugKarmaChangeReason}";
|
||||
}
|
||||
GameMain.Server.SendDirectChatMessage(msg, client);
|
||||
}
|
||||
else if (Math.Abs(KickBanThreshold - client.Karma) < KarmaNotificationInterval)
|
||||
{
|
||||
GameMain.Server.SendDirectChatMessage(TextManager.Get("KarmaBanWarning"), client);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.Server.SendDirectChatMessage(TextManager.Get(karmaChange < 0 ? "KarmaDecreasedUnknownAmount" : "KarmaIncreasedUnknownAmount"), client);
|
||||
}
|
||||
clientMemory.PreviousNotifiedKarma = client.Karma;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateClient(Client client, float deltaTime)
|
||||
{
|
||||
if (client.Character != null && !client.Character.Removed && !client.Character.IsDead)
|
||||
{
|
||||
if (client.Karma > KarmaDecayThreshold)
|
||||
{
|
||||
client.Karma -= KarmaDecay * deltaTime;
|
||||
}
|
||||
else if (client.Karma < KarmaIncreaseThreshold)
|
||||
{
|
||||
client.Karma += KarmaIncrease * deltaTime;
|
||||
}
|
||||
|
||||
//increase the strength of the herpes affliction in steps instead of linearly
|
||||
//otherwise clients could determine their exact karma value from the strength
|
||||
float herpesStrength = 0.0f;
|
||||
if (client.Karma < 20)
|
||||
herpesStrength = 100.0f;
|
||||
else if (client.Karma < 30)
|
||||
herpesStrength = 60.0f;
|
||||
else if (client.Karma < 40.0f)
|
||||
herpesStrength = 30.0f;
|
||||
|
||||
var existingAffliction = client.Character.CharacterHealth.GetAffliction<AfflictionSpaceHerpes>("spaceherpes");
|
||||
if (existingAffliction == null && herpesStrength > 0.0f)
|
||||
{
|
||||
client.Character.CharacterHealth.ApplyAffliction(null, new Affliction(herpesAffliction, herpesStrength));
|
||||
}
|
||||
else if (existingAffliction != null)
|
||||
{
|
||||
existingAffliction.Strength = herpesStrength;
|
||||
if (herpesStrength <= 0.0f)
|
||||
{
|
||||
client.Character.CharacterHealth.ReduceAffliction(null, "invertcontrols", 100.0f);
|
||||
}
|
||||
}
|
||||
|
||||
//check if the client has disconnected an excessive number of wires
|
||||
var clientMemory = GetClientMemory(client);
|
||||
if (clientMemory.WireDisconnectTime.Count > (int)AllowedWireDisconnectionsPerMinute)
|
||||
{
|
||||
clientMemory.WireDisconnectTime.RemoveRange(0, clientMemory.WireDisconnectTime.Count - (int)AllowedWireDisconnectionsPerMinute);
|
||||
if (clientMemory.WireDisconnectTime.All(w => Timing.TotalTime - w.Second < 60.0f))
|
||||
{
|
||||
float karmaDecrease = -WireDisconnectionKarmaDecrease;
|
||||
//engineers don't lose as much karma for removing lots of wires
|
||||
if (client.Character.Info?.Job.Prefab.Identifier == "engineer") { karmaDecrease *= 0.5f; }
|
||||
AdjustKarma(client.Character, karmaDecrease, "Disconnected excessive number of wires");
|
||||
}
|
||||
}
|
||||
|
||||
if (client.Character?.Info?.Job.Prefab.Identifier == "captain" && client.Character.SelectedConstruction != null)
|
||||
{
|
||||
if (client.Character.SelectedConstruction.GetComponent<Steering>() != null)
|
||||
{
|
||||
AdjustKarma(client.Character, SteerSubKarmaIncrease * deltaTime, "Steering the sub");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (client.Karma < KickBanThreshold && client.Connection != GameMain.Server.OwnerConnection)
|
||||
{
|
||||
if (TestMode)
|
||||
{
|
||||
client.Karma = 50.0f;
|
||||
GameMain.Server.SendDirectChatMessage("BANNED! (not really because karma test mode is enabled)", client);
|
||||
}
|
||||
else
|
||||
{
|
||||
bannedClients.Add(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnRoundEnded()
|
||||
{
|
||||
if (ResetKarmaBetweenRounds)
|
||||
{
|
||||
clientMemories.Clear();
|
||||
foreach (Client client in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
client.Karma = Math.Max(50.0f, client.Karma);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnClientDisconnected(Client client)
|
||||
{
|
||||
clientMemories.Remove(client);
|
||||
}
|
||||
|
||||
public void OnCharacterHealthChanged(Character target, Character attacker, float damage, IEnumerable<Affliction> appliedAfflictions = null)
|
||||
{
|
||||
if (target == null || attacker == null) { return; }
|
||||
if (target == attacker) { return; }
|
||||
|
||||
//damaging dead characters doesn't affect karma
|
||||
if (target.IsDead || target.Removed) { return; }
|
||||
|
||||
bool isEnemy = target.AIController is EnemyAIController || target.TeamID != attacker.TeamID;
|
||||
if (GameMain.Server.TraitorManager?.Traitors != null)
|
||||
{
|
||||
if (GameMain.Server.TraitorManager.Traitors.Any(t => t.Character == target))
|
||||
{
|
||||
//traitors always count as enemies
|
||||
isEnemy = true;
|
||||
}
|
||||
if (GameMain.Server.TraitorManager.Traitors.Any(t =>
|
||||
t.Character == attacker &&
|
||||
t.CurrentObjective != null &&
|
||||
t.CurrentObjective.IsEnemy(target)))
|
||||
{
|
||||
//target counts as an enemy to the traitor
|
||||
isEnemy = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool targetIsHusk = target.CharacterHealth?.GetAffliction<AfflictionHusk>("huskinfection")?.State == AfflictionHusk.InfectionState.Active;
|
||||
bool attackerIsHusk = attacker.CharacterHealth?.GetAffliction<AfflictionHusk>("huskinfection")?.State == AfflictionHusk.InfectionState.Active;
|
||||
//huskified characters count as enemies to healthy characters and vice versa
|
||||
if (targetIsHusk != attackerIsHusk) { isEnemy = true; }
|
||||
|
||||
if (appliedAfflictions != null)
|
||||
{
|
||||
foreach (Affliction affliction in appliedAfflictions)
|
||||
{
|
||||
if (MathUtils.NearlyEqual(affliction.Prefab.KarmaChangeOnApplied, 0.0f)) { continue; }
|
||||
damage -= affliction.Prefab.KarmaChangeOnApplied * affliction.Strength;
|
||||
}
|
||||
}
|
||||
|
||||
Client targetClient = GameMain.Server.ConnectedClients.Find(c => c.Character == target);
|
||||
if (damage > 0 && targetClient != null)
|
||||
{
|
||||
var targetMemory = GetClientMemory(targetClient);
|
||||
targetMemory.LastAttackTime[attacker] = Timing.TotalTime;
|
||||
}
|
||||
|
||||
Client attackerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == attacker);
|
||||
if (attackerClient != null)
|
||||
{
|
||||
//if the attacker has been attacked by the target within the last x seconds, ignore the damage
|
||||
//(= no karma penalty from retaliating against someone who attacked you)
|
||||
var attackerMemory = GetClientMemory(attackerClient);
|
||||
if (attackerMemory.LastAttackTime.ContainsKey(target) &&
|
||||
attackerMemory.LastAttackTime[target] > Timing.TotalTime - AllowedRetaliationTime)
|
||||
{
|
||||
damage = Math.Min(damage, 0);
|
||||
}
|
||||
}
|
||||
|
||||
//attacking/healing clowns has a smaller effect on karma
|
||||
if (target.HasEquippedItem("clownmask") &&
|
||||
target.HasEquippedItem("clowncostume"))
|
||||
{
|
||||
damage *= 0.5f;
|
||||
}
|
||||
|
||||
//smaller karma penalty for attacking someone who's aiming with a weapon
|
||||
if (damage > 0.0f &&
|
||||
target.IsKeyDown(InputType.Aim) &&
|
||||
target.SelectedItems.Any(it => it != null && (it.GetComponent<MeleeWeapon>() != null || it.GetComponent<RangedWeapon>() != null)))
|
||||
{
|
||||
damage *= 0.5f;
|
||||
}
|
||||
|
||||
//damage scales according to the karma of the target
|
||||
//(= smaller karma penalty from attacking someone who has a low karma)
|
||||
if (damage > 0 && targetClient != null)
|
||||
{
|
||||
damage *= MathUtils.InverseLerp(0.0f, 50.0f, targetClient.Karma);
|
||||
}
|
||||
|
||||
if (isEnemy)
|
||||
{
|
||||
if (damage > 0)
|
||||
{
|
||||
float karmaIncrease = damage * DamageEnemyKarmaIncrease;
|
||||
if (attacker?.Info?.Job.Prefab.Identifier == "securityofficer") { karmaIncrease *= 2.0f; }
|
||||
AdjustKarma(attacker, karmaIncrease, "Damaged enemy");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (damage > 0)
|
||||
{
|
||||
AdjustKarma(attacker, -damage * DamageFriendlyKarmaDecrease, "Damaged friendly");
|
||||
}
|
||||
else
|
||||
{
|
||||
float karmaIncrease = -damage * HealFriendlyKarmaIncrease;
|
||||
if (attacker?.Info?.Job.Prefab.Identifier == "medicaldoctor") { karmaIncrease *= 2.0f; }
|
||||
AdjustKarma(attacker, karmaIncrease, "Healed friendly");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void OnStructureHealthChanged(Structure structure, Character attacker, float damageAmount)
|
||||
{
|
||||
if (attacker == null) { return; }
|
||||
//damaging/repairing ruin structures or enemy subs doesn't affect karma
|
||||
if (structure.Submarine == null || structure.Submarine.TeamID != attacker.TeamID)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (damageAmount > 0)
|
||||
{
|
||||
if (StructureDamageKarmaDecrease <= 0.0f) { return; }
|
||||
if (GameMain.Server.TraitorManager?.Traitors != null)
|
||||
{
|
||||
if (GameMain.Server.TraitorManager.Traitors.Any(t =>
|
||||
t.Character == attacker &&
|
||||
t.CurrentObjective != null &&
|
||||
t.CurrentObjective.IsAllowedToDamage(structure)))
|
||||
{
|
||||
//traitor tasked to flood the sub -> damaging structures is ok
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Client client = GameMain.Server.ConnectedClients.Find(c => c.Character == attacker);
|
||||
if (client != null)
|
||||
{
|
||||
//cap the damage so the karma can't decrease by more than MaxStructureDamageKarmaDecreasePerSecond per second
|
||||
var clientMemory = GetClientMemory(client);
|
||||
clientMemory.StructureDamageAccumulator += damageAmount;
|
||||
if (clientMemory.StructureDamagePerSecond + damageAmount >= MaxStructureDamageKarmaDecreasePerSecond / StructureDamageKarmaDecrease)
|
||||
{
|
||||
damageAmount -= (MaxStructureDamageKarmaDecreasePerSecond / StructureDamageKarmaDecrease) - clientMemory.StructureDamagePerSecond;
|
||||
if (damageAmount <= 0.0f) { return; }
|
||||
}
|
||||
}
|
||||
AdjustKarma(attacker, -damageAmount * StructureDamageKarmaDecrease, "Damaged structures");
|
||||
}
|
||||
else
|
||||
{
|
||||
float karmaIncrease = -damageAmount * StructureRepairKarmaIncrease;
|
||||
//mechanics get twice as much karma for repairing walls
|
||||
if (attacker.Info?.Job.Prefab.Identifier == "mechanic") { karmaIncrease *= 2.0f; }
|
||||
AdjustKarma(attacker, karmaIncrease, "Repaired structures");
|
||||
}
|
||||
}
|
||||
|
||||
public void OnItemRepaired(Character character, Repairable repairable, float repairAmount)
|
||||
{
|
||||
float karmaIncrease = repairAmount * ItemRepairKarmaIncrease;
|
||||
if (repairable.HasRequiredSkills(character)) { karmaIncrease *= 2.0f; }
|
||||
AdjustKarma(character, karmaIncrease, "Repaired item");
|
||||
}
|
||||
|
||||
public void OnReactorOverHeating(Character character, float deltaTime)
|
||||
{
|
||||
AdjustKarma(character, -ReactorOverheatKarmaDecrease * deltaTime, "Caused reactor to overheat");
|
||||
}
|
||||
|
||||
public void OnReactorMeltdown(Character character)
|
||||
{
|
||||
AdjustKarma(character, -ReactorMeltdownKarmaDecrease, "Caused a reactor meltdown");
|
||||
}
|
||||
|
||||
public void OnExtinguishingFire(Character character, float deltaTime)
|
||||
{
|
||||
AdjustKarma(character, ExtinguishFireKarmaIncrease * deltaTime, "Extinguished a fire");
|
||||
}
|
||||
|
||||
public void OnWireDisconnected(Character character, Wire wire)
|
||||
{
|
||||
if (character == null || wire == null) { return; }
|
||||
Client client = GameMain.Server.ConnectedClients.Find(c => c.Character == character);
|
||||
if (client == null) { return; }
|
||||
|
||||
if (!clientMemories.ContainsKey(client)) { clientMemories[client] = new ClientMemory(); }
|
||||
|
||||
clientMemories[client].WireDisconnectTime.RemoveAll(w => w.First == wire);
|
||||
clientMemories[client].WireDisconnectTime.Add(new Pair<Wire, float>(wire, (float)Timing.TotalTime));
|
||||
}
|
||||
|
||||
private ClientMemory GetClientMemory(Client client)
|
||||
{
|
||||
if (!clientMemories.ContainsKey(client))
|
||||
{
|
||||
clientMemories[client] = new ClientMemory()
|
||||
{
|
||||
PreviousNotifiedKarma = client.Karma
|
||||
};
|
||||
}
|
||||
return clientMemories[client];
|
||||
}
|
||||
|
||||
public void OnSpamFilterTriggered(Client client)
|
||||
{
|
||||
if (client != null)
|
||||
{
|
||||
client.Karma -= SpamFilterKarmaDecrease;
|
||||
SendKarmaNotifications(client, "Triggered the spam filter");
|
||||
}
|
||||
}
|
||||
|
||||
private void AdjustKarma(Character target, float amount, string debugKarmaChangeReason = "")
|
||||
{
|
||||
if (target == null) { return; }
|
||||
|
||||
Client client = GameMain.Server.ConnectedClients.Find(c => c.Character == target);
|
||||
if (client == null) { return; }
|
||||
|
||||
//all penalties/rewards are halved when wearing a clown costume
|
||||
if (target.HasEquippedItem("clownmask") &&
|
||||
target.HasEquippedItem("clowncostume"))
|
||||
{
|
||||
amount *= 0.5f;
|
||||
}
|
||||
|
||||
client.Karma += amount;
|
||||
if (TestMode)
|
||||
{
|
||||
SendKarmaNotifications(client, debugKarmaChangeReason);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+567
@@ -0,0 +1,567 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class ServerEntityEvent : NetEntityEvent
|
||||
{
|
||||
private IServerSerializable serializable;
|
||||
|
||||
#if DEBUG
|
||||
public string StackTrace;
|
||||
#endif
|
||||
|
||||
private double createTime;
|
||||
public double CreateTime
|
||||
{
|
||||
get { return createTime; }
|
||||
}
|
||||
|
||||
public void ResetCreateTime()
|
||||
{
|
||||
createTime = Timing.TotalTime;
|
||||
}
|
||||
|
||||
public ServerEntityEvent(IServerSerializable serializableEntity, UInt16 id)
|
||||
: base(serializableEntity, id)
|
||||
{
|
||||
serializable = serializableEntity;
|
||||
createTime = Timing.TotalTime;
|
||||
|
||||
#if DEBUG
|
||||
StackTrace = Environment.StackTrace.ToString();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Write(IWriteMessage 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;
|
||||
private UInt16 lastSentToAnyone;
|
||||
private double lastSentToAnyoneTime;
|
||||
private double lastWarningTime;
|
||||
|
||||
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 ReadWriteMessage Data;
|
||||
|
||||
public readonly Character Character;
|
||||
|
||||
public readonly IClientSerializable TargetEntity;
|
||||
|
||||
public bool IsProcessed;
|
||||
|
||||
public BufferedEvent(Client sender, Character senderCharacter, UInt16 characterStateID, IClientSerializable targetEntity, ReadWriteMessage 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;
|
||||
|
||||
private double lastEventCountHighWarning;
|
||||
|
||||
public ServerEntityEventManager(GameServer server)
|
||||
{
|
||||
events = new List<ServerEntityEvent>();
|
||||
|
||||
this.server = server;
|
||||
|
||||
bufferedEvents = new List<BufferedEvent>();
|
||||
|
||||
uniqueEvents = new List<ServerEntityEvent>();
|
||||
|
||||
lastWarningTime = -10.0;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
bool inGameClientsPresent = server.ConnectedClients.Count(c => c.InGame) > 0;
|
||||
|
||||
//remove old 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
|
||||
// and events less than 15 seconds old to give disconnected clients a bit of time to reconnect without getting desynced
|
||||
events.RemoveAll(e => (NetIdUtils.IdMoreRecent(lastSentToAll, e.ID) || !inGameClientsPresent) && e.CreateTime < Timing.TotalTime - 15.0f);
|
||||
|
||||
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)
|
||||
{
|
||||
string errorMsg = "Failed to read server event for entity \"" + entityName + "\"!";
|
||||
GameServer.Log(errorMsg + "\n" + e.StackTrace, ServerLog.MessageType.Error);
|
||||
DebugConsole.ThrowError(errorMsg, 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)
|
||||
{
|
||||
lastSentToAnyone = inGameClients[0].LastRecvEntityEventID;
|
||||
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; }
|
||||
if (NetIdUtils.IdMoreRecent(c.LastRecvEntityEventID, lastSentToAnyone)) { lastSentToAnyone = c.LastRecvEntityEventID; }
|
||||
});
|
||||
lastSentToAnyoneTime = events.Find(e => e.ID == lastSentToAnyone)?.CreateTime ?? Timing.TotalTime;
|
||||
|
||||
if ((Timing.TotalTime - lastSentToAnyoneTime) > 10.0 && (Timing.TotalTime - lastWarningTime) > 5.0)
|
||||
{
|
||||
lastWarningTime = Timing.TotalTime;
|
||||
GameServer.Log("WARNING: ServerEntityEventManager is lagging behind! Last sent id: " + lastSentToAnyone.ToString() + ", latest create id: " + ID.ToString(), ServerLog.MessageType.ServerMessage);
|
||||
events.ForEach(e => e.ResetCreateTime());
|
||||
//TODO: reset clients if this happens, maybe do it if a majority are behind rather than all of them?
|
||||
}
|
||||
|
||||
clients.Where(c => c.NeedsMidRoundSync).ForEach(c => { if (NetIdUtils.IdMoreRecent(lastSentToAll, c.FirstNewEventID)) lastSentToAll = (ushort)(c.FirstNewEventID - 1); });
|
||||
|
||||
ServerEntityEvent firstEventToResend = events.Find(e => e.ID == (ushort)(lastSentToAll + 1));
|
||||
if (firstEventToResend != null && ((lastSentToAnyoneTime - firstEventToResend.CreateTime) > 10.0 || (Timing.TotalTime - firstEventToResend.CreateTime) > 30.0))
|
||||
{
|
||||
// This event is 10 seconds older than the last one we've successfully sent,
|
||||
// kick everyone that hasn't received it yet, this is way too old
|
||||
// UNLESS the event was created when the client was still midround syncing,
|
||||
// in which case we'll wait until the timeout runs out before kicking the client
|
||||
List<Client> toKick = inGameClients.FindAll(c =>
|
||||
NetIdUtils.IdMoreRecent((UInt16)(lastSentToAll + 1), c.LastRecvEntityEventID) &&
|
||||
(firstEventToResend.CreateTime > c.MidRoundSyncTimeOut || lastSentToAnyoneTime > c.MidRoundSyncTimeOut || Timing.TotalTime > c.MidRoundSyncTimeOut + 10.0));
|
||||
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, " +
|
||||
(lastSentToAnyoneTime - firstEventToResend.CreateTime).ToString("0.##") + " s older than last event sent to anyone)" +
|
||||
" Events queued: " + events.Count + ", last sent to all: " + lastSentToAll, ServerLog.MessageType.Error);
|
||||
server.DisconnectClient(c, "", DisconnectReason.ExcessiveDesyncOldEvent + "/ServerMessage.ExcessiveDesyncOldEvent");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
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.Error);
|
||||
server.DisconnectClient(c, "", DisconnectReason.ExcessiveDesyncRemovedEvent + "/ServerMessage.ExcessiveDesyncRemovedEvent");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var timedOutClients = clients.FindAll(c => c.Connection != GameMain.Server.OwnerConnection && 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.Error);
|
||||
GameMain.Server.DisconnectClient(timedOutClient, "", DisconnectReason.SyncTimeout + "/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
|
||||
DebugConsole.Log("Excessive amount of events in a client's event buffer. The client may be spamming events or their event IDs might be out of sync. Dropping events...");
|
||||
bufferedEvents.RemoveRange(0, 256);
|
||||
}
|
||||
|
||||
bufferedEvents.Add(bufferedEvent);
|
||||
}
|
||||
|
||||
public void RefreshEntityIDs()
|
||||
{
|
||||
events.ForEach(e => e.RefreshEntityID());
|
||||
uniqueEvents.ForEach(e => e.RefreshEntityID());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes all the events that the client hasn't received yet into the outgoing message
|
||||
/// </summary>
|
||||
public void Write(Client client, IWriteMessage 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, IWriteMessage msg, out List<NetEntityEvent> sentEvents)
|
||||
{
|
||||
List<NetEntityEvent> eventsToSync = null;
|
||||
if (client.NeedsMidRoundSync)
|
||||
{
|
||||
eventsToSync = GetEventsToSync(client);
|
||||
}
|
||||
else
|
||||
{
|
||||
eventsToSync = GetEventsToSync(client);
|
||||
}
|
||||
|
||||
if (eventsToSync.Count == 0)
|
||||
{
|
||||
sentEvents = eventsToSync;
|
||||
return;
|
||||
}
|
||||
|
||||
//too many events for one packet
|
||||
//(normal right after a round has just started, don't show a warning if it's been less than 10 seconds)
|
||||
if (eventsToSync.Count > 200 && GameMain.GameSession != null && Timing.TotalTime > GameMain.GameSession.RoundStartTime + 10.0)
|
||||
{
|
||||
if (eventsToSync.Count > 200 && !client.NeedsMidRoundSync && Timing.TotalTime > lastEventCountHighWarning + 2.0)
|
||||
{
|
||||
Color color = eventsToSync.Count > 500 ? Color.Red : Color.Orange;
|
||||
if (eventsToSync.Count < 300) { color = Color.Yellow; }
|
||||
string warningMsg = "WARNING: event count very high: " + eventsToSync.Count;
|
||||
|
||||
var sortedEvents = eventsToSync.GroupBy(e => e.Entity.ToString())
|
||||
.Select(e => new { Value = e.Key, Count = e.Count() })
|
||||
.OrderByDescending(e => e.Count);
|
||||
|
||||
int count = 1;
|
||||
foreach (var sortedEvent in sortedEvents)
|
||||
{
|
||||
warningMsg += "\n" + count + ". " + (sortedEvent.Value?.ToString() ?? "null") + " x" + sortedEvent.Count;
|
||||
count++;
|
||||
if (count > 3) { break; }
|
||||
}
|
||||
if (GameSettings.VerboseLogging)
|
||||
{
|
||||
GameServer.Log(warningMsg, ServerLog.MessageType.Error);
|
||||
}
|
||||
DebugConsole.NewMessage(warningMsg, color);
|
||||
lastEventCountHighWarning = Timing.TotalTime;
|
||||
}
|
||||
}
|
||||
|
||||
if (client.NeedsMidRoundSync)
|
||||
{
|
||||
msg.Write((byte)ServerNetObject.ENTITY_EVENT_INITIAL);
|
||||
msg.Write(client.UnreceivedEntityEventCount);
|
||||
msg.Write(client.FirstNewEventID);
|
||||
|
||||
Write(msg, eventsToSync, out sentEvents, client);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write((byte)ServerNetObject.ENTITY_EVENT);
|
||||
Write(msg, eventsToSync, out sentEvents, client);
|
||||
}
|
||||
|
||||
foreach (NetEntityEvent entityEvent in sentEvents)
|
||||
{
|
||||
(entityEvent as ServerEntityEvent).Sent = true;
|
||||
client.EntityEventLastSent[entityEvent.ID] = Lidgren.Network.NetTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of events that should be sent to the client from the eventList
|
||||
/// </summary>
|
||||
private List<NetEntityEvent> GetEventsToSync(Client client)
|
||||
{
|
||||
List<NetEntityEvent> eventsToSync = new List<NetEntityEvent>();
|
||||
|
||||
var eventList = client.NeedsMidRoundSync ? uniqueEvents : events;
|
||||
|
||||
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 roundtriptime or at all
|
||||
client.EntityEventLastSent.TryGetValue(eventList[i].ID, out double lastSent);
|
||||
|
||||
float avgRoundtripTime = 0.01f; //TODO: reimplement client.Connection.AverageRoundtripTime
|
||||
float minInterval = Math.Max(avgRoundtripTime, (float)server.UpdateInterval.TotalSeconds * 2);
|
||||
|
||||
if (lastSent > Lidgren.Network.NetTime.Now - Math.Min(minInterval, 0.5f))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (client.NeedsMidRoundSync)
|
||||
{
|
||||
if (i <= client.UnreceivedEntityEventCount)
|
||||
{
|
||||
eventsToSync.AddRange(eventList.GetRange(i, client.UnreceivedEntityEventCount - i));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
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 / 100 * server.UpdateInterval.TotalSeconds;
|
||||
midRoundSyncTimeOut = Math.Max(10.0f, midRoundSyncTimeOut * 10.0f);
|
||||
|
||||
client.UnreceivedEntityEventCount = (UInt16)uniqueEvents.Count;
|
||||
client.NeedsMidRoundSync = true;
|
||||
client.MidRoundSyncTimeOut = Timing.TotalTime + midRoundSyncTimeOut;
|
||||
|
||||
//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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the events from the message, ignoring ones we've already received
|
||||
/// </summary>
|
||||
public void Read(IReadMessage 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, Color.Red);
|
||||
}
|
||||
msg.BitPosition += 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.BitPosition += msgLength * 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GameSettings.VerboseLogging)
|
||||
{
|
||||
DebugConsole.NewMessage("Received msg " + thisEventID, Microsoft.Xna.Framework.Color.Green);
|
||||
}
|
||||
|
||||
UInt16 characterStateID = msg.ReadUInt16();
|
||||
|
||||
ReadWriteMessage buffer = new ReadWriteMessage();
|
||||
byte[] temp = msg.ReadBytes(msgLength - 2);
|
||||
buffer.Write(temp, 0, msgLength - 2);
|
||||
buffer.BitPosition = 0;
|
||||
BufferEvent(new BufferedEvent(sender, sender.Character, characterStateID, entity, buffer));
|
||||
|
||||
sender.LastSentEntityEventID++;
|
||||
}
|
||||
msg.ReadPadBits();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void WriteEvent(IWriteMessage buffer, NetEntityEvent entityEvent, Client recipient = null)
|
||||
{
|
||||
var serverEvent = entityEvent as ServerEntityEvent;
|
||||
if (serverEvent == null) return;
|
||||
|
||||
serverEvent.Write(buffer, recipient);
|
||||
}
|
||||
|
||||
protected void ReadEvent(IReadMessage 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.EntityEventLastSent.Clear();
|
||||
c.LastRecvEntityEventID = 0;
|
||||
c.LastSentEntityEventID = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
abstract partial class NetworkMember
|
||||
{
|
||||
public Character Character
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
partial class OrderChatMessage : ChatMessage
|
||||
{
|
||||
public override void ServerWrite(IWriteMessage 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+694
@@ -0,0 +1,694 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Linq;
|
||||
using Lidgren.Network;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class LidgrenServerPeer : ServerPeer
|
||||
{
|
||||
private readonly ServerSettings serverSettings;
|
||||
|
||||
private NetPeerConfiguration netPeerConfiguration;
|
||||
private NetServer netServer;
|
||||
|
||||
private class PendingClient
|
||||
{
|
||||
public string Name;
|
||||
public int OwnerKey;
|
||||
public NetConnection Connection;
|
||||
public ConnectionInitialization InitializationStep;
|
||||
public double UpdateTime;
|
||||
public double TimeOut;
|
||||
public int Retries;
|
||||
public UInt64? SteamID;
|
||||
public Int32? PasswordSalt;
|
||||
public bool AuthSessionStarted;
|
||||
|
||||
public PendingClient(NetConnection conn)
|
||||
{
|
||||
OwnerKey = 0;
|
||||
Connection = conn;
|
||||
InitializationStep = ConnectionInitialization.SteamTicketAndVersion;
|
||||
Retries = 0;
|
||||
SteamID = null;
|
||||
PasswordSalt = null;
|
||||
UpdateTime = Timing.TotalTime + Timing.Step * 3.0;
|
||||
TimeOut = NetworkConnection.TimeoutThreshold;
|
||||
AuthSessionStarted = false;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<LidgrenConnection> connectedClients;
|
||||
private readonly List<PendingClient> pendingClients;
|
||||
|
||||
private readonly List<NetIncomingMessage> incomingLidgrenMessages;
|
||||
|
||||
public LidgrenServerPeer(int? ownKey, ServerSettings settings)
|
||||
{
|
||||
serverSettings = settings;
|
||||
|
||||
netServer = null;
|
||||
|
||||
connectedClients = new List<LidgrenConnection>();
|
||||
pendingClients = new List<PendingClient>();
|
||||
|
||||
incomingLidgrenMessages = new List<NetIncomingMessage>();
|
||||
|
||||
ownerKey = ownKey;
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
if (netServer != null) { return; }
|
||||
|
||||
netPeerConfiguration = new NetPeerConfiguration("barotrauma")
|
||||
{
|
||||
AcceptIncomingConnections = true,
|
||||
AutoExpandMTU = false,
|
||||
MaximumConnections = NetConfig.MaxPlayers * 2,
|
||||
EnableUPnP = serverSettings.EnableUPnP,
|
||||
Port = serverSettings.Port
|
||||
};
|
||||
|
||||
netPeerConfiguration.DisableMessageType(NetIncomingMessageType.DebugMessage |
|
||||
NetIncomingMessageType.WarningMessage | NetIncomingMessageType.Receipt |
|
||||
NetIncomingMessageType.ErrorMessage | NetIncomingMessageType.Error |
|
||||
NetIncomingMessageType.UnconnectedData);
|
||||
|
||||
netPeerConfiguration.EnableMessageType(NetIncomingMessageType.ConnectionApproval);
|
||||
|
||||
netServer = new NetServer(netPeerConfiguration);
|
||||
|
||||
netServer.Start();
|
||||
|
||||
if (serverSettings.EnableUPnP)
|
||||
{
|
||||
InitUPnP();
|
||||
|
||||
while (DiscoveringUPnP()) { }
|
||||
|
||||
FinishUPnP();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Close(string msg = null)
|
||||
{
|
||||
if (netServer == null) { return; }
|
||||
|
||||
for (int i = pendingClients.Count - 1; i >= 0; i--)
|
||||
{
|
||||
RemovePendingClient(pendingClients[i], DisconnectReason.ServerShutdown, msg);
|
||||
}
|
||||
|
||||
for (int i = connectedClients.Count - 1; i >= 0; i--)
|
||||
{
|
||||
Disconnect(connectedClients[i], msg ?? DisconnectReason.ServerShutdown.ToString());
|
||||
}
|
||||
|
||||
netServer.Shutdown(msg ?? DisconnectReason.ServerShutdown.ToString());
|
||||
|
||||
pendingClients.Clear();
|
||||
connectedClients.Clear();
|
||||
|
||||
netServer = null;
|
||||
|
||||
Steamworks.SteamServer.OnValidateAuthTicketResponse -= OnAuthChange;
|
||||
|
||||
OnShutdown?.Invoke();
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (netServer == null) { return; }
|
||||
|
||||
if (OnOwnerDetermined != null && OwnerConnection != null)
|
||||
{
|
||||
OnOwnerDetermined?.Invoke(OwnerConnection);
|
||||
OnOwnerDetermined = null;
|
||||
}
|
||||
|
||||
netServer.ReadMessages(incomingLidgrenMessages);
|
||||
|
||||
//process incoming connections first
|
||||
foreach (NetIncomingMessage inc in incomingLidgrenMessages.Where(m => m.MessageType == NetIncomingMessageType.ConnectionApproval))
|
||||
{
|
||||
HandleConnection(inc);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
//after processing connections, go ahead with the rest of the messages
|
||||
foreach (NetIncomingMessage inc in incomingLidgrenMessages.Where(m => m.MessageType != NetIncomingMessageType.ConnectionApproval))
|
||||
{
|
||||
switch (inc.MessageType)
|
||||
{
|
||||
case NetIncomingMessageType.Data:
|
||||
HandleDataMessage(inc);
|
||||
break;
|
||||
case NetIncomingMessageType.StatusChanged:
|
||||
HandleStatusChanged(inc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
string errorMsg = "Server failed to read an incoming message. {" + e + "}\n" + e.StackTrace;
|
||||
GameAnalyticsManager.AddErrorEventOnce("LidgrenServerPeer.Update:ClientReadException" + e.TargetSite.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
#else
|
||||
if (GameSettings.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
|
||||
#endif
|
||||
}
|
||||
|
||||
for (int i = 0; i < pendingClients.Count; i++)
|
||||
{
|
||||
PendingClient pendingClient = pendingClients[i];
|
||||
UpdatePendingClient(pendingClient, deltaTime);
|
||||
if (i >= pendingClients.Count || pendingClients[i] != pendingClient) { i--; }
|
||||
}
|
||||
|
||||
incomingLidgrenMessages.Clear();
|
||||
}
|
||||
|
||||
private void InitUPnP()
|
||||
{
|
||||
if (netServer == null) { return; }
|
||||
|
||||
netServer.UPnP.ForwardPort(netPeerConfiguration.Port, "barotrauma");
|
||||
#if USE_STEAM
|
||||
netServer.UPnP.ForwardPort(serverSettings.QueryPort, "barotrauma");
|
||||
#endif
|
||||
}
|
||||
|
||||
private bool DiscoveringUPnP()
|
||||
{
|
||||
if (netServer == null) { return false; }
|
||||
|
||||
return netServer.UPnP.Status == UPnPStatus.Discovering;
|
||||
}
|
||||
|
||||
private void FinishUPnP()
|
||||
{
|
||||
//do nothing
|
||||
}
|
||||
|
||||
private void HandleConnection(NetIncomingMessage inc)
|
||||
{
|
||||
if (netServer == null) { return; }
|
||||
|
||||
if (connectedClients.Count >= serverSettings.MaxPlayers)
|
||||
{
|
||||
inc.SenderConnection.Deny(DisconnectReason.ServerFull.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (serverSettings.BanList.IsBanned(inc.SenderConnection.RemoteEndPoint.Address, 0, out string banReason))
|
||||
{
|
||||
//IP banned: deny immediately
|
||||
inc.SenderConnection.Deny(DisconnectReason.Banned.ToString() + "/ " + banReason);
|
||||
return;
|
||||
}
|
||||
|
||||
PendingClient pendingClient = pendingClients.Find(c => c.Connection == inc.SenderConnection);
|
||||
|
||||
if (pendingClient == null)
|
||||
{
|
||||
pendingClient = new PendingClient(inc.SenderConnection);
|
||||
pendingClients.Add(pendingClient);
|
||||
}
|
||||
|
||||
inc.SenderConnection.Approve();
|
||||
}
|
||||
|
||||
private void HandleDataMessage(NetIncomingMessage inc)
|
||||
{
|
||||
if (netServer == null) { return; }
|
||||
|
||||
PendingClient pendingClient = pendingClients.Find(c => c.Connection == inc.SenderConnection);
|
||||
|
||||
byte incByte = inc.ReadByte();
|
||||
bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0;
|
||||
bool isConnectionInitializationStep = (incByte & (byte)PacketHeader.IsConnectionInitializationStep) != 0;
|
||||
|
||||
if (isConnectionInitializationStep && pendingClient != null)
|
||||
{
|
||||
ReadConnectionInitializationStep(pendingClient, inc);
|
||||
}
|
||||
else if (!isConnectionInitializationStep)
|
||||
{
|
||||
LidgrenConnection conn = connectedClients.Find(c => c.NetConnection == inc.SenderConnection);
|
||||
if (conn == null)
|
||||
{
|
||||
if (pendingClient != null)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.AuthenticationRequired, "Received data message from unauthenticated client");
|
||||
}
|
||||
else if (inc.SenderConnection.Status != NetConnectionStatus.Disconnected &&
|
||||
inc.SenderConnection.Status != NetConnectionStatus.Disconnecting)
|
||||
{
|
||||
inc.SenderConnection.Disconnect(DisconnectReason.AuthenticationRequired.ToString() + "/ Received data message from unauthenticated client");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (pendingClient != null) { pendingClients.Remove(pendingClient); }
|
||||
if (serverSettings.BanList.IsBanned(conn.IPEndPoint.Address, conn.SteamID, out string banReason))
|
||||
{
|
||||
Disconnect(conn, DisconnectReason.Banned.ToString() + "/ " + banReason);
|
||||
return;
|
||||
}
|
||||
UInt16 length = inc.ReadUInt16();
|
||||
|
||||
//DebugConsole.NewMessage(isCompressed + " " + isConnectionInitializationStep + " " + (int)incByte + " " + length);
|
||||
|
||||
IReadMessage msg = new ReadOnlyMessage(inc.Data, isCompressed, inc.PositionInBytes, length, conn);
|
||||
OnMessageReceived?.Invoke(conn, msg);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleStatusChanged(NetIncomingMessage inc)
|
||||
{
|
||||
if (netServer == null) { return; }
|
||||
|
||||
switch (inc.SenderConnection.Status)
|
||||
{
|
||||
case NetConnectionStatus.Disconnected:
|
||||
string disconnectMsg;
|
||||
LidgrenConnection conn = connectedClients.Find(c => c.NetConnection == inc.SenderConnection);
|
||||
if (conn != null)
|
||||
{
|
||||
if (conn == OwnerConnection)
|
||||
{
|
||||
DebugConsole.NewMessage("Owner disconnected: closing the server...");
|
||||
GameServer.Log("Owner disconnected: closing the server...", ServerLog.MessageType.ServerMessage);
|
||||
Close(DisconnectReason.ServerShutdown.ToString() + "/ Owner disconnected");
|
||||
}
|
||||
else
|
||||
{
|
||||
disconnectMsg = $"ServerMessage.HasDisconnected~[client]={conn.Name}";
|
||||
Disconnect(conn, disconnectMsg);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PendingClient pendingClient = pendingClients.Find(c => c.Connection == inc.SenderConnection);
|
||||
if (pendingClient != null)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.Unknown, $"ServerMessage.HasDisconnected~[client]={pendingClient.Name}");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadConnectionInitializationStep(PendingClient pendingClient, NetIncomingMessage inc)
|
||||
{
|
||||
if (netServer == null) { return; }
|
||||
|
||||
pendingClient.TimeOut = NetworkConnection.TimeoutThreshold;
|
||||
|
||||
ConnectionInitialization initializationStep = (ConnectionInitialization)inc.ReadByte();
|
||||
|
||||
//DebugConsole.NewMessage(initializationStep+" "+pendingClient.InitializationStep);
|
||||
|
||||
if (pendingClient.InitializationStep != initializationStep) return;
|
||||
|
||||
pendingClient.UpdateTime = Timing.TotalTime + Timing.Step;
|
||||
|
||||
switch (initializationStep)
|
||||
{
|
||||
case ConnectionInitialization.SteamTicketAndVersion:
|
||||
string name = Client.SanitizeName(inc.ReadString());
|
||||
int ownKey = inc.ReadInt32();
|
||||
UInt64 steamId = inc.ReadUInt64();
|
||||
UInt16 ticketLength = inc.ReadUInt16();
|
||||
byte[] ticket = inc.ReadBytes(ticketLength);
|
||||
|
||||
if (!Client.IsValidName(name, serverSettings))
|
||||
{
|
||||
if (OwnerConnection != null ||
|
||||
!IPAddress.IsLoopback(pendingClient.Connection.RemoteEndPoint.Address.MapToIPv4NoThrow()) &&
|
||||
ownerKey == null || ownKey == 0 && ownKey != ownerKey)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.InvalidName, "The name \"" + name + "\" is invalid");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
string version = inc.ReadString();
|
||||
bool isCompatibleVersion = NetworkMember.IsCompatible(version, GameMain.Version.ToString()) ?? false;
|
||||
if (!isCompatibleVersion)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.InvalidVersion,
|
||||
$"DisconnectMessage.InvalidVersion~[version]={GameMain.Version.ToString()}~[clientversion]={version}");
|
||||
|
||||
GameServer.Log(name + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible game version)", ServerLog.MessageType.Error);
|
||||
DebugConsole.NewMessage(name + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible game version)", Microsoft.Xna.Framework.Color.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
int contentPackageCount = inc.ReadVariableInt32();
|
||||
List<ClientContentPackage> clientContentPackages = new List<ClientContentPackage>();
|
||||
for (int i = 0; i < contentPackageCount; i++)
|
||||
{
|
||||
string packageName = inc.ReadString();
|
||||
string packageHash = inc.ReadString();
|
||||
clientContentPackages.Add(new ClientContentPackage(packageName, packageHash));
|
||||
}
|
||||
|
||||
//check if the client is missing any of our packages
|
||||
List<ContentPackage> missingPackages = new List<ContentPackage>();
|
||||
foreach (ContentPackage serverContentPackage in GameMain.SelectedPackages)
|
||||
{
|
||||
if (!serverContentPackage.HasMultiplayerIncompatibleContent) continue;
|
||||
bool packageFound = clientContentPackages.Any(cp => cp.Name == serverContentPackage.Name && cp.Hash == serverContentPackage.MD5hash.Hash);
|
||||
if (!packageFound) { missingPackages.Add(serverContentPackage); }
|
||||
}
|
||||
|
||||
//check if the client is using packages we don't have
|
||||
List<ClientContentPackage> redundantPackages = new List<ClientContentPackage>();
|
||||
foreach (ClientContentPackage clientContentPackage in clientContentPackages)
|
||||
{
|
||||
bool packageFound = GameMain.SelectedPackages.Any(cp => cp.Name == clientContentPackage.Name && cp.MD5hash.Hash == clientContentPackage.Hash);
|
||||
if (!packageFound) { redundantPackages.Add(clientContentPackage); }
|
||||
}
|
||||
|
||||
if (missingPackages.Count == 1)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.MissingContentPackage,
|
||||
$"DisconnectMessage.MissingContentPackage~[missingcontentpackage]={GetPackageStr(missingPackages[0])}");
|
||||
GameServer.Log(name + " (" + inc.SenderConnection.RemoteEndPoint.Address + ") couldn't join the server (missing content package " + GetPackageStr(missingPackages[0]) + ")", ServerLog.MessageType.Error);
|
||||
return;
|
||||
}
|
||||
else if (missingPackages.Count > 1)
|
||||
{
|
||||
List<string> packageStrs = new List<string>();
|
||||
missingPackages.ForEach(cp => packageStrs.Add(GetPackageStr(cp)));
|
||||
RemovePendingClient(pendingClient, DisconnectReason.MissingContentPackage,
|
||||
$"DisconnectMessage.MissingContentPackages~[missingcontentpackages]={string.Join(", ", packageStrs)}");
|
||||
GameServer.Log(name + " (" + inc.SenderConnection.RemoteEndPoint.Address + ") couldn't join the server (missing content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
|
||||
return;
|
||||
}
|
||||
if (redundantPackages.Count == 1)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.IncompatibleContentPackage,
|
||||
$"DisconnectMessage.IncompatibleContentPackage~[incompatiblecontentpackage]={GetPackageStr(redundantPackages[0])}");
|
||||
GameServer.Log(name + " (" + inc.SenderConnection.RemoteEndPoint.Address + ") couldn't join the server (using an incompatible content package " + GetPackageStr(redundantPackages[0]) + ")", ServerLog.MessageType.Error);
|
||||
return;
|
||||
}
|
||||
if (redundantPackages.Count > 1)
|
||||
{
|
||||
List<string> packageStrs = new List<string>();
|
||||
redundantPackages.ForEach(cp => packageStrs.Add(GetPackageStr(cp)));
|
||||
RemovePendingClient(pendingClient, DisconnectReason.IncompatibleContentPackage,
|
||||
$"DisconnectMessage.IncompatibleContentPackages~[incompatiblecontentpackages]={string.Join(", ", packageStrs)}");
|
||||
GameServer.Log(name + " (" + inc.SenderConnection.RemoteEndPoint.Address + ") couldn't join the server (using incompatible content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pendingClient.SteamID == null)
|
||||
{
|
||||
bool requireSteamAuth = GameMain.Config.RequireSteamAuthentication;
|
||||
#if DEBUG
|
||||
requireSteamAuth = false;
|
||||
#endif
|
||||
|
||||
//steam auth cannot be done (SteamManager not initialized or no ticket given),
|
||||
//but it's not required either -> let the client join without auth
|
||||
if ((!Steam.SteamManager.IsInitialized || (ticket?.Length??0) == 0) &&
|
||||
!requireSteamAuth)
|
||||
{
|
||||
pendingClient.Name = name;
|
||||
pendingClient.OwnerKey = ownKey;
|
||||
pendingClient.InitializationStep = ConnectionInitialization.ContentPackageOrder;
|
||||
}
|
||||
else
|
||||
{
|
||||
Steamworks.BeginAuthResult authSessionStartState = Steam.SteamManager.StartAuthSession(ticket, steamId);
|
||||
if (authSessionStartState != Steamworks.BeginAuthResult.OK)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.SteamAuthenticationFailed, "Steam auth session failed to start: " + authSessionStartState.ToString());
|
||||
return;
|
||||
}
|
||||
pendingClient.SteamID = steamId;
|
||||
pendingClient.Name = name;
|
||||
pendingClient.OwnerKey = ownKey;
|
||||
pendingClient.AuthSessionStarted = true;
|
||||
}
|
||||
}
|
||||
else //TODO: could remove since this seems impossible
|
||||
{
|
||||
if (pendingClient.SteamID != steamId)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.SteamAuthenticationFailed, "SteamID mismatch");
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ConnectionInitialization.Password:
|
||||
int pwLength = inc.ReadByte();
|
||||
byte[] incPassword = new byte[pwLength];
|
||||
inc.ReadBytes(incPassword, 0, pwLength);
|
||||
if (pendingClient.PasswordSalt == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Received password message from client without salt");
|
||||
return;
|
||||
}
|
||||
if (serverSettings.IsPasswordCorrect(incPassword, pendingClient.PasswordSalt.Value))
|
||||
{
|
||||
pendingClient.InitializationStep = ConnectionInitialization.ContentPackageOrder;
|
||||
}
|
||||
else
|
||||
{
|
||||
pendingClient.Retries++;
|
||||
if (serverSettings.BanAfterWrongPassword && pendingClient.Retries > serverSettings.MaxPasswordRetriesBeforeBan)
|
||||
{
|
||||
string banMsg = "Failed to enter correct password too many times";
|
||||
if (pendingClient.SteamID != null)
|
||||
{
|
||||
serverSettings.BanList.BanPlayer(pendingClient.Name, pendingClient.SteamID.Value, banMsg, null);
|
||||
}
|
||||
serverSettings.BanList.BanPlayer(pendingClient.Name, pendingClient.Connection.RemoteEndPoint.Address, banMsg, null);
|
||||
RemovePendingClient(pendingClient, DisconnectReason.Banned, banMsg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
pendingClient.UpdateTime = Timing.TotalTime;
|
||||
break;
|
||||
case ConnectionInitialization.ContentPackageOrder:
|
||||
pendingClient.InitializationStep = ConnectionInitialization.Success;
|
||||
pendingClient.UpdateTime = Timing.TotalTime;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void UpdatePendingClient(PendingClient pendingClient, float deltaTime)
|
||||
{
|
||||
if (netServer == null) { return; }
|
||||
|
||||
if (serverSettings.BanList.IsBanned(pendingClient.Connection.RemoteEndPoint.Address, pendingClient.SteamID ?? 0, out string banReason))
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.Banned, banReason);
|
||||
return;
|
||||
}
|
||||
|
||||
//DebugConsole.NewMessage("pending client status: " + pendingClient.InitializationStep);
|
||||
|
||||
if (connectedClients.Count >= serverSettings.MaxPlayers)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.ServerFull, "");
|
||||
}
|
||||
|
||||
if (pendingClient.InitializationStep == ConnectionInitialization.Success)
|
||||
{
|
||||
LidgrenConnection newConnection = new LidgrenConnection(pendingClient.Name, pendingClient.Connection, pendingClient.SteamID ?? 0)
|
||||
{
|
||||
Status = NetworkConnectionStatus.Connected
|
||||
};
|
||||
connectedClients.Add(newConnection);
|
||||
pendingClients.Remove(pendingClient);
|
||||
|
||||
if (OwnerConnection == null &&
|
||||
IPAddress.IsLoopback(pendingClient.Connection.RemoteEndPoint.Address.MapToIPv4NoThrow()) &&
|
||||
ownerKey != null && pendingClient.OwnerKey != 0 && pendingClient.OwnerKey == ownerKey)
|
||||
{
|
||||
ownerKey = null;
|
||||
OwnerConnection = newConnection;
|
||||
}
|
||||
|
||||
OnInitializationComplete?.Invoke(newConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
pendingClient.TimeOut -= deltaTime;
|
||||
if (pendingClient.TimeOut < 0.0)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.Unknown, Lidgren.Network.NetConnection.NoResponseMessage);
|
||||
}
|
||||
|
||||
if (Timing.TotalTime < pendingClient.UpdateTime) { return; }
|
||||
pendingClient.UpdateTime = Timing.TotalTime + 1.0;
|
||||
|
||||
NetOutgoingMessage outMsg = netServer.CreateMessage();
|
||||
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
|
||||
outMsg.Write((byte)pendingClient.InitializationStep);
|
||||
switch (pendingClient.InitializationStep)
|
||||
{
|
||||
case ConnectionInitialization.ContentPackageOrder:
|
||||
var mpContentPackages = GameMain.SelectedPackages.Where(cp => cp.HasMultiplayerIncompatibleContent).ToList();
|
||||
outMsg.WriteVariableInt32(mpContentPackages.Count);
|
||||
for (int i = 0; i < mpContentPackages.Count; i++)
|
||||
{
|
||||
outMsg.Write(mpContentPackages[i].MD5hash.Hash);
|
||||
}
|
||||
break;
|
||||
case ConnectionInitialization.Password:
|
||||
outMsg.Write(pendingClient.PasswordSalt == null); outMsg.WritePadBits();
|
||||
if (pendingClient.PasswordSalt == null)
|
||||
{
|
||||
pendingClient.PasswordSalt = CryptoRandom.Instance.Next();
|
||||
outMsg.Write(pendingClient.PasswordSalt.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
outMsg.Write(pendingClient.Retries);
|
||||
}
|
||||
break;
|
||||
}
|
||||
#if DEBUG
|
||||
netPeerConfiguration.SimulatedDuplicatesChance = GameMain.Server.SimulatedDuplicatesChance;
|
||||
netPeerConfiguration.SimulatedMinimumLatency = GameMain.Server.SimulatedMinimumLatency;
|
||||
netPeerConfiguration.SimulatedRandomLatency = GameMain.Server.SimulatedRandomLatency;
|
||||
netPeerConfiguration.SimulatedLoss = GameMain.Server.SimulatedLoss;
|
||||
#endif
|
||||
NetSendResult result = netServer.SendMessage(outMsg, pendingClient.Connection, NetDeliveryMethod.ReliableUnordered);
|
||||
if (result != NetSendResult.Sent && result != NetSendResult.Queued)
|
||||
{
|
||||
DebugConsole.NewMessage("Failed to send initialization step " + pendingClient.InitializationStep.ToString() + " to pending client: " + result.ToString(), Microsoft.Xna.Framework.Color.Yellow);
|
||||
}
|
||||
//DebugConsole.NewMessage("sent update to pending client: " + pendingClient.InitializationStep);
|
||||
}
|
||||
|
||||
private void RemovePendingClient(PendingClient pendingClient, DisconnectReason reason, string msg)
|
||||
{
|
||||
if (netServer == null) { return; }
|
||||
|
||||
if (pendingClients.Contains(pendingClient))
|
||||
{
|
||||
pendingClients.Remove(pendingClient);
|
||||
|
||||
if (pendingClient.AuthSessionStarted)
|
||||
{
|
||||
Steam.SteamManager.StopAuthSession(pendingClient.SteamID.Value);
|
||||
pendingClient.SteamID = null;
|
||||
pendingClient.AuthSessionStarted = false;
|
||||
}
|
||||
|
||||
pendingClient.Connection.Disconnect(reason + "/" + msg);
|
||||
}
|
||||
}
|
||||
|
||||
public override void InitializeSteamServerCallbacks()
|
||||
{
|
||||
Steamworks.SteamServer.OnValidateAuthTicketResponse += OnAuthChange;
|
||||
}
|
||||
|
||||
private void OnAuthChange(Steamworks.SteamId steamID, Steamworks.SteamId ownerID, Steamworks.AuthResponse status)
|
||||
{
|
||||
if (netServer == null) { return; }
|
||||
|
||||
PendingClient pendingClient = pendingClients.Find(c => c.SteamID == steamID);
|
||||
DebugConsole.Log(steamID + " validation: " + status+", "+(pendingClient!=null));
|
||||
|
||||
if (pendingClient == null)
|
||||
{
|
||||
if (status != Steamworks.AuthResponse.OK)
|
||||
{
|
||||
LidgrenConnection connection = connectedClients.Find(c => c.SteamID == steamID);
|
||||
if (connection != null)
|
||||
{
|
||||
Disconnect(connection, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam authentication status changed: " + status.ToString());
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (serverSettings.BanList.IsBanned(pendingClient.Connection.RemoteEndPoint.Address, steamID, out string banReason))
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.Banned, banReason);
|
||||
return;
|
||||
}
|
||||
|
||||
if (status == Steamworks.AuthResponse.OK)
|
||||
{
|
||||
pendingClient.InitializationStep = serverSettings.HasPassword ? ConnectionInitialization.Password : ConnectionInitialization.ContentPackageOrder;
|
||||
pendingClient.UpdateTime = Timing.TotalTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.SteamAuthenticationFailed, "Steam authentication failed: " + status.ToString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod)
|
||||
{
|
||||
if (netServer == null) { return; }
|
||||
|
||||
if (!(conn is LidgrenConnection lidgrenConn)) return;
|
||||
if (!connectedClients.Contains(lidgrenConn))
|
||||
{
|
||||
DebugConsole.ThrowError("Tried to send message to unauthenticated connection: " + lidgrenConn.IPString);
|
||||
return;
|
||||
}
|
||||
|
||||
NetDeliveryMethod lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable;
|
||||
switch (deliveryMethod)
|
||||
{
|
||||
case DeliveryMethod.Unreliable:
|
||||
lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable;
|
||||
break;
|
||||
case DeliveryMethod.Reliable:
|
||||
lidgrenDeliveryMethod = NetDeliveryMethod.ReliableUnordered;
|
||||
break;
|
||||
case DeliveryMethod.ReliableOrdered:
|
||||
lidgrenDeliveryMethod = NetDeliveryMethod.ReliableOrdered;
|
||||
break;
|
||||
}
|
||||
|
||||
NetOutgoingMessage lidgrenMsg = netServer.CreateMessage();
|
||||
byte[] msgData = new byte[msg.LengthBytes];
|
||||
msg.PrepareForSending(ref msgData, out bool isCompressed, out int length);
|
||||
lidgrenMsg.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None));
|
||||
lidgrenMsg.Write((UInt16)length);
|
||||
lidgrenMsg.Write(msgData, 0, length);
|
||||
|
||||
NetSendResult result = netServer.SendMessage(lidgrenMsg, lidgrenConn.NetConnection, lidgrenDeliveryMethod);
|
||||
if (result != NetSendResult.Sent && result != NetSendResult.Queued)
|
||||
{
|
||||
DebugConsole.NewMessage("Failed to send message to "+conn.Name+": " + result.ToString(), Microsoft.Xna.Framework.Color.Yellow);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Disconnect(NetworkConnection conn,string msg=null)
|
||||
{
|
||||
if (netServer == null) { return; }
|
||||
|
||||
if (!(conn is LidgrenConnection lidgrenConn)) { return; }
|
||||
if (connectedClients.Contains(lidgrenConn))
|
||||
{
|
||||
lidgrenConn.Status = NetworkConnectionStatus.Disconnected;
|
||||
connectedClients.Remove(lidgrenConn);
|
||||
OnDisconnect?.Invoke(conn, msg);
|
||||
Steam.SteamManager.StopAuthSession(conn.SteamID);
|
||||
}
|
||||
lidgrenConn.NetConnection.Disconnect(msg ?? "Disconnected");
|
||||
}
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
abstract class ServerPeer
|
||||
{
|
||||
protected struct ClientContentPackage
|
||||
{
|
||||
public string Name;
|
||||
public string Hash;
|
||||
|
||||
public ClientContentPackage(string name, string hash)
|
||||
{
|
||||
Name = name; Hash = hash;
|
||||
}
|
||||
}
|
||||
|
||||
protected string GetPackageStr(ContentPackage contentPackage)
|
||||
{
|
||||
return "\"" + contentPackage.Name + "\" (hash " + contentPackage.MD5hash.ShortHash + ")";
|
||||
}
|
||||
protected string GetPackageStr(ClientContentPackage contentPackage)
|
||||
{
|
||||
return "\"" + contentPackage.Name + "\" (hash " + Md5Hash.GetShortHash(contentPackage.Hash) + ")";
|
||||
}
|
||||
|
||||
public delegate void MessageCallback(NetworkConnection connection, IReadMessage message);
|
||||
public delegate void DisconnectCallback(NetworkConnection connection, string reason);
|
||||
public delegate void InitializationCompleteCallback(NetworkConnection connection);
|
||||
public delegate void ShutdownCallback();
|
||||
public delegate void OwnerDeterminedCallback(NetworkConnection connection);
|
||||
|
||||
public MessageCallback OnMessageReceived;
|
||||
public DisconnectCallback OnDisconnect;
|
||||
public InitializationCompleteCallback OnInitializationComplete;
|
||||
public ShutdownCallback OnShutdown;
|
||||
public OwnerDeterminedCallback OnOwnerDetermined;
|
||||
|
||||
protected int? ownerKey;
|
||||
|
||||
public NetworkConnection OwnerConnection { get; protected set; }
|
||||
|
||||
public abstract void InitializeSteamServerCallbacks();
|
||||
|
||||
public abstract void Start();
|
||||
public abstract void Close(string msg = null);
|
||||
public abstract void Update(float deltaTime);
|
||||
|
||||
|
||||
public abstract void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod);
|
||||
public abstract void Disconnect(NetworkConnection conn, string msg = null);
|
||||
}
|
||||
}
|
||||
+578
@@ -0,0 +1,578 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class SteamP2PServerPeer : ServerPeer
|
||||
{
|
||||
private bool started;
|
||||
|
||||
private ServerSettings serverSettings;
|
||||
|
||||
public UInt64 OwnerSteamID
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private class PendingClient
|
||||
{
|
||||
public string Name;
|
||||
public ConnectionInitialization InitializationStep;
|
||||
public double UpdateTime;
|
||||
public double TimeOut;
|
||||
public int Retries;
|
||||
public UInt64 SteamID;
|
||||
public Int32? PasswordSalt;
|
||||
public bool AuthSessionStarted;
|
||||
|
||||
public PendingClient(UInt64 steamId)
|
||||
{
|
||||
InitializationStep = ConnectionInitialization.SteamTicketAndVersion;
|
||||
Retries = 0;
|
||||
SteamID = steamId;
|
||||
PasswordSalt = null;
|
||||
UpdateTime = Timing.TotalTime+Timing.Step*3.0;
|
||||
TimeOut = NetworkConnection.TimeoutThreshold;
|
||||
AuthSessionStarted = false;
|
||||
}
|
||||
|
||||
public void Heartbeat()
|
||||
{
|
||||
TimeOut = NetworkConnection.TimeoutThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
private List<SteamP2PConnection> connectedClients;
|
||||
private List<PendingClient> pendingClients;
|
||||
|
||||
public SteamP2PServerPeer(UInt64 steamId, ServerSettings settings)
|
||||
{
|
||||
serverSettings = settings;
|
||||
|
||||
connectedClients = new List<SteamP2PConnection>();
|
||||
pendingClients = new List<PendingClient>();
|
||||
|
||||
ownerKey = null;
|
||||
|
||||
OwnerSteamID = steamId;
|
||||
|
||||
started = false;
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
outMsg.Write(OwnerSteamID);
|
||||
outMsg.Write((byte)DeliveryMethod.Reliable);
|
||||
outMsg.Write((byte)(PacketHeader.IsConnectionInitializationStep | PacketHeader.IsServerMessage));
|
||||
|
||||
byte[] msgToSend = (byte[])outMsg.Buffer.Clone();
|
||||
Array.Resize(ref msgToSend, outMsg.LengthBytes);
|
||||
ChildServerRelay.Write(msgToSend);
|
||||
|
||||
started = true;
|
||||
}
|
||||
|
||||
public override void Close(string msg = null)
|
||||
{
|
||||
if (!started) { return; }
|
||||
|
||||
if (OwnerConnection != null) OwnerConnection.Status = NetworkConnectionStatus.Disconnected;
|
||||
|
||||
for (int i = pendingClients.Count - 1; i >= 0; i--)
|
||||
{
|
||||
RemovePendingClient(pendingClients[i], DisconnectReason.ServerShutdown, msg);
|
||||
}
|
||||
|
||||
for (int i = connectedClients.Count - 1; i >= 0; i--)
|
||||
{
|
||||
Disconnect(connectedClients[i], msg ?? DisconnectReason.ServerShutdown.ToString());
|
||||
}
|
||||
|
||||
pendingClients.Clear();
|
||||
connectedClients.Clear();
|
||||
|
||||
ChildServerRelay.ShutDown();
|
||||
|
||||
OnShutdown?.Invoke();
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (!started) { return; }
|
||||
|
||||
if (OnOwnerDetermined != null && OwnerConnection != null)
|
||||
{
|
||||
OnOwnerDetermined?.Invoke(OwnerConnection);
|
||||
OnOwnerDetermined = null;
|
||||
}
|
||||
|
||||
//backwards for loop so we can remove elements while iterating
|
||||
for (int i = connectedClients.Count - 1; i >= 0; i--)
|
||||
{
|
||||
connectedClients[i].Decay(deltaTime);
|
||||
if (connectedClients[i].Timeout < 0.0)
|
||||
{
|
||||
Disconnect(connectedClients[i], "Timed out");
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
while (ChildServerRelay.Read(out byte[] incBuf))
|
||||
{
|
||||
IReadMessage inc = new ReadOnlyMessage(incBuf, false, 0, incBuf.Length, OwnerConnection);
|
||||
|
||||
HandleDataMessage(inc);
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
string errorMsg = "Server failed to read an incoming message. {" + e + "}\n" + e.StackTrace;
|
||||
GameAnalyticsManager.AddErrorEventOnce("SteamP2PServerPeer.Update:ClientReadException" + e.TargetSite.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
#else
|
||||
if (GameSettings.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
|
||||
#endif
|
||||
}
|
||||
|
||||
for (int i = 0; i < pendingClients.Count; i++)
|
||||
{
|
||||
PendingClient pendingClient = pendingClients[i];
|
||||
UpdatePendingClient(pendingClient);
|
||||
if (i >= pendingClients.Count || pendingClients[i] != pendingClient) { i--; }
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleDataMessage(IReadMessage inc)
|
||||
{
|
||||
if (!started) { return; }
|
||||
|
||||
UInt64 senderSteamId = inc.ReadUInt64();
|
||||
|
||||
byte incByte = inc.ReadByte();
|
||||
bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0;
|
||||
bool isConnectionInitializationStep = (incByte & (byte)PacketHeader.IsConnectionInitializationStep) != 0;
|
||||
bool isDisconnectMessage = (incByte & (byte)PacketHeader.IsDisconnectMessage) != 0;
|
||||
bool isServerMessage = (incByte & (byte)PacketHeader.IsServerMessage) != 0;
|
||||
bool isHeartbeatMessage = (incByte & (byte)PacketHeader.IsHeartbeatMessage) != 0;
|
||||
|
||||
if (isServerMessage)
|
||||
{
|
||||
DebugConsole.ThrowError("Got server message from" + senderSteamId.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (senderSteamId != OwnerSteamID) //sender is remote, handle disconnects and heartbeats
|
||||
{
|
||||
PendingClient pendingClient = pendingClients.Find(c => c.SteamID == senderSteamId);
|
||||
SteamP2PConnection connectedClient = connectedClients.Find(c => c.SteamID == senderSteamId);
|
||||
|
||||
pendingClient?.Heartbeat();
|
||||
connectedClient?.Heartbeat();
|
||||
|
||||
if (serverSettings.BanList.IsBanned(senderSteamId, out string banReason))
|
||||
{
|
||||
if (pendingClient != null)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.Banned, banReason);
|
||||
}
|
||||
else if (connectedClient != null)
|
||||
{
|
||||
Disconnect(connectedClient, DisconnectReason.Banned.ToString() + "/ "+ banReason);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (isDisconnectMessage)
|
||||
{
|
||||
if (pendingClient != null)
|
||||
{
|
||||
string disconnectMsg = $"ServerMessage.HasDisconnected~[client]={pendingClient.Name}";
|
||||
RemovePendingClient(pendingClient, DisconnectReason.Unknown, disconnectMsg);
|
||||
}
|
||||
else if (connectedClient != null)
|
||||
{
|
||||
string disconnectMsg = $"ServerMessage.HasDisconnected~[client]={connectedClient.Name}";
|
||||
Disconnect(connectedClient, disconnectMsg, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (isHeartbeatMessage)
|
||||
{
|
||||
//message exists solely as a heartbeat, ignore its contents
|
||||
return;
|
||||
}
|
||||
else if (isConnectionInitializationStep)
|
||||
{
|
||||
if (pendingClient != null)
|
||||
{
|
||||
ReadConnectionInitializationStep(pendingClient, new ReadOnlyMessage(inc.Buffer, false, inc.BytePosition, inc.LengthBytes - inc.BytePosition, null));
|
||||
}
|
||||
else
|
||||
{
|
||||
ConnectionInitialization initializationStep = (ConnectionInitialization)inc.ReadByte();
|
||||
if (initializationStep == ConnectionInitialization.ConnectionStarted)
|
||||
{
|
||||
pendingClients.Add(new PendingClient(senderSteamId));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (connectedClient != null)
|
||||
{
|
||||
UInt16 length = inc.ReadUInt16();
|
||||
|
||||
IReadMessage msg = new ReadOnlyMessage(inc.Buffer, isCompressed, inc.BytePosition, length, connectedClient);
|
||||
OnMessageReceived?.Invoke(connectedClient, msg);
|
||||
}
|
||||
}
|
||||
else //sender is owner
|
||||
{
|
||||
if (OwnerConnection != null) { (OwnerConnection as SteamP2PConnection).Heartbeat(); }
|
||||
|
||||
if (isDisconnectMessage)
|
||||
{
|
||||
DebugConsole.ThrowError("Received disconnect message from owner");
|
||||
return;
|
||||
}
|
||||
if (isServerMessage)
|
||||
{
|
||||
DebugConsole.ThrowError("Received server message from owner");
|
||||
return;
|
||||
}
|
||||
if (isConnectionInitializationStep)
|
||||
{
|
||||
if (OwnerConnection == null)
|
||||
{
|
||||
string ownerName = inc.ReadString();
|
||||
OwnerConnection = new SteamP2PConnection(ownerName, OwnerSteamID)
|
||||
{
|
||||
Status = NetworkConnectionStatus.Connected
|
||||
};
|
||||
|
||||
OnInitializationComplete?.Invoke(OwnerConnection);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (isHeartbeatMessage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
UInt16 length = inc.ReadUInt16();
|
||||
|
||||
IReadMessage msg = new ReadOnlyMessage(inc.Buffer, isCompressed, inc.BytePosition, length, OwnerConnection);
|
||||
OnMessageReceived?.Invoke(OwnerConnection, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadConnectionInitializationStep(PendingClient pendingClient, IReadMessage inc)
|
||||
{
|
||||
if (!started) { return; }
|
||||
|
||||
pendingClient.TimeOut = NetworkConnection.TimeoutThreshold;
|
||||
|
||||
ConnectionInitialization initializationStep = (ConnectionInitialization)inc.ReadByte();
|
||||
|
||||
//DebugConsole.NewMessage(initializationStep+" "+pendingClient.InitializationStep);
|
||||
|
||||
if (pendingClient.InitializationStep != initializationStep) return;
|
||||
|
||||
pendingClient.UpdateTime = Timing.TotalTime+Timing.Step;
|
||||
|
||||
switch (initializationStep)
|
||||
{
|
||||
case ConnectionInitialization.SteamTicketAndVersion:
|
||||
string name = Client.SanitizeName(inc.ReadString());
|
||||
UInt64 steamId = inc.ReadUInt64();
|
||||
UInt16 ticketLength = inc.ReadUInt16();
|
||||
inc.BitPosition += ticketLength * 8; //skip ticket, owner handles steam authentication
|
||||
|
||||
if (!Client.IsValidName(name, serverSettings))
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.InvalidName, "The name \"" + name + "\" is invalid");
|
||||
return;
|
||||
}
|
||||
|
||||
string version = inc.ReadString();
|
||||
bool isCompatibleVersion = NetworkMember.IsCompatible(version, GameMain.Version.ToString()) ?? false;
|
||||
if (!isCompatibleVersion)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.InvalidVersion,
|
||||
$"DisconnectMessage.InvalidVersion~[version]={GameMain.Version.ToString()}~[clientversion]={version}");
|
||||
|
||||
GameServer.Log(name + " (" + pendingClient.SteamID.ToString() + ") couldn't join the server (incompatible game version)", ServerLog.MessageType.Error);
|
||||
DebugConsole.NewMessage(name + " (" + pendingClient.SteamID.ToString() + ") couldn't join the server (incompatible game version)", Microsoft.Xna.Framework.Color.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
int contentPackageCount = (int)inc.ReadVariableUInt32();
|
||||
List<ClientContentPackage> clientContentPackages = new List<ClientContentPackage>();
|
||||
for (int i = 0; i < contentPackageCount; i++)
|
||||
{
|
||||
string packageName = inc.ReadString();
|
||||
string packageHash = inc.ReadString();
|
||||
clientContentPackages.Add(new ClientContentPackage(packageName, packageHash));
|
||||
}
|
||||
|
||||
//check if the client is missing any of our packages
|
||||
List<ContentPackage> missingPackages = new List<ContentPackage>();
|
||||
foreach (ContentPackage serverContentPackage in GameMain.SelectedPackages)
|
||||
{
|
||||
if (!serverContentPackage.HasMultiplayerIncompatibleContent) continue;
|
||||
bool packageFound = clientContentPackages.Any(cp => cp.Name == serverContentPackage.Name && cp.Hash == serverContentPackage.MD5hash.Hash);
|
||||
if (!packageFound) { missingPackages.Add(serverContentPackage); }
|
||||
}
|
||||
|
||||
//check if the client is using packages we don't have
|
||||
List<ClientContentPackage> redundantPackages = new List<ClientContentPackage>();
|
||||
foreach (ClientContentPackage clientContentPackage in clientContentPackages)
|
||||
{
|
||||
bool packageFound = GameMain.SelectedPackages.Any(cp => cp.Name == clientContentPackage.Name && cp.MD5hash.Hash == clientContentPackage.Hash);
|
||||
if (!packageFound) { redundantPackages.Add(clientContentPackage); }
|
||||
}
|
||||
|
||||
if (missingPackages.Count == 1)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.MissingContentPackage,
|
||||
$"DisconnectMessage.MissingContentPackage~[missingcontentpackage]={GetPackageStr(missingPackages[0])}");
|
||||
GameServer.Log(name + " (" + pendingClient.SteamID + ") couldn't join the server (missing content package " + GetPackageStr(missingPackages[0]) + ")", ServerLog.MessageType.Error);
|
||||
return;
|
||||
}
|
||||
else if (missingPackages.Count > 1)
|
||||
{
|
||||
List<string> packageStrs = new List<string>();
|
||||
missingPackages.ForEach(cp => packageStrs.Add(GetPackageStr(cp)));
|
||||
RemovePendingClient(pendingClient, DisconnectReason.MissingContentPackage,
|
||||
$"DisconnectMessage.MissingContentPackages~[missingcontentpackages]={string.Join(", ", packageStrs)}");
|
||||
GameServer.Log(name + " (" + pendingClient.SteamID + ") couldn't join the server (missing content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
|
||||
return;
|
||||
}
|
||||
if (redundantPackages.Count == 1)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.IncompatibleContentPackage,
|
||||
$"DisconnectMessage.IncompatibleContentPackage~[incompatiblecontentpackage]={GetPackageStr(redundantPackages[0])}");
|
||||
GameServer.Log(name + " (" + pendingClient.SteamID + ") couldn't join the server (using an incompatible content package " + GetPackageStr(redundantPackages[0]) + ")", ServerLog.MessageType.Error);
|
||||
return;
|
||||
}
|
||||
if (redundantPackages.Count > 1)
|
||||
{
|
||||
List<string> packageStrs = new List<string>();
|
||||
redundantPackages.ForEach(cp => packageStrs.Add(GetPackageStr(cp)));
|
||||
RemovePendingClient(pendingClient, DisconnectReason.IncompatibleContentPackage,
|
||||
$"DisconnectMessage.IncompatibleContentPackages~[incompatiblecontentpackages]={string.Join(", ", packageStrs)}");
|
||||
GameServer.Log(name + " (" + pendingClient.SteamID + ") couldn't join the server (using incompatible content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pendingClient.AuthSessionStarted)
|
||||
{
|
||||
pendingClient.InitializationStep = serverSettings.HasPassword ? ConnectionInitialization.Password : ConnectionInitialization.ContentPackageOrder;
|
||||
|
||||
pendingClient.Name = name;
|
||||
pendingClient.AuthSessionStarted = true;
|
||||
}
|
||||
break;
|
||||
case ConnectionInitialization.Password:
|
||||
int pwLength = inc.ReadByte();
|
||||
byte[] incPassword = inc.ReadBytes(pwLength);
|
||||
if (pendingClient.PasswordSalt == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Received password message from client without salt");
|
||||
return;
|
||||
}
|
||||
if (serverSettings.IsPasswordCorrect(incPassword, pendingClient.PasswordSalt.Value))
|
||||
{
|
||||
pendingClient.InitializationStep = ConnectionInitialization.ContentPackageOrder;
|
||||
}
|
||||
else
|
||||
{
|
||||
pendingClient.Retries++;
|
||||
if (serverSettings.BanAfterWrongPassword && pendingClient.Retries > serverSettings.MaxPasswordRetriesBeforeBan)
|
||||
{
|
||||
string banMsg = "Failed to enter correct password too many times";
|
||||
serverSettings.BanList.BanPlayer(pendingClient.Name, pendingClient.SteamID, banMsg, null);
|
||||
|
||||
RemovePendingClient(pendingClient, DisconnectReason.Banned, banMsg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
pendingClient.UpdateTime = Timing.TotalTime;
|
||||
break;
|
||||
case ConnectionInitialization.ContentPackageOrder:
|
||||
pendingClient.InitializationStep = ConnectionInitialization.Success;
|
||||
pendingClient.UpdateTime = Timing.TotalTime;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void UpdatePendingClient(PendingClient pendingClient)
|
||||
{
|
||||
if (!started) { return; }
|
||||
|
||||
if (serverSettings.BanList.IsBanned(pendingClient.SteamID, out string banReason))
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.Banned, banReason);
|
||||
return;
|
||||
}
|
||||
|
||||
//DebugConsole.NewMessage("pending client status: " + pendingClient.InitializationStep);
|
||||
|
||||
if (connectedClients.Count >= serverSettings.MaxPlayers - 1)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.ServerFull, "");
|
||||
}
|
||||
|
||||
if (pendingClient.InitializationStep == ConnectionInitialization.Success)
|
||||
{
|
||||
SteamP2PConnection newConnection = new SteamP2PConnection(pendingClient.Name, pendingClient.SteamID)
|
||||
{
|
||||
Status = NetworkConnectionStatus.Connected
|
||||
};
|
||||
connectedClients.Add(newConnection);
|
||||
pendingClients.Remove(pendingClient);
|
||||
OnInitializationComplete?.Invoke(newConnection);
|
||||
}
|
||||
|
||||
pendingClient.TimeOut -= Timing.Step;
|
||||
if (pendingClient.TimeOut < 0.0)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.Unknown, Lidgren.Network.NetConnection.NoResponseMessage);
|
||||
}
|
||||
|
||||
if (Timing.TotalTime < pendingClient.UpdateTime) { return; }
|
||||
pendingClient.UpdateTime = Timing.TotalTime + 1.0;
|
||||
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
outMsg.Write(pendingClient.SteamID);
|
||||
outMsg.Write((byte)DeliveryMethod.Reliable);
|
||||
outMsg.Write((byte)(PacketHeader.IsConnectionInitializationStep |
|
||||
PacketHeader.IsServerMessage));
|
||||
outMsg.Write((byte)pendingClient.InitializationStep);
|
||||
switch (pendingClient.InitializationStep)
|
||||
{
|
||||
case ConnectionInitialization.ContentPackageOrder:
|
||||
var mpContentPackages = GameMain.SelectedPackages.Where(cp => cp.HasMultiplayerIncompatibleContent).ToList();
|
||||
outMsg.WriteVariableUInt32((UInt32)mpContentPackages.Count);
|
||||
for (int i = 0; i < mpContentPackages.Count; i++)
|
||||
{
|
||||
outMsg.Write(mpContentPackages[i].MD5hash.Hash);
|
||||
}
|
||||
break;
|
||||
case ConnectionInitialization.Password:
|
||||
outMsg.Write(pendingClient.PasswordSalt == null); outMsg.WritePadBits();
|
||||
if (pendingClient.PasswordSalt == null)
|
||||
{
|
||||
pendingClient.PasswordSalt = Lidgren.Network.CryptoRandom.Instance.Next();
|
||||
outMsg.Write(pendingClient.PasswordSalt.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
outMsg.Write(pendingClient.Retries);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
byte[] msgToSend = (byte[])outMsg.Buffer.Clone();
|
||||
Array.Resize(ref msgToSend, outMsg.LengthBytes);
|
||||
ChildServerRelay.Write(msgToSend);
|
||||
}
|
||||
|
||||
private void RemovePendingClient(PendingClient pendingClient, DisconnectReason reason, string msg)
|
||||
{
|
||||
if (!started) { return; }
|
||||
|
||||
if (pendingClients.Contains(pendingClient))
|
||||
{
|
||||
SendDisconnectMessage(pendingClient.SteamID, reason + "/" + msg);
|
||||
|
||||
pendingClients.Remove(pendingClient);
|
||||
|
||||
if (pendingClient.AuthSessionStarted)
|
||||
{
|
||||
Steam.SteamManager.StopAuthSession(pendingClient.SteamID);
|
||||
pendingClient.SteamID = 0;
|
||||
pendingClient.AuthSessionStarted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void InitializeSteamServerCallbacks()
|
||||
{
|
||||
throw new InvalidOperationException("Called InitializeSteamServerCallbacks on SteamP2PServerPeer!");
|
||||
}
|
||||
|
||||
public override void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod)
|
||||
{
|
||||
if (!started) { return; }
|
||||
|
||||
if (!(conn is SteamP2PConnection steamp2pConn)) return;
|
||||
if (!connectedClients.Contains(steamp2pConn) && conn != OwnerConnection)
|
||||
{
|
||||
DebugConsole.ThrowError("Tried to send message to unauthenticated connection: " + steamp2pConn.SteamID.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
IWriteMessage msgToSend = new WriteOnlyMessage();
|
||||
byte[] msgData = new byte[msg.LengthBytes];
|
||||
msg.PrepareForSending(ref msgData, out bool isCompressed, out int length);
|
||||
msgToSend.Write(conn.SteamID);
|
||||
msgToSend.Write((byte)deliveryMethod);
|
||||
msgToSend.Write((byte)((isCompressed ? PacketHeader.IsCompressed : PacketHeader.None) | PacketHeader.IsServerMessage));
|
||||
msgToSend.Write((UInt16)length);
|
||||
msgToSend.Write(msgData, 0, length);
|
||||
|
||||
byte[] bufToSend = (byte[])msgToSend.Buffer.Clone();
|
||||
Array.Resize(ref bufToSend, msgToSend.LengthBytes);
|
||||
ChildServerRelay.Write(bufToSend);
|
||||
}
|
||||
|
||||
private void SendDisconnectMessage(UInt64 steamId, string msg)
|
||||
{
|
||||
if (!started) { return; }
|
||||
if (string.IsNullOrWhiteSpace(msg)) { return; }
|
||||
|
||||
IWriteMessage msgToSend = new WriteOnlyMessage();
|
||||
msgToSend.Write(steamId);
|
||||
msgToSend.Write((byte)DeliveryMethod.Reliable);
|
||||
msgToSend.Write((byte)(PacketHeader.IsDisconnectMessage | PacketHeader.IsServerMessage));
|
||||
msgToSend.Write(msg);
|
||||
|
||||
byte[] bufToSend = (byte[])msgToSend.Buffer.Clone();
|
||||
Array.Resize(ref bufToSend, msgToSend.LengthBytes);
|
||||
ChildServerRelay.Write(bufToSend);
|
||||
}
|
||||
|
||||
private void Disconnect(NetworkConnection conn, string msg, bool sendDisconnectMessage)
|
||||
{
|
||||
if (!started) { return; }
|
||||
|
||||
if (!(conn is SteamP2PConnection steamp2pConn)) { return; }
|
||||
if (connectedClients.Contains(steamp2pConn))
|
||||
{
|
||||
if (sendDisconnectMessage) SendDisconnectMessage(steamp2pConn.SteamID, msg);
|
||||
steamp2pConn.Status = NetworkConnectionStatus.Disconnected;
|
||||
connectedClients.Remove(steamp2pConn);
|
||||
OnDisconnect?.Invoke(conn, msg);
|
||||
Steam.SteamManager.StopAuthSession(conn.SteamID);
|
||||
}
|
||||
else if (steamp2pConn == OwnerConnection)
|
||||
{
|
||||
//TODO: fix?
|
||||
}
|
||||
}
|
||||
|
||||
public override void Disconnect(NetworkConnection conn, string msg = null)
|
||||
{
|
||||
Disconnect(conn, msg, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Lidgren.Network;
|
||||
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 || (!GameMain.Server.ServerSettings.AllowSpectating && GameMain.Server.OwnerConnection != c.Connection)) &&
|
||||
(c.Character == null || c.Character.IsDead));
|
||||
}
|
||||
|
||||
private List<CharacterInfo> GetBotsToRespawn()
|
||||
{
|
||||
if (GameMain.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 = GameMain.Server.ConnectedClients.Count(c =>
|
||||
c.InGame &&
|
||||
(!c.SpectateOnly || (!GameMain.Server.ServerSettings.AllowSpectating && GameMain.Server.OwnerConnection != c.Connection)));
|
||||
|
||||
var existingBots = Character.CharacterList
|
||||
.FindAll(c => c.TeamID == Character.TeamType.Team1 && c.AIController != null && c.Info != null);
|
||||
|
||||
int requiredBots = GameMain.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(CharacterPrefab.HumanSpeciesName);
|
||||
}
|
||||
else
|
||||
{
|
||||
existingBots.Remove(botToRespawn.Character);
|
||||
}
|
||||
botsToRespawn.Add(botToRespawn);
|
||||
}
|
||||
return botsToRespawn;
|
||||
}
|
||||
|
||||
private bool RespawnPending()
|
||||
{
|
||||
int characterToRespawnCount = GetClientsToRespawn().Count;
|
||||
int totalCharacterCount = GameMain.Server.ConnectedClients.Count;
|
||||
return (float)characterToRespawnCount >= Math.Max((float)totalCharacterCount * GameMain.Server.ServerSettings.MinRespawnRatio, 1.0f);
|
||||
}
|
||||
|
||||
partial void UpdateWaiting(float deltaTime)
|
||||
{
|
||||
bool respawnPending = RespawnPending();
|
||||
if (respawnPending != RespawnCountdownStarted)
|
||||
{
|
||||
RespawnCountdownStarted = respawnPending;
|
||||
RespawnTime = DateTime.Now + new TimeSpan(0,0,0,0, (int)(GameMain.Server.ServerSettings.RespawnInterval * 1000.0f));
|
||||
GameMain.Server.CreateEntityEvent(this);
|
||||
}
|
||||
|
||||
if (!RespawnCountdownStarted) { return; }
|
||||
|
||||
if (DateTime.Now > RespawnTime)
|
||||
{
|
||||
DispatchShuttle();
|
||||
RespawnCountdownStarted = false;
|
||||
}
|
||||
|
||||
if (RespawnShuttle == null) { return; }
|
||||
|
||||
RespawnShuttle.Velocity = Vector2.Zero;
|
||||
|
||||
if (shuttleSteering != null)
|
||||
{
|
||||
shuttleSteering.AutoPilot = false;
|
||||
shuttleSteering.MaintainPos = false;
|
||||
}
|
||||
}
|
||||
|
||||
partial void DispatchShuttle()
|
||||
{
|
||||
if (RespawnShuttle != null)
|
||||
{
|
||||
CurrentState = State.Transporting;
|
||||
GameMain.Server.CreateEntityEvent(this);
|
||||
|
||||
ResetShuttle();
|
||||
|
||||
if (shuttleSteering != null)
|
||||
{
|
||||
shuttleSteering.TargetVelocity = Vector2.Zero;
|
||||
}
|
||||
|
||||
GameServer.Log("Dispatching the respawn shuttle.", ServerLog.MessageType.Spawning);
|
||||
|
||||
RespawnCharacters();
|
||||
|
||||
CoroutineManager.StopCoroutines("forcepos");
|
||||
Vector2 spawnPos = FindSpawnPos();
|
||||
if (spawnPos.Y > Level.Loaded.Size.Y)
|
||||
{
|
||||
CoroutineManager.StartCoroutine(ForceShuttleToPos(Level.Loaded.StartPosition - Vector2.UnitY * Level.ShaftHeight, 100.0f), "forcepos");
|
||||
}
|
||||
else
|
||||
{
|
||||
RespawnShuttle.SetPosition(spawnPos);
|
||||
RespawnShuttle.Velocity = Vector2.Zero;
|
||||
if (shuttleSteering != null)
|
||||
{
|
||||
shuttleSteering.AutoPilot = true;
|
||||
shuttleSteering.MaintainPos = true;
|
||||
shuttleSteering.PosToMaintain = RespawnShuttle.WorldPosition;
|
||||
shuttleSteering.UnsentChanges = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentState = State.Waiting;
|
||||
GameServer.Log("Respawning everyone in main sub.", ServerLog.MessageType.Spawning);
|
||||
GameMain.Server.CreateEntityEvent(this);
|
||||
|
||||
RespawnCharacters();
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateReturningProjSpecific()
|
||||
{
|
||||
foreach (Door door in shuttleDoors)
|
||||
{
|
||||
if (door.IsOpen) door.TrySetState(false, false, true);
|
||||
}
|
||||
|
||||
var shuttleGaps = Gap.GapList.FindAll(g => g.Submarine == RespawnShuttle && g.ConnectedWall != null);
|
||||
shuttleGaps.ForEach(g => Spawner.AddToRemoveQueue(g));
|
||||
|
||||
var dockingPorts = Item.ItemList.FindAll(i => i.Submarine == RespawnShuttle && i.GetComponent<DockingPort>() != null);
|
||||
dockingPorts.ForEach(d => d.GetComponent<DockingPort>().Undock());
|
||||
|
||||
//shuttle has returned if the path has been traversed or the shuttle is close enough to the exit
|
||||
if (!CoroutineManager.IsCoroutineRunning("forcepos"))
|
||||
{
|
||||
if ((shuttleSteering?.SteeringPath != null && shuttleSteering.SteeringPath.Finished)
|
||||
|| (RespawnShuttle.WorldPosition.Y + RespawnShuttle.Borders.Y > Level.Loaded.StartPosition.Y - Level.ShaftHeight &&
|
||||
Math.Abs(Level.Loaded.StartPosition.X - RespawnShuttle.WorldPosition.X) < 1000.0f))
|
||||
{
|
||||
CoroutineManager.StopCoroutines("forcepos");
|
||||
CoroutineManager.StartCoroutine(
|
||||
ForceShuttleToPos(new Vector2(Level.Loaded.StartPosition.X, Level.Loaded.Size.Y + 1000.0f), 100.0f), "forcepos");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (RespawnShuttle.WorldPosition.Y > Level.Loaded.Size.Y || DateTime.Now > despawnTime)
|
||||
{
|
||||
CoroutineManager.StopCoroutines("forcepos");
|
||||
|
||||
ResetShuttle();
|
||||
|
||||
CurrentState = State.Waiting;
|
||||
GameServer.Log("The respawn shuttle has left.", ServerLog.MessageType.Spawning);
|
||||
GameMain.Server.CreateEntityEvent(this);
|
||||
|
||||
RespawnCountdownStarted = false;
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateTransportingProjSpecific(float deltaTime)
|
||||
{
|
||||
|
||||
if (!ReturnCountdownStarted)
|
||||
{
|
||||
//if there are no living chracters inside, transporting can be stopped immediately
|
||||
if (!Character.CharacterList.Any(c => c.Submarine == RespawnShuttle && !c.IsDead))
|
||||
{
|
||||
ReturnTime = DateTime.Now;
|
||||
ReturnCountdownStarted = true;
|
||||
}
|
||||
else if (!RespawnPending())
|
||||
{
|
||||
//don't start counting down until someone else needs to respawn
|
||||
ReturnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(maxTransportTime * 1000));
|
||||
despawnTime = ReturnTime + new TimeSpan(0, 0, seconds: 30);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
ReturnCountdownStarted = true;
|
||||
GameMain.Server.CreateEntityEvent(this);
|
||||
}
|
||||
}
|
||||
|
||||
if (DateTime.Now > ReturnTime)
|
||||
{
|
||||
GameServer.Log("The respawn shuttle is leaving.", ServerLog.MessageType.ServerMessage);
|
||||
CurrentState = State.Returning;
|
||||
|
||||
GameMain.Server.CreateEntityEvent(this);
|
||||
|
||||
RespawnCountdownStarted = false;
|
||||
maxTransportTime = GameMain.Server.ServerSettings.MaxTransportTime;
|
||||
}
|
||||
}
|
||||
|
||||
partial void RespawnCharactersProjSpecific()
|
||||
{
|
||||
var respawnSub = RespawnShuttle ?? Submarine.MainSub;
|
||||
|
||||
var clients = GetClientsToRespawn();
|
||||
foreach (Client c in clients)
|
||||
{
|
||||
//get rid of the existing character
|
||||
c.Character?.DespawnNow();
|
||||
|
||||
//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(CharacterPrefab.HumanSpeciesName, c.Name); }
|
||||
}
|
||||
List<CharacterInfo> characterInfos = clients.Select(c => c.CharacterInfo).ToList();
|
||||
|
||||
var botsToSpawn = GetBotsToRespawn();
|
||||
characterInfos.AddRange(botsToSpawn);
|
||||
|
||||
GameMain.Server.AssignJobs(clients);
|
||||
foreach (Client c in clients)
|
||||
{
|
||||
c.CharacterInfo.Job = new Job(c.AssignedJob.First, c.AssignedJob.Second);
|
||||
}
|
||||
|
||||
//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;
|
||||
|
||||
characterInfos[i].CurrentOrder = null;
|
||||
characterInfos[i].CurrentOrderOption = null;
|
||||
|
||||
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
|
||||
{
|
||||
//tell the respawning client they're no longer a traitor
|
||||
if (GameMain.Server.TraitorManager?.Traitors != null && clients[i].Character != null)
|
||||
{
|
||||
if (GameMain.Server.TraitorManager.Traitors.Any(t => t.Character == clients[i].Character))
|
||||
{
|
||||
GameMain.Server.SendDirectChatMessage(TextManager.FormatServerMessage("TraitorRespawnMessage"), clients[i], ChatMessageType.ServerMessageBox);
|
||||
}
|
||||
}
|
||||
|
||||
clients[i].Character = character;
|
||||
character.OwnerClientEndPoint = clients[i].Connection.EndPointString;
|
||||
character.OwnerClientName = clients[i].Name;
|
||||
GameServer.Log(string.Format("Respawning {0} ({1}) as {2}", clients[i].Name, clients[i].Connection?.EndPointString, 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, user: null);
|
||||
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, user: null);
|
||||
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.Identifier != "idcard") continue;
|
||||
foreach (string s in shuttleSpawnPoints[i].IdCardTags)
|
||||
{
|
||||
item.AddTag(s);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(shuttleSpawnPoints[i].IdCardDesc))
|
||||
item.Description = shuttleSpawnPoints[i].IdCardDesc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
|
||||
{
|
||||
msg.WriteRangedInteger((int)CurrentState, 0, Enum.GetNames(typeof(State)).Length);
|
||||
|
||||
switch (CurrentState)
|
||||
{
|
||||
case State.Transporting:
|
||||
msg.Write(ReturnCountdownStarted);
|
||||
msg.Write(GameMain.Server.ServerSettings.MaxTransportTime);
|
||||
msg.Write((float)(ReturnTime - DateTime.Now).TotalSeconds);
|
||||
break;
|
||||
case State.Waiting:
|
||||
msg.Write(RespawnCountdownStarted);
|
||||
msg.Write((float)(RespawnTime - DateTime.Now).TotalSeconds);
|
||||
break;
|
||||
case State.Returning:
|
||||
break;
|
||||
}
|
||||
|
||||
msg.WritePadBits();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,539 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
partial class ServerSettings
|
||||
{
|
||||
public static readonly string ClientPermissionsFile = "Data" + Path.DirectorySeparatorChar + "clientpermissions.xml";
|
||||
|
||||
partial void InitProjSpecific()
|
||||
{
|
||||
LoadSettings();
|
||||
LoadClientPermissions();
|
||||
}
|
||||
|
||||
private void WriteNetProperties(IWriteMessage outMsg)
|
||||
{
|
||||
outMsg.Write((UInt16)netProperties.Keys.Count);
|
||||
foreach (UInt32 key in netProperties.Keys)
|
||||
{
|
||||
outMsg.Write(key);
|
||||
netProperties[key].Write(outMsg);
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerAdminWrite(IWriteMessage outMsg, Client c)
|
||||
{
|
||||
//outMsg.Write(isPublic);
|
||||
//outMsg.Write(EnableUPnP);
|
||||
//outMsg.WritePadBits();
|
||||
//outMsg.Write((UInt16)QueryPort);
|
||||
|
||||
WriteNetProperties(outMsg);
|
||||
WriteMonsterEnabled(outMsg);
|
||||
BanList.ServerAdminWrite(outMsg, c);
|
||||
Whitelist.ServerAdminWrite(outMsg, c);
|
||||
}
|
||||
|
||||
public void ServerWrite(IWriteMessage outMsg, Client c)
|
||||
{
|
||||
outMsg.Write(ServerName);
|
||||
outMsg.Write(ServerMessageText);
|
||||
outMsg.Write((byte)MaxPlayers);
|
||||
outMsg.Write(HasPassword);
|
||||
outMsg.Write(IsPublic);
|
||||
outMsg.WritePadBits();
|
||||
outMsg.WriteRangedInteger(TickRate, 1, 60);
|
||||
|
||||
WriteExtraCargo(outMsg);
|
||||
|
||||
Voting.ServerWrite(outMsg);
|
||||
|
||||
if (c.HasPermission(Networking.ClientPermissions.ManageSettings))
|
||||
{
|
||||
outMsg.Write(true);
|
||||
outMsg.WritePadBits();
|
||||
|
||||
ServerAdminWrite(outMsg, c);
|
||||
}
|
||||
else
|
||||
{
|
||||
outMsg.Write(false);
|
||||
outMsg.WritePadBits();
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerRead(IReadMessage incMsg, Client c)
|
||||
{
|
||||
if (!c.HasPermission(Networking.ClientPermissions.ManageSettings)) return;
|
||||
|
||||
NetFlags flags = (NetFlags)incMsg.ReadByte();
|
||||
|
||||
bool changed = false;
|
||||
|
||||
if (flags.HasFlag(NetFlags.Name))
|
||||
{
|
||||
string serverName = incMsg.ReadString();
|
||||
if (ServerName != serverName) changed = true;
|
||||
ServerName = serverName;
|
||||
}
|
||||
|
||||
if (flags.HasFlag(NetFlags.Message))
|
||||
{
|
||||
string serverMessageText = incMsg.ReadString();
|
||||
if (ServerMessageText != serverMessageText) changed = true;
|
||||
ServerMessageText = serverMessageText;
|
||||
}
|
||||
|
||||
if (flags.HasFlag(NetFlags.Properties))
|
||||
{
|
||||
changed |= ReadExtraCargo(incMsg);
|
||||
|
||||
UInt32 count = incMsg.ReadUInt32();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
UInt32 key = incMsg.ReadUInt32();
|
||||
|
||||
if (netProperties.ContainsKey(key))
|
||||
{
|
||||
object prevValue = netProperties[key].Value;
|
||||
netProperties[key].Read(incMsg);
|
||||
if (!netProperties[key].PropEquals(prevValue, netProperties[key]))
|
||||
{
|
||||
GameServer.Log(c.Name + " changed " + netProperties[key].Name + " to " + netProperties[key].Value.ToString(), ServerLog.MessageType.ServerMessage);
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
UInt32 size = incMsg.ReadVariableUInt32();
|
||||
incMsg.BitPosition += (int)(8 * size);
|
||||
}
|
||||
}
|
||||
|
||||
bool changedMonsterSettings = incMsg.ReadBoolean(); incMsg.ReadPadBits();
|
||||
changed |= changedMonsterSettings;
|
||||
if (changedMonsterSettings) ReadMonsterEnabled(incMsg);
|
||||
changed |= BanList.ServerAdminRead(incMsg, c);
|
||||
changed |= Whitelist.ServerAdminRead(incMsg, c);
|
||||
}
|
||||
|
||||
if (flags.HasFlag(NetFlags.Misc))
|
||||
{
|
||||
int orBits = incMsg.ReadRangedInteger(0, (int)Barotrauma.MissionType.All) & (int)Barotrauma.MissionType.All;
|
||||
int andBits = incMsg.ReadRangedInteger(0, (int)Barotrauma.MissionType.All) & (int)Barotrauma.MissionType.All;
|
||||
GameMain.NetLobbyScreen.MissionType = (Barotrauma.MissionType)(((int)GameMain.NetLobbyScreen.MissionType | orBits) & andBits);
|
||||
|
||||
int traitorSetting = (int)TraitorsEnabled + incMsg.ReadByte() - 1;
|
||||
if (traitorSetting < 0) traitorSetting = 2;
|
||||
if (traitorSetting > 2) traitorSetting = 0;
|
||||
TraitorsEnabled = (YesNoMaybe)traitorSetting;
|
||||
|
||||
int botCount = BotCount + incMsg.ReadByte() - 1;
|
||||
if (botCount < 0) botCount = MaxBotCount;
|
||||
if (botCount > MaxBotCount) botCount = 0;
|
||||
BotCount = botCount;
|
||||
|
||||
int botSpawnMode = (int)BotSpawnMode + incMsg.ReadByte() - 1;
|
||||
if (botSpawnMode < 0) botSpawnMode = 1;
|
||||
if (botSpawnMode > 1) botSpawnMode = 0;
|
||||
BotSpawnMode = (BotSpawnMode)botSpawnMode;
|
||||
|
||||
float levelDifficulty = incMsg.ReadSingle();
|
||||
if (levelDifficulty >= 0.0f) SelectedLevelDifficulty = levelDifficulty;
|
||||
|
||||
UseRespawnShuttle = incMsg.ReadBoolean();
|
||||
|
||||
bool changedAutoRestart = incMsg.ReadBoolean();
|
||||
bool autoRestart = incMsg.ReadBoolean();
|
||||
if (changedAutoRestart)
|
||||
{
|
||||
AutoRestart = autoRestart;
|
||||
}
|
||||
|
||||
changed |= true;
|
||||
}
|
||||
|
||||
if (flags.HasFlag(NetFlags.LevelSeed))
|
||||
{
|
||||
GameMain.NetLobbyScreen.LevelSeed = incMsg.ReadString();
|
||||
changed |= true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
if (KarmaPreset == "custom")
|
||||
{
|
||||
GameMain.NetworkMember?.KarmaManager?.SaveCustomPreset();
|
||||
GameMain.NetworkMember?.KarmaManager?.Save();
|
||||
}
|
||||
SaveSettings();
|
||||
GameMain.NetLobbyScreen.LastUpdateID++;
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveSettings()
|
||||
{
|
||||
XDocument doc = new XDocument(new XElement("serversettings"));
|
||||
|
||||
doc.Root.SetAttributeValue("name", ServerName);
|
||||
doc.Root.SetAttributeValue("public", IsPublic);
|
||||
doc.Root.SetAttributeValue("port", Port);
|
||||
#if USE_STEAM
|
||||
doc.Root.SetAttributeValue("queryport", QueryPort);
|
||||
#endif
|
||||
doc.Root.SetAttributeValue("password", password ?? "");
|
||||
|
||||
doc.Root.SetAttributeValue("enableupnp", EnableUPnP);
|
||||
doc.Root.SetAttributeValue("autorestart", autoRestart);
|
||||
|
||||
doc.Root.SetAttributeValue("LevelDifficulty", ((int)selectedLevelDifficulty).ToString());
|
||||
|
||||
doc.Root.SetAttributeValue("ServerMessage", ServerMessageText);
|
||||
|
||||
doc.Root.SetAttributeValue("AllowedRandomMissionTypes", string.Join(",", AllowedRandomMissionTypes));
|
||||
doc.Root.SetAttributeValue("AllowedClientNameChars", string.Join(",", AllowedClientNameChars.Select(c => c.First + "-" + c.Second)));
|
||||
|
||||
SerializableProperty.SerializeProperties(this, doc.Root, true);
|
||||
|
||||
XmlWriterSettings settings = new XmlWriterSettings
|
||||
{
|
||||
Indent = true,
|
||||
NewLineOnAttributes = true
|
||||
};
|
||||
|
||||
using (var writer = XmlWriter.Create(SettingsFile, settings))
|
||||
{
|
||||
doc.Save(writer);
|
||||
}
|
||||
|
||||
if (KarmaPreset == "custom")
|
||||
{
|
||||
GameMain.Server?.KarmaManager?.SaveCustomPreset();
|
||||
}
|
||||
GameMain.Server?.KarmaManager?.Save();
|
||||
}
|
||||
|
||||
private void LoadSettings()
|
||||
{
|
||||
XDocument doc = null;
|
||||
if (File.Exists(SettingsFile))
|
||||
{
|
||||
doc = XMLExtensions.TryLoadXml(SettingsFile);
|
||||
}
|
||||
|
||||
if (doc == null)
|
||||
{
|
||||
doc = new XDocument(new XElement("serversettings"));
|
||||
}
|
||||
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, doc.Root);
|
||||
|
||||
AutoRestart = doc.Root.GetAttributeBool("autorestart", false);
|
||||
|
||||
Voting.AllowSubVoting = SubSelectionMode == SelectionMode.Vote;
|
||||
Voting.AllowModeVoting = ModeSelectionMode == SelectionMode.Vote;
|
||||
|
||||
selectedLevelDifficulty = doc.Root.GetAttributeFloat("LevelDifficulty", 20.0f);
|
||||
GameMain.NetLobbyScreen.SetLevelDifficulty(selectedLevelDifficulty);
|
||||
|
||||
GameMain.NetLobbyScreen.SetTraitorsEnabled(traitorsEnabled);
|
||||
|
||||
string[] defaultAllowedClientNameChars =
|
||||
new string[] {
|
||||
"32-33",
|
||||
"38-46",
|
||||
"48-57",
|
||||
"65-90",
|
||||
"91",
|
||||
"93",
|
||||
"95-122",
|
||||
"192-255",
|
||||
"384-591",
|
||||
"1024-1279",
|
||||
"19968-40959","13312-19903","131072-15043983","15043985-173791","173824-178207","178208-183983","63744-64255","194560-195103" //CJK
|
||||
};
|
||||
|
||||
string[] allowedClientNameCharsStr = doc.Root.GetAttributeStringArray("AllowedClientNameChars", defaultAllowedClientNameChars);
|
||||
if (doc.Root.GetAttributeString("AllowedClientNameChars", "") == "65-90,97-122,48-59")
|
||||
{
|
||||
allowedClientNameCharsStr = defaultAllowedClientNameChars;
|
||||
}
|
||||
|
||||
foreach (string allowedClientNameCharRange in allowedClientNameCharsStr)
|
||||
{
|
||||
string[] splitRange = allowedClientNameCharRange.Split('-');
|
||||
if (splitRange.Length == 0 || splitRange.Length > 2)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in server settings - " + allowedClientNameCharRange + " is not a valid range for characters allowed in client names.");
|
||||
continue;
|
||||
}
|
||||
|
||||
int min = -1;
|
||||
if (!int.TryParse(splitRange[0], out min))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in server settings - " + allowedClientNameCharRange + " is not a valid range for characters allowed in client names.");
|
||||
continue;
|
||||
}
|
||||
int max = min;
|
||||
if (splitRange.Length == 2)
|
||||
{
|
||||
if (!int.TryParse(splitRange[1], out max))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in server settings - " + allowedClientNameCharRange + " is not a valid range for characters allowed in client names.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (min > -1 && max > -1) { AllowedClientNameChars.Add(new Pair<int, int>(min, max)); }
|
||||
}
|
||||
|
||||
AllowedRandomMissionTypes = new List<MissionType>();
|
||||
string[] allowedMissionTypeNames = doc.Root.GetAttributeStringArray(
|
||||
"AllowedRandomMissionTypes", Enum.GetValues(typeof(MissionType)).Cast<MissionType>().Select(m => m.ToString()).ToArray());
|
||||
foreach (string missionTypeName in allowedMissionTypeNames)
|
||||
{
|
||||
if (Enum.TryParse(missionTypeName, out MissionType missionType))
|
||||
{
|
||||
if (missionType == Barotrauma.MissionType.None) continue;
|
||||
AllowedRandomMissionTypes.Add(missionType);
|
||||
}
|
||||
}
|
||||
|
||||
ServerName = doc.Root.GetAttributeString("name", "");
|
||||
if (ServerName.Length > NetConfig.ServerNameMaxLength) { ServerName = ServerName.Substring(0, NetConfig.ServerNameMaxLength); }
|
||||
ServerMessageText = doc.Root.GetAttributeString("ServerMessage", "");
|
||||
|
||||
GameMain.NetLobbyScreen.SelectedModeIdentifier = GameModeIdentifier;
|
||||
//handle Random as the mission type, which is no longer a valid setting
|
||||
//MissionType.All offers equivalent functionality
|
||||
if (MissionType == "Random") { MissionType = "All"; }
|
||||
GameMain.NetLobbyScreen.MissionTypeName = MissionType;
|
||||
|
||||
GameMain.NetLobbyScreen.SetBotSpawnMode(BotSpawnMode);
|
||||
GameMain.NetLobbyScreen.SetBotCount(BotCount);
|
||||
|
||||
List<string> monsterNames = CharacterPrefab.Prefabs.Select(p => p.Identifier).ToList();
|
||||
MonsterEnabled = new Dictionary<string, bool>();
|
||||
foreach (string s in monsterNames)
|
||||
{
|
||||
if (!MonsterEnabled.ContainsKey(s)) MonsterEnabled.Add(s, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadClientPermissions()
|
||||
{
|
||||
ClientPermissions.Clear();
|
||||
|
||||
if (!File.Exists(ClientPermissionsFile))
|
||||
{
|
||||
if (File.Exists("Data/clientpermissions.txt"))
|
||||
{
|
||||
LoadClientPermissionsOld("Data/clientpermissions.txt");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
XDocument doc = XMLExtensions.TryLoadXml(ClientPermissionsFile);
|
||||
if (doc == null) { return; }
|
||||
foreach (XElement clientElement in doc.Root.Elements())
|
||||
{
|
||||
string clientName = clientElement.GetAttributeString("name", "");
|
||||
string clientEndPoint = clientElement.GetAttributeString("endpoint", null) ?? clientElement.GetAttributeString("ip", "");
|
||||
string steamIdStr = clientElement.GetAttributeString("steamid", "");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(clientName))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in " + ClientPermissionsFile + " - all clients must have a name and an IP address.");
|
||||
continue;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(clientEndPoint) && string.IsNullOrWhiteSpace(steamIdStr))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in " + ClientPermissionsFile + " - all clients must have an IP address or a Steam ID.");
|
||||
continue;
|
||||
}
|
||||
|
||||
ClientPermissions permissions = Networking.ClientPermissions.None;
|
||||
List<DebugConsole.Command> permittedCommands = new List<DebugConsole.Command>();
|
||||
|
||||
if (clientElement.Attribute("preset") == null)
|
||||
{
|
||||
string permissionsStr = clientElement.GetAttributeString("permissions", "");
|
||||
if (permissionsStr.Equals("all", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions)))
|
||||
{
|
||||
permissions |= permission;
|
||||
}
|
||||
}
|
||||
else if (!Enum.TryParse(permissionsStr, out permissions))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in " + ClientPermissionsFile + " - \"" + permissionsStr + "\" is not a valid client permission.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (permissions.HasFlag(Networking.ClientPermissions.ConsoleCommands))
|
||||
{
|
||||
foreach (XElement commandElement in clientElement.Elements())
|
||||
{
|
||||
if (!commandElement.Name.ToString().Equals("command", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
|
||||
string commandName = commandElement.GetAttributeString("name", "");
|
||||
DebugConsole.Command command = DebugConsole.FindCommand(commandName);
|
||||
if (command == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in " + ClientPermissionsFile + " - \"" + commandName + "\" is not a valid console command.");
|
||||
continue;
|
||||
}
|
||||
|
||||
permittedCommands.Add(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string presetName = clientElement.GetAttributeString("preset", "");
|
||||
PermissionPreset preset = PermissionPreset.List.Find(p => p.Name == presetName);
|
||||
if (preset == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to restore saved permissions to the client \"" + clientName + "\". Permission preset \"" + presetName + "\" not found.");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
permissions = preset.Permissions;
|
||||
permittedCommands = preset.PermittedCommands.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(steamIdStr))
|
||||
{
|
||||
if (ulong.TryParse(steamIdStr, out ulong steamID))
|
||||
{
|
||||
ClientPermissions.Add(new SavedClientPermission(clientName, steamID, permissions, permittedCommands));
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Error in " + ClientPermissionsFile + " - \"" + steamIdStr + "\" is not a valid Steam ID.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ClientPermissions.Add(new SavedClientPermission(clientName, clientEndPoint, permissions, permittedCommands));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method for loading old .txt client permission files to provide backwards compatibility
|
||||
/// </summary>
|
||||
private void LoadClientPermissionsOld(string file)
|
||||
{
|
||||
if (!File.Exists(file)) return;
|
||||
|
||||
string[] lines;
|
||||
try
|
||||
{
|
||||
lines = File.ReadAllLines(file);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to open client permission file " + ClientPermissionsFile, e);
|
||||
return;
|
||||
}
|
||||
|
||||
ClientPermissions.Clear();
|
||||
|
||||
foreach (string line in lines)
|
||||
{
|
||||
string[] separatedLine = line.Split('|');
|
||||
if (separatedLine.Length < 3) continue;
|
||||
|
||||
string name = string.Join("|", separatedLine.Take(separatedLine.Length - 2));
|
||||
string ip = separatedLine[separatedLine.Length - 2];
|
||||
|
||||
ClientPermissions permissions = Networking.ClientPermissions.None;
|
||||
if (Enum.TryParse(separatedLine.Last(), out permissions))
|
||||
{
|
||||
ClientPermissions.Add(new SavedClientPermission(name, ip, permissions, new List<DebugConsole.Command>()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveClientPermissions()
|
||||
{
|
||||
//delete old client permission file
|
||||
if (File.Exists("Data/clientpermissions.txt"))
|
||||
{
|
||||
File.Delete("Data/clientpermissions.txt");
|
||||
}
|
||||
|
||||
GameServer.Log("Saving client permissions", ServerLog.MessageType.ServerMessage);
|
||||
|
||||
XDocument doc = new XDocument(new XElement("ClientPermissions"));
|
||||
|
||||
foreach (SavedClientPermission clientPermission in ClientPermissions)
|
||||
{
|
||||
var matchingPreset = PermissionPreset.List.Find(p => p.MatchesPermissions(clientPermission.Permissions, clientPermission.PermittedCommands));
|
||||
if (matchingPreset != null && matchingPreset.Name == "None")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
XElement clientElement = new XElement("Client",
|
||||
new XAttribute("name", clientPermission.Name));
|
||||
|
||||
if (clientPermission.SteamID > 0)
|
||||
{
|
||||
clientElement.Add(new XAttribute("steamid", clientPermission.SteamID));
|
||||
}
|
||||
else
|
||||
{
|
||||
clientElement.Add(new XAttribute("endpoint", clientPermission.EndPoint));
|
||||
}
|
||||
|
||||
if (matchingPreset == null)
|
||||
{
|
||||
clientElement.Add(new XAttribute("permissions", clientPermission.Permissions.ToString()));
|
||||
if (clientPermission.Permissions.HasFlag(Networking.ClientPermissions.ConsoleCommands))
|
||||
{
|
||||
foreach (DebugConsole.Command command in clientPermission.PermittedCommands)
|
||||
{
|
||||
clientElement.Add(new XElement("command", new XAttribute("name", command.names[0])));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
clientElement.Add(new XAttribute("preset", matchingPreset.Name));
|
||||
}
|
||||
doc.Root.Add(clientElement);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
XmlWriterSettings settings = new XmlWriterSettings();
|
||||
settings.Indent = true;
|
||||
settings.NewLineOnAttributes = true;
|
||||
|
||||
using (var writer = XmlWriter.Create(ClientPermissionsFile, settings))
|
||||
{
|
||||
doc.Save(writer);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Saving client permissions to " + ClientPermissionsFile + " failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Steam
|
||||
{
|
||||
partial class SteamManager
|
||||
{
|
||||
#region Server
|
||||
|
||||
private static void InitializeProjectSpecific() { isInitialized = true; }
|
||||
|
||||
private static void UpdateProjectSpecific(float deltaTime) { }
|
||||
|
||||
public static bool CreateServer(Networking.GameServer server, bool isPublic)
|
||||
{
|
||||
isInitialized = true;
|
||||
|
||||
Steamworks.SteamServerInit options = new Steamworks.SteamServerInit("Barotrauma", "Barotrauma")
|
||||
{
|
||||
GamePort = (ushort)server.Port,
|
||||
QueryPort = (ushort)server.QueryPort,
|
||||
Secure = false
|
||||
};
|
||||
//options.QueryShareGamePort();
|
||||
|
||||
Steamworks.SteamServer.Init(AppID, options, false);
|
||||
if (!Steamworks.SteamServer.IsValid)
|
||||
{
|
||||
Steamworks.SteamServer.Shutdown();
|
||||
DebugConsole.ThrowError("Initializing Steam server failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
RefreshServerDetails(server);
|
||||
|
||||
server.ServerPeer.InitializeSteamServerCallbacks();
|
||||
|
||||
Steamworks.SteamServer.LogOnAnonymous();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool RefreshServerDetails(Networking.GameServer server)
|
||||
{
|
||||
if (!isInitialized || !Steamworks.SteamServer.IsValid)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var contentPackages = GameMain.Config.SelectedContentPackages.Where(cp => cp.HasMultiplayerIncompatibleContent);
|
||||
|
||||
// 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.
|
||||
Steamworks.SteamServer.ServerName = server.ServerName;
|
||||
Steamworks.SteamServer.MaxPlayers = server.ServerSettings.MaxPlayers;
|
||||
Steamworks.SteamServer.Passworded = server.ServerSettings.HasPassword;
|
||||
Steamworks.SteamServer.MapName = GameMain.NetLobbyScreen?.SelectedSub?.DisplayName ?? "";
|
||||
Steamworks.SteamServer.SetKey("message", GameMain.Server.ServerSettings.ServerMessageText);
|
||||
Steamworks.SteamServer.SetKey("version", GameMain.Version.ToString());
|
||||
Steamworks.SteamServer.SetKey("playercount", GameMain.Server.ConnectedClients.Count.ToString());
|
||||
Steamworks.SteamServer.SetKey("contentpackage", string.Join(",", contentPackages.Select(cp => cp.Name)));
|
||||
Steamworks.SteamServer.SetKey("contentpackagehash", string.Join(",", contentPackages.Select(cp => cp.MD5hash.Hash)));
|
||||
Steamworks.SteamServer.SetKey("contentpackageurl", string.Join(",", contentPackages.Select(cp => cp.SteamWorkshopUrl ?? "")));
|
||||
Steamworks.SteamServer.SetKey("usingwhitelist", (server.ServerSettings.Whitelist != null && server.ServerSettings.Whitelist.Enabled).ToString());
|
||||
Steamworks.SteamServer.SetKey("modeselectionmode", server.ServerSettings.ModeSelectionMode.ToString());
|
||||
Steamworks.SteamServer.SetKey("subselectionmode", server.ServerSettings.SubSelectionMode.ToString());
|
||||
Steamworks.SteamServer.SetKey("voicechatenabled", server.ServerSettings.VoiceChatEnabled.ToString());
|
||||
Steamworks.SteamServer.SetKey("allowspectating", server.ServerSettings.AllowSpectating.ToString());
|
||||
Steamworks.SteamServer.SetKey("allowrespawn", server.ServerSettings.AllowRespawn.ToString());
|
||||
Steamworks.SteamServer.SetKey("traitors", server.ServerSettings.TraitorsEnabled.ToString());
|
||||
Steamworks.SteamServer.SetKey("gamestarted", server.GameStarted.ToString());
|
||||
Steamworks.SteamServer.SetKey("gamemode", server.ServerSettings.GameModeIdentifier);
|
||||
|
||||
Steamworks.SteamServer.DedicatedServer = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Steamworks.BeginAuthResult StartAuthSession(byte[] authTicketData, ulong clientSteamID)
|
||||
{
|
||||
if (!isInitialized || !Steamworks.SteamServer.IsValid) return Steamworks.BeginAuthResult.ServerNotConnectedToSteam;
|
||||
|
||||
DebugConsole.Log("SteamManager authenticating Steam client " + clientSteamID);
|
||||
Steamworks.BeginAuthResult startResult = Steamworks.SteamServer.BeginAuthSession(authTicketData, clientSteamID);
|
||||
if (startResult != Steamworks.BeginAuthResult.OK)
|
||||
{
|
||||
DebugConsole.Log("Authentication failed: failed to start auth session (" + startResult.ToString() + ")");
|
||||
}
|
||||
|
||||
return startResult;
|
||||
}
|
||||
|
||||
public static void StopAuthSession(ulong clientSteamID)
|
||||
{
|
||||
if (!isInitialized || !Steamworks.SteamServer.IsValid) return;
|
||||
|
||||
DebugConsole.Log("SteamManager ending auth session with Steam client " + clientSteamID);
|
||||
Steamworks.SteamServer.EndSession(clientSteamID);
|
||||
}
|
||||
|
||||
public static bool CloseServer()
|
||||
{
|
||||
if (!isInitialized || !Steamworks.SteamServer.IsValid) return false;
|
||||
|
||||
Steamworks.SteamServer.Shutdown();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class VoipServer
|
||||
{
|
||||
private ServerPeer netServer;
|
||||
private List<VoipQueue> queues;
|
||||
private Dictionary<VoipQueue,DateTime> lastSendTime;
|
||||
|
||||
public VoipServer(ServerPeer 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; }
|
||||
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
|
||||
msg.Write((byte)ServerPacketHeader.VOICE);
|
||||
msg.Write((byte)queue.QueueID);
|
||||
queue.Write(msg);
|
||||
|
||||
netServer.Send(msg, recipient.Connection, DeliveryMethod.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;
|
||||
|
||||
//TODO: only allow spectators to hear the voice chat if close enough to the speaker?
|
||||
|
||||
//non-spectators cannot hear spectators
|
||||
if (senderSpectating && !recipientSpectating) { return false; }
|
||||
|
||||
//both spectating, no need to do radio/distance checks
|
||||
if (recipientSpectating && senderSpectating) { return true; }
|
||||
|
||||
//spectators can hear non-spectators
|
||||
if (!senderSpectating && recipientSpectating) { 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Voting
|
||||
{
|
||||
public bool AllowSubVoting
|
||||
{
|
||||
get { return allowSubVoting; }
|
||||
set { allowSubVoting = value; }
|
||||
}
|
||||
public bool AllowModeVoting
|
||||
{
|
||||
get { return allowModeVoting; }
|
||||
set { allowModeVoting = value; }
|
||||
}
|
||||
|
||||
public void ServerRead(IReadMessage 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.Connection != GameMain.Server.OwnerConnection && !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(IWriteMessage 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(c => c.HasSpawned && c.GetVote<bool>(VoteType.EndRound)));
|
||||
msg.Write((byte)GameMain.Server.ConnectedClients.Count(c => c.HasSpawned));
|
||||
}
|
||||
|
||||
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,205 @@
|
||||
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)) { return; }
|
||||
|
||||
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);
|
||||
Int32.TryParse(lineval, out int 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.MapToIPv4NoThrow().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);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
public void ServerAdminWrite(IWriteMessage 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.WriteVariableUInt32((UInt32)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(IReadMessage incMsg, Client c)
|
||||
{
|
||||
if (!c.HasPermission(ClientPermissions.ManageSettings))
|
||||
{
|
||||
bool enabled = incMsg.ReadBoolean(); incMsg.ReadPadBits();
|
||||
UInt16 removeCount = incMsg.ReadUInt16();
|
||||
incMsg.BitPosition += 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);
|
||||
}
|
||||
|
||||
bool changed = removeCount > 0 || addCount > 0 || prevEnabled != enabled;
|
||||
if (changed) { Save(); }
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user