(61d00a474) v0.9.7.1

This commit is contained in:
Regalis
2020-03-04 13:04:10 +01:00
parent 3c50efa5c9
commit 3c09ebe02f
5086 changed files with 786063 additions and 295871 deletions
@@ -0,0 +1,200 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma.Networking
{
partial class BannedPlayer
{
public BannedPlayer(string name, UInt16 uniqueIdentifier, bool isRangeBan, string ip, ulong steamID)
{
this.Name = name;
this.SteamID = steamID;
this.IsRangeBan = isRangeBan;
this.IP = ip;
this.UniqueIdentifier = uniqueIdentifier;
}
}
public partial class BanList
{
private GUIComponent banFrame;
public GUIComponent BanFrame
{
get { return banFrame; }
}
public List<UInt16> localRemovedBans = new List<UInt16>();
public List<UInt16> localRangeBans = new List<UInt16>();
private void RecreateBanFrame()
{
if (banFrame != null)
{
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));
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) })
{
UserData = banFrame
};
var paddedPlayerFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.85f), playerFrame.RectTransform, Anchor.Center))
{
Stretch = true,
RelativeSpacing = 0.05f,
CanBeFocused = true
};
var topArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.0f), paddedPlayerFrame.RectTransform),
isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
Stretch = true,
RelativeSpacing = 0.02f
};
string ip = bannedPlayer.IP;
if (localRangeBans.Contains(bannedPlayer.UniqueIdentifier)) ip = ToRange(ip);
GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), topArea.RectTransform),
bannedPlayer.Name + " (" + ip + ")");
textBlock.RectTransform.MinSize = new Point(textBlock.Rect.Width, 0);
if (bannedPlayer.IP.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));
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: GUI.SmallFont);
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),
font: GUI.SmallFont, wrap: true)
{
ToolTip = bannedPlayer.Reason
};
paddedPlayerFrame.Recalculate();
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), ((GUIListBox)banFrame).Content.RectTransform), style: "HorizontalLine");
}
return banFrame;
}
private bool RemoveBan(GUIButton button, object obj)
{
BannedPlayer banned = obj as BannedPlayer;
if (banned == null) { return false; }
localRemovedBans.Add(banned.UniqueIdentifier);
RecreateBanFrame();
GameMain.Client?.ServerSettings?.ClientAdminWrite(ServerSettings.NetFlags.Properties);
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)
{
bool hasPermission = incMsg.ReadBoolean();
if (!hasPermission)
{
incMsg.ReadPadBits();
return;
}
bool isOwner = incMsg.ReadBoolean();
incMsg.ReadPadBits();
bannedPlayers.Clear();
UInt32 bannedPlayerCount = incMsg.ReadVariableUInt32();
for (int i = 0; i < (int)bannedPlayerCount; i++)
{
string name = incMsg.ReadString();
UInt16 uniqueIdentifier = incMsg.ReadUInt16();
bool isRangeBan = incMsg.ReadBoolean(); incMsg.ReadPadBits();
string ip = "";
UInt64 steamID = 0;
if (isOwner)
{
ip = incMsg.ReadString();
steamID = incMsg.ReadUInt64();
}
else
{
ip = "IP concealed by host";
steamID = 0;
}
bannedPlayers.Add(new BannedPlayer(name, uniqueIdentifier, isRangeBan, ip, steamID));
}
if (banFrame != null)
{
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.Write(uniqueId);
}
outMsg.Write((UInt16)localRangeBans.Count);
foreach (UInt16 uniqueId in localRangeBans)
{
outMsg.Write(uniqueId);
}
localRemovedBans.Clear();
localRangeBans.Clear();
}
}
}
@@ -0,0 +1,124 @@
using System;
using System.Linq;
namespace Barotrauma.Networking
{
partial class ChatMessage
{
public virtual void ClientWrite(IWriteMessage msg)
{
msg.Write((byte)ClientNetObject.CHAT_MESSAGE);
msg.Write(NetStateID);
msg.Write((byte)Type);
msg.Write(Text);
}
public static void ClientRead(IReadMessage msg)
{
UInt16 ID = msg.ReadUInt16();
ChatMessageType type = (ChatMessageType)msg.ReadByte();
string txt = "";
if (type != ChatMessageType.Order)
{
txt = msg.ReadString();
}
string senderName = msg.ReadString();
Character senderCharacter = null;
bool hasSenderCharacter = msg.ReadBoolean();
if (hasSenderCharacter)
{
senderCharacter = Entity.FindEntityByID(msg.ReadUInt16()) as Character;
if (senderCharacter != null)
{
senderName = senderCharacter.Name;
}
}
if (type == ChatMessageType.ServerMessageBox)
{
txt = TextManager.GetServerMessage(txt);
}
else if (type == ChatMessageType.Order)
{
int orderIndex = msg.ReadByte();
UInt16 targetCharacterID = msg.ReadUInt16();
Character targetCharacter = Entity.FindEntityByID(targetCharacterID) as Character;
Entity targetEntity = Entity.FindEntityByID(msg.ReadUInt16());
int optionIndex = msg.ReadByte();
Order order = null;
if (orderIndex < 0 || orderIndex >= Order.PrefabList.Count)
{
DebugConsole.ThrowError("Invalid order message - order index out of bounds.");
if (NetIdUtils.IdMoreRecent(ID, LastID)) LastID = ID;
return;
}
else
{
order = Order.PrefabList[orderIndex];
}
string orderOption = "";
if (optionIndex >= 0 && optionIndex < order.Options.Length)
{
orderOption = order.Options[optionIndex];
}
txt = order.GetChatMessage(targetCharacter?.Name, senderCharacter?.CurrentHull?.DisplayName, givingOrderToSelf: targetCharacter == senderCharacter, orderOption: orderOption);
if (GameMain.Client.GameStarted && Screen.Selected == GameMain.GameScreen)
{
if (order.TargetAllCharacters)
{
GameMain.GameSession?.CrewManager?.AddOrder(
new Order(order.Prefab, targetEntity, (targetEntity as Item)?.Components.FirstOrDefault(ic => ic.GetType() == order.ItemComponentType), orderGiver: senderCharacter),
order.Prefab.FadeOutTime);
}
else if (targetCharacter != null)
{
targetCharacter.SetOrder(
new Order(order.Prefab, targetEntity, (targetEntity as Item)?.Components.FirstOrDefault(ic => ic.GetType() == order.ItemComponentType), orderGiver: senderCharacter),
orderOption, senderCharacter);
}
}
if (NetIdUtils.IdMoreRecent(ID, LastID))
{
GameMain.Client.AddChatMessage(
new OrderChatMessage(order, orderOption, txt, targetEntity, targetCharacter, senderCharacter));
LastID = ID;
}
return;
}
if (NetIdUtils.IdMoreRecent(ID, LastID))
{
switch (type)
{
case ChatMessageType.MessageBox:
case ChatMessageType.ServerMessageBox:
//only show the message box if the text differs from the text in the currently visible box
if ((GUIMessageBox.VisibleBox as GUIMessageBox)?.Text?.Text != txt)
{
new GUIMessageBox("", txt);
}
break;
case ChatMessageType.Console:
DebugConsole.NewMessage(txt, MessageColor[(int)ChatMessageType.Console]);
break;
case ChatMessageType.ServerLog:
if (!Enum.TryParse(senderName, out ServerLog.MessageType messageType))
{
return;
}
GameMain.Client.ServerSettings.ServerLog?.WriteLine(txt, messageType);
break;
default:
GameMain.Client.AddChatMessage(txt, type, senderName, senderCharacter);
break;
}
LastID = ID;
}
}
}
}
@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Barotrauma.Networking
{
static partial class ChildServerRelay
{
public static Process Process;
private static bool localHandlesDisposed;
private static AnonymousPipeServerStream writePipe;
private static AnonymousPipeServerStream readPipe;
public static void Start(ProcessStartInfo processInfo)
{
writePipe = new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.Inheritable);
readPipe = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable);
writeStream = writePipe; readStream = readPipe;
PrivateStart();
processInfo.Arguments += " -pipes " + writePipe.GetClientHandleAsString() + " " + readPipe.GetClientHandleAsString();
Process = Process.Start(processInfo);
localHandlesDisposed = false;
}
public static void DisposeLocalHandles()
{
if (localHandlesDisposed) { return; }
writePipe.DisposeLocalCopyOfClientHandle(); readPipe.DisposeLocalCopyOfClientHandle();
localHandlesDisposed = true;
}
public static void ShutDown()
{
Process?.Kill(); Process = null;
writePipe = null; readPipe = null;
PrivateShutDown();
}
}
}
@@ -0,0 +1,152 @@
using Barotrauma.Sounds;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma.Networking
{
struct TempClient
{
public string Name;
public string PreferredJob;
public UInt16 NameID;
public UInt64 SteamID;
public byte ID;
public UInt16 CharacterID;
public bool Muted;
public bool AllowKicking;
}
partial class Client : IDisposable
{
public VoipSound VoipSound
{
get;
set;
}
private bool mutedLocally;
public bool MutedLocally
{
get { return mutedLocally; }
set
{
if (mutedLocally == value) { return; }
mutedLocally = value;
#if CLIENT
GameMain.NetLobbyScreen.SetPlayerVoiceIconState(this, muted, mutedLocally);
GameMain.GameSession?.CrewManager?.SetPlayerVoiceIconState(this, muted, mutedLocally);
#endif
}
}
public bool AllowKicking;
public void UpdateSoundPosition()
{
if (VoipSound == null) { return; }
if (!VoipSound.IsPlaying)
{
DebugConsole.Log("Destroying voipsound");
VoipSound.Dispose();
VoipSound = null;
return;
}
if (character != null)
{
if (GameMain.Config.UseDirectionalVoiceChat)
{
VoipSound.SetPosition(new Vector3(character.WorldPosition.X, character.WorldPosition.Y, 0.0f));
}
else
{
VoipSound.SetPosition(null);
float dist = Vector3.Distance(new Vector3(character.WorldPosition, 0.0f), GameMain.SoundManager.ListenerPosition);
VoipSound.Gain = 1.0f - MathUtils.InverseLerp(VoipSound.Near, VoipSound.Far, dist);
}
}
else
{
VoipSound.SetPosition(null);
VoipSound.Gain = 1.0f;
}
}
partial void InitProjSpecific()
{
VoipQueue = null; VoipSound = null;
if (ID == GameMain.Client.ID) return;
VoipQueue = new VoipQueue(ID, false, true);
GameMain.Client?.VoipClient?.RegisterQueue(VoipQueue);
VoipSound = null;
}
public void SetPermissions(ClientPermissions permissions, List<string> permittedConsoleCommands)
{
List<DebugConsole.Command> permittedCommands = new List<DebugConsole.Command>();
foreach (string commandName in permittedConsoleCommands)
{
var consoleCommand = DebugConsole.Commands.Find(c => c.names.Contains(commandName));
if (consoleCommand != null)
{
permittedCommands.Add(consoleCommand);
}
}
SetPermissions(permissions, permittedCommands);
}
public void SetPermissions(ClientPermissions permissions, List<DebugConsole.Command> permittedConsoleCommands)
{
if (GameMain.Client == null)
{
return;
}
Permissions = permissions;
PermittedConsoleCommands = new List<DebugConsole.Command>(permittedConsoleCommands);
}
public void GivePermission(ClientPermissions permission)
{
if (GameMain.Client == null || !GameMain.Client.HasPermission(ClientPermissions.ManagePermissions))
{
return;
}
if (!Permissions.HasFlag(permission)) Permissions |= permission;
}
public void RemovePermission(ClientPermissions permission)
{
if (GameMain.Client == null || !GameMain.Client.HasPermission(ClientPermissions.ManagePermissions))
{
return;
}
if (Permissions.HasFlag(permission)) Permissions &= ~permission;
}
public bool HasPermission(ClientPermissions permission)
{
if (GameMain.Client == null)
{
return false;
}
return Permissions.HasFlag(permission);
}
partial void DisposeProjSpecific()
{
if (VoipQueue != null)
{
GameMain.Client.VoipClient.UnregisterQueue(VoipQueue);
}
if (VoipSound != null)
{
VoipSound.Dispose();
VoipSound = null;
}
}
}
}
@@ -0,0 +1,43 @@
using Barotrauma.Networking;
namespace Barotrauma
{
partial class EntitySpawner : Entity, IServerSerializable
{
public void ClientRead(ServerNetObject type, IReadMessage message, float sendingTime)
{
bool remove = message.ReadBoolean();
if (remove)
{
ushort entityId = message.ReadUInt16();
var entity = FindEntityByID(entityId);
if (entity != null)
{
DebugConsole.Log("Received entity removal message for \"" + entity.ToString() + "\".");
entity.Remove();
}
else
{
DebugConsole.Log("Received entity removal message for ID " + entityId + ". Entity with a matching ID not found.");
}
}
else
{
switch (message.ReadByte())
{
case (byte)SpawnableType.Item:
Item.ReadSpawnData(message, true);
break;
case (byte)SpawnableType.Character:
Character.ReadSpawnData(message);
break;
default:
DebugConsole.ThrowError("Received invalid entity spawn message (unknown spawnable type)");
break;
}
}
}
}
}
@@ -0,0 +1,531 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Xml;
namespace Barotrauma.Networking
{
class FileReceiver
{
public class FileTransferIn : IDisposable
{
public string FileName
{
get;
private set;
}
public string FilePath
{
get;
private set;
}
public int FileSize
{
get;
set;
}
public int Received
{
get;
private set;
}
public FileTransferType FileType
{
get;
private set;
}
public FileTransferStatus Status
{
get;
set;
}
public float BytesPerSecond
{
get;
private set;
}
public float Progress
{
get { return Received / (float)FileSize; }
}
public FileStream WriteStream
{
get;
private set;
}
public int TimeStarted
{
get;
private set;
}
public NetworkConnection Connection
{
get;
private set;
}
public int ID;
public FileTransferIn(NetworkConnection connection, string filePath, FileTransferType fileType)
{
FilePath = filePath;
FileName = Path.GetFileName(FilePath);
FileType = fileType;
Connection = connection;
Status = FileTransferStatus.NotStarted;
}
public void OpenStream()
{
if (WriteStream != null)
{
WriteStream.Flush();
WriteStream.Close();
WriteStream.Dispose();
WriteStream = null;
}
WriteStream = new FileStream(FilePath, FileMode.Create, FileAccess.Write, FileShare.None);
TimeStarted = Environment.TickCount;
}
public void ReadBytes(IReadMessage inc, int bytesToRead)
{
if (Received + bytesToRead > FileSize)
{
//strip out excess bytes
bytesToRead -= Received + bytesToRead - FileSize;
}
byte[] all = inc.ReadBytes(bytesToRead);
Received += all.Length;
WriteStream.Write(all, 0, all.Length);
int passed = Environment.TickCount - TimeStarted;
float psec = passed / 1000.0f;
if (GameSettings.VerboseLogging)
{
DebugConsole.Log("Received " + all.Length + " bytes of the file " + FileName + " (" + Received + "/" + FileSize + " received)");
}
BytesPerSecond = Received / psec;
Status = Received >= FileSize ? FileTransferStatus.Finished : FileTransferStatus.Receiving;
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (disposed) return;
if (disposing)
{
if (WriteStream != null)
{
WriteStream.Flush();
WriteStream.Close();
WriteStream.Dispose();
WriteStream = null;
}
}
disposed = true;
}
public void Dispose()
{
Dispose(true);
}
}
const int MaxFileSize = 50000000; //50 MB
public delegate void TransferInDelegate(FileTransferIn fileStreamReceiver);
public TransferInDelegate OnFinished;
public TransferInDelegate OnTransferFailed;
private readonly List<FileTransferIn> activeTransfers;
private readonly List<Pair<int, double>> finishedTransfers;
private readonly Dictionary<FileTransferType, string> downloadFolders = new Dictionary<FileTransferType, string>()
{
{ FileTransferType.Submarine, SaveUtil.SubmarineDownloadFolder },
{ FileTransferType.CampaignSave, SaveUtil.CampaignDownloadFolder }
};
public List<FileTransferIn> ActiveTransfers
{
get { return activeTransfers; }
}
public FileReceiver()
{
activeTransfers = new List<FileTransferIn>();
finishedTransfers = new List<Pair<int, double>>();
}
public void ReadMessage(IReadMessage inc)
{
System.Diagnostics.Debug.Assert(!activeTransfers.Any(t =>
t.Status == FileTransferStatus.Error ||
t.Status == FileTransferStatus.Canceled ||
t.Status == FileTransferStatus.Finished), "List of active file transfers contains entires that should have been removed");
byte transferMessageType = inc.ReadByte();
switch (transferMessageType)
{
case (byte)FileTransferMessageType.Initiate:
{
byte transferId = inc.ReadByte();
var existingTransfer = activeTransfers.Find(t => t.ID == transferId);
finishedTransfers.RemoveAll(t => t.First == transferId);
byte fileType = inc.ReadByte();
//ushort chunkLen = inc.ReadUInt16();
int fileSize = inc.ReadInt32();
string fileName = inc.ReadString();
if (existingTransfer != null)
{
if (fileType != (byte)existingTransfer.FileType ||
fileSize != existingTransfer.FileSize ||
fileName != existingTransfer.FileName)
{
GameMain.Client.CancelFileTransfer(transferId);
DebugConsole.ThrowError("File transfer error: file transfer initiated with an ID that's already in use");
}
else //resend acknowledgement packet
{
GameMain.Client.UpdateFileTransfer(transferId, 0);
}
return;
}
if (!ValidateInitialData(fileType, fileName, fileSize, out string errorMsg))
{
GameMain.Client.CancelFileTransfer(transferId);
DebugConsole.ThrowError("File transfer failed (" + errorMsg + ")");
return;
}
if (GameSettings.VerboseLogging)
{
DebugConsole.Log("Received file transfer initiation message: ");
DebugConsole.Log(" File: " + fileName);
DebugConsole.Log(" Size: " + fileSize);
DebugConsole.Log(" ID: " + transferId);
}
string downloadFolder = downloadFolders[(FileTransferType)fileType];
if (!Directory.Exists(downloadFolder))
{
try
{
Directory.CreateDirectory(downloadFolder);
}
catch (Exception e)
{
DebugConsole.ThrowError("Could not start a file transfer: failed to create the folder \"" + downloadFolder + "\".", e);
return;
}
}
FileTransferIn newTransfer = new FileTransferIn(inc.Sender, Path.Combine(downloadFolder, fileName), (FileTransferType)fileType)
{
ID = transferId,
Status = FileTransferStatus.Receiving,
FileSize = fileSize
};
int maxRetries = 4;
for (int i = 0; i <= maxRetries; i++)
{
try
{
newTransfer.OpenStream();
}
catch (IOException e)
{
if (i < maxRetries)
{
DebugConsole.NewMessage("Failed to initiate a file transfer {" + e.Message + "}, retrying in 250 ms...", Color.Red);
Thread.Sleep(250);
}
else
{
DebugConsole.NewMessage("Failed to initiate a file transfer {" + e.Message + "}", Color.Red);
GameMain.Client.CancelFileTransfer(transferId);
newTransfer.Status = FileTransferStatus.Error;
OnTransferFailed(newTransfer);
return;
}
}
}
activeTransfers.Add(newTransfer);
GameMain.Client.UpdateFileTransfer(transferId, 0); //send acknowledgement packet
}
break;
case (byte)FileTransferMessageType.TransferOnSameMachine:
{
byte transferId = inc.ReadByte();
byte fileType = inc.ReadByte();
string filePath = inc.ReadString();
if (GameSettings.VerboseLogging)
{
DebugConsole.Log("Received file transfer message on the same machine: ");
DebugConsole.Log(" File: " + filePath);
DebugConsole.Log(" ID: " + transferId);
}
if (!File.Exists(filePath))
{
DebugConsole.ThrowError("File transfer on the same machine failed, file \"" + filePath + "\" not found.");
GameMain.Client.CancelFileTransfer(transferId);
return;
}
FileTransferIn directTransfer = new FileTransferIn(inc.Sender, filePath, (FileTransferType)fileType)
{
ID = transferId,
Status = FileTransferStatus.Finished,
FileSize = 0
};
Md5Hash.RemoveFromCache(directTransfer.FilePath);
OnFinished(directTransfer);
}
break;
case (byte)FileTransferMessageType.Data:
{
byte transferId = inc.ReadByte();
var activeTransfer = activeTransfers.Find(t => t.Connection == inc.Sender && t.ID == transferId);
if (activeTransfer == null)
{
//it's possible for the server to send some extra data
//before it acknowledges that the download is finished,
//so let's suppress the error message in that case
finishedTransfers.RemoveAll(t => t.Second + 5.0 < Timing.TotalTime);
if (!finishedTransfers.Any(t => t.First == transferId))
{
GameMain.Client.CancelFileTransfer(transferId);
DebugConsole.ThrowError("File transfer error: received data without a transfer initiation message");
}
return;
}
int offset = inc.ReadInt32();
if (offset != activeTransfer.Received)
{
if (offset < activeTransfer.Received)
{
GameMain.Client.UpdateFileTransfer(activeTransfer.ID, activeTransfer.Received);
}
return;
}
int bytesToRead = inc.ReadUInt16();
if (activeTransfer.Received + bytesToRead > activeTransfer.FileSize)
{
GameMain.Client.CancelFileTransfer(transferId);
DebugConsole.ThrowError("File transfer error: Received more data than expected (total received: " + activeTransfer.Received +
", msg received: " + (inc.LengthBytes - inc.BytePosition) +
", msg length: " + inc.LengthBytes +
", msg read: " + inc.BytePosition +
", filesize: " + activeTransfer.FileSize);
activeTransfer.Status = FileTransferStatus.Error;
StopTransfer(activeTransfer);
return;
}
try
{
activeTransfer.ReadBytes(inc, bytesToRead);
}
catch (Exception e)
{
GameMain.Client.CancelFileTransfer(transferId);
DebugConsole.ThrowError("File transfer error: " + e.Message);
activeTransfer.Status = FileTransferStatus.Error;
StopTransfer(activeTransfer, true);
return;
}
if (activeTransfer.Status == FileTransferStatus.Finished)
{
GameMain.Client.UpdateFileTransfer(activeTransfer.ID, activeTransfer.Received, true);
activeTransfer.Dispose();
if (ValidateReceivedData(activeTransfer, out string errorMessage))
{
finishedTransfers.Add(new Pair<int, double>(transferId, Timing.TotalTime));
StopTransfer(activeTransfer);
Md5Hash.RemoveFromCache(activeTransfer.FilePath);
OnFinished(activeTransfer);
}
else
{
new GUIMessageBox("File transfer aborted", errorMessage);
activeTransfer.Status = FileTransferStatus.Error;
StopTransfer(activeTransfer, true);
}
}
}
break;
case (byte)FileTransferMessageType.Cancel:
{
byte transferId = inc.ReadByte();
var matchingTransfer = activeTransfers.Find(t => t.Connection == inc.Sender && t.ID == transferId);
if (matchingTransfer != null)
{
new GUIMessageBox("File transfer cancelled", "The server has cancelled the transfer of the file \"" + matchingTransfer.FileName + "\".");
StopTransfer(matchingTransfer);
}
break;
}
}
}
private bool ValidateInitialData(byte type, string fileName, int fileSize, out string errorMessage)
{
errorMessage = "";
if (fileSize > MaxFileSize)
{
errorMessage = "File too large (" + MathUtils.GetBytesReadable(fileSize) + ")";
return false;
}
if (!Enum.IsDefined(typeof(FileTransferType), (int)type))
{
errorMessage = "Unknown file type";
return false;
}
if (string.IsNullOrEmpty(fileName) ||
fileName.IndexOfAny(Path.GetInvalidFileNameChars()) > -1)
{
errorMessage = "Illegal characters in file name ''" + fileName + "''";
return false;
}
switch (type)
{
case (byte)FileTransferType.Submarine:
if (Path.GetExtension(fileName) != ".sub")
{
errorMessage = "Wrong file extension ''" + Path.GetExtension(fileName) + "''! (Expected .sub)";
return false;
}
break;
case (byte)FileTransferType.CampaignSave:
if (Path.GetExtension(fileName) != ".save")
{
errorMessage = "Wrong file extension ''" + Path.GetExtension(fileName) + "''! (Expected .save)";
return false;
}
break;
}
return true;
}
private bool ValidateReceivedData(FileTransferIn fileTransfer, out string ErrorMessage)
{
ErrorMessage = "";
switch (fileTransfer.FileType)
{
case FileTransferType.Submarine:
Stream stream;
try
{
stream = SaveUtil.DecompressFiletoStream(fileTransfer.FilePath);
}
catch (Exception e)
{
ErrorMessage = "Loading received submarine \"" + fileTransfer.FileName + "\" failed! {" + e.Message + "}";
return false;
}
if (stream == null)
{
ErrorMessage = "Decompressing received submarine file \"" + fileTransfer.FilePath + "\" failed!";
return false;
}
try
{
stream.Position = 0;
XmlReaderSettings settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit,
IgnoreProcessingInstructions = true
};
using (var reader = XmlReader.Create(stream, settings))
{
while (reader.Read());
}
}
catch
{
stream?.Close();
ErrorMessage = "Parsing file \"" + fileTransfer.FilePath + "\" failed! The file may not be a valid submarine file.";
return false;
}
stream?.Close();
break;
case FileTransferType.CampaignSave:
//TODO: verify that the received file is a valid save file
break;
}
return true;
}
public void StopTransfer(FileTransferIn transfer, bool deleteFile = false)
{
if (transfer.Status != FileTransferStatus.Finished &&
transfer.Status != FileTransferStatus.Error)
{
transfer.Status = FileTransferStatus.Canceled;
}
if (activeTransfers.Contains(transfer)) activeTransfers.Remove(transfer);
transfer.Dispose();
if (deleteFile && File.Exists(transfer.FilePath))
{
try
{
File.Delete(transfer.FilePath);
}
catch (Exception e)
{
DebugConsole.ThrowError("Failed to delete file \"" + transfer.FilePath + "\" (" + e.Message + ")");
}
}
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,125 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Barotrauma
{
partial class KarmaManager : ISerializableEntity
{
public void CreateSettingsFrame(GUIComponent parent)
{
if (TextManager.ContainsTag("Karma.ResetKarmaBetweenRounds"))
{
CreateLabeledTickBox(parent, "ResetKarmaBetweenRounds");
}
CreateLabeledSlider(parent, 0.0f, 40.0f, 1.0f, nameof(KickBanThreshold));
if (TextManager.ContainsTag("Karma.KicksBeforeBan"))
{
CreateLabeledNumberInput(parent, 0, 10, nameof(KicksBeforeBan));
}
CreateLabeledSlider(parent, 0.0f, 50.0f, 1.0f, nameof(HerpesThreshold));
CreateLabeledSlider(parent, 0.0f, 0.5f, 0.01f, nameof(KarmaDecay));
CreateLabeledSlider(parent, 50.0f, 100.0f, 1.0f, nameof(KarmaDecayThreshold));
CreateLabeledSlider(parent, 0.0f, 0.5f, 0.01f, nameof(KarmaIncrease));
CreateLabeledSlider(parent, 0.0f, 50.0f, 1.0f, nameof(KarmaIncreaseThreshold));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.12f), parent.RectTransform), TextManager.Get("Karma.PositiveActions"),
textAlignment: Alignment.Center, font: GUI.SubHeadingFont)
{
CanBeFocused = false
};
CreateLabeledSlider(parent, 0.0f, 1.0f, 0.01f, nameof(StructureRepairKarmaIncrease));
CreateLabeledSlider(parent, 0.0f, 1.0f, 0.01f, nameof(HealFriendlyKarmaIncrease));
CreateLabeledSlider(parent, 0.0f, 1.0f, 0.01f, nameof(DamageEnemyKarmaIncrease));
CreateLabeledSlider(parent, 0.0f, 1.0f, 0.01f, nameof(ItemRepairKarmaIncrease));
CreateLabeledSlider(parent, 0.0f, 10.0f, 0.05f, nameof(ExtinguishFireKarmaIncrease));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.12f), parent.RectTransform), TextManager.Get("Karma.NegativeActions"),
textAlignment: Alignment.Center, font: GUI.SubHeadingFont)
{
CanBeFocused = false
};
CreateLabeledSlider(parent, 0.0f, 1.0f, 0.01f, nameof(StructureDamageKarmaDecrease));
CreateLabeledSlider(parent, 0.0f, 1.0f, 0.01f, nameof(DamageFriendlyKarmaDecrease));
CreateLabeledSlider(parent, 0.0f, 100.0f, 1.0f, nameof(ReactorMeltdownKarmaDecrease));
CreateLabeledSlider(parent, 0.0f, 10.0f, 0.05f, nameof(ReactorOverheatKarmaDecrease));
CreateLabeledNumberInput(parent, 0, 20, nameof(AllowedWireDisconnectionsPerMinute));
CreateLabeledSlider(parent, 0.0f, 20.0f, 0.5f, nameof(WireDisconnectionKarmaDecrease));
CreateLabeledSlider(parent, 0.0f, 30.0f, 1.0f, nameof(SpamFilterKarmaDecrease));
}
private void CreateLabeledSlider(GUIComponent parent, float min, float max, float step, string propertyName)
{
var container = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), parent.RectTransform), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.05f,
ToolTip = TextManager.Get("Karma." + propertyName + "ToolTip")
};
string labelText = TextManager.Get("Karma." + propertyName);
var label = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), container.RectTransform),
labelText, textAlignment: Alignment.CenterLeft, font: GUI.SmallFont)
{
ToolTip = TextManager.Get("Karma." + propertyName + "ToolTip")
};
var slider = new GUIScrollBar(new RectTransform(new Vector2(0.3f, 1.0f), container.RectTransform), barSize: 0.1f, style: "GUISlider")
{
Step = step <= 0.0f ? 0.0f : step / (max - min),
Range = new Vector2(min, max),
OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
{
string formattedValueStr = step >= 1.0f ?
((int)scrollBar.BarScrollValue).ToString() :
scrollBar.BarScrollValue.Format(decimalCount: step <= 0.1f ? 2 : 1);
label.Text = TextManager.AddPunctuation(':', labelText, formattedValueStr);
return true;
}
};
container.RectTransform.MinSize = new Point(0, container.RectTransform.Children.Max(c => c.MinSize.Y));
GameMain.NetworkMember.ServerSettings.AssignGUIComponent(propertyName, slider);
slider.OnMoved(slider, slider.BarScroll);
}
private void CreateLabeledNumberInput(GUIComponent parent, int min, int max, string propertyName)
{
var container = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), parent.RectTransform), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.05f,
ToolTip = TextManager.Get("Karma." + propertyName + "ToolTip")
};
string labelText = TextManager.Get("Karma." + propertyName);
new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), container.RectTransform), labelText, textAlignment: Alignment.CenterLeft, font: GUI.SmallFont)
{
ToolTip = TextManager.Get("Karma." + propertyName + "ToolTip")
};
var numInput = new GUINumberInput(new RectTransform(new Vector2(0.3f, 1.0f), container.RectTransform), GUINumberInput.NumberType.Int)
{
MinValueInt = min,
MaxValueInt = max
};
container.RectTransform.MinSize = new Point(0, container.RectTransform.Children.Max(c => c.MinSize.Y));
GameMain.NetworkMember.ServerSettings.AssignGUIComponent(propertyName, numInput);
}
private void CreateLabeledTickBox(GUIComponent parent, string propertyName)
{
var tickBox = new GUITickBox(new RectTransform(new Vector2(0.3f, 0.1f), parent.RectTransform), TextManager.Get("Karma." + propertyName))
{
ToolTip = TextManager.Get("Karma." + propertyName + "ToolTip", returnNull: true) ?? ""
};
GameMain.NetworkMember.ServerSettings.AssignGUIComponent(propertyName, tickBox);
}
}
}
@@ -0,0 +1,293 @@
using System;
using System.Collections.Generic;
namespace Barotrauma.Networking
{
class ClientEntityEventManager : NetEntityEventManager
{
private List<ClientEntityEvent> events;
private UInt16 ID;
private GameClient thisClient;
//when was a specific entity event last sent to the client
// key = event id, value = NetTime.Now when sending
public Dictionary<UInt16, float> eventLastSent;
public UInt16 LastReceivedID
{
get { return lastReceivedID; }
}
private UInt16 lastReceivedID;
public bool MidRoundSyncing
{
get { return firstNewID.HasValue; }
}
public bool MidRoundSyncingDone
{
get;
private set;
}
public ClientEntityEventManager(GameClient client)
{
events = new List<ClientEntityEvent>();
eventLastSent = new Dictionary<UInt16, float>();
thisClient = client;
}
public void CreateEvent(IClientSerializable entity, object[] extraData = null)
{
if (GameMain.Client == null || GameMain.Client.Character == null) return;
if (!(entity is Entity))
{
DebugConsole.ThrowError("Can't create an entity event for " + entity + "!");
return;
}
if (((Entity)entity).Removed)
{
DebugConsole.ThrowError("Can't create an entity event for " + entity + " - the entity has been removed.\n" + Environment.StackTrace);
return;
}
if (((Entity)entity).IdFreed)
{
DebugConsole.ThrowError("Can't create an entity event for " + entity + " - the ID of the entity has been freed.\n" + Environment.StackTrace);
return;
}
var newEvent = new ClientEntityEvent(entity, (UInt16)(ID + 1))
{
CharacterStateID = GameMain.Client.Character.LastNetworkUpdateID
};
if (extraData != null) { newEvent.SetData(extraData); }
for (int i = events.Count - 1; i >= 0; i--)
{
//we already have an identical event that's waiting to be sent
// -> no need to add a new one
if (!events[i].Sent && events[i].IsDuplicate(newEvent)) return;
}
ID++;
events.Add(newEvent);
}
public void Write(IWriteMessage msg, NetworkConnection serverConnection)
{
if (events.Count == 0 || serverConnection == null) return;
List<NetEntityEvent> eventsToSync = new List<NetEntityEvent>();
//find the index of the first event the server hasn't received
int startIndex = events.Count;
while (startIndex > 0 &&
NetIdUtils.IdMoreRecent(events[startIndex - 1].ID, thisClient.LastSentEntityEventID))
{
startIndex--;
}
//remove events the server has already received
events.RemoveRange(0, startIndex);
for (int i = 0; i < events.Count; i++)
{
//find the first event that hasn't been sent in roundtriptime or at all
eventLastSent.TryGetValue(events[i].ID, out float lastSent);
if (lastSent > Lidgren.Network.NetTime.Now - 0.2) //TODO: reimplement serverConnection.AverageRoundtripTime
{
continue;
}
eventsToSync.AddRange(events.GetRange(i, events.Count - i));
break;
}
if (eventsToSync.Count == 0) { return; }
foreach (NetEntityEvent entityEvent in eventsToSync)
{
eventLastSent[entityEvent.ID] = (float)Lidgren.Network.NetTime.Now;
}
msg.Write((byte)ClientNetObject.ENTITY_STATE);
Write(msg, eventsToSync, out _);
}
private UInt16? firstNewID;
/// <summary>
/// Read the events from the message, ignoring ones we've already received. Returns false if reading the events fails.
/// </summary>
public bool Read(ServerNetObject type, IReadMessage msg, float sendingTime, List<IServerSerializable> entities)
{
UInt16 unreceivedEntityEventCount = 0;
if (type == ServerNetObject.ENTITY_EVENT_INITIAL)
{
unreceivedEntityEventCount = msg.ReadUInt16();
firstNewID = msg.ReadUInt16();
if (GameSettings.VerboseLogging)
{
DebugConsole.NewMessage(
"received midround syncing msg, unreceived: " + unreceivedEntityEventCount +
", first new ID: " + firstNewID, Microsoft.Xna.Framework.Color.Yellow);
}
}
else
{
MidRoundSyncingDone = true;
if (firstNewID != null)
{
if (GameSettings.VerboseLogging)
{
DebugConsole.NewMessage("midround syncing complete, switching to ID " + (UInt16) (firstNewID - 1),
Microsoft.Xna.Framework.Color.Yellow);
}
lastReceivedID = (UInt16)(firstNewID - 1);
firstNewID = null;
}
}
entities.Clear();
UInt16 firstEventID = msg.ReadUInt16();
int eventCount = msg.ReadByte();
for (int i = 0; i < eventCount; i++)
{
UInt16 thisEventID = (UInt16)(firstEventID + (UInt16)i);
UInt16 entityID = msg.ReadUInt16();
if (entityID == Entity.NullEntityID)
{
if (GameSettings.VerboseLogging)
{
DebugConsole.NewMessage("received msg " + thisEventID + " (null entity)",
Microsoft.Xna.Framework.Color.Orange);
}
msg.ReadPadBits();
entities.Add(null);
if (thisEventID == (UInt16)(lastReceivedID + 1)) lastReceivedID++;
continue;
}
byte msgLength = msg.ReadByte();
IServerSerializable entity = Entity.FindEntityByID(entityID) as IServerSerializable;
entities.Add(entity);
//skip the event if we've already received it or if the entity isn't found
if (thisEventID != (UInt16)(lastReceivedID + 1) || entity == null)
{
if (thisEventID != (UInt16) (lastReceivedID + 1))
{
if (GameSettings.VerboseLogging)
{
DebugConsole.NewMessage(
"Received msg " + thisEventID + " (waiting for " + (lastReceivedID + 1) + ")",
NetIdUtils.IdMoreRecent(thisEventID, (UInt16)(lastReceivedID + 1))
? GUI.Style.Red
: Microsoft.Xna.Framework.Color.Yellow);
}
}
else if (entity == null)
{
DebugConsole.NewMessage(
"Received msg " + thisEventID + ", entity " + entityID + " not found",
GUI.Style.Red);
GameMain.Client.ReportError(ClientNetError.MISSING_ENTITY, eventID: thisEventID, entityID: entityID);
return false;
}
msg.BitPosition += msgLength * 8;
msg.ReadPadBits();
}
else
{
long msgPosition = msg.BitPosition;
if (GameSettings.VerboseLogging)
{
DebugConsole.NewMessage("received msg " + thisEventID + " (" + entity.ToString() + ")",
Microsoft.Xna.Framework.Color.Green);
}
lastReceivedID++;
try
{
ReadEvent(msg, entity, sendingTime);
msg.ReadPadBits();
if (msg.BitPosition != msgPosition + msgLength * 8)
{
string errorMsg = "Message byte position incorrect after reading an event for the entity \"" + entity.ToString()
+ "\". Read " + (msg.BitPosition - msgPosition) + " bits, expected message length was " + (msgLength * 8) + " bits.";
#if DEBUG
DebugConsole.ThrowError(errorMsg);
#endif
GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:BitPosMismatch", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
//TODO: force the BitPosition to correct place? Having some entity in a potentially incorrect state is not as bad as a desync kick
//msg.BitPosition = (int)(msgPosition + msgLength * 8);
}
}
catch (Exception e)
{
string errorMsg = "Failed to read event for entity \"" + entity.ToString() + "\" (" + e.Message + ")! (MidRoundSyncing: " + thisClient.MidRoundSyncing + ")\n" + e.StackTrace;
errorMsg += "\nPrevious entities:";
for (int j = entities.Count - 2; j >= 0; j--)
{
errorMsg += "\n" + (entities[j] == null ? "NULL" : entities[j].ToString());
}
if (GameSettings.VerboseLogging)
{
DebugConsole.ThrowError("Failed to read event for entity \"" + entity.ToString() + "\"!", e);
}
GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:ReadFailed" + entity.ToString(),
GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
msg.BitPosition = (int)(msgPosition + msgLength * 8);
msg.ReadPadBits();
}
}
}
return true;
}
protected override void WriteEvent(IWriteMessage buffer, NetEntityEvent entityEvent, Client recipient = null)
{
var clientEvent = entityEvent as ClientEntityEvent;
if (clientEvent == null) return;
clientEvent.Write(buffer);
clientEvent.Sent = true;
}
protected void ReadEvent(IReadMessage buffer, IServerSerializable entity, float sendingTime)
{
entity.ClientRead(ServerNetObject.ENTITY_EVENT, buffer, sendingTime);
}
public void Clear()
{
ID = 0;
lastReceivedID = 0;
firstNewID = null;
events.Clear();
eventLastSent.Clear();
MidRoundSyncingDone = false;
}
}
}
@@ -0,0 +1,23 @@
using System;
namespace Barotrauma.Networking
{
class ClientEntityEvent : NetEntityEvent
{
private IClientSerializable serializable;
public UInt16 CharacterStateID;
public ClientEntityEvent(IClientSerializable entity, UInt16 id)
: base(entity, id)
{
serializable = entity;
}
public void Write(IWriteMessage msg)
{
msg.Write(CharacterStateID);
serializable.ClientWrite(msg, Data);
}
}
}
@@ -0,0 +1,99 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma.Networking
{
class NetStats
{
public enum NetStatType
{
SentBytes = 0,
ReceivedBytes = 1,
ResentMessages = 2
}
private Graph[] graphs;
private float[] totalValue;
private float[] lastValue;
const float UpdateInterval = 0.1f;
float updateTimer;
public NetStats()
{
graphs = new Graph[3];
totalValue = new float[3];
lastValue = new float[3];
for (int i = 0; i < 3; i++ )
{
graphs[i] = new Graph();
}
}
public void AddValue(NetStatType statType, float value)
{
float valueChange = value - lastValue[(int)statType];
totalValue[(int)statType] += valueChange;
lastValue[(int)statType] = value;
}
public void Update(float deltaTime)
{
updateTimer -= deltaTime;
if (updateTimer > 0.0f) return;
for (int i = 0; i < 3; i++)
{
graphs[i].Update(totalValue[i] / UpdateInterval);
totalValue[i] = 0.0f;
}
updateTimer = UpdateInterval;
}
public void Draw(SpriteBatch spriteBatch, Rectangle rect)
{
GUI.DrawRectangle(spriteBatch, rect, Color.Black * 0.4f, true);
graphs[(int)NetStatType.ReceivedBytes].Draw(spriteBatch, rect, null, 0.0f, Color.Cyan);
graphs[(int)NetStatType.SentBytes].Draw(spriteBatch, rect, null, 0.0f, GUI.Style.Orange);
graphs[(int)NetStatType.ResentMessages].Draw(spriteBatch, rect, null, 0.0f, GUI.Style.Red);
GUI.SmallFont.DrawString(spriteBatch,
"Peak received: " + MathUtils.GetBytesReadable((int)graphs[(int)NetStatType.ReceivedBytes].LargestValue()) + "/s " +
"Avg received: " + MathUtils.GetBytesReadable((int)graphs[(int)NetStatType.ReceivedBytes].Average()) + "/s",
new Vector2(rect.Right + 10, rect.Y + 10), Color.Cyan);
GUI.SmallFont.DrawString(spriteBatch, "Peak sent: " + MathUtils.GetBytesReadable((int)graphs[(int)NetStatType.SentBytes].LargestValue()) + "/s " +
"Avg sent: " + MathUtils.GetBytesReadable((int)graphs[(int)NetStatType.SentBytes].Average()) + "/s",
new Vector2(rect.Right + 10, rect.Y + 30), GUI.Style.Orange);
GUI.SmallFont.DrawString(spriteBatch, "Peak resent: " + graphs[(int)NetStatType.ResentMessages].LargestValue() + " messages/s",
new Vector2(rect.Right + 10, rect.Y + 50), GUI.Style.Red);
#if DEBUG
/*int y = 10;
foreach (KeyValuePair<string, long> msgBytesSent in server.messageCount.OrderBy(key => -key.Value))
{
GUI.SmallFont.DrawString(spriteBatch, msgBytesSent.Key + ": " + MathUtils.GetBytesReadable(msgBytesSent.Value),
new Vector2(rect.Right - 200, rect.Y + y), GUI.Style.Red);
y += 15;
}
TODO: reimplement?*/
#endif
}
}
}
@@ -0,0 +1,20 @@
using Lidgren.Network;
using System;
namespace Barotrauma.Networking
{
partial class OrderChatMessage : ChatMessage
{
public override void ClientWrite(IWriteMessage msg)
{
msg.Write((byte)ClientNetObject.CHAT_MESSAGE);
msg.Write(NetStateID);
msg.Write((byte)ChatMessageType.Order);
msg.Write((byte)Order.PrefabList.IndexOf(Order.Prefab));
msg.Write(TargetCharacter == null ? (UInt16)0 : TargetCharacter.ID);
msg.Write(TargetEntity == null ? (UInt16)0 : TargetEntity.ID);
msg.Write((byte)Array.IndexOf(Order.Prefab.Options, OrderOption));
}
}
}
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Barotrauma.Networking
{
abstract class ClientPeer
{
public delegate void MessageCallback(IReadMessage message);
public delegate void DisconnectCallback();
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 string Name;
public string Version { get; protected set; }
public NetworkConnection ServerConnection { get; protected set; }
public abstract void Start(object endPoint, int ownerKey);
public abstract void Close(string msg = null);
public abstract void Update(float deltaTime);
public abstract void Send(IWriteMessage msg, DeliveryMethod deliveryMethod);
public abstract void SendPassword(string password);
#if DEBUG
public abstract void ForceTimeOut();
#endif
}
}
@@ -0,0 +1,324 @@
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
{
private bool isActive;
private NetClient netClient;
private NetPeerConfiguration netPeerConfiguration;
private ConnectionInitialization initializationStep;
private bool contentPackageOrderReceived;
private int ownerKey;
private int passwordSalt;
private Steamworks.AuthTicket steamAuthTicket;
List<NetIncomingMessage> incomingLidgrenMessages;
public LidgrenClientPeer(string name)
{
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");
netPeerConfiguration.DisableMessageType(NetIncomingMessageType.DebugMessage | NetIncomingMessageType.WarningMessage | NetIncomingMessageType.Receipt
| NetIncomingMessageType.ErrorMessage | NetIncomingMessageType.Error);
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))
{
throw new InvalidCastException("endPoint is not IPEndPoint");
}
if (ServerConnection != null)
{
throw new InvalidOperationException("ServerConnection is not null");
}
netClient.Start();
ServerConnection = new LidgrenConnection("Server", netClient.Connect(ipEndPoint), 0)
{
Status = NetworkConnectionStatus.Connected
};
isActive = true;
}
public override void Update(float deltaTime)
{
if (!isActive) { return; }
if (ownerKey != 0 && (ChildServerRelay.Process?.HasExited ?? true))
{
Close();
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), TextManager.Get("ServerProcessClosed"));
msgBox.Buttons[0].OnClicked += (btn, obj) => { GameMain.MainMenuScreen.Select(); return false; };
return;
}
incomingLidgrenMessages.Clear();
netClient.ReadMessages(incomingLidgrenMessages);
foreach (NetIncomingMessage inc in incomingLidgrenMessages)
{
if (inc.SenderConnection != (ServerConnection as LidgrenConnection).NetConnection) { continue; }
switch (inc.MessageType)
{
case NetIncomingMessageType.Data:
HandleDataMessage(inc);
break;
case NetIncomingMessageType.StatusChanged:
HandleStatusChanged(inc);
break;
}
}
}
private void HandleDataMessage(NetIncomingMessage inc)
{
if (!isActive) { return; }
byte incByte = inc.ReadByte();
bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0;
bool isConnectionInitializationStep = (incByte & (byte)PacketHeader.IsConnectionInitializationStep) != 0;
//Console.WriteLine(isCompressed + " " + isConnectionInitializationStep + " " + (int)incByte);
if (isConnectionInitializationStep && initializationStep != ConnectionInitialization.Success)
{
ReadConnectionInitializationStep(inc);
}
else
{
if (initializationStep != ConnectionInitialization.Success)
{
OnInitializationComplete?.Invoke();
initializationStep = ConnectionInitialization.Success;
}
UInt16 length = inc.ReadUInt16();
IReadMessage msg = new ReadOnlyMessage(inc.Data, isCompressed, inc.PositionInBytes, length, ServerConnection);
OnMessageReceived?.Invoke(msg);
}
}
private void HandleStatusChanged(NetIncomingMessage inc)
{
if (!isActive) { return; }
NetConnectionStatus status = (NetConnectionStatus)inc.ReadByte();
switch (status)
{
case NetConnectionStatus.Disconnected:
string disconnectMsg = inc.ReadString();
Close(disconnectMsg);
OnDisconnectMessageReceived?.Invoke(disconnectMsg);
break;
}
}
private void ReadConnectionInitializationStep(NetIncomingMessage inc)
{
if (!isActive) { return; }
ConnectionInitialization step = (ConnectionInitialization)inc.ReadByte();
//Console.WriteLine(step + " " + initializationStep);
NetOutgoingMessage outMsg; NetSendResult result;
switch (step)
{
case ConnectionInitialization.SteamTicketAndVersion:
if (initializationStep != ConnectionInitialization.SteamTicketAndVersion) { return; }
outMsg = netClient.CreateMessage();
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());
IEnumerable<ContentPackage> mpContentPackages = GameMain.SelectedPackages.Where(cp => cp.HasMultiplayerIncompatibleContent);
outMsg.WriteVariableInt32(mpContentPackages.Count());
foreach (ContentPackage contentPackage in mpContentPackages)
{
outMsg.Write(contentPackage.Name);
outMsg.Write(contentPackage.MD5hash.Hash);
}
result = netClient.SendMessage(outMsg, NetDeliveryMethod.ReliableUnordered);
if (result != NetSendResult.Queued && result != NetSendResult.Sent)
{
DebugConsole.NewMessage("Failed to send "+initializationStep.ToString()+" message to host: " + result);
}
break;
case ConnectionInitialization.ContentPackageOrder:
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion ||
initializationStep == ConnectionInitialization.Password) { initializationStep = ConnectionInitialization.ContentPackageOrder; }
if (initializationStep != ConnectionInitialization.ContentPackageOrder) { return; }
outMsg = netClient.CreateMessage();
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)ConnectionInitialization.ContentPackageOrder);
Int32 cpCount = inc.ReadVariableInt32();
List<ContentPackage> serverContentPackages = new List<ContentPackage>();
for (int i = 0; i < cpCount; i++)
{
string hash = inc.ReadString();
serverContentPackages.Add(GameMain.Config.SelectedContentPackages.Find(cp => cp.MD5hash.Hash == hash));
}
if (!contentPackageOrderReceived)
{
GameMain.Config.ReorderSelectedContentPackages(cp => serverContentPackages.Contains(cp) ?
serverContentPackages.IndexOf(cp) :
serverContentPackages.Count + GameMain.Config.SelectedContentPackages.IndexOf(cp));
contentPackageOrderReceived = true;
}
result = netClient.SendMessage(outMsg, NetDeliveryMethod.ReliableUnordered);
if (result != NetSendResult.Queued && result != NetSendResult.Sent)
{
DebugConsole.NewMessage("Failed to send " + initializationStep.ToString() + " message to host: " + result);
}
break;
case 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)
{
passwordSalt = inc.ReadInt32();
}
else
{
retries = inc.ReadInt32();
}
OnRequestPassword?.Invoke(passwordSalt, retries);
break;
}
}
public override void SendPassword(string password)
{
if (!isActive) { return; }
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)
{
DebugConsole.NewMessage("Failed to send " + initializationStep.ToString() + " message to host: " + result);
}
}
public override void Close(string msg = null)
{
if (!isActive) { return; }
isActive = false;
netClient.Shutdown(msg ?? TextManager.Get("Disconnecting"));
netClient = null;
steamAuthTicket?.Cancel(); steamAuthTicket = null;
OnDisconnect?.Invoke();
}
public override void Send(IWriteMessage msg, DeliveryMethod deliveryMethod)
{
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;
}
#if DEBUG
netPeerConfiguration.SimulatedDuplicatesChance = GameMain.Client.SimulatedDuplicatesChance;
netPeerConfiguration.SimulatedMinimumLatency = GameMain.Client.SimulatedMinimumLatency;
netPeerConfiguration.SimulatedRandomLatency = GameMain.Client.SimulatedRandomLatency;
netPeerConfiguration.SimulatedLoss = GameMain.Client.SimulatedLoss;
#endif
NetOutgoingMessage lidgrenMsg = netClient.CreateMessage();
byte[] msgData = new byte[msg.LengthBytes];
msg.PrepareForSending(ref msgData, out bool isCompressed, out int length);
lidgrenMsg.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None));
lidgrenMsg.Write((UInt16)length);
lidgrenMsg.Write(msgData, 0, length);
NetSendResult result = netClient.SendMessage(lidgrenMsg, lidgrenDeliveryMethod);
if (result != NetSendResult.Queued && result != NetSendResult.Sent)
{
DebugConsole.NewMessage("Failed to send message to host: " + result);
}
}
#if DEBUG
public override void ForceTimeOut()
{
netClient?.ServerConnection?.ForceTimeOut();
}
#endif
}
}
@@ -0,0 +1,408 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using Barotrauma.Steam;
using System.Threading;
namespace Barotrauma.Networking
{
class SteamP2PClientPeer : ClientPeer
{
private bool isActive;
private UInt64 hostSteamId;
private ConnectionInitialization initializationStep;
private bool contentPackageOrderReceived;
private int passwordSalt;
private Steamworks.AuthTicket steamAuthTicket;
private double timeout;
private double heartbeatTimer;
private List<IReadMessage> incomingInitializationMessages;
private List<IReadMessage> incomingDataMessages;
public SteamP2PClientPeer(string name)
{
ServerConnection = null;
Name = name;
isActive = false;
}
public override void Start(object endPoint, int ownerKey)
{
contentPackageOrderReceived = false;
steamAuthTicket = SteamManager.GetAuthSessionTicket();
//TODO: wait for GetAuthSessionTicketResponse_t
if (steamAuthTicket == null)
{
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;
ServerConnection = new SteamP2PConnection("Server", 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);
initializationStep = ConnectionInitialization.SteamTicketAndVersion;
timeout = NetworkConnection.TimeoutThreshold;
heartbeatTimer = 1.0;
isActive = true;
}
private void OnIncomingConnection(Steamworks.SteamId steamId)
{
if (!isActive) { return; }
if (steamId == hostSteamId) { Steamworks.SteamNetworking.AcceptP2PSessionWithUser(steamId); }
}
private void OnP2PData(ulong steamId, byte[] data, int dataLength, int channel)
{
if (!isActive) { return; }
if (steamId != hostSteamId) { return; }
timeout = Screen.Selected == GameMain.GameScreen ?
NetworkConnection.TimeoutThresholdInGame :
NetworkConnection.TimeoutThreshold;
byte incByte = data[0];
bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0;
bool isConnectionInitializationStep = (incByte & (byte)PacketHeader.IsConnectionInitializationStep) != 0;
bool isDisconnectMessage = (incByte & (byte)PacketHeader.IsDisconnectMessage) != 0;
bool isServerMessage = (incByte & (byte)PacketHeader.IsServerMessage) != 0;
bool isHeartbeatMessage = (incByte & (byte)PacketHeader.IsHeartbeatMessage) != 0;
if (!isServerMessage) { return; }
if (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);
Steam.SteamManager.JoinLobby(lobbyId, false);
IReadMessage inc = new ReadOnlyMessage(data, false, 1+8, dataLength - 9, ServerConnection);
if (initializationStep != ConnectionInitialization.Success)
{
incomingInitializationMessages.Add(inc);
}
}
else if (isHeartbeatMessage)
{
return; //TODO: implement heartbeats
}
else if (isDisconnectMessage)
{
IReadMessage inc = new ReadOnlyMessage(data, false, 1, dataLength - 1, ServerConnection);
string msg = inc.ReadString();
Close(msg);
OnDisconnectMessageReceived?.Invoke(msg);
}
else
{
UInt16 length = data[1];
length |= (UInt16)(((UInt32)data[2]) << 8);
IReadMessage inc = new ReadOnlyMessage(data, isCompressed, 3, length, ServerConnection);
incomingDataMessages.Add(inc);
}
}
public override void Update(float deltaTime)
{
if (!isActive) { return; }
timeout -= deltaTime;
heartbeatTimer -= deltaTime;
while (Steamworks.SteamNetworking.IsP2PPacketAvailable())
{
var packet = Steamworks.SteamNetworking.ReadP2PPacket();
if (packet.HasValue)
{
OnP2PData(packet?.SteamId ?? 0, packet?.Data, packet?.Data.Length ?? 0, 0);
}
}
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);
heartbeatTimer = 5.0;
}
if (timeout < 0.0)
{
Close("Timed out");
OnDisconnectMessageReceived?.Invoke("");
return;
}
if (initializationStep != ConnectionInitialization.Success)
{
if (incomingDataMessages.Count > 0)
{
OnInitializationComplete?.Invoke();
initializationStep = ConnectionInitialization.Success;
}
else
{
foreach (IReadMessage inc in incomingInitializationMessages)
{
ReadConnectionInitializationStep(inc);
}
}
}
if (initializationStep == ConnectionInitialization.Success)
{
foreach (IReadMessage inc in incomingDataMessages)
{
OnMessageReceived?.Invoke(inc);
}
}
incomingInitializationMessages.Clear();
incomingDataMessages.Clear();
}
private void ReadConnectionInitializationStep(IReadMessage inc)
{
if (!isActive) { return; }
ConnectionInitialization step = (ConnectionInitialization)inc.ReadByte();
IWriteMessage outMsg;
//DebugConsole.NewMessage(step + " " + initializationStep);
switch (step)
{
case ConnectionInitialization.SteamTicketAndVersion:
if (initializationStep != ConnectionInitialization.SteamTicketAndVersion) { return; }
outMsg = new WriteOnlyMessage();
outMsg.Write((byte)DeliveryMethod.Reliable);
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)ConnectionInitialization.SteamTicketAndVersion);
outMsg.Write(Name);
outMsg.Write(SteamManager.GetSteamID());
outMsg.Write((UInt16)steamAuthTicket.Data.Length);
outMsg.Write(steamAuthTicket.Data, 0, steamAuthTicket.Data.Length);
outMsg.Write(GameMain.Version.ToString());
IEnumerable<ContentPackage> mpContentPackages = GameMain.SelectedPackages.Where(cp => cp.HasMultiplayerIncompatibleContent);
outMsg.WriteVariableUInt32((UInt32)mpContentPackages.Count());
foreach (ContentPackage contentPackage in mpContentPackages)
{
outMsg.Write(contentPackage.Name);
outMsg.Write(contentPackage.MD5hash.Hash);
}
heartbeatTimer = 5.0;
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
break;
case ConnectionInitialization.ContentPackageOrder:
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion ||
initializationStep == ConnectionInitialization.Password) { initializationStep = ConnectionInitialization.ContentPackageOrder; }
if (initializationStep != ConnectionInitialization.ContentPackageOrder) { return; }
outMsg = new WriteOnlyMessage();
outMsg.Write((byte)DeliveryMethod.Reliable);
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)ConnectionInitialization.ContentPackageOrder);
UInt32 cpCount = inc.ReadVariableUInt32();
List<ContentPackage> serverContentPackages = new List<ContentPackage>();
for (int i = 0; i < cpCount; i++)
{
string hash = inc.ReadString();
serverContentPackages.Add(GameMain.Config.SelectedContentPackages.Find(cp => cp.MD5hash.Hash == hash));
}
if (!contentPackageOrderReceived)
{
GameMain.Config.ReorderSelectedContentPackages(cp => serverContentPackages.Contains(cp) ?
serverContentPackages.IndexOf(cp) :
serverContentPackages.Count + GameMain.Config.SelectedContentPackages.IndexOf(cp));
contentPackageOrderReceived = true;
}
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
break;
case 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)
{
passwordSalt = inc.ReadInt32();
}
else
{
retries = inc.ReadInt32();
}
OnRequestPassword?.Invoke(passwordSalt, retries);
break;
}
}
public override void Send(IWriteMessage msg, DeliveryMethod deliveryMethod)
{
if (!isActive) { return; }
byte[] buf = new byte[msg.LengthBytes + 4];
buf[0] = (byte)deliveryMethod;
byte[] bufAux = new byte[msg.LengthBytes];
bool isCompressed; int length;
msg.PrepareForSending(ref bufAux, out isCompressed, out 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)
{
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)
{
DebugConsole.Log("WARNING: message length comes close to exceeding MTU, forcing reliable send (" + length.ToString() + " bytes)");
sendType = Steamworks.P2PSend.Reliable;
}
heartbeatTimer = 5.0;
#if DEBUG
CoroutineManager.InvokeAfter(() =>
{
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
}
private void Send(byte[] buf, int length, Steamworks.P2PSend sendType)
{
bool successSend = Steamworks.SteamNetworking.SendP2PPacket(hostSteamId, buf, length + 4, 0, sendType);
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);
}
if (!successSend)
{
DebugConsole.ThrowError("Failed to send message to remote peer! (" + length.ToString() + " bytes)");
}
}
}
public override void SendPassword(string password)
{
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);
}
public override void Close(string msg = null)
{
if (!isActive) { return; }
SteamManager.LeaveLobby();
isActive = false;
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write((byte)DeliveryMethod.Reliable);
outMsg.Write((byte)PacketHeader.IsDisconnectMessage);
outMsg.Write(msg ?? "Disconnected");
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
Thread.Sleep(100);
Steamworks.SteamNetworking.ResetActions();
Steamworks.SteamNetworking.CloseP2PSessionWithUser(hostSteamId);
steamAuthTicket?.Cancel(); steamAuthTicket = null;
hostSteamId = 0;
OnDisconnect?.Invoke();
}
~SteamP2PClientPeer()
{
OnDisconnect = null;
Close();
}
#if DEBUG
public override void ForceTimeOut()
{
timeout = 0.0f;
}
#endif
}
}
@@ -0,0 +1,446 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using Barotrauma.Steam;
using System.Linq;
using System.Threading;
namespace Barotrauma.Networking
{
class SteamP2POwnerPeer : ClientPeer
{
private bool isActive;
private ConnectionInitialization initializationStep;
private UInt64 selfSteamID;
class RemotePeer
{
public UInt64 SteamID;
public double? DisconnectTime;
public bool Authenticating;
public bool Authenticated;
public class UnauthedMessage
{
public DeliveryMethod DeliveryMethod;
public IWriteMessage Message;
}
public List<UnauthedMessage> UnauthedMessages;
public RemotePeer(UInt64 steamId)
{
SteamID = steamId;
DisconnectTime = null;
Authenticating = false;
Authenticated = false;
UnauthedMessages = new List<UnauthedMessage>();
}
}
List<RemotePeer> remotePeers;
public SteamP2POwnerPeer(string name)
{
ServerConnection = null;
Name = name;
isActive = false;
selfSteamID = Steam.SteamManager.GetSteamID();
}
public override void Start(object endPoint, int ownerKey)
{
if (isActive) { return; }
initializationStep = ConnectionInitialization.SteamTicketAndVersion;
ServerConnection = new PipeConnection();
ServerConnection.Status = NetworkConnectionStatus.Connected;
remotePeers = new List<RemotePeer>();
Steamworks.SteamNetworking.ResetActions();
Steamworks.SteamNetworking.OnP2PSessionRequest = OnIncomingConnection;
Steamworks.SteamUser.OnValidateAuthTicketResponse += OnAuthChange;
isActive = true;
}
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));
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.Authenticated = true;
remotePeer.Authenticating = false;
foreach (var msg in remotePeer.UnauthedMessages)
{
byte[] msgToSend = (byte[])msg.Message.Buffer.Clone();
Array.Resize(ref msgToSend, msg.Message.LengthBytes);
ChildServerRelay.Write(msgToSend);
}
remotePeer.UnauthedMessages.Clear();
}
else
{
DisconnectPeer(remotePeer, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam authentication failed: " + status.ToString());
return;
}
}
private void OnIncomingConnection(Steamworks.SteamId steamId)
{
if (!isActive) { return; }
if (!remotePeers.Any(p => p.SteamID == steamId))
{
remotePeers.Add(new RemotePeer(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)
{
if (!isActive) { return; }
RemotePeer remotePeer = remotePeers.Find(p => p.SteamID == steamId);
if (remotePeer == null || remotePeer.DisconnectTime != null)
{
return;
}
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write(steamId);
outMsg.Write(data, 1, dataLength - 1);
DeliveryMethod deliveryMethod = (DeliveryMethod)data[0];
byte incByte = data[1];
bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0;
bool isConnectionInitializationStep = (incByte & (byte)PacketHeader.IsConnectionInitializationStep) != 0;
bool isDisconnectMessage = (incByte & (byte)PacketHeader.IsDisconnectMessage) != 0;
bool isServerMessage = (incByte & (byte)PacketHeader.IsServerMessage) != 0;
bool isHeartbeatMessage = (incByte & (byte)PacketHeader.IsHeartbeatMessage) != 0;
if (!remotePeer.Authenticated)
{
if (!remotePeer.Authenticating)
{
if (isConnectionInitializationStep)
{
remotePeer.DisconnectTime = null;
IReadMessage authMsg = new ReadOnlyMessage(data, isCompressed, 2, dataLength - 2, null);
ConnectionInitialization initializationStep = (ConnectionInitialization)authMsg.ReadByte();
//Console.WriteLine("received init step from "+steamId.ToString()+" ("+initializationStep.ToString()+")");
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion)
{
remotePeer.Authenticating = true;
authMsg.ReadString(); //skip name
authMsg.ReadUInt64(); //skip steamid
UInt16 ticketLength = authMsg.ReadUInt16();
byte[] ticket = authMsg.ReadBytes(ticketLength);
Steamworks.BeginAuthResult authSessionStartState = Steam.SteamManager.StartAuthSession(ticket, steamId);
if (authSessionStartState != Steamworks.BeginAuthResult.OK)
{
DisconnectPeer(remotePeer, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam auth session failed to start: " + authSessionStartState.ToString());
return;
}
}
}
}
}
if (remotePeer.Authenticating)
{
remotePeer.UnauthedMessages.Add(new RemotePeer.UnauthedMessage() { DeliveryMethod = deliveryMethod, Message = outMsg });
}
else
{
byte[] msgToSend = (byte[])outMsg.Buffer.Clone();
Array.Resize(ref msgToSend, outMsg.LengthBytes);
ChildServerRelay.Write(msgToSend);
}
}
public override void Update(float deltaTime)
{
if (!isActive) { return; }
if (ChildServerRelay.HasShutDown || (ChildServerRelay.Process?.HasExited ?? true))
{
Close();
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), TextManager.Get("ServerProcessClosed"));
msgBox.Buttons[0].OnClicked += (btn, obj) => { GameMain.MainMenuScreen.Select(); return false; };
return;
}
for (int i = remotePeers.Count - 1; i >= 0; i--)
{
if (remotePeers[i].DisconnectTime != null && remotePeers[i].DisconnectTime < Timing.TotalTime)
{
ClosePeerSession(remotePeers[i]);
}
}
while (Steamworks.SteamNetworking.IsP2PPacketAvailable())
{
var packet = Steamworks.SteamNetworking.ReadP2PPacket();
if (packet.HasValue)
{
OnP2PData(packet?.SteamId ?? 0, packet?.Data, packet?.Data.Length ?? 0, 0);
}
}
while (ChildServerRelay.Read(out byte[] incBuf))
{
ChildServerRelay.DisposeLocalHandles();
IReadMessage inc = new ReadOnlyMessage(incBuf, false, 0, incBuf.Length, ServerConnection);
HandleDataMessage(inc);
}
}
private void HandleDataMessage(IReadMessage inc)
{
if (!isActive) { return; }
UInt64 recipientSteamId = inc.ReadUInt64();
DeliveryMethod deliveryMethod = (DeliveryMethod)inc.ReadByte();
int p2pDataStart = inc.BytePosition;
byte incByte = inc.ReadByte();
bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0;
bool isConnectionInitializationStep = (incByte & (byte)PacketHeader.IsConnectionInitializationStep) != 0;
bool isDisconnectMessage = (incByte & (byte)PacketHeader.IsDisconnectMessage) != 0;
bool isServerMessage = (incByte & (byte)PacketHeader.IsServerMessage) != 0;
bool isHeartbeatMessage = (incByte & (byte)PacketHeader.IsHeartbeatMessage) != 0;
if (recipientSteamId != selfSteamID)
{
if (!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 (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 (TODO: verify)
sendType = Steamworks.P2PSend.Reliable;
break;
default:
sendType = Steamworks.P2PSend.Unreliable;
break;
}
byte[] p2pData;
if (isConnectionInitializationStep)
{
p2pData = new byte[inc.LengthBytes - p2pDataStart + 8];
p2pData[0] = inc.Buffer[p2pDataStart];
Lidgren.Network.NetBitWriter.WriteUInt64(SteamManager.CurrentLobbyID, 64, p2pData, 8);
Array.Copy(inc.Buffer, p2pDataStart+1, p2pData, 9, inc.LengthBytes - p2pDataStart - 1);
}
else
{
p2pData = new byte[inc.LengthBytes - p2pDataStart];
Array.Copy(inc.Buffer, p2pDataStart, p2pData, 0, p2pData.Length);
}
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);
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);
}
if (!successSend)
{
DebugConsole.ThrowError("Failed to send message to remote peer! (" + p2pData.Length.ToString() + " bytes)");
}
}
}
else
{
if (isDisconnectMessage)
{
DebugConsole.ThrowError("Received disconnect message from owned server");
return;
}
if (!isServerMessage)
{
DebugConsole.ThrowError("Received non-server message from owned server");
return;
}
if (isHeartbeatMessage)
{
return; //timeout is handled by Lidgren, ignore this message
}
if (isConnectionInitializationStep)
{
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write(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, isCompressed, inc.BytePosition, length, ServerConnection);
OnMessageReceived?.Invoke(msg);
return;
}
}
}
private void DisconnectPeer(RemotePeer peer, string msg)
{
if (!string.IsNullOrWhiteSpace(msg))
{
if (peer.DisconnectTime == null)
{
peer.DisconnectTime = Timing.TotalTime + 1.0;
}
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);
}
else
{
ClosePeerSession(peer);
}
}
private void ClosePeerSession(RemotePeer peer)
{
Steamworks.SteamNetworking.CloseP2PSessionWithUser(peer.SteamID);
remotePeers.Remove(peer);
}
public override void SendPassword(string password)
{
return; //owner doesn't send passwords
}
public override void Close(string msg = null)
{
if (!isActive) { return; }
isActive = false;
for (int i = remotePeers.Count - 1; i >= 0; i--)
{
DisconnectPeer(remotePeers[i], msg ?? DisconnectReason.ServerShutdown.ToString());
}
Thread.Sleep(100);
for (int i = remotePeers.Count - 1; i >= 0; i--)
{
ClosePeerSession(remotePeers[i]);
}
ChildServerRelay.ShutDown();
OnDisconnect?.Invoke();
SteamManager.LeaveLobby();
Steamworks.SteamNetworking.ResetActions();
Steamworks.SteamUser.OnValidateAuthTicketResponse -= OnAuthChange;
}
public override void Send(IWriteMessage msg, DeliveryMethod deliveryMethod)
{
if (!isActive) { return; }
IWriteMessage msgToSend = new WriteOnlyMessage();
byte[] msgData = new byte[msg.LengthBytes];
msg.PrepareForSending(ref msgData, out bool isCompressed, out int length);
msgToSend.Write(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);
}
~SteamP2POwnerPeer()
{
OnDisconnect = null;
Close();
}
#if DEBUG
public override void ForceTimeOut()
{
//TODO: reimplement?
}
#endif
}
}
@@ -0,0 +1,58 @@
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
namespace Barotrauma.Networking
{
partial class RespawnManager
{
private DateTime lastShuttleLeavingWarningTime;
partial void UpdateTransportingProjSpecific(float deltaTime)
{
if (GameMain.Client?.Character == null || GameMain.Client.Character.Submarine != RespawnShuttle) { return; }
if (!ReturnCountdownStarted) { return; }
//show a warning when there's 20 seconds until the shuttle leaves
if ((ReturnTime - DateTime.Now).TotalSeconds < 20.0f &&
(DateTime.Now - lastShuttleLeavingWarningTime).TotalSeconds > 30.0f)
{
lastShuttleLeavingWarningTime = DateTime.Now;
GameMain.Client.AddChatMessage("ServerMessage.ShuttleLeaving", ChatMessageType.Server);
}
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
var newState = (State)msg.ReadRangedInteger(0, Enum.GetNames(typeof(State)).Length);
switch (newState)
{
case State.Transporting:
ReturnCountdownStarted = msg.ReadBoolean();
maxTransportTime = msg.ReadSingle();
float transportTimeLeft = msg.ReadSingle();
ReturnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(transportTimeLeft * 1000.0f));
RespawnCountdownStarted = false;
if (CurrentState != newState)
{
CoroutineManager.StopCoroutines("forcepos");
//CoroutineManager.StartCoroutine(ForceShuttleToPos(Level.Loaded.StartPosition - Vector2.UnitY * Level.ShaftHeight, 100.0f), "forcepos");
}
break;
case State.Waiting:
RespawnCountdownStarted = msg.ReadBoolean();
ResetShuttle();
float newRespawnTime = msg.ReadSingle();
RespawnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(newRespawnTime * 1000.0f));
break;
case State.Returning:
RespawnCountdownStarted = false;
break;
}
CurrentState = newState;
msg.ReadPadBits();
}
}
}
@@ -0,0 +1,543 @@
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.PingLocation? 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 string GameMode;
public PlayStyle? PlayStyle;
public bool Recent;
public bool Favorite;
public bool? RespondedToSteamQuery = null;
public Steamworks.Friend? SteamFriend;
public Steamworks.ISteamMatchmakingPingResponse 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<string> ContentPackageWorkshopUrls
{
get;
private set;
} = new List<string>();
public bool ContentPackagesMatch(IEnumerable<ContentPackage> myContentPackages)
{
//make sure we have all the packages the server requires
foreach (string hash in ContentPackageHashes)
{
if (!myContentPackages.Any(myPackage => myPackage.MD5hash.Hash == hash)) { return false; }
}
//make sure the server isn't missing any of our packages that cause multiplayer incompatibility
foreach (ContentPackage myPackage in myContentPackages)
{
if (myPackage.HasMultiplayerIncompatibleContent)
{
if (!ContentPackageHashes.Any(hash => hash == myPackage.MD5hash.Hash)) { return false; }
}
}
return true;
}
public bool ContentPackagesMatch(IEnumerable<string> myContentPackageHashes)
{
HashSet<string> contentPackageHashes = new HashSet<string>(ContentPackageHashes);
return contentPackageHashes.SetEquals(myContentPackageHashes);
}
public void CreatePreviewWindow(GUIFrame frame)
{
frame.ClearChildren();
if (frame == null) { return; }
var previewContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.98f), frame.RectTransform, Anchor.Center))
{
Stretch = true
};
var title = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), previewContainer.RectTransform, Anchor.CenterLeft), ServerName, font: GUI.LargeFont)
{
ToolTip = ServerName
};
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), previewContainer.RectTransform),
TextManager.AddPunctuation(':', TextManager.Get("ServerListVersion"), string.IsNullOrEmpty(GameVersion) ? TextManager.Get("Unknown") : GameVersion));
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(previewContainer.Rect.Width, (int)(previewContainer.Rect.Width / playStyleBannerAspectRatio)), previewContainer.RectTransform),
playStyleBannerSprite, null, true);
var playStyleName = new GUITextBlock(new RectTransform(new Vector2(0.15f, 0.0f), playStyleBanner.RectTransform) { RelativeOffset = new Vector2(0.01f, 0.06f) },
TextManager.AddPunctuation(':', TextManager.Get("serverplaystyle"), TextManager.Get("servertag."+ playStyle)), textColor: Color.White,
font: GUI.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 content = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.6f), previewContainer.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: GUI.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(string.IsNullOrEmpty(GameMode) ? "Unknown" : "GameMode." + GameMode, returnNull: true) ?? GameMode,
textAlignment: Alignment.Right);
/*var traitors = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), bodyContainer.RectTransform), TextManager.Get("Traitors"));
new GUITextBlock(new RectTransform(Vector2.One, traitors.RectTransform), TextManager.Get(!TraitorsEnabled.HasValue ? "Unknown" : TraitorsEnabled.Value.ToString()), 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 = GUI.SmallFont;
gameMode.GetChild<GUITextBlock>().Font = subSelection.GetChild<GUITextBlock>().Font = modeSelection.GetChild<GUITextBlock>().Font = GUI.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: GUI.SubHeadingFont);
var contentPackageList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.2f), content.RectTransform)) { ScrollBarVisible = true };
if (ContentPackageNames.Count == 0)
{
new GUITextBlock(new RectTransform(Vector2.One, contentPackageList.Content.RectTransform), TextManager.Get("Unknown"), textAlignment: Alignment.Center)
{
CanBeFocused = false
};
}
else
{
List<string> availableWorkshopUrls = new List<string>();
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
};
if (i < ContentPackageHashes.Count)
{
if (GameMain.Config.SelectedContentPackages.Any(cp => cp.MD5hash.Hash == ContentPackageHashes[i]))
{
packageText.Selected = true;
continue;
}
//matching content package found, but it hasn't been enabled
if (ContentPackage.List.Any(cp => cp.MD5hash.Hash == ContentPackageHashes[i]))
{
packageText.TextColor = GUI.Style.Orange;
packageText.ToolTip = TextManager.GetWithVariable("ServerListContentPackageNotEnabled", "[contentpackage]", ContentPackageNames[i]);
}
//workshop download link found
else if (i < ContentPackageWorkshopUrls.Count && !string.IsNullOrEmpty(ContentPackageWorkshopUrls[i]))
{
availableWorkshopUrls.Add(ContentPackageWorkshopUrls[i]);
packageText.TextColor = Color.Yellow;
packageText.ToolTip = TextManager.GetWithVariable("ServerListIncompatibleContentPackageWorkshopAvailable", "[contentpackage]", ContentPackageNames[i]);
}
else //no package or workshop download link found, tough luck
{
packageText.TextColor = GUI.Style.Red;
packageText.ToolTip = TextManager.GetWithVariables("ServerListIncompatibleContentPackage",
new string[2] { "[contentpackage]", "[hash]" }, new string[2] { ContentPackageNames[i], ContentPackageHashes[i] });
}
}
}
if (availableWorkshopUrls.Count > 0)
{
var workshopBtn = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), content.RectTransform), TextManager.Get("ServerListSubscribeMissingPackages"))
{
ToolTip = TextManager.Get(SteamManager.IsInitialized ? "ServerListSubscribeMissingPackagesTooltip" : "ServerListSubscribeMissingPackagesTooltipNoSteam"),
Enabled = SteamManager.IsInitialized,
OnClicked = (btn, userdata) =>
{
GameMain.SteamWorkshopScreen.SubscribeToPackages(availableWorkshopUrls);
GameMain.SteamWorkshopScreen.Select();
return true;
}
};
workshopBtn.TextBlock.AutoScaleHorizontal = true;
}
}
// -----------------------------------------------------------------------------
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 || ContentPackageNames[0] != GameMain.VanillaContent?.Name ? "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.GetAttributeString("GameMode", "");
info.GameVersion = element.GetAttributeString("GameVersion", "");
info.MaxPlayers = element.GetAttributeInt("MaxPlayers", 0);
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 = subSelectionTemp; }
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)
{
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.ISteamMatchmakingPingResponse(
(server) =>
{
ServerName = server.Name;
RespondedToSteamQuery = true;
PlayerCount = server.Players;
MaxPlayers = server.MaxPlayers;
HasPassword = server.Passworded;
PingChecked = true;
Ping = server.Ping;
LobbyID = 0;
TaskPool.Add(server.QueryRulesAsync(),
(t) =>
{
if (t.Status == TaskStatus.Faulted)
{
TaskPool.PrintTaskExceptions(t, "Failed to retrieve rules for " + ServerName);
return;
}
var rules = t.Result;
SteamManager.AssignServerRulesToServerInfo(rules, this);
CrossThread.RequestExecutionOnMainThread(() =>
{
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(SteamFriend?.RequestInfoAsync(),
(t) =>
{
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;
}
});
}
}
}
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;
}
}
}
@@ -0,0 +1,210 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma.Networking
{
public partial class ServerLog
{
public GUIButton LogFrame;
private GUIListBox listBox;
private string msgFilter;
public void CreateLogFrame()
{
LogFrame = new GUIButton(new RectTransform(Vector2.One, GUI.Canvas), style: "GUIBackgroundBlocker")
{
OnClicked = (btn, userdata) => { if (GUI.MouseOn == btn || GUI.MouseOn == btn.TextBlock) LogFrame = null; return true; }
};
new GUIButton(new RectTransform(Vector2.One, LogFrame.RectTransform), "", style: null).OnClicked += (btn, userData) =>
{
LogFrame = null;
return true;
};
GUIFrame innerFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.5f), LogFrame.RectTransform, Anchor.Center) { MinSize = new Point(700, 500) });
GUIFrame paddedFrame = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.9f), innerFrame.RectTransform, Anchor.Center), style: null);
// left column ----------------
var tickBoxContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.25f, 1.0f), paddedFrame.RectTransform, Anchor.BottomLeft));
int y = 30;
List<GUITickBox> tickBoxes = new List<GUITickBox>();
foreach (MessageType msgType in Enum.GetValues(typeof(MessageType)))
{
var tickBox = new GUITickBox(new RectTransform(new Point(tickBoxContainer.Rect.Width, 30), tickBoxContainer.RectTransform), TextManager.Get("ServerLog." + messageTypeName[msgType]), font: GUI.SmallFont)
{
Selected = true,
TextColor = messageColor[msgType],
OnSelected = (GUITickBox tb) =>
{
msgTypeHidden[(int)msgType] = !tb.Selected;
FilterMessages();
return true;
}
};
tickBox.TextBlock.SelectedTextColor = tickBox.TextBlock.TextColor;
tickBox.Selected = !msgTypeHidden[(int)msgType];
tickBoxes.Add(tickBox);
y += 20;
}
tickBoxes.Last().TextBlock.RectTransform.SizeChanged += () =>
{
GUITextBlock.AutoScaleAndNormalize(tickBoxes.Select(t => t.TextBlock), defaultScale: 1.0f);
};
// right column ----------------
var rightColumn = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 1.0f), paddedFrame.RectTransform, Anchor.CenterRight), childAnchor: Anchor.TopRight)
{
Stretch = true,
RelativeSpacing = 0.02f
};
GUILayoutGroup filterArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform, Anchor.TopRight),
isHorizontal: true, childAnchor: Anchor.CenterLeft);
new GUITextBlock(new RectTransform(new Vector2(0.2f, 1.0f), filterArea.RectTransform), TextManager.Get("ServerLog.Filter"),
font: GUI.SubHeadingFont);
GUITextBox searchBox = new GUITextBox(new RectTransform(new Vector2(0.8f, 1.0f), filterArea.RectTransform), font: GUI.SmallFont, createClearButton: true);
searchBox.OnTextChanged += (textBox, text) =>
{
msgFilter = text;
FilterMessages();
return true;
};
GUI.KeyboardDispatcher.Subscriber = searchBox;
filterArea.RectTransform.MinSize = new Point(0, filterArea.RectTransform.Children.Max(c => c.MinSize.Y));
listBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.95f), rightColumn.RectTransform));
GUIButton closeButton = new GUIButton(new RectTransform(new Vector2(0.25f, 0.05f), rightColumn.RectTransform), TextManager.Get("Close"))
{
OnClicked = (button, userData) =>
{
LogFrame = null;
return true;
}
};
rightColumn.Recalculate();
var currLines = lines.ToList();
foreach (LogMessage line in currLines)
{
AddLine(line);
}
FilterMessages();
listBox.UpdateScrollBarSize();
if (listBox.BarScroll == 0.0f || listBox.BarScroll == 1.0f) { listBox.BarScroll = 1.0f; }
msgFilter = "";
}
public void AssignLogFrame(GUIListBox inListBox, GUIComponent tickBoxContainer, GUITextBox searchBox)
{
searchBox.OnTextChanged += (textBox, text) =>
{
msgFilter = text;
FilterMessages();
return true;
};
tickBoxContainer.ClearChildren();
List<GUITickBox> tickBoxes = new List<GUITickBox>();
foreach (MessageType msgType in Enum.GetValues(typeof(MessageType)))
{
var tickBox = new GUITickBox(new RectTransform(new Point(tickBoxContainer.Rect.Width, (int)(25 * GUI.Scale)), tickBoxContainer.RectTransform), TextManager.Get("ServerLog." + messageTypeName[msgType]), font: GUI.SmallFont)
{
Selected = true,
TextColor = messageColor[msgType],
OnSelected = (GUITickBox tb) =>
{
msgTypeHidden[(int)msgType] = !tb.Selected;
FilterMessages();
return true;
}
};
tickBox.TextBlock.SelectedTextColor = tickBox.TextBlock.TextColor;
tickBox.Selected = !msgTypeHidden[(int)msgType];
tickBoxes.Add(tickBox);
}
tickBoxes.Last().TextBlock.RectTransform.SizeChanged += () =>
{
GUITextBlock.AutoScaleAndNormalize(tickBoxes.Select(t => t.TextBlock), defaultScale: 1.0f);
};
inListBox.ClearChildren();
listBox = inListBox;
var currLines = lines.ToList();
foreach (LogMessage line in currLines)
{
AddLine(line);
}
FilterMessages();
listBox.UpdateScrollBarSize();
}
private void AddLine(LogMessage line)
{
float prevSize = listBox.BarSize;
var textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), listBox.Content.RectTransform),
line.Text, wrap: true, font: GUI.SmallFont)
{
TextColor = messageColor[line.Type],
Visible = !msgTypeHidden[(int)line.Type],
CanBeFocused = false,
UserData = line
};
if ((prevSize == 1.0f && listBox.BarScroll == 0.0f) || (prevSize < 1.0f && listBox.BarScroll == 1.0f)) listBox.BarScroll = 1.0f;
}
private bool FilterMessages()
{
string filter = msgFilter == null ? "" : msgFilter.ToLower();
foreach (GUIComponent child in listBox.Content.Children)
{
var textBlock = child as GUITextBlock;
if (textBlock == null) continue;
child.Visible = true;
if (msgTypeHidden[(int)((LogMessage)child.UserData).Type])
{
child.Visible = false;
continue;
}
textBlock.Visible = string.IsNullOrEmpty(filter) || textBlock.Text.ToLower().Contains(filter);
}
listBox.UpdateScrollBarSize();
listBox.BarScroll = 0.0f;
return true;
}
public bool ClearFilter(GUIComponent button, object obj)
{
var searchBox = button.UserData as GUITextBox;
if (searchBox != null) searchBox.Text = "";
msgFilter = "";
FilterMessages();
return true;
}
}
}
@@ -0,0 +1,972 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
namespace Barotrauma.Networking
{
partial class ServerSettings : ISerializableEntity
{
partial class NetPropertyData
{
public GUIComponent GUIComponent;
public object TempValue;
public void AssignGUIComponent(GUIComponent component)
{
GUIComponent = component;
GUIComponentValue = property.GetValue(parentObject);
TempValue = GUIComponentValue;
}
public object GUIComponentValue
{
get
{
if (GUIComponent == null) return null;
else if (GUIComponent is GUITickBox tickBox) return tickBox.Selected;
else if (GUIComponent is GUITextBox textBox) return textBox.Text;
else if (GUIComponent is GUIScrollBar scrollBar) return scrollBar.BarScrollValue;
else if (GUIComponent is GUIRadioButtonGroup radioButtonGroup) return radioButtonGroup.Selected;
else if (GUIComponent is GUIDropDown dropdown) return dropdown.SelectedData;
else if (GUIComponent is GUINumberInput numInput)
{
if (numInput.InputType == GUINumberInput.NumberType.Int) { return numInput.IntValue; } else { return numInput.FloatValue; }
}
return null;
}
set
{
if (GUIComponent == null) return;
else if (GUIComponent is GUITickBox tickBox) tickBox.Selected = (bool)value;
else if (GUIComponent is GUITextBox textBox) textBox.Text = (string)value;
else if (GUIComponent is GUIScrollBar scrollBar)
{
if (value.GetType() == typeof(int))
{
scrollBar.BarScrollValue = (int)value;
}
else
{
scrollBar.BarScrollValue = (float)value;
}
}
else if (GUIComponent is GUIRadioButtonGroup radioButtonGroup) radioButtonGroup.Selected = (int)value;
else if (GUIComponent is GUIDropDown dropdown) dropdown.SelectItem(value);
else if (GUIComponent is GUINumberInput numInput)
{
if (numInput.InputType == GUINumberInput.NumberType.Int)
{
numInput.IntValue = (int)value;
}
else
{
numInput.FloatValue = (float)value;
}
}
}
}
public bool ChangedLocally
{
get
{
if (GUIComponent == null) return false;
return !PropEquals(TempValue, GUIComponentValue);
}
}
}
private Dictionary<string, bool> tempMonsterEnabled;
partial void InitProjSpecific()
{
var properties = TypeDescriptor.GetProperties(GetType()).Cast<PropertyDescriptor>();
SerializableProperties = new Dictionary<string, SerializableProperty>();
foreach (var property in properties)
{
SerializableProperty objProperty = new SerializableProperty(property);
SerializableProperties.Add(property.Name.ToLowerInvariant(), objProperty);
}
}
public void ClientAdminRead(IReadMessage incMsg)
{
int count = incMsg.ReadUInt16();
for (int i = 0; i < count; i++)
{
UInt32 key = incMsg.ReadUInt32();
if (netProperties.ContainsKey(key))
{
bool changedLocally = netProperties[key].ChangedLocally;
netProperties[key].Read(incMsg);
netProperties[key].TempValue = netProperties[key].Value;
if (netProperties[key].GUIComponent != null)
{
if (!changedLocally)
{
netProperties[key].GUIComponentValue = netProperties[key].Value;
}
}
}
else
{
UInt32 size = incMsg.ReadVariableUInt32();
incMsg.BitPosition += (int)(8 * size);
}
}
ReadMonsterEnabled(incMsg);
BanList.ClientAdminRead(incMsg);
Whitelist.ClientAdminRead(incMsg);
}
public void ClientRead(IReadMessage incMsg)
{
ServerName = incMsg.ReadString();
ServerMessageText = incMsg.ReadString();
MaxPlayers = incMsg.ReadByte();
HasPassword = incMsg.ReadBoolean();
IsPublic = incMsg.ReadBoolean();
incMsg.ReadPadBits();
TickRate = incMsg.ReadRangedInteger(1, 60);
GameMain.NetworkMember.TickRate = TickRate;
ReadExtraCargo(incMsg);
Voting.ClientRead(incMsg);
bool isAdmin = incMsg.ReadBoolean();
incMsg.ReadPadBits();
if (isAdmin)
{
ClientAdminRead(incMsg);
}
}
public void ClientAdminWrite(NetFlags dataToSend, int? missionTypeOr = null, int? missionTypeAnd = null, float? levelDifficulty = null, bool? autoRestart = null, int traitorSetting = 0, int botCount = 0, int botSpawnMode = 0, bool? useRespawnShuttle = null)
{
if (!GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings)) return;
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write((byte)ClientPacketHeader.SERVER_SETTINGS);
outMsg.Write((byte)dataToSend);
if (dataToSend.HasFlag(NetFlags.Name))
{
if (GameMain.NetLobbyScreen.ServerName.Text != ServerName)
{
ServerName = GameMain.NetLobbyScreen.ServerName.Text;
}
outMsg.Write(ServerName);
}
if (dataToSend.HasFlag(NetFlags.Message))
{
if (GameMain.NetLobbyScreen.ServerMessage.Text != ServerMessageText)
{
ServerMessageText = GameMain.NetLobbyScreen.ServerMessage.Text;
}
outMsg.Write(ServerMessageText);
}
if (dataToSend.HasFlag(NetFlags.Properties))
{
//TODO: split this up?
WriteExtraCargo(outMsg);
IEnumerable<KeyValuePair<UInt32, NetPropertyData>> changedProperties = netProperties.Where(kvp => kvp.Value.ChangedLocally);
UInt32 count = (UInt32)changedProperties.Count();
bool changedMonsterSettings = tempMonsterEnabled != null && tempMonsterEnabled.Any(p => p.Value != MonsterEnabled[p.Key]);
outMsg.Write(count);
foreach (KeyValuePair<UInt32, NetPropertyData> prop in changedProperties)
{
DebugConsole.NewMessage(prop.Value.Name, Color.Lime);
outMsg.Write(prop.Key);
prop.Value.Write(outMsg, prop.Value.GUIComponentValue);
}
outMsg.Write(changedMonsterSettings); outMsg.WritePadBits();
if (changedMonsterSettings) WriteMonsterEnabled(outMsg, tempMonsterEnabled);
BanList.ClientAdminWrite(outMsg);
Whitelist.ClientAdminWrite(outMsg);
}
if (dataToSend.HasFlag(NetFlags.Misc))
{
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.Write(levelDifficulty ?? -1000.0f);
outMsg.Write(useRespawnShuttle ?? UseRespawnShuttle);
outMsg.Write(autoRestart != null);
outMsg.Write(autoRestart ?? false);
outMsg.WritePadBits();
}
if (dataToSend.HasFlag(NetFlags.LevelSeed))
{
outMsg.Write(GameMain.NetLobbyScreen.SeedBox.Text);
}
GameMain.Client.ClientPeer.Send(outMsg, DeliveryMethod.Reliable);
}
//GUI stuff
private GUIFrame settingsFrame;
private GUIFrame[] settingsTabs;
private GUIButton[] tabButtons;
private int settingsTabIndex;
private GUIDropDown karmaPresetDD;
private GUIComponent karmaSettingsBlocker;
enum SettingsTab
{
General,
Rounds,
Antigriefing,
Banlist,
Whitelist
}
private NetPropertyData GetPropertyData(string name)
{
return netProperties.First(p => p.Value.Name == name).Value;
}
public void AssignGUIComponent(string propertyName, GUIComponent component)
{
GetPropertyData(propertyName).AssignGUIComponent(component);
}
public void AddToGUIUpdateList()
{
if (GUI.DisableHUD) return;
settingsFrame?.AddToGUIUpdateList();
}
private void CreateSettingsFrame()
{
foreach (NetPropertyData prop in netProperties.Values)
{
prop.TempValue = prop.Value;
}
//background frame
settingsFrame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), style: null, color: Color.Black * 0.5f);
new GUIButton(new RectTransform(Vector2.One, settingsFrame.RectTransform), "", style: null).OnClicked += (btn, userData) =>
{
if (GUI.MouseOn == btn || GUI.MouseOn == btn.TextBlock) { ToggleSettingsFrame(btn, userData); }
return true;
};
new GUIButton(new RectTransform(Vector2.One, settingsFrame.RectTransform), "", style: null)
{
OnClicked = ToggleSettingsFrame
};
//center frames
GUIFrame innerFrame = new GUIFrame(new RectTransform(new Vector2(0.4f, 0.8f), settingsFrame.RectTransform, Anchor.Center) { MinSize = new Point(400, 430) });
GUILayoutGroup paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), innerFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
{
Stretch = true,
RelativeSpacing = 0.02f
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform), TextManager.Get("Settings"), font: GUI.LargeFont);
var buttonArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.04f), paddedFrame.RectTransform), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.01f
};
var tabContent = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.85f), paddedFrame.RectTransform), style: "InnerFrame");
//tabs
var tabValues = Enum.GetValues(typeof(SettingsTab)).Cast<SettingsTab>().ToArray();
string[] tabNames = new string[tabValues.Count()];
for (int i = 0; i < tabNames.Length; i++)
{
tabNames[i] = TextManager.Get("ServerSettings" + tabValues[i] + "Tab");
}
settingsTabs = new GUIFrame[tabNames.Length];
tabButtons = new GUIButton[tabNames.Length];
for (int i = 0; i < tabNames.Length; i++)
{
settingsTabs[i] = new GUIFrame(new RectTransform(Vector2.One, tabContent.RectTransform, Anchor.Center), style: null);
tabButtons[i] = new GUIButton(new RectTransform(new Vector2(0.2f, 1.2f), buttonArea.RectTransform), tabNames[i], style: "GUITabButton")
{
UserData = i,
OnClicked = SelectSettingsTab
};
}
GUITextBlock.AutoScaleAndNormalize(tabButtons.Select(b => b.TextBlock));
SelectSettingsTab(tabButtons[0], 0);
//"Close"
var buttonContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.05f), paddedFrame.RectTransform), style: null);
var closeButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonContainer.RectTransform, Anchor.BottomRight), TextManager.Get("Close"))
{
OnClicked = ToggleSettingsFrame
};
//--------------------------------------------------------------------------------
// server settings
//--------------------------------------------------------------------------------
var serverTab = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), settingsTabs[(int)SettingsTab.General].RectTransform, Anchor.Center))
{
Stretch = true,
RelativeSpacing = 0.01f
};
//***********************************************
// Sub Selection
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), serverTab.RectTransform), TextManager.Get("ServerSettingsSubSelection"), font: GUI.SubHeadingFont);
var selectionFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.02f), serverTab.RectTransform), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.05f
};
GUIRadioButtonGroup selectionMode = new GUIRadioButtonGroup();
for (int i = 0; i < 3; i++)
{
var selectionTick = new GUITickBox(new RectTransform(new Vector2(0.3f, 1.0f), selectionFrame.RectTransform), TextManager.Get(((SelectionMode)i).ToString()), font: GUI.SmallFont, style: "GUIRadioButton");
selectionMode.AddRadioButton(i, selectionTick);
}
selectionFrame.RectTransform.NonScaledSize = new Point(selectionFrame.Rect.Width, selectionFrame.Children.First().Rect.Height);
selectionFrame.RectTransform.IsFixedSize = true;
GetPropertyData("SubSelectionMode").AssignGUIComponent(selectionMode);
// Mode Selection
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), serverTab.RectTransform), TextManager.Get("ServerSettingsModeSelection"), font: GUI.SubHeadingFont);
selectionFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.02f), serverTab.RectTransform), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.05f
};
selectionMode = new GUIRadioButtonGroup();
for (int i = 0; i < 3; i++)
{
var selectionTick = new GUITickBox(new RectTransform(new Vector2(0.3f, 1.0f), selectionFrame.RectTransform), TextManager.Get(((SelectionMode)i).ToString()), font: GUI.SmallFont, style: "GUIRadioButton");
selectionMode.AddRadioButton(i, selectionTick);
}
selectionFrame.RectTransform.NonScaledSize = new Point(selectionFrame.Rect.Width, selectionFrame.Children.First().Rect.Height);
selectionFrame.RectTransform.IsFixedSize = true;
GetPropertyData("ModeSelectionMode").AssignGUIComponent(selectionMode);
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), serverTab.RectTransform), style: "HorizontalLine");
//***********************************************
var voiceChatEnabled = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform),
TextManager.Get("ServerSettingsVoiceChatEnabled"));
GetPropertyData("VoiceChatEnabled").AssignGUIComponent(voiceChatEnabled);
//***********************************************
string autoRestartDelayLabel = TextManager.Get("ServerSettingsAutoRestartDelay") + " ";
var startIntervalText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), serverTab.RectTransform), autoRestartDelayLabel);
var startIntervalSlider = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), barSize: 0.1f, style: "GUISlider")
{
UserData = startIntervalText,
Step = 0.05f,
OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
{
GUITextBlock text = scrollBar.UserData as GUITextBlock;
text.Text = autoRestartDelayLabel + ToolBox.SecondsToReadableTime(scrollBar.BarScrollValue);
return true;
}
};
startIntervalSlider.Range = new Vector2(10.0f, 300.0f);
GetPropertyData("AutoRestartInterval").AssignGUIComponent(startIntervalSlider);
startIntervalSlider.OnMoved(startIntervalSlider, startIntervalSlider.BarScroll);
//***********************************************
var startWhenClientsReady = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform),
TextManager.Get("ServerSettingsStartWhenClientsReady"));
GetPropertyData("StartWhenClientsReady").AssignGUIComponent(startWhenClientsReady);
CreateLabeledSlider(serverTab, "ServerSettingsStartWhenClientsReadyRatio", out GUIScrollBar slider, out GUITextBlock sliderLabel);
string clientsReadyRequiredLabel = sliderLabel.Text;
slider.Step = 0.2f;
slider.Range = new Vector2(0.5f, 1.0f);
slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
{
((GUITextBlock)scrollBar.UserData).Text = clientsReadyRequiredLabel.Replace("[percentage]", ((int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 10.0f)).ToString());
return true;
};
GetPropertyData("StartWhenClientsReadyRatio").AssignGUIComponent(slider);
slider.OnMoved(slider, slider.BarScroll);
//***********************************************
var allowSpecBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsAllowSpectating"));
GetPropertyData("AllowSpectating").AssignGUIComponent(allowSpecBox);
var shareSubsBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsShareSubFiles"));
GetPropertyData("AllowFileTransfers").AssignGUIComponent(shareSubsBox);
var randomizeLevelBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsRandomizeSeed"));
GetPropertyData("RandomizeSeed").AssignGUIComponent(randomizeLevelBox);
var saveLogsBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsSaveLogs"))
{
OnSelected = (GUITickBox) =>
{
//TODO: fix?
//showLogButton.Visible = SaveServerLogs;
return true;
}
};
GetPropertyData("SaveServerLogs").AssignGUIComponent(saveLogsBox);
//--------------------------------------------------------------------------------
// game settings
//--------------------------------------------------------------------------------
var roundsTab = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), settingsTabs[(int)SettingsTab.Rounds].RectTransform, Anchor.Center))
{
Stretch = true,
RelativeSpacing = 0.02f
};
// Play Style Selection
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsPlayStyle"), font: GUI.SubHeadingFont);
var playstyleList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.16f), roundsTab.RectTransform))
{
AutoHideScrollBar = true,
UseGridLayout = true
};
playstyleList.Padding *= 2.0f;
List<GUITickBox> playStyleTickBoxes = new List<GUITickBox>();
GUIRadioButtonGroup selectionPlayStyle = new GUIRadioButtonGroup();
foreach (PlayStyle playStyle in Enum.GetValues(typeof(PlayStyle)))
{
var selectionTick = new GUITickBox(new RectTransform(new Vector2(0.32f, 0.49f), playstyleList.Content.RectTransform), TextManager.Get("servertag." + playStyle), font: GUI.SmallFont, style: "GUIRadioButton")
{
ToolTip = TextManager.Get("servertagdescription." + playStyle)
};
selectionPlayStyle.AddRadioButton((int)playStyle, selectionTick);
playStyleTickBoxes.Add(selectionTick);
}
GetPropertyData("PlayStyle").AssignGUIComponent(selectionPlayStyle);
GUITextBlock.AutoScaleAndNormalize(playStyleTickBoxes.Select(t => t.TextBlock));
playstyleList.RectTransform.MinSize = new Point(0, (int)(playstyleList.Content.Children.First().Rect.Height * 2.0f + playstyleList.Padding.Y + playstyleList.Padding.W));
var endBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform),
TextManager.Get("ServerSettingsEndRoundWhenDestReached"));
GetPropertyData("EndRoundAtLevelEnd").AssignGUIComponent(endBox);
var endVoteBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform),
TextManager.Get("ServerSettingsEndRoundVoting"));
GetPropertyData("AllowEndVoting").AssignGUIComponent(endVoteBox);
CreateLabeledSlider(roundsTab, "ServerSettingsEndRoundVotesRequired", out slider, out sliderLabel);
string endRoundLabel = sliderLabel.Text;
slider.Step = 0.2f;
slider.Range = new Vector2(0.5f, 1.0f);
GetPropertyData("EndVoteRequiredRatio").AssignGUIComponent(slider);
slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
{
((GUITextBlock)scrollBar.UserData).Text = endRoundLabel + " " + (int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 10.0f) + " %";
return true;
};
slider.OnMoved(slider, slider.BarScroll);
var respawnBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform),
TextManager.Get("ServerSettingsAllowRespawning"));
GetPropertyData("AllowRespawn").AssignGUIComponent(respawnBox);
CreateLabeledSlider(roundsTab, "ServerSettingsRespawnInterval", out slider, out sliderLabel);
string intervalLabel = sliderLabel.Text;
slider.Step = 0.05f;
slider.Range = new Vector2(10.0f, 600.0f);
GetPropertyData("RespawnInterval").AssignGUIComponent(slider);
slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
{
GUITextBlock text = scrollBar.UserData as GUITextBlock;
text.Text = intervalLabel + " " + ToolBox.SecondsToReadableTime(scrollBar.BarScrollValue);
return true;
};
slider.OnMoved(slider, slider.BarScroll);
var minRespawnText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), "")
{
ToolTip = TextManager.Get("ServerSettingsMinRespawnToolTip")
};
string minRespawnLabel = TextManager.Get("ServerSettingsMinRespawn") + " ";
CreateLabeledSlider(roundsTab, "", out slider, out sliderLabel);
slider.ToolTip = minRespawnText.RawToolTip;
slider.UserData = minRespawnText;
slider.Step = 0.1f;
slider.Range = new Vector2(0.0f, 1.0f);
GetPropertyData("MinRespawnRatio").AssignGUIComponent(slider);
slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
{
((GUITextBlock)scrollBar.UserData).Text = minRespawnLabel + (int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 10.0f) + " %";
return true;
};
slider.OnMoved(slider, MinRespawnRatio);
var respawnDurationText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), "")
{
ToolTip = TextManager.Get("ServerSettingsRespawnDurationToolTip")
};
string respawnDurationLabel = TextManager.Get("ServerSettingsRespawnDuration") + " ";
CreateLabeledSlider(roundsTab, "", out slider, out sliderLabel);
slider.ToolTip = respawnDurationText.RawToolTip;
slider.UserData = respawnDurationText;
slider.Step = 0.1f;
slider.Range = new Vector2(60.0f, 660.0f);
slider.ScrollToValue = (GUIScrollBar scrollBar, float barScroll) =>
{
return barScroll >= 1.0f ? 0.0f : barScroll * (scrollBar.Range.Y - scrollBar.Range.X) + scrollBar.Range.X;
};
slider.ValueToScroll = (GUIScrollBar scrollBar, float value) =>
{
return value <= 0.0f ? 1.0f : (value - scrollBar.Range.X) / (scrollBar.Range.Y - scrollBar.Range.X);
};
GetPropertyData("MaxTransportTime").AssignGUIComponent(slider);
slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
{
if (barScroll == 1.0f)
{
((GUITextBlock)scrollBar.UserData).Text = respawnDurationLabel + TextManager.Get("Unlimited");
}
else
{
((GUITextBlock)scrollBar.UserData).Text = respawnDurationLabel + ToolBox.SecondsToReadableTime(scrollBar.BarScrollValue);
}
return true;
};
slider.OnMoved(slider, slider.BarScroll);
var ragdollButtonBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsAllowRagdollButton"));
GetPropertyData("AllowRagdollButton").AssignGUIComponent(ragdollButtonBox);
/*var traitorRatioBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsUseTraitorRatio"));
CreateLabeledSlider(roundsTab, "", out slider, out sliderLabel);
var traitorRatioSlider = slider;
traitorRatioBox.OnSelected = (GUITickBox) =>
{
traitorRatioSlider.OnMoved(traitorRatioSlider, traitorRatioSlider.BarScroll);
return true;
};
if (TraitorUseRatio)
{
traitorRatioSlider.Range = new Vector2(0.1f, 1.0f);
}
else
{
traitorRatioSlider.Range = new Vector2(1.0f, maxPlayers);
}
string traitorRatioLabel = TextManager.Get("ServerSettingsTraitorRatio") + " ";
string traitorCountLabel = TextManager.Get("ServerSettingsTraitorCount") + " ";
traitorRatioSlider.Range = new Vector2(0.1f, 1.0f);
traitorRatioSlider.OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
{
GUITextBlock traitorText = scrollBar.UserData as GUITextBlock;
if (traitorRatioBox.Selected)
{
scrollBar.Step = 0.01f;
scrollBar.Range = new Vector2(0.1f, 1.0f);
traitorText.Text = traitorRatioLabel + (int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 1.0f) + " %";
}
else
{
scrollBar.Step = 1f / (maxPlayers - 1);
scrollBar.Range = new Vector2(1.0f, maxPlayers);
traitorText.Text = traitorCountLabel + scrollBar.BarScrollValue;
}
return true;
};
GetPropertyData("TraitorUseRatio").AssignGUIComponent(traitorRatioBox);
GetPropertyData("TraitorRatio").AssignGUIComponent(traitorRatioSlider);
traitorRatioSlider.OnMoved(traitorRatioSlider, traitorRatioSlider.BarScroll);
traitorRatioBox.OnSelected(traitorRatioBox);*/
var buttonHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.07f), roundsTab.RectTransform), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.05f
};
var monsterButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonHolder.RectTransform),
TextManager.Get("ServerSettingsMonsterSpawns"), style: "GUIButtonSmall")
{
Enabled = !GameMain.NetworkMember.GameStarted
};
var monsterFrame = new GUIListBox(new RectTransform(new Vector2(0.6f, 0.7f), settingsTabs[(int)SettingsTab.Rounds].RectTransform, Anchor.BottomLeft, Pivot.BottomRight))
{
Visible = false
};
monsterButton.UserData = monsterFrame;
monsterButton.OnClicked = (button, obj) =>
{
if (GameMain.NetworkMember.GameStarted)
{
((GUIComponent)obj).Visible = false;
button.Enabled = false;
return true;
}
((GUIComponent)obj).Visible = !((GUIComponent)obj).Visible;
return true;
};
InitMonstersEnabled();
List<string> monsterNames = MonsterEnabled.Keys.ToList();
tempMonsterEnabled = new Dictionary<string, bool>(MonsterEnabled);
foreach (string s in monsterNames)
{
string translatedLabel = TextManager.Get($"Character.{s}", true);
var monsterEnabledBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.1f), monsterFrame.Content.RectTransform) { MinSize = new Point(0, 25) },
label: translatedLabel ?? s)
{
Selected = tempMonsterEnabled[s],
OnSelected = (GUITickBox tb) =>
{
tempMonsterEnabled[s] = tb.Selected;
return true;
}
};
}
var cargoButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonHolder.RectTransform),
TextManager.Get("ServerSettingsAdditionalCargo"), style: "GUIButtonSmall")
{
Enabled = !GameMain.NetworkMember.GameStarted
};
var cargoFrame = new GUIListBox(new RectTransform(new Vector2(0.6f, 0.7f), settingsTabs[(int)SettingsTab.Rounds].RectTransform, Anchor.BottomRight, Pivot.BottomLeft))
{
Visible = false
};
cargoButton.UserData = cargoFrame;
cargoButton.OnClicked = (button, obj) =>
{
if (GameMain.NetworkMember.GameStarted)
{
((GUIComponent)obj).Visible = false;
button.Enabled = false;
return true;
}
((GUIComponent)obj).Visible = !((GUIComponent)obj).Visible;
return true;
};
GUITextBlock.AutoScaleAndNormalize(buttonHolder.Children.Select(c => ((GUIButton)c).TextBlock));
foreach (ItemPrefab ip in ItemPrefab.Prefabs)
{
if (!ip.CanBeBought && !ip.Tags.Contains("smallitem")) continue;
var itemFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), cargoFrame.Content.RectTransform) { MinSize = new Point(0, 30) }, isHorizontal: true)
{
Stretch = true,
UserData = cargoFrame,
RelativeSpacing = 0.05f
};
if (ip.InventoryIcon != null || ip.sprite != null)
{
GUIImage img = new GUIImage(new RectTransform(new Point(itemFrame.Rect.Height), itemFrame.RectTransform),
ip.InventoryIcon ?? ip.sprite, scaleToFit: true)
{
CanBeFocused = false
};
img.Color = img.Sprite == ip.InventoryIcon ? ip.InventoryIconColor : ip.SpriteColor;
}
new GUITextBlock(new RectTransform(new Vector2(0.75f, 1.0f), itemFrame.RectTransform),
ip.Name, font: GUI.SmallFont)
{
Wrap = true,
CanBeFocused = false
};
ExtraCargo.TryGetValue(ip, out int cargoVal);
var amountInput = new GUINumberInput(new RectTransform(new Vector2(0.35f, 1.0f), itemFrame.RectTransform),
GUINumberInput.NumberType.Int, textAlignment: Alignment.CenterLeft)
{
MinValueInt = 0,
MaxValueInt = 100,
IntValue = cargoVal
};
amountInput.OnValueChanged += (numberInput) =>
{
if (ExtraCargo.ContainsKey(ip))
{
ExtraCargo[ip] = numberInput.IntValue;
if (numberInput.IntValue <= 0) ExtraCargo.Remove(ip);
}
else
{
ExtraCargo.Add(ip, numberInput.IntValue);
}
};
}
//--------------------------------------------------------------------------------
// antigriefing
//--------------------------------------------------------------------------------
var antigriefingTab = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), settingsTabs[(int)SettingsTab.Antigriefing].RectTransform, Anchor.Center))
{
Stretch = true,
RelativeSpacing = 0.02f
};
var tickBoxContainer = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.16f), antigriefingTab.RectTransform))
{
AutoHideScrollBar = true,
UseGridLayout = true
};
tickBoxContainer.Padding *= 2.0f;
var allowFriendlyFire = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsAllowFriendlyFire"));
GetPropertyData("AllowFriendlyFire").AssignGUIComponent(allowFriendlyFire);
var allowRewiring = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsAllowRewiring"));
GetPropertyData("AllowRewiring").AssignGUIComponent(allowRewiring);
var allowDisguises = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsAllowDisguises"));
GetPropertyData("AllowDisguises").AssignGUIComponent(allowDisguises);
var voteKickBox = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsAllowVoteKick"));
GetPropertyData("AllowVoteKick").AssignGUIComponent(voteKickBox);
GUITextBlock.AutoScaleAndNormalize(tickBoxContainer.Content.Children.Select(c => ((GUITickBox)c).TextBlock));
tickBoxContainer.RectTransform.MinSize = new Point(0, (int)(tickBoxContainer.Content.Children.First().Rect.Height * 2.0f + tickBoxContainer.Padding.Y + tickBoxContainer.Padding.W));
CreateLabeledSlider(antigriefingTab, "ServerSettingsKickVotesRequired", out slider, out sliderLabel);
string votesRequiredLabel = sliderLabel.Text + " ";
slider.Step = 0.2f;
slider.Range = new Vector2(0.5f, 1.0f);
slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
{
((GUITextBlock)scrollBar.UserData).Text = votesRequiredLabel + (int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 10.0f) + " %";
return true;
};
GetPropertyData("KickVoteRequiredRatio").AssignGUIComponent(slider);
slider.OnMoved(slider, slider.BarScroll);
CreateLabeledSlider(antigriefingTab, "ServerSettingsAutobanTime", out slider, out sliderLabel);
string autobanLabel = sliderLabel.Text + " ";
slider.Step = 0.01f;
slider.Range = new Vector2(0.0f, MaxAutoBanTime);
slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
{
((GUITextBlock)scrollBar.UserData).Text = autobanLabel + ToolBox.SecondsToReadableTime(scrollBar.BarScrollValue);
return true;
};
GetPropertyData("AutoBanTime").AssignGUIComponent(slider);
slider.OnMoved(slider, slider.BarScroll);
var wrongPasswordBanBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), antigriefingTab.RectTransform), TextManager.Get("ServerSettingsBanAfterWrongPassword"));
GetPropertyData("BanAfterWrongPassword").AssignGUIComponent(wrongPasswordBanBox);
var allowedPasswordRetries = CreateLabeledNumberInput(antigriefingTab, "ServerSettingsPasswordRetriesBeforeBan", 0, 10);
GetPropertyData("MaxPasswordRetriesBeforeBan").AssignGUIComponent(allowedPasswordRetries);
wrongPasswordBanBox.OnSelected += (tb) =>
{
allowedPasswordRetries.Enabled = tb.Selected;
return true;
};
// karma --------------------------------------------------------------------------
var karmaBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), antigriefingTab.RectTransform), TextManager.Get("ServerSettingsUseKarma"));
GetPropertyData("KarmaEnabled").AssignGUIComponent(karmaBox);
karmaPresetDD = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.05f), antigriefingTab.RectTransform));
foreach (string karmaPreset in GameMain.NetworkMember.KarmaManager.Presets.Keys)
{
karmaPresetDD.AddItem(TextManager.Get("KarmaPreset." + karmaPreset), karmaPreset);
}
var karmaSettingsContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), antigriefingTab.RectTransform), style: null);
var karmaSettingsList = new GUIListBox(new RectTransform(Vector2.One, karmaSettingsContainer.RectTransform))
{
Spacing = (int)(8 * GUI.Scale)
};
karmaSettingsList.Padding *= 2.0f;
karmaSettingsBlocker = new GUIFrame(new RectTransform(Vector2.One, karmaSettingsContainer.RectTransform, Anchor.CenterLeft)
{ MaxSize = new Point(karmaSettingsList.ContentBackground.Rect.Width, int.MaxValue) }, style: null)
{
UserData = "karmasettingsblocker",
Color = Color.Black * 0.95f
};
karmaSettingsBlocker.Color *= 0.5f;
karmaPresetDD.SelectItem(KarmaPreset);
karmaSettingsBlocker.Visible = !karmaBox.Selected || KarmaPreset != "custom";
GameMain.NetworkMember.KarmaManager.CreateSettingsFrame(karmaSettingsList.Content);
karmaPresetDD.OnSelected = (selected, obj) =>
{
string newKarmaPreset = obj as string;
if (newKarmaPreset == KarmaPreset) { return true; }
List<NetPropertyData> properties = netProperties.Values.ToList();
List<object> prevValues = new List<object>();
foreach (NetPropertyData prop in netProperties.Values)
{
prevValues.Add(prop.TempValue);
if (prop.GUIComponent != null) { prop.Value = prop.GUIComponentValue; }
}
if (KarmaPreset == "custom")
{
GameMain.NetworkMember?.KarmaManager?.SaveCustomPreset();
GameMain.NetworkMember?.KarmaManager?.Save();
}
KarmaPreset = newKarmaPreset;
GameMain.NetworkMember.KarmaManager.SelectPreset(KarmaPreset);
karmaSettingsList.Content.ClearChildren();
karmaSettingsBlocker.Visible = !karmaBox.Selected || KarmaPreset != "custom";
GameMain.NetworkMember.KarmaManager.CreateSettingsFrame(karmaSettingsList.Content);
for (int i = 0; i < netProperties.Count; i++)
{
properties[i].TempValue = prevValues[i];
}
return true;
};
AssignGUIComponent("KarmaPreset", karmaPresetDD);
karmaBox.OnSelected = (tb) =>
{
karmaSettingsBlocker.Visible = !tb.Selected || KarmaPreset != "custom";
return true;
};
//--------------------------------------------------------------------------------
// banlist
//--------------------------------------------------------------------------------
BanList.CreateBanFrame(settingsTabs[(int)SettingsTab.Banlist]);
//--------------------------------------------------------------------------------
// whitelist
//--------------------------------------------------------------------------------
Whitelist.CreateWhiteListFrame(settingsTabs[(int)SettingsTab.Whitelist]);
}
private void CreateLabeledSlider(GUIComponent parent, string labelTag, out GUIScrollBar slider, out GUITextBlock label)
{
var container = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), parent.RectTransform), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.05f
};
slider = new GUIScrollBar(new RectTransform(new Vector2(0.5f, 1.0f), container.RectTransform), barSize: 0.1f, style: "GUISlider");
label = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), container.RectTransform),
string.IsNullOrEmpty(labelTag) ? "" : TextManager.Get(labelTag), textAlignment: Alignment.CenterLeft, font: GUI.SmallFont);
container.RectTransform.MinSize = new Point(0, slider.RectTransform.MinSize.Y);
container.RectTransform.MaxSize = new Point(int.MaxValue, slider.RectTransform.MaxSize.Y);
//slider has a reference to the label to change the text when it's used
slider.UserData = label;
}
private GUINumberInput CreateLabeledNumberInput(GUIComponent parent, string labelTag, int min, int max)
{
var container = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), parent.RectTransform), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.05f,
ToolTip = TextManager.Get(labelTag)
};
new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), container.RectTransform),
TextManager.Get(labelTag), textAlignment: Alignment.CenterLeft, font: GUI.SmallFont)
{
AutoScaleHorizontal = true
};
var input = new GUINumberInput(new RectTransform(new Vector2(0.3f, 1.0f), container.RectTransform), GUINumberInput.NumberType.Int)
{
MinValueInt = min,
MaxValueInt = max
};
container.RectTransform.MinSize = new Point(0, input.RectTransform.MinSize.Y);
container.RectTransform.MaxSize = new Point(int.MaxValue, input.RectTransform.MaxSize.Y);
return input;
}
private bool SelectSettingsTab(GUIButton button, object obj)
{
settingsTabIndex = (int)obj;
for (int i = 0; i < settingsTabs.Length; i++)
{
settingsTabs[i].Visible = i == settingsTabIndex;
tabButtons[i].Selected = i == settingsTabIndex;
}
return true;
}
public bool ToggleSettingsFrame(GUIButton button, object obj)
{
if (settingsFrame == null)
{
CreateSettingsFrame();
}
else
{
if (KarmaPreset == "custom")
{
GameMain.NetworkMember?.KarmaManager?.SaveCustomPreset();
GameMain.NetworkMember?.KarmaManager?.Save();
}
ClientAdminWrite(NetFlags.Properties);
foreach (NetPropertyData prop in netProperties.Values)
{
prop.GUIComponent = null;
}
settingsFrame = null;
}
return false;
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,260 @@
using Microsoft.Xna.Framework;
using OpenAL;
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
namespace Barotrauma.Networking
{
class VoipCapture : VoipQueue, IDisposable
{
public static VoipCapture Instance
{
get;
private set;
}
private IntPtr captureDevice;
private Thread captureThread;
private bool capturing;
public double LastdB
{
get;
private set;
}
public double LastAmplitude
{
get;
private set;
}
public float Gain
{
get { return GameMain.Config?.MicrophoneVolume ?? 1.0f; }
}
public DateTime LastEnqueueAudio;
public override byte QueueID
{
get
{
return GameMain.Client?.ID ?? 0;
}
protected set
{
//do nothing
}
}
public static void Create(string deviceName, UInt16? storedBufferID=null)
{
if (Instance != null)
{
throw new Exception("Tried to instance more than one VoipCapture object");
}
var capture = new VoipCapture(deviceName)
{
LatestBufferID = storedBufferID ?? BUFFER_COUNT - 1
};
if (capture.captureDevice != IntPtr.Zero)
{
Instance = capture;
}
}
private VoipCapture(string deviceName) : base(GameMain.Client?.ID ?? 0, true, false)
{
VoipConfig.SetupEncoding();
//set up capture device
captureDevice = Alc.CaptureOpenDevice(deviceName, VoipConfig.FREQUENCY, Al.FormatMono16, VoipConfig.BUFFER_SIZE * 5);
if (captureDevice == IntPtr.Zero)
{
DebugConsole.NewMessage("Alc.CaptureOpenDevice attempt 1 failed: error code " + Alc.GetError(IntPtr.Zero).ToString(),Color.Orange);
//attempt using a smaller buffer size
captureDevice = Alc.CaptureOpenDevice(deviceName, VoipConfig.FREQUENCY, Al.FormatMono16, VoipConfig.BUFFER_SIZE * 2);
}
if (captureDevice == IntPtr.Zero)
{
DebugConsole.NewMessage("Alc.CaptureOpenDevice attempt 2 failed: error code " + Alc.GetError(IntPtr.Zero).ToString(), Color.Orange);
//attempt using the default device
captureDevice = Alc.CaptureOpenDevice("", VoipConfig.FREQUENCY, Al.FormatMono16, VoipConfig.BUFFER_SIZE * 2);
}
if (captureDevice == IntPtr.Zero)
{
string errorCode = Alc.GetError(IntPtr.Zero).ToString();
if (!GUIMessageBox.MessageBoxes.Any(mb => mb.UserData as string == "capturedevicenotfound"))
{
GUI.SettingsMenuOpen = false;
new GUIMessageBox(TextManager.Get("Error"),
(TextManager.Get("VoipCaptureDeviceNotFound", returnNull: true) ?? "Could not start voice capture, suitable capture device not found.") + " (" + errorCode + ")")
{
UserData = "capturedevicenotfound"
};
}
GameAnalyticsManager.AddErrorEventOnce("Alc.CaptureDeviceOpenFailed", GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Alc.CaptureDeviceOpen(" + deviceName + ") failed. Error code: " + errorCode);
GameMain.Config.VoiceSetting = GameSettings.VoiceMode.Disabled;
Instance?.Dispose();
Instance = null;
return;
}
int alError = Al.GetError();
int alcError = Alc.GetError(captureDevice);
if (alcError != Alc.NoError)
{
throw new Exception("Failed to open capture device: " + alcError.ToString() + " (ALC)");
}
if (alError != Al.NoError)
{
throw new Exception("Failed to open capture device: " + alError.ToString() + " (AL)");
}
Alc.CaptureStart(captureDevice);
alcError = Alc.GetError(captureDevice);
if (alcError != Alc.NoError)
{
throw new Exception("Failed to start capturing: " + alcError.ToString());
}
capturing = true;
captureThread = new Thread(UpdateCapture)
{
IsBackground = true,
Name = "VoipCapture"
};
captureThread.Start();
}
public static void ChangeCaptureDevice(string deviceName)
{
GameMain.Config.VoiceCaptureDevice = deviceName;
if (Instance != null)
{
UInt16 storedBufferID = Instance.LatestBufferID;
Instance.Dispose();
Create(GameMain.Config.VoiceCaptureDevice, storedBufferID);
}
}
void UpdateCapture()
{
short[] uncompressedBuffer = new short[VoipConfig.BUFFER_SIZE];
while (capturing)
{
int alcError;
Alc.GetInteger(captureDevice, Alc.EnumCaptureSamples, out int sampleCount);
alcError = Alc.GetError(captureDevice);
if (alcError != Alc.NoError)
{
throw new Exception("Failed to determine sample count: " + alcError.ToString());
}
if (sampleCount < VoipConfig.BUFFER_SIZE)
{
int sleepMs = (VoipConfig.BUFFER_SIZE - sampleCount) * 800 / VoipConfig.FREQUENCY;
if (sleepMs < 5) sleepMs = 5;
Thread.Sleep(sleepMs);
continue;
}
GCHandle handle = GCHandle.Alloc(uncompressedBuffer, GCHandleType.Pinned);
try
{
Alc.CaptureSamples(captureDevice, handle.AddrOfPinnedObject(), VoipConfig.BUFFER_SIZE);
}
finally
{
handle.Free();
}
alcError = Alc.GetError(captureDevice);
if (alcError != Alc.NoError)
{
throw new Exception("Failed to capture samples: " + alcError.ToString());
}
double maxAmplitude = 0.0f;
for (int i = 0; i < VoipConfig.BUFFER_SIZE; i++)
{
uncompressedBuffer[i] = (short)MathHelper.Clamp((uncompressedBuffer[i] * Gain), -short.MaxValue, short.MaxValue);
double sampleVal = uncompressedBuffer[i] / (double)short.MaxValue;
maxAmplitude = Math.Max(maxAmplitude, Math.Abs(sampleVal));
}
double dB = Math.Min(20 * Math.Log10(maxAmplitude), 0.0);
LastdB = dB;
LastAmplitude = maxAmplitude;
bool allowEnqueue = false;
if (GameMain.WindowActive)
{
if (GameMain.Config.VoiceSetting == GameSettings.VoiceMode.Activity)
{
if (dB > GameMain.Config.NoiseGateThreshold)
{
allowEnqueue = true;
}
}
else if (GameMain.Config.VoiceSetting == GameSettings.VoiceMode.PushToTalk)
{
if (PlayerInput.KeyDown(InputType.Voice) && GUI.KeyboardDispatcher.Subscriber == null)
{
allowEnqueue = true;
}
}
}
if (allowEnqueue)
{
LastEnqueueAudio = DateTime.Now;
//encode audio and enqueue it
lock (buffers)
{
int compressedCount = VoipConfig.Encoder.Encode(uncompressedBuffer, 0, VoipConfig.BUFFER_SIZE, BufferToQueue, 0, VoipConfig.MAX_COMPRESSED_SIZE);
EnqueueBuffer(compressedCount);
}
}
else
{
//enqueue silence
lock (buffers)
{
EnqueueBuffer(0);
}
}
Thread.Sleep(10);
}
}
public override void Write(IWriteMessage msg)
{
lock (buffers)
{
base.Write(msg);
}
}
public override void Dispose()
{
Instance = null;
capturing = false;
captureThread?.Join();
captureThread = null;
}
}
}
@@ -0,0 +1,180 @@
using Barotrauma.Sounds;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Xna.Framework;
using Barotrauma.Items.Components;
namespace Barotrauma.Networking
{
class VoipClient : IDisposable
{
private GameClient gameClient;
private ClientPeer netClient;
private DateTime lastSendTime;
private List<VoipQueue> queues;
private UInt16 storedBufferID = 0;
private static Rectangle[] voiceIconSheetRects;
public VoipClient(GameClient gClient,ClientPeer nClient)
{
gameClient = gClient;
netClient = nClient;
queues = new List<VoipQueue>();
lastSendTime = DateTime.Now;
}
public void RegisterQueue(VoipQueue queue)
{
if (queue == VoipCapture.Instance) return;
if (!queues.Contains(queue)) queues.Add(queue);
}
public void UnregisterQueue(VoipQueue queue)
{
if (queues.Contains(queue)) queues.Remove(queue);
}
public void SendToServer()
{
if (GameMain.Config.VoiceSetting == GameSettings.VoiceMode.Disabled)
{
if (VoipCapture.Instance != null)
{
storedBufferID = VoipCapture.Instance.LatestBufferID;
VoipCapture.Instance.Dispose();
}
return;
}
else
{
if (VoipCapture.Instance == null) VoipCapture.Create(GameMain.Config.VoiceCaptureDevice, storedBufferID);
if (VoipCapture.Instance == null || VoipCapture.Instance.EnqueuedTotalLength <= 0) return;
}
if (DateTime.Now >= lastSendTime + VoipConfig.SEND_INTERVAL)
{
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.VOICE);
msg.Write((byte)VoipCapture.Instance.QueueID);
VoipCapture.Instance.Write(msg);
netClient.Send(msg, DeliveryMethod.Unreliable);
lastSendTime = DateTime.Now;
}
}
public void Read(IReadMessage msg)
{
byte queueId = msg.ReadByte();
VoipQueue queue = queues.Find(q => q.QueueID == queueId);
if (queue == null)
{
#if DEBUG
DebugConsole.NewMessage("Couldn't find VoipQueue with id " + queueId.ToString() + "!", GUI.Style.Red);
#endif
return;
}
if (queue.Read(msg))
{
Client client = gameClient.ConnectedClients.Find(c => c.VoipQueue == queue);
if (client.Muted || client.MutedLocally) { return; }
if (client.VoipSound == null)
{
DebugConsole.Log("Recreating voipsound " + queueId);
client.VoipSound = new VoipSound(client.Name, GameMain.SoundManager, client.VoipQueue);
}
if (client.Character != null && !client.Character.IsDead && !client.Character.Removed && client.Character.SpeechImpediment <= 100.0f)
{
var messageType = ChatMessage.CanUseRadio(client.Character, out WifiComponent radio) ? ChatMessageType.Radio : ChatMessageType.Default;
client.Character.ShowSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio;
if (client.VoipSound.UseRadioFilter)
{
client.VoipSound.SetRange(radio.Range * 0.8f, radio.Range);
}
else
{
client.VoipSound.SetRange(ChatMessage.SpeakRange * 0.4f, ChatMessage.SpeakRange);
}
if (!client.VoipSound.UseRadioFilter && Character.Controlled != null)
{
client.VoipSound.UseMuffleFilter = SoundPlayer.ShouldMuffleSound(Character.Controlled, client.Character.WorldPosition, ChatMessage.SpeakRange, client.Character.CurrentHull);
}
}
GameMain.NetLobbyScreen?.SetPlayerSpeaking(client);
GameMain.GameSession?.CrewManager?.SetClientSpeaking(client);
if ((client.VoipSound.CurrentAmplitude * client.VoipSound.Gain * GameMain.SoundManager.GetCategoryGainMultiplier("voip")) > 0.1f) //TODO: might need to tweak
{
if (client.Character != null && !client.Character.Removed)
{
Vector3 clientPos = new Vector3(client.Character.WorldPosition.X, client.Character.WorldPosition.Y, 0.0f);
Vector3 listenerPos = GameMain.SoundManager.ListenerPosition;
float attenuationDist = client.VoipSound.Near * 1.125f;
if (Vector3.DistanceSquared(clientPos, listenerPos) < attenuationDist * attenuationDist)
{
GameMain.SoundManager.VoipAttenuatedGain = 0.5f;
}
}
else
{
GameMain.SoundManager.VoipAttenuatedGain = 0.5f;
}
}
}
}
public static void UpdateVoiceIndicator(GUIImage soundIcon, float voipAmplitude, float deltaTime)
{
if (voiceIconSheetRects == null)
{
var soundIconStyle = GUI.Style.GetComponentStyle("GUISoundIcon");
Rectangle sourceRect = soundIconStyle.Sprites.First().Value.First().Sprite.SourceRect;
var indexPieces = soundIconStyle.Element.Attribute("sheetindices").Value.Split(';');
voiceIconSheetRects = new Rectangle[indexPieces.Length];
for (int i = 0; i < indexPieces.Length; i++)
{
Point location = sourceRect.Location + XMLExtensions.ParsePoint(indexPieces[i].Trim()) * sourceRect.Size;
voiceIconSheetRects[i] = new Rectangle(location, sourceRect.Size);
}
}
Pair<string, float> userdata = soundIcon.UserData as Pair<string, float>;
userdata.Second = Math.Max(voipAmplitude, userdata.Second - deltaTime);
if (userdata.Second <= 0.0f)
{
soundIcon.Visible = false;
}
else
{
soundIcon.Visible = true;
int sheetIndex = (int)Math.Floor(userdata.Second * voiceIconSheetRects.Length);
sheetIndex = MathHelper.Clamp(sheetIndex, 0, voiceIconSheetRects.Length - 1);
soundIcon.SourceRect = voiceIconSheetRects[sheetIndex];
soundIcon.OverrideState = GUIComponent.ComponentState.None;
soundIcon.HoverColor = Color.White;
}
}
public void Dispose()
{
VoipCapture.Instance?.Dispose();
}
}
}
@@ -0,0 +1,44 @@
using Concentus.Enums;
using Concentus.Structs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Barotrauma.Networking
{
static partial class VoipConfig
{
public static bool Ready = false;
public const int FREQUENCY = 48000; //not amazing, but not bad audio quality
public const int BUFFER_SIZE = 2880; //60ms window, the max Opus seems to support
public static OpusEncoder Encoder
{
get;
private set;
}
public static OpusDecoder Decoder
{
get;
private set;
}
public static void SetupEncoding()
{
if (!Ready)
{
Encoder = new OpusEncoder(FREQUENCY, 1, OpusApplication.OPUS_APPLICATION_VOIP);
Encoder.Bandwidth = OpusBandwidth.OPUS_BANDWIDTH_AUTO;
Encoder.Bitrate = 8 * MAX_COMPRESSED_SIZE * FREQUENCY / BUFFER_SIZE;
Encoder.SignalType = OpusSignal.OPUS_SIGNAL_VOICE;
Decoder = new OpusDecoder(FREQUENCY, 1);
Ready = true;
}
}
}
}
@@ -0,0 +1,199 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
partial class Voting
{
public bool AllowSubVoting
{
get { return allowSubVoting; }
set
{
if (value == allowSubVoting) return;
allowSubVoting = value;
GameMain.NetLobbyScreen.SubList.Enabled = value ||
(GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.SelectSub));
GameMain.NetLobbyScreen.Frame.FindChild("subvotes", true).Visible = value;
UpdateVoteTexts(null, VoteType.Sub);
GameMain.NetLobbyScreen.SubList.Deselect();
}
}
public bool AllowModeVoting
{
get { return allowModeVoting; }
set
{
if (value == allowModeVoting) return;
allowModeVoting = value;
GameMain.NetLobbyScreen.ModeList.Enabled =
value ||
(GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.SelectMode));
GameMain.NetLobbyScreen.Frame.FindChild("modevotes", true).Visible = value;
// Disable modes that cannot be voted on
foreach (var guiComponent in GameMain.NetLobbyScreen.ModeList.Content.Children)
{
if (guiComponent is GUIFrame frame)
{
frame.CanBeFocused = !allowModeVoting || ((GameModePreset) frame.UserData).Votable;
}
}
UpdateVoteTexts(null, VoteType.Mode);
GameMain.NetLobbyScreen.ModeList.Deselect();
}
}
public void UpdateVoteTexts(List<Client> clients, VoteType voteType)
{
switch (voteType)
{
case VoteType.Sub:
case VoteType.Mode:
GUIListBox listBox = (voteType == VoteType.Sub) ?
GameMain.NetLobbyScreen.SubList : GameMain.NetLobbyScreen.ModeList;
foreach (GUIComponent comp in listBox.Content.Children)
{
if (comp.FindChild("votes") is GUITextBlock voteText) comp.RemoveChild(voteText);
}
if (clients == null) return;
List<Pair<object, int>> voteList = GetVoteList(voteType, clients);
foreach (Pair<object, int> votable in voteList)
{
SetVoteText(listBox, votable.First, votable.Second);
}
break;
case VoteType.StartRound:
if (clients == null) return;
foreach (Client client in clients)
{
var clientReady = GameMain.NetLobbyScreen.PlayerList.Content.FindChild(client)?.FindChild("clientready");
if (clientReady != null)
{
clientReady.Visible = client.GetVote<bool>(VoteType.StartRound);
}
}
break;
}
}
private void SetVoteText(GUIListBox listBox, object userData, int votes)
{
if (userData == null) return;
foreach (GUIComponent comp in listBox.Content.Children)
{
if (comp.UserData != userData) continue;
GUITextBlock voteText = comp.FindChild("votes") as GUITextBlock;
if (voteText == null)
{
voteText = new GUITextBlock(new RectTransform(new Point(30, comp.Rect.Height), comp.RectTransform, Anchor.CenterRight),
"", textAlignment: Alignment.CenterRight)
{
UserData = "votes"
};
}
voteText.Text = votes == 0 ? "" : votes.ToString();
}
}
public void ClientWrite(IWriteMessage msg, VoteType voteType, object data)
{
msg.Write((byte)voteType);
switch (voteType)
{
case VoteType.Sub:
Submarine sub = data as Submarine;
if (sub == null) return;
msg.Write(sub.Name);
break;
case VoteType.Mode:
GameModePreset gameMode = data as GameModePreset;
if (gameMode == null) return;
msg.Write(gameMode.Identifier);
break;
case VoteType.EndRound:
if (!(data is bool)) return;
msg.Write((bool)data);
break;
case VoteType.Kick:
Client votedClient = data as Client;
if (votedClient == null) return;
msg.Write(votedClient.ID);
break;
case VoteType.StartRound:
if (!(data is bool)) return;
msg.Write((bool)data);
break;
}
msg.WritePadBits();
}
public void ClientRead(IReadMessage inc)
{
AllowSubVoting = inc.ReadBoolean();
if (allowSubVoting)
{
UpdateVoteTexts(null, VoteType.Sub);
int votableCount = inc.ReadByte();
for (int i = 0; i < votableCount; i++)
{
int votes = inc.ReadByte();
string subName = inc.ReadString();
List<Submarine> serversubs = new List<Submarine>();
foreach (GUIComponent item in GameMain.NetLobbyScreen?.SubList?.Content?.Children)
{
if (item.UserData != null && item.UserData is Submarine) serversubs.Add(item.UserData as Submarine);
}
Submarine sub = serversubs.FirstOrDefault(sm => sm.Name == subName);
SetVoteText(GameMain.NetLobbyScreen.SubList, sub, votes);
}
}
AllowModeVoting = inc.ReadBoolean();
if (allowModeVoting)
{
UpdateVoteTexts(null, VoteType.Mode);
int votableCount = inc.ReadByte();
for (int i = 0; i < votableCount; i++)
{
int votes = inc.ReadByte();
string modeIdentifier = inc.ReadString();
GameModePreset mode = GameModePreset.List.Find(m => m.Identifier == modeIdentifier);
SetVoteText(GameMain.NetLobbyScreen.ModeList, mode, votes);
}
}
AllowEndVoting = inc.ReadBoolean();
if (AllowEndVoting)
{
GameMain.NetworkMember.EndVoteCount = inc.ReadByte();
GameMain.NetworkMember.EndVoteMax = inc.ReadByte();
}
AllowVoteKick = inc.ReadBoolean();
GameMain.NetworkMember.ConnectedClients.ForEach(c => c.SetVote(VoteType.StartRound, false));
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);
matchingClient?.SetVote(VoteType.StartRound, true);
}
UpdateVoteTexts(GameMain.NetworkMember.ConnectedClients, VoteType.StartRound);
inc.ReadPadBits();
}
}
}
@@ -0,0 +1,250 @@
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;
localEnabled = box.Selected;
return true;
}
};
localEnabled = Enabled;
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();
}
}
}