Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into unstable
This commit is contained in:
@@ -1,60 +1,54 @@
|
||||
using Barotrauma.Steam;
|
||||
#nullable enable
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
partial class BannedPlayer
|
||||
{
|
||||
public BannedPlayer(string name, UInt16 uniqueIdentifier, bool isRangeBan, string endPoint, ulong steamID, string reason, DateTime? expiration)
|
||||
public BannedPlayer(
|
||||
UInt32 uniqueIdentifier,
|
||||
string name,
|
||||
Either<Address, AccountId> addressOrAccountId,
|
||||
string reason,
|
||||
DateTime? expiration)
|
||||
{
|
||||
this.Name = name;
|
||||
this.EndPoint = endPoint;
|
||||
this.SteamID = steamID;
|
||||
ParseEndPointAsSteamId();
|
||||
this.IsRangeBan = isRangeBan;
|
||||
this.AddressOrAccountId = addressOrAccountId;
|
||||
this.UniqueIdentifier = uniqueIdentifier;
|
||||
this.Reason = reason;
|
||||
this.ExpirationTime = expiration;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class BanList
|
||||
partial class BanList
|
||||
{
|
||||
private GUIComponent banFrame;
|
||||
public GUIComponent? BanFrame { get; private set; }
|
||||
|
||||
public GUIComponent BanFrame
|
||||
{
|
||||
get { return banFrame; }
|
||||
}
|
||||
|
||||
public List<UInt16> localRemovedBans = new List<UInt16>();
|
||||
public List<UInt16> localRangeBans = new List<UInt16>();
|
||||
public List<UInt32> localRemovedBans = new List<UInt32>();
|
||||
|
||||
private void RecreateBanFrame()
|
||||
{
|
||||
if (banFrame != null)
|
||||
if (BanFrame != null)
|
||||
{
|
||||
var parent = banFrame.Parent;
|
||||
parent.RemoveChild(banFrame);
|
||||
var parent = BanFrame.Parent;
|
||||
parent.RemoveChild(BanFrame);
|
||||
CreateBanFrame(parent);
|
||||
}
|
||||
}
|
||||
|
||||
public GUIComponent CreateBanFrame(GUIComponent parent)
|
||||
{
|
||||
banFrame = new GUIListBox(new RectTransform(Vector2.One, parent.RectTransform, Anchor.Center));
|
||||
BanFrame = new GUIListBox(new RectTransform(Vector2.One, parent.RectTransform, Anchor.Center));
|
||||
|
||||
foreach (BannedPlayer bannedPlayer in bannedPlayers)
|
||||
{
|
||||
if (localRemovedBans.Contains(bannedPlayer.UniqueIdentifier)) { continue; }
|
||||
|
||||
var playerFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), ((GUIListBox)banFrame).Content.RectTransform) { MinSize = new Point(0, 70) })
|
||||
var playerFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), ((GUIListBox)BanFrame).Content.RectTransform) { MinSize = new Point(0, 70) })
|
||||
{
|
||||
UserData = banFrame
|
||||
UserData = BanFrame
|
||||
};
|
||||
|
||||
var paddedPlayerFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.85f), playerFrame.RectTransform, Anchor.Center))
|
||||
@@ -71,54 +65,48 @@ namespace Barotrauma.Networking
|
||||
RelativeSpacing = 0.02f
|
||||
};
|
||||
|
||||
string endPoint = bannedPlayer.EndPoint;
|
||||
if (localRangeBans.Contains(bannedPlayer.UniqueIdentifier)) endPoint = ToRange(endPoint);
|
||||
GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), topArea.RectTransform),
|
||||
bannedPlayer.Name + " (" + endPoint + ")");
|
||||
textBlock.RectTransform.MinSize = new Point(textBlock.Rect.Width, 0);
|
||||
var addressOrAccountId = bannedPlayer.AddressOrAccountId;
|
||||
GUITextBlock textBlock = new GUITextBlock(
|
||||
new RectTransform(new Vector2(0.5f, 1.0f), topArea.RectTransform),
|
||||
bannedPlayer.Name + " (" + addressOrAccountId + ")") { CanBeFocused = true };
|
||||
textBlock.RectTransform.MinSize = new Point(
|
||||
(int)textBlock.Font.MeasureString(textBlock.Text.SanitizedValue).X, 0);
|
||||
|
||||
if (bannedPlayer.EndPoint.IndexOf(".x") <= -1)
|
||||
{
|
||||
var rangeBanButton = new GUIButton(new RectTransform(new Vector2(0.25f, 0.4f), topArea.RectTransform),
|
||||
TextManager.Get("BanRange"), style: "GUIButtonSmall")
|
||||
{
|
||||
UserData = bannedPlayer,
|
||||
OnClicked = RangeBan
|
||||
};
|
||||
}
|
||||
var removeButton = new GUIButton(new RectTransform(new Vector2(0.2f, 0.4f), topArea.RectTransform),
|
||||
TextManager.Get("BanListRemove"), style: "GUIButtonSmall")
|
||||
{
|
||||
UserData = bannedPlayer,
|
||||
OnClicked = RemoveBan
|
||||
};
|
||||
topArea.RectTransform.MinSize = new Point(0, (int)topArea.RectTransform.Children.Max(c => c.Rect.Height * 1.25f));
|
||||
topArea.RectTransform.MinSize = new Point(0, (int)(removeButton.Rect.Height * 1.25f));
|
||||
|
||||
topArea.ForceLayoutRecalculation();
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedPlayerFrame.RectTransform),
|
||||
bannedPlayer.ExpirationTime == null ?
|
||||
TextManager.Get("BanPermanent") : TextManager.GetWithVariable("BanExpires", "[time]", bannedPlayer.ExpirationTime.Value.ToString()),
|
||||
font: GUIStyle.SmallFont);
|
||||
|
||||
LocalizedString reason = TextManager.GetServerMessage(bannedPlayer.Reason).Fallback(bannedPlayer.Reason);
|
||||
var reasonText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedPlayerFrame.RectTransform),
|
||||
TextManager.Get("BanReason") + " " +
|
||||
(string.IsNullOrEmpty(bannedPlayer.Reason) ? TextManager.Get("None") : bannedPlayer.Reason),
|
||||
(string.IsNullOrEmpty(bannedPlayer.Reason) ? TextManager.Get("None") : reason),
|
||||
font: GUIStyle.SmallFont, wrap: true)
|
||||
{
|
||||
ToolTip = bannedPlayer.Reason
|
||||
ToolTip = reason
|
||||
};
|
||||
|
||||
paddedPlayerFrame.Recalculate();
|
||||
|
||||
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), ((GUIListBox)banFrame).Content.RectTransform), style: "HorizontalLine");
|
||||
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), ((GUIListBox)BanFrame).Content.RectTransform), style: "HorizontalLine");
|
||||
}
|
||||
|
||||
return banFrame;
|
||||
return BanFrame;
|
||||
}
|
||||
|
||||
private bool RemoveBan(GUIButton button, object obj)
|
||||
{
|
||||
BannedPlayer banned = obj as BannedPlayer;
|
||||
if (banned == null) { return false; }
|
||||
if (!(obj is BannedPlayer banned)) { return false; }
|
||||
|
||||
localRemovedBans.Add(banned.UniqueIdentifier);
|
||||
RecreateBanFrame();
|
||||
@@ -127,19 +115,6 @@ namespace Barotrauma.Networking
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool RangeBan(GUIButton button, object obj)
|
||||
{
|
||||
BannedPlayer banned = obj as BannedPlayer;
|
||||
if (banned == null) { return false; }
|
||||
|
||||
localRangeBans.Add(banned.UniqueIdentifier);
|
||||
RecreateBanFrame();
|
||||
|
||||
GameMain.Client?.ServerSettings?.ClientAdminWrite(ServerSettings.NetFlags.Properties);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ClientAdminRead(IReadMessage incMsg)
|
||||
{
|
||||
@@ -159,8 +134,7 @@ namespace Barotrauma.Networking
|
||||
for (int i = 0; i < (int)bannedPlayerCount; i++)
|
||||
{
|
||||
string name = incMsg.ReadString();
|
||||
UInt16 uniqueIdentifier = incMsg.ReadUInt16();
|
||||
bool isRangeBan = incMsg.ReadBoolean();
|
||||
UInt32 uniqueIdentifier = incMsg.ReadUInt32();
|
||||
bool includesExpiration = incMsg.ReadBoolean();
|
||||
incMsg.ReadPadBits();
|
||||
|
||||
@@ -173,45 +147,49 @@ namespace Barotrauma.Networking
|
||||
|
||||
string reason = incMsg.ReadString();
|
||||
|
||||
string endPoint = "";
|
||||
UInt64 steamID = 0;
|
||||
Either<Address, AccountId> addressOrAccountId;
|
||||
if (isOwner)
|
||||
{
|
||||
endPoint = incMsg.ReadString();
|
||||
steamID = incMsg.ReadUInt64();
|
||||
bool isAddress = incMsg.ReadBoolean();
|
||||
incMsg.ReadPadBits();
|
||||
string str = incMsg.ReadString();
|
||||
if (isAddress && Address.Parse(str).TryUnwrap(out var address))
|
||||
{
|
||||
addressOrAccountId = address;
|
||||
}
|
||||
else if (AccountId.Parse(str).TryUnwrap(out var accountId))
|
||||
{
|
||||
addressOrAccountId = accountId;
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
endPoint = "Endpoint concealed by host";
|
||||
steamID = 0;
|
||||
addressOrAccountId = new UnknownAddress();
|
||||
}
|
||||
bannedPlayers.Add(new BannedPlayer(name, uniqueIdentifier, isRangeBan, endPoint, steamID, reason, expiration));
|
||||
bannedPlayers.Add(new BannedPlayer(uniqueIdentifier, name, addressOrAccountId, reason, expiration));
|
||||
}
|
||||
|
||||
if (banFrame != null)
|
||||
if (BanFrame != null)
|
||||
{
|
||||
var parent = banFrame.Parent;
|
||||
parent.RemoveChild(banFrame);
|
||||
var parent = BanFrame.Parent;
|
||||
parent.RemoveChild(BanFrame);
|
||||
CreateBanFrame(parent);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClientAdminWrite(IWriteMessage outMsg)
|
||||
{
|
||||
outMsg.Write((UInt16)localRemovedBans.Count);
|
||||
foreach (UInt16 uniqueId in localRemovedBans)
|
||||
outMsg.WriteVariableUInt32((UInt32)localRemovedBans.Count);
|
||||
foreach (UInt32 uniqueId in localRemovedBans)
|
||||
{
|
||||
outMsg.Write(uniqueId);
|
||||
}
|
||||
|
||||
outMsg.Write((UInt16)localRangeBans.Count);
|
||||
foreach (UInt16 uniqueId in localRangeBans)
|
||||
{
|
||||
outMsg.Write(uniqueId);
|
||||
outMsg.WriteUInt32(uniqueId);
|
||||
}
|
||||
|
||||
localRemovedBans.Clear();
|
||||
localRangeBans.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
public virtual void ClientWrite(IWriteMessage msg)
|
||||
{
|
||||
msg.Write((byte)ClientNetObject.CHAT_MESSAGE);
|
||||
msg.Write(NetStateID);
|
||||
msg.WriteByte((byte)ClientNetObject.CHAT_MESSAGE);
|
||||
msg.WriteUInt16(NetStateID);
|
||||
msg.WriteRangedInteger((int)Type, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
|
||||
msg.WriteRangedInteger((int)ChatMode, 0, Enum.GetValues(typeof(ChatMode)).Length - 1);
|
||||
msg.Write(Text);
|
||||
msg.WriteString(Text);
|
||||
}
|
||||
|
||||
public static void ClientRead(IReadMessage msg)
|
||||
@@ -35,8 +35,9 @@ namespace Barotrauma.Networking
|
||||
bool hasSenderClient = msg.ReadBoolean();
|
||||
if (hasSenderClient)
|
||||
{
|
||||
UInt64 clientId = msg.ReadUInt64();
|
||||
senderClient = GameMain.Client.ConnectedClients.Find(c => c.SteamID == clientId || c.ID == clientId);
|
||||
string userId = msg.ReadString();
|
||||
senderClient = GameMain.Client.ConnectedClients.Find(c
|
||||
=> c.SessionOrAccountIdMatches(userId));
|
||||
if (senderClient != null) { senderName = senderClient.Name; }
|
||||
}
|
||||
bool hasSenderCharacter = msg.ReadBoolean();
|
||||
|
||||
@@ -31,12 +31,17 @@ namespace Barotrauma.Networking
|
||||
|
||||
public bool IsOwner;
|
||||
|
||||
public bool AllowKicking;
|
||||
|
||||
public bool IsDownloading;
|
||||
|
||||
public float Karma;
|
||||
|
||||
public bool AllowKicking =>
|
||||
!IsOwner &&
|
||||
!HasPermission(ClientPermissions.Ban) &&
|
||||
!HasPermission(ClientPermissions.Kick) &&
|
||||
!HasPermission(ClientPermissions.Unban);
|
||||
|
||||
public void UpdateSoundPosition()
|
||||
{
|
||||
if (VoipSound == null) { return; }
|
||||
@@ -72,8 +77,8 @@ namespace Barotrauma.Networking
|
||||
partial void InitProjSpecific()
|
||||
{
|
||||
VoipQueue = null; VoipSound = null;
|
||||
if (ID == GameMain.Client.ID) return;
|
||||
VoipQueue = new VoipQueue(ID, false, true);
|
||||
if (SessionId == GameMain.Client.SessionId) { return; }
|
||||
VoipQueue = new VoipQueue(SessionId, canSend: false, canReceive: true);
|
||||
GameMain.Client?.VoipClient?.RegisterQueue(VoipQueue);
|
||||
VoipSound = null;
|
||||
}
|
||||
@@ -134,6 +139,14 @@ namespace Barotrauma.Networking
|
||||
return Permissions.HasFlag(permission);
|
||||
}
|
||||
|
||||
public void ResetVotes()
|
||||
{
|
||||
for (int i = 0; i < votes.Length; i++)
|
||||
{
|
||||
votes[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
partial void DisposeProjSpecific()
|
||||
{
|
||||
if (VoipQueue != null)
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Barotrauma.Networking
|
||||
class FileReceiver
|
||||
{
|
||||
public class FileTransferIn : IDisposable
|
||||
{
|
||||
{
|
||||
public string FileName
|
||||
{
|
||||
get;
|
||||
@@ -36,7 +36,7 @@ namespace Barotrauma.Networking
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
|
||||
public int LastSeen { get; set; }
|
||||
|
||||
public FileTransferType FileType
|
||||
@@ -93,6 +93,12 @@ namespace Barotrauma.Networking
|
||||
|
||||
public int ID;
|
||||
|
||||
public const int DataBufferSize = 50;
|
||||
/// <summary>
|
||||
/// Data that we've ignored because we're waiting for some earlier data. Key = byte offset, value = the actual data
|
||||
/// </summary>
|
||||
public readonly Dictionary<int, byte[]> DataBuffer = new Dictionary<int, byte[]>();
|
||||
|
||||
public FileTransferIn(NetworkConnection connection, string filePath, FileTransferType fileType)
|
||||
{
|
||||
FilePath = filePath;
|
||||
@@ -128,20 +134,25 @@ namespace Barotrauma.Networking
|
||||
bytesToRead -= Received + bytesToRead - FileSize;
|
||||
}
|
||||
|
||||
byte[] all = inc.ReadBytes(bytesToRead);
|
||||
Received += all.Length;
|
||||
WriteStream.Write(all, 0, all.Length);
|
||||
ReadBytes(inc.ReadBytes(bytesToRead));
|
||||
}
|
||||
|
||||
public void ReadBytes(byte[] data)
|
||||
{
|
||||
Received += data.Length;
|
||||
WriteStream.Write(data, 0, data.Length);
|
||||
|
||||
int passed = Environment.TickCount - TimeStarted;
|
||||
float psec = passed / 1000.0f;
|
||||
|
||||
if (GameSettings.CurrentConfig.VerboseLogging)
|
||||
{
|
||||
DebugConsole.Log($"Received {all.Length} bytes of the file {FileName} ({Received / 1000}/{FileSize / 1000} kB received)");
|
||||
}
|
||||
|
||||
BytesPerSecond = Received / psec;
|
||||
|
||||
var outdatedKeys = DataBuffer.Keys.Where(k => k < Received).ToList();
|
||||
foreach (int key in outdatedKeys)
|
||||
{
|
||||
DataBuffer.Remove(key);
|
||||
}
|
||||
|
||||
Status = Received >= FileSize ? FileTransferStatus.Finished : FileTransferStatus.Receiving;
|
||||
}
|
||||
|
||||
@@ -206,7 +217,7 @@ namespace Barotrauma.Networking
|
||||
case (byte)FileTransferMessageType.Initiate:
|
||||
{
|
||||
byte transferId = inc.ReadByte();
|
||||
var existingTransfer = activeTransfers.Find(t => t.Connection.EndpointMatches(t.Connection.EndPointString) && t.ID == transferId);
|
||||
var existingTransfer = activeTransfers.Find(t => t.Connection.EndpointMatches(t.Connection.Endpoint) && t.ID == transferId);
|
||||
finishedTransfers.RemoveAll(t => t.transferId == transferId);
|
||||
byte fileType = inc.ReadByte();
|
||||
//ushort chunkLen = inc.ReadUInt16();
|
||||
@@ -329,7 +340,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
byte transferId = inc.ReadByte();
|
||||
|
||||
var activeTransfer = activeTransfers.Find(t => t.Connection.EndpointMatches(t.Connection.EndPointString) && t.ID == transferId);
|
||||
var activeTransfer = activeTransfers.Find(t => t.Connection.EndpointMatches(t.Connection.Endpoint) && t.ID == transferId);
|
||||
if (activeTransfer == null)
|
||||
{
|
||||
//it's possible for the server to send some extra data
|
||||
@@ -349,6 +360,10 @@ namespace Barotrauma.Networking
|
||||
if (offset != activeTransfer.Received)
|
||||
{
|
||||
activeTransfer.LastSeen = Math.Max(offset, activeTransfer.LastSeen);
|
||||
if (!activeTransfer.DataBuffer.ContainsKey(offset) && activeTransfer.DataBuffer.Count < FileTransferIn.DataBufferSize)
|
||||
{
|
||||
activeTransfer.DataBuffer.Add(offset, inc.ReadBytes(bytesToRead));
|
||||
}
|
||||
DebugConsole.Log($"Received {bytesToRead} bytes of the file {activeTransfer.FileName} (ignoring: offset {offset}, waiting for {activeTransfer.Received})");
|
||||
GameMain.Client.UpdateFileTransfer(activeTransfer, activeTransfer.Received, activeTransfer.LastSeen);
|
||||
return;
|
||||
@@ -370,7 +385,16 @@ namespace Barotrauma.Networking
|
||||
|
||||
try
|
||||
{
|
||||
activeTransfer.ReadBytes(inc, bytesToRead);
|
||||
activeTransfer.ReadBytes(inc, bytesToRead);
|
||||
if (GameSettings.CurrentConfig.VerboseLogging)
|
||||
{
|
||||
DebugConsole.Log($"Received {bytesToRead} bytes of the file {activeTransfer.FileName} ({activeTransfer.Received / 1000}/{activeTransfer.FileSize / 1000} kB received)");
|
||||
}
|
||||
while (activeTransfer.DataBuffer.TryGetValue(activeTransfer.Received, out byte[] data))
|
||||
{
|
||||
activeTransfer.ReadBytes(data);
|
||||
DebugConsole.Log($"Read {data.Length} bytes of buffer data of the file {activeTransfer.FileName} ({activeTransfer.Received / 1000}/{activeTransfer.FileSize / 1000} kB received)");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -406,7 +430,7 @@ namespace Barotrauma.Networking
|
||||
case (byte)FileTransferMessageType.Cancel:
|
||||
{
|
||||
byte transferId = inc.ReadByte();
|
||||
var matchingTransfer = activeTransfers.Find(t => t.Connection.EndpointMatches(t.Connection.EndPointString) && t.ID == transferId);
|
||||
var matchingTransfer = activeTransfers.Find(t => t.Connection.EndpointMatches(t.Connection.Endpoint) && t.ID == transferId);
|
||||
if (matchingTransfer != null)
|
||||
{
|
||||
new GUIMessageBox("File transfer cancelled", "The server has cancelled the transfer of the file \"" + matchingTransfer.FileName + "\".");
|
||||
@@ -434,7 +458,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(fileName) ||
|
||||
fileName.IndexOfAny(Path.GetInvalidFileNameChars().ToArray()) > -1)
|
||||
fileName.IndexOfAny(Path.GetInvalidFileNameCharsCrossPlatform().ToArray()) > -1)
|
||||
{
|
||||
errorMessage = "Illegal characters in file name ''" + fileName + "''";
|
||||
return false;
|
||||
@@ -470,7 +494,7 @@ namespace Barotrauma.Networking
|
||||
System.IO.Stream stream;
|
||||
try
|
||||
{
|
||||
stream = SaveUtil.DecompressFiletoStream(fileTransfer.FilePath);
|
||||
stream = SaveUtil.DecompressFileToStream(fileTransfer.FilePath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -103,7 +103,7 @@ namespace Barotrauma.Networking
|
||||
eventLastSent[entityEvent.ID] = (float)Lidgren.Network.NetTime.Now;
|
||||
}
|
||||
|
||||
msg.Write((byte)ClientNetObject.ENTITY_STATE);
|
||||
msg.WriteByte((byte)ClientNetObject.ENTITY_STATE);
|
||||
Write(msg, eventsToSync, out _);
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ namespace Barotrauma.Networking
|
||||
DebugConsole.NewMessage(
|
||||
"Received msg " + thisEventID + ", entity " + entityID + " not found",
|
||||
GUIStyle.Red);
|
||||
GameMain.Client.ReportError(ClientNetError.MISSING_ENTITY, eventID: thisEventID, entityID: entityID);
|
||||
GameMain.Client.ReportError(ClientNetError.MISSING_ENTITY, eventId: thisEventID, entityId: entityID);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
public void Write(IWriteMessage msg)
|
||||
{
|
||||
msg.Write(CharacterStateID);
|
||||
msg.WriteUInt16(CharacterStateID);
|
||||
serializable.ClientEventWrite(msg, Data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
public override void ClientWrite(IWriteMessage msg)
|
||||
{
|
||||
msg.Write((byte)ClientNetObject.CHAT_MESSAGE);
|
||||
msg.Write(NetStateID);
|
||||
msg.WriteByte((byte)ClientNetObject.CHAT_MESSAGE);
|
||||
msg.WriteUInt16(NetStateID);
|
||||
msg.WriteRangedInteger((int)ChatMessageType.Order, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
|
||||
msg.WriteRangedInteger((int)ChatMode.None, 0, Enum.GetValues(typeof(ChatMode)).Length - 1);
|
||||
WriteOrder(msg);
|
||||
|
||||
+155
-120
@@ -1,164 +1,199 @@
|
||||
using Barotrauma.Extensions;
|
||||
#nullable enable
|
||||
using Barotrauma.Steam;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
abstract class ClientPeer
|
||||
internal abstract class ClientPeer
|
||||
{
|
||||
public class ServerContentPackage
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly Md5Hash Hash;
|
||||
public readonly UInt64 WorkshopId;
|
||||
public readonly DateTime InstallTime;
|
||||
|
||||
public RegularPackage RegularPackage
|
||||
{
|
||||
get
|
||||
{
|
||||
return ContentPackageManager.RegularPackages.FirstOrDefault(p => p.Hash.Equals(Hash));
|
||||
}
|
||||
}
|
||||
|
||||
public CorePackage CorePackage
|
||||
{
|
||||
get
|
||||
{
|
||||
return ContentPackageManager.CorePackages.FirstOrDefault(p => p.Hash.Equals(Hash));
|
||||
}
|
||||
}
|
||||
|
||||
public ContentPackage ContentPackage
|
||||
=> (ContentPackage)RegularPackage ?? CorePackage;
|
||||
|
||||
|
||||
public string GetPackageStr()
|
||||
=> $"\"{Name}\" (hash {Hash.ShortRepresentation})";
|
||||
|
||||
public ServerContentPackage(string name, Md5Hash hash, UInt64 workshopId, DateTime installTime)
|
||||
{
|
||||
Name = name;
|
||||
Hash = hash;
|
||||
WorkshopId = workshopId;
|
||||
InstallTime = installTime;
|
||||
}
|
||||
}
|
||||
|
||||
public ImmutableArray<ServerContentPackage> ServerContentPackages { get; set; } =
|
||||
ImmutableArray<ServerContentPackage>.Empty;
|
||||
|
||||
public delegate void MessageCallback(IReadMessage message);
|
||||
public delegate void DisconnectCallback(bool disableReconnect);
|
||||
public delegate void DisconnectMessageCallback(string message);
|
||||
public delegate void PasswordCallback(int salt, int retries);
|
||||
public delegate void InitializationCompleteCallback();
|
||||
|
||||
public MessageCallback OnMessageReceived;
|
||||
public DisconnectCallback OnDisconnect;
|
||||
public DisconnectMessageCallback OnDisconnectMessageReceived;
|
||||
public PasswordCallback OnRequestPassword;
|
||||
public InitializationCompleteCallback OnInitializationComplete;
|
||||
public readonly record struct Callbacks(
|
||||
Callbacks.MessageCallback OnMessageReceived,
|
||||
Callbacks.DisconnectCallback OnDisconnect,
|
||||
Callbacks.InitializationCompleteCallback OnInitializationComplete)
|
||||
{
|
||||
public delegate void MessageCallback(IReadMessage message);
|
||||
public delegate void DisconnectCallback(PeerDisconnectPacket disconnectPacket);
|
||||
public delegate void InitializationCompleteCallback();
|
||||
}
|
||||
|
||||
public string Name;
|
||||
protected readonly Callbacks callbacks;
|
||||
|
||||
public string Version { get; protected set; }
|
||||
public readonly Endpoint ServerEndpoint;
|
||||
public NetworkConnection? ServerConnection { get; protected set; }
|
||||
|
||||
public NetworkConnection ServerConnection { get; protected set; }
|
||||
protected readonly bool isOwner;
|
||||
protected readonly Option<int> ownerKey;
|
||||
|
||||
public abstract void Start(object endPoint, int ownerKey);
|
||||
public abstract void Close(string msg = null, bool disableReconnect = false);
|
||||
protected bool isActive;
|
||||
|
||||
public ClientPeer(Endpoint serverEndpoint, Callbacks callbacks, Option<int> ownerKey)
|
||||
{
|
||||
ServerEndpoint = serverEndpoint;
|
||||
this.callbacks = callbacks;
|
||||
this.ownerKey = ownerKey;
|
||||
isOwner = ownerKey.IsSome();
|
||||
}
|
||||
|
||||
public abstract void Start();
|
||||
public abstract void Close(PeerDisconnectPacket peerDisconnectPacket);
|
||||
public abstract void Update(float deltaTime);
|
||||
public abstract void Send(IWriteMessage msg, DeliveryMethod deliveryMethod, bool compressPastThreshold = true);
|
||||
public abstract void SendPassword(string password);
|
||||
|
||||
protected abstract void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg);
|
||||
protected abstract void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body);
|
||||
|
||||
protected ConnectionInitialization initializationStep;
|
||||
protected bool contentPackageOrderReceived;
|
||||
protected int ownerKey = 0;
|
||||
public bool ContentPackageOrderReceived { get; set; }
|
||||
protected int passwordSalt;
|
||||
protected Steamworks.AuthTicket steamAuthTicket;
|
||||
protected void ReadConnectionInitializationStep(IReadMessage inc)
|
||||
protected Steamworks.AuthTicket? steamAuthTicket;
|
||||
private GUIMessageBox? passwordMsgBox;
|
||||
|
||||
public bool WaitingForPassword
|
||||
=> isActive && initializationStep == ConnectionInitialization.Password
|
||||
&& passwordMsgBox != null
|
||||
&& GUIMessageBox.MessageBoxes.Contains(passwordMsgBox);
|
||||
|
||||
public struct IncomingInitializationMessage
|
||||
{
|
||||
ConnectionInitialization step = (ConnectionInitialization)inc.ReadByte();
|
||||
public ConnectionInitialization InitializationStep;
|
||||
public IReadMessage Message;
|
||||
}
|
||||
|
||||
IWriteMessage outMsg;
|
||||
|
||||
switch (step)
|
||||
protected void ReadConnectionInitializationStep(IncomingInitializationMessage inc)
|
||||
{
|
||||
switch (inc.InitializationStep)
|
||||
{
|
||||
case ConnectionInitialization.SteamTicketAndVersion:
|
||||
{
|
||||
if (initializationStep != ConnectionInitialization.SteamTicketAndVersion) { return; }
|
||||
outMsg = new WriteOnlyMessage();
|
||||
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
|
||||
outMsg.Write((byte)ConnectionInitialization.SteamTicketAndVersion);
|
||||
outMsg.Write(Name);
|
||||
outMsg.Write(ownerKey);
|
||||
outMsg.Write(SteamManager.GetSteamID());
|
||||
if (steamAuthTicket == null)
|
||||
{
|
||||
outMsg.Write((UInt16)0);
|
||||
}
|
||||
else
|
||||
{
|
||||
outMsg.Write((UInt16)steamAuthTicket.Data.Length);
|
||||
outMsg.Write(steamAuthTicket.Data, 0, steamAuthTicket.Data.Length);
|
||||
}
|
||||
outMsg.Write(GameMain.Version.ToString());
|
||||
outMsg.Write(GameSettings.CurrentConfig.Language.Value);
|
||||
|
||||
SendMsgInternal(DeliveryMethod.Reliable, outMsg);
|
||||
PeerPacketHeaders headers = new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsConnectionInitializationStep,
|
||||
Initialization = ConnectionInitialization.SteamTicketAndVersion
|
||||
};
|
||||
|
||||
ClientSteamTicketAndVersionPacket body = new ClientSteamTicketAndVersionPacket
|
||||
{
|
||||
Name = GameMain.Client.Name,
|
||||
OwnerKey = ownerKey,
|
||||
SteamId = SteamManager.GetSteamId().Select(id => (AccountId)id),
|
||||
SteamAuthTicket = steamAuthTicket switch
|
||||
{
|
||||
null => Option<byte[]>.None(),
|
||||
var ticket => Option<byte[]>.Some(ticket.Data)
|
||||
},
|
||||
GameVersion = GameMain.Version.ToString(),
|
||||
Language = GameSettings.CurrentConfig.Language.Value
|
||||
};
|
||||
|
||||
SendMsgInternal(headers, body);
|
||||
break;
|
||||
}
|
||||
case ConnectionInitialization.ContentPackageOrder:
|
||||
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion ||
|
||||
initializationStep == ConnectionInitialization.Password) { initializationStep = ConnectionInitialization.ContentPackageOrder; }
|
||||
{
|
||||
if (initializationStep
|
||||
is ConnectionInitialization.SteamTicketAndVersion
|
||||
or ConnectionInitialization.Password)
|
||||
{
|
||||
initializationStep = ConnectionInitialization.ContentPackageOrder;
|
||||
}
|
||||
|
||||
if (initializationStep != ConnectionInitialization.ContentPackageOrder) { return; }
|
||||
outMsg = new WriteOnlyMessage();
|
||||
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
|
||||
outMsg.Write((byte)ConnectionInitialization.ContentPackageOrder);
|
||||
|
||||
string serverName = inc.ReadString();
|
||||
|
||||
UInt32 packageCount = inc.ReadVariableUInt32();
|
||||
List<ServerContentPackage> serverPackages = new List<ServerContentPackage>();
|
||||
for (int i = 0; i < packageCount; i++)
|
||||
PeerPacketHeaders headers = new PeerPacketHeaders
|
||||
{
|
||||
string name = inc.ReadString();
|
||||
UInt32 hashByteCount = inc.ReadVariableUInt32();
|
||||
byte[] hashBytes = inc.ReadBytes((int)hashByteCount);
|
||||
UInt64 workshopId = inc.ReadUInt64();
|
||||
UInt32 installTimeDiffSeconds = inc.ReadUInt32();
|
||||
DateTime installTime = DateTime.UtcNow + TimeSpan.FromSeconds(installTimeDiffSeconds);
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsConnectionInitializationStep,
|
||||
Initialization = ConnectionInitialization.ContentPackageOrder
|
||||
};
|
||||
|
||||
var pkg = new ServerContentPackage(name, Md5Hash.BytesAsHash(hashBytes), workshopId, installTime);
|
||||
serverPackages.Add(pkg);
|
||||
var orderPacket = INetSerializableStruct.Read<ServerPeerContentPackageOrderPacket>(inc.Message);
|
||||
|
||||
if (!ContentPackageOrderReceived)
|
||||
{
|
||||
ServerContentPackages = orderPacket.ContentPackages;
|
||||
if (ServerContentPackages.Length == 0)
|
||||
{
|
||||
string errorMsg = "Error in ContentPackageOrder message: list of content packages enabled on the server was empty.";
|
||||
GameAnalyticsManager.AddErrorEventOnce("ClientPeer.ReadConnectionInitializationStep:NoContentPackages", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
}
|
||||
ContentPackageOrderReceived = true;
|
||||
|
||||
SendMsgInternal(headers, null);
|
||||
}
|
||||
|
||||
if (!contentPackageOrderReceived)
|
||||
{
|
||||
ServerContentPackages = serverPackages.ToImmutableArray();
|
||||
SendMsgInternal(DeliveryMethod.Reliable, outMsg);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ConnectionInitialization.Password:
|
||||
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion) { initializationStep = ConnectionInitialization.Password; }
|
||||
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion)
|
||||
{
|
||||
initializationStep = ConnectionInitialization.Password;
|
||||
}
|
||||
|
||||
if (initializationStep != ConnectionInitialization.Password) { return; }
|
||||
bool incomingSalt = inc.ReadBoolean(); inc.ReadPadBits();
|
||||
int retries = 0;
|
||||
if (incomingSalt)
|
||||
|
||||
var passwordPacket = INetSerializableStruct.Read<ServerPeerPasswordPacket>(inc.Message);
|
||||
|
||||
if (WaitingForPassword) { return; }
|
||||
|
||||
passwordPacket.Salt.TryUnwrap(out passwordSalt);
|
||||
passwordPacket.RetriesLeft.TryUnwrap(out var retries);
|
||||
|
||||
LocalizedString pwMsg = TextManager.Get("PasswordRequired");
|
||||
|
||||
passwordMsgBox = new GUIMessageBox(pwMsg, "", new LocalizedString[] { TextManager.Get("OK"), TextManager.Get("Cancel") },
|
||||
relativeSize: new Vector2(0.25f, 0.1f), minSize: new Point(400, GUI.IntScale(170)));
|
||||
var passwordHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), passwordMsgBox.Content.RectTransform), childAnchor: Anchor.TopCenter);
|
||||
var passwordBox = new GUITextBox(new RectTransform(new Vector2(0.8f, 1f), passwordHolder.RectTransform) { MinSize = new Point(0, 20) })
|
||||
{
|
||||
passwordSalt = inc.ReadInt32();
|
||||
}
|
||||
else
|
||||
Censor = true
|
||||
};
|
||||
|
||||
if (retries > 0)
|
||||
{
|
||||
retries = inc.ReadInt32();
|
||||
var incorrectPasswordText = new GUITextBlock(new RectTransform(new Vector2(1f, 0.0f), passwordHolder.RectTransform), TextManager.Get("incorrectpassword"), GUIStyle.Red, GUIStyle.Font, textAlignment: Alignment.Center);
|
||||
incorrectPasswordText.RectTransform.MinSize = new Point(0, (int)incorrectPasswordText.TextSize.Y);
|
||||
passwordHolder.Recalculate();
|
||||
}
|
||||
OnRequestPassword?.Invoke(passwordSalt, retries);
|
||||
|
||||
passwordMsgBox.Content.Recalculate();
|
||||
passwordMsgBox.Content.RectTransform.MinSize = new Point(0, passwordMsgBox.Content.RectTransform.Children.Sum(c => c.Rect.Height));
|
||||
passwordMsgBox.Content.Parent.RectTransform.MinSize = new Point(0, (int)(passwordMsgBox.Content.RectTransform.MinSize.Y / passwordMsgBox.Content.RectTransform.RelativeSize.Y));
|
||||
|
||||
var okButton = passwordMsgBox.Buttons[0];
|
||||
okButton.OnClicked += (_, __) =>
|
||||
{
|
||||
SendPassword(passwordBox.Text);
|
||||
return true;
|
||||
};
|
||||
okButton.OnClicked += passwordMsgBox.Close;
|
||||
|
||||
var cancelButton = passwordMsgBox.Buttons[1];
|
||||
cancelButton.OnClicked = (_, __) =>
|
||||
{
|
||||
Close(PeerDisconnectPacket.WithReason(DisconnectReason.Disconnected));
|
||||
passwordMsgBox?.Close(); passwordMsgBox = null;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
passwordBox.OnEnterPressed += (_, __) =>
|
||||
{
|
||||
okButton.OnClicked.Invoke(okButton, okButton.UserData);
|
||||
return true;
|
||||
};
|
||||
|
||||
passwordBox.Select();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -167,4 +202,4 @@ namespace Barotrauma.Networking
|
||||
public abstract void ForceTimeOut();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
+127
-88
@@ -1,75 +1,82 @@
|
||||
using System;
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using Lidgren.Network;
|
||||
using Barotrauma.Steam;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class LidgrenClientPeer : ClientPeer
|
||||
internal sealed class LidgrenClientPeer : ClientPeer
|
||||
{
|
||||
private bool isActive;
|
||||
private NetClient netClient;
|
||||
private NetPeerConfiguration netPeerConfiguration;
|
||||
private NetClient? netClient;
|
||||
private readonly NetPeerConfiguration netPeerConfiguration;
|
||||
|
||||
List<NetIncomingMessage> incomingLidgrenMessages;
|
||||
private readonly List<NetIncomingMessage> incomingLidgrenMessages;
|
||||
|
||||
public LidgrenClientPeer(string name)
|
||||
private LidgrenEndpoint lidgrenEndpoint =>
|
||||
ServerConnection is LidgrenConnection { Endpoint: LidgrenEndpoint result }
|
||||
? result
|
||||
: throw new InvalidOperationException();
|
||||
|
||||
public LidgrenClientPeer(LidgrenEndpoint endpoint, Callbacks callbacks, Option<int> ownerKey) : base(endpoint, callbacks, ownerKey)
|
||||
{
|
||||
ServerConnection = null;
|
||||
|
||||
Name = name;
|
||||
|
||||
netClient = null;
|
||||
isActive = false;
|
||||
}
|
||||
|
||||
public override void Start(object endPoint, int ownerKey)
|
||||
{
|
||||
if (isActive) { return; }
|
||||
|
||||
this.ownerKey = ownerKey;
|
||||
|
||||
contentPackageOrderReceived = false;
|
||||
|
||||
netPeerConfiguration = new NetPeerConfiguration("barotrauma")
|
||||
{
|
||||
UseDualModeSockets = GameSettings.CurrentConfig.UseDualModeSockets
|
||||
};
|
||||
|
||||
netPeerConfiguration.DisableMessageType(NetIncomingMessageType.DebugMessage | NetIncomingMessageType.WarningMessage | NetIncomingMessageType.Receipt
|
||||
| NetIncomingMessageType.ErrorMessage | NetIncomingMessageType.Error);
|
||||
netPeerConfiguration.DisableMessageType(
|
||||
NetIncomingMessageType.DebugMessage
|
||||
| NetIncomingMessageType.WarningMessage
|
||||
| NetIncomingMessageType.Receipt
|
||||
| NetIncomingMessageType.ErrorMessage
|
||||
| NetIncomingMessageType.Error);
|
||||
|
||||
incomingLidgrenMessages = new List<NetIncomingMessage>();
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
if (isActive) { return; }
|
||||
|
||||
incomingLidgrenMessages.Clear();
|
||||
|
||||
ContentPackageOrderReceived = false;
|
||||
|
||||
netClient = new NetClient(netPeerConfiguration);
|
||||
|
||||
if (SteamManager.IsInitialized)
|
||||
{
|
||||
steamAuthTicket = SteamManager.GetAuthSessionTicket();
|
||||
//TODO: wait for GetAuthSessionTicketResponse_t
|
||||
|
||||
if (steamAuthTicket == null)
|
||||
{
|
||||
throw new Exception("GetAuthSessionTicket returned null");
|
||||
}
|
||||
}
|
||||
|
||||
incomingLidgrenMessages = new List<NetIncomingMessage>();
|
||||
|
||||
initializationStep = ConnectionInitialization.SteamTicketAndVersion;
|
||||
|
||||
if (!(endPoint is IPEndPoint ipEndPoint))
|
||||
if (!(ServerEndpoint is LidgrenEndpoint lidgrenEndpointValue))
|
||||
{
|
||||
throw new InvalidCastException("endPoint is not IPEndPoint");
|
||||
throw new InvalidCastException($"Endpoint is not {nameof(LidgrenEndpoint)}");
|
||||
}
|
||||
|
||||
if (ServerConnection != null)
|
||||
{
|
||||
throw new InvalidOperationException("ServerConnection is not null");
|
||||
}
|
||||
|
||||
netClient.Start();
|
||||
ServerConnection = new LidgrenConnection("Server", netClient.Connect(ipEndPoint), 0)
|
||||
|
||||
var netConnection = netClient.Connect(lidgrenEndpointValue.NetEndpoint);
|
||||
|
||||
ServerConnection = new LidgrenConnection(netConnection)
|
||||
{
|
||||
Status = NetworkConnectionStatus.Connected
|
||||
};
|
||||
@@ -81,11 +88,18 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
if (ownerKey != 0 && (ChildServerRelay.Process?.HasExited ?? true))
|
||||
ToolBox.ThrowIfNull(netClient);
|
||||
ToolBox.ThrowIfNull(incomingLidgrenMessages);
|
||||
|
||||
if (isOwner && !(ChildServerRelay.Process is { HasExited: false }))
|
||||
{
|
||||
Close();
|
||||
Close(PeerDisconnectPacket.WithReason(DisconnectReason.ServerCrashed));
|
||||
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
|
||||
msgBox.Buttons[0].OnClicked += (btn, obj) => { GameMain.MainMenuScreen.Select(); return false; };
|
||||
msgBox.Buttons[0].OnClicked += (btn, obj) =>
|
||||
{
|
||||
GameMain.MainMenuScreen.Select();
|
||||
return false;
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -97,7 +111,11 @@ namespace Barotrauma.Networking
|
||||
|
||||
foreach (NetIncomingMessage inc in incomingLidgrenMessages)
|
||||
{
|
||||
if (inc.SenderConnection != (ServerConnection as LidgrenConnection).NetConnection) { continue; }
|
||||
if (!inc.SenderConnection.RemoteEndPoint.Equals(lidgrenEndpoint.NetEndpoint))
|
||||
{
|
||||
DebugConsole.AddWarning($"Mismatched endpoint: expected {lidgrenEndpoint.NetEndpoint}, got {inc.SenderConnection.RemoteEndPoint}");
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (inc.MessageType)
|
||||
{
|
||||
@@ -111,26 +129,36 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleDataMessage(NetIncomingMessage inc)
|
||||
private void HandleDataMessage(NetIncomingMessage lidgrenMsg)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
PacketHeader packetHeader = (PacketHeader)inc.ReadByte();
|
||||
ToolBox.ThrowIfNull(ServerConnection);
|
||||
|
||||
if (packetHeader.IsConnectionInitializationStep() && initializationStep != ConnectionInitialization.Success)
|
||||
IReadMessage inc = lidgrenMsg.ToReadMessage();
|
||||
|
||||
var (_, packetHeader, initialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
||||
|
||||
if (packetHeader.IsConnectionInitializationStep())
|
||||
{
|
||||
ReadConnectionInitializationStep(new ReadWriteMessage(inc.Data, (int)inc.Position, inc.LengthBits, false));
|
||||
if (initializationStep == ConnectionInitialization.Success) { return; }
|
||||
|
||||
ReadConnectionInitializationStep(new IncomingInitializationMessage
|
||||
{
|
||||
InitializationStep = initialization ?? throw new Exception("Initialization step missing"),
|
||||
Message = inc
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (initializationStep != ConnectionInitialization.Success)
|
||||
{
|
||||
OnInitializationComplete?.Invoke();
|
||||
callbacks.OnInitializationComplete.Invoke();
|
||||
initializationStep = ConnectionInitialization.Success;
|
||||
}
|
||||
UInt16 length = inc.ReadUInt16();
|
||||
IReadMessage msg = new ReadOnlyMessage(inc.Data, packetHeader.IsCompressed(), inc.PositionInBytes, length, ServerConnection);
|
||||
OnMessageReceived?.Invoke(msg);
|
||||
|
||||
var packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
|
||||
callbacks.OnMessageReceived.Invoke(packet.GetReadMessage(packetHeader.IsCompressed(), ServerConnection));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,13 +166,14 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
NetConnectionStatus status = (NetConnectionStatus)inc.ReadByte();
|
||||
NetConnectionStatus status = inc.ReadHeader<NetConnectionStatus>();
|
||||
switch (status)
|
||||
{
|
||||
case NetConnectionStatus.Disconnected:
|
||||
string disconnectMsg = inc.ReadString();
|
||||
Close(disconnectMsg);
|
||||
OnDisconnectMessageReceived?.Invoke(disconnectMsg);
|
||||
var peerDisconnectPacket =
|
||||
PeerDisconnectPacket.FromLidgrenStringRepresentation(disconnectMsg);
|
||||
Close(peerDisconnectPacket.Fallback(PeerDisconnectPacket.WithReason(DisconnectReason.Unknown)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -153,49 +182,47 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
ToolBox.ThrowIfNull(netClient);
|
||||
|
||||
if (initializationStep != ConnectionInitialization.Password) { return; }
|
||||
NetOutgoingMessage outMsg = netClient.CreateMessage();
|
||||
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
|
||||
outMsg.Write((byte)ConnectionInitialization.Password);
|
||||
byte[] saltedPw = ServerSettings.SaltPassword(Encoding.UTF8.GetBytes(password), passwordSalt);
|
||||
outMsg.Write((byte)saltedPw.Length);
|
||||
outMsg.Write(saltedPw, 0, saltedPw.Length);
|
||||
NetSendResult result = netClient.SendMessage(outMsg, NetDeliveryMethod.ReliableUnordered);
|
||||
if (result != NetSendResult.Queued && result != NetSendResult.Sent)
|
||||
|
||||
var headers = new PeerPacketHeaders
|
||||
{
|
||||
DebugConsole.NewMessage("Failed to send " + initializationStep.ToString() + " message to host: " + result);
|
||||
}
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsConnectionInitializationStep,
|
||||
Initialization = ConnectionInitialization.Password
|
||||
};
|
||||
var body = new ClientPeerPasswordPacket
|
||||
{
|
||||
Password = ServerSettings.SaltPassword(Encoding.UTF8.GetBytes(password), passwordSalt)
|
||||
};
|
||||
|
||||
SendMsgInternal(headers, body);
|
||||
}
|
||||
|
||||
public override void Close(string msg = null, bool disableReconnect = false)
|
||||
public override void Close(PeerDisconnectPacket peerDisconnectPacket)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
ToolBox.ThrowIfNull(netClient);
|
||||
|
||||
isActive = false;
|
||||
|
||||
netClient.Shutdown(msg ?? TextManager.Get("Disconnecting").Value);
|
||||
netClient.Shutdown(peerDisconnectPacket.ToLidgrenStringRepresentation());
|
||||
netClient = null;
|
||||
steamAuthTicket?.Cancel(); steamAuthTicket = null;
|
||||
OnDisconnect?.Invoke(disableReconnect);
|
||||
|
||||
steamAuthTicket?.Cancel();
|
||||
steamAuthTicket = null;
|
||||
|
||||
callbacks.OnDisconnect.Invoke(peerDisconnectPacket);
|
||||
}
|
||||
|
||||
public override void Send(IWriteMessage msg, DeliveryMethod deliveryMethod, bool compressPastThreshold = true)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
NetDeliveryMethod lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable;
|
||||
switch (deliveryMethod)
|
||||
{
|
||||
case DeliveryMethod.Unreliable:
|
||||
lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable;
|
||||
break;
|
||||
case DeliveryMethod.Reliable:
|
||||
lidgrenDeliveryMethod = NetDeliveryMethod.ReliableUnordered;
|
||||
break;
|
||||
case DeliveryMethod.ReliableOrdered:
|
||||
lidgrenDeliveryMethod = NetDeliveryMethod.ReliableOrdered;
|
||||
break;
|
||||
}
|
||||
ToolBox.ThrowIfNull(netClient);
|
||||
ToolBox.ThrowIfNull(netPeerConfiguration);
|
||||
|
||||
#if DEBUG
|
||||
netPeerConfiguration.SimulatedDuplicatesChance = GameMain.Client.SimulatedDuplicatesChance;
|
||||
@@ -204,30 +231,42 @@ namespace Barotrauma.Networking
|
||||
netPeerConfiguration.SimulatedLoss = GameMain.Client.SimulatedLoss;
|
||||
#endif
|
||||
|
||||
NetOutgoingMessage lidgrenMsg = netClient.CreateMessage();
|
||||
byte[] msgData = new byte[msg.LengthBytes];
|
||||
msg.PrepareForSending(ref msgData, compressPastThreshold, out bool isCompressed, out int length);
|
||||
lidgrenMsg.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None));
|
||||
lidgrenMsg.Write((UInt16)length);
|
||||
lidgrenMsg.Write(msgData, 0, length);
|
||||
byte[] bufAux = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _);
|
||||
|
||||
NetSendResult result = netClient.SendMessage(lidgrenMsg, lidgrenDeliveryMethod);
|
||||
var headers = new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = deliveryMethod,
|
||||
PacketHeader = isCompressed ? PacketHeader.IsCompressed : PacketHeader.None,
|
||||
Initialization = null
|
||||
};
|
||||
var body = new PeerPacketMessage
|
||||
{
|
||||
Buffer = bufAux
|
||||
};
|
||||
|
||||
SendMsgInternal(headers, body);
|
||||
}
|
||||
|
||||
protected override void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body)
|
||||
{
|
||||
ToolBox.ThrowIfNull(netClient);
|
||||
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
msg.WriteNetSerializableStruct(headers);
|
||||
body?.Write(msg);
|
||||
|
||||
NetSendResult result = ForwardToLidgren(msg, DeliveryMethod.Reliable);
|
||||
if (result != NetSendResult.Queued && result != NetSendResult.Sent)
|
||||
{
|
||||
DebugConsole.NewMessage("Failed to send message to host: " + result);
|
||||
DebugConsole.NewMessage($"Failed to send message to host: {result}\n{Environment.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg)
|
||||
private NetSendResult ForwardToLidgren(IWriteMessage msg, DeliveryMethod deliveryMethod)
|
||||
{
|
||||
NetOutgoingMessage lidgrenMsg = netClient.CreateMessage();
|
||||
lidgrenMsg.Write(msg.Buffer, 0, msg.LengthBytes);
|
||||
ToolBox.ThrowIfNull(netClient);
|
||||
|
||||
NetSendResult result = netClient.SendMessage(lidgrenMsg, NetDeliveryMethod.ReliableUnordered);
|
||||
if (result != NetSendResult.Queued && result != NetSendResult.Sent)
|
||||
{
|
||||
DebugConsole.NewMessage("Failed to send message to host: " + result + "\n" + Environment.StackTrace);
|
||||
}
|
||||
return netClient.SendMessage(msg.ToLidgren(netClient), deliveryMethod.ToLidgren());
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
@@ -237,4 +276,4 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
+178
-193
@@ -1,38 +1,44 @@
|
||||
using System;
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Barotrauma.Steam;
|
||||
using System.Threading;
|
||||
using Barotrauma.Items.Components;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class SteamP2PClientPeer : ClientPeer
|
||||
internal sealed class SteamP2PClientPeer : ClientPeer
|
||||
{
|
||||
private bool isActive;
|
||||
private UInt64 hostSteamId;
|
||||
private readonly SteamId hostSteamId;
|
||||
private double timeout;
|
||||
private double heartbeatTimer;
|
||||
private double connectionStatusTimer;
|
||||
|
||||
private long sentBytes, receivedBytes;
|
||||
|
||||
private List<IReadMessage> incomingInitializationMessages;
|
||||
private List<IReadMessage> incomingDataMessages;
|
||||
private readonly List<IncomingInitializationMessage> incomingInitializationMessages = new List<IncomingInitializationMessage>();
|
||||
private readonly List<IReadMessage> incomingDataMessages = new List<IReadMessage>();
|
||||
|
||||
public SteamP2PClientPeer(string name)
|
||||
public SteamP2PClientPeer(SteamP2PEndpoint endpoint, Callbacks callbacks) : base(endpoint, callbacks, Option<int>.None())
|
||||
{
|
||||
ServerConnection = null;
|
||||
|
||||
Name = name;
|
||||
|
||||
isActive = false;
|
||||
|
||||
if (!(ServerEndpoint is SteamP2PEndpoint steamIdEndpoint))
|
||||
{
|
||||
throw new InvalidCastException("endPoint is not SteamId");
|
||||
}
|
||||
|
||||
hostSteamId = steamIdEndpoint.SteamId;
|
||||
}
|
||||
|
||||
public override void Start(object endPoint, int ownerKey)
|
||||
public override void Start()
|
||||
{
|
||||
contentPackageOrderReceived = false;
|
||||
if (isActive) { return; }
|
||||
|
||||
ContentPackageOrderReceived = false;
|
||||
|
||||
steamAuthTicket = SteamManager.GetAuthSessionTicket();
|
||||
//TODO: wait for GetAuthSessionTicketResponse_t
|
||||
@@ -42,32 +48,22 @@ namespace Barotrauma.Networking
|
||||
throw new Exception("GetAuthSessionTicket returned null");
|
||||
}
|
||||
|
||||
if (!(endPoint is UInt64 steamIdEndpoint))
|
||||
{
|
||||
throw new InvalidCastException("endPoint is not UInt64");
|
||||
}
|
||||
|
||||
hostSteamId = steamIdEndpoint;
|
||||
|
||||
Steamworks.SteamNetworking.ResetActions();
|
||||
Steamworks.SteamNetworking.OnP2PSessionRequest = OnIncomingConnection;
|
||||
Steamworks.SteamNetworking.OnP2PConnectionFailed = OnConnectionFailed;
|
||||
|
||||
Steamworks.SteamNetworking.AllowP2PPacketRelay(true);
|
||||
|
||||
ServerConnection = new SteamP2PConnection("Server", hostSteamId);
|
||||
ServerConnection.SetOwnerSteamIDIfUnknown(hostSteamId);
|
||||
ServerConnection = new SteamP2PConnection(hostSteamId);
|
||||
ServerConnection.SetAccountInfo(new AccountInfo(hostSteamId));
|
||||
|
||||
incomingInitializationMessages = new List<IReadMessage>();
|
||||
incomingDataMessages = new List<IReadMessage>();
|
||||
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
outMsg.Write((byte)DeliveryMethod.Reliable);
|
||||
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
|
||||
outMsg.Write((byte)ConnectionInitialization.ConnectionStarted);
|
||||
|
||||
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
|
||||
sentBytes += outMsg.LengthBytes;
|
||||
var headers = new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsConnectionInitializationStep,
|
||||
Initialization = ConnectionInitialization.ConnectionStarted
|
||||
};
|
||||
SendMsgInternal(headers, null);
|
||||
|
||||
initializationStep = ConnectionInitialization.SteamTicketAndVersion;
|
||||
|
||||
@@ -81,7 +77,8 @@ namespace Barotrauma.Networking
|
||||
private void OnIncomingConnection(Steamworks.SteamId steamId)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
if (steamId == hostSteamId)
|
||||
|
||||
if (steamId == hostSteamId.Value)
|
||||
{
|
||||
Steamworks.SteamNetworking.AcceptP2PSessionWithUser(steamId);
|
||||
}
|
||||
@@ -89,44 +86,51 @@ namespace Barotrauma.Networking
|
||||
initializationStep != ConnectionInitialization.ContentPackageOrder &&
|
||||
initializationStep != ConnectionInitialization.Success)
|
||||
{
|
||||
DebugConsole.ThrowError($"Connection from incorrect SteamID was rejected: "+
|
||||
$"expected {SteamManager.SteamIDUInt64ToString(hostSteamId)}," +
|
||||
$"got {SteamManager.SteamIDUInt64ToString(steamId)}");
|
||||
DebugConsole.ThrowError("Connection from incorrect SteamID was rejected: " +
|
||||
$"expected {hostSteamId}," +
|
||||
$"got {new SteamId(steamId)}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnConnectionFailed(Steamworks.SteamId steamId, Steamworks.P2PSessionError error)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
if (steamId != hostSteamId) { return; }
|
||||
Close($"SteamP2P connection failed: {error}");
|
||||
OnDisconnectMessageReceived?.Invoke($"{DisconnectReason.SteamP2PError}/SteamP2P connection failed: {error}");
|
||||
|
||||
if (steamId != hostSteamId.Value) { return; }
|
||||
|
||||
Close(PeerDisconnectPacket.SteamP2PError(error));
|
||||
}
|
||||
|
||||
private void OnP2PData(ulong steamId, byte[] data, int dataLength)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
if (steamId != hostSteamId) { return; }
|
||||
|
||||
timeout = Screen.Selected == GameMain.GameScreen ?
|
||||
NetworkConnection.TimeoutThresholdInGame :
|
||||
NetworkConnection.TimeoutThreshold;
|
||||
|
||||
PacketHeader packetHeader = (PacketHeader)data[0];
|
||||
if (steamId != hostSteamId.Value) { return; }
|
||||
|
||||
timeout = Screen.Selected == GameMain.GameScreen
|
||||
? NetworkConnection.TimeoutThresholdInGame
|
||||
: NetworkConnection.TimeoutThreshold;
|
||||
|
||||
IReadMessage inc = new ReadOnlyMessage(data, false, 0, dataLength, ServerConnection);
|
||||
|
||||
var (deliveryMethod, packetHeader, initialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
||||
|
||||
if (!packetHeader.IsServerMessage()) { return; }
|
||||
|
||||
if (packetHeader.IsConnectionInitializationStep())
|
||||
{
|
||||
ulong low = Lidgren.Network.NetBitWriter.ReadUInt32(data, 32, 8);
|
||||
ulong high = Lidgren.Network.NetBitWriter.ReadUInt32(data, 32, 8 + 32);
|
||||
ulong lobbyId = low + (high << 32);
|
||||
if (!initialization.HasValue) { return; }
|
||||
|
||||
Steam.SteamManager.JoinLobby(lobbyId, false);
|
||||
IReadMessage inc = new ReadOnlyMessage(data, false, 1 + 8, dataLength - (1 + 8), ServerConnection);
|
||||
var relayPacket = INetSerializableStruct.Read<SteamP2PInitializationRelayPacket>(inc);
|
||||
|
||||
SteamManager.JoinLobby(relayPacket.LobbyID, false);
|
||||
if (initializationStep != ConnectionInitialization.Success)
|
||||
{
|
||||
incomingInitializationMessages.Add(inc);
|
||||
incomingInitializationMessages.Add(new IncomingInitializationMessage
|
||||
{
|
||||
InitializationStep = initialization.Value,
|
||||
Message = relayPacket.Message.GetReadMessageUncompressed()
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (packetHeader.IsHeartbeatMessage())
|
||||
@@ -135,17 +139,13 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
else if (packetHeader.IsDisconnectMessage())
|
||||
{
|
||||
IReadMessage inc = new ReadOnlyMessage(data, false, 1, dataLength - 1, ServerConnection);
|
||||
string msg = inc.ReadString();
|
||||
Close(msg);
|
||||
OnDisconnectMessageReceived?.Invoke(msg);
|
||||
PeerDisconnectPacket packet = INetSerializableStruct.Read<PeerDisconnectPacket>(inc);
|
||||
Close(packet);
|
||||
}
|
||||
else
|
||||
{
|
||||
UInt16 length = Lidgren.Network.NetBitWriter.ReadUInt16(data, 16, 8);
|
||||
|
||||
IReadMessage inc = new ReadOnlyMessage(data, packetHeader.IsCompressed(), 3, length, ServerConnection);
|
||||
incomingDataMessages.Add(inc);
|
||||
var packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
|
||||
incomingDataMessages.Add(packet.GetReadMessage(packetHeader.IsCompressed(), ServerConnection!));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +157,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
timeout -= deltaTime;
|
||||
}
|
||||
|
||||
heartbeatTimer -= deltaTime;
|
||||
|
||||
if (initializationStep != ConnectionInitialization.Password &&
|
||||
@@ -166,20 +167,18 @@ namespace Barotrauma.Networking
|
||||
connectionStatusTimer -= deltaTime;
|
||||
if (connectionStatusTimer <= 0.0)
|
||||
{
|
||||
var state = Steamworks.SteamNetworking.GetP2PSessionState(hostSteamId);
|
||||
if (state == null)
|
||||
if (Steamworks.SteamNetworking.GetP2PSessionState(hostSteamId.Value) is { } state)
|
||||
{
|
||||
Close("SteamP2P connection could not be established");
|
||||
OnDisconnectMessageReceived?.Invoke(DisconnectReason.SteamP2PError.ToString());
|
||||
if (state.P2PSessionError != Steamworks.P2PSessionError.None)
|
||||
{
|
||||
Close(PeerDisconnectPacket.SteamP2PError(state.P2PSessionError));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (state?.P2PSessionError != Steamworks.P2PSessionError.None)
|
||||
{
|
||||
Close($"SteamP2P error code: {state?.P2PSessionError}");
|
||||
OnDisconnectMessageReceived?.Invoke($"{DisconnectReason.SteamP2PError}/SteamP2P error code: {state?.P2PSessionError}");
|
||||
}
|
||||
Close(PeerDisconnectPacket.WithReason(DisconnectReason.Timeout));
|
||||
}
|
||||
|
||||
connectionStatusTimer = 1.0f;
|
||||
}
|
||||
}
|
||||
@@ -187,11 +186,13 @@ namespace Barotrauma.Networking
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
if (!Steamworks.SteamNetworking.IsP2PPacketAvailable()) { break; }
|
||||
|
||||
var packet = Steamworks.SteamNetworking.ReadP2PPacket();
|
||||
if (packet.HasValue)
|
||||
if (packet is { SteamId: var steamId, Data: var data })
|
||||
{
|
||||
OnP2PData(packet?.SteamId ?? 0, packet?.Data, packet?.Data.Length ?? 0);
|
||||
receivedBytes += packet?.Data.Length ?? 0;
|
||||
OnP2PData(steamId, data, data.Length);
|
||||
if (!isActive) { return; }
|
||||
receivedBytes += data.Length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,20 +201,18 @@ namespace Barotrauma.Networking
|
||||
|
||||
if (heartbeatTimer < 0.0)
|
||||
{
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
outMsg.Write((byte)DeliveryMethod.Unreliable);
|
||||
outMsg.Write((byte)PacketHeader.IsHeartbeatMessage);
|
||||
|
||||
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Unreliable);
|
||||
sentBytes += outMsg.LengthBytes;
|
||||
|
||||
heartbeatTimer = 5.0;
|
||||
var headers = new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Unreliable,
|
||||
PacketHeader = PacketHeader.IsHeartbeatMessage,
|
||||
Initialization = null
|
||||
};
|
||||
SendMsgInternal(headers, null);
|
||||
}
|
||||
|
||||
if (timeout < 0.0)
|
||||
{
|
||||
Close("Timed out");
|
||||
OnDisconnectMessageReceived?.Invoke(DisconnectReason.SteamP2PTimeOut.ToString());
|
||||
Close(PeerDisconnectPacket.WithReason(DisconnectReason.SteamP2PTimeOut));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -221,12 +220,33 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (incomingDataMessages.Count > 0)
|
||||
{
|
||||
OnInitializationComplete?.Invoke();
|
||||
void initializationError(string errorMsg, string analyticsTag)
|
||||
{
|
||||
GameAnalyticsManager.AddErrorEventOnce($"SteamP2PClientPeer.OnInitializationComplete:{analyticsTag}", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
Close(PeerDisconnectPacket.WithReason(DisconnectReason.Disconnected));
|
||||
}
|
||||
|
||||
if (!ContentPackageOrderReceived)
|
||||
{
|
||||
initializationError(
|
||||
errorMsg: "Error during connection initialization: completed initialization before receiving content package order.",
|
||||
analyticsTag: "ContentPackageOrderNotReceived");
|
||||
return;
|
||||
}
|
||||
if (ServerContentPackages.Length == 0)
|
||||
{
|
||||
initializationError(
|
||||
errorMsg: "Error during connection initialization: list of content packages enabled on the server was empty when completing initialization.",
|
||||
analyticsTag: "NoContentPackages");
|
||||
return;
|
||||
}
|
||||
callbacks.OnInitializationComplete.Invoke();
|
||||
initializationStep = ConnectionInitialization.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (IReadMessage inc in incomingInitializationMessages)
|
||||
foreach (var inc in incomingInitializationMessages)
|
||||
{
|
||||
ReadConnectionInitializationStep(inc);
|
||||
}
|
||||
@@ -237,7 +257,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
foreach (IReadMessage inc in incomingDataMessages)
|
||||
{
|
||||
OnMessageReceived?.Invoke(inc);
|
||||
callbacks.OnMessageReceived.Invoke(inc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,76 +269,40 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
byte[] buf = new byte[msg.LengthBytes + 4];
|
||||
buf[0] = (byte)deliveryMethod;
|
||||
byte[] bufAux = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _);
|
||||
|
||||
byte[] bufAux = new byte[msg.LengthBytes];
|
||||
msg.PrepareForSending(ref bufAux, compressPastThreshold, out bool isCompressed, out int length);
|
||||
|
||||
buf[1] = (byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None);
|
||||
|
||||
buf[2] = (byte)(length & 0xff);
|
||||
buf[3] = (byte)((length >> 8) & 0xff);
|
||||
|
||||
Array.Copy(bufAux, 0, buf, 4, length);
|
||||
|
||||
Steamworks.P2PSend sendType;
|
||||
switch (deliveryMethod)
|
||||
var headers = new PeerPacketHeaders
|
||||
{
|
||||
case DeliveryMethod.Reliable:
|
||||
case DeliveryMethod.ReliableOrdered:
|
||||
//the documentation seems to suggest that the Reliable send type
|
||||
//enforces packet order (TODO: verify)
|
||||
sendType = Steamworks.P2PSend.Reliable;
|
||||
break;
|
||||
default:
|
||||
sendType = Steamworks.P2PSend.Unreliable;
|
||||
break;
|
||||
}
|
||||
|
||||
if (length + 8 >= MsgConstants.MTU)
|
||||
DeliveryMethod = deliveryMethod,
|
||||
PacketHeader = isCompressed ? PacketHeader.IsCompressed : PacketHeader.None,
|
||||
Initialization = null
|
||||
};
|
||||
var body = new PeerPacketMessage
|
||||
{
|
||||
DebugConsole.Log("WARNING: message length comes close to exceeding MTU, forcing reliable send (" + length.ToString() + " bytes)");
|
||||
sendType = Steamworks.P2PSend.Reliable;
|
||||
}
|
||||
Buffer = bufAux
|
||||
};
|
||||
|
||||
heartbeatTimer = 5.0;
|
||||
|
||||
// Using an extra local method here to reduce chance of error whenever we need to change this
|
||||
void performSend() => SendMsgInternal(headers, body);
|
||||
#if DEBUG
|
||||
CoroutineManager.Invoke(() =>
|
||||
{
|
||||
if (GameMain.Client == null) { return; }
|
||||
if (Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedLoss && sendType != Steamworks.P2PSend.Reliable) { return; }
|
||||
int count = Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedDuplicatesChance ? 2 : 1;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Send(buf, length + 4, sendType);
|
||||
}
|
||||
},
|
||||
GameMain.Client.SimulatedMinimumLatency + Rand.Range(0.0f, GameMain.Client.SimulatedRandomLatency));
|
||||
#else
|
||||
Send(buf, length + 4, sendType);
|
||||
#endif
|
||||
}
|
||||
if (GameMain.Client == null) { return; }
|
||||
|
||||
private void Send(byte[] buf, int length, Steamworks.P2PSend sendType)
|
||||
{
|
||||
bool successSend = Steamworks.SteamNetworking.SendP2PPacket(hostSteamId, buf, length + 4, 0, sendType);
|
||||
sentBytes += length + 4;
|
||||
if (!successSend)
|
||||
{
|
||||
if (sendType != Steamworks.P2PSend.Reliable)
|
||||
{
|
||||
DebugConsole.Log("WARNING: message couldn't be sent unreliably, forcing reliable send (" + length.ToString() + " bytes)");
|
||||
sendType = Steamworks.P2PSend.Reliable;
|
||||
successSend = Steamworks.SteamNetworking.SendP2PPacket(hostSteamId, buf, length + 4, 0, sendType);
|
||||
sentBytes += length + 4;
|
||||
}
|
||||
if (!successSend)
|
||||
{
|
||||
DebugConsole.AddWarning("Failed to send message to remote peer! (" + length.ToString() + " bytes)");
|
||||
}
|
||||
}
|
||||
if (Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedLoss && deliveryMethod is DeliveryMethod.Unreliable) { return; }
|
||||
|
||||
int count = Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedDuplicatesChance ? 2 : 1;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
performSend();
|
||||
}
|
||||
},
|
||||
GameMain.Client.SimulatedMinimumLatency + Rand.Range(0.0f, GameMain.Client.SimulatedRandomLatency));
|
||||
#else
|
||||
performSend();
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void SendPassword(string password)
|
||||
@@ -326,20 +310,22 @@ namespace Barotrauma.Networking
|
||||
if (!isActive) { return; }
|
||||
|
||||
if (initializationStep != ConnectionInitialization.Password) { return; }
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
outMsg.Write((byte)DeliveryMethod.Reliable);
|
||||
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
|
||||
outMsg.Write((byte)ConnectionInitialization.Password);
|
||||
byte[] saltedPw = ServerSettings.SaltPassword(Encoding.UTF8.GetBytes(password), passwordSalt);
|
||||
outMsg.Write((byte)saltedPw.Length);
|
||||
outMsg.Write(saltedPw, 0, saltedPw.Length);
|
||||
|
||||
heartbeatTimer = 5.0;
|
||||
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
|
||||
sentBytes += outMsg.LengthBytes;
|
||||
var headers = new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsConnectionInitializationStep,
|
||||
Initialization = ConnectionInitialization.Password
|
||||
};
|
||||
var body = new ClientPeerPasswordPacket
|
||||
{
|
||||
Password = ServerSettings.SaltPassword(Encoding.UTF8.GetBytes(password), passwordSalt)
|
||||
};
|
||||
|
||||
SendMsgInternal(headers, body);
|
||||
}
|
||||
|
||||
public override void Close(string msg = null, bool disableReconnect = false)
|
||||
|
||||
public override void Close(PeerDisconnectPacket peerDisconnectPacket)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
@@ -347,55 +333,54 @@ namespace Barotrauma.Networking
|
||||
|
||||
isActive = false;
|
||||
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
outMsg.Write((byte)DeliveryMethod.Reliable);
|
||||
outMsg.Write((byte)PacketHeader.IsDisconnectMessage);
|
||||
outMsg.Write(msg ?? "Disconnected");
|
||||
|
||||
try
|
||||
var headers = new PeerPacketHeaders
|
||||
{
|
||||
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
|
||||
sentBytes += outMsg.LengthBytes;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to send a disconnect message to the server using SteamP2P.", e);
|
||||
}
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsDisconnectMessage,
|
||||
Initialization = null
|
||||
};
|
||||
SendMsgInternal(headers, peerDisconnectPacket);
|
||||
|
||||
Thread.Sleep(100);
|
||||
|
||||
Steamworks.SteamNetworking.ResetActions();
|
||||
Steamworks.SteamNetworking.CloseP2PSessionWithUser(hostSteamId);
|
||||
Steamworks.SteamNetworking.CloseP2PSessionWithUser(hostSteamId.Value);
|
||||
|
||||
steamAuthTicket?.Cancel(); steamAuthTicket = null;
|
||||
hostSteamId = 0;
|
||||
steamAuthTicket?.Cancel();
|
||||
steamAuthTicket = null;
|
||||
|
||||
OnDisconnect?.Invoke(disableReconnect);
|
||||
callbacks.OnDisconnect.Invoke(peerDisconnectPacket);
|
||||
}
|
||||
|
||||
protected override void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg)
|
||||
protected override void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body)
|
||||
{
|
||||
Steamworks.P2PSend sendType;
|
||||
switch (deliveryMethod)
|
||||
IWriteMessage msgToSend = new WriteOnlyMessage();
|
||||
msgToSend.WriteNetSerializableStruct(headers);
|
||||
body?.Write(msgToSend);
|
||||
ForwardToSteamP2P(msgToSend, headers.DeliveryMethod);
|
||||
}
|
||||
|
||||
private void ForwardToSteamP2P(IWriteMessage msg, DeliveryMethod deliveryMethod)
|
||||
{
|
||||
heartbeatTimer = 5.0;
|
||||
int length = msg.LengthBytes;
|
||||
|
||||
bool successSend = Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, msg.Buffer, length, 0, deliveryMethod.ToSteam());
|
||||
sentBytes += length;
|
||||
|
||||
if (successSend) { return; }
|
||||
|
||||
if (deliveryMethod is DeliveryMethod.Unreliable)
|
||||
{
|
||||
case DeliveryMethod.Reliable:
|
||||
case DeliveryMethod.ReliableOrdered:
|
||||
//the documentation seems to suggest that the Reliable send type
|
||||
//enforces packet order (TODO: verify)
|
||||
sendType = Steamworks.P2PSend.Reliable;
|
||||
break;
|
||||
default:
|
||||
sendType = Steamworks.P2PSend.Unreliable;
|
||||
break;
|
||||
DebugConsole.Log($"WARNING: message couldn't be sent unreliably, forcing reliable send ({length} bytes)");
|
||||
successSend = Steamworks.SteamNetworking.SendP2PPacket(hostSteamId.Value, msg.Buffer, length, 0, DeliveryMethod.Reliable.ToSteam());
|
||||
sentBytes += length;
|
||||
}
|
||||
|
||||
IWriteMessage msgToSend = new WriteOnlyMessage();
|
||||
msgToSend.Write((byte)deliveryMethod);
|
||||
msgToSend.Write(msg.Buffer, 0, msg.LengthBytes);
|
||||
|
||||
heartbeatTimer = 5.0;
|
||||
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId, msgToSend.Buffer, msgToSend.LengthBytes, 0, sendType);
|
||||
sentBytes += msg.LengthBytes;
|
||||
if (!successSend)
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to send message to remote peer! ({length} bytes)");
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
@@ -405,4 +390,4 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
+262
-252
@@ -1,75 +1,82 @@
|
||||
using Barotrauma.Steam;
|
||||
#nullable enable
|
||||
using Barotrauma.Steam;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class SteamP2POwnerPeer : ClientPeer
|
||||
sealed class SteamP2POwnerPeer : ClientPeer
|
||||
{
|
||||
private bool isActive;
|
||||
private readonly SteamId selfSteamID;
|
||||
private UInt64 ownerKey64 => unchecked((UInt64)ownerKey.Fallback(0));
|
||||
|
||||
private readonly UInt64 selfSteamID;
|
||||
private UInt64 ownerKey64 => unchecked((UInt64)ownerKey);
|
||||
private SteamId ReadSteamId(IReadMessage inc) => new SteamId(inc.ReadUInt64() ^ ownerKey64);
|
||||
private void WriteSteamId(IWriteMessage msg, SteamId val) => msg.WriteUInt64(val.Value ^ ownerKey64);
|
||||
|
||||
private UInt64 ReadSteamId(IReadMessage inc)
|
||||
=> inc.ReadUInt64() ^ ownerKey64;
|
||||
private void WriteSteamId(IWriteMessage msg, UInt64 val)
|
||||
=> msg.Write(val ^ ownerKey64);
|
||||
|
||||
private long sentBytes, receivedBytes;
|
||||
|
||||
class RemotePeer
|
||||
private sealed class RemotePeer
|
||||
{
|
||||
public UInt64 SteamID;
|
||||
public UInt64 OwnerSteamID;
|
||||
public readonly SteamId SteamId;
|
||||
public Option<SteamId> OwnerSteamId;
|
||||
public double? DisconnectTime;
|
||||
public bool Authenticating;
|
||||
public bool Authenticated;
|
||||
|
||||
public class UnauthedMessage
|
||||
public readonly struct UnauthedMessage
|
||||
{
|
||||
public DeliveryMethod DeliveryMethod;
|
||||
public IWriteMessage Message;
|
||||
}
|
||||
public List<UnauthedMessage> UnauthedMessages;
|
||||
public readonly SteamId Sender;
|
||||
public readonly byte[] Bytes;
|
||||
public readonly int Length;
|
||||
|
||||
public RemotePeer(UInt64 steamId)
|
||||
public UnauthedMessage(SteamId sender, byte[] bytes)
|
||||
{
|
||||
Sender = sender;
|
||||
Bytes = bytes;
|
||||
Length = bytes.Length;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly List<UnauthedMessage> UnauthedMessages;
|
||||
|
||||
public RemotePeer(SteamId steamId)
|
||||
{
|
||||
SteamID = steamId;
|
||||
OwnerSteamID = 0;
|
||||
SteamId = steamId;
|
||||
OwnerSteamId = Option<SteamId>.None();
|
||||
DisconnectTime = null;
|
||||
Authenticating = false;
|
||||
Authenticated = false;
|
||||
|
||||
UnauthedMessages = new List<UnauthedMessage>();
|
||||
}
|
||||
|
||||
}
|
||||
List<RemotePeer> remotePeers;
|
||||
|
||||
public SteamP2POwnerPeer(string name)
|
||||
private List<RemotePeer> remotePeers = null!;
|
||||
|
||||
public SteamP2POwnerPeer(Callbacks callbacks, int ownerKey) : base(new PipeEndpoint(), callbacks, Option<int>.Some(ownerKey))
|
||||
{
|
||||
ServerConnection = null;
|
||||
|
||||
Name = name;
|
||||
|
||||
isActive = false;
|
||||
|
||||
selfSteamID = Steam.SteamManager.GetSteamID();
|
||||
selfSteamID = SteamManager.GetSteamId().TryUnwrap(out var steamId)
|
||||
? steamId
|
||||
: throw new InvalidOperationException("Steamworks not initialized");
|
||||
}
|
||||
|
||||
public override void Start(object endPoint, int ownerKey)
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
if (isActive) { return; }
|
||||
|
||||
this.ownerKey = ownerKey;
|
||||
|
||||
initializationStep = ConnectionInitialization.SteamTicketAndVersion;
|
||||
|
||||
ServerConnection = new PipeConnection(selfSteamID);
|
||||
ServerConnection.Status = NetworkConnectionStatus.Connected;
|
||||
ServerConnection = new PipeConnection(selfSteamID)
|
||||
{
|
||||
Status = NetworkConnectionStatus.Connected
|
||||
};
|
||||
|
||||
remotePeers = new List<RemotePeer>();
|
||||
|
||||
@@ -82,47 +89,34 @@ namespace Barotrauma.Networking
|
||||
isActive = true;
|
||||
}
|
||||
|
||||
private void OnAuthChange(Steamworks.SteamId steamID, Steamworks.SteamId ownerID, Steamworks.AuthResponse status)
|
||||
private void OnAuthChange(Steamworks.SteamId steamId, Steamworks.SteamId ownerId, Steamworks.AuthResponse status)
|
||||
{
|
||||
RemotePeer remotePeer = remotePeers.Find(p => p.SteamID == steamID);
|
||||
DebugConsole.Log(steamID + " validation: " + status + ", " + (remotePeer != null));
|
||||
RemotePeer? remotePeer = remotePeers.Find(p => p.SteamId.Value == steamId);
|
||||
|
||||
if (remotePeer == null) { return; }
|
||||
|
||||
if (remotePeer.Authenticated)
|
||||
{
|
||||
if (status != Steamworks.AuthResponse.OK)
|
||||
{
|
||||
DisconnectPeer(remotePeer, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam authentication status changed: " + status.ToString());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (status == Steamworks.AuthResponse.OK)
|
||||
{
|
||||
remotePeer.OwnerSteamID = ownerID;
|
||||
if (remotePeer.Authenticated) { return; }
|
||||
|
||||
SteamId ownerSteamId = new SteamId(ownerId);
|
||||
remotePeer.OwnerSteamId = Option<SteamId>.Some(ownerSteamId);
|
||||
remotePeer.Authenticated = true;
|
||||
remotePeer.Authenticating = false;
|
||||
foreach (var msg in remotePeer.UnauthedMessages)
|
||||
foreach (var unauthedMessage in remotePeer.UnauthedMessages)
|
||||
{
|
||||
//rewrite the owner id before
|
||||
//forwarding the messages to
|
||||
//the server, since it's only
|
||||
//known now
|
||||
int prevBitPosition = msg.Message.BitPosition;
|
||||
msg.Message.BitPosition = sizeof(ulong) * 8;
|
||||
WriteSteamId(msg.Message, ownerID);
|
||||
msg.Message.BitPosition = prevBitPosition;
|
||||
byte[] msgToSend = (byte[])msg.Message.Buffer.Clone();
|
||||
Array.Resize(ref msgToSend, msg.Message.LengthBytes);
|
||||
ChildServerRelay.Write(msgToSend);
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
WriteSteamId(msg, unauthedMessage.Sender);
|
||||
WriteSteamId(msg, ownerSteamId);
|
||||
msg.WriteBytes(unauthedMessage.Bytes, 0, unauthedMessage.Length);
|
||||
ForwardToServerProcess(msg);
|
||||
}
|
||||
|
||||
remotePeer.UnauthedMessages.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
DisconnectPeer(remotePeer, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam authentication failed: " + status.ToString());
|
||||
return;
|
||||
DisconnectPeer(remotePeer, PeerDisconnectPacket.SteamAuthError(status));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,67 +124,62 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
if (!remotePeers.Any(p => p.SteamID == steamId))
|
||||
if (remotePeers.None(p => p.SteamId.Value == steamId))
|
||||
{
|
||||
remotePeers.Add(new RemotePeer(steamId));
|
||||
remotePeers.Add(new RemotePeer(new SteamId(steamId)));
|
||||
}
|
||||
|
||||
Steamworks.SteamNetworking.AcceptP2PSessionWithUser(steamId); //accept all connections, the server will figure things out later
|
||||
}
|
||||
|
||||
private void OnP2PData(ulong steamId, byte[] data, int dataLength, int channel)
|
||||
private void OnP2PData(ulong steamId, IReadMessage inc)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
RemotePeer remotePeer = remotePeers.Find(p => p.SteamID == steamId);
|
||||
if (remotePeer == null || remotePeer.DisconnectTime != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
RemotePeer? remotePeer = remotePeers.Find(p => p.SteamId.Value == steamId);
|
||||
if (remotePeer == null) { return; }
|
||||
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
WriteSteamId(outMsg, steamId);
|
||||
WriteSteamId(outMsg, remotePeer.OwnerSteamID);
|
||||
outMsg.Write(data, 1, dataLength - 1);
|
||||
if (remotePeer.DisconnectTime != null) { return; }
|
||||
|
||||
DeliveryMethod deliveryMethod = (DeliveryMethod)data[0];
|
||||
var peerPacketHeaders = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
||||
|
||||
PacketHeader packetHeader = peerPacketHeaders.PacketHeader;
|
||||
|
||||
PacketHeader packetHeader = (PacketHeader)data[1];
|
||||
|
||||
if (!remotePeer.Authenticated & !remotePeer.Authenticating && packetHeader.IsConnectionInitializationStep())
|
||||
if (!remotePeer.Authenticated && !remotePeer.Authenticating && packetHeader.IsConnectionInitializationStep())
|
||||
{
|
||||
remotePeer.DisconnectTime = null;
|
||||
|
||||
IReadMessage authMsg = new ReadOnlyMessage(data, packetHeader.IsCompressed(), 2, dataLength - 2, null);
|
||||
ConnectionInitialization initializationStep = (ConnectionInitialization)authMsg.ReadByte();
|
||||
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion)
|
||||
ConnectionInitialization initialization = peerPacketHeaders.Initialization ?? throw new Exception("Initialization step missing");
|
||||
if (initialization == ConnectionInitialization.SteamTicketAndVersion)
|
||||
{
|
||||
remotePeer.Authenticating = true;
|
||||
|
||||
authMsg.ReadString(); //skip name
|
||||
authMsg.ReadInt32(); //skip owner key
|
||||
authMsg.ReadUInt64(); //skip steamid
|
||||
UInt16 ticketLength = authMsg.ReadUInt16();
|
||||
byte[] ticket = authMsg.ReadBytes(ticketLength);
|
||||
|
||||
Steamworks.BeginAuthResult authSessionStartState = Steam.SteamManager.StartAuthSession(ticket, steamId);
|
||||
var packet = INetSerializableStruct.Read<ClientSteamTicketAndVersionPacket>(inc);
|
||||
|
||||
packet.SteamAuthTicket.TryUnwrap(out byte[] ticket);
|
||||
|
||||
Steamworks.BeginAuthResult authSessionStartState = SteamManager.StartAuthSession(ticket, steamId);
|
||||
if (authSessionStartState != Steamworks.BeginAuthResult.OK)
|
||||
{
|
||||
DisconnectPeer(remotePeer, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam auth session failed to start: " + authSessionStartState.ToString());
|
||||
DisconnectPeer(remotePeer, PeerDisconnectPacket.SteamAuthError(authSessionStartState));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var steamUserId = new SteamId(steamId);
|
||||
if (remotePeer.Authenticating)
|
||||
{
|
||||
remotePeer.UnauthedMessages.Add(new RemotePeer.UnauthedMessage() { DeliveryMethod = deliveryMethod, Message = outMsg });
|
||||
remotePeer.UnauthedMessages.Add(new RemotePeer.UnauthedMessage(steamUserId, inc.Buffer));
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] msgToSend = (byte[])outMsg.Buffer.Clone();
|
||||
Array.Resize(ref msgToSend, outMsg.LengthBytes);
|
||||
ChildServerRelay.Write(msgToSend);
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
WriteSteamId(outMsg, steamUserId);
|
||||
WriteSteamId(outMsg, remotePeer.OwnerSteamId.Fallback(steamUserId));
|
||||
outMsg.WriteBytes(inc.Buffer, 0, inc.LengthBytes);
|
||||
|
||||
ForwardToServerProcess(outMsg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,11 +187,15 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
if (ChildServerRelay.HasShutDown || (ChildServerRelay.Process?.HasExited ?? true))
|
||||
if (ChildServerRelay.HasShutDown || !(ChildServerRelay.Process is { HasExited: false }))
|
||||
{
|
||||
Close();
|
||||
Close(PeerDisconnectPacket.WithReason(DisconnectReason.ServerCrashed));
|
||||
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
|
||||
msgBox.Buttons[0].OnClicked += (btn, obj) => { GameMain.MainMenuScreen.Select(); return false; };
|
||||
msgBox.Buttons[0].OnClicked += (btn, obj) =>
|
||||
{
|
||||
GameMain.MainMenuScreen.Select();
|
||||
return false;
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -217,11 +210,12 @@ namespace Barotrauma.Networking
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
if (!Steamworks.SteamNetworking.IsP2PPacketAvailable()) { break; }
|
||||
|
||||
var packet = Steamworks.SteamNetworking.ReadP2PPacket();
|
||||
if (packet.HasValue)
|
||||
if (packet is { SteamId: var steamId, Data: var data })
|
||||
{
|
||||
OnP2PData(packet?.SteamId ?? 0, packet?.Data, packet?.Data.Length ?? 0, 0);
|
||||
receivedBytes += packet?.Data.Length ?? 0;
|
||||
OnP2PData(steamId, new ReadWriteMessage(data, 0, data.Length * 8, false));
|
||||
receivedBytes += data.Length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,177 +234,156 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
UInt64 recipientSteamId = ReadSteamId(inc);
|
||||
DeliveryMethod deliveryMethod = (DeliveryMethod)inc.ReadByte();
|
||||
SteamId recipientSteamId = ReadSteamId(inc);
|
||||
|
||||
int p2pDataStart = inc.BytePosition;
|
||||
|
||||
PacketHeader packetHeader = (PacketHeader)inc.ReadByte();
|
||||
var peerPacketHeaders = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
||||
|
||||
if (recipientSteamId != selfSteamID)
|
||||
{
|
||||
if (!packetHeader.IsServerMessage())
|
||||
{
|
||||
DebugConsole.ThrowError("Received non-server message meant for remote peer");
|
||||
return;
|
||||
}
|
||||
|
||||
RemotePeer peer = remotePeers.Find(p => p.SteamID == recipientSteamId);
|
||||
|
||||
if (peer == null) { return; }
|
||||
|
||||
if (packetHeader.IsDisconnectMessage())
|
||||
{
|
||||
DisconnectPeer(peer, inc.ReadString());
|
||||
return;
|
||||
}
|
||||
|
||||
Steamworks.P2PSend sendType;
|
||||
switch (deliveryMethod)
|
||||
{
|
||||
case DeliveryMethod.Reliable:
|
||||
case DeliveryMethod.ReliableOrdered:
|
||||
//the documentation seems to suggest that the
|
||||
//Reliable send type enforces packet order
|
||||
sendType = Steamworks.P2PSend.Reliable;
|
||||
break;
|
||||
default:
|
||||
sendType = Steamworks.P2PSend.Unreliable;
|
||||
break;
|
||||
}
|
||||
|
||||
byte[] p2pData;
|
||||
|
||||
if (packetHeader.IsConnectionInitializationStep())
|
||||
{
|
||||
p2pData = new byte[inc.LengthBytes - p2pDataStart + 8];
|
||||
p2pData[0] = inc.Buffer[p2pDataStart];
|
||||
Lidgren.Network.NetBitWriter.WriteUInt64(SteamManager.CurrentLobbyID, 8 * 8, p2pData, 1 * 8);
|
||||
Array.Copy(inc.Buffer, p2pDataStart+1, p2pData, 1 + 8, inc.LengthBytes - p2pDataStart - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
p2pData = new byte[inc.LengthBytes - p2pDataStart];
|
||||
Array.Copy(inc.Buffer, p2pDataStart, p2pData, 0, p2pData.Length);
|
||||
|
||||
if (!packetHeader.IsHeartbeatMessage() && !packetHeader.IsDisconnectMessage())
|
||||
{
|
||||
UInt16 length = Lidgren.Network.NetBitWriter.ReadUInt16(p2pData, 16, 8);
|
||||
if (length > p2pData.Length - 2)
|
||||
{
|
||||
string errorMsg = $"Length written in message to send to client is larger than buffer size ({length} > {p2pData.Length - 2})";
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"SteamP2POwnerPeerLengthValidationFail",
|
||||
GameAnalyticsManager.ErrorSeverity.Error,
|
||||
errorMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (p2pData.Length + 4 >= MsgConstants.MTU)
|
||||
{
|
||||
DebugConsole.Log("WARNING: message length comes close to exceeding MTU, forcing reliable send (" + p2pData.Length.ToString() + " bytes)");
|
||||
sendType = Steamworks.P2PSend.Reliable;
|
||||
}
|
||||
|
||||
bool successSend = Steamworks.SteamNetworking.SendP2PPacket(recipientSteamId, p2pData, p2pData.Length, 0, sendType);
|
||||
sentBytes += p2pData.Length;
|
||||
|
||||
if (!successSend)
|
||||
{
|
||||
if (sendType != Steamworks.P2PSend.Reliable)
|
||||
{
|
||||
DebugConsole.Log("WARNING: message couldn't be sent unreliably, forcing reliable send (" + p2pData.Length.ToString() + " bytes)");
|
||||
sendType = Steamworks.P2PSend.Reliable;
|
||||
successSend = Steamworks.SteamNetworking.SendP2PPacket(recipientSteamId, p2pData, p2pData.Length, 0, sendType);
|
||||
sentBytes += p2pData.Length;
|
||||
}
|
||||
if (!successSend)
|
||||
{
|
||||
DebugConsole.AddWarning("Failed to send message to remote peer! (" + p2pData.Length.ToString() + " bytes)");
|
||||
}
|
||||
}
|
||||
HandleMessageForRemotePeer(peerPacketHeaders, recipientSteamId, inc);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (packetHeader.IsDisconnectMessage())
|
||||
{
|
||||
DebugConsole.ThrowError("Received disconnect message from owned server");
|
||||
return;
|
||||
}
|
||||
if (!packetHeader.IsServerMessage())
|
||||
{
|
||||
DebugConsole.ThrowError("Received non-server message from owned server");
|
||||
return;
|
||||
}
|
||||
if (packetHeader.IsHeartbeatMessage())
|
||||
{
|
||||
return; //no timeout since we're using pipes, ignore this message
|
||||
}
|
||||
if (packetHeader.IsConnectionInitializationStep())
|
||||
{
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
WriteSteamId(outMsg, selfSteamID);
|
||||
WriteSteamId(outMsg, selfSteamID);
|
||||
outMsg.Write((byte)(PacketHeader.IsConnectionInitializationStep));
|
||||
outMsg.Write(Name);
|
||||
|
||||
byte[] msgToSend = (byte[])outMsg.Buffer.Clone();
|
||||
Array.Resize(ref msgToSend, outMsg.LengthBytes);
|
||||
ChildServerRelay.Write(msgToSend);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (initializationStep != ConnectionInitialization.Success)
|
||||
{
|
||||
OnInitializationComplete?.Invoke();
|
||||
initializationStep = ConnectionInitialization.Success;
|
||||
}
|
||||
UInt16 length = inc.ReadUInt16();
|
||||
IReadMessage msg = new ReadOnlyMessage(inc.Buffer, packetHeader.IsCompressed(), inc.BytePosition, length, ServerConnection);
|
||||
OnMessageReceived?.Invoke(msg);
|
||||
|
||||
return;
|
||||
}
|
||||
HandleMessageForOwner(peerPacketHeaders, inc);
|
||||
}
|
||||
}
|
||||
|
||||
private void DisconnectPeer(RemotePeer peer, string msg)
|
||||
private static byte[] GetRemainingBytes(IReadMessage msg)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(msg))
|
||||
return msg.Buffer[msg.BytePosition..msg.LengthBytes];
|
||||
}
|
||||
|
||||
private void HandleMessageForRemotePeer(PeerPacketHeaders peerPacketHeaders, SteamId recipientSteamId, IReadMessage inc)
|
||||
{
|
||||
var (deliveryMethod, packetHeader, initialization) = peerPacketHeaders;
|
||||
|
||||
if (!packetHeader.IsServerMessage())
|
||||
{
|
||||
if (peer.DisconnectTime == null)
|
||||
DebugConsole.ThrowError("Received non-server message meant for remote peer");
|
||||
return;
|
||||
}
|
||||
|
||||
RemotePeer? peer = remotePeers.Find(p => p.SteamId == recipientSteamId);
|
||||
if (peer is null) { return; }
|
||||
|
||||
if (packetHeader.IsDisconnectMessage())
|
||||
{
|
||||
var packet = INetSerializableStruct.Read<PeerDisconnectPacket>(inc);
|
||||
DisconnectPeer(peer, packet);
|
||||
return;
|
||||
}
|
||||
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
|
||||
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = deliveryMethod,
|
||||
PacketHeader = packetHeader,
|
||||
Initialization = initialization
|
||||
});
|
||||
|
||||
if (packetHeader.IsConnectionInitializationStep())
|
||||
{
|
||||
var initRelayPacket = new SteamP2PInitializationRelayPacket
|
||||
{
|
||||
peer.DisconnectTime = Timing.TotalTime + 1.0;
|
||||
}
|
||||
LobbyID = SteamManager.CurrentLobbyID,
|
||||
Message = new PeerPacketMessage
|
||||
{
|
||||
Buffer = GetRemainingBytes(inc)
|
||||
}
|
||||
};
|
||||
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
outMsg.Write((byte)(PacketHeader.IsServerMessage | PacketHeader.IsDisconnectMessage));
|
||||
outMsg.Write(msg);
|
||||
|
||||
Steamworks.SteamNetworking.SendP2PPacket(peer.SteamID, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
|
||||
sentBytes += outMsg.LengthBytes;
|
||||
outMsg.WriteNetSerializableStruct(initRelayPacket);
|
||||
}
|
||||
else
|
||||
{
|
||||
ClosePeerSession(peer);
|
||||
byte[] userMessage = GetRemainingBytes(inc);
|
||||
outMsg.WriteBytes(userMessage, 0, userMessage.Length);
|
||||
}
|
||||
|
||||
ForwardToRemotePeer(deliveryMethod, recipientSteamId, outMsg);
|
||||
}
|
||||
|
||||
private void HandleMessageForOwner(PeerPacketHeaders peerPacketHeaders, IReadMessage inc)
|
||||
{
|
||||
var (_, packetHeader, _) = peerPacketHeaders;
|
||||
|
||||
if (packetHeader.IsDisconnectMessage())
|
||||
{
|
||||
DebugConsole.ThrowError("Received disconnect message from owned server");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!packetHeader.IsServerMessage())
|
||||
{
|
||||
DebugConsole.ThrowError("Received non-server message from owned server");
|
||||
return;
|
||||
}
|
||||
|
||||
if (packetHeader.IsHeartbeatMessage())
|
||||
{
|
||||
return; //no timeout since we're using pipes, ignore this message
|
||||
}
|
||||
|
||||
if (packetHeader.IsConnectionInitializationStep())
|
||||
{
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
WriteSteamId(outMsg, selfSteamID);
|
||||
WriteSteamId(outMsg, selfSteamID);
|
||||
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsConnectionInitializationStep,
|
||||
Initialization = ConnectionInitialization.SteamTicketAndVersion
|
||||
});
|
||||
outMsg.WriteNetSerializableStruct(new SteamP2PInitializationOwnerPacket
|
||||
{
|
||||
OwnerName = GameMain.Client.Name
|
||||
});
|
||||
ForwardToServerProcess(outMsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (initializationStep != ConnectionInitialization.Success)
|
||||
{
|
||||
callbacks.OnInitializationComplete.Invoke();
|
||||
initializationStep = ConnectionInitialization.Success;
|
||||
}
|
||||
|
||||
PeerPacketMessage packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
|
||||
IReadMessage msg = new ReadOnlyMessage(packet.Buffer, packetHeader.IsCompressed(), 0, packet.Length, ServerConnection);
|
||||
callbacks.OnMessageReceived.Invoke(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private void DisconnectPeer(RemotePeer peer, PeerDisconnectPacket peerDisconnectPacket)
|
||||
{
|
||||
peer.DisconnectTime ??= Timing.TotalTime + 1.0;
|
||||
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsServerMessage | PacketHeader.IsDisconnectMessage
|
||||
});
|
||||
outMsg.WriteNetSerializableStruct(peerDisconnectPacket);
|
||||
|
||||
Steamworks.SteamNetworking.SendP2PPacket(peer.SteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
|
||||
sentBytes += outMsg.LengthBytes;
|
||||
}
|
||||
|
||||
private void ClosePeerSession(RemotePeer peer)
|
||||
{
|
||||
Steamworks.SteamNetworking.CloseP2PSessionWithUser(peer.SteamID);
|
||||
Steamworks.SteamNetworking.CloseP2PSessionWithUser(peer.SteamId.Value);
|
||||
remotePeers.Remove(peer);
|
||||
}
|
||||
|
||||
public override void SendPassword(string password)
|
||||
{
|
||||
return; //owner doesn't send passwords
|
||||
//owner doesn't send passwords
|
||||
}
|
||||
|
||||
public override void Close(string msg = null, bool disableReconnect = false)
|
||||
public override void Close(PeerDisconnectPacket peerDisconnectPacket)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
@@ -418,7 +391,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
for (int i = remotePeers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
DisconnectPeer(remotePeers[i], msg ?? DisconnectReason.ServerShutdown.ToString());
|
||||
DisconnectPeer(remotePeers[i], PeerDisconnectPacket.WithReason(DisconnectReason.ServerShutdown));
|
||||
}
|
||||
|
||||
Thread.Sleep(100);
|
||||
@@ -430,7 +403,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
ChildServerRelay.ClosePipes();
|
||||
|
||||
OnDisconnect?.Invoke(disableReconnect);
|
||||
callbacks.OnDisconnect.Invoke(peerDisconnectPacket);
|
||||
|
||||
SteamManager.LeaveLobby();
|
||||
Steamworks.SteamNetworking.ResetActions();
|
||||
@@ -442,25 +415,62 @@ namespace Barotrauma.Networking
|
||||
if (!isActive) { return; }
|
||||
|
||||
IWriteMessage msgToSend = new WriteOnlyMessage();
|
||||
byte[] msgData = new byte[msg.LengthBytes];
|
||||
msg.PrepareForSending(ref msgData, compressPastThreshold, out bool isCompressed, out int length);
|
||||
byte[] msgData = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _);
|
||||
WriteSteamId(msgToSend, selfSteamID);
|
||||
WriteSteamId(msgToSend, selfSteamID);
|
||||
msgToSend.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None));
|
||||
msgToSend.Write((UInt16)length);
|
||||
msgToSend.Write(msgData, 0, length);
|
||||
|
||||
byte[] bufToSend = (byte[])msgToSend.Buffer.Clone();
|
||||
Array.Resize(ref bufToSend, msgToSend.LengthBytes);
|
||||
ChildServerRelay.Write(bufToSend);
|
||||
msgToSend.WriteNetSerializableStruct(new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = deliveryMethod,
|
||||
PacketHeader = isCompressed ? PacketHeader.IsCompressed : PacketHeader.None
|
||||
});
|
||||
msgToSend.WriteNetSerializableStruct(new PeerPacketMessage
|
||||
{
|
||||
Buffer = msgData
|
||||
});
|
||||
ForwardToServerProcess(msgToSend);
|
||||
}
|
||||
|
||||
protected override void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg)
|
||||
protected override void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body)
|
||||
{
|
||||
//not currently used by SteamP2POwnerPeer
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private static void ForwardToServerProcess(IWriteMessage msg)
|
||||
{
|
||||
byte[] bufToSend = new byte[msg.LengthBytes];
|
||||
msg.Buffer[..msg.LengthBytes].CopyTo(bufToSend.AsSpan());
|
||||
ChildServerRelay.Write(bufToSend);
|
||||
}
|
||||
|
||||
private void ForwardToRemotePeer(DeliveryMethod deliveryMethod, SteamId recipent, IWriteMessage outMsg)
|
||||
{
|
||||
byte[] buf = outMsg.PrepareForSending(compressPastThreshold: false, out _, out int length);
|
||||
|
||||
if (length + 4 >= MsgConstants.MTU)
|
||||
{
|
||||
DebugConsole.Log($"WARNING: message length comes close to exceeding MTU, forcing reliable send ({length} bytes)");
|
||||
deliveryMethod = DeliveryMethod.Reliable;
|
||||
}
|
||||
|
||||
bool successSend = Steamworks.SteamNetworking.SendP2PPacket(recipent.Value, buf, length, 0, deliveryMethod.ToSteam());
|
||||
sentBytes += length;
|
||||
|
||||
if (successSend) { return; }
|
||||
|
||||
if (deliveryMethod is DeliveryMethod.Unreliable)
|
||||
{
|
||||
DebugConsole.Log($"WARNING: message couldn't be sent unreliably, forcing reliable send ({length} bytes)");
|
||||
successSend = Steamworks.SteamNetworking.SendP2PPacket(recipent.Value, buf, length, 0, DeliveryMethod.Reliable.ToSteam());
|
||||
sentBytes += length;
|
||||
}
|
||||
|
||||
if (!successSend)
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to send message to remote peer! ({length} bytes)");
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public override void ForceTimeOut()
|
||||
{
|
||||
@@ -468,4 +478,4 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,14 +44,19 @@ namespace Barotrauma.Networking
|
||||
if (!UseRespawnPrompt) { return; }
|
||||
if (CoroutineManager.IsCoroutineRunning(respawnPromptCoroutine) || GUIMessageBox.MessageBoxes.Any(mb => mb.UserData as string == "respawnquestionprompt"))
|
||||
{
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
respawnPromptCoroutine = CoroutineManager.Invoke(() =>
|
||||
{
|
||||
if (Character.Controlled != null || (!(GameMain.GameSession?.IsRunning ?? false))) { return; }
|
||||
|
||||
LocalizedString text =
|
||||
TextManager.GetWithVariable("respawnskillpenalty", "[percentage]", ((int)(SkillReductionOnDeath * 100)).ToString())
|
||||
+ "\n\n" + TextManager.Get("respawnquestionprompt");
|
||||
|
||||
var respawnPrompt = new GUIMessageBox(
|
||||
TextManager.Get("tutorial.tryagainheader"), TextManager.Get("respawnquestionprompt"),
|
||||
TextManager.Get("tutorial.tryagainheader"), text,
|
||||
new LocalizedString[] { TextManager.Get("respawnquestionpromptrespawn"), TextManager.Get("respawnquestionpromptwait") })
|
||||
{
|
||||
UserData = "respawnquestionprompt"
|
||||
|
||||
@@ -1,553 +0,0 @@
|
||||
using Barotrauma.Steam;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class ServerInfo
|
||||
{
|
||||
public string IP;
|
||||
public string Port;
|
||||
public string QueryPort;
|
||||
|
||||
public Steamworks.Data.NetPingLocation? PingLocation;
|
||||
public UInt64 LobbyID;
|
||||
public UInt64 OwnerID;
|
||||
public bool OwnerVerified;
|
||||
|
||||
private string serverName;
|
||||
public string ServerName
|
||||
{
|
||||
get { return serverName; }
|
||||
set
|
||||
{
|
||||
serverName = value;
|
||||
if (serverName.Length > NetConfig.ServerNameMaxLength) { ServerName = ServerName.Substring(0, NetConfig.ServerNameMaxLength); }
|
||||
}
|
||||
}
|
||||
|
||||
public string ServerMessage;
|
||||
public bool GameStarted;
|
||||
public int PlayerCount;
|
||||
public int MaxPlayers;
|
||||
public bool HasPassword;
|
||||
|
||||
public bool PingChecked;
|
||||
public int Ping = -1;
|
||||
|
||||
//null value means that the value isn't known (the server may be using
|
||||
//an old version of the game that didn't report these values or the FetchRules query to Steam may not have finished yet)
|
||||
public bool? UsingWhiteList;
|
||||
public SelectionMode? ModeSelectionMode;
|
||||
public SelectionMode? SubSelectionMode;
|
||||
public bool? AllowSpectating;
|
||||
public bool? VoipEnabled;
|
||||
public bool? KarmaEnabled;
|
||||
public bool? FriendlyFireEnabled;
|
||||
public bool? AllowRespawn;
|
||||
public YesNoMaybe? TraitorsEnabled;
|
||||
public Identifier GameMode;
|
||||
public PlayStyle? PlayStyle;
|
||||
|
||||
public bool Recent;
|
||||
public bool Favorite;
|
||||
|
||||
public bool? RespondedToSteamQuery = null;
|
||||
|
||||
public Steamworks.Friend? SteamFriend;
|
||||
public Steamworks.SteamMatchmakingPingResponse MatchmakingPingResponse;
|
||||
|
||||
public string GameVersion;
|
||||
public List<string> ContentPackageNames
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new List<string>();
|
||||
public List<string> ContentPackageHashes
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new List<string>();
|
||||
public List<ulong> ContentPackageWorkshopIds
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new List<ulong>();
|
||||
|
||||
public void CreatePreviewWindow(GUIFrame frame)
|
||||
{
|
||||
if (frame == null) { return; }
|
||||
|
||||
frame.ClearChildren();
|
||||
|
||||
var title = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform), ServerName, font: GUIStyle.LargeFont)
|
||||
{
|
||||
ToolTip = ServerName,
|
||||
CanBeFocused = false
|
||||
};
|
||||
title.Text = ToolBox.LimitString(title.Text, title.Font, (int)(title.Rect.Width * 0.85f));
|
||||
|
||||
GUITickBox favoriteTickBox = new GUITickBox(new RectTransform(new Vector2(0.15f, 0.8f), title.RectTransform, Anchor.CenterRight),
|
||||
"", null, "GUIServerListFavoriteTickBox")
|
||||
{
|
||||
Selected = Favorite,
|
||||
ToolTip = TextManager.Get(Favorite ? "removefromfavorites" : "addtofavorites"),
|
||||
OnSelected = (tickbox) =>
|
||||
{
|
||||
if (tickbox.Selected)
|
||||
{
|
||||
GameMain.ServerListScreen.AddToFavoriteServers(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.ServerListScreen.RemoveFromFavoriteServers(this);
|
||||
}
|
||||
tickbox.ToolTip = TextManager.Get(tickbox.Selected ? "removefromfavorites" : "addtofavorites");
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform),
|
||||
TextManager.AddPunctuation(':', TextManager.Get("ServerListVersion"),
|
||||
string.IsNullOrEmpty(GameVersion) ? TextManager.Get("Unknown") : GameVersion))
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
|
||||
bool hidePlaystyleBanner = !PlayStyle.HasValue;
|
||||
if (!hidePlaystyleBanner)
|
||||
{
|
||||
PlayStyle playStyle = PlayStyle ?? Networking.PlayStyle.Serious;
|
||||
Sprite playStyleBannerSprite = ServerListScreen.PlayStyleBanners[(int)playStyle];
|
||||
float playStyleBannerAspectRatio = playStyleBannerSprite.SourceRect.Width / playStyleBannerSprite.SourceRect.Height;
|
||||
var playStyleBanner = new GUIImage(new RectTransform(new Point(frame.Rect.Width, (int)(frame.Rect.Width / playStyleBannerAspectRatio)), frame.RectTransform),
|
||||
playStyleBannerSprite, null, true);
|
||||
|
||||
var playStyleName = new GUITextBlock(
|
||||
new RectTransform(new Vector2(0.15f, 0.0f), playStyleBanner.RectTransform)
|
||||
{ RelativeOffset = new Vector2(0.0f, 0.06f) },
|
||||
TextManager.AddPunctuation(':', TextManager.Get("serverplaystyle"),
|
||||
TextManager.Get("servertag." + playStyle)), textColor: Color.White,
|
||||
font: GUIStyle.SmallFont, textAlignment: Alignment.Center,
|
||||
color: ServerListScreen.PlayStyleColors[(int)playStyle], style: "GUISlopedHeader");
|
||||
playStyleName.RectTransform.NonScaledSize = (playStyleName.Font.MeasureString(playStyleName.Text) + new Vector2(20, 5) * GUI.Scale).ToPoint();
|
||||
playStyleName.RectTransform.IsFixedSize = true;
|
||||
}
|
||||
|
||||
var serverType = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform),
|
||||
TextManager.Get((OwnerID != 0 || LobbyID != 0) ? "SteamP2PServer" : "DedicatedServer"),
|
||||
textAlignment: Alignment.TopLeft)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
serverType.RectTransform.MinSize = new Point(0, (int)(serverType.Rect.Height * 1.5f));
|
||||
|
||||
var content = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.6f), frame.RectTransform))
|
||||
{
|
||||
Stretch = true
|
||||
};
|
||||
// playstyle tags -----------------------------------------------------------------------------
|
||||
|
||||
var playStyleContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), content.RectTransform), isHorizontal: true)
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.01f,
|
||||
CanBeFocused = true
|
||||
};
|
||||
|
||||
var playStyleTags = GetPlayStyleTags();
|
||||
foreach (string tag in playStyleTags)
|
||||
{
|
||||
if (!ServerListScreen.PlayStyleIcons.ContainsKey(tag)) { continue; }
|
||||
|
||||
new GUIImage(new RectTransform(Vector2.One, playStyleContainer.RectTransform),
|
||||
ServerListScreen.PlayStyleIcons[tag], scaleToFit: true)
|
||||
{
|
||||
ToolTip = TextManager.Get("servertagdescription." + tag),
|
||||
Color = ServerListScreen.PlayStyleIconColors[tag]
|
||||
};
|
||||
}
|
||||
|
||||
playStyleContainer.Recalculate();
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
float elementHeight = 0.075f;
|
||||
|
||||
// Spacing
|
||||
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.025f), content.RectTransform), style: null);
|
||||
|
||||
var serverMsg = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.3f), content.RectTransform)) { ScrollBarVisible = true };
|
||||
var msgText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), serverMsg.Content.RectTransform), ServerMessage, font: GUIStyle.SmallFont, wrap: true)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
serverMsg.Content.RectTransform.SizeChanged += () => { msgText.CalculateHeightFromText(); };
|
||||
msgText.RectTransform.SizeChanged += () => { serverMsg.UpdateScrollBarSize(); };
|
||||
|
||||
var gameMode = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("GameMode"));
|
||||
new GUITextBlock(new RectTransform(Vector2.One, gameMode.RectTransform),
|
||||
TextManager.Get(GameMode.IsEmpty ? "Unknown" : "GameMode." + GameMode).Fallback(GameMode.Value),
|
||||
textAlignment: Alignment.Right);
|
||||
|
||||
GUITextBlock playStyleText = null;
|
||||
if (hidePlaystyleBanner && PlayStyle.HasValue)
|
||||
{
|
||||
PlayStyle playStyle = PlayStyle.Value;
|
||||
playStyleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("serverplaystyle"));
|
||||
new GUITextBlock(new RectTransform(Vector2.One, playStyleText.RectTransform), TextManager.Get("servertag." + playStyle), textAlignment: Alignment.Right);
|
||||
}
|
||||
|
||||
var subSelection = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("ServerListSubSelection"));
|
||||
new GUITextBlock(new RectTransform(Vector2.One, subSelection.RectTransform), TextManager.Get(!SubSelectionMode.HasValue ? "Unknown" : SubSelectionMode.Value.ToString()), textAlignment: Alignment.Right);
|
||||
|
||||
var modeSelection = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("ServerListModeSelection"));
|
||||
new GUITextBlock(new RectTransform(Vector2.One, modeSelection.RectTransform), TextManager.Get(!ModeSelectionMode.HasValue ? "Unknown" : ModeSelectionMode.Value.ToString()), textAlignment: Alignment.Right);
|
||||
|
||||
if (gameMode.TextSize.X + gameMode.GetChild<GUITextBlock>().TextSize.X > gameMode.Rect.Width ||
|
||||
subSelection.TextSize.X + subSelection.GetChild<GUITextBlock>().TextSize.X > subSelection.Rect.Width ||
|
||||
modeSelection.TextSize.X + modeSelection.GetChild<GUITextBlock>().TextSize.X > modeSelection.Rect.Width)
|
||||
{
|
||||
gameMode.Font = subSelection.Font = modeSelection.Font = GUIStyle.SmallFont;
|
||||
gameMode.GetChild<GUITextBlock>().Font = subSelection.GetChild<GUITextBlock>().Font = modeSelection.GetChild<GUITextBlock>().Font = GUIStyle.SmallFont;
|
||||
if (playStyleText != null)
|
||||
{
|
||||
playStyleText.Font = playStyleText.GetChild<GUITextBlock>().Font = GUIStyle.SmallFont;
|
||||
}
|
||||
}
|
||||
|
||||
var allowSpectating = new GUITickBox(new RectTransform(new Vector2(1, elementHeight), content.RectTransform), TextManager.Get("ServerListAllowSpectating"))
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
if (!AllowSpectating.HasValue)
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.8f), allowSpectating.Box.RectTransform, Anchor.Center), "?", textAlignment: Alignment.Center);
|
||||
else
|
||||
allowSpectating.Selected = AllowSpectating.Value;
|
||||
|
||||
var allowRespawn = new GUITickBox(new RectTransform(new Vector2(1, elementHeight), content.RectTransform), TextManager.Get("ServerSettingsAllowRespawning"))
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
if (!AllowRespawn.HasValue)
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.8f), allowRespawn.Box.RectTransform, Anchor.Center), "?", textAlignment: Alignment.Center);
|
||||
else
|
||||
allowRespawn.Selected = AllowRespawn.Value;
|
||||
|
||||
/*var voipEnabledTickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), bodyContainer.RectTransform), TextManager.Get("serversettingsvoicechatenabled"))
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
if (!VoipEnabled.HasValue)
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.8f), voipEnabledTickBox.Box.RectTransform, Anchor.Center), "?", textAlignment: Alignment.Center);
|
||||
else
|
||||
voipEnabledTickBox.Selected = VoipEnabled.Value;*/
|
||||
|
||||
var usingWhiteList = new GUITickBox(new RectTransform(new Vector2(1, elementHeight), content.RectTransform), TextManager.Get("ServerListUsingWhitelist"))
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
if (!UsingWhiteList.HasValue)
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.8f), usingWhiteList.Box.RectTransform, Anchor.Center), "?", textAlignment: Alignment.Center);
|
||||
else
|
||||
usingWhiteList.Selected = UsingWhiteList.Value;
|
||||
|
||||
content.RectTransform.SizeChanged += () =>
|
||||
{
|
||||
GUITextBlock.AutoScaleAndNormalize(allowSpectating.TextBlock, allowRespawn.TextBlock, usingWhiteList.TextBlock);
|
||||
};
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
|
||||
TextManager.Get("ServerListContentPackages"), textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont);
|
||||
|
||||
var contentPackageList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.3f), frame.RectTransform))
|
||||
{
|
||||
ScrollBarVisible = true,
|
||||
OnSelected = (component, o) => false
|
||||
};
|
||||
if (ContentPackageNames.Count == 0)
|
||||
{
|
||||
new GUITextBlock(new RectTransform(Vector2.One, contentPackageList.Content.RectTransform), TextManager.Get("Unknown"), textAlignment: Alignment.Center)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < ContentPackageNames.Count; i++)
|
||||
{
|
||||
var packageText = new GUITickBox(
|
||||
new RectTransform(new Vector2(1.0f, 0.15f), contentPackageList.Content.RectTransform)
|
||||
{ MinSize = new Point(0, 15) },
|
||||
ContentPackageNames[i])
|
||||
{
|
||||
Enabled = false
|
||||
};
|
||||
packageText.Box.Enabled = true;
|
||||
packageText.TextBlock.Enabled = true;
|
||||
if (i < ContentPackageHashes.Count)
|
||||
{
|
||||
if (ContentPackageManager.AllPackages.Any(contentPackage => contentPackage.Hash.StringRepresentation == ContentPackageHashes[i]))
|
||||
{
|
||||
packageText.TextColor = GUIStyle.Green;
|
||||
packageText.Selected = true;
|
||||
}
|
||||
//workshop download link found
|
||||
else if (i < ContentPackageWorkshopIds.Count && ContentPackageWorkshopIds[i] != 0)
|
||||
{
|
||||
packageText.ToolTip = TextManager.GetWithVariable("ServerListIncompatibleContentPackageWorkshopAvailable", "[contentpackage]", ContentPackageNames[i]);
|
||||
}
|
||||
else //no package or workshop download link found (TODO: update text to say that they could be downloaded through the server)
|
||||
{
|
||||
packageText.TextColor = GameMain.VanillaContent.NameMatches(ContentPackageNames[i]) ? GUIStyle.Red : GUIStyle.Yellow;
|
||||
packageText.ToolTip = TextManager.GetWithVariables("ServerListIncompatibleContentPackage",
|
||||
("[contentpackage]", ContentPackageNames[i]), ("[hash]", ContentPackageHashes[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
foreach (GUIComponent c in content.Children)
|
||||
{
|
||||
if (c is GUITextBlock textBlock) { textBlock.Padding = Vector4.Zero; }
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetPlayStyleTags()
|
||||
{
|
||||
List<string> tags = new List<string>();
|
||||
if (KarmaEnabled.HasValue)
|
||||
{
|
||||
tags.Add(KarmaEnabled.Value ? "karma.true" : "karma.false");
|
||||
}
|
||||
if (TraitorsEnabled.HasValue)
|
||||
{
|
||||
tags.Add(TraitorsEnabled.Value == YesNoMaybe.Maybe ?
|
||||
"traitors.maybe" :
|
||||
(TraitorsEnabled.Value == YesNoMaybe.Yes ? "traitors.true" : "traitors.false"));
|
||||
}
|
||||
if (VoipEnabled.HasValue)
|
||||
{
|
||||
tags.Add(VoipEnabled.Value ? "voip.true" : "voip.false");
|
||||
}
|
||||
if (FriendlyFireEnabled.HasValue)
|
||||
{
|
||||
tags.Add(FriendlyFireEnabled.Value ? "friendlyfire.true" : "friendlyfire.false");
|
||||
}
|
||||
if (ContentPackageNames.Count > 0)
|
||||
{
|
||||
tags.Add(ContentPackageNames.Count > 1 || !GameMain.VanillaContent.NameMatches(ContentPackageNames[0]) ? "modded.true" : "modded.false");
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
public static ServerInfo FromXElement(XElement element)
|
||||
{
|
||||
ServerInfo info = new ServerInfo()
|
||||
{
|
||||
ServerName = element.GetAttributeString("ServerName", ""),
|
||||
ServerMessage = element.GetAttributeString("ServerMessage", ""),
|
||||
IP = element.GetAttributeString("IP", ""),
|
||||
Port = element.GetAttributeString("Port", ""),
|
||||
QueryPort = element.GetAttributeString("QueryPort", ""),
|
||||
OwnerID = element.GetAttributeSteamID("OwnerID",0)
|
||||
};
|
||||
|
||||
info.RespondedToSteamQuery = null;
|
||||
|
||||
info.GameMode = element.GetAttributeIdentifier("GameMode", Identifier.Empty);
|
||||
info.GameVersion = element.GetAttributeString("GameVersion", "");
|
||||
|
||||
int maxPlayersElement = element.GetAttributeInt("MaxPlayers", 0);
|
||||
|
||||
if (maxPlayersElement > NetConfig.MaxPlayers)
|
||||
{
|
||||
/*DebugConsole.IsOpen = true;
|
||||
DebugConsole.NewMessage($"Setting the maximum amount of players to {maxPlayersElement} failed due to exceeding the limit of {NetConfig.MaxPlayers} players per server. Using the maximum of {NetConfig.MaxPlayers} instead.", Color.Red);*/
|
||||
maxPlayersElement = NetConfig.MaxPlayers;
|
||||
}
|
||||
|
||||
info.MaxPlayers = maxPlayersElement;
|
||||
|
||||
if (Enum.TryParse(element.GetAttributeString("PlayStyle", ""), out PlayStyle playStyleTemp)) { info.PlayStyle = playStyleTemp; }
|
||||
if (bool.TryParse(element.GetAttributeString("UsingWhiteList", ""), out bool whitelistTemp)) { info.UsingWhiteList = whitelistTemp; }
|
||||
if (Enum.TryParse(element.GetAttributeString("TraitorsEnabled", ""), out YesNoMaybe traitorsTemp)) { info.TraitorsEnabled = traitorsTemp; }
|
||||
if (Enum.TryParse(element.GetAttributeString("SubSelectionMode", ""), out SelectionMode subSelectionTemp)) { info.SubSelectionMode = subSelectionTemp; }
|
||||
if (Enum.TryParse(element.GetAttributeString("ModeSelectionMode", ""), out SelectionMode modeSelectionTemp)) { info.ModeSelectionMode = modeSelectionTemp; }
|
||||
if (bool.TryParse(element.GetAttributeString("VoipEnabled", ""), out bool voipTemp)) { info.VoipEnabled = voipTemp; }
|
||||
if (bool.TryParse(element.GetAttributeString("KarmaEnabled", ""), out bool karmaTemp)) { info.KarmaEnabled = karmaTemp; }
|
||||
if (bool.TryParse(element.GetAttributeString("FriendlyFireEnabled", ""), out bool friendlyFireTemp)) { info.FriendlyFireEnabled = friendlyFireTemp; }
|
||||
|
||||
info.HasPassword = element.GetAttributeBool("HasPassword", false);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
public void QueryLiveInfo(Action<Networking.ServerInfo> onServerRulesReceived, Action<Networking.ServerInfo> onQueryDone)
|
||||
{
|
||||
if (!SteamManager.IsInitialized) { return; }
|
||||
|
||||
if (int.TryParse(QueryPort, out int parsedPort) && IPAddress.TryParse(IP, out IPAddress parsedIP))
|
||||
{
|
||||
if (MatchmakingPingResponse?.QueryActive ?? false)
|
||||
{
|
||||
MatchmakingPingResponse.Cancel();
|
||||
}
|
||||
|
||||
MatchmakingPingResponse = new Steamworks.SteamMatchmakingPingResponse(
|
||||
(server) =>
|
||||
{
|
||||
ServerName = server.Name;
|
||||
RespondedToSteamQuery = true;
|
||||
PlayerCount = server.Players;
|
||||
MaxPlayers = server.MaxPlayers;
|
||||
HasPassword = server.Passworded;
|
||||
PingChecked = true;
|
||||
Ping = server.Ping;
|
||||
LobbyID = 0;
|
||||
TaskPool.Add("QueryServerRules (QueryLiveInfo)", server.QueryRulesAsync(),
|
||||
(t) =>
|
||||
{
|
||||
onQueryDone(this);
|
||||
if (t.Status == TaskStatus.Faulted)
|
||||
{
|
||||
TaskPool.PrintTaskExceptions(t, "Failed to retrieve rules for " + ServerName);
|
||||
return;
|
||||
}
|
||||
|
||||
t.TryGetResult(out Dictionary<string, string> rules);
|
||||
SteamManager.AssignServerRulesToServerInfo(rules, this);
|
||||
|
||||
onServerRulesReceived(this);
|
||||
});
|
||||
},
|
||||
() =>
|
||||
{
|
||||
RespondedToSteamQuery = false;
|
||||
});
|
||||
|
||||
MatchmakingPingResponse.HQueryPing(parsedIP, parsedPort);
|
||||
}
|
||||
else if (OwnerID != 0)
|
||||
{
|
||||
if (SteamFriend == null)
|
||||
{
|
||||
SteamFriend = new Steamworks.Friend(OwnerID);
|
||||
}
|
||||
if (LobbyID == 0)
|
||||
{
|
||||
TaskPool.Add("RequestSteamP2POwnerInfo", SteamFriend?.RequestInfoAsync(),
|
||||
(t) =>
|
||||
{
|
||||
onQueryDone(this);
|
||||
if ((SteamFriend?.IsPlayingThisGame ?? false) && ((SteamFriend?.GameInfo?.Lobby?.Id ?? 0) != 0))
|
||||
{
|
||||
LobbyID = SteamFriend?.GameInfo?.Lobby?.Id.Value ?? 0;
|
||||
Steamworks.SteamMatchmaking.OnLobbyDataChanged += UpdateInfoFromSteamworksLobby;
|
||||
SteamFriend?.GameInfo?.Lobby?.Refresh();
|
||||
}
|
||||
else
|
||||
{
|
||||
RespondedToSteamQuery = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
onQueryDone(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateInfoFromSteamworksLobby(Steamworks.Data.Lobby lobby)
|
||||
{
|
||||
if (lobby.Id != LobbyID) { return; }
|
||||
Steamworks.SteamMatchmaking.OnLobbyDataChanged -= UpdateInfoFromSteamworksLobby;
|
||||
if (string.IsNullOrWhiteSpace(lobby.GetData("haspassword"))) { return; }
|
||||
bool.TryParse(lobby.GetData("haspassword"), out bool hasPassword);
|
||||
int.TryParse(lobby.GetData("playercount"), out int currPlayers);
|
||||
int.TryParse(lobby.GetData("maxplayernum"), out int maxPlayers);
|
||||
UInt64 ownerId = SteamManager.SteamIDStringToUInt64(lobby.GetData("lobbyowner"));
|
||||
|
||||
if (OwnerID != ownerId) { return; }
|
||||
|
||||
ServerName = lobby.GetData("name");
|
||||
IP = "";
|
||||
Port = "";
|
||||
QueryPort = "";
|
||||
PlayerCount = currPlayers;
|
||||
MaxPlayers = maxPlayers;
|
||||
HasPassword = hasPassword;
|
||||
RespondedToSteamQuery = true;
|
||||
LobbyID = lobby.Id;
|
||||
OwnerID = ownerId;
|
||||
PingChecked = false;
|
||||
OwnerVerified = true;
|
||||
|
||||
SteamManager.AssignLobbyDataToServerInfo(lobby, this);
|
||||
}
|
||||
|
||||
public XElement ToXElement()
|
||||
{
|
||||
if (OwnerID == 0 && string.IsNullOrEmpty(Port))
|
||||
{
|
||||
return null; //can't save this one since it's not set up correctly
|
||||
}
|
||||
|
||||
XElement element = new XElement("ServerInfo");
|
||||
|
||||
element.SetAttributeValue("ServerName", ServerName);
|
||||
element.SetAttributeValue("ServerMessage", ServerMessage);
|
||||
if (OwnerID == 0)
|
||||
{
|
||||
element.SetAttributeValue("IP", IP);
|
||||
element.SetAttributeValue("Port", Port);
|
||||
element.SetAttributeValue("QueryPort", QueryPort);
|
||||
}
|
||||
else
|
||||
{
|
||||
element.SetAttributeValue("OwnerID", SteamManager.SteamIDUInt64ToString(OwnerID));
|
||||
}
|
||||
|
||||
element.SetAttributeValue("GameMode", GameMode);
|
||||
element.SetAttributeValue("GameVersion", GameVersion ?? "");
|
||||
element.SetAttributeValue("MaxPlayers", MaxPlayers);
|
||||
if (PlayStyle.HasValue) { element.SetAttributeValue("PlayStyle", PlayStyle.Value.ToString()); }
|
||||
if (UsingWhiteList.HasValue) { element.SetAttributeValue("UsingWhiteList", UsingWhiteList.Value.ToString()); }
|
||||
if (TraitorsEnabled.HasValue) { element.SetAttributeValue("TraitorsEnabled", TraitorsEnabled.Value.ToString()); }
|
||||
if (SubSelectionMode.HasValue) { element.SetAttributeValue("SubSelectionMode", SubSelectionMode.Value.ToString()); }
|
||||
if (ModeSelectionMode.HasValue) { element.SetAttributeValue("ModeSelectionMode", ModeSelectionMode.Value.ToString()); }
|
||||
if (VoipEnabled.HasValue) { element.SetAttributeValue("VoipEnabled", VoipEnabled.Value.ToString()); }
|
||||
if (KarmaEnabled.HasValue) { element.SetAttributeValue("KarmaEnabled", KarmaEnabled.Value.ToString()); }
|
||||
if (FriendlyFireEnabled.HasValue) { element.SetAttributeValue("FriendlyFireEnabled", FriendlyFireEnabled.Value.ToString()); }
|
||||
element.SetAttributeValue("HasPassword", HasPassword.ToString());
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is ServerInfo other ? Equals(other) : base.Equals(obj);
|
||||
}
|
||||
|
||||
public bool Equals(ServerInfo other)
|
||||
{
|
||||
return
|
||||
other.OwnerID == OwnerID &&
|
||||
(other.LobbyID == LobbyID || other.LobbyID == 0 || LobbyID == 0) &&
|
||||
((OwnerID == 0) ? (other.IP == IP && other.Port == Port) : true);
|
||||
}
|
||||
|
||||
public bool MatchesByEndpoint(ServerInfo other)
|
||||
{
|
||||
return OwnerID == other.OwnerID && (OwnerID != 0 ? true : (IP == other.IP && Port == other.Port));
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
abstract class FriendProvider
|
||||
{
|
||||
public abstract ServerListScreen.FriendInfo[] RetrieveFriends();
|
||||
public abstract void RetrieveAvatar(ServerListScreen.FriendInfo friend, ServerListScreen.AvatarSize avatarSize);
|
||||
public abstract string GetUserName();
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Steam;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class SteamFriendProvider : FriendProvider
|
||||
{
|
||||
private static ServerListScreen.FriendInfo FromSteamFriend(Steamworks.Friend steamFriend)
|
||||
=> new ServerListScreen.FriendInfo(
|
||||
steamFriend.Name,
|
||||
new SteamId(steamFriend.Id),
|
||||
steamFriend.State switch
|
||||
{
|
||||
Steamworks.FriendState.Offline => ServerListScreen.FriendInfo.Status.Offline,
|
||||
Steamworks.FriendState.Invisible => ServerListScreen.FriendInfo.Status.Offline,
|
||||
_ when steamFriend.IsPlayingThisGame => ServerListScreen.FriendInfo.Status.PlayingBarotrauma,
|
||||
_ when steamFriend.GameInfo is { GameID: var gameId } && gameId > 0 => ServerListScreen.FriendInfo.Status.PlayingAnotherGame,
|
||||
_ => ServerListScreen.FriendInfo.Status.NotPlaying
|
||||
})
|
||||
{
|
||||
ServerName = steamFriend.GetRichPresence("servername"),
|
||||
ConnectCommand = steamFriend.GetRichPresence("connect") is { } connectCmd
|
||||
? ToolBox.ParseConnectCommand(ToolBox.SplitCommand(connectCmd))
|
||||
: Option<ConnectCommand>.None()
|
||||
};
|
||||
|
||||
public override ServerListScreen.FriendInfo[] RetrieveFriends()
|
||||
=> SteamManager.IsInitialized
|
||||
? Steamworks.SteamFriends.GetFriends().Select(FromSteamFriend).ToArray()
|
||||
: Array.Empty<ServerListScreen.FriendInfo>();
|
||||
|
||||
public override void RetrieveAvatar(ServerListScreen.FriendInfo friend, ServerListScreen.AvatarSize avatarSize)
|
||||
{
|
||||
if (!(friend.Id is SteamId steamId)) { return; }
|
||||
|
||||
Func<Steamworks.SteamId, Task<Steamworks.Data.Image?>> avatarFunc = avatarSize switch
|
||||
{
|
||||
ServerListScreen.AvatarSize.Small => Steamworks.SteamFriends.GetSmallAvatarAsync,
|
||||
ServerListScreen.AvatarSize.Medium => Steamworks.SteamFriends.GetMediumAvatarAsync,
|
||||
ServerListScreen.AvatarSize.Large => Steamworks.SteamFriends.GetLargeAvatarAsync,
|
||||
};
|
||||
TaskPool.Add($"Get{avatarSize}AvatarAsync", avatarFunc(steamId.Value), task =>
|
||||
{
|
||||
if (!task.TryGetResult(out Steamworks.Data.Image? img)) { return; }
|
||||
if (!(img is { } avatarImage)) { return; }
|
||||
|
||||
if (friend.Avatar.TryUnwrap(out var prevAvatar))
|
||||
{
|
||||
prevAvatar.Remove();
|
||||
}
|
||||
|
||||
#warning TODO: create an avatar atlas?
|
||||
var avatarTexture = new Texture2D(GameMain.Instance.GraphicsDevice, (int)avatarImage.Width, (int)avatarImage.Height);
|
||||
avatarTexture.SetData(avatarImage.Data);
|
||||
friend.Avatar = Option<Sprite>.Some(new Sprite(avatarTexture, null, null));
|
||||
});
|
||||
}
|
||||
|
||||
public override string GetUserName()
|
||||
=> SteamManager.GetUsername();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
using Barotrauma.Steam;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using Steamworks.Data;
|
||||
using Color = Microsoft.Xna.Framework.Color;
|
||||
using Socket = System.Net.Sockets.Socket;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
static class PingUtils
|
||||
{
|
||||
private static readonly Dictionary<IPAddress, int> activePings = new Dictionary<IPAddress, int>();
|
||||
|
||||
private static bool steamPingInfoReady;
|
||||
|
||||
public static void QueryPingData()
|
||||
{
|
||||
steamPingInfoReady = false;
|
||||
if (SteamManager.IsInitialized)
|
||||
{
|
||||
TaskPool.Add("WaitForPingDataAsync (serverlist)", Steamworks.SteamNetworkingUtils.WaitForPingDataAsync(), task =>
|
||||
{
|
||||
steamPingInfoReady = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetServerPing(ServerInfo serverInfo, Action<ServerInfo> onPingDiscovered)
|
||||
{
|
||||
if (CoroutineManager.IsCoroutineRunning("ConnectToServer")) { return; }
|
||||
|
||||
switch (serverInfo.Endpoint)
|
||||
{
|
||||
case LidgrenEndpoint { NetEndpoint: { Address: var address } }:
|
||||
|
||||
GetIPAddressPing(serverInfo, address, onPingDiscovered);
|
||||
break;
|
||||
case SteamP2PEndpoint steamP2PEndpoint:
|
||||
TaskPool.Add($"EstimateSteamLobbyPing ({steamP2PEndpoint.StringRepresentation})",
|
||||
EstimateSteamLobbyPing(serverInfo),
|
||||
t =>
|
||||
{
|
||||
if (!t.TryGetResult(out Option<int> ping)) { return; }
|
||||
serverInfo.Ping = ping;
|
||||
onPingDiscovered(serverInfo);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ref struct LobbyDataChangedEventHandler
|
||||
{
|
||||
private readonly Action<Lobby> action;
|
||||
|
||||
public LobbyDataChangedEventHandler(Action<Lobby> action)
|
||||
{
|
||||
this.action = action;
|
||||
Steamworks.SteamMatchmaking.OnLobbyDataChanged += action;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Steamworks.SteamMatchmaking.OnLobbyDataChanged -= action;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<Lobby?> GetSteamLobbyForUser(SteamId steamId)
|
||||
{
|
||||
var steamFriend = new Steamworks.Friend(steamId.Value);
|
||||
await steamFriend.RequestInfoAsync();
|
||||
|
||||
var friendLobby = steamFriend.GameInfo?.Lobby;
|
||||
if (!(friendLobby is { } lobby)) { return null; }
|
||||
|
||||
bool waiting = true;
|
||||
Lobby loadedLobby = default;
|
||||
|
||||
void finishWaiting(Steamworks.Data.Lobby l)
|
||||
{
|
||||
loadedLobby = l;
|
||||
waiting = false;
|
||||
}
|
||||
|
||||
using (new LobbyDataChangedEventHandler(finishWaiting))
|
||||
{
|
||||
lobby.Refresh();
|
||||
|
||||
for (int i = 0;; i++)
|
||||
{
|
||||
if (!waiting) { break; }
|
||||
if (i >= 100) { return null; }
|
||||
}
|
||||
}
|
||||
|
||||
return loadedLobby;
|
||||
}
|
||||
|
||||
private static async Task<Option<int>> EstimateSteamLobbyPing(ServerInfo serverInfo)
|
||||
{
|
||||
if (!(serverInfo.Endpoint is SteamP2PEndpoint { SteamId: var ownerId })) { return Option<int>.None(); }
|
||||
while (!steamPingInfoReady) { await Task.Delay(50); }
|
||||
|
||||
Lobby lobby;
|
||||
|
||||
if (serverInfo.MetadataSource.TryUnwrap(out SteamP2PServerProvider.DataSource src))
|
||||
{
|
||||
lobby = src.Lobby;
|
||||
}
|
||||
else
|
||||
{
|
||||
var friendLobby = await GetSteamLobbyForUser(ownerId);
|
||||
if (friendLobby is null) { return Option<int>.None(); }
|
||||
lobby = friendLobby.Value;
|
||||
}
|
||||
|
||||
var pingLocation = NetPingLocation.TryParseFromString(lobby.GetData("pinglocation"));
|
||||
|
||||
if (pingLocation.HasValue && Steamworks.SteamNetworkingUtils.LocalPingLocation.HasValue)
|
||||
{
|
||||
int ping = Steamworks.SteamNetworkingUtils.LocalPingLocation.Value.EstimatePingTo(pingLocation.Value);
|
||||
return ping >= 0 ? Option<int>.Some(ping) : Option<int>.None();
|
||||
}
|
||||
else
|
||||
{
|
||||
return Option<int>.None();
|
||||
}
|
||||
}
|
||||
|
||||
private static void GetIPAddressPing(ServerInfo serverInfo, IPAddress address, Action<ServerInfo> onPingDiscovered)
|
||||
{
|
||||
if (IPAddress.IsLoopback(address))
|
||||
{
|
||||
serverInfo.Ping = Option<int>.Some(0);
|
||||
onPingDiscovered(serverInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
lock (activePings)
|
||||
{
|
||||
if (activePings.ContainsKey(address)) { return; }
|
||||
activePings.Add(address, activePings.Any() ? activePings.Values.Max() + 1 : 0);
|
||||
}
|
||||
serverInfo.Ping = Option<int>.None();
|
||||
TaskPool.Add($"PingServerAsync ({address})", PingServerAsync(address, 1000),
|
||||
rtt =>
|
||||
{
|
||||
if (!rtt.TryGetResult(out serverInfo.Ping)) { serverInfo.Ping = Option<int>.None(); }
|
||||
onPingDiscovered(serverInfo);
|
||||
lock (activePings)
|
||||
{
|
||||
activePings.Remove(address);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<Option<int>> PingServerAsync(IPAddress ipAddress, int timeOut)
|
||||
{
|
||||
await Task.Yield();
|
||||
bool shouldGo = false;
|
||||
while (!shouldGo)
|
||||
{
|
||||
lock (activePings)
|
||||
{
|
||||
shouldGo = activePings.Count(kvp => kvp.Value < activePings[ipAddress]) < 25;
|
||||
}
|
||||
await Task.Delay(25);
|
||||
}
|
||||
|
||||
if (ipAddress == null) { return Option<int>.None(); }
|
||||
|
||||
//don't attempt to ping if the address is IPv6 and it's not supported
|
||||
if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6 && !Socket.OSSupportsIPv6) { return Option<int>.None(); }
|
||||
|
||||
Ping ping = new Ping();
|
||||
byte[] buffer = new byte[32];
|
||||
try
|
||||
{
|
||||
PingReply pingReply = await ping.SendPingAsync(ipAddress, timeOut, buffer, new PingOptions(128, true));
|
||||
|
||||
return pingReply.Status switch
|
||||
{
|
||||
IPStatus.Success => Option<int>.Some((int)pingReply.RoundtripTime),
|
||||
_ => Option<int>.None(),
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GameAnalyticsManager.AddErrorEventOnce("ServerListScreen.PingServer:PingException" + ipAddress, GameAnalyticsManager.ErrorSeverity.Warning, "Failed to ping a server - " + (ex?.InnerException?.Message ?? ex.Message));
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage("Failed to ping a server (" + ipAddress + ") - " + (ex?.InnerException?.Message ?? ex.Message), Color.Red);
|
||||
#endif
|
||||
|
||||
return Option<int>.None();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,509 @@
|
||||
#nullable enable
|
||||
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Steam;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
sealed class ServerInfo : ISerializableEntity
|
||||
{
|
||||
public abstract class DataSource
|
||||
{
|
||||
public static Option<DataSource> Parse(XElement element)
|
||||
=> ReflectionUtils.ParseDerived<DataSource, XElement>(element);
|
||||
public abstract void Write(XElement element);
|
||||
}
|
||||
|
||||
public Endpoint Endpoint { get; private set; }
|
||||
|
||||
public Option<DataSource> MetadataSource = Option<DataSource>.None();
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public string ServerName { get; set; } = "";
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public string ServerMessage { get; set; } = "";
|
||||
|
||||
public int PlayerCount { get; set; }
|
||||
|
||||
[Serialize(0, IsPropertySaveable.Yes)]
|
||||
public int MaxPlayers { get; set; }
|
||||
|
||||
public bool GameStarted { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool HasPassword { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier GameMode { get; set; }
|
||||
|
||||
[Serialize(SelectionMode.Manual, IsPropertySaveable.Yes)]
|
||||
public SelectionMode ModeSelectionMode { get; set; }
|
||||
|
||||
[Serialize(SelectionMode.Manual, IsPropertySaveable.Yes)]
|
||||
public SelectionMode SubSelectionMode { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool AllowSpectating { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool VoipEnabled { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool KarmaEnabled { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool FriendlyFireEnabled { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool AllowRespawn { get; set; }
|
||||
|
||||
[Serialize(YesNoMaybe.No, IsPropertySaveable.Yes)]
|
||||
public YesNoMaybe TraitorsEnabled { get; set; }
|
||||
|
||||
[Serialize(PlayStyle.Casual, IsPropertySaveable.Yes)]
|
||||
public PlayStyle PlayStyle { get; set; }
|
||||
|
||||
public Version GameVersion { get; set; } = new Version(0, 0, 0, 0);
|
||||
|
||||
public Option<int> Ping = Option<int>.None();
|
||||
|
||||
public bool Checked = false;
|
||||
|
||||
public readonly struct ContentPackageInfo
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly string Hash;
|
||||
public readonly Option<ContentPackageId> Id;
|
||||
|
||||
public ContentPackageInfo(string name, string hash, Option<ContentPackageId> id)
|
||||
{
|
||||
Name = name;
|
||||
Hash = hash;
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public ContentPackageInfo(ContentPackage pkg)
|
||||
{
|
||||
Name = pkg.Name;
|
||||
Hash = pkg.Hash.StringRepresentation;
|
||||
Id = pkg.UgcId;
|
||||
}
|
||||
}
|
||||
|
||||
public ImmutableArray<ContentPackageInfo> ContentPackages;
|
||||
|
||||
public bool IsModded => ContentPackages.Any(p => !GameMain.VanillaContent.NameMatches(p.Name));
|
||||
|
||||
public ServerInfo(Endpoint endpoint)
|
||||
{
|
||||
SerializableProperties = SerializableProperty.GetProperties(this);
|
||||
Endpoint = endpoint;
|
||||
ContentPackages = ImmutableArray<ContentPackageInfo>.Empty;
|
||||
}
|
||||
|
||||
public static ServerInfo FromServerConnection(NetworkConnection connection, ServerSettings serverSettings)
|
||||
{
|
||||
var serverInfo = new ServerInfo(connection.Endpoint)
|
||||
{
|
||||
GameMode = GameMain.NetLobbyScreen.SelectedMode?.Identifier ?? Identifier.Empty,
|
||||
GameStarted = Screen.Selected != GameMain.NetLobbyScreen,
|
||||
GameVersion = GameMain.Version,
|
||||
PlayerCount = GameMain.Client.ConnectedClients.Count,
|
||||
ContentPackages = ContentPackageManager.EnabledPackages.All.Select(p => new ContentPackageInfo(p)).ToImmutableArray(),
|
||||
Ping = GameMain.Client.Ping,
|
||||
|
||||
// -------------------------------------
|
||||
// Settings that cannot be copied via
|
||||
// SerializableProperty because they do
|
||||
// not implement the attribute
|
||||
ServerName = serverSettings.ServerName,
|
||||
ServerMessage = serverSettings.ServerMessageText,
|
||||
// -------------------------------------
|
||||
// Settings that cannot be copied via
|
||||
// SerializableProperty due to name mismatch
|
||||
HasPassword = serverSettings.HasPassword,
|
||||
VoipEnabled = serverSettings.VoiceChatEnabled,
|
||||
FriendlyFireEnabled = serverSettings.AllowFriendlyFire,
|
||||
// -------------------------------------
|
||||
|
||||
Checked = true
|
||||
};
|
||||
|
||||
var serverInfoSerializableProperties
|
||||
= SerializableProperty.GetProperties(serverInfo);
|
||||
var serverSettingsSerializableProperties
|
||||
= SerializableProperty.GetProperties(serverSettings);
|
||||
|
||||
var intersection = serverInfoSerializableProperties.Keys
|
||||
.Where(serverSettingsSerializableProperties.ContainsKey);
|
||||
|
||||
foreach (var key in intersection)
|
||||
{
|
||||
var propToGet = serverSettingsSerializableProperties[key];
|
||||
var propToSet = serverInfoSerializableProperties[key];
|
||||
if (!propToGet.PropertyInfo.CanRead) { continue; }
|
||||
if (!propToSet.PropertyInfo.CanWrite) { continue; }
|
||||
propToSet.SetValue(
|
||||
serverInfo,
|
||||
propToGet.GetValue(serverSettings));
|
||||
}
|
||||
|
||||
return serverInfo;
|
||||
}
|
||||
|
||||
public void CreatePreviewWindow(GUIFrame frame)
|
||||
{
|
||||
frame.ClearChildren();
|
||||
|
||||
var serverListScreen = GameMain.ServerListScreen;
|
||||
|
||||
var title = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform), ServerName, font: GUIStyle.LargeFont)
|
||||
{
|
||||
ToolTip = ServerName,
|
||||
CanBeFocused = false
|
||||
};
|
||||
title.Text = ToolBox.LimitString(title.Text, title.Font, (int)(title.Rect.Width * 0.85f));
|
||||
|
||||
bool isFavorite = serverListScreen.IsFavorite(this);
|
||||
|
||||
static LocalizedString favoriteTickBoxToolTip(bool isFavorite)
|
||||
=> TextManager.Get(isFavorite ? "RemoveFromFavorites" : "AddToFavorites");
|
||||
|
||||
GUITickBox favoriteTickBox = new GUITickBox(new RectTransform(new Vector2(0.15f, 0.8f), title.RectTransform, Anchor.CenterRight),
|
||||
"", null, "GUIServerListFavoriteTickBox")
|
||||
{
|
||||
UserData = this,
|
||||
Selected = isFavorite,
|
||||
ToolTip = favoriteTickBoxToolTip(isFavorite),
|
||||
OnSelected = tickbox =>
|
||||
{
|
||||
ServerInfo info = (ServerInfo)tickbox.UserData;
|
||||
if (tickbox.Selected)
|
||||
{
|
||||
GameMain.ServerListScreen.AddToFavoriteServers(info);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.ServerListScreen.RemoveFromFavoriteServers(info);
|
||||
}
|
||||
tickbox.ToolTip = favoriteTickBoxToolTip(tickbox.Selected);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform),
|
||||
TextManager.AddPunctuation(':', TextManager.Get("ServerListVersion"),
|
||||
GameVersion == new Version(0, 0, 0, 0) ? TextManager.Get("Unknown") : GameVersion.ToString()))
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
|
||||
PlayStyle playStyle = PlayStyle;
|
||||
Sprite? playStyleBannerSprite = GUIStyle.GetComponentStyle($"PlayStyleBanner.{playStyle}")?.GetSprite(GUIComponent.ComponentState.None);
|
||||
|
||||
GUIComponent playStyleBanner;
|
||||
Color playStyleBannerColor;
|
||||
if (playStyleBannerSprite != null)
|
||||
{
|
||||
float playStyleBannerAspectRatio = (float)playStyleBannerSprite.SourceRect.Width / (float)playStyleBannerSprite.SourceRect.Height;
|
||||
playStyleBanner = new GUIImage(new RectTransform(new Vector2(1.0f, 1.0f / playStyleBannerAspectRatio), frame.RectTransform, scaleBasis: ScaleBasis.BothWidth),
|
||||
playStyleBannerSprite, null, true);
|
||||
playStyleBannerColor = playStyleBannerSprite.SourceElement.GetAttributeColor("bannercolor", Color.Black);
|
||||
}
|
||||
else
|
||||
{
|
||||
playStyleBanner = new GUIFrame(new RectTransform((1.0f, 0.2f), frame.RectTransform), style: null)
|
||||
{
|
||||
Color = Color.Black,
|
||||
DisabledColor = Color.Black,
|
||||
OutlineColor = Color.Black,
|
||||
PressedColor = Color.Black,
|
||||
SelectedColor = Color.Black,
|
||||
HoverColor = Color.Black
|
||||
};
|
||||
playStyleBannerColor = Color.Black;
|
||||
}
|
||||
|
||||
var playStyleName = new GUITextBlock(
|
||||
new RectTransform(new Vector2(0.15f, 0.0f), playStyleBanner.RectTransform)
|
||||
{ RelativeOffset = new Vector2(0.0f, 0.06f) },
|
||||
TextManager.AddPunctuation(':', TextManager.Get("serverplaystyle"),
|
||||
TextManager.Get($"servertag.{playStyle}")), textColor: Color.White,
|
||||
font: GUIStyle.SmallFont, textAlignment: Alignment.Center,
|
||||
color: playStyleBannerColor, style: "GUISlopedHeader");
|
||||
playStyleName.RectTransform.NonScaledSize = (playStyleName.Font.MeasureString(playStyleName.Text) + new Vector2(20, 5) * GUI.Scale).ToPoint();
|
||||
playStyleName.RectTransform.IsFixedSize = true;
|
||||
|
||||
var serverType = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform),
|
||||
Endpoint?.ServerTypeString ?? string.Empty,
|
||||
textAlignment: Alignment.TopLeft)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
serverType.RectTransform.MinSize = new Point(0, (int)(serverType.Rect.Height * 1.5f));
|
||||
|
||||
var content = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.6f), frame.RectTransform))
|
||||
{
|
||||
Stretch = true
|
||||
};
|
||||
// playstyle tags -----------------------------------------------------------------------------
|
||||
|
||||
var playStyleContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), content.RectTransform), isHorizontal: true)
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.01f,
|
||||
CanBeFocused = true
|
||||
};
|
||||
|
||||
var playStyleTags = GetPlayStyleTags();
|
||||
foreach (var tag in playStyleTags)
|
||||
{
|
||||
var playStyleIcon = GUIStyle.GetComponentStyle($"PlayStyleIcon.{tag}")
|
||||
?.GetSprite(GUIComponent.ComponentState.None);
|
||||
if (playStyleIcon is null) { continue; }
|
||||
|
||||
new GUIImage(new RectTransform(Vector2.One, playStyleContainer.RectTransform),
|
||||
playStyleIcon, scaleToFit: true)
|
||||
{
|
||||
ToolTip = TextManager.Get($"servertagdescription.{tag}"),
|
||||
Color = Color.White
|
||||
};
|
||||
}
|
||||
|
||||
playStyleContainer.Recalculate();
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
float elementHeight = 0.075f;
|
||||
|
||||
// Spacing
|
||||
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.025f), content.RectTransform), style: null);
|
||||
|
||||
var serverMsg = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.3f), content.RectTransform)) { ScrollBarVisible = true };
|
||||
var msgText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), serverMsg.Content.RectTransform), ServerMessage ?? string.Empty, font: GUIStyle.SmallFont, wrap: true)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
serverMsg.Content.RectTransform.SizeChanged += () => { msgText.CalculateHeightFromText(); };
|
||||
msgText.RectTransform.SizeChanged += () => { serverMsg.UpdateScrollBarSize(); };
|
||||
|
||||
var gameMode = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("GameMode"));
|
||||
new GUITextBlock(new RectTransform(Vector2.One, gameMode.RectTransform),
|
||||
TextManager.Get(GameMode.IsEmpty ? "Unknown" : "GameMode." + GameMode).Fallback(GameMode.Value),
|
||||
textAlignment: Alignment.Right);
|
||||
|
||||
GUITextBlock playStyleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("serverplaystyle"));
|
||||
new GUITextBlock(new RectTransform(Vector2.One, playStyleText.RectTransform), TextManager.Get("servertag." + playStyle), textAlignment: Alignment.Right);
|
||||
|
||||
var subSelection = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("ServerListSubSelection"));
|
||||
new GUITextBlock(new RectTransform(Vector2.One, subSelection.RectTransform), TextManager.Get(SubSelectionMode.ToString()), textAlignment: Alignment.Right);
|
||||
|
||||
var modeSelection = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("ServerListModeSelection"));
|
||||
new GUITextBlock(new RectTransform(Vector2.One, modeSelection.RectTransform), TextManager.Get(ModeSelectionMode.ToString()), textAlignment: Alignment.Right);
|
||||
|
||||
if (gameMode.TextSize.X + gameMode.GetChild<GUITextBlock>().TextSize.X > gameMode.Rect.Width ||
|
||||
subSelection.TextSize.X + subSelection.GetChild<GUITextBlock>().TextSize.X > subSelection.Rect.Width ||
|
||||
modeSelection.TextSize.X + modeSelection.GetChild<GUITextBlock>().TextSize.X > modeSelection.Rect.Width)
|
||||
{
|
||||
gameMode.Font = subSelection.Font = modeSelection.Font = GUIStyle.SmallFont;
|
||||
gameMode.GetChild<GUITextBlock>().Font = subSelection.GetChild<GUITextBlock>().Font = modeSelection.GetChild<GUITextBlock>().Font = GUIStyle.SmallFont;
|
||||
playStyleText.Font = playStyleText.GetChild<GUITextBlock>().Font = GUIStyle.SmallFont;
|
||||
}
|
||||
|
||||
var allowSpectating = new GUITickBox(new RectTransform(new Vector2(1, elementHeight), content.RectTransform), TextManager.Get("ServerListAllowSpectating"))
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
allowSpectating.Selected = AllowSpectating;
|
||||
|
||||
var allowRespawn = new GUITickBox(new RectTransform(new Vector2(1, elementHeight), content.RectTransform), TextManager.Get("ServerSettingsAllowRespawning"))
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
allowRespawn.Selected = AllowRespawn;
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
|
||||
TextManager.Get("ServerListContentPackages"), textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont);
|
||||
|
||||
var contentPackageList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.3f), frame.RectTransform))
|
||||
{
|
||||
ScrollBarVisible = true,
|
||||
OnSelected = (component, o) => false
|
||||
};
|
||||
if (ContentPackages.Length == 0)
|
||||
{
|
||||
new GUITextBlock(new RectTransform(Vector2.One, contentPackageList.Content.RectTransform), TextManager.Get("Unknown"), textAlignment: Alignment.Center)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var package in ContentPackages)
|
||||
{
|
||||
var packageText = new GUITickBox(
|
||||
new RectTransform(new Vector2(1.0f, 0.15f), contentPackageList.Content.RectTransform)
|
||||
{ MinSize = new Point(0, 15) },
|
||||
package.Name)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
if (!string.IsNullOrEmpty(package.Hash))
|
||||
{
|
||||
if (ContentPackageManager.AllPackages.Any(contentPackage => contentPackage.Hash.StringRepresentation == package.Hash))
|
||||
{
|
||||
packageText.TextColor = GUIStyle.Green;
|
||||
packageText.Selected = true;
|
||||
}
|
||||
//workshop download link found
|
||||
else if (package.Id is Some<ContentPackageId> { Value: var ugcId } && ugcId is SteamWorkshopId)
|
||||
{
|
||||
packageText.ToolTip = TextManager.GetWithVariable("ServerListIncompatibleContentPackageWorkshopAvailable", "[contentpackage]", package.Name);
|
||||
}
|
||||
else //no package or workshop download link found
|
||||
{
|
||||
packageText.TextColor = GameMain.VanillaContent.NameMatches(package.Name) ? GUIStyle.Red : GUIStyle.Yellow;
|
||||
packageText.ToolTip = TextManager.GetWithVariables("ServerListIncompatibleContentPackage",
|
||||
("[contentpackage]", package.Name), ("[hash]", package.Hash));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
foreach (GUIComponent c in content.Children)
|
||||
{
|
||||
if (c is GUITextBlock textBlock) { textBlock.Padding = Vector4.Zero; }
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Identifier> GetPlayStyleTags()
|
||||
{
|
||||
yield return $"Karma.{KarmaEnabled}".ToIdentifier();
|
||||
yield return (TraitorsEnabled == YesNoMaybe.Yes ? $"Traitors.True" : $"Traitors.False").ToIdentifier();
|
||||
yield return $"VoIP.{VoipEnabled}".ToIdentifier();
|
||||
yield return $"FriendlyFire.{FriendlyFireEnabled}".ToIdentifier();
|
||||
yield return $"Modded.{ContentPackages.Any()}".ToIdentifier();
|
||||
}
|
||||
|
||||
public void UpdateInfo(Func<string, string?> valueGetter)
|
||||
{
|
||||
ServerMessage = valueGetter("message") ?? "";
|
||||
if (Version.TryParse(valueGetter("version"), out var version))
|
||||
{
|
||||
GameVersion = version;
|
||||
}
|
||||
if (int.TryParse(valueGetter("playercount"), out int playerCount)) { PlayerCount = playerCount; }
|
||||
if (int.TryParse(valueGetter("maxplayernum"), out int maxPlayers)) { MaxPlayers = maxPlayers; }
|
||||
if (Enum.TryParse(valueGetter("modeselectionmode"), out SelectionMode modeSelectionMode)) { ModeSelectionMode = modeSelectionMode; }
|
||||
if (Enum.TryParse(valueGetter("subselectionmode"), out SelectionMode subSelectionMode)) { SubSelectionMode = subSelectionMode; }
|
||||
|
||||
HasPassword = getBool("haspassword");
|
||||
GameStarted = getBool("gamestarted");
|
||||
KarmaEnabled = getBool("karmaenabled");
|
||||
FriendlyFireEnabled = getBool("friendlyfireenabled");
|
||||
AllowSpectating = getBool("allowspectating");
|
||||
AllowRespawn = getBool("allowrespawn");
|
||||
VoipEnabled = getBool("voicechatenabled");
|
||||
|
||||
GameMode = valueGetter("gamemode")?.ToIdentifier() ?? Identifier.Empty;
|
||||
if (Enum.TryParse(valueGetter("traitors"), out YesNoMaybe traitorsEnabled)) { TraitorsEnabled = traitorsEnabled; }
|
||||
if (Enum.TryParse(valueGetter("playstyle"), out PlayStyle playStyle)) { PlayStyle = playStyle; }
|
||||
|
||||
ContentPackages = ExtractContentPackageInfo(valueGetter).ToImmutableArray();
|
||||
|
||||
bool getBool(string key)
|
||||
{
|
||||
string? data = valueGetter(key);
|
||||
return bool.TryParse(data, out var result) && result;
|
||||
}
|
||||
}
|
||||
|
||||
private static ContentPackageInfo[] ExtractContentPackageInfo(Func<string, string?> valueGetter)
|
||||
{
|
||||
string? joinedNames = valueGetter("contentpackage");
|
||||
string? joinedHashes = valueGetter("contentpackagehash");
|
||||
string? joinedWorkshopIds = valueGetter("contentpackageid");
|
||||
|
||||
string[] contentPackageNames = joinedNames.IsNullOrEmpty() ? Array.Empty<string>() : joinedNames.Split(',');
|
||||
string[] contentPackageHashes = joinedHashes.IsNullOrEmpty() ? Array.Empty<string>() : joinedHashes.Split(',');
|
||||
#warning TODO: genericize
|
||||
ulong[] contentPackageIds = joinedWorkshopIds.IsNullOrEmpty() ? new ulong[1] : SteamManager.ParseWorkshopIds(joinedWorkshopIds).ToArray();
|
||||
|
||||
if (contentPackageNames.Length != contentPackageHashes.Length
|
||||
|| contentPackageHashes.Length != contentPackageIds.Length)
|
||||
{
|
||||
return Array.Empty<ContentPackageInfo>();
|
||||
}
|
||||
|
||||
return contentPackageNames
|
||||
.Zip(contentPackageHashes, (name, hash) => (name, hash))
|
||||
.Zip(contentPackageIds, (t1, id) =>
|
||||
new ContentPackageInfo(
|
||||
t1.name,
|
||||
t1.hash,
|
||||
Option<ContentPackageId>.Some(new SteamWorkshopId(id))))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public static Option<ServerInfo> FromXElement(XElement element)
|
||||
{
|
||||
string endpointStr
|
||||
= element.GetAttributeString("Endpoint", null)
|
||||
?? element.GetAttributeString("OwnerID", null)
|
||||
?? $"{element.GetAttributeString("IP", "")}:{element.GetAttributeInt("Port", 0)}";
|
||||
|
||||
if (!Endpoint.Parse(endpointStr).TryUnwrap(out var endpoint)) { return Option<ServerInfo>.None(); }
|
||||
|
||||
var gameVersionStr = element.GetAttributeString("GameVersion", "");
|
||||
if (!Version.TryParse(gameVersionStr, out var gameVersion)) { gameVersion = GameMain.Version; }
|
||||
var info = new ServerInfo(endpoint)
|
||||
{
|
||||
GameVersion = gameVersion
|
||||
};
|
||||
SerializableProperty.DeserializeProperties(info, element);
|
||||
|
||||
info.MetadataSource = DataSource.Parse(element);
|
||||
|
||||
return Option<ServerInfo>.Some(info);
|
||||
}
|
||||
|
||||
public XElement ToXElement()
|
||||
{
|
||||
XElement element = new XElement(GetType().Name);
|
||||
|
||||
element.SetAttributeValue("Endpoint", Endpoint.ToString());
|
||||
element.SetAttributeValue("GameVersion", GameVersion.ToString());
|
||||
|
||||
SerializableProperty.SerializeProperties(this, element, saveIfDefault: true);
|
||||
|
||||
if (MetadataSource.TryUnwrap(out var dataSource))
|
||||
{
|
||||
dataSource.Write(element);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is ServerInfo other && Equals(other);
|
||||
}
|
||||
|
||||
public bool Equals(ServerInfo other)
|
||||
=> other.Endpoint == Endpoint;
|
||||
|
||||
public override int GetHashCode() => Endpoint.GetHashCode();
|
||||
|
||||
string ISerializableEntity.Name => "ServerInfo";
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; }
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CompositeServerProvider : ServerProvider
|
||||
{
|
||||
private readonly ImmutableArray<ServerProvider> providers;
|
||||
|
||||
public CompositeServerProvider(params ServerProvider[] providers)
|
||||
{
|
||||
this.providers = providers.ToImmutableArray();
|
||||
}
|
||||
|
||||
protected override void RetrieveServersImpl(Action<ServerInfo> onServerDataReceived, Action onQueryCompleted)
|
||||
{
|
||||
int providersFinished = 0;
|
||||
void ackFinishedProvider()
|
||||
{
|
||||
providersFinished++;
|
||||
if (providersFinished == providers.Length)
|
||||
{
|
||||
onQueryCompleted();
|
||||
}
|
||||
}
|
||||
providers.ForEach(p => p.RetrieveServers(onServerDataReceived, ackFinishedProvider));
|
||||
}
|
||||
|
||||
public override void Cancel()
|
||||
=> providers.ForEach(p => p.Cancel());
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
abstract class ServerProvider
|
||||
{
|
||||
public void RetrieveServers(Action<ServerInfo> onServerDataReceived, Action onQueryCompleted)
|
||||
{
|
||||
Cancel();
|
||||
RetrieveServersImpl(onServerDataReceived, onQueryCompleted);
|
||||
}
|
||||
protected abstract void RetrieveServersImpl(Action<ServerInfo> onServerDataReceived, Action onQueryCompleted);
|
||||
public abstract void Cancel();
|
||||
}
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Steam;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
sealed class SteamDedicatedServerProvider : ServerProvider
|
||||
{
|
||||
public class DataSource : ServerInfo.DataSource
|
||||
{
|
||||
public readonly UInt16 QueryPort;
|
||||
|
||||
public DataSource(UInt16 queryPort)
|
||||
{
|
||||
QueryPort = queryPort;
|
||||
}
|
||||
|
||||
/// Method is invoked via reflection,
|
||||
/// see <see cref="ServerInfo.DataSource.Parse" />
|
||||
public new static Option<DataSource> Parse(XElement element)
|
||||
=> element.TryGetAttributeInt("QueryPort", out var result)
|
||||
? result switch
|
||||
{
|
||||
var invalidPort when invalidPort <= 0 || invalidPort > UInt16.MaxValue => Option<DataSource>.None(),
|
||||
var queryPort => Option<DataSource>.Some(new DataSource((UInt16)queryPort))
|
||||
}
|
||||
: Option<DataSource>.None();
|
||||
|
||||
public override void Write(XElement element) => element.SetAttributeValue("QueryPort", QueryPort);
|
||||
}
|
||||
|
||||
private static Option<ServerInfo> InfoFromListEntry(Steamworks.Data.ServerInfo entry) =>
|
||||
entry.Name.IsNullOrEmpty()
|
||||
? Option<ServerInfo>.None()
|
||||
: Option<ServerInfo>.Some(new ServerInfo(new LidgrenEndpoint(entry.Address, entry.ConnectionPort))
|
||||
{
|
||||
ServerName = entry.Name,
|
||||
HasPassword = entry.Passworded,
|
||||
PlayerCount = entry.Players,
|
||||
MaxPlayers = entry.MaxPlayers,
|
||||
MetadataSource = Option<ServerInfo.DataSource>.Some(new DataSource((UInt16)entry.QueryPort))
|
||||
});
|
||||
|
||||
private static void HandleResponsiveServer(Steamworks.Data.ServerInfo entry, Action<ServerInfo> onServerDataReceived)
|
||||
{
|
||||
TaskPool.Add($"QueryServerRules (GetServers, {entry.Name}, {entry.Address})", entry.QueryRulesAsync(),
|
||||
t =>
|
||||
{
|
||||
if (t.Status == TaskStatus.Faulted)
|
||||
{
|
||||
TaskPool.PrintTaskExceptions(t, $"Failed to retrieve rules for {entry.Name}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!t.TryGetResult(out Dictionary<string, string> rules)) { return; }
|
||||
if (rules is null) { return; }
|
||||
if (!InfoFromListEntry(entry).TryUnwrap(out var serverInfo)) { return; }
|
||||
serverInfo.UpdateInfo(key =>
|
||||
{
|
||||
if (rules.TryGetValue(key, out var val)) { return val; }
|
||||
return null;
|
||||
});
|
||||
serverInfo.Checked = true; //rules != null;
|
||||
|
||||
onServerDataReceived(serverInfo);
|
||||
});
|
||||
}
|
||||
|
||||
private static void HandleUnresponsiveServer(Steamworks.Data.ServerInfo entry, Action<ServerInfo> onServerDataReceived)
|
||||
{
|
||||
//TODO: do we still want to list unresponsive servers?
|
||||
if (!InfoFromListEntry(entry).TryUnwrap(out var serverInfo)) { return; }
|
||||
onServerDataReceived(serverInfo);
|
||||
}
|
||||
|
||||
private Steamworks.ServerList.Internet? serverQuery;
|
||||
private CoroutineHandle? queryCoroutine;
|
||||
|
||||
protected override void RetrieveServersImpl(Action<ServerInfo> onServerDataReceived, Action onQueryCompleted)
|
||||
{
|
||||
if (!SteamManager.IsInitialized)
|
||||
{
|
||||
onQueryCompleted();
|
||||
return;
|
||||
}
|
||||
|
||||
// All lambdas in here must only capture this call's
|
||||
// query, not the provider's latest query
|
||||
var selfServerQuery = new Steamworks.ServerList.Internet();
|
||||
serverQuery = selfServerQuery;
|
||||
|
||||
ConcurrentQueue<Steamworks.Data.ServerInfo> responsiveServers =
|
||||
new ConcurrentQueue<Steamworks.Data.ServerInfo>();
|
||||
ConcurrentQueue<Steamworks.Data.ServerInfo> unresponsiveServers =
|
||||
new ConcurrentQueue<Steamworks.Data.ServerInfo>();
|
||||
|
||||
selfServerQuery.OnResponsiveServer = responsiveServers.Enqueue;
|
||||
selfServerQuery.OnUnresponsiveServer = unresponsiveServers.Enqueue;
|
||||
|
||||
void dequeue(int? limit = null)
|
||||
{
|
||||
for (int i = 0; (!limit.HasValue || i < limit) && responsiveServers.TryDequeue(out var serverInfo); i++)
|
||||
{
|
||||
HandleResponsiveServer(serverInfo, onServerDataReceived);
|
||||
}
|
||||
|
||||
for (int i = 0; (!limit.HasValue || i < limit) && unresponsiveServers.TryDequeue(out var serverInfo); i++)
|
||||
{
|
||||
HandleUnresponsiveServer(serverInfo, onServerDataReceived);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<CoroutineStatus> dequeueCoroutine()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
dequeue(limit: 20);
|
||||
yield return new WaitForSeconds(0.1f, ignorePause: true);
|
||||
}
|
||||
}
|
||||
var selfQueryCoroutine = CoroutineManager.StartCoroutine(dequeueCoroutine(),
|
||||
$"{nameof(SteamDedicatedServerProvider)}.{nameof(RetrieveServers)}.{nameof(dequeueCoroutine)}");
|
||||
queryCoroutine = selfQueryCoroutine;
|
||||
|
||||
TaskPool.Add("RunServerQuery", selfServerQuery.RunQueryAsync(timeoutSeconds: 30f),
|
||||
t =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Clear the callbacks because it's too late now, we want to get this over with
|
||||
selfServerQuery.OnResponsiveServer = null;
|
||||
selfServerQuery.OnUnresponsiveServer = null;
|
||||
|
||||
CoroutineManager.StopCoroutines(selfQueryCoroutine);
|
||||
dequeue();
|
||||
|
||||
if (t.Status == TaskStatus.Faulted) { TaskPool.PrintTaskExceptions(t, "Failed to retrieve servers"); }
|
||||
|
||||
selfServerQuery.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
onQueryCompleted();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public override void Cancel()
|
||||
{
|
||||
if (queryCoroutine != null) { CoroutineManager.StopCoroutines(queryCoroutine); }
|
||||
serverQuery?.Dispose();
|
||||
serverQuery = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Steam;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
sealed class SteamP2PServerProvider : ServerProvider
|
||||
{
|
||||
public class DataSource : ServerInfo.DataSource
|
||||
{
|
||||
public readonly Steamworks.Data.Lobby Lobby;
|
||||
|
||||
public override void Write(XElement element) { /* do nothing */ }
|
||||
|
||||
public DataSource(Steamworks.Data.Lobby lobby)
|
||||
{
|
||||
Lobby = lobby;
|
||||
}
|
||||
}
|
||||
|
||||
private object? queryRef = null;
|
||||
|
||||
protected override void RetrieveServersImpl(Action<ServerInfo> onServerDataReceived, Action onQueryCompleted)
|
||||
{
|
||||
if (!SteamManager.IsInitialized)
|
||||
{
|
||||
onQueryCompleted();
|
||||
return;
|
||||
}
|
||||
|
||||
// All lambdas and local methods in here must only capture
|
||||
// this call's query, not the provider's latest query
|
||||
var selfQueryRef = new object();
|
||||
queryRef = selfQueryRef;
|
||||
|
||||
Steamworks.Data.LobbyQuery lobbyQuery = Steamworks.SteamMatchmaking.CreateLobbyQuery()
|
||||
.FilterDistanceWorldwide()
|
||||
.WithMaxResults(50);
|
||||
// Steamworks is unable to retrieve more than 50 lobbies per request
|
||||
// (see https://partner.steamgames.com/doc/features/multiplayer/matchmaking#3)
|
||||
// To work around this, we'll make up to 10 requests, asking to ignore
|
||||
// all previous results in each subsequent request.
|
||||
#warning TODO: do something less horrible here?
|
||||
|
||||
int requestCount = 0;
|
||||
HashSet<SteamId> retrieved = new HashSet<SteamId>();
|
||||
|
||||
void startQuery()
|
||||
{
|
||||
if (requestCount >= 10) { return; }
|
||||
requestCount++;
|
||||
TaskPool.Add($"LobbyQuery.RequestAsync ({requestCount})", lobbyQuery.RequestAsync(), onRequestComplete);
|
||||
}
|
||||
|
||||
void onRequestComplete(Task t)
|
||||
{
|
||||
// If queryRef != selfQueryRef, this query was cancelled
|
||||
if (!ReferenceEquals(selfQueryRef, queryRef)) { return; }
|
||||
|
||||
if (!t.TryGetResult(out Steamworks.Data.Lobby[] lobbies)
|
||||
|| lobbies is null
|
||||
|| lobbies.Length == 0)
|
||||
{
|
||||
onQueryCompleted();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var lobby in lobbies)
|
||||
{
|
||||
string lobbyOwnerStr = lobby.GetData("lobbyowner");
|
||||
lobbyQuery = lobbyQuery.WithoutKeyValue("lobbyowner", lobbyOwnerStr);
|
||||
|
||||
string serverName = lobby.GetData("name");
|
||||
if (string.IsNullOrEmpty(serverName)) { continue; }
|
||||
|
||||
var ownerId = SteamId.Parse(lobbyOwnerStr);
|
||||
if (!ownerId.TryUnwrap(out var lobbyOwnerId)) { continue; }
|
||||
|
||||
if (retrieved.Contains(lobbyOwnerId)) { continue; }
|
||||
retrieved.Add(lobbyOwnerId);
|
||||
|
||||
var serverInfo = new ServerInfo(new SteamP2PEndpoint(lobbyOwnerId))
|
||||
{
|
||||
ServerName = serverName,
|
||||
MetadataSource = Option<ServerInfo.DataSource>.Some(new DataSource(lobby))
|
||||
};
|
||||
serverInfo.UpdateInfo(key => lobby.GetData(key));
|
||||
serverInfo.Checked = true;
|
||||
|
||||
onServerDataReceived(serverInfo);
|
||||
}
|
||||
startQuery();
|
||||
}
|
||||
|
||||
startQuery();
|
||||
}
|
||||
|
||||
public override void Cancel()
|
||||
{
|
||||
queryRef = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
@@ -196,11 +196,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
foreach (var data in richString.RichTextData.Value)
|
||||
{
|
||||
if (!UInt64.TryParse(data.Metadata, out ulong id)) { return; }
|
||||
Client client = GameMain.Client.ConnectedClients.Find(c => c.SteamID == id)
|
||||
?? GameMain.Client.ConnectedClients.Find(c => c.ID == id)
|
||||
?? GameMain.Client.PreviouslyConnectedClients.FirstOrDefault(c => c.SteamID == id)
|
||||
?? GameMain.Client.PreviouslyConnectedClients.FirstOrDefault(c => c.ID == id);
|
||||
Client client = data.ExtractClient();
|
||||
if (client != null && client.Karma < 40.0f)
|
||||
{
|
||||
textContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.0f), listBox.Content.RectTransform),
|
||||
@@ -258,11 +254,8 @@ namespace Barotrauma.Networking
|
||||
|
||||
foreach (GUIComponent child in listBox.Content.Children)
|
||||
{
|
||||
var textBlock = child as GUITextBlock;
|
||||
if (textBlock == null) continue;
|
||||
|
||||
if (!(child is GUITextBlock textBlock)) { continue; }
|
||||
child.Visible = true;
|
||||
|
||||
if (msgTypeHidden[(int)((LogMessage)child.UserData).Type])
|
||||
{
|
||||
child.Visible = false;
|
||||
@@ -287,10 +280,10 @@ namespace Barotrauma.Networking
|
||||
listBox.Content.RectTransform.ReverseChildren();
|
||||
}
|
||||
|
||||
public bool ClearFilter(GUIComponent button, object obj)
|
||||
public bool ClearFilter(GUIComponent button, object _)
|
||||
{
|
||||
var searchBox = button.UserData as GUITextBox;
|
||||
if (searchBox != null) searchBox.Text = "";
|
||||
if (searchBox != null) { searchBox.Text = ""; }
|
||||
|
||||
msgFilter = "";
|
||||
FilterMessages();
|
||||
|
||||
@@ -121,13 +121,10 @@ namespace Barotrauma.Networking
|
||||
|
||||
ReadMonsterEnabled(incMsg);
|
||||
BanList.ClientAdminRead(incMsg);
|
||||
Whitelist.ClientAdminRead(incMsg);
|
||||
}
|
||||
|
||||
public void ClientRead(IReadMessage incMsg)
|
||||
{
|
||||
cachedServerListInfo = null;
|
||||
|
||||
NetFlags requiredFlags = (NetFlags)incMsg.ReadByte();
|
||||
|
||||
if (requiredFlags.HasFlag(NetFlags.Name))
|
||||
@@ -147,7 +144,6 @@ namespace Barotrauma.Networking
|
||||
AllowFileTransfers = incMsg.ReadBoolean();
|
||||
incMsg.ReadPadBits();
|
||||
TickRate = incMsg.ReadRangedInteger(1, 60);
|
||||
GameMain.NetworkMember.TickRate = TickRate;
|
||||
|
||||
if (requiredFlags.HasFlag(NetFlags.Properties))
|
||||
{
|
||||
@@ -183,9 +179,9 @@ namespace Barotrauma.Networking
|
||||
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
|
||||
outMsg.Write((byte)ClientPacketHeader.SERVER_SETTINGS);
|
||||
outMsg.WriteByte((byte)ClientPacketHeader.SERVER_SETTINGS);
|
||||
|
||||
outMsg.Write((byte)dataToSend);
|
||||
outMsg.WriteByte((byte)dataToSend);
|
||||
|
||||
if (dataToSend.HasFlag(NetFlags.Name))
|
||||
{
|
||||
@@ -193,7 +189,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
ServerName = GameMain.NetLobbyScreen.ServerName.Text;
|
||||
}
|
||||
outMsg.Write(ServerName);
|
||||
outMsg.WriteString(ServerName);
|
||||
}
|
||||
|
||||
if (dataToSend.HasFlag(NetFlags.Message))
|
||||
@@ -202,7 +198,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
ServerMessageText = GameMain.NetLobbyScreen.ServerMessage.Text;
|
||||
}
|
||||
outMsg.Write(ServerMessageText);
|
||||
outMsg.WriteString(ServerMessageText);
|
||||
}
|
||||
|
||||
if (dataToSend.HasFlag(NetFlags.Properties))
|
||||
@@ -214,18 +210,17 @@ namespace Barotrauma.Networking
|
||||
UInt32 count = (UInt32)changedProperties.Count();
|
||||
bool changedMonsterSettings = tempMonsterEnabled != null && tempMonsterEnabled.Any(p => p.Value != MonsterEnabled[p.Key]);
|
||||
|
||||
outMsg.Write(count);
|
||||
outMsg.WriteUInt32(count);
|
||||
foreach (KeyValuePair<UInt32, NetPropertyData> prop in changedProperties)
|
||||
{
|
||||
DebugConsole.NewMessage(prop.Value.Name.Value, Color.Lime);
|
||||
outMsg.Write(prop.Key);
|
||||
outMsg.WriteUInt32(prop.Key);
|
||||
prop.Value.Write(outMsg, prop.Value.GUIComponentValue);
|
||||
}
|
||||
|
||||
outMsg.Write(changedMonsterSettings); outMsg.WritePadBits();
|
||||
outMsg.WriteBoolean(changedMonsterSettings); outMsg.WritePadBits();
|
||||
if (changedMonsterSettings) WriteMonsterEnabled(outMsg, tempMonsterEnabled);
|
||||
BanList.ClientAdminWrite(outMsg);
|
||||
Whitelist.ClientAdminWrite(outMsg);
|
||||
}
|
||||
|
||||
if (dataToSend.HasFlag(NetFlags.HiddenSubs))
|
||||
@@ -237,23 +232,24 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
outMsg.WriteRangedInteger(missionTypeOr ?? (int)Barotrauma.MissionType.None, 0, (int)Barotrauma.MissionType.All);
|
||||
outMsg.WriteRangedInteger(missionTypeAnd ?? (int)Barotrauma.MissionType.All, 0, (int)Barotrauma.MissionType.All);
|
||||
outMsg.Write((byte)(traitorSetting + 1));
|
||||
outMsg.Write((byte)(botCount + 1));
|
||||
outMsg.Write((byte)(botSpawnMode + 1));
|
||||
outMsg.WriteByte((byte)(traitorSetting + 1));
|
||||
outMsg.WriteByte((byte)(botCount + 1));
|
||||
outMsg.WriteByte((byte)(botSpawnMode + 1));
|
||||
|
||||
outMsg.Write(levelDifficulty ?? -1000.0f);
|
||||
outMsg.WriteSingle(levelDifficulty ?? -1000.0f);
|
||||
|
||||
outMsg.Write(useRespawnShuttle ?? UseRespawnShuttle);
|
||||
outMsg.WriteBoolean(useRespawnShuttle != null);
|
||||
outMsg.WriteBoolean(useRespawnShuttle ?? false);
|
||||
|
||||
outMsg.Write(autoRestart != null);
|
||||
outMsg.Write(autoRestart ?? false);
|
||||
outMsg.WriteBoolean(autoRestart != null);
|
||||
outMsg.WriteBoolean(autoRestart ?? false);
|
||||
|
||||
outMsg.WritePadBits();
|
||||
}
|
||||
|
||||
if (dataToSend.HasFlag(NetFlags.LevelSeed))
|
||||
{
|
||||
outMsg.Write(GameMain.NetLobbyScreen.SeedBox.Text);
|
||||
outMsg.WriteString(GameMain.NetLobbyScreen.SeedBox.Text);
|
||||
}
|
||||
|
||||
GameMain.Client.ClientPeer.Send(outMsg, DeliveryMethod.Reliable);
|
||||
@@ -273,8 +269,7 @@ namespace Barotrauma.Networking
|
||||
General,
|
||||
Rounds,
|
||||
Antigriefing,
|
||||
Banlist,
|
||||
Whitelist
|
||||
Banlist
|
||||
}
|
||||
|
||||
private NetPropertyData GetPropertyData(string name)
|
||||
@@ -949,13 +944,6 @@ namespace Barotrauma.Networking
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
BanList.CreateBanFrame(settingsTabs[(int)SettingsTab.Banlist]);
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
// whitelist
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
Whitelist.CreateWhiteListFrame(settingsTabs[(int)SettingsTab.Whitelist]);
|
||||
Whitelist.localEnabled = Whitelist.Enabled;
|
||||
}
|
||||
|
||||
private void CreateLabeledSlider(GUIComponent parent, string labelTag, out GUIScrollBar slider, out GUITextBlock label)
|
||||
@@ -1067,15 +1055,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
settingsFrame = null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private ServerInfo cachedServerListInfo = null;
|
||||
public ServerInfo GetServerListInfo()
|
||||
{
|
||||
cachedServerListInfo ??= GameMain.ServerListScreen.UpdateServerInfoWithServerSettings(GameMain.Client.ClientPeer.ServerConnection, this);
|
||||
return cachedServerListInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using Microsoft.Xna.Framework;
|
||||
using OpenAL;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
@@ -22,13 +21,13 @@ namespace Barotrauma.Networking
|
||||
public static IReadOnlyList<string> CaptureDeviceNames =>
|
||||
Alc.GetStringList(IntPtr.Zero, OpenAL.Alc.CaptureDeviceSpecifier);
|
||||
|
||||
private IntPtr captureDevice;
|
||||
private readonly IntPtr captureDevice;
|
||||
|
||||
private Thread captureThread;
|
||||
|
||||
private bool capturing;
|
||||
|
||||
private OpusEncoder encoder;
|
||||
private readonly OpusEncoder encoder;
|
||||
|
||||
public double LastdB
|
||||
{
|
||||
@@ -53,7 +52,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
get
|
||||
{
|
||||
return GameMain.Client?.ID ?? 0;
|
||||
return GameMain.Client?.SessionId ?? 0;
|
||||
}
|
||||
protected set
|
||||
{
|
||||
@@ -82,7 +81,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
private VoipCapture(string deviceName) : base(GameMain.Client?.ID ?? 0, true, false)
|
||||
private VoipCapture(string deviceName) : base(GameMain.Client?.SessionId ?? 0, true, false)
|
||||
{
|
||||
Disconnected = false;
|
||||
|
||||
@@ -171,8 +170,8 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
IntPtr nativeBuffer;
|
||||
short[] uncompressedBuffer = new short[VoipConfig.BUFFER_SIZE];
|
||||
short[] prevUncompressedBuffer = new short[VoipConfig.BUFFER_SIZE];
|
||||
readonly short[] uncompressedBuffer = new short[VoipConfig.BUFFER_SIZE];
|
||||
readonly short[] prevUncompressedBuffer = new short[VoipConfig.BUFFER_SIZE];
|
||||
bool prevCaptured = true;
|
||||
int captureTimer;
|
||||
|
||||
@@ -227,15 +226,20 @@ namespace Barotrauma.Networking
|
||||
bool allowEnqueue = overrideSound != null;
|
||||
if (GameMain.WindowActive && SettingsMenu.Instance is null)
|
||||
{
|
||||
bool pttDown = PlayerInput.KeyDown(InputType.Voice) && GUI.KeyboardDispatcher.Subscriber == null;
|
||||
if (GameMain.LuaCs.Game.ForceVoice != null) { pttDown = GameMain.LuaCs.Game.ForceVoice.Value; }
|
||||
if (pttDown || captureTimer <= 0)
|
||||
{
|
||||
ForceLocal = GameMain.ActiveChatMode == ChatMode.Local;
|
||||
if (GameMain.LuaCs.Game.ForceLocalVoice != null) { ForceLocal = GameMain.LuaCs.Game.ForceLocalVoice.Value; }
|
||||
}
|
||||
bool usingLocalMode = PlayerInput.KeyDown(InputType.LocalVoice);
|
||||
bool usingRadioMode = PlayerInput.KeyDown(InputType.RadioVoice);
|
||||
if (GameSettings.CurrentConfig.Audio.VoiceSetting == VoiceMode.Activity)
|
||||
{
|
||||
bool pttDown = (usingLocalMode || usingRadioMode) && GUI.KeyboardDispatcher.Subscriber == null;
|
||||
if (pttDown)
|
||||
{
|
||||
ForceLocal = usingLocalMode;
|
||||
}
|
||||
//in Activity mode, we default to the active mode UNLESS a specific ptt key is held
|
||||
else
|
||||
{
|
||||
ForceLocal = GameMain.ActiveChatMode == ChatMode.Local;
|
||||
}
|
||||
if (dB > GameSettings.CurrentConfig.Audio.NoiseGateThreshold)
|
||||
{
|
||||
allowEnqueue = true;
|
||||
@@ -243,6 +247,13 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
else if (GameSettings.CurrentConfig.Audio.VoiceSetting == VoiceMode.PushToTalk)
|
||||
{
|
||||
//in push-to-talk mode, InputType.Voice uses the active chat mode
|
||||
bool usingActiveMode = PlayerInput.KeyDown(InputType.Voice);
|
||||
bool pttDown = (usingActiveMode || usingLocalMode || usingRadioMode) && GUI.KeyboardDispatcher.Subscriber == null;
|
||||
if (pttDown || captureTimer <= 0)
|
||||
{
|
||||
ForceLocal = (usingActiveMode && GameMain.ActiveChatMode == ChatMode.Local) || usingLocalMode;
|
||||
}
|
||||
if (pttDown)
|
||||
{
|
||||
allowEnqueue = true;
|
||||
|
||||
@@ -72,8 +72,8 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
|
||||
msg.Write((byte)ClientPacketHeader.VOICE);
|
||||
msg.Write((byte)VoipCapture.Instance.QueueID);
|
||||
msg.WriteByte((byte)ClientPacketHeader.VOICE);
|
||||
msg.WriteByte((byte)VoipCapture.Instance.QueueID);
|
||||
VoipCapture.Instance.Write(msg);
|
||||
|
||||
netClient.Send(msg, DeliveryMethod.Unreliable);
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -54,7 +56,7 @@ namespace Barotrauma
|
||||
voteCountMax[voteType] = value;
|
||||
}
|
||||
|
||||
public void UpdateVoteTexts(List<Client> clients, VoteType voteType)
|
||||
public void UpdateVoteTexts(IEnumerable<Client> clients, VoteType voteType)
|
||||
{
|
||||
switch (voteType)
|
||||
{
|
||||
@@ -92,7 +94,7 @@ namespace Barotrauma
|
||||
|
||||
private void SetVoteText(GUIListBox listBox, object userData, int votes)
|
||||
{
|
||||
if (userData == null) return;
|
||||
if (userData == null) { return; }
|
||||
foreach (GUIComponent comp in listBox.Content.Children)
|
||||
{
|
||||
if (comp.UserData != userData) { continue; }
|
||||
@@ -110,37 +112,54 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetVotes(IEnumerable<Client> connectedClients)
|
||||
{
|
||||
foreach (Client client in connectedClients)
|
||||
{
|
||||
client.ResetVotes();
|
||||
}
|
||||
|
||||
foreach (VoteType voteType in Enum.GetValues(typeof(VoteType)))
|
||||
{
|
||||
SetVoteCountYes(voteType, 0);
|
||||
SetVoteCountNo(voteType, 0);
|
||||
SetVoteCountMax(voteType, 0);
|
||||
}
|
||||
UpdateVoteTexts(connectedClients, VoteType.Mode);
|
||||
UpdateVoteTexts(connectedClients, VoteType.Sub);
|
||||
}
|
||||
|
||||
public void ClientWrite(IWriteMessage msg, VoteType voteType, object data)
|
||||
{
|
||||
msg.Write((byte)voteType);
|
||||
msg.WriteByte((byte)voteType);
|
||||
|
||||
switch (voteType)
|
||||
{
|
||||
case VoteType.Sub:
|
||||
if (!(data is SubmarineInfo sub)) { return; }
|
||||
msg.Write(sub.EqualityCheckVal);
|
||||
msg.WriteInt32(sub.EqualityCheckVal);
|
||||
if (sub.EqualityCheckVal == 0)
|
||||
{
|
||||
//sub doesn't exist client-side, use hash to let the server know which one we voted for
|
||||
msg.Write(sub.MD5Hash.StringRepresentation);
|
||||
msg.WriteString(sub.MD5Hash.StringRepresentation);
|
||||
}
|
||||
break;
|
||||
case VoteType.Mode:
|
||||
if (!(data is GameModePreset gameMode)) { return; }
|
||||
msg.Write(gameMode.Identifier);
|
||||
msg.WriteIdentifier(gameMode.Identifier);
|
||||
break;
|
||||
case VoteType.EndRound:
|
||||
if (!(data is bool)) { return; }
|
||||
msg.Write((bool)data);
|
||||
msg.WriteBoolean((bool)data);
|
||||
break;
|
||||
case VoteType.Kick:
|
||||
if (!(data is Client votedClient)) { return; }
|
||||
|
||||
msg.Write(votedClient.ID);
|
||||
msg.WriteByte(votedClient.SessionId);
|
||||
break;
|
||||
case VoteType.StartRound:
|
||||
if (!(data is bool)) { return; }
|
||||
msg.Write((bool)data);
|
||||
msg.WriteBoolean((bool)data);
|
||||
break;
|
||||
case VoteType.PurchaseAndSwitchSub:
|
||||
case VoteType.PurchaseSub:
|
||||
@@ -148,22 +167,22 @@ namespace Barotrauma
|
||||
if (data is (SubmarineInfo voteSub, bool transferItems))
|
||||
{
|
||||
//initiate sub vote
|
||||
msg.Write(true);
|
||||
msg.Write(voteSub.Name);
|
||||
msg.Write(transferItems);
|
||||
msg.WriteBoolean(true);
|
||||
msg.WriteString(voteSub.Name);
|
||||
msg.WriteBoolean(transferItems);
|
||||
}
|
||||
else
|
||||
{
|
||||
// vote
|
||||
if (!(data is int)) { return; }
|
||||
msg.Write(false);
|
||||
msg.Write((int)data);
|
||||
msg.WriteBoolean(false);
|
||||
msg.WriteInt32((int)data);
|
||||
}
|
||||
break;
|
||||
case VoteType.TransferMoney:
|
||||
if (!(data is int)) { return; }
|
||||
msg.Write(false); //not initiating a vote
|
||||
msg.Write((int)data);
|
||||
msg.WriteBoolean(false); //not initiating a vote
|
||||
msg.WriteInt32((int)data);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -233,21 +252,22 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError("Failed to cast vote type \"" + voteTypeByte + "\"", e);
|
||||
}
|
||||
|
||||
byte yesClientCount = inc.ReadByte();
|
||||
for (int i = 0; i < yesClientCount; i++)
|
||||
int readVote(int value)
|
||||
{
|
||||
byte clientID = inc.ReadByte();
|
||||
var matchingClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == clientID);
|
||||
matchingClient?.SetVote(voteType, 2);
|
||||
}
|
||||
byte clientCount = inc.ReadByte();
|
||||
for (int i = 0; i < clientCount; i++)
|
||||
{
|
||||
byte clientId = inc.ReadByte();
|
||||
var matchingClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.SessionId == clientId);
|
||||
matchingClient?.SetVote(voteType, value);
|
||||
}
|
||||
|
||||
byte noClientCount = inc.ReadByte();
|
||||
for (int i = 0; i < noClientCount; i++)
|
||||
{
|
||||
byte clientID = inc.ReadByte();
|
||||
var matchingClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == clientID);
|
||||
matchingClient?.SetVote(voteType, 1);
|
||||
return clientCount;
|
||||
}
|
||||
|
||||
int yesClientCount = readVote(value: 2);
|
||||
int noClientCount = readVote(value: 1);
|
||||
|
||||
byte maxClientCount = inc.ReadByte();
|
||||
|
||||
SetVoteCountYes(voteType, yesClientCount);
|
||||
@@ -258,10 +278,10 @@ namespace Barotrauma
|
||||
{
|
||||
case VoteState.Started:
|
||||
byte starterID = inc.ReadByte();
|
||||
Client starterClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == starterID);
|
||||
Client starterClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.SessionId == starterID);
|
||||
float timeOut = inc.ReadByte();
|
||||
|
||||
Client myClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == GameMain.Client.ID);
|
||||
Client myClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.SessionId == GameMain.Client.SessionId);
|
||||
if (myClient == null || !myClient.InGame) { return; }
|
||||
|
||||
switch (voteType)
|
||||
@@ -284,8 +304,8 @@ namespace Barotrauma
|
||||
byte toClientId = inc.ReadByte();
|
||||
int transferAmount = inc.ReadInt32();
|
||||
|
||||
Client fromClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == fromClientId);
|
||||
Client toClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == toClientId);
|
||||
Client fromClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.SessionId == fromClientId);
|
||||
Client toClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.SessionId == toClientId);
|
||||
GameMain.Client.ShowMoneyTransferVoteInterface(starterClient, fromClient, transferAmount, toClient, timeOut);
|
||||
break;
|
||||
}
|
||||
@@ -343,8 +363,8 @@ namespace Barotrauma
|
||||
byte readyClientCount = inc.ReadByte();
|
||||
for (int i = 0; i < readyClientCount; i++)
|
||||
{
|
||||
byte clientID = inc.ReadByte();
|
||||
var matchingClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.ID == clientID);
|
||||
byte clientId = inc.ReadByte();
|
||||
var matchingClient = GameMain.NetworkMember.ConnectedClients.Find(c => c.SessionId == clientId);
|
||||
matchingClient?.SetVote(VoteType.StartRound, true);
|
||||
}
|
||||
UpdateVoteTexts(GameMain.NetworkMember.ConnectedClients, VoteType.StartRound);
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
partial class WhiteListedPlayer
|
||||
{
|
||||
public WhiteListedPlayer(string name, UInt16 identifier, string ip)
|
||||
{
|
||||
Name = name;
|
||||
IP = ip;
|
||||
|
||||
UniqueIdentifier = identifier;
|
||||
}
|
||||
}
|
||||
|
||||
partial class WhiteList
|
||||
{
|
||||
private GUIComponent whitelistFrame;
|
||||
|
||||
private GUITextBox nameBox;
|
||||
private GUITextBox ipBox;
|
||||
private GUIButton addNewButton;
|
||||
|
||||
public class LocalAdded
|
||||
{
|
||||
public string Name;
|
||||
public string IP;
|
||||
};
|
||||
|
||||
public bool localEnabled;
|
||||
public List<UInt16> localRemoved = new List<UInt16>();
|
||||
public List<LocalAdded> localAdded = new List<LocalAdded>();
|
||||
|
||||
public GUIComponent CreateWhiteListFrame(GUIComponent parent)
|
||||
{
|
||||
if (whitelistFrame != null)
|
||||
{
|
||||
whitelistFrame.Parent.ClearChildren();
|
||||
whitelistFrame = null;
|
||||
}
|
||||
|
||||
whitelistFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), parent.RectTransform, Anchor.Center))
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.02f
|
||||
};
|
||||
|
||||
var enabledTick = new GUITickBox(new RectTransform(new Vector2(0.1f, 0.1f), whitelistFrame.RectTransform), TextManager.Get("WhiteListEnabled"))
|
||||
{
|
||||
Selected = localEnabled,
|
||||
UpdateOrder = 1,
|
||||
OnSelected = (GUITickBox box) =>
|
||||
{
|
||||
nameBox.Enabled = box.Selected;
|
||||
ipBox.Enabled = box.Selected;
|
||||
addNewButton.Enabled = box.Selected && !string.IsNullOrEmpty(ipBox.Text) && !string.IsNullOrEmpty(nameBox.Text);
|
||||
localEnabled = box.Selected;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
var listBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.7f), whitelistFrame.RectTransform));
|
||||
foreach (WhiteListedPlayer wlp in whitelistedPlayers)
|
||||
{
|
||||
if (localRemoved.Contains(wlp.UniqueIdentifier)) continue;
|
||||
string blockText = wlp.Name;
|
||||
if (!string.IsNullOrWhiteSpace(wlp.IP)) blockText += " (" + wlp.IP + ")";
|
||||
GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), listBox.Content.RectTransform),
|
||||
blockText)
|
||||
{
|
||||
UserData = wlp
|
||||
};
|
||||
|
||||
var removeButton = new GUIButton(new RectTransform(new Vector2(0.3f, 0.8f), textBlock.RectTransform, Anchor.CenterRight),
|
||||
TextManager.Get("WhiteListRemove"), style: "GUIButtonSmall")
|
||||
{
|
||||
UserData = wlp,
|
||||
OnClicked = RemoveFromWhiteList
|
||||
};
|
||||
}
|
||||
|
||||
foreach (LocalAdded lad in localAdded)
|
||||
{
|
||||
string blockText = lad.Name;
|
||||
if (!string.IsNullOrWhiteSpace(lad.IP)) blockText += " (" + lad.IP + ")";
|
||||
GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), listBox.Content.RectTransform),
|
||||
blockText)
|
||||
{
|
||||
UserData = lad
|
||||
};
|
||||
|
||||
var removeButton = new GUIButton(new RectTransform(new Vector2(0.3f, 0.8f), textBlock.RectTransform, Anchor.CenterRight),
|
||||
TextManager.Get("WhiteListRemove"), style: "GUIButtonSmall")
|
||||
{
|
||||
UserData = lad,
|
||||
OnClicked = RemoveFromWhiteList
|
||||
};
|
||||
}
|
||||
|
||||
foreach (GUIComponent c in listBox.Content.Children)
|
||||
{
|
||||
c.RectTransform.MinSize = new Point(0, Math.Max((int)(20 * GUI.Scale), c.RectTransform.Children.Max(c2 => c2.MinSize.Y)));
|
||||
}
|
||||
|
||||
var nameArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), whitelistFrame.RectTransform), isHorizontal: true)
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.05f
|
||||
};
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.3f, 1.0f), nameArea.RectTransform), TextManager.Get("WhiteListName"));
|
||||
nameBox = new GUITextBox(new RectTransform(new Vector2(0.7f, 1.0f), nameArea.RectTransform), "");
|
||||
nameBox.OnTextChanged += (textBox, text) =>
|
||||
{
|
||||
addNewButton.Enabled = !string.IsNullOrEmpty(ipBox.Text) && !string.IsNullOrEmpty(nameBox.Text);
|
||||
return true;
|
||||
};
|
||||
nameArea.RectTransform.MinSize = new Point(0, nameBox.RectTransform.MinSize.Y);
|
||||
|
||||
var ipArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), whitelistFrame.RectTransform), isHorizontal: true)
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.05f
|
||||
};
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.3f, 1.0f), ipArea.RectTransform), TextManager.Get("WhiteListIP"));
|
||||
ipBox = new GUITextBox(new RectTransform(new Vector2(0.7f, 1.0f), ipArea.RectTransform), "");
|
||||
ipBox.OnTextChanged += (textBox, text) =>
|
||||
{
|
||||
addNewButton.Enabled = !string.IsNullOrEmpty(ipBox.Text) && !string.IsNullOrEmpty(nameBox.Text);
|
||||
return true;
|
||||
};
|
||||
ipBox.RectTransform.MinSize = new Point(0, ipBox.RectTransform.MinSize.Y);
|
||||
|
||||
addNewButton = new GUIButton(new RectTransform(new Vector2(0.5f, 0.1f), whitelistFrame.RectTransform), TextManager.Get("WhiteListAdd"), style: "GUIButtonSmall")
|
||||
{
|
||||
OnClicked = AddToWhiteList
|
||||
};
|
||||
GUITextBlock.AutoScaleAndNormalize(addNewButton.TextBlock);
|
||||
|
||||
nameBox.Enabled = localEnabled;
|
||||
ipBox.Enabled = localEnabled;
|
||||
addNewButton.Enabled = false;
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
private bool RemoveFromWhiteList(GUIButton button, object obj)
|
||||
{
|
||||
if (obj is WhiteListedPlayer)
|
||||
{
|
||||
if (!(obj is WhiteListedPlayer wlp)) return false;
|
||||
if (!localRemoved.Contains(wlp.UniqueIdentifier)) localRemoved.Add(wlp.UniqueIdentifier);
|
||||
}
|
||||
else if (obj is LocalAdded)
|
||||
{
|
||||
if (!(obj is LocalAdded lad)) return false;
|
||||
if (localAdded.Contains(lad)) localAdded.Remove(lad);
|
||||
}
|
||||
|
||||
if (whitelistFrame != null)
|
||||
{
|
||||
CreateWhiteListFrame(whitelistFrame.Parent);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool AddToWhiteList(GUIButton button, object obj)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(nameBox.Text)) return false;
|
||||
if (whitelistedPlayers.Any(x => x.Name.ToLower() == nameBox.Text.ToLower() && x.IP == ipBox.Text)) return false;
|
||||
|
||||
if (!localAdded.Any(p => p.IP == ipBox.Text)) localAdded.Add(new LocalAdded() { Name = nameBox.Text, IP = ipBox.Text });
|
||||
|
||||
if (whitelistFrame != null)
|
||||
{
|
||||
CreateWhiteListFrame(whitelistFrame.Parent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ClientAdminRead(IReadMessage incMsg)
|
||||
{
|
||||
bool hasPermission = incMsg.ReadBoolean();
|
||||
if (!hasPermission)
|
||||
{
|
||||
incMsg.ReadPadBits();
|
||||
return;
|
||||
}
|
||||
|
||||
bool isOwner = incMsg.ReadBoolean();
|
||||
localEnabled = incMsg.ReadBoolean();
|
||||
Enabled = localEnabled;
|
||||
incMsg.ReadPadBits();
|
||||
|
||||
whitelistedPlayers.Clear();
|
||||
UInt32 bannedPlayerCount = incMsg.ReadVariableUInt32();
|
||||
for (int i = 0; i < (int)bannedPlayerCount; i++)
|
||||
{
|
||||
string name = incMsg.ReadString();
|
||||
UInt16 uniqueIdentifier = incMsg.ReadUInt16();
|
||||
|
||||
string ip = "";
|
||||
if (isOwner)
|
||||
{
|
||||
ip = incMsg.ReadString();
|
||||
}
|
||||
else
|
||||
{
|
||||
ip = "IP concealed by host";
|
||||
}
|
||||
whitelistedPlayers.Add(new WhiteListedPlayer(name, uniqueIdentifier, ip));
|
||||
}
|
||||
|
||||
if (whitelistFrame != null)
|
||||
{
|
||||
CreateWhiteListFrame(whitelistFrame.Parent);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClientAdminWrite(IWriteMessage outMsg)
|
||||
{
|
||||
outMsg.Write(localEnabled);
|
||||
outMsg.WritePadBits();
|
||||
|
||||
outMsg.Write((UInt16)localRemoved.Count);
|
||||
foreach (UInt16 uniqueId in localRemoved)
|
||||
{
|
||||
outMsg.Write(uniqueId);
|
||||
}
|
||||
|
||||
outMsg.Write((UInt16)localAdded.Count);
|
||||
foreach (LocalAdded lad in localAdded)
|
||||
{
|
||||
outMsg.Write(lad.Name);
|
||||
outMsg.Write(lad.IP); //TODO: ENCRYPT
|
||||
}
|
||||
|
||||
localRemoved.Clear();
|
||||
localAdded.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user