2f107db...5202af9
This commit is contained in:
@@ -1,47 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class BannedPlayer
|
||||
partial class BannedPlayer
|
||||
{
|
||||
public string Name;
|
||||
public string IP;
|
||||
public ulong SteamID;
|
||||
public string IP; public bool IsRangeBan;
|
||||
public UInt64 SteamID;
|
||||
public string Reason;
|
||||
public DateTime? ExpirationTime;
|
||||
|
||||
public bool CompareTo(string ipCompare)
|
||||
{
|
||||
int rangeBanIndex = IP.IndexOf(".x");
|
||||
if (rangeBanIndex <= -1)
|
||||
{
|
||||
return ipCompare == IP;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ipCompare.Length < rangeBanIndex) return false;
|
||||
return ipCompare.Substring(0, rangeBanIndex) == IP.Substring(0, rangeBanIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public BannedPlayer(string name, string ip, string reason, DateTime? expirationTime)
|
||||
{
|
||||
this.Name = name;
|
||||
this.IP = ip;
|
||||
this.Reason = reason;
|
||||
this.ExpirationTime = expirationTime;
|
||||
}
|
||||
|
||||
public BannedPlayer(string name, ulong steamID, string reason, DateTime? expirationTime)
|
||||
{
|
||||
this.Name = name;
|
||||
this.SteamID = steamID;
|
||||
this.Reason = reason;
|
||||
this.ExpirationTime = expirationTime;
|
||||
}
|
||||
public UInt16 UniqueIdentifier;
|
||||
}
|
||||
|
||||
partial class BanList
|
||||
@@ -60,149 +30,13 @@ namespace Barotrauma.Networking
|
||||
get { return bannedPlayers.Select(bp => bp.IP); }
|
||||
}
|
||||
|
||||
partial void InitProjectSpecific();
|
||||
|
||||
|
||||
public BanList()
|
||||
{
|
||||
bannedPlayers = new List<BannedPlayer>();
|
||||
|
||||
if (File.Exists(SavePath))
|
||||
{
|
||||
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 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 void BanPlayer(string name, string ip, string reason, TimeSpan? duration)
|
||||
{
|
||||
BanPlayer(name, ip, 0, reason, duration);
|
||||
}
|
||||
|
||||
public void BanPlayer(string name, ulong steamID, string reason, TimeSpan? duration)
|
||||
{
|
||||
BanPlayer(name, "", steamID, reason, duration);
|
||||
}
|
||||
|
||||
private void BanPlayer(string name, string ip, ulong steamID, string reason, TimeSpan? duration)
|
||||
{
|
||||
var existingBan = bannedPlayers.Find(bp => bp.IP == ip && bp.SteamID == steamID);
|
||||
if (existingBan != null)
|
||||
{
|
||||
if (!duration.HasValue) return;
|
||||
|
||||
DebugConsole.Log("Set \"" + name + "\"'s ban duration to " + duration.Value);
|
||||
existingBan.ExpirationTime = DateTime.Now + duration.Value;
|
||||
Save();
|
||||
return;
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.Assert(!name.Contains(','));
|
||||
|
||||
string logMsg = "Banned " + name;
|
||||
if (!string.IsNullOrEmpty(reason)) logMsg += ", reason: " + reason;
|
||||
if (duration.HasValue) logMsg += ", duration: " + duration.Value.ToString();
|
||||
|
||||
DebugConsole.Log(logMsg);
|
||||
|
||||
DateTime? expirationTime = null;
|
||||
if (duration.HasValue)
|
||||
{
|
||||
expirationTime = DateTime.Now + duration.Value;
|
||||
}
|
||||
|
||||
bannedPlayers.Add(new BannedPlayer(name, ip, reason, expirationTime));
|
||||
Save();
|
||||
}
|
||||
|
||||
public void UnbanPlayer(string name)
|
||||
{
|
||||
var player = bannedPlayers.Find(bp => bp.Name == name);
|
||||
if (player == null)
|
||||
{
|
||||
DebugConsole.Log("Could not unban player \"" + name + "\". Matching player not found.");
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.Log("Unbanned \"" + name + ".");
|
||||
bannedPlayers.Remove(player);
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
DebugConsole.Log("Unbanned \"" + ip + ".");
|
||||
bannedPlayers.Remove(player);
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsBanned(string IP, ulong steamID)
|
||||
{
|
||||
bannedPlayers.RemoveAll(bp => bp.ExpirationTime.HasValue && DateTime.Now > bp.ExpirationTime.Value);
|
||||
return bannedPlayers.Any(bp => bp.CompareTo(IP) || (steamID != 0 && bp.SteamID == steamID));
|
||||
}
|
||||
|
||||
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();
|
||||
InitProjectSpecific();
|
||||
}
|
||||
|
||||
public string ToRange(string ip)
|
||||
@@ -217,48 +51,5 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,15 +32,33 @@ namespace Barotrauma.Networking
|
||||
new Color(255, 255, 255), //messagebox
|
||||
new Color(255, 128, 0) //order
|
||||
};
|
||||
|
||||
|
||||
public readonly string Text;
|
||||
|
||||
private string translatedText;
|
||||
public string TranslatedText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Type.HasFlag(ChatMessageType.Server | ChatMessageType.Error))
|
||||
{
|
||||
return Text;
|
||||
}
|
||||
if (translatedText == null || translatedText.Length == 0)
|
||||
{
|
||||
translatedText = TextManager.GetServerMessage(Text);
|
||||
}
|
||||
|
||||
return translatedText;
|
||||
}
|
||||
}
|
||||
|
||||
public ChatMessageType Type;
|
||||
|
||||
public readonly Character Sender;
|
||||
|
||||
public readonly string SenderName;
|
||||
|
||||
|
||||
public Color Color
|
||||
{
|
||||
get { return MessageColor[(int)Type]; }
|
||||
@@ -48,8 +66,10 @@ namespace Barotrauma.Networking
|
||||
|
||||
public string TextWithSender
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
get
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(SenderName) ? TranslatedText : SenderName + ": " + TranslatedText;
|
||||
}
|
||||
}
|
||||
|
||||
public static UInt16 LastID = 0;
|
||||
@@ -68,8 +88,6 @@ namespace Barotrauma.Networking
|
||||
Sender = sender;
|
||||
|
||||
SenderName = senderName;
|
||||
|
||||
TextWithSender = string.IsNullOrWhiteSpace(senderName) ? text : senderName + ": " + text;
|
||||
}
|
||||
|
||||
public static ChatMessage Create(string senderName, string text, ChatMessageType type, Character sender)
|
||||
@@ -103,6 +121,19 @@ namespace Barotrauma.Networking
|
||||
return command;
|
||||
}
|
||||
|
||||
public static float GetGarbleAmount(Entity listener, Entity sender, float range, float obstructionmult = 2.0f)
|
||||
{
|
||||
if (listener.WorldPosition == sender.WorldPosition) { return 0.0f; }
|
||||
|
||||
float dist = Vector2.Distance(listener.WorldPosition, sender.WorldPosition);
|
||||
if (dist > range) { return 0.0f; }
|
||||
|
||||
if (Submarine.CheckVisibility(listener.SimPosition, sender.SimPosition) != null) dist = (dist + 100f) * obstructionmult;
|
||||
if (dist > range) { return 0.0f; }
|
||||
|
||||
return dist / range;
|
||||
}
|
||||
|
||||
public string ApplyDistanceEffect(Character listener)
|
||||
{
|
||||
if (Sender == null) return Text;
|
||||
@@ -110,17 +141,9 @@ namespace Barotrauma.Networking
|
||||
return ApplyDistanceEffect(listener, Sender, Text, SpeakRange);
|
||||
}
|
||||
|
||||
public static string ApplyDistanceEffect(Entity listener, Entity Sender, string text, float range, float obstructionmult = 2.0f)
|
||||
public static string ApplyDistanceEffect(Entity listener, Entity sender, string text, float range, float obstructionmult = 2.0f)
|
||||
{
|
||||
if (listener.WorldPosition == Sender.WorldPosition) return text;
|
||||
|
||||
float dist = Vector2.Distance(listener.WorldPosition, Sender.WorldPosition);
|
||||
if (dist > range) return "";
|
||||
|
||||
if (Submarine.CheckVisibility(listener.SimPosition, Sender.SimPosition) != null) dist = (dist + 100f) * obstructionmult;
|
||||
if (dist > range) return "";
|
||||
|
||||
return ApplyDistanceEffect(text, dist / range);
|
||||
return ApplyDistanceEffect(text, GetGarbleAmount(listener, sender, range, obstructionmult));
|
||||
}
|
||||
|
||||
public static string ApplyDistanceEffect(string text, float garbleAmount)
|
||||
@@ -182,138 +205,6 @@ namespace Barotrauma.Networking
|
||||
return message;
|
||||
}
|
||||
|
||||
public static void ServerRead(NetIncomingMessage msg, Client c)
|
||||
{
|
||||
c.KickAFKTimer = 0.0f;
|
||||
|
||||
UInt16 ID = msg.ReadUInt16();
|
||||
ChatMessageType type = (ChatMessageType)msg.ReadByte();
|
||||
string txt = "";
|
||||
|
||||
int orderIndex = -1;
|
||||
Character orderTargetCharacter = null;
|
||||
Entity orderTargetEntity = null;
|
||||
int orderOptionIndex = -1;
|
||||
OrderChatMessage orderMsg = null;
|
||||
if (type == ChatMessageType.Order)
|
||||
{
|
||||
orderIndex = msg.ReadByte();
|
||||
orderTargetCharacter = Entity.FindEntityByID(msg.ReadUInt16()) as Character;
|
||||
orderTargetEntity = Entity.FindEntityByID(msg.ReadUInt16()) as Entity;
|
||||
orderOptionIndex = msg.ReadByte();
|
||||
|
||||
if (orderIndex < 0 || orderIndex >= Order.PrefabList.Count)
|
||||
{
|
||||
DebugConsole.ThrowError("Invalid order message from client \"" + c.Name + "\" - order index out of bounds.");
|
||||
return;
|
||||
}
|
||||
|
||||
Order order = Order.PrefabList[orderIndex];
|
||||
string orderOption = orderOptionIndex < 0 || orderOptionIndex >= order.Options.Length ? "" : order.Options[orderOptionIndex];
|
||||
orderMsg = new OrderChatMessage(order, orderOption, orderTargetEntity, orderTargetCharacter, c.Character);
|
||||
txt = orderMsg.Text;
|
||||
}
|
||||
else
|
||||
{
|
||||
txt = msg.ReadString() ?? "";
|
||||
}
|
||||
|
||||
if (!NetIdUtils.IdMoreRecent(ID, c.LastSentChatMsgID)) return;
|
||||
|
||||
c.LastSentChatMsgID = ID;
|
||||
|
||||
if (txt.Length > MaxLength)
|
||||
{
|
||||
txt = txt.Substring(0, MaxLength);
|
||||
}
|
||||
|
||||
c.LastSentChatMessages.Add(txt);
|
||||
if (c.LastSentChatMessages.Count > 10)
|
||||
{
|
||||
c.LastSentChatMessages.RemoveRange(0, c.LastSentChatMessages.Count - 10);
|
||||
}
|
||||
|
||||
float similarity = 0.0f;
|
||||
//don't do message similarity checks on order messages
|
||||
if (orderMsg == null)
|
||||
{
|
||||
for (int i = 0; i < c.LastSentChatMessages.Count; i++)
|
||||
{
|
||||
float closeFactor = 1.0f / (c.LastSentChatMessages.Count - i);
|
||||
if (string.IsNullOrEmpty(txt))
|
||||
{
|
||||
similarity += closeFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
int levenshteinDist = ToolBox.LevenshteinDistance(txt, c.LastSentChatMessages[i]);
|
||||
similarity += Math.Max((txt.Length - levenshteinDist) / (float)txt.Length * closeFactor, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (similarity + c.ChatSpamSpeed > 5.0f)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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.SpeechImpediment >= 100.0f || c.Character.IsDead) return;
|
||||
|
||||
ChatMessageType messageType = CanUseRadio(orderMsg.Sender) ? ChatMessageType.Radio : ChatMessageType.Default;
|
||||
if (orderMsg.Order.TargetAllCharacters)
|
||||
{
|
||||
#if CLIENT
|
||||
//add the order to the crewmanager only if the host is not controlling a character
|
||||
//OR the character is close enough to hear it
|
||||
if (Character.Controlled == null ||
|
||||
!string.IsNullOrEmpty(ApplyDistanceEffect(orderMsg.Text, messageType, orderMsg.Sender, Character.Controlled)))
|
||||
{
|
||||
GameMain.GameSession?.CrewManager?.AddOrder(
|
||||
new Order(orderMsg.Order.Prefab, orderTargetEntity, (orderTargetEntity as Item)?.GetComponent<ItemComponent>()),
|
||||
orderMsg.Order.Prefab.FadeOutTime);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else if (orderTargetCharacter != null)
|
||||
{
|
||||
orderTargetCharacter.SetOrder(
|
||||
new Order(orderMsg.Order.Prefab, orderTargetEntity, (orderTargetEntity as Item)?.GetComponent<ItemComponent>()),
|
||||
orderMsg.OrderOption, orderMsg.Sender);
|
||||
}
|
||||
|
||||
GameMain.Server.SendOrderChatMessage(orderMsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.Server.SendChatMessage(txt, null, c);
|
||||
}
|
||||
}
|
||||
|
||||
public int EstimateLengthBytesClient()
|
||||
{
|
||||
int length = 1 + //(byte)ServerNetObject.CHAT_MESSAGE
|
||||
@@ -323,46 +214,19 @@ namespace Barotrauma.Networking
|
||||
return length;
|
||||
}
|
||||
|
||||
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 static bool CanUseRadio(Character sender)
|
||||
{
|
||||
if (sender == null) return false;
|
||||
var senderItem = sender.Inventory.Items.FirstOrDefault(i => i?.GetComponent<WifiComponent>() != null);
|
||||
return senderItem != null && sender.HasEquippedItem(senderItem) && senderItem.GetComponent<WifiComponent>().CanTransmit();
|
||||
return CanUseRadio(sender, out _);
|
||||
}
|
||||
|
||||
public virtual void ServerWrite(NetOutgoingMessage msg, Client c)
|
||||
public static bool CanUseRadio(Character sender, out WifiComponent radio)
|
||||
{
|
||||
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);
|
||||
}
|
||||
radio = null;
|
||||
if (sender == null) { return false; }
|
||||
var senderItem = sender.Inventory.Items.FirstOrDefault(i => i?.GetComponent<WifiComponent>() != null);
|
||||
if (senderItem == null) { return false; }
|
||||
radio = senderItem.GetComponent<WifiComponent>();
|
||||
return sender.HasEquippedItem(senderItem) && radio.CanTransmit();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,30 +5,12 @@ using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class Client
|
||||
partial class Client : IDisposable
|
||||
{
|
||||
public string Name;
|
||||
public byte ID;
|
||||
public ulong SteamID;
|
||||
|
||||
private float karma = 1.0f;
|
||||
public float Karma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (GameMain.Server == null) return 1.0f;
|
||||
if (!GameMain.Server.KarmaEnabled) return 1.0f;
|
||||
return karma;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (GameMain.Server == null) return;
|
||||
if (!GameMain.Server.KarmaEnabled) return;
|
||||
karma = Math.Min(Math.Max(value,0.0f),1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public byte TeamID = 0;
|
||||
|
||||
public Character.TeamType TeamID;
|
||||
|
||||
private Character character;
|
||||
public Character Character
|
||||
@@ -36,58 +18,59 @@ namespace Barotrauma.Networking
|
||||
get { return character; }
|
||||
set
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.NetworkMember.LastClientListUpdateID++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (value!=null)
|
||||
{
|
||||
DebugConsole.NewMessage(value.Name, Microsoft.Xna.Framework.Color.Yellow);
|
||||
}
|
||||
}
|
||||
character = value;
|
||||
if (character != null) HasSpawned = true;
|
||||
if (character != null)
|
||||
{
|
||||
HasSpawned = true;
|
||||
#if CLIENT
|
||||
GameMain.GameSession?.CrewManager?.SetPlayerVoiceIconState(this, muted, mutedLocally);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
public CharacterInfo CharacterInfo;
|
||||
public NetConnection Connection { get; set; }
|
||||
public bool InGame;
|
||||
|
||||
private bool muted;
|
||||
public bool Muted
|
||||
{
|
||||
get { return muted; }
|
||||
set
|
||||
{
|
||||
if (muted == value) { return; }
|
||||
muted = value;
|
||||
#if CLIENT
|
||||
GameMain.NetLobbyScreen.SetPlayerVoiceIconState(this, muted, mutedLocally);
|
||||
GameMain.GameSession?.CrewManager?.SetPlayerVoiceIconState(this, muted, mutedLocally);
|
||||
#endif
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.NetworkMember.LastClientListUpdateID++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public VoipQueue VoipQueue
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool InGame;
|
||||
public bool HasSpawned; //has the client spawned as a character during the current round
|
||||
|
||||
public UInt16 LastRecvGeneralUpdate = 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 readonly List<ChatMessage> ChatMsgQueue = new List<ChatMessage>();
|
||||
public UInt16 LastChatMsgQueueID;
|
||||
|
||||
//latest chat messages sent by this client
|
||||
public readonly List<string> LastSentChatMessages = new List<string>();
|
||||
public float ChatSpamSpeed;
|
||||
public float ChatSpamTimer;
|
||||
public int ChatSpamCount;
|
||||
|
||||
public float KickAFKTimer;
|
||||
|
||||
public double MidRoundSyncTimeOut;
|
||||
|
||||
public bool NeedsMidRoundSync;
|
||||
//how many unique events the client missed before joining the server
|
||||
public UInt16 UnreceivedEntityEventCount;
|
||||
public UInt16 FirstNewEventID;
|
||||
|
||||
private List<Client> kickVoters;
|
||||
|
||||
//when was a specific entity event last sent to the client
|
||||
// key = event id, value = NetTime.Now when sending
|
||||
public readonly Dictionary<UInt16, float> EntityEventLastSent = new Dictionary<UInt16, float>();
|
||||
|
||||
public readonly Queue<Entity> PendingPositionUpdates = new Queue<Entity>();
|
||||
|
||||
public bool ReadyToStart;
|
||||
|
||||
public List<JobPrefab> JobPreferences;
|
||||
public JobPrefab AssignedJob;
|
||||
|
||||
public float DeleteDisconnectedTimer;
|
||||
public HashSet<string> GivenAchievements = new HashSet<string>();
|
||||
|
||||
public HashSet<string> GivenAchievements = new HashSet<string>();
|
||||
|
||||
@@ -98,34 +81,21 @@ namespace Barotrauma.Networking
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool SpectateOnly;
|
||||
|
||||
private object[] votes;
|
||||
|
||||
public void InitClientSync()
|
||||
{
|
||||
LastSentChatMsgID = 0;
|
||||
LastRecvChatMsgID = ChatMessage.LastID;
|
||||
|
||||
LastRecvGeneralUpdate = 0;
|
||||
|
||||
LastRecvEntityEventID = 0;
|
||||
|
||||
UnreceivedEntityEventCount = 0;
|
||||
NeedsMidRoundSync = false;
|
||||
}
|
||||
|
||||
public int KickVoteCount
|
||||
{
|
||||
get { return kickVoters.Count; }
|
||||
}
|
||||
|
||||
public Client(NetPeer server, string name, byte ID)
|
||||
/*public Client(NetPeer server, string name, byte ID)
|
||||
: this(name, ID)
|
||||
{
|
||||
|
||||
}
|
||||
}*/
|
||||
|
||||
partial void InitProjSpecific();
|
||||
partial void DisposeProjSpecific();
|
||||
public Client(string name, byte ID)
|
||||
{
|
||||
this.Name = name;
|
||||
@@ -136,56 +106,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
votes = new object[Enum.GetNames(typeof(VoteType)).Length];
|
||||
|
||||
JobPreferences = new List<JobPrefab>(JobPrefab.List.GetRange(0, Math.Min(JobPrefab.List.Count, 3)));
|
||||
}
|
||||
|
||||
public static bool IsValidName(string name, GameServer server)
|
||||
{
|
||||
char[] disallowedChars = new char[] { ';', ',', '<', '>', '/', '\\', '[', ']', '"', '?' };
|
||||
if (name.Any(c => disallowedChars.Contains(c))) return false;
|
||||
|
||||
foreach (char character in name)
|
||||
{
|
||||
if (!server.AllowedClientNameChars.Any(charRange => (int)character >= charRange.First && (int)character <= charRange.Second)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string SanitizeName(string name)
|
||||
{
|
||||
name = name.Trim();
|
||||
if (name.Length > 20)
|
||||
{
|
||||
name = name.Substring(0, 20);
|
||||
}
|
||||
string rName = "";
|
||||
for (int i = 0; i < name.Length; i++)
|
||||
{
|
||||
rName += name[i] < 32 ? '?' : name[i];
|
||||
}
|
||||
return rName;
|
||||
}
|
||||
|
||||
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);
|
||||
InitProjSpecific();
|
||||
}
|
||||
|
||||
public T GetVote<T>(VoteType voteType)
|
||||
@@ -235,6 +156,60 @@ namespace Barotrauma.Networking
|
||||
client.kickVoters.RemoveAll(voter => !connectedClients.Contains(voter));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void WritePermissions(NetBuffer msg)
|
||||
{
|
||||
msg.Write(ID);
|
||||
msg.Write((UInt16)Permissions);
|
||||
if (HasPermission(ClientPermissions.ConsoleCommands))
|
||||
{
|
||||
msg.Write((UInt16)PermittedConsoleCommands.Count);
|
||||
foreach (DebugConsole.Command command in PermittedConsoleCommands)
|
||||
{
|
||||
msg.Write(command.names[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
public static void ReadPermissions(NetBuffer inc, out ClientPermissions permissions, out List<DebugConsole.Command> permittedCommands)
|
||||
{
|
||||
UInt16 permissionsInt = inc.ReadUInt16();
|
||||
|
||||
permissions = ClientPermissions.None;
|
||||
permittedCommands = new List<DebugConsole.Command>();
|
||||
try
|
||||
{
|
||||
permissions = (ClientPermissions)permissionsInt;
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (permissions.HasFlag(ClientPermissions.ConsoleCommands))
|
||||
{
|
||||
UInt16 commandCount = inc.ReadUInt16();
|
||||
for (int i = 0; i < commandCount; i++)
|
||||
{
|
||||
string commandName = inc.ReadString();
|
||||
var consoleCommand = DebugConsole.Commands.Find(c => c.names.Contains(commandName));
|
||||
if (consoleCommand != null)
|
||||
{
|
||||
permittedCommands.Add(consoleCommand);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ReadPermissions(NetIncomingMessage inc)
|
||||
{
|
||||
ClientPermissions permissions = ClientPermissions.None;
|
||||
List<DebugConsole.Command> permittedCommands = new List<DebugConsole.Command>();
|
||||
ReadPermissions(inc, out permissions, out permittedCommands);
|
||||
SetPermissions(permissions, permittedCommands);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DisposeProjSpecific();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,19 @@ namespace Barotrauma.Networking
|
||||
[Flags]
|
||||
enum ClientPermissions
|
||||
{
|
||||
None = 0,
|
||||
EndRound = 1,
|
||||
Kick = 2,
|
||||
Ban = 4,
|
||||
Unban = 8,
|
||||
SelectSub = 16,
|
||||
SelectMode = 32,
|
||||
ManageCampaign = 64,
|
||||
ConsoleCommands = 128,
|
||||
ServerLog = 256
|
||||
None = 0x0,
|
||||
ManageRound = 0x1,
|
||||
Kick = 0x2,
|
||||
Ban = 0x4,
|
||||
Unban = 0x8,
|
||||
SelectSub = 0x10,
|
||||
SelectMode = 0x20,
|
||||
ManageCampaign = 0x40,
|
||||
ConsoleCommands = 0x80,
|
||||
ServerLog = 0x100,
|
||||
ManageSettings = 0x200,
|
||||
ManagePermissions = 0x400,
|
||||
All = 0x7ff
|
||||
}
|
||||
|
||||
class PermissionPreset
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -26,14 +27,14 @@ namespace Barotrauma
|
||||
|
||||
public ItemSpawnInfo(ItemPrefab prefab, Vector2 worldPosition, float? condition = null)
|
||||
{
|
||||
Prefab = prefab;
|
||||
Prefab = prefab ?? throw new ArgumentException("ItemSpawnInfo prefab cannot be null.");
|
||||
Position = worldPosition;
|
||||
Condition = condition ?? prefab.Health;
|
||||
}
|
||||
|
||||
public ItemSpawnInfo(ItemPrefab prefab, Vector2 position, Submarine sub, float? condition = null)
|
||||
{
|
||||
Prefab = prefab;
|
||||
Prefab = prefab ?? throw new ArgumentException("ItemSpawnInfo prefab cannot be null.");
|
||||
Position = position;
|
||||
Submarine = sub;
|
||||
Condition = condition ?? prefab.Health;
|
||||
@@ -41,13 +42,17 @@ namespace Barotrauma
|
||||
|
||||
public ItemSpawnInfo(ItemPrefab prefab, Inventory inventory, float? condition = null)
|
||||
{
|
||||
Prefab = prefab;
|
||||
Prefab = prefab ?? throw new ArgumentException("ItemSpawnInfo prefab cannot be null.");
|
||||
Inventory = inventory;
|
||||
Condition = condition ?? prefab.Health;
|
||||
}
|
||||
|
||||
public Entity Spawn()
|
||||
{
|
||||
if (Prefab == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
Item spawnedItem = null;
|
||||
if (Inventory != null)
|
||||
{
|
||||
@@ -87,37 +92,57 @@ namespace Barotrauma
|
||||
|
||||
public void AddToSpawnQueue(ItemPrefab itemPrefab, Vector2 worldPosition, float? condition = null)
|
||||
{
|
||||
if (GameMain.Client != null) return;
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
string errorMsg = "Attempted to add a null item to entity spawn queue.\n" + Environment.StackTrace;
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue3:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
return;
|
||||
}
|
||||
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, worldPosition, condition));
|
||||
}
|
||||
|
||||
public void AddToSpawnQueue(ItemPrefab itemPrefab, Vector2 position, Submarine sub, float? condition = null)
|
||||
{
|
||||
if (GameMain.Client != null) return;
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
string errorMsg = "Attempted to add a null item to entity spawn queue.\n" + Environment.StackTrace;
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue3:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
return;
|
||||
}
|
||||
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, position, sub, condition));
|
||||
}
|
||||
|
||||
public void AddToSpawnQueue(ItemPrefab itemPrefab, Inventory inventory, float? condition = null)
|
||||
{
|
||||
if (GameMain.Client != null) return;
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
string errorMsg = "Attempted to add a null item to entity spawn queue.\n" + Environment.StackTrace;
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue3:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
return;
|
||||
}
|
||||
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, inventory, condition));
|
||||
}
|
||||
|
||||
public void AddToRemoveQueue(Entity entity)
|
||||
{
|
||||
if (GameMain.Client != null) return;
|
||||
if (removeQueue.Contains(entity) || entity.Removed) return;
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (removeQueue.Contains(entity) || entity.Removed || entity == null) { return; }
|
||||
if (entity is Character)
|
||||
{
|
||||
Character character = entity as Character;
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
Client client = GameMain.Server.ConnectedClients.Find(c => c.Character == character);
|
||||
if (client != null) GameMain.Server.SetClientCharacter(client, null);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
removeQueue.Enqueue(entity);
|
||||
@@ -125,8 +150,8 @@ namespace Barotrauma
|
||||
|
||||
public void AddToRemoveQueue(Item item)
|
||||
{
|
||||
if (GameMain.Client != null) return;
|
||||
if (removeQueue.Contains(item) || item.Removed) return;
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (removeQueue.Contains(item) || item.Removed) { return; }
|
||||
|
||||
removeQueue.Enqueue(item);
|
||||
if (item.ContainedItems == null) return;
|
||||
@@ -136,18 +161,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateNetworkEvent(Entity entity, bool remove)
|
||||
{
|
||||
if (GameMain.Server != null && entity != null)
|
||||
{
|
||||
GameMain.Server.CreateEntityEvent(this, new object[] { new SpawnOrRemove(entity, remove) });
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (GameMain.Client != null) return;
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
while (spawnQueue.Count > 0)
|
||||
{
|
||||
var entitySpawnInfo = spawnQueue.Dequeue();
|
||||
@@ -155,7 +171,9 @@ namespace Barotrauma
|
||||
var spawnedEntity = entitySpawnInfo.Spawn();
|
||||
if (spawnedEntity != null)
|
||||
{
|
||||
#if SERVER
|
||||
CreateNetworkEvent(spawnedEntity, false);
|
||||
#endif
|
||||
if (spawnedEntity is Item)
|
||||
{
|
||||
((Item)spawnedEntity).Condition = ((ItemSpawnInfo)entitySpawnInfo).Condition;
|
||||
@@ -167,10 +185,12 @@ namespace Barotrauma
|
||||
{
|
||||
var removedEntity = removeQueue.Dequeue();
|
||||
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
CreateNetworkEvent(removedEntity, true);
|
||||
}
|
||||
#endif
|
||||
|
||||
removedEntity.Remove();
|
||||
}
|
||||
@@ -181,33 +201,5 @@ namespace Barotrauma
|
||||
removeQueue.Clear();
|
||||
spawnQueue.Clear();
|
||||
}
|
||||
|
||||
public void ServerWrite(Lidgren.Network.NetBuffer message, Client client, object[] extraData = null)
|
||||
{
|
||||
if (GameMain.Server == null) return;
|
||||
|
||||
SpawnOrRemove entities = (SpawnOrRemove)extraData[0];
|
||||
|
||||
message.Write(entities.Remove);
|
||||
|
||||
if (entities.Remove)
|
||||
{
|
||||
message.Write(entities.Entity.ID);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (entities.Entity is Item)
|
||||
{
|
||||
message.Write((byte)SpawnableType.Item);
|
||||
((Item)entities.Entity).WriteSpawnData(message);
|
||||
}
|
||||
else if (entities.Entity is Character)
|
||||
{
|
||||
message.Write((byte)SpawnableType.Character);
|
||||
DebugConsole.NewMessage("WRITING CHARACTER DATA: " + (entities.Entity).ToString() + " (ID: " + entities.Entity.ID + ")", Color.Cyan);
|
||||
((Character)entities.Entity).WriteSpawnData(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,293 +0,0 @@
|
||||
using Lidgren.Network;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
enum FileTransferStatus
|
||||
{
|
||||
NotStarted, Sending, Receiving, Finished, Canceled, Error
|
||||
}
|
||||
|
||||
enum FileTransferMessageType
|
||||
{
|
||||
Unknown, Initiate, Data, Cancel
|
||||
}
|
||||
|
||||
enum FileTransferType
|
||||
{
|
||||
Submarine, CampaignSave
|
||||
}
|
||||
|
||||
class FileSender
|
||||
{
|
||||
public class FileTransferOut
|
||||
{
|
||||
private byte[] data;
|
||||
|
||||
private DateTime startingTime;
|
||||
|
||||
private NetConnection connection;
|
||||
|
||||
public FileTransferStatus Status;
|
||||
|
||||
public string FileName
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string FilePath
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public FileTransferType FileType
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public float Progress
|
||||
{
|
||||
get { return SentOffset / (float)Data.Length; }
|
||||
}
|
||||
|
||||
public float WaitTimer
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public byte[] Data
|
||||
{
|
||||
get { return data; }
|
||||
}
|
||||
|
||||
public int SentOffset
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public NetConnection Connection
|
||||
{
|
||||
get { return connection; }
|
||||
}
|
||||
|
||||
public int SequenceChannel;
|
||||
|
||||
public FileTransferOut(NetConnection recipient, FileTransferType fileType, string filePath)
|
||||
{
|
||||
connection = recipient;
|
||||
|
||||
FileType = fileType;
|
||||
FilePath = filePath;
|
||||
FileName = Path.GetFileName(filePath);
|
||||
|
||||
Status = FileTransferStatus.NotStarted;
|
||||
|
||||
startingTime = DateTime.Now;
|
||||
|
||||
data = File.ReadAllBytes(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
const int MaxTransferCount = 16;
|
||||
const int MaxTransferCountPerRecipient = 5;
|
||||
|
||||
public static TimeSpan MaxTransferDuration = new TimeSpan(0, 2, 0);
|
||||
|
||||
public delegate void FileTransferDelegate(FileTransferOut fileStreamReceiver);
|
||||
public FileTransferDelegate OnStarted;
|
||||
public FileTransferDelegate OnEnded;
|
||||
|
||||
private List<FileTransferOut> activeTransfers;
|
||||
|
||||
private int chunkLen;
|
||||
|
||||
private NetPeer peer;
|
||||
|
||||
public List<FileTransferOut> ActiveTransfers
|
||||
{
|
||||
get { return activeTransfers; }
|
||||
}
|
||||
|
||||
public FileSender(NetworkMember networkMember)
|
||||
{
|
||||
peer = networkMember.NetPeer;
|
||||
chunkLen = peer.Configuration.MaximumTransmissionUnit - 100;
|
||||
|
||||
activeTransfers = new List<FileTransferOut>();
|
||||
}
|
||||
|
||||
public FileTransferOut StartTransfer(NetConnection recipient, FileTransferType fileType, string filePath)
|
||||
{
|
||||
if (activeTransfers.Count >= MaxTransferCount)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (activeTransfers.Count(t => t.Connection == recipient) > MaxTransferCountPerRecipient)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to initiate file transfer (file \""+filePath+"\" not found.");
|
||||
return null;
|
||||
}
|
||||
|
||||
FileTransferOut transfer = null;
|
||||
try
|
||||
{
|
||||
transfer = new FileTransferOut(recipient, fileType, filePath);
|
||||
transfer.SequenceChannel = 1;
|
||||
while (activeTransfers.Any(t => t.Connection == recipient && t.SequenceChannel == transfer.SequenceChannel))
|
||||
{
|
||||
transfer.SequenceChannel++;
|
||||
}
|
||||
activeTransfers.Add(transfer);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to initiate file transfer", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
OnStarted(transfer);
|
||||
|
||||
return transfer;
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
activeTransfers.RemoveAll(t => t.Connection.Status != NetConnectionStatus.Connected);
|
||||
|
||||
var endedTransfers = activeTransfers.FindAll(t =>
|
||||
t.Connection.Status != NetConnectionStatus.Connected ||
|
||||
t.Status == FileTransferStatus.Finished ||
|
||||
t.Status == FileTransferStatus.Canceled ||
|
||||
t.Status == FileTransferStatus.Error);
|
||||
|
||||
foreach (FileTransferOut transfer in endedTransfers)
|
||||
{
|
||||
activeTransfers.Remove(transfer);
|
||||
OnEnded(transfer);
|
||||
}
|
||||
|
||||
foreach (FileTransferOut transfer in activeTransfers)
|
||||
{
|
||||
transfer.WaitTimer -= deltaTime;
|
||||
if (transfer.WaitTimer > 0.0f) continue;
|
||||
|
||||
if (!transfer.Connection.CanSendImmediately(NetDeliveryMethod.ReliableOrdered, 1)) continue;
|
||||
|
||||
transfer.WaitTimer = transfer.Connection.AverageRoundtripTime;
|
||||
|
||||
// send another part of the file
|
||||
long remaining = transfer.Data.Length - transfer.SentOffset;
|
||||
int sendByteCount = (remaining > chunkLen ? chunkLen : (int)remaining);
|
||||
|
||||
NetOutgoingMessage message;
|
||||
|
||||
//first message; send length, chunk length, file name etc
|
||||
if (transfer.SentOffset == 0)
|
||||
{
|
||||
message = peer.CreateMessage();
|
||||
message.Write((byte)ServerPacketHeader.FILE_TRANSFER);
|
||||
message.Write((byte)FileTransferMessageType.Initiate);
|
||||
message.Write((byte)transfer.FileType);
|
||||
message.Write((ushort)chunkLen);
|
||||
message.Write((ulong)transfer.Data.Length);
|
||||
message.Write(transfer.FileName);
|
||||
GameMain.Server.CompressOutgoingMessage(message);
|
||||
transfer.Connection.SendMessage(message, NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel);
|
||||
|
||||
transfer.Status = FileTransferStatus.Sending;
|
||||
|
||||
if (GameSettings.VerboseLogging)
|
||||
{
|
||||
DebugConsole.Log("Sending file transfer initiation message: ");
|
||||
DebugConsole.Log(" File: " + transfer.FileName);
|
||||
DebugConsole.Log(" Size: " + transfer.Data.Length);
|
||||
DebugConsole.Log(" Sequence channel: " + transfer.SequenceChannel);
|
||||
}
|
||||
}
|
||||
|
||||
message = peer.CreateMessage(1 + 1 + sendByteCount);
|
||||
message.Write((byte)ServerPacketHeader.FILE_TRANSFER);
|
||||
message.Write((byte)FileTransferMessageType.Data);
|
||||
|
||||
byte[] sendBytes = new byte[sendByteCount];
|
||||
Array.Copy(transfer.Data, transfer.SentOffset, sendBytes, 0, sendByteCount);
|
||||
|
||||
message.Write(sendBytes);
|
||||
|
||||
GameMain.Server.CompressOutgoingMessage(message);
|
||||
transfer.Connection.SendMessage(message, NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel);
|
||||
transfer.SentOffset += sendByteCount;
|
||||
|
||||
if (GameSettings.VerboseLogging)
|
||||
{
|
||||
DebugConsole.Log("Sending " + sendByteCount + " bytes of the file " + transfer.FileName + " (" + transfer.SentOffset + "/" + transfer.Data.Length + " sent)");
|
||||
}
|
||||
|
||||
if (remaining - sendByteCount <= 0)
|
||||
{
|
||||
transfer.Status = FileTransferStatus.Finished;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CancelTransfer(FileTransferOut transfer)
|
||||
{
|
||||
transfer.Status = FileTransferStatus.Canceled;
|
||||
activeTransfers.Remove(transfer);
|
||||
|
||||
OnEnded(transfer);
|
||||
|
||||
GameMain.Server.SendCancelTransferMsg(transfer);
|
||||
}
|
||||
|
||||
public void ReadFileRequest(NetIncomingMessage inc)
|
||||
{
|
||||
byte messageType = inc.ReadByte();
|
||||
|
||||
if (messageType == (byte)FileTransferMessageType.Cancel)
|
||||
{
|
||||
byte sequenceChannel = inc.ReadByte();
|
||||
var matchingTransfer = activeTransfers.Find(t => t.Connection == inc.SenderConnection && t.SequenceChannel == sequenceChannel);
|
||||
if (matchingTransfer != null) CancelTransfer(matchingTransfer);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
byte fileType = inc.ReadByte();
|
||||
switch (fileType)
|
||||
{
|
||||
case (byte)FileTransferType.Submarine:
|
||||
string fileName = inc.ReadString();
|
||||
string fileHash = inc.ReadString();
|
||||
var requestedSubmarine = Submarine.SavedSubmarines.FirstOrDefault(s => s.Name == fileName && s.MD5Hash.Hash == fileHash);
|
||||
|
||||
if (requestedSubmarine != null)
|
||||
{
|
||||
StartTransfer(inc.SenderConnection, FileTransferType.Submarine, requestedSubmarine.FilePath);
|
||||
}
|
||||
break;
|
||||
case (byte)FileTransferType.CampaignSave:
|
||||
if (GameMain.GameSession != null)
|
||||
{
|
||||
StartTransfer(inc.SenderConnection, FileTransferType.CampaignSave, GameMain.GameSession.SavePath);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
enum FileTransferStatus
|
||||
{
|
||||
NotStarted, Sending, Receiving, Finished, Canceled, Error
|
||||
}
|
||||
|
||||
enum FileTransferMessageType
|
||||
{
|
||||
Unknown, Initiate, Data, Cancel
|
||||
}
|
||||
|
||||
enum FileTransferType
|
||||
{
|
||||
Submarine, CampaignSave
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,462 +0,0 @@
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class UnauthenticatedClient
|
||||
{
|
||||
public readonly NetConnection Connection;
|
||||
public readonly ulong SteamID;
|
||||
public Facepunch.Steamworks.ServerAuth.Status? SteamAuthStatus = null;
|
||||
public readonly int Nonce;
|
||||
|
||||
public int FailedAttempts;
|
||||
|
||||
public float AuthTimer;
|
||||
|
||||
public UnauthenticatedClient(NetConnection connection, int nonce, ulong steamID = 0)
|
||||
{
|
||||
Connection = connection;
|
||||
SteamID = steamID;
|
||||
Nonce = nonce;
|
||||
AuthTimer = 10.0f;
|
||||
FailedAttempts = 0;
|
||||
}
|
||||
}
|
||||
|
||||
partial class GameServer : NetworkMember, ISerializableEntity
|
||||
{
|
||||
List<UnauthenticatedClient> unauthenticatedClients = new List<UnauthenticatedClient>();
|
||||
|
||||
private void ReadClientSteamAuthRequest(NetIncomingMessage inc, out ulong clientSteamID)
|
||||
{
|
||||
clientSteamID = 0;
|
||||
if (!Steam.SteamManager.USE_STEAM)
|
||||
{
|
||||
//not using steam, handle auth normally
|
||||
HandleClientAuthRequest(inc.SenderConnection, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
clientSteamID = inc.ReadUInt64();
|
||||
int authTicketLength = inc.ReadInt32();
|
||||
inc.ReadBytes(authTicketLength, out byte[] authTicketData);
|
||||
|
||||
DebugConsole.Log("Received a Steam auth request");
|
||||
DebugConsole.Log(" Steam ID: "+ clientSteamID);
|
||||
DebugConsole.Log(" Auth ticket length: " + authTicketLength);
|
||||
DebugConsole.Log(" Auth ticket data: " +
|
||||
((authTicketData == null) ? "null" : ToolBox.LimitString(string.Concat(authTicketData.Select(b => b.ToString("X2"))), 16)));
|
||||
|
||||
if (banList.IsBanned("", clientSteamID))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ulong steamID = clientSteamID;
|
||||
if (unauthenticatedClients.Any(uc => uc.Connection == inc.SenderConnection))
|
||||
{
|
||||
var steamAuthedClient = unauthenticatedClients.Find(uc =>
|
||||
uc.Connection == inc.SenderConnection &&
|
||||
uc.SteamID == steamID &&
|
||||
uc.SteamAuthStatus == Facepunch.Steamworks.ServerAuth.Status.OK);
|
||||
if (steamAuthedClient != null)
|
||||
{
|
||||
DebugConsole.Log("Client already authenticated, sending AUTH_RESPONSE again...");
|
||||
HandleClientAuthRequest(inc.SenderConnection, steamID);
|
||||
}
|
||||
DebugConsole.Log("Steam authentication already pending...");
|
||||
return;
|
||||
}
|
||||
|
||||
if (authTicketData == null)
|
||||
{
|
||||
DebugConsole.Log("Invalid request");
|
||||
return;
|
||||
}
|
||||
|
||||
unauthenticatedClients.RemoveAll(uc => uc.Connection == inc.SenderConnection);
|
||||
var unauthClient = new UnauthenticatedClient(inc.SenderConnection, 0, clientSteamID)
|
||||
{
|
||||
AuthTimer = 20
|
||||
};
|
||||
unauthenticatedClients.Add(unauthClient);
|
||||
|
||||
if (!Steam.SteamManager.StartAuthSession(authTicketData, clientSteamID))
|
||||
{
|
||||
unauthenticatedClients.Remove(unauthClient);
|
||||
if (GameMain.Config.RequireSteamAuthentication)
|
||||
{
|
||||
unauthClient.Connection.Disconnect(DisconnectReason.SteamAuthenticationFailed.ToString());
|
||||
Log("Disconnected unauthenticated client (Steam ID: " + steamID + "). Steam authentication failed.", ServerLog.MessageType.ServerMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.Log("Steam authentication failed, skipping to basic auth...");
|
||||
HandleClientAuthRequest(inc.SenderConnection);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public void OnAuthChange(ulong steamID, ulong ownerID, Facepunch.Steamworks.ServerAuth.Status status)
|
||||
{
|
||||
DebugConsole.Log("************ OnAuthChange");
|
||||
DebugConsole.Log(" Steam ID: " + steamID);
|
||||
DebugConsole.Log(" Owner ID: " + ownerID);
|
||||
DebugConsole.Log(" Status: " + status);
|
||||
|
||||
UnauthenticatedClient unauthClient = unauthenticatedClients.Find(uc => uc.SteamID == ownerID);
|
||||
if (unauthClient != null)
|
||||
{
|
||||
unauthClient.SteamAuthStatus = status;
|
||||
switch (status)
|
||||
{
|
||||
case Facepunch.Steamworks.ServerAuth.Status.OK:
|
||||
////steam authentication done, check password next
|
||||
Log("Successfully authenticated client via Steam (Steam ID: " + steamID + ").", ServerLog.MessageType.ServerMessage);
|
||||
HandleClientAuthRequest(unauthClient.Connection, unauthClient.SteamID);
|
||||
break;
|
||||
default:
|
||||
unauthenticatedClients.Remove(unauthClient);
|
||||
if (GameMain.Config.RequireSteamAuthentication)
|
||||
{
|
||||
Log("Disconnected unauthenticated client (Steam ID: " + steamID + "). Steam authentication failed, (" + status + ").", ServerLog.MessageType.ServerMessage);
|
||||
unauthClient.Connection.Disconnect(DisconnectReason.SteamAuthenticationFailed.ToString() + "; (" + status.ToString() + ")");
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.Log("Steam authentication failed (" + status.ToString() + "), skipping to basic auth...");
|
||||
HandleClientAuthRequest(unauthClient.Connection);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.Log(" No unauthenticated clients found with the Steam ID " + steamID);
|
||||
}
|
||||
|
||||
//kick connected client if status becomes invalid (e.g. VAC banned, not connected to steam)
|
||||
if (status != Facepunch.Steamworks.ServerAuth.Status.OK && GameMain.Config.RequireSteamAuthentication)
|
||||
{
|
||||
var connectedClient = connectedClients.Find(c => c.SteamID == ownerID);
|
||||
if (connectedClient != null)
|
||||
{
|
||||
Log("Disconnecting client " + connectedClient.Name + " (Steam ID: " + steamID + "). Steam authentication no longer valid (" + status + ").", ServerLog.MessageType.ServerMessage);
|
||||
KickClient(connectedClient, TextManager.Get("DisconnectMessage.SteamAuthNoLongerValid").Replace("[status]", status.ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleClientAuthRequest(NetConnection connection, ulong steamID = 0)
|
||||
{
|
||||
DebugConsole.Log("HandleClientAuthRequest (steamID " + steamID + ")");
|
||||
|
||||
if (GameMain.Config.RequireSteamAuthentication && steamID == 0)
|
||||
{
|
||||
connection.Disconnect(DisconnectReason.SteamAuthenticationRequired.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
//client wants to know if server requires password
|
||||
if (ConnectedClients.Find(c => c.Connection == connection) != null)
|
||||
{
|
||||
//this client has already been authenticated
|
||||
return;
|
||||
}
|
||||
|
||||
UnauthenticatedClient unauthClient = unauthenticatedClients.Find(uc => uc.Connection == connection);
|
||||
if (unauthClient == null)
|
||||
{
|
||||
DebugConsole.Log("Unauthed client, generating a nonce...");
|
||||
//new client, generate nonce and add to unauth queue
|
||||
if (ConnectedClients.Count >= maxPlayers)
|
||||
{
|
||||
//server is full, can't allow new connection
|
||||
connection.Disconnect(DisconnectReason.ServerFull.ToString());
|
||||
if (steamID > 0) { Steam.SteamManager.StopAuthSession(steamID); }
|
||||
return;
|
||||
}
|
||||
|
||||
int nonce = CryptoRandom.Instance.Next();
|
||||
unauthClient = new UnauthenticatedClient(connection, nonce, steamID);
|
||||
unauthenticatedClients.Add(unauthClient);
|
||||
}
|
||||
unauthClient.AuthTimer = 10.0f;
|
||||
//if the client is already in the queue, getting another unauth request means that our response was lost; resend
|
||||
NetOutgoingMessage nonceMsg = server.CreateMessage();
|
||||
nonceMsg.Write((byte)ServerPacketHeader.AUTH_RESPONSE);
|
||||
if (string.IsNullOrEmpty(password))
|
||||
{
|
||||
nonceMsg.Write(false); //false = no password
|
||||
}
|
||||
else
|
||||
{
|
||||
nonceMsg.Write(true); //true = password
|
||||
nonceMsg.Write((Int32)unauthClient.Nonce); //here's nonce, encrypt with this
|
||||
}
|
||||
CompressOutgoingMessage(nonceMsg);
|
||||
DebugConsole.Log("Sending auth response...");
|
||||
server.SendMessage(nonceMsg, connection, NetDeliveryMethod.Unreliable);
|
||||
}
|
||||
|
||||
private void ClientInitRequest(NetIncomingMessage inc)
|
||||
{
|
||||
DebugConsole.Log("Received client init request");
|
||||
if (ConnectedClients.Find(c => c.Connection == inc.SenderConnection) != null)
|
||||
{
|
||||
//this client was already authenticated
|
||||
//another init request means they didn't get any update packets yet
|
||||
DebugConsole.Log("Client already connected, ignoring...");
|
||||
return;
|
||||
}
|
||||
|
||||
UnauthenticatedClient unauthClient = unauthenticatedClients.Find(uc => uc.Connection == inc.SenderConnection);
|
||||
if (unauthClient == null)
|
||||
{
|
||||
//client did not ask for nonce first, can't authorize
|
||||
inc.SenderConnection.Disconnect(DisconnectReason.AuthenticationRequired.ToString());
|
||||
if (unauthClient.SteamID > 0) { Steam.SteamManager.StopAuthSession(unauthClient.SteamID); }
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(password))
|
||||
{
|
||||
//decrypt message and compare password
|
||||
string saltedPw = password;
|
||||
saltedPw = saltedPw + Convert.ToString(unauthClient.Nonce);
|
||||
saltedPw = Encoding.UTF8.GetString(NetUtility.ComputeSHAHash(Encoding.UTF8.GetBytes(saltedPw)));
|
||||
string clPw = inc.ReadString();
|
||||
if (clPw != saltedPw)
|
||||
{
|
||||
unauthClient.FailedAttempts++;
|
||||
if (unauthClient.FailedAttempts > 3)
|
||||
{
|
||||
//disconnect and ban after too many failed attempts
|
||||
banList.BanPlayer("Unnamed", unauthClient.Connection.RemoteEndPoint.Address.ToString(), TextManager.Get("DisconnectMessage.TooManyFailedLogins"), duration: null);
|
||||
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.TooManyFailedLogins, "");
|
||||
|
||||
Log(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " has been banned from the server (too many wrong passwords)", ServerLog.MessageType.Error);
|
||||
DebugConsole.NewMessage(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " has been banned from the server (too many wrong passwords)", Color.Red);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
//not disconnecting the player here, because they'll still use the same connection and nonce if they try logging in again
|
||||
NetOutgoingMessage reject = server.CreateMessage();
|
||||
reject.Write((byte)ServerPacketHeader.AUTH_FAILURE);
|
||||
reject.Write("Wrong password! You have " + Convert.ToString(4 - unauthClient.FailedAttempts) + " more attempts before you're banned from the server.");
|
||||
Log(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " failed to join the server (incorrect password)", ServerLog.MessageType.Error);
|
||||
DebugConsole.NewMessage(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " failed to join the server (incorrect password)", Color.Red);
|
||||
CompressOutgoingMessage(reject);
|
||||
server.SendMessage(reject, unauthClient.Connection, NetDeliveryMethod.Unreliable);
|
||||
unauthClient.AuthTimer = 10.0f;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
string clVersion = inc.ReadString();
|
||||
|
||||
UInt16 contentPackageCount = inc.ReadUInt16();
|
||||
List<string> contentPackageNames = new List<string>();
|
||||
List<string> contentPackageHashes = new List<string>();
|
||||
for (int i = 0; i < contentPackageCount; i++)
|
||||
{
|
||||
string packageName = inc.ReadString();
|
||||
string packageHash = inc.ReadString();
|
||||
contentPackageNames.Add(packageName);
|
||||
contentPackageHashes.Add(packageHash);
|
||||
if (contentPackageCount == 0)
|
||||
{
|
||||
DebugConsole.Log("Client is using content package " +
|
||||
(packageName ?? "null") + " (" + (packageHash ?? "null" + ")"));
|
||||
}
|
||||
}
|
||||
|
||||
if (contentPackageCount == 0)
|
||||
{
|
||||
DebugConsole.Log("Client did not list any content packages.");
|
||||
}
|
||||
|
||||
string clName = Client.SanitizeName(inc.ReadString());
|
||||
if (string.IsNullOrWhiteSpace(clName))
|
||||
{
|
||||
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.NoName, "");
|
||||
|
||||
Log(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " couldn't join the server (no name given)", ServerLog.MessageType.Error);
|
||||
DebugConsole.NewMessage(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " couldn't join the server (no name given)", Color.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
if (clVersion != GameMain.Version.ToString())
|
||||
{
|
||||
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.InvalidVersion,
|
||||
TextManager.Get("DisconnectMessage.InvalidVersion").Replace("[version]", GameMain.Version.ToString()).Replace("[clientversion]", clVersion));
|
||||
|
||||
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (wrong game version)", ServerLog.MessageType.Error);
|
||||
DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (wrong game version)", Color.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
//check if the client is missing any of the content packages the server requires
|
||||
List<ContentPackage> missingPackages = new List<ContentPackage>();
|
||||
foreach (ContentPackage contentPackage in GameMain.SelectedPackages)
|
||||
{
|
||||
if (!contentPackage.HasMultiplayerIncompatibleContent) continue;
|
||||
bool packageFound = false;
|
||||
for (int i = 0; i < contentPackageCount; i++)
|
||||
{
|
||||
if (contentPackageNames[i] == contentPackage.Name && contentPackageHashes[i] == contentPackage.MD5hash.Hash)
|
||||
{
|
||||
packageFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!packageFound) missingPackages.Add(contentPackage);
|
||||
}
|
||||
|
||||
if (missingPackages.Count == 1)
|
||||
{
|
||||
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.MissingContentPackage, TextManager.Get("DisconnectMessage.MissingContentPackage").Replace("[missingcontentpackage]", GetPackageStr(missingPackages[0])));
|
||||
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") 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)));
|
||||
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.MissingContentPackage, TextManager.Get("DisconnectMessage.MissingContentPackages").Replace("[missingcontentpackages]", string.Join(", ", packageStrs)));
|
||||
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (missing content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
string GetPackageStr(ContentPackage contentPackage)
|
||||
{
|
||||
return "\"" + contentPackage.Name + "\" (hash " + contentPackage.MD5hash.ShortHash + ")";
|
||||
}
|
||||
|
||||
//check if the client is using any contentpackages that are not compatible with the server
|
||||
List<Pair<string, string>> incompatiblePackages = new List<Pair<string, string>>();
|
||||
for (int i = 0; i < contentPackageNames.Count; i++)
|
||||
{
|
||||
if (!GameMain.Config.SelectedContentPackages.Any(cp => cp.Name == contentPackageNames[i] && cp.MD5hash.Hash == contentPackageHashes[i]))
|
||||
{
|
||||
incompatiblePackages.Add(new Pair<string, string>(contentPackageNames[i], contentPackageHashes[i]));
|
||||
}
|
||||
}
|
||||
|
||||
if (incompatiblePackages.Count == 1)
|
||||
{
|
||||
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.IncompatibleContentPackage,
|
||||
TextManager.Get("DisconnectMessage.IncompatibleContentPackage").Replace("[incompatiblecontentpackage]", GetPackageStr2(incompatiblePackages[0])));
|
||||
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible content package " + GetPackageStr2(incompatiblePackages[0]) + ")", ServerLog.MessageType.Error);
|
||||
return;
|
||||
}
|
||||
else if (incompatiblePackages.Count > 1)
|
||||
{
|
||||
List<string> packageStrs = new List<string>();
|
||||
incompatiblePackages.ForEach(cp => packageStrs.Add(GetPackageStr2(cp)));
|
||||
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.IncompatibleContentPackage,
|
||||
TextManager.Get("DisconnectMessage.IncompatibleContentPackages").Replace("[incompatiblecontentpackages]", string.Join(", ", packageStrs)));
|
||||
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
string GetPackageStr2(Pair<string, string> nameAndHash)
|
||||
{
|
||||
return "\"" + nameAndHash.First + "\" (hash " + Md5Hash.GetShortHash(nameAndHash.Second) + ")";
|
||||
}
|
||||
|
||||
if (!whitelist.IsWhiteListed(clName, inc.SenderConnection.RemoteEndPoint.Address.ToString()))
|
||||
{
|
||||
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.NotOnWhitelist, "");
|
||||
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (not in whitelist)", ServerLog.MessageType.Error);
|
||||
DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (not in whitelist)", Color.Red);
|
||||
return;
|
||||
}
|
||||
if (!Client.IsValidName(clName, this))
|
||||
{
|
||||
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.InvalidName, "");
|
||||
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (invalid name)", ServerLog.MessageType.Error);
|
||||
DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (invalid name)", Color.Red);
|
||||
return;
|
||||
}
|
||||
if (Homoglyphs.Compare(clName.ToLower(),Name.ToLower()))
|
||||
{
|
||||
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.NameTaken, "");
|
||||
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (name taken by the server)", ServerLog.MessageType.Error);
|
||||
DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (name taken by the server)", Color.Red);
|
||||
return;
|
||||
}
|
||||
Client nameTaken = ConnectedClients.Find(c => Homoglyphs.Compare(c.Name.ToLower(), clName.ToLower()));
|
||||
if (nameTaken != null)
|
||||
{
|
||||
if (nameTaken.Connection.RemoteEndPoint.Address.ToString() == inc.SenderEndPoint.Address.ToString())
|
||||
{
|
||||
//both name and IP address match, replace this player's connection
|
||||
nameTaken.Connection.Disconnect(DisconnectReason.SessionTaken.ToString());
|
||||
nameTaken.Connection = unauthClient.Connection;
|
||||
nameTaken.InitClientSync(); //reinitialize sync ids because this is a new connection
|
||||
unauthenticatedClients.Remove(unauthClient);
|
||||
unauthClient = null;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
//can't authorize this client
|
||||
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.NameTaken, "");
|
||||
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (name already taken)", ServerLog.MessageType.Error);
|
||||
DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (name already taken)", Color.Red);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//new client
|
||||
Client newClient = new Client(clName, GetNewClientID());
|
||||
newClient.InitClientSync();
|
||||
newClient.Connection = unauthClient.Connection;
|
||||
newClient.SteamID = unauthClient.SteamID;
|
||||
unauthenticatedClients.Remove(unauthClient);
|
||||
unauthClient = null;
|
||||
ConnectedClients.Add(newClient);
|
||||
|
||||
#if CLIENT
|
||||
GameMain.NetLobbyScreen.AddPlayer(newClient.Name);
|
||||
#endif
|
||||
GameMain.Server.SendChatMessage(clName + " has joined the server.", ChatMessageType.Server, null);
|
||||
|
||||
var savedPermissions = clientPermissions.Find(cp =>
|
||||
cp.SteamID > 0 ?
|
||||
cp.SteamID == newClient.SteamID :
|
||||
cp.IP == newClient.Connection.RemoteEndPoint.Address.ToString());
|
||||
|
||||
if (savedPermissions != null)
|
||||
{
|
||||
newClient.SetPermissions(savedPermissions.Permissions, savedPermissions.PermittedCommands);
|
||||
}
|
||||
else
|
||||
{
|
||||
newClient.SetPermissions(ClientPermissions.None, new List<DebugConsole.Command>());
|
||||
}
|
||||
}
|
||||
|
||||
private void DisconnectUnauthClient(NetIncomingMessage inc, UnauthenticatedClient unauthClient, DisconnectReason reason, string message)
|
||||
{
|
||||
inc.SenderConnection.Disconnect(reason.ToString() + "; " + message);
|
||||
if (unauthClient.SteamID > 0) { Steam.SteamManager.StopAuthSession(unauthClient.SteamID); }
|
||||
if (unauthClient != null)
|
||||
{
|
||||
unauthenticatedClients.Remove(unauthClient);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,715 +0,0 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
enum SelectionMode
|
||||
{
|
||||
Manual = 0, Random = 1, Vote = 2
|
||||
}
|
||||
|
||||
enum YesNoMaybe
|
||||
{
|
||||
No = 0, Maybe = 1, Yes = 2
|
||||
}
|
||||
|
||||
enum BotSpawnMode
|
||||
{
|
||||
Normal, Fill
|
||||
}
|
||||
|
||||
partial class GameServer : NetworkMember, ISerializableEntity
|
||||
{
|
||||
private class SavedClientPermission
|
||||
{
|
||||
public readonly string IP;
|
||||
public readonly ulong SteamID;
|
||||
public readonly string Name;
|
||||
public List<DebugConsole.Command> PermittedCommands;
|
||||
|
||||
public ClientPermissions Permissions;
|
||||
|
||||
public SavedClientPermission(string name, string ip, ClientPermissions permissions, List<DebugConsole.Command> permittedCommands)
|
||||
{
|
||||
this.Name = name;
|
||||
this.IP = ip;
|
||||
|
||||
this.Permissions = permissions;
|
||||
this.PermittedCommands = permittedCommands;
|
||||
}
|
||||
public SavedClientPermission(string name, ulong steamID, ClientPermissions permissions, List<DebugConsole.Command> permittedCommands)
|
||||
{
|
||||
this.Name = name;
|
||||
this.SteamID = steamID;
|
||||
|
||||
this.Permissions = permissions;
|
||||
this.PermittedCommands = permittedCommands;
|
||||
}
|
||||
}
|
||||
|
||||
public const string SettingsFile = "serversettings.xml";
|
||||
public static readonly string PermissionPresetFile = "Data" + Path.DirectorySeparatorChar + "permissionpresets.xml";
|
||||
public static readonly string ClientPermissionsFile = "Data" + Path.DirectorySeparatorChar + "clientpermissions.xml";
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Dictionary<ItemPrefab, int> extraCargo;
|
||||
|
||||
public bool ShowNetStats;
|
||||
|
||||
private TimeSpan refreshMasterInterval = new TimeSpan(0, 0, 30);
|
||||
private TimeSpan sparseUpdateInterval = new TimeSpan(0, 0, 0, 3);
|
||||
|
||||
private SelectionMode subSelectionMode, modeSelectionMode;
|
||||
|
||||
private float selectedLevelDifficulty;
|
||||
|
||||
private bool registeredToMaster;
|
||||
|
||||
private WhiteList whitelist;
|
||||
private BanList banList;
|
||||
|
||||
private string password;
|
||||
|
||||
public float AutoRestartTimer;
|
||||
|
||||
private bool autoRestart;
|
||||
|
||||
private bool isPublic;
|
||||
|
||||
private int maxPlayers;
|
||||
|
||||
private List<SavedClientPermission> clientPermissions = new List<SavedClientPermission>();
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool RandomizeSeed
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(300.0f, true)]
|
||||
public float RespawnInterval
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(180.0f, true)]
|
||||
public float MaxTransportTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(0.2f, true)]
|
||||
public float MinRespawnRatio
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(60.0f, true)]
|
||||
public float AutoRestartInterval
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, true)]
|
||||
public bool StartWhenClientsReady
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(0.8f, true)]
|
||||
public float StartWhenClientsReadyRatio
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool AllowSpectating
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool EndRoundAtLevelEnd
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool SaveServerLogs
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool AllowRagdollButton
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool AllowFileTransfers
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(800, true)]
|
||||
private int LinesPerLogFile
|
||||
{
|
||||
get
|
||||
{
|
||||
return ServerLog.LinesPerFile;
|
||||
}
|
||||
set
|
||||
{
|
||||
ServerLog.LinesPerFile = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool AutoRestart
|
||||
{
|
||||
get { return autoRestart; }
|
||||
set
|
||||
{
|
||||
autoRestart = value;
|
||||
|
||||
AutoRestartTimer = autoRestart ? AutoRestartInterval : 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool AllowRespawn
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0, true)]
|
||||
public int BotCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(16, true)]
|
||||
public int MaxBotCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public BotSpawnMode BotSpawnMode
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public float SelectedLevelDifficulty
|
||||
{
|
||||
get { return selectedLevelDifficulty; }
|
||||
set { selectedLevelDifficulty = MathHelper.Clamp(value, 0.0f, 100.0f); }
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool AllowDisguises
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public YesNoMaybe TraitorsEnabled
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public SelectionMode SubSelectionMode
|
||||
{
|
||||
get { return subSelectionMode; }
|
||||
}
|
||||
|
||||
public SelectionMode ModeSelectionMode
|
||||
{
|
||||
get { return modeSelectionMode; }
|
||||
}
|
||||
|
||||
public BanList BanList
|
||||
{
|
||||
get { return banList; }
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool AllowVoteKick
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(0.6f, true)]
|
||||
public float EndVoteRequiredRatio
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(0.6f, true)]
|
||||
public float KickVoteRequiredRatio
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(30.0f, true)]
|
||||
public float KillDisconnectedTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(120.0f, true)]
|
||||
public float KickAFKTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool TraitorUseRatio
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(0.2f, true)]
|
||||
public float TraitorRatio
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(false, true)]
|
||||
public bool KarmaEnabled
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("sandbox", true)]
|
||||
public string GameModeIdentifier
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("Random", true)]
|
||||
public string MissionType
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public int MaxPlayers
|
||||
{
|
||||
get { return maxPlayers; }
|
||||
}
|
||||
|
||||
public List<MissionType> AllowedRandomMissionTypes
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(60f, true)]
|
||||
public float AutoBanTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(360f, true)]
|
||||
public float MaxAutoBanTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A list of int pairs that represent the ranges of UTF-16 codes allowed in client names
|
||||
/// </summary>
|
||||
public List<Pair<int, int>> AllowedClientNameChars
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new List<Pair<int, int>>();
|
||||
|
||||
private void SaveSettings()
|
||||
{
|
||||
XDocument doc = new XDocument(new XElement("serversettings"));
|
||||
|
||||
SerializableProperty.SerializeProperties(this, doc.Root, true);
|
||||
|
||||
doc.Root.SetAttributeValue("name", name);
|
||||
doc.Root.SetAttributeValue("public", isPublic);
|
||||
doc.Root.SetAttributeValue("port", NetPeerConfiguration.Port);
|
||||
if (Steam.SteamManager.USE_STEAM) doc.Root.SetAttributeValue("queryport", QueryPort);
|
||||
doc.Root.SetAttributeValue("maxplayers", maxPlayers);
|
||||
doc.Root.SetAttributeValue("enableupnp", NetPeerConfiguration.EnableUPnP);
|
||||
|
||||
doc.Root.SetAttributeValue("autorestart", autoRestart);
|
||||
|
||||
doc.Root.SetAttributeValue("SubSelection", subSelectionMode.ToString());
|
||||
doc.Root.SetAttributeValue("ModeSelection", modeSelectionMode.ToString());
|
||||
doc.Root.SetAttributeValue("LevelDifficulty", ((int)selectedLevelDifficulty).ToString());
|
||||
doc.Root.SetAttributeValue("TraitorsEnabled", TraitorsEnabled.ToString());
|
||||
|
||||
/*doc.Root.SetAttributeValue("BotCount", BotCount);
|
||||
doc.Root.SetAttributeValue("MaxBotCount", MaxBotCount);*/
|
||||
doc.Root.SetAttributeValue("BotSpawnMode", BotSpawnMode.ToString());
|
||||
|
||||
doc.Root.SetAttributeValue("AllowedRandomMissionTypes", string.Join(",", AllowedRandomMissionTypes));
|
||||
|
||||
doc.Root.SetAttributeValue("AllowedClientNameChars", string.Join(",", AllowedClientNameChars.Select(c => c.First + "-" + c.Second)));
|
||||
|
||||
#if SERVER
|
||||
doc.Root.SetAttributeValue("password", password);
|
||||
#endif
|
||||
|
||||
if (GameMain.NetLobbyScreen != null
|
||||
#if CLIENT
|
||||
&& GameMain.NetLobbyScreen.ServerMessage != null
|
||||
#endif
|
||||
)
|
||||
{
|
||||
doc.Root.SetAttributeValue("ServerMessage", GameMain.NetLobbyScreen.ServerMessageText);
|
||||
}
|
||||
|
||||
XmlWriterSettings settings = new XmlWriterSettings();
|
||||
settings.Indent = true;
|
||||
settings.NewLineOnAttributes = true;
|
||||
|
||||
using (var writer = XmlWriter.Create(SettingsFile, settings))
|
||||
{
|
||||
doc.Save(writer);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadSettings()
|
||||
{
|
||||
XDocument doc = null;
|
||||
if (File.Exists(SettingsFile))
|
||||
{
|
||||
doc = XMLExtensions.TryLoadXml(SettingsFile);
|
||||
}
|
||||
|
||||
if (doc == null || doc.Root == null)
|
||||
{
|
||||
doc = new XDocument(new XElement("serversettings"));
|
||||
}
|
||||
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, doc.Root);
|
||||
|
||||
AutoRestart = doc.Root.GetAttributeBool("autorestart", false);
|
||||
#if CLIENT
|
||||
if (autoRestart)
|
||||
{
|
||||
GameMain.NetLobbyScreen.SetAutoRestart(autoRestart, AutoRestartInterval);
|
||||
}
|
||||
#endif
|
||||
|
||||
subSelectionMode = SelectionMode.Manual;
|
||||
Enum.TryParse(doc.Root.GetAttributeString("SubSelection", "Manual"), out subSelectionMode);
|
||||
Voting.AllowSubVoting = subSelectionMode == SelectionMode.Vote;
|
||||
|
||||
modeSelectionMode = SelectionMode.Manual;
|
||||
Enum.TryParse(doc.Root.GetAttributeString("ModeSelection", "Manual"), out modeSelectionMode);
|
||||
Voting.AllowModeVoting = modeSelectionMode == SelectionMode.Vote;
|
||||
|
||||
selectedLevelDifficulty = doc.Root.GetAttributeFloat("LevelDifficulty", 20.0f);
|
||||
GameMain.NetLobbyScreen.SetLevelDifficulty(selectedLevelDifficulty);
|
||||
|
||||
var traitorsEnabled = TraitorsEnabled;
|
||||
Enum.TryParse(doc.Root.GetAttributeString("TraitorsEnabled", "No"), out traitorsEnabled);
|
||||
TraitorsEnabled = traitorsEnabled;
|
||||
GameMain.NetLobbyScreen.SetTraitorsEnabled(traitorsEnabled);
|
||||
|
||||
var botSpawnMode = BotSpawnMode.Fill;
|
||||
Enum.TryParse(doc.Root.GetAttributeString("BotSpawnMode", "Fill"), out botSpawnMode);
|
||||
BotSpawnMode = botSpawnMode;
|
||||
|
||||
//"65-90", "97-122", "48-59" = upper and lower case english alphabet and numbers
|
||||
string[] allowedClientNameCharsStr = doc.Root.GetAttributeStringArray("AllowedClientNameChars", new string[] { "32-33", "65-90", "97-122", "48-59" });
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.NetLobbyScreen != null
|
||||
#if CLIENT
|
||||
&& GameMain.NetLobbyScreen.ServerMessage != null
|
||||
#endif
|
||||
)
|
||||
{
|
||||
#if SERVER
|
||||
GameMain.NetLobbyScreen.ServerName = doc.Root.GetAttributeString("name", "");
|
||||
GameMain.NetLobbyScreen.SelectedModeIdentifier = GameModeIdentifier;
|
||||
GameMain.NetLobbyScreen.MissionTypeName = MissionType;
|
||||
#endif
|
||||
GameMain.NetLobbyScreen.ServerMessageText = doc.Root.GetAttributeString("ServerMessage", "");
|
||||
}
|
||||
|
||||
GameMain.NetLobbyScreen.SetBotSpawnMode(BotSpawnMode);
|
||||
GameMain.NetLobbyScreen.SetBotCount(BotCount);
|
||||
|
||||
#if CLIENT
|
||||
showLogButton.Visible = SaveServerLogs;
|
||||
#endif
|
||||
|
||||
List<string> monsterNames = GameMain.Instance.GetFilesOfType(ContentType.Character).ToList();
|
||||
for (int i = 0; i < monsterNames.Count; i++)
|
||||
{
|
||||
monsterNames[i] = Path.GetFileName(Path.GetDirectoryName(monsterNames[i]));
|
||||
}
|
||||
monsterEnabled = new Dictionary<string, bool>();
|
||||
foreach (string s in monsterNames)
|
||||
{
|
||||
if (!monsterEnabled.ContainsKey(s)) monsterEnabled.Add(s, true);
|
||||
}
|
||||
extraCargo = new Dictionary<ItemPrefab, int>();
|
||||
|
||||
AutoBanTime = doc.Root.GetAttributeFloat("autobantime", 60);
|
||||
MaxAutoBanTime = doc.Root.GetAttributeFloat("maxautobantime", 360);
|
||||
}
|
||||
|
||||
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);
|
||||
foreach (XElement clientElement in doc.Root.Elements())
|
||||
{
|
||||
string clientName = clientElement.GetAttributeString("name", "");
|
||||
string clientIP = 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(clientIP) && string.IsNullOrWhiteSpace(steamIdStr))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in " + ClientPermissionsFile + " - all clients must have an IP address or a Steam ID.");
|
||||
continue;
|
||||
}
|
||||
|
||||
string permissionsStr = clientElement.GetAttributeString("permissions", "");
|
||||
ClientPermissions permissions = ClientPermissions.None;
|
||||
if (permissionsStr.ToLowerInvariant() == "all")
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
List<DebugConsole.Command> permittedCommands = new List<DebugConsole.Command>();
|
||||
if (permissions.HasFlag(ClientPermissions.ConsoleCommands))
|
||||
{
|
||||
foreach (XElement commandElement in clientElement.Elements())
|
||||
{
|
||||
if (commandElement.Name.ToString().ToLowerInvariant() != "command") 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);
|
||||
}
|
||||
}
|
||||
|
||||
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, clientIP, 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 = 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");
|
||||
}
|
||||
|
||||
Log("Saving client permissions", ServerLog.MessageType.ServerMessage);
|
||||
|
||||
XDocument doc = new XDocument(new XElement("ClientPermissions"));
|
||||
|
||||
foreach (SavedClientPermission clientPermission in clientPermissions)
|
||||
{
|
||||
XElement clientElement = new XElement("Client",
|
||||
new XAttribute("name", clientPermission.Name),
|
||||
new XAttribute("permissions", clientPermission.Permissions.ToString()));
|
||||
|
||||
if (clientPermission.SteamID > 0)
|
||||
{
|
||||
clientElement.Add(new XAttribute("steamid", clientPermission.SteamID));
|
||||
}
|
||||
else
|
||||
{
|
||||
clientElement.Add(new XAttribute("ip", clientPermission.IP));
|
||||
}
|
||||
|
||||
if (clientPermission.Permissions.HasFlag(ClientPermissions.ConsoleCommands))
|
||||
{
|
||||
foreach (DebugConsole.Command command in clientPermission.PermittedCommands)
|
||||
{
|
||||
clientElement.Add(new XElement("command", new XAttribute("name", command.names[0])));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,9 @@ namespace Barotrauma.Networking
|
||||
#if CLIENT
|
||||
void ClientWrite(NetBuffer msg, object[] extraData = null);
|
||||
#endif
|
||||
#if SERVER
|
||||
void ServerRead(ClientNetObject type, NetBuffer msg, Client c);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -20,7 +22,9 @@ namespace Barotrauma.Networking
|
||||
/// </summary>
|
||||
interface IServerSerializable : INetSerializable
|
||||
{
|
||||
#if SERVER
|
||||
void ServerWrite(NetBuffer msg, Client c, object[] extraData = null);
|
||||
#endif
|
||||
#if CLIENT
|
||||
void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime);
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
namespace Barotrauma.Networking
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
static class NetConfig
|
||||
{
|
||||
@@ -18,10 +21,55 @@
|
||||
public const float EnableCharacterDist = 20000.0f;
|
||||
public const float EnableCharacterDistSqr = EnableCharacterDist * EnableCharacterDist;
|
||||
|
||||
public const float MaxPhysicsBodyVelocity = 64.0f;
|
||||
public const float MaxPhysicsBodyAngularVelocity = 16.0f;
|
||||
|
||||
public const float MaxHealthUpdateInterval = 2.0f;
|
||||
public const float MaxHealthUpdateIntervalDead = 10.0f;
|
||||
|
||||
public const float HighPrioCharacterPositionUpdateDistance = 1000.0f;
|
||||
public const float LowPrioCharacterPositionUpdateDistance = 10000.0f;
|
||||
public const float HighPrioCharacterPositionUpdateInterval = 0.0f;
|
||||
public const float LowPrioCharacterPositionUpdateInterval = 1.0f;
|
||||
|
||||
//how much the physics body of an item has to move until the server
|
||||
//send a position update to clients (in sim units)
|
||||
public const float ItemPosUpdateDistance = 2.0f;
|
||||
|
||||
public const float DeleteDisconnectedTime = 10.0f;
|
||||
public const float DeleteDisconnectedTime = 10.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Interpolates the positional error of a physics body towards zero.
|
||||
/// </summary>
|
||||
public static Vector2 InterpolateSimPositionError(Vector2 simPositionError, float? smoothingFactor = null)
|
||||
{
|
||||
float lengthSqr = simPositionError.LengthSquared();
|
||||
//correct immediately if the error is very large
|
||||
if (lengthSqr > 100.0f) { return Vector2.Zero; }
|
||||
float positionSmoothingFactor = smoothingFactor ?? MathHelper.Lerp(0.95f, 0.8f, MathHelper.Clamp(lengthSqr, 0.0f, 1.0f));
|
||||
return simPositionError *= positionSmoothingFactor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interpolates the rotational error of a physics body towards zero.
|
||||
/// </summary>
|
||||
public static float InterpolateRotationError(float rotationError)
|
||||
{
|
||||
//correct immediately if the error is very large
|
||||
if (rotationError > MathHelper.TwoPi) { return 0.0f; }
|
||||
float rotationSmoothingFactor = MathHelper.Lerp(0.95f, 0.8f, Math.Min(Math.Abs(rotationError), 1.0f));
|
||||
return rotationError *= rotationSmoothingFactor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interpolates the cursor position error towards zero.
|
||||
/// </summary>
|
||||
public static Vector2 InterpolateCursorPositionError(Vector2 cursorPositionError)
|
||||
{
|
||||
float lengthSqr = cursorPositionError.LengthSquared();
|
||||
//correct immediately if the error is very large
|
||||
if (lengthSqr > 1000.0f) { return Vector2.Zero; }
|
||||
return cursorPositionError *= 0.7f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,38 +62,4 @@ namespace Barotrauma.Networking
|
||||
return Data == other.Data;
|
||||
}
|
||||
}
|
||||
|
||||
class ServerEntityEvent : NetEntityEvent
|
||||
{
|
||||
private IServerSerializable serializable;
|
||||
|
||||
public bool Sent;
|
||||
|
||||
#if DEBUG
|
||||
public string StackTrace;
|
||||
#endif
|
||||
|
||||
private double createTime;
|
||||
public double CreateTime
|
||||
{
|
||||
get { return createTime; }
|
||||
}
|
||||
|
||||
public ServerEntityEvent(IServerSerializable entity, UInt16 id)
|
||||
: base(entity, id)
|
||||
{
|
||||
serializable = entity;
|
||||
|
||||
createTime = Timing.TotalTime;
|
||||
|
||||
#if DEBUG
|
||||
StackTrace = Environment.StackTrace.ToString();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Write(NetBuffer msg, Client recipient)
|
||||
{
|
||||
serializable.ServerWrite(msg, recipient, Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-434
@@ -1,434 +0,0 @@
|
||||
using Lidgren.Network;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class ServerEntityEventManager : NetEntityEventManager
|
||||
{
|
||||
private List<ServerEntityEvent> events;
|
||||
|
||||
//list of unique events (i.e. !IsDuplicate) created during the round
|
||||
//used for syncing clients who join mid-round
|
||||
private List<ServerEntityEvent> uniqueEvents;
|
||||
|
||||
private UInt16 lastSentToAll;
|
||||
|
||||
public List<ServerEntityEvent> Events
|
||||
{
|
||||
get { return events; }
|
||||
}
|
||||
|
||||
public List<ServerEntityEvent> UniqueEvents
|
||||
{
|
||||
get { return uniqueEvents; }
|
||||
}
|
||||
|
||||
private class BufferedEvent
|
||||
{
|
||||
public readonly Client Sender;
|
||||
|
||||
public readonly UInt16 CharacterStateID;
|
||||
public readonly NetBuffer Data;
|
||||
|
||||
public readonly Character Character;
|
||||
|
||||
public readonly IClientSerializable TargetEntity;
|
||||
|
||||
public bool IsProcessed;
|
||||
|
||||
public BufferedEvent(Client sender, Character senderCharacter, UInt16 characterStateID, IClientSerializable targetEntity, NetBuffer data)
|
||||
{
|
||||
this.Sender = sender;
|
||||
this.Character = senderCharacter;
|
||||
this.CharacterStateID = characterStateID;
|
||||
|
||||
this.TargetEntity = targetEntity;
|
||||
|
||||
this.Data = data;
|
||||
}
|
||||
}
|
||||
|
||||
private List<BufferedEvent> bufferedEvents;
|
||||
|
||||
private UInt16 ID;
|
||||
|
||||
private GameServer server;
|
||||
|
||||
public ServerEntityEventManager(GameServer server)
|
||||
{
|
||||
events = new List<ServerEntityEvent>();
|
||||
|
||||
this.server = server;
|
||||
|
||||
bufferedEvents = new List<BufferedEvent>();
|
||||
|
||||
uniqueEvents = new List<ServerEntityEvent>();
|
||||
}
|
||||
|
||||
public void CreateEvent(IServerSerializable entity, object[] extraData = null)
|
||||
{
|
||||
if (entity == null || !(entity is Entity))
|
||||
{
|
||||
DebugConsole.ThrowError("Can't create an entity event for " + entity + "!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (((Entity)entity).Removed && !(entity is Level))
|
||||
{
|
||||
DebugConsole.ThrowError("Can't create an entity event for " + entity + " - the entity has been removed.\n"+Environment.StackTrace);
|
||||
return;
|
||||
}
|
||||
if (((Entity)entity).IdFreed)
|
||||
{
|
||||
DebugConsole.ThrowError("Can't create an entity event for " + entity + " - the ID of the entity has been freed.\n"+Environment.StackTrace);
|
||||
return;
|
||||
}
|
||||
|
||||
var newEvent = new ServerEntityEvent(entity, (UInt16)(ID + 1));
|
||||
if (extraData != null) newEvent.SetData(extraData);
|
||||
|
||||
//remove events that have been sent to all clients, they are redundant now
|
||||
//keep at least one event in the list (lastSentToAll == e.ID) so we can use it to keep track of the latest ID
|
||||
events.RemoveAll(e => NetIdUtils.IdMoreRecent(lastSentToAll, e.ID));
|
||||
|
||||
if (server.ConnectedClients.Count(c => c.InGame) == 0 && events.Count > 1)
|
||||
{
|
||||
events.RemoveRange(0, events.Count - 1);
|
||||
}
|
||||
|
||||
for (int i = events.Count - 1; i >= 0; i--)
|
||||
{
|
||||
//we already have an identical event that's waiting to be sent
|
||||
// -> no need to add a new one
|
||||
if (events[i].IsDuplicate(newEvent) && !events[i].Sent) return;
|
||||
}
|
||||
|
||||
ID++;
|
||||
|
||||
events.Add(newEvent);
|
||||
|
||||
if (!uniqueEvents.Any(e => e.IsDuplicate(newEvent)))
|
||||
{
|
||||
//create a copy of the event and give it a new ID
|
||||
var uniqueEvent = new ServerEntityEvent(entity, (UInt16)(uniqueEvents.Count + 1));
|
||||
uniqueEvent.SetData(extraData);
|
||||
|
||||
uniqueEvents.Add(uniqueEvent);
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(List<Client> clients)
|
||||
{
|
||||
foreach (BufferedEvent bufferedEvent in bufferedEvents)
|
||||
{
|
||||
if (bufferedEvent.Character == null || bufferedEvent.Character.IsDead)
|
||||
{
|
||||
bufferedEvent.IsProcessed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
//delay reading the events until the inputs for the corresponding frame have been processed
|
||||
|
||||
//UNLESS the character is unconscious, in which case we'll read the messages immediately (because further inputs will be ignored)
|
||||
//atm the "give in" command is the only thing unconscious characters can do, other types of events are ignored
|
||||
if (!bufferedEvent.Character.IsUnconscious &&
|
||||
NetIdUtils.IdMoreRecent(bufferedEvent.CharacterStateID, bufferedEvent.Character.LastProcessedID))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ReadEvent(bufferedEvent.Data, bufferedEvent.TargetEntity, bufferedEvent.Sender);
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
string entityName = bufferedEvent.TargetEntity == null ? "null" : bufferedEvent.TargetEntity.ToString();
|
||||
if (GameSettings.VerboseLogging)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to read server event for entity \"" + entityName + "\"!", e);
|
||||
}
|
||||
GameAnalyticsManager.AddErrorEventOnce("ServerEntityEventManager.Read:ReadFailed" + entityName,
|
||||
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
||||
"Failed to read server event for entity \"" + entityName + "\"!\n" + e.StackTrace);
|
||||
}
|
||||
|
||||
bufferedEvent.IsProcessed = true;
|
||||
}
|
||||
|
||||
var inGameClients = clients.FindAll(c => c.InGame && !c.NeedsMidRoundSync);
|
||||
if (inGameClients.Count > 0)
|
||||
{
|
||||
lastSentToAll = inGameClients[0].LastRecvEntityEventID;
|
||||
inGameClients.ForEach(c => { if (NetIdUtils.IdMoreRecent(lastSentToAll, c.LastRecvEntityEventID)) lastSentToAll = c.LastRecvEntityEventID; });
|
||||
|
||||
ServerEntityEvent firstEventToResend = events.Find(e => e.ID == (ushort)(lastSentToAll + 1));
|
||||
if (firstEventToResend != null && (Timing.TotalTime - firstEventToResend.CreateTime) > 10.0f)
|
||||
{
|
||||
//it's been 10 seconds since this event was created
|
||||
//kick everyone that hasn't received it yet, this is way too old
|
||||
List<Client> toKick = inGameClients.FindAll(c => NetIdUtils.IdMoreRecent((UInt16)(lastSentToAll + 1), c.LastRecvEntityEventID));
|
||||
toKick.ForEach(c =>
|
||||
{
|
||||
DebugConsole.NewMessage(c.Name + " was kicked due to excessive desync (expected old event " + c.LastRecvEntityEventID.ToString() + ")", Microsoft.Xna.Framework.Color.Red);
|
||||
GameServer.Log("Disconnecting client " + c.Name + " due to excessive desync (expected old event " + c.LastRecvEntityEventID.ToString() + ").", ServerLog.MessageType.ServerMessage);
|
||||
server.DisconnectClient(c, "", "You have been disconnected because of excessive desync");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
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 " + c.LastRecvEntityEventID.ToString() + ", last available is " + events[0].ID.ToString() + ")", Microsoft.Xna.Framework.Color.Red);
|
||||
GameServer.Log("Disconnecting client " + c.Name + " due to excessive desync (expected " + c.LastRecvEntityEventID.ToString() + ", last available is " + events[0].ID.ToString() + ")", ServerLog.MessageType.ServerMessage);
|
||||
server.DisconnectClient(c, "", "You have been disconnected because of excessive desync");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var timedOutClients = clients.FindAll(c => c.InGame && c.NeedsMidRoundSync && Timing.TotalTime > c.MidRoundSyncTimeOut);
|
||||
foreach (Client timedOutClient in timedOutClients)
|
||||
{
|
||||
GameServer.Log("Disconnecting client " + timedOutClient.Name + ". Syncing the client with the server took too long.", ServerLog.MessageType.ServerMessage);
|
||||
GameMain.Server.DisconnectClient(timedOutClient, "", "You have been disconnected because syncing your client with the server took too long.");
|
||||
}
|
||||
|
||||
bufferedEvents.RemoveAll(b => b.IsProcessed);
|
||||
}
|
||||
|
||||
private void BufferEvent(BufferedEvent bufferedEvent)
|
||||
{
|
||||
if (bufferedEvents.Count > 512)
|
||||
{
|
||||
//should normally never happen
|
||||
|
||||
//a client could potentially spam events with a much higher character state ID
|
||||
//than the state of their character and/or stop sending character inputs,
|
||||
//so we'll drop some events to make sure no-one blows up our buffer
|
||||
bufferedEvents.RemoveRange(0, 256);
|
||||
}
|
||||
|
||||
bufferedEvents.Add(bufferedEvent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes all the events that the client hasn't received yet into the outgoing message
|
||||
/// </summary>
|
||||
public void Write(Client client, NetOutgoingMessage msg)
|
||||
{
|
||||
List<NetEntityEvent> eventsToSync = null;
|
||||
if (client.NeedsMidRoundSync)
|
||||
{
|
||||
eventsToSync = GetEventsToSync(client, uniqueEvents);
|
||||
}
|
||||
else
|
||||
{
|
||||
eventsToSync = GetEventsToSync(client, events);
|
||||
}
|
||||
|
||||
if (eventsToSync.Count == 0) return;
|
||||
|
||||
//too many events for one packet
|
||||
if (eventsToSync.Count > MaxEventsPerWrite)
|
||||
{
|
||||
eventsToSync.RemoveRange(MaxEventsPerWrite, eventsToSync.Count - MaxEventsPerWrite);
|
||||
}
|
||||
|
||||
foreach (NetEntityEvent entityEvent in eventsToSync)
|
||||
{
|
||||
(entityEvent as ServerEntityEvent).Sent = true;
|
||||
client.EntityEventLastSent[entityEvent.ID] = (float)NetTime.Now;
|
||||
}
|
||||
|
||||
if (client.NeedsMidRoundSync)
|
||||
{
|
||||
msg.Write((byte)ServerNetObject.ENTITY_EVENT_INITIAL);
|
||||
|
||||
//how many (unique) events the clients had missed before joining
|
||||
client.UnreceivedEntityEventCount = (UInt16)uniqueEvents.Count;
|
||||
//ID of the first event sent after the client joined
|
||||
//(after the client has been synced they'll switch their lastReceivedID
|
||||
//to the one before this, and the eventmanagers will start to function "normally")
|
||||
client.FirstNewEventID = events.Count == 0 ? (UInt16)0 : events[events.Count - 1].ID;
|
||||
|
||||
msg.Write(client.UnreceivedEntityEventCount);
|
||||
msg.Write(client.FirstNewEventID);
|
||||
//DebugConsole.NewMessage(eventsToSync[0].ID.ToString(), Microsoft.Xna.Framework.Color.Yellow);
|
||||
Write(msg, eventsToSync, client);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write((byte)ServerNetObject.ENTITY_EVENT);
|
||||
Write(msg, eventsToSync, client);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of events that should be sent to the client from the eventList
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="eventList"></param>
|
||||
/// <returns></returns>
|
||||
private List<NetEntityEvent> GetEventsToSync(Client client, List<ServerEntityEvent> eventList)
|
||||
{
|
||||
List<NetEntityEvent> eventsToSync = new List<NetEntityEvent>();
|
||||
if (eventList.Count == 0) return eventsToSync;
|
||||
|
||||
//find the index of the first event the client hasn't received
|
||||
int startIndex = eventList.Count;
|
||||
while (startIndex > 0 &&
|
||||
NetIdUtils.IdMoreRecent(eventList[startIndex - 1].ID,client.LastRecvEntityEventID))
|
||||
{
|
||||
startIndex--;
|
||||
}
|
||||
|
||||
for (int i = startIndex; i < eventList.Count; i++)
|
||||
{
|
||||
//find the first event that hasn't been sent in 1.5 * roundtriptime or at all
|
||||
float lastSent = 0;
|
||||
client.EntityEventLastSent.TryGetValue(eventList[i].ID, out lastSent);
|
||||
|
||||
if (lastSent > NetTime.Now - client.Connection.AverageRoundtripTime * 1.5f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
eventsToSync.AddRange(eventList.GetRange(i, eventList.Count - i));
|
||||
break;
|
||||
}
|
||||
|
||||
return eventsToSync;
|
||||
}
|
||||
|
||||
public void InitClientMidRoundSync(Client client)
|
||||
{
|
||||
//no need for midround syncing if no events have been created,
|
||||
//or if the first created unique event is still in the event list
|
||||
if (uniqueEvents.Count == 0 || (events.Count > 0 && events[0].ID == uniqueEvents[0].ID))
|
||||
{
|
||||
client.UnreceivedEntityEventCount = 0;
|
||||
client.FirstNewEventID = 0;
|
||||
client.NeedsMidRoundSync = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
double midRoundSyncTimeOut = uniqueEvents.Count / MaxEventsPerWrite * server.UpdateInterval.TotalSeconds;
|
||||
midRoundSyncTimeOut = Math.Max(10.0f, midRoundSyncTimeOut * 2.0f);
|
||||
|
||||
client.UnreceivedEntityEventCount = (UInt16)uniqueEvents.Count;
|
||||
client.FirstNewEventID = 0;
|
||||
client.NeedsMidRoundSync = true;
|
||||
client.MidRoundSyncTimeOut = Timing.TotalTime + midRoundSyncTimeOut;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the events from the message, ignoring ones we've already received
|
||||
/// </summary>
|
||||
public void Read(NetIncomingMessage msg, Client sender = null)
|
||||
{
|
||||
UInt16 firstEventID = msg.ReadUInt16();
|
||||
int eventCount = msg.ReadByte();
|
||||
|
||||
for (int i = 0; i < eventCount; i++)
|
||||
{
|
||||
UInt16 thisEventID = (UInt16)(firstEventID + (UInt16)i);
|
||||
UInt16 entityID = msg.ReadUInt16();
|
||||
|
||||
if (entityID == Entity.NullEntityID)
|
||||
{
|
||||
msg.ReadPadBits();
|
||||
if (thisEventID == (UInt16)(sender.LastSentEntityEventID + 1)) sender.LastSentEntityEventID++;
|
||||
continue;
|
||||
}
|
||||
|
||||
byte msgLength = msg.ReadByte();
|
||||
|
||||
IClientSerializable entity = Entity.FindEntityByID(entityID) as IClientSerializable;
|
||||
|
||||
//skip the event if we've already received it
|
||||
if (thisEventID != (UInt16)(sender.LastSentEntityEventID + 1))
|
||||
{
|
||||
if (GameSettings.VerboseLogging)
|
||||
{
|
||||
DebugConsole.NewMessage("received msg " + thisEventID, Microsoft.Xna.Framework.Color.Red);
|
||||
}
|
||||
msg.Position += msgLength * 8;
|
||||
}
|
||||
else if (entity == null)
|
||||
{
|
||||
//entity not found -> consider the even read and skip over it
|
||||
//(can happen, for example, when a client uses a medical item repeatedly
|
||||
//and creates an event for it before receiving the event about it being removed)
|
||||
if (GameSettings.VerboseLogging)
|
||||
{
|
||||
DebugConsole.NewMessage(
|
||||
"received msg " + thisEventID + ", entity " + entityID + " not found",
|
||||
Microsoft.Xna.Framework.Color.Orange);
|
||||
}
|
||||
sender.LastSentEntityEventID++;
|
||||
msg.Position += msgLength * 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GameSettings.VerboseLogging)
|
||||
{
|
||||
DebugConsole.NewMessage("received msg " + thisEventID, Microsoft.Xna.Framework.Color.Green);
|
||||
}
|
||||
|
||||
UInt16 characterStateID = msg.ReadUInt16();
|
||||
|
||||
NetBuffer buffer = new NetBuffer();
|
||||
buffer.Write(msg.ReadBytes(msgLength - 2));
|
||||
BufferEvent(new BufferedEvent(sender, sender.Character, characterStateID, entity, buffer));
|
||||
|
||||
sender.LastSentEntityEventID++;
|
||||
}
|
||||
msg.ReadPadBits();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void WriteEvent(NetBuffer buffer, NetEntityEvent entityEvent, Client recipient = null)
|
||||
{
|
||||
var serverEvent = entityEvent as ServerEntityEvent;
|
||||
if (serverEvent == null) return;
|
||||
|
||||
serverEvent.Write(buffer, recipient);
|
||||
}
|
||||
|
||||
protected void ReadEvent(NetBuffer buffer, INetSerializable entity, Client sender = null)
|
||||
{
|
||||
var clientEntity = entity as IClientSerializable;
|
||||
if (clientEntity == null) return;
|
||||
|
||||
clientEntity.ServerRead(ClientNetObject.ENTITY_STATE, buffer, sender);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
ID = 0;
|
||||
events.Clear();
|
||||
|
||||
bufferedEvents.Clear();
|
||||
|
||||
lastSentToAll = 0;
|
||||
|
||||
uniqueEvents.Clear();
|
||||
|
||||
foreach (Client c in server.ConnectedClients)
|
||||
{
|
||||
c.EntityEventLastSent.Clear();
|
||||
c.LastRecvEntityEventID = 0;
|
||||
c.LastSentEntityEventID = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,13 @@ namespace Barotrauma.Networking
|
||||
UPDATE_LOBBY, //update state in lobby
|
||||
UPDATE_INGAME, //update state ingame
|
||||
|
||||
SERVER_SETTINGS, //change server settings
|
||||
|
||||
CAMPAIGN_SETUP_INFO,
|
||||
|
||||
FILE_REQUEST, //request a (submarine) file from the server
|
||||
|
||||
VOICE,
|
||||
|
||||
RESPONSE_STARTGAME, //tell the server whether you're ready to start
|
||||
SERVER_COMMAND, //tell the server to end a round or kick/ban someone (special permissions required)
|
||||
@@ -47,9 +53,14 @@ namespace Barotrauma.Networking
|
||||
|
||||
PERMISSIONS, //tell the client which special permissions they have (if any)
|
||||
ACHIEVEMENT, //give the client a steam achievement
|
||||
CHEATS_ENABLED, //tell the clients whether cheats are on or off
|
||||
|
||||
CAMPAIGN_SETUP_INFO,
|
||||
|
||||
FILE_TRANSFER,
|
||||
|
||||
VOICE,
|
||||
|
||||
QUERY_STARTGAME, //ask the clients whether they're ready to start
|
||||
STARTGAME, //start a new round
|
||||
ENDGAME
|
||||
@@ -60,6 +71,7 @@ namespace Barotrauma.Networking
|
||||
SYNC_IDS,
|
||||
CHAT_MESSAGE,
|
||||
VOTE,
|
||||
CLIENT_LIST,
|
||||
ENTITY_POSITION,
|
||||
ENTITY_EVENT,
|
||||
ENTITY_EVENT_INITIAL,
|
||||
@@ -98,6 +110,24 @@ namespace Barotrauma.Networking
|
||||
|
||||
abstract partial class NetworkMember
|
||||
{
|
||||
public UInt16 LastClientListUpdateID
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public virtual bool IsServer
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public virtual bool IsClient
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public abstract void CreateEntityEvent(INetSerializable entity, object[] extraData = null);
|
||||
|
||||
#if DEBUG
|
||||
public Dictionary<string, long> messageCount = new Dictionary<string, long>();
|
||||
#endif
|
||||
@@ -110,6 +140,8 @@ namespace Barotrauma.Networking
|
||||
|
||||
protected string name;
|
||||
|
||||
protected ServerSettings serverSettings;
|
||||
|
||||
protected TimeSpan updateInterval;
|
||||
protected DateTime updateTimer;
|
||||
|
||||
@@ -117,17 +149,25 @@ namespace Barotrauma.Networking
|
||||
|
||||
protected bool gameStarted;
|
||||
|
||||
public Dictionary<string, bool> monsterEnabled;
|
||||
|
||||
protected RespawnManager respawnManager;
|
||||
|
||||
public Voting Voting;
|
||||
|
||||
public bool ShowNetStats;
|
||||
|
||||
public int Port
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public int TickRate
|
||||
{
|
||||
get { return serverSettings.TickRate; }
|
||||
set
|
||||
{
|
||||
serverSettings.TickRate = MathHelper.Clamp(value, 1, 60);
|
||||
updateInterval = new TimeSpan(0, 0, 0, 0, MathHelper.Clamp(1000 / serverSettings.TickRate, 1, 500));
|
||||
}
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
@@ -154,25 +194,17 @@ namespace Barotrauma.Networking
|
||||
get { return respawnManager; }
|
||||
}
|
||||
|
||||
public ServerLog ServerLog
|
||||
public ServerSettings ServerSettings
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
get { return serverSettings; }
|
||||
}
|
||||
|
||||
|
||||
public NetPeerConfiguration NetPeerConfiguration
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public NetworkMember()
|
||||
{
|
||||
InitProjSpecific();
|
||||
|
||||
Voting = new Voting();
|
||||
}
|
||||
|
||||
|
||||
public bool CanUseRadio(Character sender)
|
||||
{
|
||||
if (sender == null) return false;
|
||||
@@ -190,19 +222,14 @@ namespace Barotrauma.Networking
|
||||
AddChatMessage(ChatMessage.Create(senderName, message, type, senderCharacter));
|
||||
}
|
||||
|
||||
public void AddChatMessage(ChatMessage message)
|
||||
public virtual void AddChatMessage(ChatMessage message)
|
||||
{
|
||||
GameServer.Log(message.TextWithSender, ServerLog.MessageType.Chat);
|
||||
|
||||
if (string.IsNullOrEmpty(message.Text)) { return; }
|
||||
|
||||
if (message.Sender != null && !message.Sender.IsDead)
|
||||
{
|
||||
message.Sender.ShowSpeechBubble(2.0f, ChatMessage.MessageColor[(int)message.Type]);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
GameMain.NetLobbyScreen.NewChatMessage(message);
|
||||
chatBox.AddMessage(message);
|
||||
#endif
|
||||
}
|
||||
|
||||
public virtual void KickPlayer(string kickedName, string reason) { }
|
||||
@@ -211,12 +238,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
public virtual void UnbanPlayer(string playerName, string playerIP) { }
|
||||
|
||||
public virtual void Update(float deltaTime)
|
||||
{
|
||||
#if CLIENT
|
||||
UpdateHUD(deltaTime);
|
||||
#endif
|
||||
}
|
||||
public virtual void Update(float deltaTime) { }
|
||||
|
||||
public virtual void Disconnect() { }
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
public OrderChatMessage(Order order, string orderOption, Entity targetEntity, Character targetCharacter, Character sender)
|
||||
: this(order, orderOption,
|
||||
order.GetChatMessage(targetCharacter?.Name, sender?.CurrentHull?.RoomName, orderOption),
|
||||
order.GetChatMessage(targetCharacter?.Name, sender?.CurrentHull?.RoomName, givingOrderToSelf: targetCharacter == sender, orderOption: orderOption),
|
||||
targetEntity, targetCharacter, sender)
|
||||
{
|
||||
}
|
||||
@@ -33,24 +33,5 @@ namespace Barotrauma.Networking
|
||||
TargetCharacter = targetCharacter;
|
||||
TargetEntity = targetEntity;
|
||||
}
|
||||
|
||||
public override void ServerWrite(NetOutgoingMessage msg, Client c)
|
||||
{
|
||||
msg.Write((byte)ServerNetObject.CHAT_MESSAGE);
|
||||
msg.Write(NetStateID);
|
||||
msg.Write((byte)ChatMessageType.Order);
|
||||
|
||||
msg.Write(SenderName);
|
||||
msg.Write(Sender != null && c.InGame);
|
||||
if (Sender != null && c.InGame)
|
||||
{
|
||||
msg.Write(Sender.ID);
|
||||
}
|
||||
|
||||
msg.Write((byte)Order.PrefabList.IndexOf(Order.Prefab));
|
||||
msg.Write(TargetCharacter == null ? (UInt16)0 : TargetCharacter.ID);
|
||||
msg.Write(TargetEntity == null ? (UInt16)0 : TargetEntity.ID);
|
||||
msg.Write((byte)Array.IndexOf(Order.Prefab.Options, OrderOption));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class RespawnManager : Entity, IServerSerializable
|
||||
{
|
||||
partial class RespawnManager : Entity, IServerSerializable
|
||||
{
|
||||
public enum State
|
||||
{
|
||||
Waiting,
|
||||
@@ -116,60 +116,15 @@ namespace Barotrauma.Networking
|
||||
respawnShuttle = null;
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
if (networkMember is GameServer server)
|
||||
{
|
||||
respawnTimer = server.RespawnInterval;
|
||||
maxTransportTime = server.MaxTransportTime;
|
||||
}
|
||||
respawnTimer = server.ServerSettings.RespawnInterval;
|
||||
maxTransportTime = server.ServerSettings.MaxTransportTime;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private List<Client> GetClientsToRespawn()
|
||||
{
|
||||
return networkMember.ConnectedClients.FindAll(c =>
|
||||
c.InGame &&
|
||||
(!c.SpectateOnly || !((GameServer)networkMember).AllowSpectating) &&
|
||||
(c.Character == null || c.Character.IsDead));
|
||||
}
|
||||
|
||||
private List<CharacterInfo> GetBotsToRespawn()
|
||||
{
|
||||
GameServer server = networkMember as GameServer;
|
||||
|
||||
if (server.BotSpawnMode == BotSpawnMode.Normal)
|
||||
{
|
||||
return Character.CharacterList
|
||||
.FindAll(c => c.TeamID == 1 && c.AIController != null && c.Info != null && c.IsDead)
|
||||
.Select(c => c.Info)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
int currPlayerCount = server.ConnectedClients.Count(c => c.InGame && (!c.SpectateOnly || !server.AllowSpectating));
|
||||
if (server.CharacterInfo != null) currPlayerCount++;
|
||||
|
||||
var existingBots = Character.CharacterList
|
||||
.FindAll(c => c.TeamID == 1 && c.AIController != null && c.Info != null);
|
||||
|
||||
int requiredBots = server.BotCount - currPlayerCount;
|
||||
requiredBots -= existingBots.Count(b => !b.IsDead);
|
||||
|
||||
List<CharacterInfo> botsToRespawn = new List<CharacterInfo>();
|
||||
for (int i = 0; i < requiredBots; i++)
|
||||
{
|
||||
CharacterInfo botToRespawn = existingBots.Find(b => b.IsDead)?.Info;
|
||||
if (botToRespawn == null)
|
||||
{
|
||||
botToRespawn = new CharacterInfo(Character.HumanConfigFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
existingBots.Remove(botToRespawn.Character);
|
||||
}
|
||||
botsToRespawn.Add(botToRespawn);
|
||||
}
|
||||
return botsToRespawn;
|
||||
}
|
||||
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (respawnShuttle == null)
|
||||
@@ -194,61 +149,8 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateWaiting(float deltaTime)
|
||||
{
|
||||
var server = networkMember as GameServer;
|
||||
if (server == null)
|
||||
{
|
||||
if (CountdownStarted)
|
||||
{
|
||||
respawnTimer = Math.Max(0.0f, respawnTimer - deltaTime);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int characterToRespawnCount = GetClientsToRespawn().Count;
|
||||
int totalCharacterCount = server.ConnectedClients.Count;
|
||||
if (server.Character != null)
|
||||
{
|
||||
totalCharacterCount++;
|
||||
if (server.Character.IsDead) characterToRespawnCount++;
|
||||
}
|
||||
bool startCountdown = (float)characterToRespawnCount >= Math.Max((float)totalCharacterCount * server.MinRespawnRatio, 1.0f);
|
||||
|
||||
if (startCountdown)
|
||||
{
|
||||
if (!CountdownStarted)
|
||||
{
|
||||
CountdownStarted = true;
|
||||
server.CreateEntityEvent(this);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CountdownStarted = false;
|
||||
}
|
||||
|
||||
if (!CountdownStarted) return;
|
||||
|
||||
respawnTimer -= deltaTime;
|
||||
if (respawnTimer <= 0.0f)
|
||||
{
|
||||
respawnTimer = server.RespawnInterval;
|
||||
|
||||
DispatchShuttle();
|
||||
}
|
||||
|
||||
if (respawnShuttle == null) return;
|
||||
|
||||
respawnShuttle.Velocity = Vector2.Zero;
|
||||
|
||||
if (shuttleSteering != null)
|
||||
{
|
||||
shuttleSteering.AutoPilot = false;
|
||||
shuttleSteering.MaintainPos = false;
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateWaiting(float deltaTime);
|
||||
|
||||
private void UpdateTransporting(float deltaTime)
|
||||
{
|
||||
//infinite transport time -> shuttle wont return
|
||||
@@ -256,14 +158,17 @@ namespace Barotrauma.Networking
|
||||
|
||||
shuttleTransportTimer -= deltaTime;
|
||||
|
||||
#if CLIENT
|
||||
GameClient nClient = networkMember as GameClient;
|
||||
if (shuttleTransportTimer + deltaTime > 15.0f && shuttleTransportTimer <= 15.0f &&
|
||||
networkMember.Character != null &&
|
||||
networkMember.Character.Submarine == respawnShuttle)
|
||||
nClient.Character != null &&
|
||||
nClient.Character.Submarine == respawnShuttle)
|
||||
{
|
||||
networkMember.AddChatMessage("The shuttle will automatically return back to the outpost. Please leave the shuttle immediately.", ChatMessageType.Server);
|
||||
nClient.AddChatMessage("ServerMessage.ShuttleLeaving", ChatMessageType.Server);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#if SERVER
|
||||
var server = networkMember as GameServer;
|
||||
if (server == null) return;
|
||||
|
||||
@@ -281,10 +186,11 @@ namespace Barotrauma.Networking
|
||||
server.CreateEntityEvent(this);
|
||||
|
||||
CountdownStarted = false;
|
||||
maxTransportTime = server.MaxTransportTime;
|
||||
maxTransportTime = server.ServerSettings.MaxTransportTime;
|
||||
shuttleReturnTimer = maxTransportTime;
|
||||
shuttleTransportTimer = maxTransportTime;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private void UpdateReturning(float deltaTime)
|
||||
@@ -311,13 +217,15 @@ namespace Barotrauma.Networking
|
||||
shuttleSteering.SetDestinationLevelStart();
|
||||
}
|
||||
|
||||
foreach (Door door in shuttleDoors)
|
||||
{
|
||||
if (door.IsOpen) door.SetState(false,false,true);
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
var server = networkMember as GameServer;
|
||||
if (server == null) return;
|
||||
|
||||
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));
|
||||
@@ -350,46 +258,15 @@ namespace Barotrauma.Networking
|
||||
GameServer.Log("The respawn shuttle has left.", ServerLog.MessageType.Spawning);
|
||||
server.CreateEntityEvent(this);
|
||||
|
||||
respawnTimer = server.RespawnInterval;
|
||||
respawnTimer = server.ServerSettings.RespawnInterval;
|
||||
CountdownStarted = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void DispatchShuttle()
|
||||
{
|
||||
var server = networkMember as GameServer;
|
||||
if (server == null) return;
|
||||
|
||||
if (respawnShuttle != null)
|
||||
{
|
||||
state = State.Transporting;
|
||||
server.CreateEntityEvent(this);
|
||||
|
||||
ResetShuttle();
|
||||
|
||||
if (shuttleSteering != null)
|
||||
{
|
||||
shuttleSteering.TargetVelocity = Vector2.Zero;
|
||||
}
|
||||
|
||||
GameServer.Log("Dispatching the respawn shuttle.", ServerLog.MessageType.Spawning);
|
||||
|
||||
RespawnCharacters();
|
||||
|
||||
CoroutineManager.StopCoroutines("forcepos");
|
||||
CoroutineManager.StartCoroutine(ForceShuttleToPos(Level.Loaded.StartPosition - Vector2.UnitY * Level.ShaftHeight, 100.0f), "forcepos");
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.Waiting;
|
||||
GameServer.Log("Respawning everyone in main sub.", ServerLog.MessageType.Spawning);
|
||||
server.CreateEntityEvent(this);
|
||||
|
||||
RespawnCharacters();
|
||||
}
|
||||
}
|
||||
|
||||
partial void DispatchShuttle();
|
||||
|
||||
private IEnumerable<object> ForceShuttleToPos(Vector2 position, float speed)
|
||||
{
|
||||
if (respawnShuttle == null)
|
||||
@@ -468,8 +345,10 @@ namespace Barotrauma.Networking
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.Submarine != respawnShuttle) continue;
|
||||
|
||||
|
||||
#if CLIENT
|
||||
if (Character.Controlled == c) Character.Controlled = null;
|
||||
#endif
|
||||
c.Kill(CauseOfDeathType.Unknown, null, true);
|
||||
c.Enabled = false;
|
||||
|
||||
@@ -492,133 +371,10 @@ namespace Barotrauma.Networking
|
||||
|
||||
}
|
||||
|
||||
partial void RespawnCharactersProjSpecific();
|
||||
public void RespawnCharacters()
|
||||
{
|
||||
var server = networkMember as GameServer;
|
||||
if (server == null) return;
|
||||
|
||||
var respawnSub = respawnShuttle ?? Submarine.MainSub;
|
||||
|
||||
var clients = GetClientsToRespawn();
|
||||
foreach (Client c in clients)
|
||||
{
|
||||
//all characters are in Team 1 in game modes/missions with only one team.
|
||||
//if at some point we add a game mode with multiple teams where respawning is possible, this needs to be reworked
|
||||
c.TeamID = 1;
|
||||
if (c.CharacterInfo == null) c.CharacterInfo = new CharacterInfo(Character.HumanConfigFile, c.Name);
|
||||
}
|
||||
List<CharacterInfo> characterInfos = clients.Select(c => c.CharacterInfo).ToList();
|
||||
|
||||
var botsToSpawn = GetBotsToRespawn();
|
||||
characterInfos.AddRange(botsToSpawn);
|
||||
|
||||
if (server.Character != null && server.Character.IsDead)
|
||||
{
|
||||
characterInfos.Add(server.CharacterInfo);
|
||||
}
|
||||
|
||||
server.AssignJobs(clients, server.Character != null && server.Character.IsDead);
|
||||
foreach (Client c in clients)
|
||||
{
|
||||
c.CharacterInfo.Job = new Job(c.AssignedJob);
|
||||
}
|
||||
|
||||
//the spawnpoints where the characters will spawn
|
||||
var shuttleSpawnPoints = WayPoint.SelectCrewSpawnPoints(characterInfos, respawnSub);
|
||||
//the spawnpoints where they would spawn if they were spawned inside the main sub
|
||||
//(in order to give them appropriate ID card tags)
|
||||
var mainSubSpawnPoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSub);
|
||||
|
||||
ItemPrefab divingSuitPrefab = MapEntityPrefab.Find(null, "divingsuit") as ItemPrefab;
|
||||
ItemPrefab oxyPrefab = MapEntityPrefab.Find(null, "oxygentank") as ItemPrefab;
|
||||
ItemPrefab scooterPrefab = MapEntityPrefab.Find(null, "underwaterscooter") as ItemPrefab;
|
||||
ItemPrefab batteryPrefab = MapEntityPrefab.Find(null, "batterycell") as ItemPrefab;
|
||||
|
||||
var cargoSp = WayPoint.WayPointList.Find(wp => wp.Submarine == respawnSub && wp.SpawnType == SpawnType.Cargo);
|
||||
|
||||
for (int i = 0; i < characterInfos.Count; i++)
|
||||
{
|
||||
bool myCharacter = false;
|
||||
#if CLIENT
|
||||
myCharacter = i >= clients.Count + botsToSpawn.Count;
|
||||
#endif
|
||||
bool bot = i >= clients.Count && !myCharacter;
|
||||
|
||||
var character = Character.Create(characterInfos[i], shuttleSpawnPoints[i].WorldPosition, characterInfos[i].Name, !myCharacter && !bot, bot);
|
||||
character.TeamID = 1;
|
||||
|
||||
#if CLIENT
|
||||
GameMain.GameSession.CrewManager.AddCharacter(character);
|
||||
|
||||
if (myCharacter)
|
||||
{
|
||||
server.Character = character;
|
||||
Character.Controlled = character;
|
||||
|
||||
GameMain.LightManager.LosEnabled = true;
|
||||
GameServer.Log(string.Format("Respawning {0} (host) as {1}", character.Name, characterInfos[i].Job.Name), ServerLog.MessageType.Spawning);
|
||||
}
|
||||
#endif
|
||||
if (!myCharacter)
|
||||
{
|
||||
if (bot)
|
||||
{
|
||||
GameServer.Log(string.Format("Respawning bot {0} as {1}", character.Info.Name, characterInfos[i].Job.Name), ServerLog.MessageType.Spawning);
|
||||
}
|
||||
else
|
||||
{
|
||||
clients[i].Character = character;
|
||||
character.OwnerClientIP = clients[i].Connection.RemoteEndPoint.Address.ToString();
|
||||
character.OwnerClientName = clients[i].Name;
|
||||
GameServer.Log(string.Format("Respawning {0} ({1}) as {2}", clients[i].Name, clients[i].Connection?.RemoteEndPoint?.Address, characterInfos[i].Job.Name), ServerLog.MessageType.Spawning);
|
||||
}
|
||||
}
|
||||
|
||||
if (divingSuitPrefab != null && oxyPrefab != null && respawnShuttle != null)
|
||||
{
|
||||
Vector2 pos = cargoSp == null ? character.Position : cargoSp.Position;
|
||||
if (divingSuitPrefab != null && oxyPrefab != null)
|
||||
{
|
||||
var divingSuit = new Item(divingSuitPrefab, pos, respawnSub);
|
||||
Spawner.CreateNetworkEvent(divingSuit, false);
|
||||
respawnItems.Add(divingSuit);
|
||||
|
||||
var oxyTank = new Item(oxyPrefab, pos, respawnSub);
|
||||
Spawner.CreateNetworkEvent(oxyTank, false);
|
||||
divingSuit.Combine(oxyTank);
|
||||
respawnItems.Add(oxyTank);
|
||||
}
|
||||
|
||||
if (scooterPrefab != null && batteryPrefab != null)
|
||||
{
|
||||
var scooter = new Item(scooterPrefab, pos, respawnSub);
|
||||
Spawner.CreateNetworkEvent(scooter, false);
|
||||
|
||||
var battery = new Item(batteryPrefab, pos, respawnSub);
|
||||
Spawner.CreateNetworkEvent(battery, false);
|
||||
|
||||
scooter.Combine(battery);
|
||||
respawnItems.Add(scooter);
|
||||
respawnItems.Add(battery);
|
||||
}
|
||||
}
|
||||
|
||||
//give the character the items they would've gotten if they had spawned in the main sub
|
||||
character.GiveJobItems(mainSubSpawnPoints[i]);
|
||||
|
||||
//add the ID card tags they should've gotten when spawning in the shuttle
|
||||
foreach (Item item in character.Inventory.Items)
|
||||
{
|
||||
if (item == null || item.Prefab.Name != "ID Card") continue;
|
||||
foreach (string s in shuttleSpawnPoints[i].IdCardTags)
|
||||
{
|
||||
item.AddTag(s);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(shuttleSpawnPoints[i].IdCardDesc))
|
||||
item.Description = shuttleSpawnPoints[i].IdCardDesc;
|
||||
}
|
||||
}
|
||||
|
||||
RespawnCharactersProjSpecific();
|
||||
}
|
||||
|
||||
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
|
||||
|
||||
@@ -46,13 +46,13 @@ namespace Barotrauma.Networking
|
||||
|
||||
private readonly string[] messageTypeName =
|
||||
{
|
||||
"Chat message",
|
||||
"Item interaction",
|
||||
"Inventory usage",
|
||||
"Attack & death",
|
||||
"ChatMessage",
|
||||
"ItemInteraction",
|
||||
"InventoryUsage",
|
||||
"AttackDeath",
|
||||
"Spawning",
|
||||
"Server message",
|
||||
"Console usage",
|
||||
"ServerMessage",
|
||||
"ConsoleUsage",
|
||||
"Error"
|
||||
};
|
||||
|
||||
@@ -64,7 +64,6 @@ namespace Barotrauma.Networking
|
||||
|
||||
private int unsavedLineCount;
|
||||
|
||||
private string msgFilter;
|
||||
private bool[] msgTypeHidden = new bool[Enum.GetValues(typeof(MessageType)).Length];
|
||||
|
||||
public int LinesPerFile
|
||||
@@ -86,7 +85,7 @@ namespace Barotrauma.Networking
|
||||
//string logLine = "[" + DateTime.Now.ToLongTimeString() + "] " + line;
|
||||
|
||||
#if SERVER
|
||||
DebugConsole.NewMessage(line, Color.White); //TODO: REMOVE
|
||||
DebugConsole.NewMessage(line, messageColor[(int)messageType]); //TODO: REMOVE
|
||||
#endif
|
||||
|
||||
var newText = new LogMessage(line, messageType);
|
||||
|
||||
@@ -0,0 +1,704 @@
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
enum SelectionMode
|
||||
{
|
||||
Manual = 0, Random = 1, Vote = 2
|
||||
}
|
||||
|
||||
enum YesNoMaybe
|
||||
{
|
||||
No = 0, Maybe = 1, Yes = 2
|
||||
}
|
||||
|
||||
enum BotSpawnMode
|
||||
{
|
||||
Normal, Fill
|
||||
}
|
||||
|
||||
partial class ServerSettings : ISerializableEntity
|
||||
{
|
||||
[Flags]
|
||||
public enum NetFlags : byte
|
||||
{
|
||||
Name = 0x1,
|
||||
Message = 0x2,
|
||||
Properties = 0x4,
|
||||
Misc = 0x8,
|
||||
LevelSeed = 0x10
|
||||
}
|
||||
|
||||
public static readonly string PermissionPresetFile = "Data" + Path.DirectorySeparatorChar + "permissionpresets.xml";
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return "ServerSettings"; }
|
||||
}
|
||||
|
||||
public class SavedClientPermission
|
||||
{
|
||||
public readonly string IP;
|
||||
public readonly ulong SteamID;
|
||||
public readonly string Name;
|
||||
public List<DebugConsole.Command> PermittedCommands;
|
||||
|
||||
public ClientPermissions Permissions;
|
||||
|
||||
public SavedClientPermission(string name, IPAddress ip, ClientPermissions permissions, List<DebugConsole.Command> permittedCommands)
|
||||
{
|
||||
this.Name = name;
|
||||
this.IP = ip.IsIPv4MappedToIPv6 ? ip.MapToIPv4().ToString() : ip.ToString();
|
||||
this.Permissions = permissions;
|
||||
this.PermittedCommands = permittedCommands;
|
||||
}
|
||||
public SavedClientPermission(string name, string ip, ClientPermissions permissions, List<DebugConsole.Command> permittedCommands)
|
||||
{
|
||||
this.Name = name;
|
||||
this.IP = ip;
|
||||
|
||||
this.Permissions = permissions;
|
||||
this.PermittedCommands = permittedCommands;
|
||||
}
|
||||
public SavedClientPermission(string name, ulong steamID, ClientPermissions permissions, List<DebugConsole.Command> permittedCommands)
|
||||
{
|
||||
this.Name = name;
|
||||
this.SteamID = steamID;
|
||||
|
||||
this.Permissions = permissions;
|
||||
this.PermittedCommands = permittedCommands;
|
||||
}
|
||||
}
|
||||
|
||||
partial class NetPropertyData
|
||||
{
|
||||
private SerializableProperty property;
|
||||
private string typeString;
|
||||
|
||||
private ServerSettings serverSettings;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return property.Name; }
|
||||
}
|
||||
|
||||
public object Value
|
||||
{
|
||||
get { return property.GetValue(serverSettings); }
|
||||
}
|
||||
|
||||
public NetPropertyData(ServerSettings serverSettings, SerializableProperty property, string typeString)
|
||||
{
|
||||
this.property = property;
|
||||
this.typeString = typeString;
|
||||
this.serverSettings = serverSettings;
|
||||
}
|
||||
|
||||
public void Read(NetBuffer msg)
|
||||
{
|
||||
long oldPos = msg.Position;
|
||||
UInt32 size = msg.ReadVariableUInt32();
|
||||
|
||||
float x; float y; float z; float w;
|
||||
byte r; byte g; byte b; byte a;
|
||||
int ix; int iy; int width; int height;
|
||||
|
||||
switch (typeString)
|
||||
{
|
||||
case "float":
|
||||
if (size != 4) break;
|
||||
property.SetValue(serverSettings, msg.ReadFloat());
|
||||
return;
|
||||
case "vector2":
|
||||
if (size != 8) break;
|
||||
x = msg.ReadFloat();
|
||||
y = msg.ReadFloat();
|
||||
property.SetValue(serverSettings, new Vector2(x, y));
|
||||
return;
|
||||
case "vector3":
|
||||
if (size != 12) break;
|
||||
x = msg.ReadFloat();
|
||||
y = msg.ReadFloat();
|
||||
z = msg.ReadFloat();
|
||||
property.SetValue(serverSettings, new Vector3(x, y, z));
|
||||
return;
|
||||
case "vector4":
|
||||
if (size != 16) break;
|
||||
x = msg.ReadFloat();
|
||||
y = msg.ReadFloat();
|
||||
z = msg.ReadFloat();
|
||||
w = msg.ReadFloat();
|
||||
property.SetValue(serverSettings, new Vector4(x, y, z, w));
|
||||
return;
|
||||
case "color":
|
||||
if (size != 4) break;
|
||||
r = msg.ReadByte();
|
||||
g = msg.ReadByte();
|
||||
b = msg.ReadByte();
|
||||
a = msg.ReadByte();
|
||||
property.SetValue(serverSettings, new Color(r, g, b, a));
|
||||
return;
|
||||
case "rectangle":
|
||||
if (size != 16) break;
|
||||
ix = msg.ReadInt32();
|
||||
iy = msg.ReadInt32();
|
||||
width = msg.ReadInt32();
|
||||
height = msg.ReadInt32();
|
||||
property.SetValue(serverSettings, new Rectangle(ix, iy, width, height));
|
||||
return;
|
||||
default:
|
||||
msg.Position = oldPos; //reset position to properly read the string
|
||||
string incVal = msg.ReadString();
|
||||
property.TrySetValue(serverSettings, incVal);
|
||||
return;
|
||||
}
|
||||
|
||||
//size didn't match: skip this
|
||||
msg.Position += 8 * size;
|
||||
}
|
||||
|
||||
public void Write(NetBuffer msg, object overrideValue = null)
|
||||
{
|
||||
if (overrideValue == null) overrideValue = property.GetValue(serverSettings);
|
||||
switch (typeString)
|
||||
{
|
||||
case "float":
|
||||
msg.WriteVariableUInt32(4);
|
||||
msg.Write((float)overrideValue);
|
||||
break;
|
||||
case "vector2":
|
||||
msg.WriteVariableUInt32(8);
|
||||
msg.Write(((Vector2)overrideValue).X);
|
||||
msg.Write(((Vector2)overrideValue).Y);
|
||||
break;
|
||||
case "vector3":
|
||||
msg.WriteVariableUInt32(12);
|
||||
msg.Write(((Vector3)overrideValue).X);
|
||||
msg.Write(((Vector3)overrideValue).Y);
|
||||
msg.Write(((Vector3)overrideValue).Z);
|
||||
break;
|
||||
case "vector4":
|
||||
msg.WriteVariableUInt32(16);
|
||||
msg.Write(((Vector4)overrideValue).X);
|
||||
msg.Write(((Vector4)overrideValue).Y);
|
||||
msg.Write(((Vector4)overrideValue).Z);
|
||||
msg.Write(((Vector4)overrideValue).W);
|
||||
break;
|
||||
case "color":
|
||||
msg.WriteVariableUInt32(4);
|
||||
msg.Write(((Color)overrideValue).R);
|
||||
msg.Write(((Color)overrideValue).G);
|
||||
msg.Write(((Color)overrideValue).B);
|
||||
msg.Write(((Color)overrideValue).A);
|
||||
break;
|
||||
case "rectangle":
|
||||
msg.WriteVariableUInt32(16);
|
||||
msg.Write(((Rectangle)overrideValue).X);
|
||||
msg.Write(((Rectangle)overrideValue).Y);
|
||||
msg.Write(((Rectangle)overrideValue).Width);
|
||||
msg.Write(((Rectangle)overrideValue).Height);
|
||||
break;
|
||||
default:
|
||||
string strVal = overrideValue.ToString();
|
||||
|
||||
msg.Write(strVal);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
Dictionary<UInt32,NetPropertyData> netProperties;
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
public ServerSettings(string serverName, int port, int queryPort, int maxPlayers, bool isPublic, bool enableUPnP)
|
||||
{
|
||||
ServerLog = new ServerLog(serverName);
|
||||
|
||||
Voting = new Voting();
|
||||
|
||||
Whitelist = new WhiteList();
|
||||
BanList = new BanList();
|
||||
|
||||
ExtraCargo = new Dictionary<ItemPrefab, int>();
|
||||
|
||||
PermissionPreset.LoadAll(PermissionPresetFile);
|
||||
InitProjSpecific();
|
||||
|
||||
ServerName = serverName;
|
||||
Port = port;
|
||||
QueryPort = queryPort;
|
||||
//EnableUPnP = enableUPnP;
|
||||
this.maxPlayers = maxPlayers;
|
||||
this.isPublic = isPublic;
|
||||
|
||||
netProperties = new Dictionary<UInt32, NetPropertyData>();
|
||||
|
||||
using (MD5 md5 = MD5.Create())
|
||||
{
|
||||
var saveProperties = SerializableProperty.GetProperties<Serialize>(this);
|
||||
foreach (var property in saveProperties)
|
||||
{
|
||||
object value = property.GetValue(this);
|
||||
if (value == null) continue;
|
||||
|
||||
string typeName = SerializableProperty.GetSupportedTypeName(value.GetType());
|
||||
if (typeName != null || property.PropertyType.IsEnum)
|
||||
{
|
||||
NetPropertyData netPropertyData = new NetPropertyData(this, property, typeName);
|
||||
|
||||
UInt32 key = ToolBox.StringToUInt32Hash(property.Name, md5);
|
||||
|
||||
if (netProperties.ContainsKey(key)) throw new Exception("Hashing collision in ServerSettings.netProperties: " + netProperties[key] + " has same key as " + property.Name + " (" + key.ToString() + ")");
|
||||
|
||||
netProperties.Add(key, netPropertyData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string ServerName;
|
||||
|
||||
public string ServerMessageText;
|
||||
|
||||
public int Port;
|
||||
|
||||
public int QueryPort;
|
||||
|
||||
public ServerLog ServerLog;
|
||||
|
||||
public Voting Voting;
|
||||
|
||||
public Dictionary<string, bool> MonsterEnabled { get; private set; }
|
||||
|
||||
public Dictionary<ItemPrefab, int> ExtraCargo { get; private set; }
|
||||
|
||||
private TimeSpan sparseUpdateInterval = new TimeSpan(0, 0, 0, 3);
|
||||
private float selectedLevelDifficulty;
|
||||
private string password;
|
||||
|
||||
public float AutoRestartTimer;
|
||||
|
||||
private bool autoRestart;
|
||||
|
||||
public bool isPublic;
|
||||
|
||||
private int maxPlayers;
|
||||
|
||||
public List<SavedClientPermission> ClientPermissions { get; private set; } = new List<SavedClientPermission>();
|
||||
|
||||
public WhiteList Whitelist { get; private set; }
|
||||
|
||||
[Serialize(20, true)]
|
||||
public int TickRate
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool RandomizeSeed
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(300.0f, true)]
|
||||
public float RespawnInterval
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(180.0f, true)]
|
||||
public float MaxTransportTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(0.2f, true)]
|
||||
public float MinRespawnRatio
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
|
||||
[Serialize(60.0f, true)]
|
||||
public float AutoRestartInterval
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, true)]
|
||||
public bool StartWhenClientsReady
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(0.8f, true)]
|
||||
public float StartWhenClientsReadyRatio
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool AllowSpectating
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool EndRoundAtLevelEnd
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool SaveServerLogs
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool AllowRagdollButton
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool AllowFileTransfers
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool VoiceChatEnabled
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(800, true)]
|
||||
private int LinesPerLogFile
|
||||
{
|
||||
get
|
||||
{
|
||||
return ServerLog.LinesPerFile;
|
||||
}
|
||||
set
|
||||
{
|
||||
ServerLog.LinesPerFile = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool AutoRestart
|
||||
{
|
||||
get { return autoRestart; }
|
||||
set
|
||||
{
|
||||
autoRestart = value;
|
||||
|
||||
AutoRestartTimer = autoRestart ? AutoRestartInterval : 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasPassword
|
||||
{
|
||||
get { return !string.IsNullOrEmpty(password); }
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool AllowVoteKick
|
||||
{
|
||||
get
|
||||
{
|
||||
return Voting.AllowVoteKick;
|
||||
}
|
||||
set
|
||||
{
|
||||
Voting.AllowVoteKick = value;
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool AllowEndVoting
|
||||
{
|
||||
get
|
||||
{
|
||||
return Voting.AllowEndVoting;
|
||||
}
|
||||
set
|
||||
{
|
||||
Voting.AllowEndVoting = value;
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool AllowRespawn
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0, true)]
|
||||
public int BotCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(16, true)]
|
||||
public int MaxBotCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public BotSpawnMode BotSpawnMode
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public float SelectedLevelDifficulty
|
||||
{
|
||||
get { return selectedLevelDifficulty; }
|
||||
set { selectedLevelDifficulty = MathHelper.Clamp(value, 0.0f, 100.0f); }
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool AllowDisguises
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public YesNoMaybe TraitorsEnabled
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(SelectionMode.Manual, true)]
|
||||
public SelectionMode SubSelectionMode { get; private set; }
|
||||
|
||||
[Serialize(SelectionMode.Manual, true)]
|
||||
public SelectionMode ModeSelectionMode { get; private set; }
|
||||
|
||||
public BanList BanList { get; private set; }
|
||||
|
||||
[Serialize(0.6f, true)]
|
||||
public float EndVoteRequiredRatio
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(0.6f, true)]
|
||||
public float KickVoteRequiredRatio
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(30.0f, true)]
|
||||
public float KillDisconnectedTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(120.0f, true)]
|
||||
public float KickAFKTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool TraitorUseRatio
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(0.2f, true)]
|
||||
public float TraitorRatio
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(false, true)]
|
||||
public bool KarmaEnabled
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("sandbox", true)]
|
||||
public string GameModeIdentifier
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("Random", true)]
|
||||
public string MissionType
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public int MaxPlayers
|
||||
{
|
||||
get { return maxPlayers; }
|
||||
}
|
||||
|
||||
public List<MissionType> AllowedRandomMissionTypes
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(60f, true)]
|
||||
public float AutoBanTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(360f, true)]
|
||||
public float MaxAutoBanTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public void SetPassword(string password)
|
||||
{
|
||||
this.password = Encoding.UTF8.GetString(NetUtility.ComputeSHAHash(Encoding.UTF8.GetBytes(password)));
|
||||
}
|
||||
|
||||
public bool IsPasswordCorrect(string input, int nonce)
|
||||
{
|
||||
if (!HasPassword) return true;
|
||||
string saltedPw = password;
|
||||
saltedPw = saltedPw + Convert.ToString(nonce);
|
||||
saltedPw = Encoding.UTF8.GetString(NetUtility.ComputeSHAHash(Encoding.UTF8.GetBytes(saltedPw)));
|
||||
return input == saltedPw;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A list of int pairs that represent the ranges of UTF-16 codes allowed in client names
|
||||
/// </summary>
|
||||
public List<Pair<int, int>> AllowedClientNameChars
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new List<Pair<int, int>>();
|
||||
|
||||
public void ReadMonsterEnabled(NetBuffer inc)
|
||||
{
|
||||
//monster spawn settings
|
||||
if (MonsterEnabled == null)
|
||||
{
|
||||
List<string> monsterNames1 = GameMain.Instance.GetFilesOfType(ContentType.Character).ToList();
|
||||
for (int i = 0; i < monsterNames1.Count; i++)
|
||||
{
|
||||
monsterNames1[i] = Path.GetFileName(Path.GetDirectoryName(monsterNames1[i]));
|
||||
}
|
||||
|
||||
MonsterEnabled = new Dictionary<string, bool>();
|
||||
foreach (string s in monsterNames1)
|
||||
{
|
||||
if (!MonsterEnabled.ContainsKey(s)) MonsterEnabled.Add(s, true);
|
||||
}
|
||||
}
|
||||
|
||||
List<string> monsterNames = MonsterEnabled.Keys.ToList();
|
||||
foreach (string s in monsterNames)
|
||||
{
|
||||
MonsterEnabled[s] = inc.ReadBoolean();
|
||||
}
|
||||
inc.ReadPadBits();
|
||||
}
|
||||
|
||||
public void WriteMonsterEnabled(NetBuffer msg, Dictionary<string, bool> monsterEnabled = null)
|
||||
{
|
||||
//monster spawn settings
|
||||
if (monsterEnabled == null) monsterEnabled = MonsterEnabled;
|
||||
|
||||
List<string> monsterNames = monsterEnabled.Keys.ToList();
|
||||
foreach (string s in monsterNames)
|
||||
{
|
||||
msg.Write(monsterEnabled[s]);
|
||||
}
|
||||
msg.WritePadBits();
|
||||
}
|
||||
|
||||
public bool ReadExtraCargo(NetBuffer msg)
|
||||
{
|
||||
bool changed = false;
|
||||
UInt32 count = msg.ReadUInt32();
|
||||
if (ExtraCargo == null || count != ExtraCargo.Count) changed = true;
|
||||
Dictionary<ItemPrefab, int> extraCargo = new Dictionary<ItemPrefab, int>();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
string prefabName = msg.ReadString();
|
||||
byte amount = msg.ReadByte();
|
||||
ItemPrefab ip = MapEntityPrefab.List.Find(p => p is ItemPrefab && p.Name.Equals(prefabName, StringComparison.InvariantCulture)) as ItemPrefab;
|
||||
if (ip != null && amount > 0)
|
||||
{
|
||||
if (changed || !ExtraCargo.ContainsKey(ip) || ExtraCargo[ip] != amount) changed = true;
|
||||
extraCargo.Add(ip, amount);
|
||||
}
|
||||
}
|
||||
if (changed) ExtraCargo = extraCargo;
|
||||
return changed;
|
||||
}
|
||||
|
||||
public void WriteExtraCargo(NetBuffer msg)
|
||||
{
|
||||
if (ExtraCargo == null)
|
||||
{
|
||||
msg.Write((UInt32)0);
|
||||
return;
|
||||
}
|
||||
|
||||
msg.Write((UInt32)ExtraCargo.Count);
|
||||
foreach (KeyValuePair<ItemPrefab, int> kvp in ExtraCargo)
|
||||
{
|
||||
msg.Write(kvp.Key.Name); msg.Write((byte)kvp.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,11 @@
|
||||
using Barotrauma.Networking;
|
||||
using Facepunch.Steamworks;
|
||||
using Microsoft.Xna.Framework;
|
||||
using RestSharp.Extensions.MonoHttp;
|
||||
using Facepunch.Steamworks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Steam
|
||||
{
|
||||
class SteamManager
|
||||
partial class SteamManager
|
||||
{
|
||||
#if DEBUG
|
||||
public static bool USE_STEAM
|
||||
@@ -72,165 +66,10 @@ namespace Barotrauma.Steam
|
||||
if (!USE_STEAM) return;
|
||||
instance = new SteamManager();
|
||||
}
|
||||
|
||||
private SteamManager()
|
||||
{
|
||||
#if SERVER
|
||||
return;
|
||||
#endif
|
||||
|
||||
try
|
||||
{
|
||||
client = new Facepunch.Steamworks.Client(AppID);
|
||||
isInitialized = client.IsSubscribed && client.IsValid;
|
||||
|
||||
if (isInitialized)
|
||||
{
|
||||
DebugConsole.Log("Logged in as " + client.Username + " (SteamID " + client.SteamId + ")");
|
||||
}
|
||||
}
|
||||
catch (DllNotFoundException e)
|
||||
{
|
||||
isInitialized = false;
|
||||
#if CLIENT
|
||||
new Barotrauma.GUIMessageBox(TextManager.Get("Error"), TextManager.Get("SteamDllNotFound"));
|
||||
#else
|
||||
DebugConsole.ThrowError("Initializing Steam client failed (steam_api64.dll not found).", e);
|
||||
#endif
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
isInitialized = false;
|
||||
#if CLIENT
|
||||
new Barotrauma.GUIMessageBox(TextManager.Get("Error"), TextManager.Get("SteamClientInitFailed"));
|
||||
#else
|
||||
DebugConsole.ThrowError("Initializing Steam client failed.", e);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!isInitialized)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
Facepunch.Steamworks.Client.Instance.Dispose();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (GameSettings.VerboseLogging) DebugConsole.ThrowError("Disposing Steam client failed.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region Server
|
||||
|
||||
public static bool CreateServer(Networking.GameServer server, bool isPublic)
|
||||
{
|
||||
|
||||
#if !SERVER
|
||||
if (instance == null || !instance.isInitialized)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
ServerInit options = new ServerInit("Barotrauma", "Barotrauma")
|
||||
{
|
||||
GamePort = (ushort)server.Port,
|
||||
QueryPort = (ushort)server.QueryPort
|
||||
};
|
||||
|
||||
instance.server = new Server(AppID, options, isPublic);
|
||||
if (!instance.server.IsValid)
|
||||
{
|
||||
instance.server.Dispose();
|
||||
instance.server = null;
|
||||
DebugConsole.ThrowError("Initializing Steam server failed.");
|
||||
return false;
|
||||
}
|
||||
#if SERVER
|
||||
instance.isInitialized = true;
|
||||
#endif
|
||||
RefreshServerDetails(server);
|
||||
|
||||
instance.server.Auth.OnAuthChange = server.OnAuthChange;
|
||||
Instance.server.LogOnAnonymous();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool RefreshServerDetails(Networking.GameServer server)
|
||||
{
|
||||
if (instance == null || !instance.isInitialized)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// These server state variables may be changed at any time. Note that there is no lnoger a mechanism
|
||||
// to send the player count. The player count is maintained by steam and you should use the player
|
||||
// creation/authentication functions to maintain your player count.
|
||||
instance.server.ServerName = server.Name;
|
||||
instance.server.MaxPlayers = server.MaxPlayers;
|
||||
instance.server.Passworded = server.HasPassword;
|
||||
Instance.server.SetKey("message", GameMain.NetLobbyScreen.ServerMessageText);
|
||||
Instance.server.SetKey("version", GameMain.Version.ToString());
|
||||
Instance.server.SetKey("contentpackage", string.Join(",", GameMain.Config.SelectedContentPackages.Select(cp => cp.Name)));
|
||||
Instance.server.SetKey("contentpackagehash", string.Join(",", GameMain.Config.SelectedContentPackages.Select(cp => cp.MD5hash.Hash)));
|
||||
Instance.server.SetKey("contentpackageurl", string.Join(",", GameMain.Config.SelectedContentPackages.Select(cp => cp.SteamWorkshopUrl ?? "")));
|
||||
Instance.server.SetKey("usingwhitelist", (server.WhiteList != null && server.WhiteList.Enabled).ToString());
|
||||
Instance.server.SetKey("modeselectionmode", server.ModeSelectionMode.ToString());
|
||||
Instance.server.SetKey("subselectionmode", server.SubSelectionMode.ToString());
|
||||
Instance.server.SetKey("allowspectating", server.AllowSpectating.ToString());
|
||||
Instance.server.SetKey("allowrespawn", server.AllowRespawn.ToString());
|
||||
Instance.server.SetKey("traitors", server.TraitorsEnabled.ToString());
|
||||
Instance.server.SetKey("gamestarted", server.GameStarted.ToString());
|
||||
Instance.server.SetKey("gamemode", server.GameModeIdentifier);
|
||||
|
||||
#if SERVER
|
||||
instance.server.DedicatedServer = true;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool StartAuthSession(byte[] authTicketData, ulong clientSteamID)
|
||||
{
|
||||
if (instance == null || !instance.isInitialized || instance.server == null) return false;
|
||||
|
||||
DebugConsole.Log("SteamManager authenticating Steam client " + clientSteamID);
|
||||
if (!instance.server.Auth.StartSession(authTicketData, clientSteamID))
|
||||
{
|
||||
DebugConsole.Log("Authentication failed");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void StopAuthSession(ulong clientSteamID)
|
||||
{
|
||||
if (instance == null || !instance.isInitialized || instance.server == null) return;
|
||||
|
||||
DebugConsole.Log("SteamManager ending auth session with Steam client " + clientSteamID);
|
||||
instance.server.Auth.EndSession(clientSteamID);
|
||||
}
|
||||
|
||||
public static bool CloseServer()
|
||||
{
|
||||
if (instance == null || !instance.isInitialized || instance.server == null) return false;
|
||||
|
||||
instance.server.Dispose();
|
||||
instance.server = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
public static bool UnlockAchievement(string achievementName)
|
||||
{
|
||||
if (instance == null || !instance.isInitialized)
|
||||
if (instance == null || !instance.isInitialized || instance.client == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -280,770 +119,11 @@ namespace Barotrauma.Steam
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
public static ulong GetSteamID()
|
||||
{
|
||||
if (instance == null || !instance.isInitialized)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return instance.client.SteamId;
|
||||
}
|
||||
|
||||
public static ulong GetWorkshopItemIDFromUrl(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
Uri uri = new Uri(url);
|
||||
string idStr = HttpUtility.ParseQueryString(uri.Query).Get("id");
|
||||
if (ulong.TryParse(idStr, out ulong id))
|
||||
{
|
||||
return id;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to get Workshop item ID from the url \""+url+"\"!", e);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#region Connecting to servers
|
||||
|
||||
public static bool GetServers(Action<Networking.ServerInfo> onServerFound, Action<Networking.ServerInfo> onServerRulesReceived, Action onFinished)
|
||||
{
|
||||
if (instance == null || !instance.isInitialized)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var filter = new ServerList.Filter
|
||||
{
|
||||
{ "appid", AppID.ToString() },
|
||||
{ "gamedir", "Barotrauma" },
|
||||
{ "secure", "1" }
|
||||
};
|
||||
|
||||
//include unresponsive servers in the server list
|
||||
|
||||
//the response is queried using the server's query port, not the game port,
|
||||
//so it may be possible to play on the server even if it doesn't respond to server list queries
|
||||
var query = instance.client.ServerList.Internet(filter);
|
||||
query.OnUpdate += () => { UpdateServerQuery(query, onServerFound, onServerRulesReceived, includeUnresponsive: true); };
|
||||
query.OnFinished = onFinished;
|
||||
|
||||
var localQuery = instance.client.ServerList.Local(filter);
|
||||
localQuery.OnUpdate += () => { UpdateServerQuery(localQuery, onServerFound, onServerRulesReceived, includeUnresponsive: true); };
|
||||
localQuery.OnFinished = onFinished;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void UpdateServerQuery(ServerList.Request query, Action<Networking.ServerInfo> onServerFound, Action<Networking.ServerInfo> onServerRulesReceived, bool includeUnresponsive)
|
||||
{
|
||||
IEnumerable<ServerList.Server> servers = includeUnresponsive ?
|
||||
new List<ServerList.Server>(query.Responded).Concat(query.Unresponsive) :
|
||||
query.Responded;
|
||||
|
||||
foreach (ServerList.Server s in servers)
|
||||
{
|
||||
if (!ValidateServerInfo(s)) { continue; }
|
||||
|
||||
bool responded = query.Responded.Contains(s);
|
||||
if (responded)
|
||||
{
|
||||
DebugConsole.Log(s.Name + " responded to server query.");
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.Log(s.Name + " did not respond to server query.");
|
||||
}
|
||||
var serverInfo = new ServerInfo()
|
||||
{
|
||||
ServerName = s.Name,
|
||||
Port = s.ConnectionPort.ToString(),
|
||||
IP = s.Address.ToString(),
|
||||
PlayerCount = s.Players,
|
||||
MaxPlayers = s.MaxPlayers,
|
||||
HasPassword = s.Passworded,
|
||||
RespondedToSteamQuery = responded
|
||||
};
|
||||
serverInfo.PingChecked = true;
|
||||
serverInfo.Ping = s.Ping;
|
||||
if (responded)
|
||||
{
|
||||
s.FetchRules();
|
||||
}
|
||||
s.OnReceivedRules += (bool rulesReceived) =>
|
||||
{
|
||||
if (!rulesReceived || s.Rules == null) { return; }
|
||||
|
||||
if (s.Rules.ContainsKey("message")) serverInfo.ServerMessage = s.Rules["message"];
|
||||
if (s.Rules.ContainsKey("version")) serverInfo.GameVersion = s.Rules["version"];
|
||||
|
||||
if (s.Rules.ContainsKey("contentpackage")) serverInfo.ContentPackageNames.AddRange(s.Rules["contentpackage"].Split(','));
|
||||
if (s.Rules.ContainsKey("contentpackagehash")) serverInfo.ContentPackageHashes.AddRange(s.Rules["contentpackagehash"].Split(','));
|
||||
if (s.Rules.ContainsKey("contentpackageurl")) serverInfo.ContentPackageWorkshopUrls.AddRange(s.Rules["contentpackageurl"].Split(','));
|
||||
|
||||
if (s.Rules.ContainsKey("usingwhitelist")) serverInfo.UsingWhiteList = s.Rules["usingwhitelist"] == "True";
|
||||
if (s.Rules.ContainsKey("modeselectionmode"))
|
||||
{
|
||||
if (Enum.TryParse(s.Rules["modeselectionmode"], out SelectionMode selectionMode)) serverInfo.ModeSelectionMode = selectionMode;
|
||||
}
|
||||
if (s.Rules.ContainsKey("subselectionmode"))
|
||||
{
|
||||
if (Enum.TryParse(s.Rules["subselectionmode"], out SelectionMode selectionMode)) serverInfo.SubSelectionMode = selectionMode;
|
||||
}
|
||||
if (s.Rules.ContainsKey("allowspectating")) serverInfo.AllowSpectating = s.Rules["allowspectating"] == "True";
|
||||
if (s.Rules.ContainsKey("allowrespawn")) serverInfo.AllowRespawn = s.Rules["allowrespawn"] == "True";
|
||||
if (s.Rules.ContainsKey("traitors"))
|
||||
{
|
||||
if (Enum.TryParse(s.Rules["traitors"], out YesNoMaybe traitorsEnabled)) serverInfo.TraitorsEnabled = traitorsEnabled;
|
||||
}
|
||||
|
||||
if (serverInfo.ContentPackageNames.Count != serverInfo.ContentPackageHashes.Count ||
|
||||
serverInfo.ContentPackageHashes.Count != serverInfo.ContentPackageWorkshopUrls.Count)
|
||||
{
|
||||
//invalid contentpackage info
|
||||
serverInfo.ContentPackageNames.Clear();
|
||||
serverInfo.ContentPackageHashes.Clear();
|
||||
}
|
||||
onServerRulesReceived(serverInfo);
|
||||
};
|
||||
|
||||
onServerFound(serverInfo);
|
||||
}
|
||||
query.Responded.Clear();
|
||||
}
|
||||
|
||||
private static bool ValidateServerInfo(ServerList.Server server)
|
||||
{
|
||||
if (string.IsNullOrEmpty(server.Name)) { return false; }
|
||||
if (server.Address == null) { return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Auth.Ticket GetAuthSessionTicket()
|
||||
{
|
||||
if (instance == null || !instance.isInitialized)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return instance.client.Auth.GetAuthSessionTicket();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Workshop
|
||||
|
||||
public const string WorkshopItemStagingFolder = "NewWorkshopItem";
|
||||
public const string WorkshopItemPreviewImageFolder = "Workshop";
|
||||
public const string PreviewImageName = "PreviewImage.png";
|
||||
private const string MetadataFileName = "filelist.xml";
|
||||
private const string DefaultPreviewImagePath = "Content/DefaultWorkshopPreviewImage.png";
|
||||
|
||||
private Sprite defaultPreviewImage;
|
||||
public Sprite DefaultPreviewImage
|
||||
{
|
||||
get
|
||||
{
|
||||
if (defaultPreviewImage == null)
|
||||
{
|
||||
defaultPreviewImage = new Sprite(DefaultPreviewImagePath, sourceRectangle: null);
|
||||
}
|
||||
return defaultPreviewImage;
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetSubscribedWorkshopItems(Action<IList<Workshop.Item>> onItemsFound, List<string> requireTags = null)
|
||||
{
|
||||
if (instance == null || !instance.isInitialized) return;
|
||||
|
||||
var query = instance.client.Workshop.CreateQuery();
|
||||
query.Order = Workshop.Order.RankedByTrend;
|
||||
query.UserId = instance.client.SteamId;
|
||||
query.UserQueryType = Workshop.UserQueryType.Subscribed;
|
||||
query.UploaderAppId = AppID;
|
||||
if (requireTags != null) query.RequireTags = requireTags;
|
||||
query.Run();
|
||||
query.OnResult += (Workshop.Query q) =>
|
||||
{
|
||||
onItemsFound?.Invoke(q.Items);
|
||||
};
|
||||
}
|
||||
|
||||
public static void GetPopularWorkshopItems(Action<IList<Workshop.Item>> onItemsFound, int amount, List<string> requireTags = null)
|
||||
{
|
||||
if (instance == null || !instance.isInitialized) return;
|
||||
|
||||
var query = instance.client.Workshop.CreateQuery();
|
||||
query.Order = Workshop.Order.RankedByTrend;
|
||||
query.RankedByTrendDays = 30;
|
||||
query.UploaderAppId = AppID;
|
||||
if (requireTags != null) query.RequireTags = requireTags;
|
||||
query.Run();
|
||||
query.OnResult += (Workshop.Query q) =>
|
||||
{
|
||||
//count the number of each unique tag
|
||||
foreach (var item in q.Items)
|
||||
{
|
||||
foreach (string tag in item.Tags)
|
||||
{
|
||||
if (string.IsNullOrEmpty(tag)) { continue; }
|
||||
string caseInvariantTag = tag.ToLowerInvariant();
|
||||
if (!instance.tagCommonness.ContainsKey(caseInvariantTag))
|
||||
{
|
||||
instance.tagCommonness[caseInvariantTag] = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
instance.tagCommonness[caseInvariantTag]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
//populate the popularTags list with tags sorted by commonness
|
||||
instance.popularTags.Clear();
|
||||
foreach (KeyValuePair<string, int> tagCommonness in instance.tagCommonness)
|
||||
{
|
||||
int i = 0;
|
||||
while (i < instance.popularTags.Count &&
|
||||
instance.tagCommonness[instance.popularTags[i]] > tagCommonness.Value)
|
||||
{
|
||||
i++;
|
||||
}
|
||||
instance.popularTags.Insert(i, tagCommonness.Key);
|
||||
}
|
||||
|
||||
var nonSubscribedItems = q.Items.Where(it => !it.Subscribed && !it.Installed);
|
||||
if (nonSubscribedItems.Count() > amount)
|
||||
{
|
||||
nonSubscribedItems = nonSubscribedItems.Take(amount);
|
||||
}
|
||||
onItemsFound?.Invoke(nonSubscribedItems.ToList());
|
||||
};
|
||||
}
|
||||
|
||||
public static void GetPublishedWorkshopItems(Action<IList<Workshop.Item>> onItemsFound, List<string> requireTags = null)
|
||||
{
|
||||
if (instance == null || !instance.isInitialized) return;
|
||||
|
||||
var query = instance.client.Workshop.CreateQuery();
|
||||
query.Order = Workshop.Order.RankedByPublicationDate;
|
||||
query.UserId = instance.client.SteamId;
|
||||
query.UserQueryType = Workshop.UserQueryType.Published;
|
||||
query.UploaderAppId = AppID;
|
||||
if (requireTags != null) query.RequireTags = requireTags;
|
||||
query.Run();
|
||||
query.OnResult += (Workshop.Query q) =>
|
||||
{
|
||||
onItemsFound?.Invoke(q.Items);
|
||||
};
|
||||
}
|
||||
|
||||
public static void SubscribeToWorkshopItem(string itemUrl)
|
||||
{
|
||||
if (instance == null || !instance.isInitialized) return;
|
||||
|
||||
ulong id = GetWorkshopItemIDFromUrl(itemUrl);
|
||||
if (id == 0) { return; }
|
||||
|
||||
var item = instance.client.Workshop.GetItem(id);
|
||||
if (item == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to find a Steam Workshop item with the ID " + id + ".");
|
||||
return;
|
||||
}
|
||||
|
||||
item.Subscribe();
|
||||
item.Download();
|
||||
}
|
||||
|
||||
public static void SaveToWorkshop(Submarine sub)
|
||||
{
|
||||
if (instance == null || !instance.isInitialized) return;
|
||||
|
||||
Workshop.Editor item;
|
||||
ContentPackage contentPackage;
|
||||
try
|
||||
{
|
||||
CreateWorkshopItemStaging(
|
||||
new List<ContentFile>() { new ContentFile(sub.FilePath, ContentType.None) },
|
||||
out item, out contentPackage);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Creating the workshop item failed.", e);
|
||||
return;
|
||||
}
|
||||
|
||||
item.Description = sub.Description;
|
||||
item.Title = sub.Name;
|
||||
item.Tags.Add("Submarine");
|
||||
|
||||
string subPreviewPath = Path.GetFullPath(Path.Combine(item.Folder, PreviewImageName));
|
||||
#if CLIENT
|
||||
try
|
||||
{
|
||||
using (Stream s = File.Create(subPreviewPath))
|
||||
{
|
||||
sub.PreviewImage.Texture.SaveAsPng(s, (int)sub.PreviewImage.size.X, (int)sub.PreviewImage.size.Y);
|
||||
item.PreviewImage = subPreviewPath;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Saving submarine preview image failed.", e);
|
||||
item.PreviewImage = null;
|
||||
}
|
||||
#endif
|
||||
|
||||
StartPublishItem(contentPackage, item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new folder, copies the specified files there and creates a metadata file with install instructions.
|
||||
/// </summary>
|
||||
public static void CreateWorkshopItemStaging(List<ContentFile> contentFiles, out Workshop.Editor itemEditor, out ContentPackage contentPackage)
|
||||
{
|
||||
var stagingFolder = new DirectoryInfo(WorkshopItemStagingFolder);
|
||||
if (stagingFolder.Exists)
|
||||
{
|
||||
SaveUtil.ClearFolder(stagingFolder.FullName);
|
||||
}
|
||||
else
|
||||
{
|
||||
stagingFolder.Create();
|
||||
}
|
||||
Directory.CreateDirectory(Path.Combine(WorkshopItemStagingFolder, "Submarines"));
|
||||
Directory.CreateDirectory(Path.Combine(WorkshopItemStagingFolder, "Mods"));
|
||||
Directory.CreateDirectory(Path.Combine(WorkshopItemStagingFolder, "Mods", "ModName"));
|
||||
|
||||
itemEditor = instance.client.Workshop.CreateItem(Workshop.ItemType.Community);
|
||||
itemEditor.Visibility = Workshop.Editor.VisibilityType.Public;
|
||||
itemEditor.WorkshopUploadAppId = AppID;
|
||||
itemEditor.Folder = stagingFolder.FullName;
|
||||
|
||||
string previewImagePath = Path.GetFullPath(Path.Combine(itemEditor.Folder, PreviewImageName));
|
||||
File.Copy("Content/DefaultWorkshopPreviewImage.png", previewImagePath);
|
||||
|
||||
//copy content files to the staging folder
|
||||
List<string> copiedFilePaths = new List<string>();
|
||||
foreach (ContentFile file in contentFiles)
|
||||
{
|
||||
string relativePath = UpdaterUtil.GetRelativePath(Path.GetFullPath(file.Path), Environment.CurrentDirectory);
|
||||
string destinationPath = Path.Combine(stagingFolder.FullName, relativePath);
|
||||
//make sure the directory exists
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(destinationPath));
|
||||
File.Copy(file.Path, destinationPath);
|
||||
copiedFilePaths.Add(destinationPath);
|
||||
}
|
||||
System.Diagnostics.Debug.Assert(copiedFilePaths.Count == contentFiles.Count);
|
||||
|
||||
//create a new content package and include the copied files in it
|
||||
contentPackage = ContentPackage.CreatePackage("ContentPackage", Path.Combine(itemEditor.Folder, MetadataFileName), false);
|
||||
for (int i = 0; i < copiedFilePaths.Count; i++)
|
||||
{
|
||||
contentPackage.AddFile(copiedFilePaths[i], contentFiles[i].Type);
|
||||
}
|
||||
|
||||
contentPackage.Save(Path.Combine(stagingFolder.FullName, MetadataFileName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a copy of the specified workshop item in the staging folder and an editor that can be used to edit and update the item
|
||||
/// </summary>
|
||||
public static void CreateWorkshopItemStaging(Workshop.Item existingItem, out Workshop.Editor itemEditor, out ContentPackage contentPackage)
|
||||
{
|
||||
if (!existingItem.Installed)
|
||||
{
|
||||
itemEditor = null;
|
||||
contentPackage = null;
|
||||
DebugConsole.ThrowError("Cannot edit the workshop item \"" + existingItem.Title + "\" because it has not been installed.");
|
||||
return;
|
||||
}
|
||||
|
||||
var stagingFolder = new DirectoryInfo(WorkshopItemStagingFolder);
|
||||
if (stagingFolder.Exists)
|
||||
{
|
||||
SaveUtil.ClearFolder(stagingFolder.FullName);
|
||||
}
|
||||
else
|
||||
{
|
||||
stagingFolder.Create();
|
||||
}
|
||||
|
||||
itemEditor = instance.client.Workshop.EditItem(existingItem.Id);
|
||||
itemEditor.Visibility = Workshop.Editor.VisibilityType.Public;
|
||||
itemEditor.Title = existingItem.Title;
|
||||
itemEditor.Tags = existingItem.Tags.ToList();
|
||||
itemEditor.Description = existingItem.Description;
|
||||
itemEditor.WorkshopUploadAppId = AppID;
|
||||
itemEditor.Folder = stagingFolder.FullName;
|
||||
|
||||
string previewImagePath = Path.GetFullPath(Path.Combine(itemEditor.Folder, PreviewImageName));
|
||||
itemEditor.PreviewImage = previewImagePath;
|
||||
|
||||
using (WebClient client = new WebClient())
|
||||
{
|
||||
if (File.Exists(previewImagePath))
|
||||
{
|
||||
File.Delete(previewImagePath);
|
||||
}
|
||||
client.DownloadFile(new Uri(existingItem.PreviewImageUrl), previewImagePath);
|
||||
}
|
||||
|
||||
ContentPackage tempContentPackage = new ContentPackage(Path.Combine(existingItem.Directory.FullName, MetadataFileName));
|
||||
if (File.Exists(tempContentPackage.Path))
|
||||
{
|
||||
string newContentPackagePath = Path.Combine(WorkshopItemStagingFolder, MetadataFileName);
|
||||
File.Copy(tempContentPackage.Path, newContentPackagePath, overwrite: true);
|
||||
contentPackage = new ContentPackage(newContentPackagePath);
|
||||
foreach (ContentFile contentFile in tempContentPackage.Files)
|
||||
{
|
||||
string sourceFile = Path.Combine(existingItem.Directory.FullName, contentFile.Path);
|
||||
if (!File.Exists(sourceFile)) { continue; }
|
||||
//make sure the destination directory exists
|
||||
string destinationPath = Path.Combine(WorkshopItemStagingFolder, contentFile.Path);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(destinationPath));
|
||||
File.Copy(sourceFile, destinationPath, overwrite: true);
|
||||
contentPackage.AddFile(contentFile.Path, contentFile.Type);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
contentPackage = ContentPackage.CreatePackage(existingItem.Title, Path.Combine(WorkshopItemStagingFolder, MetadataFileName), false);
|
||||
contentPackage.Save(contentPackage.Path);
|
||||
}
|
||||
}
|
||||
|
||||
public static void StartPublishItem(ContentPackage contentPackage, Workshop.Editor item)
|
||||
{
|
||||
if (instance == null || !instance.isInitialized) return;
|
||||
|
||||
if (string.IsNullOrEmpty(item.Title))
|
||||
{
|
||||
DebugConsole.ThrowError("Cannot publish workshop item - title not set.");
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrEmpty(item.Folder))
|
||||
{
|
||||
DebugConsole.ThrowError("Cannot publish workshop item \"" + item.Title + "\" - folder not set.");
|
||||
return;
|
||||
}
|
||||
|
||||
contentPackage.Name = item.Title;
|
||||
contentPackage.GameVersion = GameMain.Version;
|
||||
contentPackage.Save(Path.Combine(WorkshopItemStagingFolder, MetadataFileName));
|
||||
|
||||
if (File.Exists(PreviewImageName)) File.Delete(PreviewImageName);
|
||||
//move the preview image out of the staging folder, it does not need to be included in the folder sent to Workshop
|
||||
File.Move(Path.GetFullPath(Path.Combine(item.Folder, PreviewImageName)), PreviewImageName);
|
||||
item.PreviewImage = Path.GetFullPath(PreviewImageName);
|
||||
|
||||
CoroutineManager.StartCoroutine(PublishItem(item));
|
||||
}
|
||||
|
||||
private static IEnumerable<object> PublishItem(Workshop.Editor item)
|
||||
{
|
||||
if (instance == null || !instance.isInitialized)
|
||||
{
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
item.Publish();
|
||||
while (item.Publishing)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(item.Error))
|
||||
{
|
||||
DebugConsole.NewMessage("Published workshop item " + item.Title + " successfully.", Microsoft.Xna.Framework.Color.LightGreen);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Publishing workshop item " + item.Title + " failed. " + item.Error);
|
||||
}
|
||||
|
||||
SaveUtil.ClearFolder(WorkshopItemStagingFolder);
|
||||
Directory.Delete(WorkshopItemStagingFolder);
|
||||
File.Delete(PreviewImageName);
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables a workshop item by moving it to the game folder.
|
||||
/// </summary>
|
||||
public static bool EnableWorkShopItem(Workshop.Item item, bool allowFileOverwrite, out string errorMsg)
|
||||
{
|
||||
if (!item.Installed)
|
||||
{
|
||||
errorMsg = TextManager.Get("WorkshopErrorInstallRequiredToEnable").Replace("[itemname]", item.Title);
|
||||
DebugConsole.NewMessage(errorMsg, Microsoft.Xna.Framework.Color.Red);
|
||||
return false;
|
||||
}
|
||||
|
||||
ContentPackage contentPackage = new ContentPackage(Path.Combine(item.Directory.FullName, MetadataFileName));
|
||||
string newContentPackagePath = GetWorkshopItemContentPackagePath(contentPackage);
|
||||
|
||||
var allPackageFiles = Directory.GetFiles(item.Directory.FullName, "*", SearchOption.AllDirectories);
|
||||
List<string> nonContentFiles = new List<string>();
|
||||
foreach (string file in allPackageFiles)
|
||||
{
|
||||
if (file == MetadataFileName) { continue; }
|
||||
string relativePath = UpdaterUtil.GetRelativePath(file, item.Directory.FullName);
|
||||
string fullPath = Path.GetFullPath(relativePath);
|
||||
if (contentPackage.Files.Any(f => { string fp = Path.GetFullPath(f.Path); return fp == fullPath; })) { continue; }
|
||||
if (ContentPackage.IsModFilePathAllowed(relativePath))
|
||||
{
|
||||
nonContentFiles.Add(relativePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!allowFileOverwrite)
|
||||
{
|
||||
if (File.Exists(newContentPackagePath))
|
||||
{
|
||||
errorMsg = TextManager.Get("WorkshopErrorOverwriteOnEnable")
|
||||
.Replace("[itemname]", item.Title)
|
||||
.Replace("[filename]", newContentPackagePath);
|
||||
DebugConsole.NewMessage(errorMsg, Microsoft.Xna.Framework.Color.Red);
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (ContentFile contentFile in contentPackage.Files)
|
||||
{
|
||||
string sourceFile = Path.Combine(item.Directory.FullName, contentFile.Path);
|
||||
if (File.Exists(sourceFile) && File.Exists(contentFile.Path))
|
||||
{
|
||||
errorMsg = TextManager.Get("WorkshopErrorOverwriteOnEnable")
|
||||
.Replace("[itemname]", item.Title)
|
||||
.Replace("[filename]", contentFile.Path);
|
||||
DebugConsole.NewMessage(errorMsg, Microsoft.Xna.Framework.Color.Red);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
//we only need to create a new content package for the item if it contains content with a type other than None or Submarine
|
||||
//e.g. items that are just a sub file are just copied to the game folder
|
||||
if (contentPackage.Files.Any(f => f.Type != ContentType.None && f.Type != ContentType.Submarine))
|
||||
{
|
||||
File.Copy(contentPackage.Path, newContentPackagePath);
|
||||
}
|
||||
|
||||
foreach (ContentFile contentFile in contentPackage.Files)
|
||||
{
|
||||
string sourceFile = Path.Combine(item.Directory.FullName, contentFile.Path);
|
||||
if (!File.Exists(sourceFile)) { continue; }
|
||||
if (!ContentPackage.IsModFilePathAllowed(contentFile))
|
||||
{
|
||||
DebugConsole.ThrowError(TextManager.Get("WorkshopErrorIllegalPathOnEnable").Replace("[filename]", contentFile.Path));
|
||||
continue;
|
||||
}
|
||||
|
||||
//make sure the destination directory exists
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(contentFile.Path));
|
||||
File.Copy(sourceFile, contentFile.Path, overwrite: true);
|
||||
}
|
||||
|
||||
foreach (string nonContentFile in nonContentFiles)
|
||||
{
|
||||
string sourceFile = Path.Combine(item.Directory.FullName, nonContentFile);
|
||||
if (!File.Exists(sourceFile)) { continue; }
|
||||
if (!ContentPackage.IsModFilePathAllowed(nonContentFile))
|
||||
{
|
||||
DebugConsole.ThrowError(TextManager.Get("WorkshopErrorIllegalPathOnEnable").Replace("[filename]", nonContentFile));
|
||||
continue;
|
||||
}
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(nonContentFile));
|
||||
File.Copy(sourceFile, nonContentFile, overwrite: true);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
errorMsg = TextManager.Get("WorkshopErrorEnableFailed").Replace("[itemname]", item.Title) + " " + e.Message;
|
||||
DebugConsole.NewMessage(errorMsg, Microsoft.Xna.Framework.Color.Red);
|
||||
return false;
|
||||
}
|
||||
|
||||
var newPackage = new ContentPackage(contentPackage.Path, newContentPackagePath)
|
||||
{
|
||||
SteamWorkshopUrl = item.Url
|
||||
};
|
||||
newPackage.Save(newContentPackagePath);
|
||||
ContentPackage.List.Add(newPackage);
|
||||
if (newPackage.CorePackage)
|
||||
{
|
||||
//if enabling a core package, disable all other core packages
|
||||
GameMain.Config.SelectedContentPackages.RemoveWhere(cp => cp.CorePackage);
|
||||
}
|
||||
GameMain.Config.SelectedContentPackages.Add(newPackage);
|
||||
GameMain.Config.Save();
|
||||
|
||||
if (item.Tags.Contains("Submarine") || newPackage.Files.Any(f => f.Type == ContentType.Submarine))
|
||||
{
|
||||
Submarine.RefreshSavedSubs();
|
||||
}
|
||||
|
||||
errorMsg = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disables a workshop item by removing the files from the game folder.
|
||||
/// </summary>
|
||||
public static bool DisableWorkShopItem(Workshop.Item item, out string errorMsg)
|
||||
{
|
||||
if (!item.Installed)
|
||||
{
|
||||
errorMsg = "Cannot disable workshop item \"" + item.Title + "\" because it has not been installed.";
|
||||
DebugConsole.NewMessage(errorMsg, Microsoft.Xna.Framework.Color.Red);
|
||||
return false;
|
||||
}
|
||||
|
||||
ContentPackage contentPackage = new ContentPackage(Path.Combine(item.Directory.FullName, MetadataFileName));
|
||||
string installedContentPackagePath = GetWorkshopItemContentPackagePath(contentPackage);
|
||||
|
||||
var allPackageFiles = Directory.GetFiles(item.Directory.FullName, "*", SearchOption.AllDirectories);
|
||||
List<string> nonContentFiles = new List<string>();
|
||||
foreach (string file in allPackageFiles)
|
||||
{
|
||||
if (file == MetadataFileName) { continue; }
|
||||
string relativePath = UpdaterUtil.GetRelativePath(file, item.Directory.FullName);
|
||||
string fullPath = Path.GetFullPath(relativePath);
|
||||
if (contentPackage.Files.Any(f => { string fp = Path.GetFullPath(f.Path); return fp == fullPath; })) { continue; }
|
||||
if (ContentPackage.IsModFilePathAllowed(relativePath))
|
||||
{
|
||||
nonContentFiles.Add(relativePath);
|
||||
}
|
||||
}
|
||||
if (File.Exists(installedContentPackagePath)) { File.Delete(installedContentPackagePath); }
|
||||
|
||||
bool wasSub = item.Tags.Contains("Submarine") || contentPackage.Files.Any(f => f.Type == ContentType.Submarine);
|
||||
|
||||
HashSet<string> directories = new HashSet<string>();
|
||||
try
|
||||
{
|
||||
foreach (ContentFile contentFile in contentPackage.Files)
|
||||
{
|
||||
if (!ContentPackage.IsModFilePathAllowed(contentFile))
|
||||
{
|
||||
//Workshop items are not allowed to add or modify files in the Content or Data folders;
|
||||
continue;
|
||||
}
|
||||
if (!File.Exists(contentFile.Path)) { continue; }
|
||||
File.Delete(contentFile.Path);
|
||||
directories.Add(Path.GetDirectoryName(contentFile.Path));
|
||||
}
|
||||
foreach (string nonContentFile in nonContentFiles)
|
||||
{
|
||||
if (!ContentPackage.IsModFilePathAllowed(nonContentFile))
|
||||
{
|
||||
//Workshop items are not allowed to add or modify files in the Content or Data folders;
|
||||
continue;
|
||||
}
|
||||
if (!File.Exists(nonContentFile)) { continue; }
|
||||
File.Delete(nonContentFile);
|
||||
directories.Add(Path.GetDirectoryName(nonContentFile));
|
||||
}
|
||||
|
||||
foreach (string directory in directories)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(directory) || !Directory.Exists(directory)) { continue; }
|
||||
if (Directory.GetFiles(directory, "*", SearchOption.AllDirectories).Count() == 0)
|
||||
{
|
||||
Directory.Delete(directory, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
ContentPackage.List.RemoveAll(cp => System.IO.Path.GetFullPath(cp.Path) == System.IO.Path.GetFullPath(installedContentPackagePath));
|
||||
GameMain.Config.SelectedContentPackages.RemoveWhere(cp => !ContentPackage.List.Contains(cp));
|
||||
GameMain.Config.Save();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
errorMsg = "Disabling the workshop item \"" + item.Title + "\" failed. "+e.Message;
|
||||
DebugConsole.NewMessage(errorMsg, Microsoft.Xna.Framework.Color.Red);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wasSub)
|
||||
{
|
||||
Submarine.RefreshSavedSubs();
|
||||
}
|
||||
|
||||
errorMsg = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the item compatible with this version of Barotrauma. Returns null if compatibility couldn't be determined (item not installed)
|
||||
/// </summary>
|
||||
public static bool? CheckWorkshopItemCompatibility(Workshop.Item item)
|
||||
{
|
||||
if (!item.Installed) { return null; }
|
||||
|
||||
string metaDataPath = Path.Combine(item.Directory.FullName, MetadataFileName);
|
||||
if (!File.Exists(metaDataPath))
|
||||
{
|
||||
throw new FileNotFoundException("Metadata file for the Workshop item \"" + item.Title + "\" not found. The file may be corrupted.");
|
||||
}
|
||||
|
||||
ContentPackage contentPackage = new ContentPackage(metaDataPath);
|
||||
return contentPackage.IsCompatible();
|
||||
}
|
||||
|
||||
public static bool CheckWorkshopItemEnabled(Workshop.Item item)
|
||||
{
|
||||
if (!item.Installed) return false;
|
||||
|
||||
string metaDataPath = Path.Combine(item.Directory.FullName, MetadataFileName);
|
||||
if (!File.Exists(metaDataPath))
|
||||
{
|
||||
throw new FileNotFoundException("Metadata file for the Workshop item \"" + item.Title + "\" not found. The file may be corrupted.");
|
||||
}
|
||||
|
||||
ContentPackage contentPackage = new ContentPackage(metaDataPath);
|
||||
//make sure the contentpackage file is present
|
||||
//(unless the package only contains submarine files, in which case we don't need a content package)
|
||||
if (contentPackage.Files.Any(f => f.Type != ContentType.Submarine) &&
|
||||
!File.Exists(GetWorkshopItemContentPackagePath(contentPackage)) &&
|
||||
!ContentPackage.List.Any(cp => cp.Name == contentPackage.Name))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
foreach (ContentFile contentFile in contentPackage.Files)
|
||||
{
|
||||
if (!File.Exists(contentFile.Path)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string GetWorkshopItemContentPackagePath(ContentPackage contentPackage)
|
||||
{
|
||||
string fileName = contentPackage.Name + ".xml";
|
||||
string invalidChars = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
|
||||
foreach (char c in invalidChars) fileName = fileName.Replace(c.ToString(), "");
|
||||
return Path.Combine("Data", "ContentPackages", fileName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
public static void Update(float deltaTime)
|
||||
{
|
||||
if (instance == null || !instance.isInitialized) return;
|
||||
|
||||
if (instance == null || !instance.isInitialized) { return; }
|
||||
|
||||
instance.client?.Update();
|
||||
instance.server?.Update();
|
||||
|
||||
@@ -1052,6 +132,8 @@ namespace Barotrauma.Steam
|
||||
|
||||
public static void ShutDown()
|
||||
{
|
||||
if (instance == null) { return; }
|
||||
|
||||
instance.client?.Dispose();
|
||||
instance.client = null;
|
||||
instance.server?.Dispose();
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using Lidgren.Network;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
static partial class VoipConfig
|
||||
{
|
||||
public const int MAX_COMPRESSED_SIZE = 120; //amount of bytes we expect each 60ms of audio to fit in
|
||||
|
||||
public static readonly TimeSpan SEND_INTERVAL = new TimeSpan(0,0,0,0,120);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
using Lidgren.Network;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
|
||||
public class VoipQueue : IDisposable
|
||||
{
|
||||
public const int BUFFER_COUNT = 8;
|
||||
protected int[] bufferLengths;
|
||||
protected byte[][] buffers;
|
||||
protected int newestBufferInd;
|
||||
protected bool firstRead;
|
||||
|
||||
public int EnqueuedTotalLength
|
||||
{
|
||||
get
|
||||
{
|
||||
int enqueuedTotalLength = 0;
|
||||
for (int i = 0; i < BUFFER_COUNT; i++)
|
||||
{
|
||||
enqueuedTotalLength += bufferLengths[i];
|
||||
}
|
||||
return enqueuedTotalLength;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] BufferToQueue
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public virtual byte QueueID
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public UInt16 LatestBufferID
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public bool CanSend
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public bool CanReceive
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public DateTime LastReadTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public VoipQueue(byte id, bool canSend, bool canReceive)
|
||||
{
|
||||
BufferToQueue = new byte[VoipConfig.MAX_COMPRESSED_SIZE];
|
||||
newestBufferInd = BUFFER_COUNT - 1;
|
||||
bufferLengths = new int[BUFFER_COUNT];
|
||||
buffers = new byte[BUFFER_COUNT][];
|
||||
for (int i = 0; i < BUFFER_COUNT; i++)
|
||||
{
|
||||
buffers[i] = new byte[VoipConfig.MAX_COMPRESSED_SIZE];
|
||||
}
|
||||
QueueID = id;
|
||||
CanSend = canSend;
|
||||
CanReceive = canReceive;
|
||||
LatestBufferID = BUFFER_COUNT-1;
|
||||
firstRead = true;
|
||||
|
||||
LastReadTime = DateTime.Now;
|
||||
}
|
||||
|
||||
public void EnqueueBuffer(int length)
|
||||
{
|
||||
if (length > byte.MaxValue) return;
|
||||
|
||||
newestBufferInd = (newestBufferInd + 1) % BUFFER_COUNT;
|
||||
|
||||
int enqueuedTotalLength = EnqueuedTotalLength;
|
||||
|
||||
bufferLengths[newestBufferInd] = length;
|
||||
BufferToQueue.CopyTo(buffers[newestBufferInd], 0);
|
||||
|
||||
if ((enqueuedTotalLength+length)>0) LatestBufferID++;
|
||||
}
|
||||
|
||||
public void RetrieveBuffer(int id,out int outSize,out byte[] outBuf)
|
||||
{
|
||||
lock (buffers)
|
||||
{
|
||||
if (id >= LatestBufferID - (BUFFER_COUNT - 1) && id <= LatestBufferID)
|
||||
{
|
||||
int index = (newestBufferInd - (LatestBufferID - id)); if (index < 0) index += BUFFER_COUNT;
|
||||
outSize = bufferLengths[index];
|
||||
outBuf = buffers[index];
|
||||
return;
|
||||
}
|
||||
}
|
||||
outSize = -1;
|
||||
outBuf = null;
|
||||
}
|
||||
|
||||
public virtual void Write(NetBuffer msg)
|
||||
{
|
||||
if (!CanSend) throw new Exception("Called Write on a VoipQueue not set up for sending");
|
||||
|
||||
msg.Write((UInt16)LatestBufferID);
|
||||
for (int i = 0; i < BUFFER_COUNT; i++)
|
||||
{
|
||||
int index = (newestBufferInd + i + 1) % BUFFER_COUNT;
|
||||
|
||||
msg.Write((byte)bufferLengths[index]);
|
||||
msg.Write(buffers[index], 0, bufferLengths[index]);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool Read(NetBuffer msg)
|
||||
{
|
||||
if (!CanReceive) throw new Exception("Called Read on a VoipQueue not set up for receiving");
|
||||
|
||||
UInt16 incLatestBufferID = msg.ReadUInt16();
|
||||
if (firstRead || NetIdUtils.IdMoreRecent(incLatestBufferID,LatestBufferID))
|
||||
{
|
||||
firstRead = false;
|
||||
for (int i = 0; i < BUFFER_COUNT; i++)
|
||||
{
|
||||
bufferLengths[i] = msg.ReadByte();
|
||||
msg.ReadBytes(buffers[i], 0, bufferLengths[i]);
|
||||
}
|
||||
newestBufferInd = BUFFER_COUNT - 1;
|
||||
LatestBufferID = incLatestBufferID;
|
||||
LastReadTime = DateTime.Now;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < BUFFER_COUNT; i++)
|
||||
{
|
||||
byte len = msg.ReadByte();
|
||||
msg.Position += len * 8;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Dispose() { }
|
||||
}
|
||||
}
|
||||
@@ -72,128 +72,5 @@ namespace Barotrauma
|
||||
UpdateVoteTexts(connectedClients, VoteType.Sub);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void ServerRead(NetIncomingMessage inc, Client sender)
|
||||
{
|
||||
if (GameMain.Server == null || sender == null) return;
|
||||
|
||||
byte voteTypeByte = inc.ReadByte();
|
||||
VoteType voteType = VoteType.Unknown;
|
||||
try
|
||||
{
|
||||
voteType = (VoteType)voteTypeByte;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to cast vote type \"" + voteTypeByte + "\"", e);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (voteType)
|
||||
{
|
||||
case VoteType.Sub:
|
||||
string subName = inc.ReadString();
|
||||
Submarine sub = Submarine.SavedSubmarines.FirstOrDefault(s => s.Name == subName);
|
||||
sender.SetVote(voteType, sub);
|
||||
#if CLIENT
|
||||
UpdateVoteTexts(GameMain.Server.ConnectedClients, voteType);
|
||||
#endif
|
||||
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);
|
||||
#if CLIENT
|
||||
UpdateVoteTexts(GameMain.Server.ConnectedClients, voteType);
|
||||
#endif
|
||||
break;
|
||||
case VoteType.EndRound:
|
||||
if (!sender.HasSpawned) return;
|
||||
sender.SetVote(voteType, inc.ReadBoolean());
|
||||
|
||||
GameMain.NetworkMember.EndVoteCount = GameMain.Server.ConnectedClients.Count(c => c.HasSpawned && c.GetVote<bool>(VoteType.EndRound));
|
||||
GameMain.NetworkMember.EndVoteMax = GameMain.Server.ConnectedClients.Count(c => c.HasSpawned);
|
||||
|
||||
break;
|
||||
case VoteType.Kick:
|
||||
byte kickedClientID = inc.ReadByte();
|
||||
|
||||
Client kicked = GameMain.Server.ConnectedClients.Find(c => c.ID == kickedClientID);
|
||||
if (kicked != null && !kicked.HasKickVoteFrom(sender))
|
||||
{
|
||||
kicked.AddKickVote(sender);
|
||||
Client.UpdateKickVotes(GameMain.Server.ConnectedClients);
|
||||
|
||||
GameMain.Server.SendChatMessage(sender.Name + " has voted to kick " + 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);
|
||||
#if CLIENT
|
||||
UpdateVoteTexts(GameMain.Server.ConnectedClients, voteType);
|
||||
#endif
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
inc.ReadPadBits();
|
||||
|
||||
GameMain.Server.UpdateVoteStatus();
|
||||
}
|
||||
|
||||
public void ServerWrite(NetBuffer msg)
|
||||
{
|
||||
if (GameMain.Server == null) return;
|
||||
|
||||
msg.Write(allowSubVoting);
|
||||
if (allowSubVoting)
|
||||
{
|
||||
List<Pair<object, int>> voteList = GetVoteList(VoteType.Sub, GameMain.Server.ConnectedClients);
|
||||
msg.Write((byte)voteList.Count);
|
||||
foreach (Pair<object, int> vote in voteList)
|
||||
{
|
||||
msg.Write((byte)vote.Second);
|
||||
msg.Write(((Submarine)vote.First).Name);
|
||||
}
|
||||
}
|
||||
msg.Write(AllowModeVoting);
|
||||
if (allowModeVoting)
|
||||
{
|
||||
List<Pair<object, int>> voteList = GetVoteList(VoteType.Mode, GameMain.Server.ConnectedClients);
|
||||
msg.Write((byte)voteList.Count);
|
||||
foreach (Pair<object, int> vote in voteList)
|
||||
{
|
||||
msg.Write((byte)vote.Second);
|
||||
msg.Write(((GameModePreset)vote.First).Identifier);
|
||||
}
|
||||
}
|
||||
msg.Write(AllowEndVoting);
|
||||
if (AllowEndVoting)
|
||||
{
|
||||
msg.Write((byte)GameMain.Server.ConnectedClients.Count(v => v.GetVote<bool>(VoteType.EndRound)));
|
||||
msg.Write((byte)GameMain.Server.ConnectedClients.Count);
|
||||
}
|
||||
|
||||
msg.Write(AllowVoteKick);
|
||||
|
||||
var readyClients = GameMain.Server.ConnectedClients.FindAll(c => c.GetVote<bool>(VoteType.StartRound));
|
||||
msg.Write((byte)readyClients.Count);
|
||||
foreach (Client c in readyClients)
|
||||
{
|
||||
msg.Write(c.ID);
|
||||
}
|
||||
|
||||
msg.WritePadBits();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,16 +5,12 @@ using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class WhiteListedPlayer
|
||||
partial class WhiteListedPlayer
|
||||
{
|
||||
public string Name;
|
||||
public string IP;
|
||||
|
||||
public WhiteListedPlayer(string name,string ip)
|
||||
{
|
||||
Name = name;
|
||||
IP = ip;
|
||||
}
|
||||
public UInt16 UniqueIdentifier;
|
||||
}
|
||||
|
||||
partial class WhiteList
|
||||
@@ -29,107 +25,13 @@ namespace Barotrauma.Networking
|
||||
|
||||
public bool Enabled;
|
||||
|
||||
partial void InitProjSpecific();
|
||||
public WhiteList()
|
||||
{
|
||||
Enabled = false;
|
||||
whitelistedPlayers = new List<WhiteListedPlayer>();
|
||||
|
||||
if (File.Exists(SavePath))
|
||||
{
|
||||
string[] lines;
|
||||
try
|
||||
{
|
||||
lines = File.ReadAllLines(SavePath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to open whitelist in " + SavePath, e);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (string line in lines)
|
||||
{
|
||||
if (line[0] == '#')
|
||||
{
|
||||
string lineval = line.Substring(1, line.Length - 1);
|
||||
int intVal = 0;
|
||||
Int32.TryParse(lineval, out intVal);
|
||||
if (lineval.ToLower() == "true" || intVal != 0)
|
||||
{
|
||||
Enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Enabled = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string[] separatedLine = line.Split(',');
|
||||
if (separatedLine.Length < 2) continue;
|
||||
|
||||
string name = String.Join(",", separatedLine.Take(separatedLine.Length - 1));
|
||||
string ip = separatedLine.Last();
|
||||
|
||||
whitelistedPlayers.Add(new WhiteListedPlayer(name, ip));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
GameServer.Log("Saving whitelist", ServerLog.MessageType.ServerMessage);
|
||||
|
||||
List<string> lines = new List<string>();
|
||||
|
||||
if (Enabled)
|
||||
{
|
||||
lines.Add("#true");
|
||||
}
|
||||
else
|
||||
{
|
||||
lines.Add("#false");
|
||||
}
|
||||
foreach (WhiteListedPlayer wlp in whitelistedPlayers)
|
||||
{
|
||||
lines.Add(wlp.Name + "," + wlp.IP);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllLines(SavePath, lines);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Saving the whitelist to " + SavePath + " failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsWhiteListed(string name, string ip)
|
||||
{
|
||||
if (!Enabled) return true;
|
||||
WhiteListedPlayer wlp = whitelistedPlayers.Find(p => p.Name == name);
|
||||
if (wlp == null) return false;
|
||||
if (wlp.IP != ip && !string.IsNullOrWhiteSpace(wlp.IP)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void RemoveFromWhiteList(WhiteListedPlayer wlp)
|
||||
{
|
||||
DebugConsole.Log("Removing " + wlp.Name + " from whitelist");
|
||||
GameServer.Log("Removing " + wlp.Name + " from whitelist", ServerLog.MessageType.ServerMessage);
|
||||
|
||||
whitelistedPlayers.Remove(wlp);
|
||||
Save();
|
||||
}
|
||||
|
||||
private void AddToWhiteList(string name,string ip)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name)) return;
|
||||
if (whitelistedPlayers.Any(x => x.Name.ToLower() == name.ToLower() && x.IP == ip)) return;
|
||||
whitelistedPlayers.Add(new WhiteListedPlayer(name, ip));
|
||||
Save();
|
||||
InitProjSpecific();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user