(61d00a474) v0.9.7.1
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
partial class BannedPlayer
|
||||
{
|
||||
public string Name;
|
||||
public string IP; public bool IsRangeBan;
|
||||
public UInt64 SteamID;
|
||||
public string Reason;
|
||||
public DateTime? ExpirationTime;
|
||||
public UInt16 UniqueIdentifier;
|
||||
}
|
||||
|
||||
partial class BanList
|
||||
{
|
||||
private readonly List<BannedPlayer> bannedPlayers;
|
||||
public IEnumerable<string> BannedNames
|
||||
{
|
||||
get { return bannedPlayers.Select(bp => bp.Name); }
|
||||
}
|
||||
|
||||
public IEnumerable<string> BannedIPs
|
||||
{
|
||||
get { return bannedPlayers.Select(bp => bp.IP); }
|
||||
}
|
||||
|
||||
partial void InitProjectSpecific();
|
||||
|
||||
|
||||
public BanList()
|
||||
{
|
||||
bannedPlayers = new List<BannedPlayer>();
|
||||
InitProjectSpecific();
|
||||
}
|
||||
|
||||
public static string ToRange(string ip)
|
||||
{
|
||||
for (int i = ip.Length - 1; i > 0; i--)
|
||||
{
|
||||
if (ip[i] == '.')
|
||||
{
|
||||
ip = ip.Substring(0, i) + ".x";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
public enum ChatMessageType
|
||||
{
|
||||
Default, Error, Dead, Server, Radio, Private, Console, MessageBox, Order, ServerLog, ServerMessageBox
|
||||
}
|
||||
|
||||
partial class ChatMessage
|
||||
{
|
||||
public const int MaxLength = 150;
|
||||
|
||||
public const int MaxMessagesPerPacket = 10;
|
||||
|
||||
public const float SpeakRange = 2000.0f;
|
||||
|
||||
private static readonly string dateTimeFormatLongTimePattern = System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern;
|
||||
|
||||
public static Color[] MessageColor =
|
||||
{
|
||||
new Color(190, 198, 205), //default
|
||||
new Color(204, 74, 78), //error
|
||||
new Color(136, 177, 255), //dead
|
||||
new Color(157, 225, 160), //server
|
||||
new Color(238, 208, 0), //radio
|
||||
new Color(64, 240, 89), //private
|
||||
new Color(255, 255, 255), //console
|
||||
new Color(255, 255, 255), //messagebox
|
||||
new Color(255, 128, 0) //order
|
||||
};
|
||||
|
||||
public readonly string Text;
|
||||
|
||||
private string translatedText;
|
||||
public string TranslatedText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Type.HasFlag(ChatMessageType.Server) || Type.HasFlag(ChatMessageType.Error) || Type.HasFlag(ChatMessageType.ServerLog))
|
||||
{
|
||||
if (translatedText == null || translatedText.Length == 0)
|
||||
{
|
||||
translatedText = TextManager.GetServerMessage(Text);
|
||||
}
|
||||
|
||||
return translatedText;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ChatMessageType Type;
|
||||
|
||||
public readonly Character Sender;
|
||||
|
||||
public readonly string SenderName;
|
||||
|
||||
public Color Color
|
||||
{
|
||||
get { return MessageColor[(int)Type]; }
|
||||
}
|
||||
|
||||
public static string GetTimeStamp()
|
||||
{
|
||||
return $"[{DateTime.Now.ToString(dateTimeFormatLongTimePattern)}] ";
|
||||
}
|
||||
|
||||
public string TextWithSender
|
||||
{
|
||||
get
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(SenderName) ? TranslatedText : SenderName + ": " + TranslatedText;
|
||||
}
|
||||
}
|
||||
|
||||
public static UInt16 LastID = 0;
|
||||
|
||||
public UInt16 NetStateID
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
protected ChatMessage(string senderName, string text, ChatMessageType type, Character sender)
|
||||
{
|
||||
Text = text;
|
||||
Type = type;
|
||||
|
||||
Sender = sender;
|
||||
|
||||
SenderName = senderName;
|
||||
}
|
||||
|
||||
public static ChatMessage Create(string senderName, string text, ChatMessageType type, Character sender)
|
||||
{
|
||||
return new ChatMessage(senderName, text, type, sender);
|
||||
}
|
||||
|
||||
public static string GetChatMessageCommand(string message, out string messageWithoutCommand)
|
||||
{
|
||||
messageWithoutCommand = message;
|
||||
|
||||
int separatorIndex = message.IndexOf(";");
|
||||
if (separatorIndex == -1) return "";
|
||||
|
||||
//int colonIndex = message.IndexOf(":");
|
||||
|
||||
string command = "";
|
||||
try
|
||||
{
|
||||
command = message.Substring(0, separatorIndex);
|
||||
command = command.Trim();
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
return command;
|
||||
}
|
||||
|
||||
messageWithoutCommand = message.Substring(separatorIndex + 1, message.Length - separatorIndex - 1).TrimStart();
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
public static float GetGarbleAmount(Entity listener, Entity sender, float range, float obstructionmult = 2.0f)
|
||||
{
|
||||
if (listener == null || sender == null)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
if (listener.WorldPosition == sender.WorldPosition) { return 0.0f; }
|
||||
|
||||
float dist = Vector2.Distance(listener.WorldPosition, sender.WorldPosition);
|
||||
if (dist > range) { return 1.0f; }
|
||||
|
||||
Hull listenerHull = listener == null ? null : Hull.FindHull(listener.WorldPosition);
|
||||
Hull sourceHull = sender == null ? null : Hull.FindHull(sender.WorldPosition);
|
||||
if (sourceHull != listenerHull)
|
||||
{
|
||||
if (Submarine.CheckVisibility(listener.SimPosition, sender.SimPosition) != null) dist = (dist + 100f) * obstructionmult;
|
||||
}
|
||||
if (dist > range) { return 1.0f; }
|
||||
|
||||
return dist / range;
|
||||
}
|
||||
|
||||
public string ApplyDistanceEffect(Character listener)
|
||||
{
|
||||
if (Sender == null) return Text;
|
||||
|
||||
return ApplyDistanceEffect(listener, Sender, Text, SpeakRange);
|
||||
}
|
||||
|
||||
public static string ApplyDistanceEffect(Entity listener, Entity sender, string text, float range, float obstructionmult = 2.0f)
|
||||
{
|
||||
return ApplyDistanceEffect(text, GetGarbleAmount(listener, sender, range, obstructionmult));
|
||||
}
|
||||
|
||||
public static string ApplyDistanceEffect(string text, float garbleAmount)
|
||||
{
|
||||
if (garbleAmount < 0.3f) return text;
|
||||
if (garbleAmount >= 1.0f) return "";
|
||||
|
||||
int startIndex = Math.Max(text.IndexOf(':') + 1, 1);
|
||||
|
||||
StringBuilder sb = new StringBuilder(text.Length);
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
sb.Append((i > startIndex && Rand.Range(0.0f, 1.0f) < garbleAmount) ? '-' : text[i]);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string ApplyDistanceEffect(string message, ChatMessageType type, Character sender, Character receiver)
|
||||
{
|
||||
if (sender == null) return "";
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case ChatMessageType.Default:
|
||||
if (receiver != null && !receiver.IsDead)
|
||||
{
|
||||
return ApplyDistanceEffect(receiver, sender, message, SpeakRange * (1.0f - sender.SpeechImpediment / 100.0f), 3.0f);
|
||||
}
|
||||
break;
|
||||
case ChatMessageType.Radio:
|
||||
case ChatMessageType.Order:
|
||||
if (receiver != null && !receiver.IsDead)
|
||||
{
|
||||
var receiverItem = receiver.Inventory?.Items.FirstOrDefault(i => i?.GetComponent<WifiComponent>() != null);
|
||||
//character doesn't have a radio -> don't send
|
||||
if (receiverItem == null || !receiver.HasEquippedItem(receiverItem)) return "";
|
||||
|
||||
var senderItem = sender.Inventory?.Items.FirstOrDefault(i => i?.GetComponent<WifiComponent>() != null);
|
||||
if (senderItem == null || !sender.HasEquippedItem(senderItem)) return "";
|
||||
|
||||
var receiverRadio = receiverItem.GetComponent<WifiComponent>();
|
||||
var senderRadio = senderItem.GetComponent<WifiComponent>();
|
||||
|
||||
if (!receiverRadio.CanReceive(senderRadio)) return "";
|
||||
|
||||
string msg = ApplyDistanceEffect(receiverItem, senderItem, message, senderRadio.Range);
|
||||
if (sender.SpeechImpediment > 0.0f)
|
||||
{
|
||||
//speech impediment doesn't reduce the range when using a radio, but adds extra garbling
|
||||
msg = ApplyDistanceEffect(msg, sender.SpeechImpediment / 100.0f);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public int EstimateLengthBytesClient()
|
||||
{
|
||||
int length = 1 + //(byte)ServerNetObject.CHAT_MESSAGE
|
||||
2 + //(UInt16)NetStateID
|
||||
Encoding.UTF8.GetBytes(Text).Length + 2;
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
public static bool CanUseRadio(Character sender)
|
||||
{
|
||||
return CanUseRadio(sender, out _);
|
||||
}
|
||||
|
||||
public static bool CanUseRadio(Character sender, out WifiComponent radio)
|
||||
{
|
||||
radio = null;
|
||||
if (sender?.Inventory == null || sender.Removed) { return false; }
|
||||
radio = sender.Inventory.Items.FirstOrDefault(i => i?.GetComponent<WifiComponent>() != null)?.GetComponent<WifiComponent>();
|
||||
if (radio?.Item == null) { return false; }
|
||||
return sender.HasEquippedItem(radio.Item) && radio.CanTransmit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
static partial class ChildServerRelay
|
||||
{
|
||||
private static Stream writeStream;
|
||||
private static Stream readStream;
|
||||
private static volatile bool shutDown;
|
||||
public static bool HasShutDown
|
||||
{
|
||||
get { return shutDown; }
|
||||
}
|
||||
private static ManualResetEvent writeManualResetEvent;
|
||||
|
||||
private static byte[] tempBytes;
|
||||
private enum ReadState
|
||||
{
|
||||
WaitingForPacketStart,
|
||||
WaitingForPacketEnd
|
||||
};
|
||||
private static ReadState readState;
|
||||
private static byte[] readIncBuf;
|
||||
private static int readIncOffset;
|
||||
private static int readIncTotal;
|
||||
|
||||
private static Queue<byte[]> msgsToWrite;
|
||||
private static Queue<byte[]> msgsToRead;
|
||||
|
||||
private static Thread readThread;
|
||||
private static Thread writeThread;
|
||||
|
||||
private static CancellationTokenSource readCancellationToken;
|
||||
|
||||
private static void PrivateStart()
|
||||
{
|
||||
readState = ReadState.WaitingForPacketStart;
|
||||
readIncOffset = 0;
|
||||
readIncTotal = 0;
|
||||
|
||||
tempBytes = new byte[MsgConstants.MTU * 2];
|
||||
|
||||
msgsToWrite = new Queue<byte[]>();
|
||||
msgsToRead = new Queue<byte[]>();
|
||||
|
||||
shutDown = false;
|
||||
|
||||
readCancellationToken = new CancellationTokenSource();
|
||||
|
||||
writeManualResetEvent = new ManualResetEvent(false);
|
||||
|
||||
readThread = new Thread(UpdateRead)
|
||||
{
|
||||
Name = "ChildServerRelay.ReadThread",
|
||||
IsBackground = true
|
||||
};
|
||||
writeThread = new Thread(UpdateWrite)
|
||||
{
|
||||
Name = "ChildServerRelay.WriteThread",
|
||||
IsBackground = true
|
||||
};
|
||||
readThread.Start();
|
||||
writeThread.Start();
|
||||
}
|
||||
|
||||
private static void PrivateShutDown()
|
||||
{
|
||||
shutDown = true;
|
||||
writeManualResetEvent?.Set();
|
||||
readCancellationToken?.Cancel();
|
||||
readThread?.Join(); readThread = null;
|
||||
writeThread?.Join(); writeThread = null;
|
||||
readCancellationToken?.Dispose();
|
||||
readCancellationToken = null;
|
||||
readStream?.Dispose(); readStream = null;
|
||||
writeStream?.Dispose(); writeStream = null;
|
||||
msgsToRead?.Clear(); msgsToWrite?.Clear();
|
||||
}
|
||||
|
||||
|
||||
private static int ReadIncomingMsgs()
|
||||
{
|
||||
Task<int> readTask = readStream?.ReadAsync(tempBytes, 0, tempBytes.Length, readCancellationToken.Token);
|
||||
TimeSpan ts = TimeSpan.FromMilliseconds(100);
|
||||
for (int i = 0; i < 150; i++)
|
||||
{
|
||||
if (shutDown)
|
||||
{
|
||||
readCancellationToken?.Cancel();
|
||||
shutDown = true;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((readTask?.IsCompleted ?? true) || (readTask?.Wait(ts) ?? true))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (readTask == null || !readTask.IsCompleted)
|
||||
{
|
||||
readCancellationToken?.Cancel();
|
||||
shutDown = true;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (readTask.Status != TaskStatus.RanToCompletion)
|
||||
{
|
||||
shutDown = true;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return readTask.Result;
|
||||
}
|
||||
|
||||
|
||||
private static void UpdateRead()
|
||||
{
|
||||
while (!shutDown)
|
||||
{
|
||||
#if SERVER
|
||||
if (!((readStream as AnonymousPipeClientStream)?.IsConnected ?? false))
|
||||
{
|
||||
shutDown = true;
|
||||
return;
|
||||
}
|
||||
#else
|
||||
if (!((readStream as AnonymousPipeServerStream)?.IsConnected ?? false))
|
||||
{
|
||||
shutDown = true;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
int readLen = ReadIncomingMsgs();
|
||||
|
||||
if (readLen < 0) { shutDown = true; return; }
|
||||
|
||||
int procIndex = 0;
|
||||
|
||||
while (procIndex < readLen)
|
||||
{
|
||||
if (readState == ReadState.WaitingForPacketStart)
|
||||
{
|
||||
readIncTotal = tempBytes[procIndex];
|
||||
procIndex++;
|
||||
|
||||
if (procIndex >= readLen)
|
||||
{
|
||||
readLen = ReadIncomingMsgs();
|
||||
|
||||
if (readLen < 0) { shutDown = true; return; }
|
||||
|
||||
procIndex = 0;
|
||||
}
|
||||
|
||||
readIncTotal |= (tempBytes[procIndex] << 8);
|
||||
procIndex++;
|
||||
|
||||
if (readIncTotal <= 0) { continue; }
|
||||
|
||||
readIncOffset = 0;
|
||||
readIncBuf = new byte[readIncTotal];
|
||||
readState = ReadState.WaitingForPacketEnd;
|
||||
}
|
||||
else if (readState == ReadState.WaitingForPacketEnd)
|
||||
{
|
||||
if ((readIncTotal - readIncOffset) > (readLen - procIndex))
|
||||
{
|
||||
Array.Copy(tempBytes, procIndex, readIncBuf, readIncOffset, readLen - procIndex);
|
||||
readIncOffset += (readLen - procIndex);
|
||||
procIndex = readLen;
|
||||
}
|
||||
else
|
||||
{
|
||||
Array.Copy(tempBytes, procIndex, readIncBuf, readIncOffset, readIncTotal - readIncOffset);
|
||||
procIndex += (readIncTotal - readIncOffset);
|
||||
readIncOffset = readIncTotal;
|
||||
lock (msgsToRead)
|
||||
{
|
||||
msgsToRead.Enqueue(readIncBuf);
|
||||
}
|
||||
readIncBuf = null;
|
||||
readState = ReadState.WaitingForPacketStart;
|
||||
}
|
||||
}
|
||||
|
||||
if (shutDown) { break; }
|
||||
}
|
||||
Thread.Yield();
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateWrite()
|
||||
{
|
||||
while (!shutDown)
|
||||
{
|
||||
#if SERVER
|
||||
if (!((writeStream as AnonymousPipeClientStream)?.IsConnected ?? false))
|
||||
{
|
||||
shutDown = true;
|
||||
return;
|
||||
}
|
||||
#else
|
||||
if (!((writeStream as AnonymousPipeServerStream)?.IsConnected ?? false))
|
||||
{
|
||||
shutDown = true;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
bool msgAvailable; byte[] msg;
|
||||
lock (msgsToWrite)
|
||||
{
|
||||
msgAvailable = msgsToWrite.TryDequeue(out msg);
|
||||
}
|
||||
while (msgAvailable)
|
||||
{
|
||||
byte[] lengthBytes = new byte[2];
|
||||
lengthBytes[0] = (byte)(msg.Length & 0xFF);
|
||||
lengthBytes[1] = (byte)((msg.Length >> 8) & 0xFF);
|
||||
|
||||
msg = lengthBytes.Concat(msg).ToArray();
|
||||
|
||||
try
|
||||
{
|
||||
writeStream?.Write(msg, 0, msg.Length);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
shutDown = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (shutDown) { break; }
|
||||
|
||||
lock (msgsToWrite)
|
||||
{
|
||||
msgAvailable = msgsToWrite.TryDequeue(out msg);
|
||||
}
|
||||
}
|
||||
if (!shutDown)
|
||||
{
|
||||
writeManualResetEvent.Reset();
|
||||
if (!writeManualResetEvent.WaitOne(1000))
|
||||
{
|
||||
if (shutDown)
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
//heartbeat to keep the other end alive
|
||||
byte[] lengthBytes = new byte[2];
|
||||
lengthBytes[0] = (byte)0;
|
||||
lengthBytes[1] = (byte)0;
|
||||
writeStream?.Write(lengthBytes, 0, 2);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
shutDown = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Write(byte[] msg)
|
||||
{
|
||||
if (shutDown) { return; }
|
||||
|
||||
lock (msgsToWrite)
|
||||
{
|
||||
msgsToWrite.Enqueue(msg);
|
||||
writeManualResetEvent.Set();
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Read(out byte[] msg)
|
||||
{
|
||||
if (shutDown) { msg = null; return false; }
|
||||
|
||||
lock (msgsToRead)
|
||||
{
|
||||
return msgsToRead.TryDequeue(out msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
partial class Client : IDisposable
|
||||
{
|
||||
public const int MaxNameLength = 20;
|
||||
|
||||
public string Name; public UInt16 NameID;
|
||||
public byte ID;
|
||||
public UInt64 SteamID;
|
||||
|
||||
public string PreferredJob;
|
||||
|
||||
public Character.TeamType TeamID;
|
||||
|
||||
private Character character;
|
||||
public Character Character
|
||||
{
|
||||
get { return character; }
|
||||
set
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.NetworkMember.LastClientListUpdateID++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (value!=null)
|
||||
{
|
||||
DebugConsole.NewMessage(value.Name, Microsoft.Xna.Framework.Color.Yellow);
|
||||
}
|
||||
}
|
||||
character = value;
|
||||
if (character != null)
|
||||
{
|
||||
HasSpawned = true;
|
||||
#if CLIENT
|
||||
GameMain.GameSession?.CrewManager?.SetPlayerVoiceIconState(this, muted, mutedLocally);
|
||||
|
||||
if (character == GameMain.Client.Character && GameMain.Client.SpawnAsTraitor)
|
||||
{
|
||||
character.IsTraitor = true;
|
||||
character.TraitorCurrentObjective = GameMain.Client.TraitorFirstObjective;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 spectate_position;
|
||||
public Vector2? SpectatePos
|
||||
{
|
||||
get
|
||||
{
|
||||
if (character == null || character.IsDead)
|
||||
{
|
||||
return spectate_position;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
spectate_position = value.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private bool muted;
|
||||
public bool Muted
|
||||
{
|
||||
get { return muted; }
|
||||
set
|
||||
{
|
||||
if (muted == value) { return; }
|
||||
muted = value;
|
||||
#if CLIENT
|
||||
GameMain.NetLobbyScreen.SetPlayerVoiceIconState(this, muted, mutedLocally);
|
||||
GameMain.GameSession?.CrewManager?.SetPlayerVoiceIconState(this, muted, mutedLocally);
|
||||
#endif
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.NetworkMember.LastClientListUpdateID++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public VoipQueue VoipQueue
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool InGame;
|
||||
public bool HasSpawned; //has the client spawned as a character during the current round
|
||||
|
||||
private List<Client> kickVoters;
|
||||
|
||||
public HashSet<string> GivenAchievements = new HashSet<string>();
|
||||
|
||||
public ClientPermissions Permissions = ClientPermissions.None;
|
||||
public List<DebugConsole.Command> PermittedConsoleCommands
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private object[] votes;
|
||||
|
||||
public int KickVoteCount
|
||||
{
|
||||
get { return kickVoters.Count; }
|
||||
}
|
||||
|
||||
/*public Client(NetPeer server, string name, byte ID)
|
||||
: this(name, ID)
|
||||
{
|
||||
|
||||
}*/
|
||||
|
||||
partial void InitProjSpecific();
|
||||
partial void DisposeProjSpecific();
|
||||
public Client(string name, byte ID)
|
||||
{
|
||||
this.Name = name;
|
||||
this.ID = ID;
|
||||
|
||||
PermittedConsoleCommands = new List<DebugConsole.Command>();
|
||||
kickVoters = new List<Client>();
|
||||
|
||||
votes = new object[Enum.GetNames(typeof(VoteType)).Length];
|
||||
|
||||
InitProjSpecific();
|
||||
}
|
||||
|
||||
public T GetVote<T>(VoteType voteType)
|
||||
{
|
||||
return (votes[(int)voteType] is T) ? (T)votes[(int)voteType] : default(T);
|
||||
}
|
||||
|
||||
public void SetVote(VoteType voteType, object value)
|
||||
{
|
||||
votes[(int)voteType] = value;
|
||||
}
|
||||
|
||||
public void ResetVotes()
|
||||
{
|
||||
for (int i = 0; i < votes.Length; i++)
|
||||
{
|
||||
votes[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddKickVote(Client voter)
|
||||
{
|
||||
if (!kickVoters.Contains(voter)) kickVoters.Add(voter);
|
||||
}
|
||||
|
||||
|
||||
public void RemoveKickVote(Client voter)
|
||||
{
|
||||
kickVoters.Remove(voter);
|
||||
}
|
||||
|
||||
public bool HasKickVoteFrom(Client voter)
|
||||
{
|
||||
return kickVoters.Contains(voter);
|
||||
}
|
||||
|
||||
public bool HasKickVoteFromID(int id)
|
||||
{
|
||||
return kickVoters.Any(k => k.ID == id);
|
||||
}
|
||||
|
||||
|
||||
public static void UpdateKickVotes(List<Client> connectedClients)
|
||||
{
|
||||
foreach (Client client in connectedClients)
|
||||
{
|
||||
client.kickVoters.RemoveAll(voter => !connectedClients.Contains(voter));
|
||||
}
|
||||
}
|
||||
|
||||
public void WritePermissions(IWriteMessage msg)
|
||||
{
|
||||
msg.Write(ID);
|
||||
msg.Write((UInt16)Permissions);
|
||||
if (HasPermission(ClientPermissions.ConsoleCommands))
|
||||
{
|
||||
msg.Write((UInt16)PermittedConsoleCommands.Count);
|
||||
foreach (DebugConsole.Command command in PermittedConsoleCommands)
|
||||
{
|
||||
msg.Write(command.names[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
public static void ReadPermissions(IReadMessage inc, out ClientPermissions permissions, out List<DebugConsole.Command> permittedCommands)
|
||||
{
|
||||
UInt16 permissionsInt = inc.ReadUInt16();
|
||||
|
||||
permissions = ClientPermissions.None;
|
||||
permittedCommands = new List<DebugConsole.Command>();
|
||||
try
|
||||
{
|
||||
permissions = (ClientPermissions)permissionsInt;
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (permissions.HasFlag(ClientPermissions.ConsoleCommands))
|
||||
{
|
||||
UInt16 commandCount = inc.ReadUInt16();
|
||||
for (int i = 0; i < commandCount; i++)
|
||||
{
|
||||
string commandName = inc.ReadString();
|
||||
var consoleCommand = DebugConsole.Commands.Find(c => c.names.Contains(commandName));
|
||||
if (consoleCommand != null)
|
||||
{
|
||||
permittedCommands.Add(consoleCommand);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ReadPermissions(IReadMessage inc)
|
||||
{
|
||||
ClientPermissions permissions = ClientPermissions.None;
|
||||
List<DebugConsole.Command> permittedCommands = new List<DebugConsole.Command>();
|
||||
ReadPermissions(inc, out permissions, out permittedCommands);
|
||||
SetPermissions(permissions, permittedCommands);
|
||||
}
|
||||
|
||||
public static string SanitizeName(string name)
|
||||
{
|
||||
name = name.Trim();
|
||||
if (name.Length > MaxNameLength)
|
||||
{
|
||||
name = name.Substring(0, MaxNameLength);
|
||||
}
|
||||
string rName = "";
|
||||
for (int i = 0; i < name.Length; i++)
|
||||
{
|
||||
rName += name[i] < 32 ? '?' : name[i];
|
||||
}
|
||||
return rName;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DisposeProjSpecific();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
[Flags]
|
||||
public enum ClientPermissions
|
||||
{
|
||||
None = 0x0,
|
||||
ManageRound = 0x1,
|
||||
Kick = 0x2,
|
||||
Ban = 0x4,
|
||||
Unban = 0x8,
|
||||
SelectSub = 0x10,
|
||||
SelectMode = 0x20,
|
||||
ManageCampaign = 0x40,
|
||||
ConsoleCommands = 0x80,
|
||||
ServerLog = 0x100,
|
||||
ManageSettings = 0x200,
|
||||
ManagePermissions = 0x400,
|
||||
KarmaImmunity = 0x800,
|
||||
All = 0xFFF
|
||||
}
|
||||
|
||||
class PermissionPreset
|
||||
{
|
||||
public static List<PermissionPreset> List = new List<PermissionPreset>();
|
||||
|
||||
public readonly string Name;
|
||||
public readonly string Description;
|
||||
public readonly ClientPermissions Permissions;
|
||||
public readonly List<DebugConsole.Command> PermittedCommands;
|
||||
|
||||
public PermissionPreset(XElement element)
|
||||
{
|
||||
string name = element.GetAttributeString("name", "");
|
||||
Name = TextManager.Get("permissionpresetname." + name, true) ?? name;
|
||||
Description = TextManager.Get("permissionpresetdescription." + name, true) ?? element.GetAttributeString("description", "");
|
||||
|
||||
string permissionsStr = element.GetAttributeString("permissions", "");
|
||||
if (!Enum.TryParse(permissionsStr, out Permissions))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in permission preset \"" + Name + "\" - " + permissionsStr + " is not a valid permission!");
|
||||
}
|
||||
|
||||
PermittedCommands = new List<DebugConsole.Command>();
|
||||
if (Permissions.HasFlag(ClientPermissions.ConsoleCommands))
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (!subElement.Name.ToString().Equals("command", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
string commandName = subElement.GetAttributeString("name", "");
|
||||
|
||||
DebugConsole.Command command = DebugConsole.FindCommand(commandName);
|
||||
if (command == null)
|
||||
{
|
||||
#if SERVER
|
||||
DebugConsole.ThrowError("Error in permission preset \"" + Name + "\" - " + commandName + "\" is not a valid console command.");
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
PermittedCommands.Add(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadAll(string file)
|
||||
{
|
||||
if (!File.Exists(file)) { return; }
|
||||
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file);
|
||||
if (doc == null) { return; }
|
||||
|
||||
List.Clear();
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
List.Add(new PermissionPreset(element));
|
||||
}
|
||||
}
|
||||
|
||||
public bool MatchesPermissions(ClientPermissions permissions, List<DebugConsole.Command> permittedConsoleCommands)
|
||||
{
|
||||
return permissions == this.Permissions && PermittedCommands.SequenceEqual(permittedConsoleCommands);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class EntitySpawner : Entity, IServerSerializable
|
||||
{
|
||||
private enum SpawnableType { Item, Character };
|
||||
|
||||
interface IEntitySpawnInfo
|
||||
{
|
||||
Entity Spawn();
|
||||
void OnSpawned(Entity entity);
|
||||
}
|
||||
|
||||
class ItemSpawnInfo : IEntitySpawnInfo
|
||||
{
|
||||
public readonly ItemPrefab Prefab;
|
||||
|
||||
public readonly Vector2 Position;
|
||||
public readonly Inventory Inventory;
|
||||
public readonly Submarine Submarine;
|
||||
public readonly float Condition;
|
||||
|
||||
private readonly Action<Item> onSpawned;
|
||||
|
||||
public ItemSpawnInfo(ItemPrefab prefab, Vector2 worldPosition, Action<Item> onSpawned, float? condition = null)
|
||||
{
|
||||
Prefab = prefab ?? throw new ArgumentException("ItemSpawnInfo prefab cannot be null.");
|
||||
Position = worldPosition;
|
||||
Condition = condition ?? prefab.Health;
|
||||
this.onSpawned = onSpawned;
|
||||
}
|
||||
|
||||
public ItemSpawnInfo(ItemPrefab prefab, Vector2 position, Submarine sub, Action<Item> onSpawned, float? condition = null)
|
||||
{
|
||||
Prefab = prefab ?? throw new ArgumentException("ItemSpawnInfo prefab cannot be null.");
|
||||
Position = position;
|
||||
Submarine = sub;
|
||||
Condition = condition ?? prefab.Health;
|
||||
this.onSpawned = onSpawned;
|
||||
}
|
||||
|
||||
public ItemSpawnInfo(ItemPrefab prefab, Inventory inventory, Action<Item> onSpawned, float? condition = null)
|
||||
{
|
||||
Prefab = prefab ?? throw new ArgumentException("ItemSpawnInfo prefab cannot be null.");
|
||||
Inventory = inventory;
|
||||
Condition = condition ?? prefab.Health;
|
||||
this.onSpawned = onSpawned;
|
||||
}
|
||||
|
||||
public Entity Spawn()
|
||||
{
|
||||
if (Prefab == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
Item spawnedItem;
|
||||
if (Inventory != null)
|
||||
{
|
||||
spawnedItem = new Item(Prefab, Vector2.Zero, null);
|
||||
Inventory.TryPutItem(spawnedItem, null, spawnedItem.AllowedSlots);
|
||||
}
|
||||
else
|
||||
{
|
||||
spawnedItem = new Item(Prefab, Position, Submarine);
|
||||
}
|
||||
return spawnedItem;
|
||||
}
|
||||
|
||||
public void OnSpawned(Entity spawnedItem)
|
||||
{
|
||||
if (!(spawnedItem is Item item)) { throw new ArgumentException($"The entity passed to ItemSpawnInfo.OnSpawned must be an Item (value was {spawnedItem?.ToString() ?? "null"})."); }
|
||||
onSpawned?.Invoke(item);
|
||||
}
|
||||
}
|
||||
|
||||
class CharacterSpawnInfo : IEntitySpawnInfo
|
||||
{
|
||||
public readonly string identifier;
|
||||
|
||||
public readonly Vector2 Position;
|
||||
public readonly Submarine Submarine;
|
||||
|
||||
private readonly Action<Character> onSpawned;
|
||||
|
||||
public CharacterSpawnInfo(string identifier, Vector2 worldPosition, Action<Character> onSpawn = null)
|
||||
{
|
||||
this.identifier = identifier ?? throw new ArgumentException("ItemSpawnInfo prefab cannot be null.");
|
||||
Position = worldPosition;
|
||||
this.onSpawned = onSpawn;
|
||||
}
|
||||
|
||||
public CharacterSpawnInfo(string identifier, Vector2 position, Submarine sub, Action<Character> onSpawn = null)
|
||||
{
|
||||
this.identifier = identifier ?? throw new ArgumentException("ItemSpawnInfo prefab cannot be null.");
|
||||
Position = position;
|
||||
Submarine = sub;
|
||||
this.onSpawned = onSpawn;
|
||||
}
|
||||
|
||||
|
||||
public Entity Spawn()
|
||||
{
|
||||
var character = string.IsNullOrEmpty(identifier) ? null :
|
||||
Character.Create(identifier,
|
||||
Submarine == null ? Position : Submarine.Position + Position,
|
||||
ToolBox.RandomSeed(8), createNetworkEvent: false);
|
||||
return character;
|
||||
}
|
||||
|
||||
public void OnSpawned(Entity spawnedCharacter)
|
||||
{
|
||||
if (!(spawnedCharacter is Character character)) { throw new ArgumentException($"The entity passed to CharacterSpawnInfo.OnSpawned must be a Character (value was {spawnedCharacter?.ToString() ?? "null"})."); }
|
||||
onSpawned?.Invoke(character);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Queue<IEntitySpawnInfo> spawnQueue;
|
||||
private readonly Queue<Entity> removeQueue;
|
||||
|
||||
public class SpawnOrRemove
|
||||
{
|
||||
public readonly Entity Entity;
|
||||
|
||||
public readonly UInt16 OriginalID;
|
||||
|
||||
public readonly bool Remove = false;
|
||||
|
||||
public SpawnOrRemove(Entity entity, bool remove)
|
||||
{
|
||||
Entity = entity;
|
||||
OriginalID = entity.ID;
|
||||
Remove = remove;
|
||||
}
|
||||
}
|
||||
|
||||
public EntitySpawner()
|
||||
: base(null)
|
||||
{
|
||||
spawnQueue = new Queue<IEntitySpawnInfo>();
|
||||
removeQueue = new Queue<Entity>();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "EntitySpawner";
|
||||
}
|
||||
|
||||
public void AddToSpawnQueue(ItemPrefab itemPrefab, Vector2 worldPosition, float? condition = null, Action<Item> onSpawned = null)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
string errorMsg = "Attempted to add a null item to entity spawn queue.\n" + Environment.StackTrace;
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue1:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
return;
|
||||
}
|
||||
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, worldPosition, onSpawned, condition));
|
||||
}
|
||||
|
||||
public void AddToSpawnQueue(ItemPrefab itemPrefab, Vector2 position, Submarine sub, float? condition = null, Action<Item> onSpawned = null)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
string errorMsg = "Attempted to add a null item to entity spawn queue.\n" + Environment.StackTrace;
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue2:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
return;
|
||||
}
|
||||
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, position, sub, onSpawned, condition));
|
||||
}
|
||||
|
||||
public void AddToSpawnQueue(ItemPrefab itemPrefab, Inventory inventory, float? condition = null, Action<Item> onSpawned = null)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
string errorMsg = "Attempted to add a null item to entity spawn queue.\n" + Environment.StackTrace;
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue3:ItemPrefabNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
return;
|
||||
}
|
||||
spawnQueue.Enqueue(new ItemSpawnInfo(itemPrefab, inventory, onSpawned, condition));
|
||||
}
|
||||
|
||||
public void AddToSpawnQueue(string speciesName, Vector2 worldPosition, Action<Character> onSpawn = null)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (string.IsNullOrEmpty(speciesName))
|
||||
{
|
||||
string errorMsg = "Attempted to add an empty/null species name to entity spawn queue.\n" + Environment.StackTrace;
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue4:SpeciesNameNullOrEmpty", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
return;
|
||||
}
|
||||
spawnQueue.Enqueue(new CharacterSpawnInfo(speciesName, worldPosition, onSpawn));
|
||||
}
|
||||
|
||||
public void AddToSpawnQueue(string speciesName, Vector2 position, Submarine sub, Action<Character> onSpawn = null)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (string.IsNullOrEmpty(speciesName))
|
||||
{
|
||||
string errorMsg = "Attempted to add an empty/null species name to entity spawn queue.\n" + Environment.StackTrace;
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue5:SpeciesNameNullOrEmpty", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
return;
|
||||
}
|
||||
spawnQueue.Enqueue(new CharacterSpawnInfo(speciesName, position, sub, onSpawn));
|
||||
}
|
||||
|
||||
public void AddToRemoveQueue(Entity entity)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (removeQueue.Contains(entity) || entity.Removed || entity == null) { return; }
|
||||
if (entity is Character)
|
||||
{
|
||||
Character character = entity as Character;
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
Client client = GameMain.Server.ConnectedClients.Find(c => c.Character == character);
|
||||
if (client != null) GameMain.Server.SetClientCharacter(client, null);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
removeQueue.Enqueue(entity);
|
||||
}
|
||||
|
||||
public void AddToRemoveQueue(Item item)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (removeQueue.Contains(item) || item.Removed) { return; }
|
||||
|
||||
removeQueue.Enqueue(item);
|
||||
if (item.ContainedItems == null) return;
|
||||
foreach (Item containedItem in item.ContainedItems)
|
||||
{
|
||||
if (containedItem != null) AddToRemoveQueue(containedItem);
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
while (spawnQueue.Count > 0)
|
||||
{
|
||||
var entitySpawnInfo = spawnQueue.Dequeue();
|
||||
|
||||
var spawnedEntity = entitySpawnInfo.Spawn();
|
||||
if (spawnedEntity != null)
|
||||
{
|
||||
CreateNetworkEventProjSpecific(spawnedEntity, false);
|
||||
if (spawnedEntity is Item)
|
||||
{
|
||||
((Item)spawnedEntity).Condition = ((ItemSpawnInfo)entitySpawnInfo).Condition;
|
||||
}
|
||||
entitySpawnInfo.OnSpawned(spawnedEntity);
|
||||
}
|
||||
}
|
||||
|
||||
while (removeQueue.Count > 0)
|
||||
{
|
||||
var removedEntity = removeQueue.Dequeue();
|
||||
if (removedEntity is Item item)
|
||||
{
|
||||
item.SendPendingNetworkUpdates();
|
||||
}
|
||||
CreateNetworkEventProjSpecific(removedEntity, true);
|
||||
removedEntity.Remove();
|
||||
}
|
||||
}
|
||||
|
||||
partial void CreateNetworkEventProjSpecific(Entity entity, bool remove);
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
removeQueue.Clear();
|
||||
spawnQueue.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
enum FileTransferStatus
|
||||
{
|
||||
NotStarted, Sending, Receiving, Finished, Canceled, Error
|
||||
}
|
||||
|
||||
enum FileTransferMessageType
|
||||
{
|
||||
Unknown, Initiate, Data, TransferOnSameMachine, Cancel
|
||||
}
|
||||
|
||||
enum FileTransferType
|
||||
{
|
||||
Submarine, CampaignSave
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
interface INetSerializable { }
|
||||
|
||||
/// <summary>
|
||||
/// Interface for entities that the clients can send information of to the server
|
||||
/// </summary>
|
||||
interface IClientSerializable : INetSerializable
|
||||
{
|
||||
#if CLIENT
|
||||
void ClientWrite(IWriteMessage msg, object[] extraData = null);
|
||||
#endif
|
||||
#if SERVER
|
||||
void ServerRead(ClientNetObject type, IReadMessage msg, Client c);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for entities that the server can send information of to the clients
|
||||
/// </summary>
|
||||
interface IServerSerializable : INetSerializable
|
||||
{
|
||||
#if SERVER
|
||||
void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null);
|
||||
#endif
|
||||
#if CLIENT
|
||||
void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class KarmaManager : ISerializableEntity
|
||||
{
|
||||
public static readonly string ConfigFile = "Data" + Path.DirectorySeparatorChar + "karmasettings.xml";
|
||||
|
||||
public string Name => "KarmaManager";
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; private set; }
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool ResetKarmaBetweenRounds { get; set; }
|
||||
|
||||
[Serialize(0.1f, true)]
|
||||
public float KarmaDecay { get; set; }
|
||||
|
||||
[Serialize(50.0f, true)]
|
||||
public float KarmaDecayThreshold { get; set; }
|
||||
|
||||
[Serialize(0.15f, true)]
|
||||
public float KarmaIncrease { get; set; }
|
||||
|
||||
[Serialize(50.0f, true)]
|
||||
public float KarmaIncreaseThreshold { get; set; }
|
||||
|
||||
[Serialize(0.05f, true)]
|
||||
public float StructureRepairKarmaIncrease { get; set; }
|
||||
[Serialize(0.1f, true)]
|
||||
public float StructureDamageKarmaDecrease { get; set; }
|
||||
[Serialize(30.0f, true)]
|
||||
public float MaxStructureDamageKarmaDecreasePerSecond { get; set; }
|
||||
|
||||
[Serialize(0.03f, true)]
|
||||
public float ItemRepairKarmaIncrease { get; set; }
|
||||
|
||||
[Serialize(0.5f, true)]
|
||||
public float ReactorOverheatKarmaDecrease { get; set; }
|
||||
[Serialize(30.0f, true)]
|
||||
public float ReactorMeltdownKarmaDecrease { get; set; }
|
||||
|
||||
[Serialize(0.1f, true)]
|
||||
public float DamageEnemyKarmaIncrease { get; set; }
|
||||
[Serialize(0.2f, true)]
|
||||
public float HealFriendlyKarmaIncrease { get; set; }
|
||||
[Serialize(0.25f, true)]
|
||||
public float DamageFriendlyKarmaDecrease { get; set; }
|
||||
|
||||
[Serialize(1.0f, true)]
|
||||
public float ExtinguishFireKarmaIncrease { get; set; }
|
||||
|
||||
|
||||
private int allowedWireDisconnectionsPerMinute;
|
||||
[Serialize(5, true)]
|
||||
public int AllowedWireDisconnectionsPerMinute
|
||||
{
|
||||
get { return allowedWireDisconnectionsPerMinute; }
|
||||
set { allowedWireDisconnectionsPerMinute = Math.Max(0, value); }
|
||||
}
|
||||
|
||||
[Serialize(6.0f, true)]
|
||||
public float WireDisconnectionKarmaDecrease { get; set; }
|
||||
|
||||
[Serialize(0.15f, true)]
|
||||
public float SteerSubKarmaIncrease { get; set; }
|
||||
|
||||
[Serialize(15.0f, true)]
|
||||
public float SpamFilterKarmaDecrease { get; set; }
|
||||
|
||||
[Serialize(40.0f, true)]
|
||||
public float HerpesThreshold { get; set; }
|
||||
|
||||
[Serialize(1.0f, true)]
|
||||
public float KickBanThreshold { get; set; }
|
||||
|
||||
[Serialize(0, true)]
|
||||
public int KicksBeforeBan { get; set; }
|
||||
|
||||
[Serialize(10.0f, true)]
|
||||
public float KarmaNotificationInterval { get; set; }
|
||||
|
||||
[Serialize(120.0f, true)]
|
||||
public float AllowedRetaliationTime { get; set; }
|
||||
|
||||
private readonly AfflictionPrefab herpesAffliction;
|
||||
|
||||
public Dictionary<string, XElement> Presets = new Dictionary<string, XElement>();
|
||||
|
||||
public KarmaManager()
|
||||
{
|
||||
XDocument doc = null;
|
||||
int maxLoadRetries = 4;
|
||||
for (int i = 0; i <= maxLoadRetries; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
doc = XMLExtensions.TryLoadXml(ConfigFile);
|
||||
break;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
if (i == maxLoadRetries) { break; }
|
||||
DebugConsole.NewMessage("Opening karma settings file \"" + ConfigFile + "\" failed, retrying in 250 ms...");
|
||||
System.Threading.Thread.Sleep(250);
|
||||
}
|
||||
}
|
||||
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, doc?.Root);
|
||||
if (doc?.Root != null)
|
||||
{
|
||||
Presets["custom"] = doc.Root;
|
||||
foreach (XElement subElement in doc.Root.Elements())
|
||||
{
|
||||
string presetName = subElement.GetAttributeString("name", "");
|
||||
Presets[presetName.ToLowerInvariant()] = subElement;
|
||||
}
|
||||
SelectPreset(GameMain.NetworkMember?.ServerSettings?.KarmaPreset ?? "default");
|
||||
}
|
||||
herpesAffliction = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier == "spaceherpes");
|
||||
}
|
||||
|
||||
public void SelectPreset(string presetName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(presetName)) { return; }
|
||||
presetName = presetName.ToLowerInvariant();
|
||||
|
||||
if (Presets.ContainsKey(presetName))
|
||||
{
|
||||
SerializableProperty.DeserializeProperties(this, Presets[presetName]);
|
||||
}
|
||||
else if (Presets.ContainsKey("custom"))
|
||||
{
|
||||
SerializableProperty.DeserializeProperties(this, Presets["custom"]);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveCustomPreset()
|
||||
{
|
||||
if (Presets.ContainsKey("custom"))
|
||||
{
|
||||
SerializableProperty.SerializeProperties(this, Presets["custom"], saveIfDefault: true);
|
||||
}
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
XDocument doc = new XDocument(new XElement(Name));
|
||||
|
||||
foreach (KeyValuePair<string, XElement> preset in Presets)
|
||||
{
|
||||
doc.Root.Add(preset.Value);
|
||||
}
|
||||
|
||||
XmlWriterSettings settings = new XmlWriterSettings
|
||||
{
|
||||
Indent = true,
|
||||
NewLineOnAttributes = true
|
||||
};
|
||||
|
||||
int maxLoadRetries = 4;
|
||||
for (int i = 0; i <= maxLoadRetries; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var writer = XmlWriter.Create(ConfigFile, settings))
|
||||
{
|
||||
doc.Save(writer);
|
||||
}
|
||||
break;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
if (i == maxLoadRetries) { throw; }
|
||||
|
||||
DebugConsole.NewMessage("Saving karma settings file file \"" + ConfigFile + "\" failed, retrying in 250 ms...");
|
||||
System.Threading.Thread.Sleep(250);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
static class NetBufferExtensions
|
||||
{
|
||||
//public static void WriteEnum(this NetBuffer buffer, Enum value)
|
||||
//{
|
||||
// buffer.WriteRangedInteger(0, Enum.GetValues(value.GetType()).Length - 1, Convert.ToInt32(value));
|
||||
//}
|
||||
|
||||
//public static TEnum ReadEnum<TEnum>(this NetBuffer buffer)
|
||||
//{
|
||||
// return (TEnum)(object)buffer.ReadRangedInteger(0, Enum.GetValues(typeof(TEnum)).Length - 1);
|
||||
//}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
static class NetConfig
|
||||
{
|
||||
public const int DefaultPort = 27015;
|
||||
public const int DefaultQueryPort = 27016;
|
||||
|
||||
public const int MaxPlayers = 16;
|
||||
|
||||
public const int ServerNameMaxLength = 60;
|
||||
|
||||
public static string MasterServerUrl = GameMain.Config.MasterServerUrl;
|
||||
|
||||
//if a Character is further than this from the sub and the players, the server will disable it
|
||||
//(in display units)
|
||||
public const float DisableCharacterDist = 22000.0f;
|
||||
public const float DisableCharacterDistSqr = DisableCharacterDist * DisableCharacterDist;
|
||||
|
||||
//the character needs to get this close to be re-enabled
|
||||
public const float EnableCharacterDist = 20000.0f;
|
||||
public const float EnableCharacterDistSqr = EnableCharacterDist * EnableCharacterDist;
|
||||
|
||||
public const float MaxPhysicsBodyVelocity = 64.0f;
|
||||
public const float MaxPhysicsBodyAngularVelocity = 16.0f;
|
||||
|
||||
public const float MaxHealthUpdateInterval = 2.0f;
|
||||
public const float MaxHealthUpdateIntervalDead = 10.0f;
|
||||
|
||||
public const float HighPrioCharacterPositionUpdateDistance = 1000.0f;
|
||||
public const float LowPrioCharacterPositionUpdateDistance = 10000.0f;
|
||||
public const float HighPrioCharacterPositionUpdateInterval = 0.0f;
|
||||
public const float LowPrioCharacterPositionUpdateInterval = 1.0f;
|
||||
|
||||
//this should be higher than LowPrioCharacterPositionUpdateInterval,
|
||||
//otherwise the clients may freeze characters even though the server hasn't actually stopped sending position updates
|
||||
public const float FreezeCharacterIfPositionDataMissingDelay = 2.0f;
|
||||
public const float DisableCharacterIfPositionDataMissingDelay = 3.5f;
|
||||
|
||||
public const float DeleteDisconnectedTime = 20.0f;
|
||||
|
||||
public const float ItemConditionUpdateInterval = 0.15f;
|
||||
public const float LevelObjectUpdateInterval = 0.5f;
|
||||
public const float HullUpdateInterval = 0.5f;
|
||||
public const float SparseHullUpdateInterval = 5.0f;
|
||||
public const float HullUpdateDistance = 20000.0f;
|
||||
|
||||
public const int MaxEventPacketsPerUpdate = 4;
|
||||
|
||||
/// <summary>
|
||||
/// Interpolates the positional error of a physics body towards zero.
|
||||
/// </summary>
|
||||
public static Vector2 InterpolateSimPositionError(Vector2 simPositionError, float? smoothingFactor = null)
|
||||
{
|
||||
float lengthSqr = simPositionError.LengthSquared();
|
||||
//correct immediately if the error is very large
|
||||
if (lengthSqr > 100.0f) { return Vector2.Zero; }
|
||||
float positionSmoothingFactor = smoothingFactor ?? MathHelper.Lerp(0.95f, 0.8f, MathHelper.Clamp(lengthSqr, 0.0f, 1.0f));
|
||||
return simPositionError *= positionSmoothingFactor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interpolates the rotational error of a physics body towards zero.
|
||||
/// </summary>
|
||||
public static float InterpolateRotationError(float rotationError)
|
||||
{
|
||||
//correct immediately if the error is very large
|
||||
if (rotationError > MathHelper.TwoPi) { return 0.0f; }
|
||||
float rotationSmoothingFactor = MathHelper.Lerp(0.95f, 0.8f, Math.Min(Math.Abs(rotationError), 1.0f));
|
||||
return rotationError *= rotationSmoothingFactor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interpolates the cursor position error towards zero.
|
||||
/// </summary>
|
||||
public static Vector2 InterpolateCursorPositionError(Vector2 cursorPositionError)
|
||||
{
|
||||
float lengthSqr = cursorPositionError.LengthSquared();
|
||||
//correct immediately if the error is very large
|
||||
if (lengthSqr > 1000.0f) { return Vector2.Zero; }
|
||||
return cursorPositionError *= 0.7f;
|
||||
}
|
||||
|
||||
public static Vector2 Quantize(Vector2 value, float min, float max, int numberOfBits)
|
||||
{
|
||||
return new Vector2(
|
||||
Quantize(value.X, min, max, numberOfBits),
|
||||
Quantize(value.Y, min, max, numberOfBits));
|
||||
}
|
||||
|
||||
public static float Quantize(float value, float min, float max, int numberOfBits)
|
||||
{
|
||||
float step = (max - min) / (1 << (numberOfBits + 1));
|
||||
if (Math.Abs(value) < step + 0.00001f)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return MathUtils.RoundTowardsClosest(MathHelper.Clamp(value, min, max), step);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
abstract class NetEntityEvent
|
||||
{
|
||||
public enum Type
|
||||
{
|
||||
Invalid,
|
||||
ComponentState,
|
||||
InventoryState,
|
||||
Status,
|
||||
Treatment,
|
||||
ApplyStatusEffect,
|
||||
ChangeProperty,
|
||||
Control,
|
||||
UpdateSkills,
|
||||
Combine
|
||||
}
|
||||
|
||||
public readonly Entity Entity;
|
||||
public readonly UInt16 ID;
|
||||
|
||||
public UInt16 EntityID
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
//arbitrary extra data that will be passed to the Write method of the serializable entity
|
||||
//(the index of an itemcomponent for example)
|
||||
public object[] Data
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool Sent;
|
||||
|
||||
protected NetEntityEvent(INetSerializable serializableEntity, UInt16 id)
|
||||
{
|
||||
this.ID = id;
|
||||
this.Entity = serializableEntity as Entity;
|
||||
RefreshEntityID();
|
||||
}
|
||||
|
||||
public void RefreshEntityID()
|
||||
{
|
||||
this.EntityID = this.Entity is Entity entity ? entity.ID : Entity.NullEntityID;
|
||||
}
|
||||
|
||||
public void SetData(object[] data)
|
||||
{
|
||||
this.Data = data;
|
||||
}
|
||||
|
||||
public bool IsDuplicate(NetEntityEvent other)
|
||||
{
|
||||
if (other.Entity != this.Entity) return false;
|
||||
|
||||
if (Data != null && other.Data != null)
|
||||
{
|
||||
if (Data.Length != other.Data.Length) return false;
|
||||
|
||||
for (int i = 0; i < Data.Length; i++)
|
||||
{
|
||||
if (Data[i] == null)
|
||||
{
|
||||
if (other.Data[i] != null) return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (other.Data[i] == null) return false;
|
||||
if (!Data[i].Equals(other.Data[i])) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return Data == other.Data;
|
||||
}
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
abstract class NetEntityEventManager
|
||||
{
|
||||
public const int MaxEventBufferLength = 1024;
|
||||
|
||||
/// <summary>
|
||||
/// Write the events to the outgoing message. The recipient parameter is only needed for ServerEntityEventManager
|
||||
/// </summary>
|
||||
protected void Write(IWriteMessage msg, List<NetEntityEvent> eventsToSync, out List<NetEntityEvent> sentEvents, Client recipient = null)
|
||||
{
|
||||
//write into a temporary buffer so we can write the number of events before the actual data
|
||||
IWriteMessage tempBuffer = new WriteOnlyMessage();
|
||||
|
||||
sentEvents = new List<NetEntityEvent>();
|
||||
|
||||
int eventCount = 0;
|
||||
foreach (NetEntityEvent e in eventsToSync)
|
||||
{
|
||||
//write into a temporary buffer so we can write the length before the actual data
|
||||
IWriteMessage tempEventBuffer = new WriteOnlyMessage();
|
||||
try
|
||||
{
|
||||
WriteEvent(tempEventBuffer, e, recipient);
|
||||
}
|
||||
|
||||
catch (Exception exception)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to write an event for the entity \"" + e.Entity + "\"", exception);
|
||||
GameAnalyticsManager.AddErrorEventOnce("NetEntityEventManager.Write:WriteFailed" + e.Entity.ToString(),
|
||||
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
||||
"Failed to write an event for the entity \"" + e.Entity + "\"\n" + exception.StackTrace);
|
||||
|
||||
//write an empty event to avoid messing up IDs
|
||||
//(otherwise the clients might read the next event in the message and think its ID
|
||||
//is consecutive to the previous one, even though we skipped over this broken event)
|
||||
tempBuffer.Write(Entity.NullEntityID);
|
||||
tempBuffer.WritePadBits();
|
||||
eventCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
//the length of the data is written as a byte, so the data needs to be less than 255 bytes long
|
||||
if (tempEventBuffer.LengthBytes > 255)
|
||||
{
|
||||
DebugConsole.ThrowError("Too much data in network event for entity \"" + e.Entity.ToString() + "\" (" + tempEventBuffer.LengthBytes + " bytes, event ID " + e.ID + ")");
|
||||
GameAnalyticsManager.AddErrorEventOnce("NetEntityEventManager.Write:TooLong" + e.Entity.ToString(),
|
||||
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
||||
"Too much data in network event for entity \"" + e.Entity.ToString() + "\" (" + tempEventBuffer.LengthBytes + " bytes, event ID " + e.ID + ")");
|
||||
|
||||
//write an empty event to prevent breaking the event syncing
|
||||
tempBuffer.Write(Entity.NullEntityID);
|
||||
tempBuffer.WritePadBits();
|
||||
eventCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (msg.LengthBytes + tempBuffer.LengthBytes + tempEventBuffer.LengthBytes > MaxEventBufferLength)
|
||||
{
|
||||
//no more room in this packet
|
||||
break;
|
||||
}
|
||||
|
||||
tempBuffer.Write(e.EntityID);
|
||||
tempBuffer.Write((byte)tempEventBuffer.LengthBytes);
|
||||
tempBuffer.Write(tempEventBuffer.Buffer, 0, tempEventBuffer.LengthBytes);
|
||||
tempBuffer.WritePadBits();
|
||||
sentEvents.Add(e);
|
||||
|
||||
eventCount++;
|
||||
}
|
||||
|
||||
if (eventCount > 0)
|
||||
{
|
||||
msg.Write(eventsToSync[0].ID);
|
||||
msg.Write((byte)eventCount);
|
||||
msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void WriteEvent(IWriteMessage buffer, NetEntityEvent entityEvent, Client recipient = null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class for dealing with 16-bit IDs that wrap around ushort.MaxValue
|
||||
/// </summary>
|
||||
static class NetIdUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Is newID more recent than oldID
|
||||
/// </summary>
|
||||
public static bool IdMoreRecent(ushort newID, ushort oldID)
|
||||
{
|
||||
uint id1 = newID;
|
||||
uint id2 = oldID;
|
||||
|
||||
return
|
||||
(id1 > id2) && (id1 - id2 <= ushort.MaxValue / 2)
|
||||
||
|
||||
(id2 > id1) && (id2 - id1 > ushort.MaxValue / 2);
|
||||
}
|
||||
|
||||
public static ushort Clamp(ushort id, ushort min, ushort max)
|
||||
{
|
||||
if (IdMoreRecent(min, max))
|
||||
{
|
||||
throw new ArgumentException($"Min cannot be larger than max ({min}, {max})");
|
||||
}
|
||||
|
||||
if (!IdMoreRecent(id, min))
|
||||
{
|
||||
return min;
|
||||
}
|
||||
else if (IdMoreRecent(id, max))
|
||||
{
|
||||
return max;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the current ID valid given the previous ID and latest possible ID (not smaller than the previous ID or larger than the latest ID)
|
||||
/// </summary>
|
||||
public static bool IsValidId(ushort currentId, ushort previousId, ushort latestPossibleId)
|
||||
{
|
||||
//cannot be valid if more recent than the latest Id
|
||||
if (IdMoreRecent(currentId, latestPossibleId)) { return false; }
|
||||
|
||||
//normally the id needs to be more recent than the previous id,
|
||||
//but there's a special case when the previous id was 0:
|
||||
// if a client reconnects mid-round and tries to jump from the unitialized state (0) back to some previous high id (> ushort.MaxValue / 2),
|
||||
// this would normally get interpreted as trying to jump backwards, but in this case we'll allow it
|
||||
return IdMoreRecent(currentId, previousId) || (previousId == 0 && currentId > ushort.MaxValue / 2);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public static void Test()
|
||||
{
|
||||
Debug.Assert(IdMoreRecent((ushort)2, (ushort)1));
|
||||
Debug.Assert(IdMoreRecent((ushort)2, (ushort)(ushort.MaxValue - 5)));
|
||||
Debug.Assert(!IdMoreRecent((ushort)ushort.MaxValue, (ushort)5));
|
||||
|
||||
Debug.Assert(Clamp((ushort)5, (ushort)1, (ushort)10) == 5);
|
||||
Debug.Assert(Clamp((ushort)(ushort.MaxValue - 5), (ushort)(ushort.MaxValue - 2), (ushort)3) == (ushort)(ushort.MaxValue - 2));
|
||||
|
||||
Debug.Assert(IsValidId((ushort)10, (ushort)1, (ushort)10));
|
||||
Debug.Assert(!IsValidId((ushort)11, (ushort)1, (ushort)10));
|
||||
|
||||
Debug.Assert(IsValidId((ushort)1, (ushort)(ushort.MaxValue - 5), (ushort)10));
|
||||
Debug.Assert(!IsValidId((ushort)(ushort.MaxValue - 6), (ushort)(ushort.MaxValue - 5), (ushort)10));
|
||||
|
||||
Debug.Assert(IsValidId((ushort)0, (ushort)ushort.MaxValue - 100, (ushort)ushort.MaxValue));
|
||||
Debug.Assert(!IsValidId((ushort)(ushort.MaxValue - 101), (ushort)ushort.MaxValue - 100, (ushort)ushort.MaxValue));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
enum ClientPacketHeader
|
||||
{
|
||||
UPDATE_LOBBY, //update state in lobby
|
||||
UPDATE_INGAME, //update state ingame
|
||||
|
||||
SERVER_SETTINGS, //change server settings
|
||||
|
||||
CAMPAIGN_SETUP_INFO,
|
||||
|
||||
FILE_REQUEST, //request a (submarine) file from the server
|
||||
|
||||
VOICE,
|
||||
|
||||
RESPONSE_STARTGAME, //tell the server whether you're ready to start
|
||||
SERVER_COMMAND, //tell the server to end a round or kick/ban someone (special permissions required)
|
||||
|
||||
REQUEST_STARTGAMEFINALIZE, //tell the server you're ready to finalize round initialization
|
||||
|
||||
ERROR //tell the server that an error occurred
|
||||
}
|
||||
enum ClientNetObject
|
||||
{
|
||||
END_OF_MESSAGE, //self-explanatory
|
||||
SYNC_IDS, //ids of the last changes the client knows about
|
||||
CHAT_MESSAGE, //also self-explanatory
|
||||
VOTE, //you get the idea
|
||||
CHARACTER_INPUT,
|
||||
ENTITY_STATE,
|
||||
SPECTATING_POS
|
||||
}
|
||||
|
||||
enum ClientNetError
|
||||
{
|
||||
MISSING_EVENT, //client was expecting a previous event
|
||||
MISSING_ENTITY //client can't find an entity of a certain ID
|
||||
}
|
||||
|
||||
enum ServerPacketHeader
|
||||
{
|
||||
AUTH_RESPONSE, //tell the player if they require a password to log in
|
||||
AUTH_FAILURE, //the server won't authorize player yet, however connection is still alive
|
||||
UPDATE_LOBBY, //update state in lobby (votes and chat messages)
|
||||
UPDATE_INGAME, //update state ingame (character input and chat messages)
|
||||
|
||||
PERMISSIONS, //tell the client which special permissions they have (if any)
|
||||
ACHIEVEMENT, //give the client a steam achievement
|
||||
CHEATS_ENABLED, //tell the clients whether cheats are on or off
|
||||
|
||||
CAMPAIGN_SETUP_INFO,
|
||||
|
||||
FILE_TRANSFER,
|
||||
|
||||
VOICE,
|
||||
|
||||
QUERY_STARTGAME, //ask the clients whether they're ready to start
|
||||
STARTGAME, //start a new round
|
||||
STARTGAMEFINALIZE, //finalize round initialization
|
||||
ENDGAME,
|
||||
|
||||
TRAITOR_MESSAGE,
|
||||
MISSION
|
||||
}
|
||||
enum ServerNetObject
|
||||
{
|
||||
END_OF_MESSAGE,
|
||||
SYNC_IDS,
|
||||
CHAT_MESSAGE,
|
||||
VOTE,
|
||||
CLIENT_LIST,
|
||||
ENTITY_POSITION,
|
||||
ENTITY_EVENT,
|
||||
ENTITY_EVENT_INITIAL,
|
||||
}
|
||||
|
||||
enum TraitorMessageType
|
||||
{
|
||||
Server,
|
||||
ServerMessageBox,
|
||||
Objective,
|
||||
Console
|
||||
}
|
||||
|
||||
enum VoteType
|
||||
{
|
||||
Unknown,
|
||||
Sub,
|
||||
Mode,
|
||||
EndRound,
|
||||
Kick,
|
||||
StartRound
|
||||
}
|
||||
|
||||
enum DisconnectReason
|
||||
{
|
||||
Unknown,
|
||||
Banned,
|
||||
Kicked,
|
||||
ServerShutdown,
|
||||
ServerCrashed,
|
||||
ServerFull,
|
||||
AuthenticationRequired,
|
||||
SteamAuthenticationRequired,
|
||||
SteamAuthenticationFailed,
|
||||
SessionTaken,
|
||||
TooManyFailedLogins,
|
||||
NoName,
|
||||
InvalidName,
|
||||
NameTaken,
|
||||
InvalidVersion,
|
||||
MissingContentPackage,
|
||||
IncompatibleContentPackage,
|
||||
NotOnWhitelist,
|
||||
ExcessiveDesyncOldEvent,
|
||||
ExcessiveDesyncRemovedEvent,
|
||||
SyncTimeout
|
||||
}
|
||||
|
||||
abstract partial class NetworkMember
|
||||
{
|
||||
public UInt16 LastClientListUpdateID
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public virtual bool IsServer
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public virtual bool IsClient
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public abstract void CreateEntityEvent(INetSerializable entity, object[] extraData = null);
|
||||
|
||||
#if DEBUG
|
||||
public Dictionary<string, long> messageCount = new Dictionary<string, long>();
|
||||
#endif
|
||||
|
||||
protected ServerSettings serverSettings;
|
||||
|
||||
protected TimeSpan updateInterval;
|
||||
protected DateTime updateTimer;
|
||||
|
||||
public int EndVoteCount, EndVoteMax;
|
||||
|
||||
protected bool gameStarted;
|
||||
|
||||
protected RespawnManager respawnManager;
|
||||
|
||||
public bool ShowNetStats;
|
||||
|
||||
public float SimulatedRandomLatency, SimulatedMinimumLatency;
|
||||
public float SimulatedLoss;
|
||||
public float SimulatedDuplicatesChance;
|
||||
|
||||
public int TickRate
|
||||
{
|
||||
get { return serverSettings.TickRate; }
|
||||
set
|
||||
{
|
||||
serverSettings.TickRate = MathHelper.Clamp(value, 1, 60);
|
||||
updateInterval = new TimeSpan(0, 0, 0, 0, MathHelper.Clamp(1000 / serverSettings.TickRate, 1, 500));
|
||||
}
|
||||
}
|
||||
|
||||
public KarmaManager KarmaManager
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new KarmaManager();
|
||||
|
||||
public bool GameStarted
|
||||
{
|
||||
get { return gameStarted; }
|
||||
}
|
||||
|
||||
public virtual List<Client> ConnectedClients
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public RespawnManager RespawnManager
|
||||
{
|
||||
get { return respawnManager; }
|
||||
}
|
||||
|
||||
public ServerSettings ServerSettings
|
||||
{
|
||||
get { return serverSettings; }
|
||||
}
|
||||
|
||||
public bool CanUseRadio(Character sender)
|
||||
{
|
||||
if (sender == null) return false;
|
||||
|
||||
var radio = sender.Inventory.Items.FirstOrDefault(i => i != null && i.GetComponent<WifiComponent>() != null);
|
||||
if (radio == null || !sender.HasEquippedItem(radio)) return false;
|
||||
|
||||
var radioComponent = radio.GetComponent<WifiComponent>();
|
||||
if (radioComponent == null) return false;
|
||||
return radioComponent.HasRequiredContainedItems(sender, addMessage: false);
|
||||
}
|
||||
|
||||
public void AddChatMessage(string message, ChatMessageType type, string senderName = "", Character senderCharacter = null)
|
||||
{
|
||||
AddChatMessage(ChatMessage.Create(senderName, message, type, senderCharacter));
|
||||
}
|
||||
|
||||
public virtual void AddChatMessage(ChatMessage message)
|
||||
{
|
||||
if (string.IsNullOrEmpty(message.Text)) { return; }
|
||||
|
||||
if (message.Sender != null && !message.Sender.IsDead)
|
||||
{
|
||||
message.Sender.ShowSpeechBubble(2.0f, ChatMessage.MessageColor[(int)message.Type]);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void KickPlayer(string kickedName, string reason) { }
|
||||
|
||||
public virtual void BanPlayer(string kickedName, string reason, bool range = false, TimeSpan? duration = null) { }
|
||||
|
||||
public virtual void UnbanPlayer(string playerName, string playerIP) { }
|
||||
|
||||
public virtual void Update(float deltaTime) { }
|
||||
|
||||
public virtual void Disconnect() { }
|
||||
|
||||
/// <summary>
|
||||
/// Check if the two version are compatible (= if they can play together in multiplayer).
|
||||
/// Returns null if compatibility could not be determined (invalid/unknown version number).
|
||||
/// </summary>
|
||||
public static bool? IsCompatible(string myVersion, string remoteVersion)
|
||||
{
|
||||
if (string.IsNullOrEmpty(myVersion) || string.IsNullOrEmpty(remoteVersion)) { return null; }
|
||||
|
||||
if (!Version.TryParse(myVersion, out Version myVersionNumber)) { return null; }
|
||||
if (!Version.TryParse(remoteVersion, out Version remoteVersionNumber)) { return null; }
|
||||
|
||||
return IsCompatible(myVersionNumber, remoteVersionNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the two version are compatible (= if they can play together in multiplayer).
|
||||
/// </summary>
|
||||
public static bool IsCompatible(Version myVersion, Version remoteVersion)
|
||||
{
|
||||
//major.minor.build.revision
|
||||
//revision number is ignored, other values have to match
|
||||
return
|
||||
myVersion.Major == remoteVersion.Major &&
|
||||
myVersion.Minor == remoteVersion.Minor &&
|
||||
myVersion.Build == remoteVersion.Build;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
partial class OrderChatMessage : ChatMessage
|
||||
{
|
||||
public readonly Order Order;
|
||||
|
||||
//who was this order given to
|
||||
public readonly Character TargetCharacter;
|
||||
|
||||
//which entity is this order referring to (hull, reactor, railgun controller, etc)
|
||||
public readonly Entity TargetEntity;
|
||||
|
||||
//additional instructions (power up, fire at will, etc)
|
||||
public readonly string OrderOption;
|
||||
|
||||
public OrderChatMessage(Order order, string orderOption, Entity targetEntity, Character targetCharacter, Character sender)
|
||||
: this(order, orderOption,
|
||||
order?.GetChatMessage(targetCharacter?.Name, sender?.CurrentHull?.DisplayName, givingOrderToSelf: targetCharacter == sender, orderOption: orderOption),
|
||||
targetEntity, targetCharacter, sender)
|
||||
{
|
||||
}
|
||||
|
||||
public OrderChatMessage(Order order, string orderOption, string text, Entity targetEntity, Character targetCharacter, Character sender)
|
||||
: base(sender?.Name, text, ChatMessageType.Order, sender)
|
||||
{
|
||||
Order = order;
|
||||
OrderOption = orderOption;
|
||||
TargetCharacter = targetCharacter;
|
||||
TargetEntity = targetEntity;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
public enum DeliveryMethod : byte
|
||||
{
|
||||
Unreliable = 0x0,
|
||||
Reliable = 0x1,
|
||||
ReliableOrdered = 0x2
|
||||
}
|
||||
|
||||
public enum ConnectionInitialization : byte
|
||||
{
|
||||
//used by all peer implementations
|
||||
SteamTicketAndVersion = 0x1,
|
||||
ContentPackageOrder = 0x2,
|
||||
Password = 0x3,
|
||||
Success = 0x0,
|
||||
|
||||
//used only by SteamP2P implementations
|
||||
ConnectionStarted = 0x4
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum PacketHeader : byte
|
||||
{
|
||||
//used by all peer implementations
|
||||
None = 0x0,
|
||||
IsCompressed = 0x1,
|
||||
IsConnectionInitializationStep = 0x2,
|
||||
|
||||
//used only by SteamP2P implementations
|
||||
IsDisconnectMessage = 0x4,
|
||||
IsServerMessage = 0x8,
|
||||
IsHeartbeatMessage = 0x10
|
||||
}
|
||||
}
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
public interface IReadMessage
|
||||
{
|
||||
bool ReadBoolean();
|
||||
void ReadPadBits();
|
||||
byte ReadByte();
|
||||
UInt16 ReadUInt16();
|
||||
Int16 ReadInt16();
|
||||
UInt32 ReadUInt32();
|
||||
Int32 ReadInt32();
|
||||
UInt64 ReadUInt64();
|
||||
Int64 ReadInt64();
|
||||
Single ReadSingle();
|
||||
Double ReadDouble();
|
||||
UInt32 ReadVariableUInt32();
|
||||
String ReadString();
|
||||
int ReadRangedInteger(int min, int max);
|
||||
Single ReadRangedSingle(Single min, Single max, int bitCount);
|
||||
byte[] ReadBytes(int numberOfBytes);
|
||||
|
||||
int BitPosition { get; set; }
|
||||
int BytePosition { get; }
|
||||
byte[] Buffer { get; }
|
||||
int LengthBits { get; set; }
|
||||
int LengthBytes { get; }
|
||||
|
||||
NetworkConnection Sender { get; }
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
public interface IWriteMessage
|
||||
{
|
||||
void Write(bool val);
|
||||
void WritePadBits();
|
||||
void Write(byte val);
|
||||
void Write(Int16 val);
|
||||
void Write(UInt16 val);
|
||||
void Write(Int32 val);
|
||||
void Write(UInt32 val);
|
||||
void Write(Int64 val);
|
||||
void Write(UInt64 val);
|
||||
void Write(Single val);
|
||||
void Write(Double val);
|
||||
void WriteVariableUInt32(UInt32 val);
|
||||
void Write(string val);
|
||||
void WriteRangedInteger(int val, int min, int max);
|
||||
void WriteRangedSingle(Single val, Single min, Single max, int bitCount);
|
||||
void Write(byte[] val, int startIndex, int length);
|
||||
|
||||
void PrepareForSending(ref byte[] outBuf, out bool isCompressed, out int outLength);
|
||||
|
||||
int BitPosition { get; set; }
|
||||
int BytePosition { get; }
|
||||
byte[] Buffer { get; }
|
||||
int LengthBits { get; set; }
|
||||
int LengthBytes { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,946 @@
|
||||
using Lidgren.Network;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
public static class MsgConstants
|
||||
{
|
||||
public const int MTU = 1200;
|
||||
public const int CompressionThreshold = 1000;
|
||||
public const int InitialBufferSize = 256;
|
||||
public const int BufferOverAllocateAmount = 4;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Utility struct for writing Singles
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct SingleUIntUnion
|
||||
{
|
||||
/// <summary>
|
||||
/// Value as a 32 bit float
|
||||
/// </summary>
|
||||
[FieldOffset(0)]
|
||||
public float SingleValue;
|
||||
|
||||
/// <summary>
|
||||
/// Value as an unsigned 32 bit integer
|
||||
/// </summary>
|
||||
[FieldOffset(0)]
|
||||
public uint UIntValue;
|
||||
}
|
||||
|
||||
internal static class MsgWriter
|
||||
{
|
||||
internal static void Write(ref byte[] buf, ref int bitPos, bool val)
|
||||
{
|
||||
#if DEBUG
|
||||
int resetPos = bitPos;
|
||||
#endif
|
||||
|
||||
EnsureBufferSize(ref buf, bitPos + 1);
|
||||
|
||||
int bytePos = bitPos / 8;
|
||||
int bitOffset = bitPos % 8;
|
||||
byte bitFlag = (byte)(1 << bitOffset);
|
||||
byte bitMask = (byte)((~bitFlag) & 0xff);
|
||||
buf[bytePos] &= bitMask;
|
||||
if (val) buf[bytePos] |= bitFlag;
|
||||
bitPos++;
|
||||
|
||||
#if DEBUG
|
||||
bool testVal = MsgReader.ReadBoolean(buf, ref resetPos);
|
||||
if (testVal != val || resetPos != bitPos)
|
||||
{
|
||||
DebugConsole.ThrowError("Boolean written incorrectly! " + testVal + ", " + val + "; " + resetPos + ", " + bitPos);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static void WritePadBits(ref byte[] buf, ref int bitPos)
|
||||
{
|
||||
int bitOffset = bitPos % 8;
|
||||
bitPos += ((8 - bitOffset) % 8);
|
||||
EnsureBufferSize(ref buf, bitPos);
|
||||
}
|
||||
|
||||
internal static void Write(ref byte[] buf, ref int bitPos, byte val)
|
||||
{
|
||||
EnsureBufferSize(ref buf, bitPos + 8);
|
||||
NetBitWriter.WriteByte(val, 8, buf, bitPos);
|
||||
bitPos += 8;
|
||||
}
|
||||
|
||||
internal static void Write(ref byte[] buf, ref int bitPos, UInt16 val)
|
||||
{
|
||||
EnsureBufferSize(ref buf, bitPos + 16);
|
||||
NetBitWriter.WriteUInt16(val, 16, buf, bitPos);
|
||||
bitPos += 16;
|
||||
}
|
||||
|
||||
internal static void Write(ref byte[] buf, ref int bitPos, Int16 val)
|
||||
{
|
||||
EnsureBufferSize(ref buf, bitPos + 16);
|
||||
NetBitWriter.WriteUInt16((UInt16)val, 16, buf, bitPos);
|
||||
bitPos += 16;
|
||||
}
|
||||
|
||||
internal static void Write(ref byte[] buf, ref int bitPos, UInt32 val)
|
||||
{
|
||||
EnsureBufferSize(ref buf, bitPos + 32);
|
||||
NetBitWriter.WriteUInt32(val, 32, buf, bitPos);
|
||||
bitPos += 32;
|
||||
}
|
||||
|
||||
internal static void Write(ref byte[] buf, ref int bitPos, Int32 val)
|
||||
{
|
||||
EnsureBufferSize(ref buf, bitPos + 32);
|
||||
NetBitWriter.WriteUInt32((UInt32)val, 32, buf, bitPos);
|
||||
bitPos += 32;
|
||||
}
|
||||
|
||||
internal static void Write(ref byte[] buf, ref int bitPos, UInt64 val)
|
||||
{
|
||||
EnsureBufferSize(ref buf, bitPos + 64);
|
||||
NetBitWriter.WriteUInt64(val, 64, buf, bitPos);
|
||||
bitPos += 64;
|
||||
}
|
||||
|
||||
internal static void Write(ref byte[] buf, ref int bitPos, Int64 val)
|
||||
{
|
||||
EnsureBufferSize(ref buf, bitPos + 64);
|
||||
NetBitWriter.WriteUInt64((UInt64)val, 64, buf, bitPos);
|
||||
bitPos += 64;
|
||||
}
|
||||
|
||||
internal static void Write(ref byte[] buf, ref int bitPos, Single val)
|
||||
{
|
||||
// Use union to avoid BitConverter.GetBytes() which allocates memory on the heap
|
||||
SingleUIntUnion su;
|
||||
su.UIntValue = 0; // must initialize every member of the union to avoid warning
|
||||
su.SingleValue = val;
|
||||
|
||||
EnsureBufferSize(ref buf, bitPos + 32);
|
||||
|
||||
NetBitWriter.WriteUInt32(su.UIntValue, 32, buf, bitPos);
|
||||
bitPos += 32;
|
||||
}
|
||||
|
||||
internal static void Write(ref byte[] buf, ref int bitPos, Double val)
|
||||
{
|
||||
EnsureBufferSize(ref buf, bitPos + 64);
|
||||
|
||||
byte[] bytes = BitConverter.GetBytes(val);
|
||||
WriteBytes(ref buf, ref bitPos, bytes, 0, bytes.Length);
|
||||
bitPos += 64;
|
||||
}
|
||||
internal static void Write(ref byte[] buf, ref int bitPos, string val)
|
||||
{
|
||||
if (string.IsNullOrEmpty(val))
|
||||
{
|
||||
WriteVariableUInt32(ref buf, ref bitPos, (uint)0);
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(val);
|
||||
WriteVariableUInt32(ref buf, ref bitPos, (uint)bytes.Length);
|
||||
WriteBytes(ref buf, ref bitPos, bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
internal static int WriteVariableUInt32(ref byte[] buf, ref int bitPos, uint value)
|
||||
{
|
||||
int retval = 1;
|
||||
uint num1 = (uint)value;
|
||||
while (num1 >= 0x80)
|
||||
{
|
||||
Write(ref buf, ref bitPos, (byte)(num1 | 0x80));
|
||||
num1 = num1 >> 7;
|
||||
retval++;
|
||||
}
|
||||
Write(ref buf, ref bitPos, (byte)num1);
|
||||
return retval;
|
||||
}
|
||||
|
||||
internal static void WriteRangedInteger(ref byte[] buf, ref int bitPos, int val, int min, int max)
|
||||
{
|
||||
uint range = (uint)(max - min);
|
||||
int numberOfBits = NetUtility.BitsToHoldUInt(range);
|
||||
|
||||
EnsureBufferSize(ref buf, bitPos + numberOfBits);
|
||||
|
||||
uint rvalue = (uint)(val - min);
|
||||
NetBitWriter.WriteUInt32(rvalue, numberOfBits, buf, bitPos);
|
||||
bitPos += numberOfBits;
|
||||
}
|
||||
|
||||
internal static void WriteRangedSingle(ref byte[] buf, ref int bitPos, Single val, Single min, Single max, int numberOfBits)
|
||||
{
|
||||
float range = max - min;
|
||||
float unit = ((val - min) / range);
|
||||
int maxVal = (1 << numberOfBits) - 1;
|
||||
|
||||
EnsureBufferSize(ref buf, bitPos + numberOfBits);
|
||||
|
||||
NetBitWriter.WriteUInt32((UInt32)((float)maxVal * unit), numberOfBits, buf, bitPos);
|
||||
bitPos += numberOfBits;
|
||||
}
|
||||
|
||||
internal static void WriteBytes(ref byte[] buf, ref int bitPos, byte[] val, int pos, int length)
|
||||
{
|
||||
EnsureBufferSize(ref buf, bitPos + length * 8);
|
||||
NetBitWriter.WriteBytes(val, pos, length, buf, bitPos);
|
||||
bitPos += length * 8;
|
||||
}
|
||||
|
||||
internal static void EnsureBufferSize(ref byte[] buf, int numberOfBits)
|
||||
{
|
||||
int byteLen = ((numberOfBits + 7) >> 3);
|
||||
if (buf == null)
|
||||
{
|
||||
buf = new byte[byteLen + MsgConstants.BufferOverAllocateAmount];
|
||||
return;
|
||||
}
|
||||
if (buf.Length < byteLen)
|
||||
{
|
||||
Array.Resize<byte>(ref buf, byteLen + MsgConstants.BufferOverAllocateAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static class MsgReader
|
||||
{
|
||||
internal static bool ReadBoolean(byte[] buf, ref int bitPos)
|
||||
{
|
||||
byte retval = NetBitWriter.ReadByte(buf, 1, bitPos);
|
||||
bitPos++;
|
||||
return (retval > 0 ? true : false);
|
||||
}
|
||||
|
||||
internal static void ReadPadBits(byte[] buf, ref int bitPos)
|
||||
{
|
||||
int bitOffset = bitPos % 8;
|
||||
bitPos += (8 - bitOffset) % 8;
|
||||
}
|
||||
|
||||
internal static byte ReadByte(byte[] buf, ref int bitPos)
|
||||
{
|
||||
byte retval = NetBitWriter.ReadByte(buf, 8, bitPos);
|
||||
bitPos += 8;
|
||||
return retval;
|
||||
}
|
||||
|
||||
internal static UInt16 ReadUInt16(byte[] buf, ref int bitPos)
|
||||
{
|
||||
uint retval = NetBitWriter.ReadUInt16(buf, 16, bitPos);
|
||||
bitPos += 16;
|
||||
return (ushort)retval;
|
||||
}
|
||||
|
||||
internal static Int16 ReadInt16(byte[] buf, ref int bitPos)
|
||||
{
|
||||
return (Int16)ReadUInt16(buf, ref bitPos);
|
||||
}
|
||||
|
||||
internal static UInt32 ReadUInt32(byte[] buf, ref int bitPos)
|
||||
{
|
||||
uint retval = NetBitWriter.ReadUInt32(buf, 32, bitPos);
|
||||
bitPos += 32;
|
||||
return retval;
|
||||
}
|
||||
|
||||
internal static Int32 ReadInt32(byte[] buf, ref int bitPos)
|
||||
{
|
||||
return (Int32)ReadUInt32(buf, ref bitPos);
|
||||
}
|
||||
|
||||
internal static UInt64 ReadUInt64(byte[] buf, ref int bitPos)
|
||||
{
|
||||
ulong low = NetBitWriter.ReadUInt32(buf, 32, bitPos);
|
||||
bitPos += 32;
|
||||
ulong high = NetBitWriter.ReadUInt32(buf, 32, bitPos);
|
||||
ulong retval = low + (high << 32);
|
||||
bitPos += 32;
|
||||
return retval;
|
||||
}
|
||||
|
||||
internal static Int64 ReadInt64(byte[] buf, ref int bitPos)
|
||||
{
|
||||
return (Int64)ReadUInt64(buf, ref bitPos);
|
||||
}
|
||||
|
||||
internal static Single ReadSingle(byte[] buf, ref int bitPos)
|
||||
{
|
||||
if ((bitPos & 7) == 0) // read directly
|
||||
{
|
||||
float retval = BitConverter.ToSingle(buf, bitPos >> 3);
|
||||
bitPos += 32;
|
||||
return retval;
|
||||
}
|
||||
|
||||
byte[] bytes = ReadBytes(buf, ref bitPos, 4);
|
||||
return BitConverter.ToSingle(bytes, 0);
|
||||
}
|
||||
|
||||
internal static Double ReadDouble(byte[] buf, ref int bitPos)
|
||||
{
|
||||
if ((bitPos & 7) == 0) // read directly
|
||||
{
|
||||
// read directly
|
||||
double retval = BitConverter.ToDouble(buf, bitPos >> 3);
|
||||
bitPos += 64;
|
||||
return retval;
|
||||
}
|
||||
|
||||
byte[] bytes = ReadBytes(buf, ref bitPos, 8);
|
||||
return BitConverter.ToDouble(bytes, 0);
|
||||
}
|
||||
|
||||
internal static UInt32 ReadVariableUInt32(byte[] buf, ref int bitPos)
|
||||
{
|
||||
int bitLength = buf.Length * 8;
|
||||
|
||||
int num1 = 0;
|
||||
int num2 = 0;
|
||||
while (bitLength - bitPos >= 8)
|
||||
{
|
||||
byte num3 = ReadByte(buf, ref bitPos);
|
||||
num1 |= (num3 & 0x7f) << num2;
|
||||
num2 += 7;
|
||||
if ((num3 & 0x80) == 0)
|
||||
return (uint)num1;
|
||||
}
|
||||
|
||||
// ouch; failed to find enough bytes; malformed variable length number?
|
||||
return (uint)num1;
|
||||
}
|
||||
|
||||
internal static String ReadString(byte[] buf, ref int bitPos)
|
||||
{
|
||||
int bitLength = buf.Length * 8;
|
||||
int byteLen = (int)ReadVariableUInt32(buf, ref bitPos);
|
||||
|
||||
if (byteLen <= 0) { return String.Empty; }
|
||||
|
||||
if ((ulong)(bitLength - bitPos) < ((ulong)byteLen * 8))
|
||||
{
|
||||
// not enough data
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((bitPos & 7) == 0)
|
||||
{
|
||||
// read directly
|
||||
string retval = System.Text.Encoding.UTF8.GetString(buf, bitPos >> 3, byteLen);
|
||||
bitPos += (8 * byteLen);
|
||||
return retval;
|
||||
}
|
||||
|
||||
byte[] bytes = ReadBytes(buf, ref bitPos, byteLen);
|
||||
return System.Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
internal static int ReadRangedInteger(byte[] buf, ref int bitPos, int min, int max)
|
||||
{
|
||||
uint range = (uint)(max - min);
|
||||
int numBits = NetUtility.BitsToHoldUInt(range);
|
||||
|
||||
uint rvalue = NetBitWriter.ReadUInt32(buf, numBits, bitPos);
|
||||
bitPos += numBits;
|
||||
|
||||
return (int)(min + rvalue);
|
||||
}
|
||||
|
||||
internal static Single ReadRangedSingle(byte[] buf, ref int bitPos, Single min, Single max, int bitCount)
|
||||
{
|
||||
int maxInt = (1 << bitCount) - 1;
|
||||
int intVal = ReadRangedInteger(buf, ref bitPos, 0, maxInt);
|
||||
Single range = max - min;
|
||||
return min + (range * ((Single)intVal) / ((Single)maxInt));
|
||||
}
|
||||
|
||||
internal static byte[] ReadBytes(byte[] buf, ref int bitPos, int numberOfBytes)
|
||||
{
|
||||
byte[] retval = new byte[numberOfBytes];
|
||||
NetBitWriter.ReadBytes(buf, numberOfBytes, bitPos, retval, 0);
|
||||
bitPos += (8 * numberOfBytes);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
|
||||
public class WriteOnlyMessage : IWriteMessage
|
||||
{
|
||||
private byte[] buf = new byte[MsgConstants.InitialBufferSize];
|
||||
private int seekPos = 0;
|
||||
private int lengthBits = 0;
|
||||
|
||||
public int BitPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return seekPos;
|
||||
}
|
||||
set
|
||||
{
|
||||
seekPos = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int BytePosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return seekPos / 8;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] Buffer
|
||||
{
|
||||
get
|
||||
{
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
public int LengthBits
|
||||
{
|
||||
get
|
||||
{
|
||||
lengthBits = seekPos > lengthBits ? seekPos : lengthBits;
|
||||
return lengthBits;
|
||||
}
|
||||
set
|
||||
{
|
||||
lengthBits = value;
|
||||
seekPos = seekPos > lengthBits ? lengthBits : seekPos;
|
||||
}
|
||||
}
|
||||
|
||||
public int LengthBytes
|
||||
{
|
||||
get
|
||||
{
|
||||
return (LengthBits + ((8 - (LengthBits % 8)) % 8)) / 8;
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(bool val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void WritePadBits()
|
||||
{
|
||||
MsgWriter.WritePadBits(ref buf, ref seekPos);
|
||||
}
|
||||
|
||||
public void Write(byte val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(UInt16 val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(Int16 val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(UInt32 val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(Int32 val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(UInt64 val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(Int64 val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(Single val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(Double val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void WriteVariableUInt32(UInt32 val)
|
||||
{
|
||||
MsgWriter.WriteVariableUInt32(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(String val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void WriteRangedInteger(int val, int min, int max)
|
||||
{
|
||||
MsgWriter.WriteRangedInteger(ref buf, ref seekPos, val, min, max);
|
||||
}
|
||||
|
||||
public void WriteRangedSingle(Single val, Single min, Single max, int bitCount)
|
||||
{
|
||||
MsgWriter.WriteRangedSingle(ref buf, ref seekPos, val, min, max, bitCount);
|
||||
}
|
||||
|
||||
public void Write(byte[] val, int startPos, int length)
|
||||
{
|
||||
MsgWriter.WriteBytes(ref buf, ref seekPos, val, startPos, length);
|
||||
}
|
||||
|
||||
public void PrepareForSending(ref byte[] outBuf, out bool isCompressed, out int length)
|
||||
{
|
||||
if (LengthBytes <= MsgConstants.CompressionThreshold)
|
||||
{
|
||||
isCompressed = false;
|
||||
if (LengthBytes > outBuf.Length) { Array.Resize(ref outBuf, LengthBytes); }
|
||||
Array.Copy(buf, outBuf, LengthBytes);
|
||||
length = LengthBytes;
|
||||
}
|
||||
else
|
||||
{
|
||||
using (MemoryStream output = new MemoryStream())
|
||||
{
|
||||
using (DeflateStream dstream = new DeflateStream(output, CompressionLevel.Fastest))
|
||||
{
|
||||
dstream.Write(buf, 0, LengthBytes);
|
||||
}
|
||||
|
||||
byte[] compressedBuf = output.ToArray();
|
||||
//don't send the data as compressed if the data takes up more space after compression
|
||||
//(which may happen when sending a sub/save file that's already been compressed with a better compression ratio)
|
||||
if (compressedBuf.Length >= outBuf.Length)
|
||||
{
|
||||
isCompressed = false;
|
||||
if (LengthBytes > outBuf.Length) { Array.Resize(ref outBuf, LengthBytes); }
|
||||
Array.Copy(buf, outBuf, LengthBytes);
|
||||
length = LengthBytes;
|
||||
}
|
||||
else
|
||||
{
|
||||
isCompressed = true;
|
||||
if (compressedBuf.Length > outBuf.Length) { Array.Resize(ref outBuf, compressedBuf.Length); }
|
||||
Array.Copy(compressedBuf, outBuf, compressedBuf.Length);
|
||||
length = compressedBuf.Length;
|
||||
DebugConsole.Log("Compressed message: " + LengthBytes + " to " + length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ReadOnlyMessage : IReadMessage
|
||||
{
|
||||
private byte[] buf;
|
||||
private int seekPos = 0;
|
||||
private int lengthBits = 0;
|
||||
|
||||
public int BitPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return seekPos;
|
||||
}
|
||||
set
|
||||
{
|
||||
seekPos = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int BytePosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return seekPos / 8;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] Buffer
|
||||
{
|
||||
get
|
||||
{
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
public int LengthBits
|
||||
{
|
||||
get
|
||||
{
|
||||
lengthBits = seekPos > lengthBits ? seekPos : lengthBits;
|
||||
return lengthBits;
|
||||
}
|
||||
set
|
||||
{
|
||||
lengthBits = value;
|
||||
seekPos = seekPos > lengthBits ? lengthBits : seekPos;
|
||||
}
|
||||
}
|
||||
|
||||
public int LengthBytes
|
||||
{
|
||||
get
|
||||
{
|
||||
return lengthBits / 8;
|
||||
}
|
||||
}
|
||||
|
||||
public NetworkConnection Sender { get; private set; }
|
||||
|
||||
public ReadOnlyMessage(byte[] inBuf, bool isCompressed, int startPos, int inLength, NetworkConnection sender)
|
||||
{
|
||||
Sender = sender;
|
||||
if (isCompressed)
|
||||
{
|
||||
byte[] decompressedData;
|
||||
using (MemoryStream input = new MemoryStream(inBuf, startPos, inLength))
|
||||
{
|
||||
using (MemoryStream output = new MemoryStream())
|
||||
{
|
||||
using (DeflateStream dstream = new DeflateStream(input, CompressionMode.Decompress))
|
||||
{
|
||||
dstream.CopyTo(output);
|
||||
}
|
||||
decompressedData = output.ToArray();
|
||||
}
|
||||
}
|
||||
buf = new byte[decompressedData.Length];
|
||||
Array.Copy(decompressedData, 0, buf, 0, decompressedData.Length);
|
||||
lengthBits = decompressedData.Length * 8;
|
||||
DebugConsole.Log("Decompressing message: " + inLength + " to " + LengthBytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
buf = new byte[inBuf.Length];
|
||||
Array.Copy(inBuf, startPos, buf, 0, inLength);
|
||||
lengthBits = inLength * 8;
|
||||
}
|
||||
seekPos = 0;
|
||||
}
|
||||
|
||||
public bool ReadBoolean()
|
||||
{
|
||||
return MsgReader.ReadBoolean(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public void ReadPadBits()
|
||||
{
|
||||
MsgReader.ReadPadBits(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public byte ReadByte()
|
||||
{
|
||||
return MsgReader.ReadByte(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public UInt16 ReadUInt16()
|
||||
{
|
||||
return MsgReader.ReadUInt16(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public Int16 ReadInt16()
|
||||
{
|
||||
return MsgReader.ReadInt16(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public UInt32 ReadUInt32()
|
||||
{
|
||||
return MsgReader.ReadUInt32(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public Int32 ReadInt32()
|
||||
{
|
||||
return MsgReader.ReadInt32(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public UInt64 ReadUInt64()
|
||||
{
|
||||
return MsgReader.ReadUInt64(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public Int64 ReadInt64()
|
||||
{
|
||||
return MsgReader.ReadInt64(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public Single ReadSingle()
|
||||
{
|
||||
return MsgReader.ReadSingle(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public Double ReadDouble()
|
||||
{
|
||||
return MsgReader.ReadDouble(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public UInt32 ReadVariableUInt32()
|
||||
{
|
||||
return MsgReader.ReadVariableUInt32(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public String ReadString()
|
||||
{
|
||||
return MsgReader.ReadString(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public int ReadRangedInteger(int min, int max)
|
||||
{
|
||||
return MsgReader.ReadRangedInteger(buf, ref seekPos, min, max);
|
||||
}
|
||||
|
||||
public Single ReadRangedSingle(Single min, Single max, int bitCount)
|
||||
{
|
||||
return MsgReader.ReadRangedSingle(buf, ref seekPos, min, max, bitCount);
|
||||
}
|
||||
|
||||
public byte[] ReadBytes(int numberOfBytes)
|
||||
{
|
||||
return MsgReader.ReadBytes(buf, ref seekPos, numberOfBytes);
|
||||
}
|
||||
}
|
||||
|
||||
public class ReadWriteMessage : IWriteMessage, IReadMessage
|
||||
{
|
||||
private byte[] buf = new byte[MsgConstants.InitialBufferSize];
|
||||
private int seekPos = 0;
|
||||
private int lengthBits = 0;
|
||||
|
||||
public int BitPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return seekPos;
|
||||
}
|
||||
set
|
||||
{
|
||||
seekPos = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int BytePosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return seekPos / 8;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] Buffer
|
||||
{
|
||||
get
|
||||
{
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
public int LengthBits
|
||||
{
|
||||
get
|
||||
{
|
||||
lengthBits = seekPos > lengthBits ? seekPos : lengthBits;
|
||||
return lengthBits;
|
||||
}
|
||||
set
|
||||
{
|
||||
lengthBits = value;
|
||||
seekPos = seekPos > lengthBits ? lengthBits : seekPos;
|
||||
}
|
||||
}
|
||||
|
||||
public int LengthBytes
|
||||
{
|
||||
get
|
||||
{
|
||||
return (LengthBits + ((8 - (LengthBits % 8)) % 8)) / 8;
|
||||
}
|
||||
}
|
||||
|
||||
public NetworkConnection Sender { get { return null; } }
|
||||
|
||||
public void Write(bool val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void WritePadBits()
|
||||
{
|
||||
MsgWriter.WritePadBits(ref buf, ref seekPos);
|
||||
}
|
||||
|
||||
public void Write(byte val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(UInt16 val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(Int16 val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(UInt32 val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(Int32 val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(UInt64 val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(Int64 val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(Single val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(Double val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void WriteVariableUInt32(UInt32 val)
|
||||
{
|
||||
MsgWriter.WriteVariableUInt32(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
public void Write(String val)
|
||||
{
|
||||
MsgWriter.Write(ref buf, ref seekPos, val);
|
||||
}
|
||||
|
||||
|
||||
public void WriteRangedInteger(int val, int min, int max)
|
||||
{
|
||||
MsgWriter.WriteRangedInteger(ref buf, ref seekPos, val, min, max);
|
||||
}
|
||||
|
||||
public void WriteRangedSingle(Single val, Single min, Single max, int bitCount)
|
||||
{
|
||||
MsgWriter.WriteRangedSingle(ref buf, ref seekPos, val, min, max, bitCount);
|
||||
}
|
||||
|
||||
public void Write(byte[] val, int startPos, int length)
|
||||
{
|
||||
MsgWriter.WriteBytes(ref buf, ref seekPos, val, startPos, length);
|
||||
}
|
||||
|
||||
public bool ReadBoolean()
|
||||
{
|
||||
return MsgReader.ReadBoolean(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public void ReadPadBits()
|
||||
{
|
||||
MsgReader.ReadPadBits(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public byte ReadByte()
|
||||
{
|
||||
return MsgReader.ReadByte(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public UInt16 ReadUInt16()
|
||||
{
|
||||
return MsgReader.ReadUInt16(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public Int16 ReadInt16()
|
||||
{
|
||||
return MsgReader.ReadInt16(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public UInt32 ReadUInt32()
|
||||
{
|
||||
return MsgReader.ReadUInt32(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public Int32 ReadInt32()
|
||||
{
|
||||
return MsgReader.ReadInt32(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public UInt64 ReadUInt64()
|
||||
{
|
||||
return MsgReader.ReadUInt64(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public Int64 ReadInt64()
|
||||
{
|
||||
return MsgReader.ReadInt64(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public Single ReadSingle()
|
||||
{
|
||||
return MsgReader.ReadSingle(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public Double ReadDouble()
|
||||
{
|
||||
return MsgReader.ReadDouble(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public UInt32 ReadVariableUInt32()
|
||||
{
|
||||
return MsgReader.ReadVariableUInt32(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public String ReadString()
|
||||
{
|
||||
return MsgReader.ReadString(buf, ref seekPos);
|
||||
}
|
||||
|
||||
public int ReadRangedInteger(int min, int max)
|
||||
{
|
||||
return MsgReader.ReadRangedInteger(buf, ref seekPos, min, max);
|
||||
}
|
||||
|
||||
public Single ReadRangedSingle(Single min, Single max, int bitCount)
|
||||
{
|
||||
return MsgReader.ReadRangedSingle(buf, ref seekPos, min, max, bitCount);
|
||||
}
|
||||
|
||||
public byte[] ReadBytes(int numberOfBytes)
|
||||
{
|
||||
return MsgReader.ReadBytes(buf, ref seekPos, numberOfBytes);
|
||||
}
|
||||
|
||||
public void PrepareForSending(ref byte[] outBuf, out bool isCompressed, out int outLength)
|
||||
{
|
||||
throw new InvalidOperationException("ReadWriteMessages are not to be sent");
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using Lidgren.Network;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
public class LidgrenConnection : NetworkConnection
|
||||
{
|
||||
public NetConnection NetConnection { get; private set; }
|
||||
|
||||
public IPEndPoint IPEndPoint => NetConnection.RemoteEndPoint;
|
||||
|
||||
public string IPString
|
||||
{
|
||||
get
|
||||
{
|
||||
return IPEndPoint.Address.IsIPv4MappedToIPv6 ? IPEndPoint.Address.MapToIPv4NoThrow().ToString() : IPEndPoint.Address.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public UInt16 Port
|
||||
{
|
||||
get
|
||||
{
|
||||
return (UInt16)IPEndPoint.Port;
|
||||
}
|
||||
}
|
||||
|
||||
public LidgrenConnection(string name, NetConnection netConnection, UInt64 steamId)
|
||||
{
|
||||
Name = name;
|
||||
NetConnection = netConnection;
|
||||
SteamID = steamId;
|
||||
EndPointString = IPString;
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
public enum NetworkConnectionStatus
|
||||
{
|
||||
Connected = 0x1,
|
||||
Disconnected = 0x2
|
||||
}
|
||||
|
||||
public abstract class NetworkConnection
|
||||
{
|
||||
public const double TimeoutThreshold = 60.0; //full minute for timeout because loading screens can take quite a while
|
||||
public const double TimeoutThresholdInGame = 10.0;
|
||||
|
||||
public string Name;
|
||||
|
||||
public UInt64 SteamID
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public string EndPointString
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public NetworkConnectionStatus Status = NetworkConnectionStatus.Disconnected;
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
public class PipeConnection : NetworkConnection
|
||||
{
|
||||
public PipeConnection()
|
||||
{
|
||||
EndPointString = "PIPE";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
public class SteamP2PConnection : NetworkConnection
|
||||
{
|
||||
public double Timeout = 0.0;
|
||||
|
||||
public SteamP2PConnection(string name, UInt64 steamId)
|
||||
{
|
||||
SteamID = steamId;
|
||||
EndPointString = SteamID.ToString();
|
||||
Name = name;
|
||||
Heartbeat();
|
||||
}
|
||||
|
||||
public void Decay(float deltaTime)
|
||||
{
|
||||
Timeout -= deltaTime;
|
||||
}
|
||||
|
||||
public void Heartbeat()
|
||||
{
|
||||
Timeout = TimeoutThreshold;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Dynamics.Contacts;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
partial class RespawnManager : Entity, IServerSerializable
|
||||
{
|
||||
public enum State
|
||||
{
|
||||
Waiting,
|
||||
Transporting,
|
||||
Returning
|
||||
}
|
||||
|
||||
private NetworkMember networkMember;
|
||||
private Steering shuttleSteering;
|
||||
private List<Door> shuttleDoors;
|
||||
|
||||
//items created during respawn
|
||||
//any respawn items left in the shuttle are removed when the shuttle despawns
|
||||
private List<Item> respawnItems = new List<Item>();
|
||||
|
||||
public bool UsingShuttle
|
||||
{
|
||||
get { return RespawnShuttle != null; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When will the shuttle be dispatched with respawned characters
|
||||
/// </summary>
|
||||
public DateTime RespawnTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// When will the sub start heading back out of the level
|
||||
/// </summary>
|
||||
public DateTime ReturnTime { get; private set; }
|
||||
|
||||
public bool RespawnCountdownStarted
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool ReturnCountdownStarted
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public State CurrentState { get; private set; }
|
||||
|
||||
private DateTime despawnTime;
|
||||
|
||||
private float maxTransportTime;
|
||||
|
||||
private float updateReturnTimer;
|
||||
|
||||
public Submarine RespawnShuttle { get; private set; }
|
||||
|
||||
public RespawnManager(NetworkMember networkMember, Submarine shuttle)
|
||||
: base(shuttle)
|
||||
{
|
||||
this.networkMember = networkMember;
|
||||
|
||||
if (shuttle != null)
|
||||
{
|
||||
RespawnShuttle = new Submarine(shuttle.FilePath, shuttle.MD5Hash.Hash, true);
|
||||
RespawnShuttle.Load(false);
|
||||
RespawnShuttle.PhysicsBody.FarseerBody.OnCollision += OnShuttleCollision;
|
||||
|
||||
//prevent wifi components from communicating between the respawn shuttle and other subs
|
||||
List<WifiComponent> wifiComponents = new List<WifiComponent>();
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine == RespawnShuttle) { wifiComponents.AddRange(item.GetComponents<WifiComponent>()); }
|
||||
}
|
||||
foreach (WifiComponent wifiComponent in wifiComponents)
|
||||
{
|
||||
wifiComponent.TeamID = Character.TeamType.FriendlyNPC;
|
||||
}
|
||||
|
||||
ResetShuttle();
|
||||
|
||||
shuttleDoors = new List<Door>();
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine != RespawnShuttle) continue;
|
||||
|
||||
var steering = item.GetComponent<Steering>();
|
||||
if (steering != null) shuttleSteering = steering;
|
||||
|
||||
var door = item.GetComponent<Door>();
|
||||
if (door != null) shuttleDoors.Add(door);
|
||||
|
||||
//lock all wires to prevent the players from messing up the electronics
|
||||
var connectionPanel = item.GetComponent<ConnectionPanel>();
|
||||
if (connectionPanel != null)
|
||||
{
|
||||
foreach (Connection connection in connectionPanel.Connections)
|
||||
{
|
||||
foreach (Wire wire in connection.Wires)
|
||||
{
|
||||
if (wire != null) wire.Locked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RespawnShuttle = null;
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
if (networkMember is GameServer server)
|
||||
{
|
||||
maxTransportTime = server.ServerSettings.MaxTransportTime;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private bool OnShuttleCollision(Fixture sender, Fixture other, Contact contact)
|
||||
{
|
||||
//ignore collisions with the top barrier when returning
|
||||
return CurrentState != State.Returning || other?.Body != Level.Loaded?.TopBarrier;
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (RespawnShuttle == null)
|
||||
{
|
||||
if (CurrentState != State.Waiting)
|
||||
{
|
||||
CurrentState = State.Waiting;
|
||||
}
|
||||
}
|
||||
|
||||
switch (CurrentState)
|
||||
{
|
||||
case State.Waiting:
|
||||
UpdateWaiting(deltaTime);
|
||||
break;
|
||||
case State.Transporting:
|
||||
UpdateTransporting(deltaTime);
|
||||
break;
|
||||
case State.Returning:
|
||||
UpdateReturning(deltaTime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateWaiting(float deltaTime);
|
||||
|
||||
private void UpdateTransporting(float deltaTime)
|
||||
{
|
||||
//infinite transport time -> shuttle wont return
|
||||
if (maxTransportTime <= 0.0f) return;
|
||||
UpdateTransportingProjSpecific(deltaTime);
|
||||
}
|
||||
|
||||
partial void UpdateTransportingProjSpecific(float deltaTime);
|
||||
|
||||
public void ForceRespawn()
|
||||
{
|
||||
ResetShuttle();
|
||||
RespawnTime = DateTime.Now;
|
||||
CurrentState = State.Waiting;
|
||||
}
|
||||
|
||||
private void UpdateReturning(float deltaTime)
|
||||
{
|
||||
updateReturnTimer += deltaTime;
|
||||
if (updateReturnTimer > 1.0f)
|
||||
{
|
||||
updateReturnTimer = 0.0f;
|
||||
|
||||
if (shuttleSteering != null)
|
||||
{
|
||||
shuttleSteering.SetDestinationLevelStart();
|
||||
}
|
||||
UpdateReturningProjSpecific();
|
||||
}
|
||||
}
|
||||
|
||||
partial void DispatchShuttle();
|
||||
|
||||
partial void UpdateReturningProjSpecific();
|
||||
|
||||
private IEnumerable<object> ForceShuttleToPos(Vector2 position, float speed)
|
||||
{
|
||||
if (RespawnShuttle == null)
|
||||
{
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
while (Math.Abs(position.Y - RespawnShuttle.WorldPosition.Y) > 100.0f)
|
||||
{
|
||||
Vector2 diff = position - RespawnShuttle.WorldPosition;
|
||||
if (diff.LengthSquared() > 0.01f)
|
||||
{
|
||||
Vector2 displayVel = Vector2.Normalize(diff) * speed;
|
||||
RespawnShuttle.SubBody.Body.LinearVelocity = ConvertUnits.ToSimUnits(displayVel);
|
||||
}
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
if (RespawnShuttle.SubBody == null) yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
private void ResetShuttle()
|
||||
{
|
||||
ReturnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(maxTransportTime * 1000));
|
||||
despawnTime = ReturnTime + new TimeSpan(0, 0, seconds: 30);
|
||||
|
||||
if (RespawnShuttle == null) return;
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine != RespawnShuttle) continue;
|
||||
|
||||
//remove respawn items that have been left in the shuttle
|
||||
if (respawnItems.Contains(item))
|
||||
{
|
||||
Spawner.AddToRemoveQueue(item);
|
||||
continue;
|
||||
}
|
||||
|
||||
//restore other items to full condition and recharge batteries
|
||||
item.Condition = item.Prefab.Health;
|
||||
item.GetComponent<Repairable>()?.ResetDeterioration();
|
||||
var powerContainer = item.GetComponent<PowerContainer>();
|
||||
if (powerContainer != null)
|
||||
{
|
||||
powerContainer.Charge = powerContainer.Capacity;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Structure wall in Structure.WallList)
|
||||
{
|
||||
if (wall.Submarine != RespawnShuttle) continue;
|
||||
|
||||
for (int i = 0; i < wall.SectionCount; i++)
|
||||
{
|
||||
wall.AddDamage(i, -100000.0f);
|
||||
}
|
||||
}
|
||||
|
||||
var shuttleGaps = Gap.GapList.FindAll(g => g.Submarine == RespawnShuttle && g.ConnectedWall != null);
|
||||
shuttleGaps.ForEach(g => Spawner.AddToRemoveQueue(g));
|
||||
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
if (hull.Submarine != RespawnShuttle) continue;
|
||||
|
||||
hull.OxygenPercentage = 100.0f;
|
||||
hull.WaterVolume = 0.0f;
|
||||
}
|
||||
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.Submarine != RespawnShuttle) continue;
|
||||
|
||||
#if CLIENT
|
||||
if (Character.Controlled == c) Character.Controlled = null;
|
||||
#endif
|
||||
c.Kill(CauseOfDeathType.Unknown, null, true);
|
||||
c.Enabled = false;
|
||||
|
||||
Spawner.AddToRemoveQueue(c);
|
||||
if (c.Inventory != null)
|
||||
{
|
||||
foreach (Item item in c.Inventory.Items)
|
||||
{
|
||||
if (item == null) continue;
|
||||
Spawner.AddToRemoveQueue(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RespawnShuttle.SetPosition(new Vector2(Level.Loaded.StartPosition.X, Level.Loaded.Size.Y + RespawnShuttle.Borders.Height));
|
||||
RespawnShuttle.Velocity = Vector2.Zero;
|
||||
}
|
||||
|
||||
partial void RespawnCharactersProjSpecific();
|
||||
public void RespawnCharacters()
|
||||
{
|
||||
RespawnCharactersProjSpecific();
|
||||
}
|
||||
|
||||
public Vector2 FindSpawnPos()
|
||||
{
|
||||
if (Level.Loaded == null || Submarine.MainSub == null) { return Vector2.Zero; }
|
||||
|
||||
Rectangle dockedBorders = RespawnShuttle.GetDockedBorders();
|
||||
Vector2 diffFromDockedBorders =
|
||||
new Vector2(dockedBorders.Center.X, dockedBorders.Y - dockedBorders.Height / 2)
|
||||
- new Vector2(RespawnShuttle.Borders.Center.X, RespawnShuttle.Borders.Y - RespawnShuttle.Borders.Height / 2);
|
||||
|
||||
int minWidth = Math.Max(dockedBorders.Width, 1000);
|
||||
int minHeight = Math.Max(dockedBorders.Height, 1000);
|
||||
|
||||
List<Level.InterestingPosition> potentialSpawnPositions = new List<Level.InterestingPosition>();
|
||||
foreach (Level.InterestingPosition potentialSpawnPos in Level.Loaded.PositionsOfInterest.Where(p => p.PositionType == Level.PositionType.MainPath))
|
||||
{
|
||||
bool invalid = false;
|
||||
//make sure the shuttle won't overlap with any ruins
|
||||
foreach (var ruin in Level.Loaded.Ruins)
|
||||
{
|
||||
if (Math.Abs(ruin.Area.Center.X - potentialSpawnPos.Position.X) < (minWidth + ruin.Area.Width) / 2) { invalid = true; break; }
|
||||
if (Math.Abs(ruin.Area.Center.Y - potentialSpawnPos.Position.Y) < (minHeight + ruin.Area.Height) / 2) { invalid = true; break; }
|
||||
}
|
||||
if (invalid) { continue; }
|
||||
|
||||
//make sure there aren't any walls too close
|
||||
var tooCloseCells = Level.Loaded.GetTooCloseCells(potentialSpawnPos.Position.ToVector2(), Math.Max(minWidth, minHeight));
|
||||
if (tooCloseCells.Any()) { continue; }
|
||||
|
||||
//make sure the spawnpoint is far enough from other subs
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
{
|
||||
if (sub == RespawnShuttle || RespawnShuttle.DockedTo.Contains(sub)) { continue; }
|
||||
float minDist = Math.Max(Math.Max(minWidth, minHeight) + Math.Max(sub.Borders.Width, sub.Borders.Height), 10000.0f);
|
||||
if (Vector2.DistanceSquared(sub.WorldPosition, potentialSpawnPos.Position.ToVector2()) < minDist * minDist)
|
||||
{
|
||||
invalid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (invalid) { continue; }
|
||||
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.IsDead)
|
||||
{
|
||||
//cannot spawn directly over dead bodies
|
||||
if (Math.Abs(character.WorldPosition.X - potentialSpawnPos.Position.X) < minWidth) { invalid = true; break; }
|
||||
if (Math.Abs(character.WorldPosition.Y - potentialSpawnPos.Position.Y) < minHeight) { invalid = true; break; }
|
||||
}
|
||||
else
|
||||
{
|
||||
//cannot spawn near alive characters (to prevent other players from seeing the shuttle
|
||||
//appear out of nowhere, or monsters from immediatelly wrecking the shuttle)
|
||||
if (Vector2.DistanceSquared(character.WorldPosition, potentialSpawnPos.Position.ToVector2()) < 5000.0f * 5000.0f)
|
||||
{
|
||||
invalid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (invalid) { continue; }
|
||||
|
||||
potentialSpawnPositions.Add(potentialSpawnPos);
|
||||
}
|
||||
Vector2 bestSpawnPos = new Vector2(Level.Loaded.StartPosition.X, Level.Loaded.Size.Y + RespawnShuttle.Borders.Height);
|
||||
float bestSpawnPosValue = 0.0f;
|
||||
foreach (var potentialSpawnPos in potentialSpawnPositions)
|
||||
{
|
||||
//the closer the spawnpos is to the main sub, the better
|
||||
float spawnPosValue = 100000.0f / Math.Max(Vector2.Distance(potentialSpawnPos.Position.ToVector2(), Submarine.MainSub.WorldPosition), 1.0f);
|
||||
|
||||
//prefer spawnpoints that are at the left side of the sub (so the shuttle doesn't have to go backwards)
|
||||
if (potentialSpawnPos.Position.X > Submarine.MainSub.WorldPosition.X)
|
||||
{
|
||||
spawnPosValue *= 0.1f;
|
||||
}
|
||||
|
||||
if (spawnPosValue > bestSpawnPosValue)
|
||||
{
|
||||
bestSpawnPos = potentialSpawnPos.Position.ToVector2();
|
||||
bestSpawnPosValue = spawnPosValue;
|
||||
}
|
||||
}
|
||||
|
||||
return bestSpawnPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
public partial class ServerLog
|
||||
{
|
||||
private struct LogMessage
|
||||
{
|
||||
public readonly string Text;
|
||||
public readonly MessageType Type;
|
||||
|
||||
public LogMessage(string text, MessageType type)
|
||||
{
|
||||
if (type.HasFlag(MessageType.Chat))
|
||||
{
|
||||
Text = $"[{DateTime.Now.ToString()}]\n {text}";
|
||||
}
|
||||
else
|
||||
{
|
||||
Text = $"[{DateTime.Now.ToString()}]\n {TextManager.GetServerMessage(text)}";
|
||||
}
|
||||
|
||||
Type = type;
|
||||
}
|
||||
}
|
||||
|
||||
public enum MessageType
|
||||
{
|
||||
Chat,
|
||||
ItemInteraction,
|
||||
Inventory,
|
||||
Attack,
|
||||
Spawning,
|
||||
Wiring,
|
||||
ServerMessage,
|
||||
ConsoleUsage,
|
||||
Error,
|
||||
}
|
||||
|
||||
private readonly Dictionary<MessageType, Color> messageColor = new Dictionary<MessageType, Color>
|
||||
{
|
||||
{ MessageType.Chat, Color.LightBlue },
|
||||
{ MessageType.ItemInteraction, new Color(205, 205, 180) },
|
||||
{ MessageType.Inventory, new Color(255, 234, 85) },
|
||||
{ MessageType.Attack, new Color(204, 74, 78) },
|
||||
{ MessageType.Spawning, new Color(163, 73, 164) },
|
||||
{ MessageType.Wiring, new Color(255, 157, 85) },
|
||||
{ MessageType.ServerMessage, new Color(157, 225, 160) },
|
||||
{ MessageType.ConsoleUsage, new Color(0, 162, 232) },
|
||||
{ MessageType.Error, Color.Red },
|
||||
};
|
||||
|
||||
private readonly Dictionary<MessageType, string> messageTypeName = new Dictionary<MessageType, string>
|
||||
{
|
||||
{ MessageType.Chat, "ChatMessage" },
|
||||
{ MessageType.ItemInteraction, "ItemInteraction" },
|
||||
{ MessageType.Inventory, "InventoryUsage" },
|
||||
{ MessageType.Attack, "AttackDeath" },
|
||||
{ MessageType.Spawning, "Spawning" },
|
||||
{ MessageType.Wiring, "Wiring" },
|
||||
{ MessageType.ServerMessage, "ServerMessage" },
|
||||
{ MessageType.ConsoleUsage, "ConsoleUsage" },
|
||||
{ MessageType.Error, "Error" }
|
||||
};
|
||||
|
||||
private int linesPerFile = 800;
|
||||
|
||||
public const string SavePath = "ServerLogs";
|
||||
|
||||
private readonly Queue<LogMessage> lines;
|
||||
|
||||
private int unsavedLineCount;
|
||||
|
||||
private readonly bool[] msgTypeHidden = new bool[Enum.GetValues(typeof(MessageType)).Length];
|
||||
|
||||
public int LinesPerFile
|
||||
{
|
||||
get { return linesPerFile; }
|
||||
set { linesPerFile = Math.Max(value, 10); }
|
||||
}
|
||||
|
||||
public string ServerName;
|
||||
|
||||
public ServerLog(string serverName)
|
||||
{
|
||||
ServerName = serverName;
|
||||
lines = new Queue<LogMessage>();
|
||||
|
||||
foreach (MessageType messageType in Enum.GetValues(typeof(MessageType)))
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(messageColor.ContainsKey(messageType));
|
||||
System.Diagnostics.Debug.Assert(messageTypeName.ContainsKey(messageType));
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteLine(string line, MessageType messageType)
|
||||
{
|
||||
//string logLine = "[" + DateTime.Now.ToLongTimeString() + "] " + line;
|
||||
|
||||
#if SERVER
|
||||
DebugConsole.NewMessage(line, messageColor[messageType]); //TODO: REMOVE
|
||||
#endif
|
||||
|
||||
var newText = new LogMessage(line, messageType);
|
||||
|
||||
lines.Enqueue(newText);
|
||||
|
||||
#if CLIENT
|
||||
if (listBox != null)
|
||||
{
|
||||
AddLine(newText);
|
||||
|
||||
listBox.UpdateScrollBarSize();
|
||||
}
|
||||
#endif
|
||||
|
||||
unsavedLineCount++;
|
||||
|
||||
if (unsavedLineCount >= LinesPerFile)
|
||||
{
|
||||
Save();
|
||||
unsavedLineCount = 0;
|
||||
}
|
||||
|
||||
while (lines.Count > LinesPerFile)
|
||||
{
|
||||
lines.Dequeue();
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
while (listBox != null && listBox.Content.CountChildren > LinesPerFile)
|
||||
{
|
||||
listBox.RemoveChild(listBox.Content.Children.First());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
if (!Directory.Exists(SavePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(SavePath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to create a folder for server logs", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
string fileName = ServerName + "_" + DateTime.Now.ToString("yyyy-MM-dd_HH:mm");
|
||||
fileName = ToolBox.RemoveInvalidFileNameChars(fileName);
|
||||
|
||||
string filePath = Path.Combine(SavePath, fileName + ".txt");
|
||||
int i = 2;
|
||||
while (File.Exists(filePath))
|
||||
{
|
||||
filePath = Path.Combine(SavePath, fileName + " (" + i + ").txt");
|
||||
i++;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllLines(filePath, lines.Select(l => l.Text));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Saving the server log to " + filePath + " failed", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,967 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
public enum SelectionMode
|
||||
{
|
||||
Manual = 0, Random = 1, Vote = 2
|
||||
}
|
||||
|
||||
public enum YesNoMaybe
|
||||
{
|
||||
No = 0, Maybe = 1, Yes = 2
|
||||
}
|
||||
|
||||
public enum BotSpawnMode
|
||||
{
|
||||
Normal, Fill
|
||||
}
|
||||
|
||||
public enum PlayStyle
|
||||
{
|
||||
Serious = 0,
|
||||
Casual = 1,
|
||||
Roleplay = 2,
|
||||
Rampage = 3,
|
||||
SomethingDifferent = 4
|
||||
}
|
||||
|
||||
partial class ServerSettings : ISerializableEntity
|
||||
{
|
||||
public const string SettingsFile = "serversettings.xml";
|
||||
|
||||
[Flags]
|
||||
public enum NetFlags : byte
|
||||
{
|
||||
Name = 0x1,
|
||||
Message = 0x2,
|
||||
Properties = 0x4,
|
||||
Misc = 0x8,
|
||||
LevelSeed = 0x10
|
||||
}
|
||||
|
||||
public static readonly string PermissionPresetFile = "Data" + Path.DirectorySeparatorChar + "permissionpresets.xml";
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return "ServerSettings"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Have some of the properties listed in the server list changed
|
||||
/// </summary>
|
||||
public bool ServerDetailsChanged;
|
||||
|
||||
public class SavedClientPermission
|
||||
{
|
||||
public readonly string EndPoint;
|
||||
public readonly ulong SteamID;
|
||||
public readonly string Name;
|
||||
public List<DebugConsole.Command> PermittedCommands;
|
||||
|
||||
public ClientPermissions Permissions;
|
||||
|
||||
public SavedClientPermission(string name, string endpoint, ClientPermissions permissions, List<DebugConsole.Command> permittedCommands)
|
||||
{
|
||||
this.Name = name;
|
||||
this.EndPoint = endpoint;
|
||||
this.Permissions = permissions;
|
||||
this.PermittedCommands = permittedCommands;
|
||||
}
|
||||
public SavedClientPermission(string name, ulong steamID, ClientPermissions permissions, List<DebugConsole.Command> permittedCommands)
|
||||
{
|
||||
this.Name = name;
|
||||
this.SteamID = steamID;
|
||||
|
||||
this.Permissions = permissions;
|
||||
this.PermittedCommands = permittedCommands;
|
||||
}
|
||||
}
|
||||
|
||||
partial class NetPropertyData
|
||||
{
|
||||
private readonly SerializableProperty property;
|
||||
private readonly string typeString;
|
||||
private readonly object parentObject;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return property.Name; }
|
||||
}
|
||||
|
||||
public object Value
|
||||
{
|
||||
get { return property.GetValue(parentObject); }
|
||||
set { property.SetValue(parentObject, value); }
|
||||
}
|
||||
|
||||
public NetPropertyData(object parentObject, SerializableProperty property, string typeString)
|
||||
{
|
||||
this.property = property;
|
||||
this.typeString = typeString;
|
||||
this.parentObject = parentObject;
|
||||
}
|
||||
|
||||
public bool PropEquals(object a, object b)
|
||||
{
|
||||
switch (typeString)
|
||||
{
|
||||
case "float":
|
||||
if (!(a is float?)) return false;
|
||||
if (!(b is float?)) return false;
|
||||
return MathUtils.NearlyEqual((float)a, (float)b);
|
||||
case "int":
|
||||
if (!(a is int?)) return false;
|
||||
if (!(b is int?)) return false;
|
||||
return (int)a == (int)b;
|
||||
case "bool":
|
||||
if (!(a is bool?)) return false;
|
||||
if (!(b is bool?)) return false;
|
||||
return (bool)a == (bool)b;
|
||||
case "Enum":
|
||||
if (!(a is Enum)) return false;
|
||||
if (!(b is Enum)) return false;
|
||||
return ((Enum)a).Equals((Enum)b);
|
||||
default:
|
||||
if (a == null || b == null)
|
||||
{
|
||||
return (a == null) == (b == null);
|
||||
}
|
||||
return a.ToString().Equals(b.ToString(), StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
public void Read(IReadMessage msg)
|
||||
{
|
||||
int oldPos = msg.BitPosition;
|
||||
UInt32 size = msg.ReadVariableUInt32();
|
||||
|
||||
float x; float y; float z; float w;
|
||||
byte r; byte g; byte b; byte a;
|
||||
int ix; int iy; int width; int height;
|
||||
|
||||
switch (typeString)
|
||||
{
|
||||
case "float":
|
||||
if (size != 4) break;
|
||||
property.SetValue(parentObject, msg.ReadSingle());
|
||||
return;
|
||||
case "int":
|
||||
if (size != 4) break;
|
||||
property.SetValue(parentObject, msg.ReadInt32());
|
||||
return;
|
||||
case "vector2":
|
||||
if (size != 8) break;
|
||||
x = msg.ReadSingle();
|
||||
y = msg.ReadSingle();
|
||||
property.SetValue(parentObject, new Vector2(x, y));
|
||||
return;
|
||||
case "vector3":
|
||||
if (size != 12) break;
|
||||
x = msg.ReadSingle();
|
||||
y = msg.ReadSingle();
|
||||
z = msg.ReadSingle();
|
||||
property.SetValue(parentObject, new Vector3(x, y, z));
|
||||
return;
|
||||
case "vector4":
|
||||
if (size != 16) break;
|
||||
x = msg.ReadSingle();
|
||||
y = msg.ReadSingle();
|
||||
z = msg.ReadSingle();
|
||||
w = msg.ReadSingle();
|
||||
property.SetValue(parentObject, new Vector4(x, y, z, w));
|
||||
return;
|
||||
case "color":
|
||||
if (size != 4) break;
|
||||
r = msg.ReadByte();
|
||||
g = msg.ReadByte();
|
||||
b = msg.ReadByte();
|
||||
a = msg.ReadByte();
|
||||
property.SetValue(parentObject, new Color(r, g, b, a));
|
||||
return;
|
||||
case "rectangle":
|
||||
if (size != 16) break;
|
||||
ix = msg.ReadInt32();
|
||||
iy = msg.ReadInt32();
|
||||
width = msg.ReadInt32();
|
||||
height = msg.ReadInt32();
|
||||
property.SetValue(parentObject, new Rectangle(ix, iy, width, height));
|
||||
return;
|
||||
default:
|
||||
msg.BitPosition = oldPos; //reset position to properly read the string
|
||||
string incVal = msg.ReadString();
|
||||
property.TrySetValue(parentObject, incVal);
|
||||
return;
|
||||
}
|
||||
|
||||
//size didn't match: skip this
|
||||
msg.BitPosition += (int)(8 * size);
|
||||
}
|
||||
|
||||
public void Write(IWriteMessage msg, object overrideValue = null)
|
||||
{
|
||||
if (overrideValue == null) overrideValue = property.GetValue(parentObject);
|
||||
switch (typeString)
|
||||
{
|
||||
case "float":
|
||||
msg.WriteVariableUInt32(4);
|
||||
msg.Write((float)overrideValue);
|
||||
break;
|
||||
case "int":
|
||||
msg.WriteVariableUInt32(4);
|
||||
msg.Write((int)overrideValue);
|
||||
break;
|
||||
case "vector2":
|
||||
msg.WriteVariableUInt32(8);
|
||||
msg.Write(((Vector2)overrideValue).X);
|
||||
msg.Write(((Vector2)overrideValue).Y);
|
||||
break;
|
||||
case "vector3":
|
||||
msg.WriteVariableUInt32(12);
|
||||
msg.Write(((Vector3)overrideValue).X);
|
||||
msg.Write(((Vector3)overrideValue).Y);
|
||||
msg.Write(((Vector3)overrideValue).Z);
|
||||
break;
|
||||
case "vector4":
|
||||
msg.WriteVariableUInt32(16);
|
||||
msg.Write(((Vector4)overrideValue).X);
|
||||
msg.Write(((Vector4)overrideValue).Y);
|
||||
msg.Write(((Vector4)overrideValue).Z);
|
||||
msg.Write(((Vector4)overrideValue).W);
|
||||
break;
|
||||
case "color":
|
||||
msg.WriteVariableUInt32(4);
|
||||
msg.Write(((Color)overrideValue).R);
|
||||
msg.Write(((Color)overrideValue).G);
|
||||
msg.Write(((Color)overrideValue).B);
|
||||
msg.Write(((Color)overrideValue).A);
|
||||
break;
|
||||
case "rectangle":
|
||||
msg.WriteVariableUInt32(16);
|
||||
msg.Write(((Rectangle)overrideValue).X);
|
||||
msg.Write(((Rectangle)overrideValue).Y);
|
||||
msg.Write(((Rectangle)overrideValue).Width);
|
||||
msg.Write(((Rectangle)overrideValue).Height);
|
||||
break;
|
||||
default:
|
||||
string strVal = overrideValue.ToString();
|
||||
|
||||
msg.Write(strVal);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private readonly Dictionary<UInt32, NetPropertyData> netProperties;
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
public ServerSettings(NetworkMember networkMember, string serverName, int port, int queryPort, int maxPlayers, bool isPublic, bool enableUPnP)
|
||||
{
|
||||
ServerLog = new ServerLog(serverName);
|
||||
|
||||
Voting = new Voting();
|
||||
|
||||
Whitelist = new WhiteList();
|
||||
BanList = new BanList();
|
||||
|
||||
ExtraCargo = new Dictionary<ItemPrefab, int>();
|
||||
|
||||
PermissionPreset.LoadAll(PermissionPresetFile);
|
||||
InitProjSpecific();
|
||||
|
||||
ServerName = serverName;
|
||||
Port = port;
|
||||
QueryPort = queryPort;
|
||||
EnableUPnP = enableUPnP;
|
||||
MaxPlayers = maxPlayers;
|
||||
IsPublic = isPublic;
|
||||
|
||||
netProperties = new Dictionary<UInt32, NetPropertyData>();
|
||||
|
||||
using (MD5 md5 = MD5.Create())
|
||||
{
|
||||
var saveProperties = SerializableProperty.GetProperties<Serialize>(this);
|
||||
foreach (var property in saveProperties)
|
||||
{
|
||||
object value = property.GetValue(this);
|
||||
if (value == null) { continue; }
|
||||
|
||||
string typeName = SerializableProperty.GetSupportedTypeName(value.GetType());
|
||||
if (typeName != null || property.PropertyType.IsEnum)
|
||||
{
|
||||
NetPropertyData netPropertyData = new NetPropertyData(this, property, typeName);
|
||||
UInt32 key = ToolBox.StringToUInt32Hash(property.Name, md5);
|
||||
if (netProperties.ContainsKey(key)){ throw new Exception("Hashing collision in ServerSettings.netProperties: " + netProperties[key] + " has same key as " + property.Name + " (" + key.ToString() + ")"); }
|
||||
netProperties.Add(key, netPropertyData);
|
||||
}
|
||||
}
|
||||
|
||||
var karmaProperties = SerializableProperty.GetProperties<Serialize>(networkMember.KarmaManager);
|
||||
foreach (var property in karmaProperties)
|
||||
{
|
||||
object value = property.GetValue(networkMember.KarmaManager);
|
||||
if (value == null) { continue; }
|
||||
|
||||
string typeName = SerializableProperty.GetSupportedTypeName(value.GetType());
|
||||
if (typeName != null || property.PropertyType.IsEnum)
|
||||
{
|
||||
NetPropertyData netPropertyData = new NetPropertyData(networkMember.KarmaManager, property, typeName);
|
||||
UInt32 key = ToolBox.StringToUInt32Hash(property.Name, md5);
|
||||
if (netProperties.ContainsKey(key)) { throw new Exception("Hashing collision in ServerSettings.netProperties: " + netProperties[key] + " has same key as " + property.Name + " (" + key.ToString() + ")"); }
|
||||
netProperties.Add(key, netPropertyData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string serverName;
|
||||
public string ServerName
|
||||
{
|
||||
get { return serverName; }
|
||||
set
|
||||
{
|
||||
serverName = value;
|
||||
if (serverName.Length > NetConfig.ServerNameMaxLength) { ServerName = ServerName.Substring(0, NetConfig.ServerNameMaxLength); }
|
||||
}
|
||||
}
|
||||
|
||||
private string serverMessageText;
|
||||
public string ServerMessageText
|
||||
{
|
||||
get { return serverMessageText; }
|
||||
set
|
||||
{
|
||||
if (serverMessageText == value) { return; }
|
||||
serverMessageText = value;
|
||||
ServerDetailsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
public int Port;
|
||||
|
||||
public int QueryPort;
|
||||
|
||||
public bool EnableUPnP;
|
||||
|
||||
public ServerLog ServerLog;
|
||||
|
||||
public Voting Voting;
|
||||
|
||||
public Dictionary<string, bool> MonsterEnabled { get; private set; }
|
||||
|
||||
public Dictionary<ItemPrefab, int> ExtraCargo { get; private set; }
|
||||
|
||||
private float selectedLevelDifficulty;
|
||||
private string password;
|
||||
|
||||
public float AutoRestartTimer;
|
||||
|
||||
private bool autoRestart;
|
||||
|
||||
public bool IsPublic;
|
||||
|
||||
private int maxPlayers;
|
||||
|
||||
public List<SavedClientPermission> ClientPermissions { get; private set; } = new List<SavedClientPermission>();
|
||||
|
||||
public WhiteList Whitelist { get; private set; }
|
||||
|
||||
[Serialize(20, true)]
|
||||
public int TickRate
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool RandomizeSeed
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool UseRespawnShuttle
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(300.0f, true)]
|
||||
public float RespawnInterval
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(180.0f, true)]
|
||||
public float MaxTransportTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(0.2f, true)]
|
||||
public float MinRespawnRatio
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(60.0f, true)]
|
||||
public float AutoRestartInterval
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, true)]
|
||||
public bool StartWhenClientsReady
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.8f, true)]
|
||||
public float StartWhenClientsReadyRatio
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private bool allowSpectating;
|
||||
[Serialize(true, true)]
|
||||
public bool AllowSpectating
|
||||
{
|
||||
get { return allowSpectating; }
|
||||
private set
|
||||
{
|
||||
if (allowSpectating == value) { return; }
|
||||
allowSpectating = value;
|
||||
ServerDetailsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool EndRoundAtLevelEnd
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool SaveServerLogs
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool AllowRagdollButton
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool AllowFileTransfers
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private bool voiceChatEnabled;
|
||||
[Serialize(true, true)]
|
||||
public bool VoiceChatEnabled
|
||||
{
|
||||
get { return voiceChatEnabled; }
|
||||
set
|
||||
{
|
||||
if (voiceChatEnabled == value) { return; }
|
||||
voiceChatEnabled = value;
|
||||
ServerDetailsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
private PlayStyle playstyleSelection;
|
||||
[Serialize(PlayStyle.Casual, true)]
|
||||
public PlayStyle PlayStyle
|
||||
{
|
||||
get { return playstyleSelection; }
|
||||
set
|
||||
{
|
||||
playstyleSelection = value;
|
||||
ServerDetailsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(800, true)]
|
||||
private int LinesPerLogFile
|
||||
{
|
||||
get
|
||||
{
|
||||
return ServerLog.LinesPerFile;
|
||||
}
|
||||
set
|
||||
{
|
||||
ServerLog.LinesPerFile = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool AutoRestart
|
||||
{
|
||||
get { return autoRestart; }
|
||||
set
|
||||
{
|
||||
autoRestart = value;
|
||||
|
||||
AutoRestartTimer = autoRestart ? AutoRestartInterval : 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasPassword
|
||||
{
|
||||
get { return password != null; }
|
||||
#if CLIENT
|
||||
set
|
||||
{
|
||||
password = value ? (password ?? "_") : null;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool AllowVoteKick
|
||||
{
|
||||
get
|
||||
{
|
||||
return Voting.AllowVoteKick;
|
||||
}
|
||||
set
|
||||
{
|
||||
Voting.AllowVoteKick = value;
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool AllowEndVoting
|
||||
{
|
||||
get
|
||||
{
|
||||
return Voting.AllowEndVoting;
|
||||
}
|
||||
set
|
||||
{
|
||||
Voting.AllowEndVoting = value;
|
||||
}
|
||||
}
|
||||
|
||||
private bool allowRespawn;
|
||||
[Serialize(true, true)]
|
||||
public bool AllowRespawn
|
||||
{
|
||||
get { return allowRespawn; ; }
|
||||
set
|
||||
{
|
||||
if (allowRespawn == value) { return; }
|
||||
allowRespawn = value;
|
||||
ServerDetailsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(0, true)]
|
||||
public int BotCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(16, true)]
|
||||
public int MaxBotCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(BotSpawnMode.Normal, true)]
|
||||
public BotSpawnMode BotSpawnMode
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public float SelectedLevelDifficulty
|
||||
{
|
||||
get { return selectedLevelDifficulty; }
|
||||
set { selectedLevelDifficulty = MathHelper.Clamp(value, 0.0f, 100.0f); }
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool AllowDisguises
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool AllowRewiring
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool AllowFriendlyFire
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool BanAfterWrongPassword
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(3, true)]
|
||||
public int MaxPasswordRetriesBeforeBan
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize("", true)]
|
||||
public string SelectedSubmarine
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
[Serialize("", true)]
|
||||
public string SelectedShuttle
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private YesNoMaybe traitorsEnabled;
|
||||
[Serialize(YesNoMaybe.No, true)]
|
||||
public YesNoMaybe TraitorsEnabled
|
||||
{
|
||||
get { return traitorsEnabled; }
|
||||
set
|
||||
{
|
||||
if (traitorsEnabled == value) { return; }
|
||||
traitorsEnabled = value;
|
||||
ServerDetailsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(defaultValue: 1, isSaveable: true)]
|
||||
public int TraitorsMinPlayerCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(defaultValue: 90.0f, isSaveable: true)]
|
||||
public float TraitorsMinStartDelay
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(defaultValue: 180.0f, isSaveable: true)]
|
||||
public float TraitorsMaxStartDelay
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(defaultValue: 30.0f, isSaveable: true)]
|
||||
public float TraitorsMinRestartDelay
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(defaultValue: 90.0f, isSaveable: true)]
|
||||
public float TraitorsMaxRestartDelay
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private SelectionMode subSelectionMode;
|
||||
[Serialize(SelectionMode.Manual, true)]
|
||||
public SelectionMode SubSelectionMode
|
||||
{
|
||||
get { return subSelectionMode; }
|
||||
set
|
||||
{
|
||||
subSelectionMode = value;
|
||||
Voting.AllowSubVoting = subSelectionMode == SelectionMode.Vote;
|
||||
ServerDetailsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
private SelectionMode modeSelectionMode;
|
||||
[Serialize(SelectionMode.Manual, true)]
|
||||
public SelectionMode ModeSelectionMode
|
||||
{
|
||||
get { return modeSelectionMode; }
|
||||
set
|
||||
{
|
||||
modeSelectionMode = value;
|
||||
Voting.AllowModeVoting = modeSelectionMode == SelectionMode.Vote;
|
||||
ServerDetailsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
public BanList BanList { get; private set; }
|
||||
|
||||
[Serialize(0.6f, true)]
|
||||
public float EndVoteRequiredRatio
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(0.6f, true)]
|
||||
public float KickVoteRequiredRatio
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(120.0f, true)]
|
||||
public float KillDisconnectedTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(600.0f, true)]
|
||||
public float KickAFKTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private bool karmaEnabled;
|
||||
[Serialize(false, true)]
|
||||
public bool KarmaEnabled
|
||||
{
|
||||
get { return karmaEnabled; }
|
||||
set
|
||||
{
|
||||
karmaEnabled = value;
|
||||
#if CLIENT
|
||||
if (karmaSettingsBlocker != null) { karmaSettingsBlocker.Visible = !karmaEnabled || karmaPresetDD.SelectedData as string != "custom"; }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private string karmaPreset = "default";
|
||||
[Serialize("default", true)]
|
||||
public string KarmaPreset
|
||||
{
|
||||
get { return karmaPreset; }
|
||||
set
|
||||
{
|
||||
if (karmaPreset == value) { return; }
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
GameMain.NetworkMember?.KarmaManager?.SelectPreset(value);
|
||||
}
|
||||
karmaPreset = value;
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize("sandbox", true)]
|
||||
public string GameModeIdentifier
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("All", true)]
|
||||
public string MissionType
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(8, true)]
|
||||
public int MaxPlayers
|
||||
{
|
||||
get { return maxPlayers; }
|
||||
set { maxPlayers = MathHelper.Clamp(value, 1, NetConfig.MaxPlayers); }
|
||||
}
|
||||
|
||||
public List<MissionType> AllowedRandomMissionTypes
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(60f * 60.0f, true)]
|
||||
public float AutoBanTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(60.0f * 60.0f * 24.0f, true)]
|
||||
public float MaxAutoBanTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public void SetPassword(string password)
|
||||
{
|
||||
if (string.IsNullOrEmpty(password))
|
||||
{
|
||||
this.password = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] SaltPassword(byte[] password, int salt)
|
||||
{
|
||||
byte[] saltedPw = new byte[password.Length*2];
|
||||
for (int i = 0; i < password.Length; i++)
|
||||
{
|
||||
saltedPw[(i * 2)] = password[i];
|
||||
saltedPw[(i * 2) + 1] = (byte)((salt >> (8 * (i % 4))) & 0xff);
|
||||
}
|
||||
saltedPw = Lidgren.Network.NetUtility.ComputeSHAHash(saltedPw);
|
||||
return saltedPw;
|
||||
}
|
||||
|
||||
public bool IsPasswordCorrect(byte[] input, int salt)
|
||||
{
|
||||
if (!HasPassword) return true;
|
||||
byte[] saltedPw = SaltPassword(Encoding.UTF8.GetBytes(password), salt);
|
||||
DebugConsole.NewMessage(ToolBox.ByteArrayToString(input) + " " + ToolBox.ByteArrayToString(saltedPw));
|
||||
if (input.Length != saltedPw.Length) return false;
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
if (input[i] != saltedPw[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A list of int pairs that represent the ranges of UTF-16 codes allowed in client names
|
||||
/// </summary>
|
||||
public List<Pair<int, int>> AllowedClientNameChars
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new List<Pair<int, int>>();
|
||||
|
||||
private void InitMonstersEnabled()
|
||||
{
|
||||
//monster spawn settings
|
||||
if (MonsterEnabled == null)
|
||||
{
|
||||
List<string> monsterNames1 = CharacterPrefab.Prefabs.Select(p => p.Identifier).ToList();
|
||||
|
||||
MonsterEnabled = new Dictionary<string, bool>();
|
||||
foreach (string s in monsterNames1)
|
||||
{
|
||||
if (!MonsterEnabled.ContainsKey(s)) MonsterEnabled.Add(s, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ReadMonsterEnabled(IReadMessage inc)
|
||||
{
|
||||
InitMonstersEnabled();
|
||||
List<string> monsterNames = MonsterEnabled.Keys.ToList();
|
||||
foreach (string s in monsterNames)
|
||||
{
|
||||
MonsterEnabled[s] = inc.ReadBoolean();
|
||||
}
|
||||
inc.ReadPadBits();
|
||||
}
|
||||
|
||||
public void WriteMonsterEnabled(IWriteMessage msg, Dictionary<string, bool> monsterEnabled = null)
|
||||
{
|
||||
//monster spawn settings
|
||||
if (monsterEnabled == null) monsterEnabled = MonsterEnabled;
|
||||
|
||||
List<string> monsterNames = monsterEnabled.Keys.ToList();
|
||||
foreach (string s in monsterNames)
|
||||
{
|
||||
msg.Write(monsterEnabled[s]);
|
||||
}
|
||||
msg.WritePadBits();
|
||||
}
|
||||
|
||||
public bool ReadExtraCargo(IReadMessage msg)
|
||||
{
|
||||
bool changed = false;
|
||||
UInt32 count = msg.ReadUInt32();
|
||||
if (ExtraCargo == null || count != ExtraCargo.Count) changed = true;
|
||||
Dictionary<ItemPrefab, int> extraCargo = new Dictionary<ItemPrefab, int>();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
string prefabIdentifier = msg.ReadString();
|
||||
string prefabName = msg.ReadString();
|
||||
byte amount = msg.ReadByte();
|
||||
|
||||
var itemPrefab = string.IsNullOrEmpty(prefabIdentifier) ?
|
||||
MapEntityPrefab.Find(prefabName, null, showErrorMessages: false) as ItemPrefab :
|
||||
MapEntityPrefab.Find(prefabName, prefabIdentifier, showErrorMessages: false) as ItemPrefab;
|
||||
if (itemPrefab != null && amount > 0)
|
||||
{
|
||||
if (changed || !ExtraCargo.ContainsKey(itemPrefab) || ExtraCargo[itemPrefab] != amount) changed = true;
|
||||
extraCargo.Add(itemPrefab, amount);
|
||||
}
|
||||
}
|
||||
if (changed) ExtraCargo = extraCargo;
|
||||
return changed;
|
||||
}
|
||||
|
||||
public void WriteExtraCargo(IWriteMessage msg)
|
||||
{
|
||||
if (ExtraCargo == null)
|
||||
{
|
||||
msg.Write((UInt32)0);
|
||||
return;
|
||||
}
|
||||
|
||||
msg.Write((UInt32)ExtraCargo.Count);
|
||||
foreach (KeyValuePair<ItemPrefab, int> kvp in ExtraCargo)
|
||||
{
|
||||
msg.Write(kvp.Key.Identifier ?? "");
|
||||
msg.Write(kvp.Key.OriginalName ?? "");
|
||||
msg.Write((byte)kvp.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
#if USE_STEAM
|
||||
namespace Barotrauma.Steam
|
||||
{
|
||||
static partial class SteamManager
|
||||
{
|
||||
public const int STEAMP2P_OWNER_PORT = 30000;
|
||||
|
||||
public const uint AppID = 602960;
|
||||
|
||||
private static readonly List<string> initializationErrors = new List<string>();
|
||||
public static IEnumerable<string> InitializationErrors
|
||||
{
|
||||
get { return initializationErrors; }
|
||||
}
|
||||
|
||||
public const string MetadataFileName = "filelist.xml";
|
||||
|
||||
private static readonly Dictionary<string, int> tagCommonness = new Dictionary<string, int>()
|
||||
{
|
||||
{ "submarine", 10 },
|
||||
{ "item", 10 },
|
||||
{ "monster", 8 },
|
||||
{ "art", 8 },
|
||||
{ "mission", 8 },
|
||||
{ "event set", 8 },
|
||||
{ "total conversion", 5 },
|
||||
{ "environment", 5 },
|
||||
{ "item assembly", 5 },
|
||||
{ "language", 5 }
|
||||
};
|
||||
|
||||
private static readonly List<string> popularTags = new List<string>();
|
||||
public static IEnumerable<string> PopularTags
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!isInitialized) { return Enumerable.Empty<string>(); }
|
||||
return popularTags;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool isInitialized;
|
||||
public static bool IsInitialized => isInitialized;
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
InitializeProjectSpecific();
|
||||
}
|
||||
|
||||
public static ulong GetSteamID()
|
||||
{
|
||||
if (!isInitialized || !Steamworks.SteamClient.IsValid)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Steamworks.SteamClient.SteamId;
|
||||
}
|
||||
|
||||
public static string GetUsername()
|
||||
{
|
||||
if (!isInitialized || !Steamworks.SteamClient.IsValid)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
return Steamworks.SteamClient.Name;
|
||||
}
|
||||
|
||||
public static bool OverlayCustomURL(string url)
|
||||
{
|
||||
if (!isInitialized || !Steamworks.SteamClient.IsValid)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Steamworks.SteamFriends.OpenWebOverlay(url);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool UnlockAchievement(string achievementName)
|
||||
{
|
||||
if (!isInitialized || !Steamworks.SteamClient.IsValid)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DebugConsole.Log("Unlocked achievement \"" + achievementName + "\"");
|
||||
|
||||
var achievements = Steamworks.SteamUserStats.Achievements.ToList();
|
||||
int achIndex = achievements.FindIndex(ach => ach.Name == achievementName);
|
||||
bool unlocked = achIndex >= 0 ? achievements[achIndex].Trigger() : false;
|
||||
if (!unlocked)
|
||||
{
|
||||
//can be caused by an incorrect identifier, but also happens during normal gameplay:
|
||||
//SteamAchievementManager tries to unlock achievements that may or may not exist
|
||||
//(discovered[whateverbiomewasentered], kill[withwhateveritem], kill[somemonster] etc) so that we can add
|
||||
//some types of new achievements without the need for client-side changes.
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage("Failed to unlock achievement \"" + achievementName + "\".");
|
||||
#endif
|
||||
}
|
||||
|
||||
return unlocked;
|
||||
}
|
||||
|
||||
|
||||
public static bool IncrementStat(string statName, int increment)
|
||||
{
|
||||
if (!isInitialized || !Steamworks.SteamClient.IsValid) { return false; }
|
||||
DebugConsole.Log("Incremented stat \"" + statName + "\" by " + increment);
|
||||
bool success = Steamworks.SteamUserStats.AddStat(statName, increment);
|
||||
if (!success)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage("Failed to increment stat \"" + statName + "\".");
|
||||
#endif
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
public static bool IncrementStat(string statName, float increment)
|
||||
{
|
||||
if (!isInitialized || !Steamworks.SteamClient.IsValid) { return false; }
|
||||
DebugConsole.Log("Incremented stat \"" + statName + "\" by " + increment);
|
||||
bool success = Steamworks.SteamUserStats.AddStat(statName, increment);
|
||||
if (!success)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage("Failed to increment stat \"" + statName + "\".");
|
||||
#endif
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
public static void Update(float deltaTime)
|
||||
{
|
||||
if (!isInitialized) { return; }
|
||||
|
||||
if (Steamworks.SteamClient.IsValid) { Steamworks.SteamClient.RunCallbacks(); }
|
||||
if (Steamworks.SteamServer.IsValid) { Steamworks.SteamServer.RunCallbacks(); }
|
||||
|
||||
SteamAchievementManager.Update(deltaTime);
|
||||
UpdateProjectSpecific(deltaTime);
|
||||
}
|
||||
|
||||
public static void ShutDown()
|
||||
{
|
||||
if (!isInitialized) { return; }
|
||||
|
||||
if (Steamworks.SteamClient.IsValid) { Steamworks.SteamClient.Shutdown(); }
|
||||
if (Steamworks.SteamServer.IsValid) { Steamworks.SteamServer.Shutdown(); }
|
||||
isInitialized = false;
|
||||
}
|
||||
|
||||
public static UInt64 SteamIDStringToUInt64(string str)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str)) { return 0; }
|
||||
UInt64 retVal;
|
||||
if (UInt64.TryParse(str, out retVal) && retVal >(1<<52)) { return retVal; }
|
||||
if (str.ToUpper().IndexOf("STEAM_") != 0) { return 0; }
|
||||
string[] split = str.Substring(6).Split(':');
|
||||
if (split.Length != 3) { return 0; }
|
||||
|
||||
if (!UInt64.TryParse(split[0], out UInt64 universe)) { return 0; }
|
||||
if (!UInt64.TryParse(split[1], out UInt64 y)) { return 0; }
|
||||
if (!UInt64.TryParse(split[2], out UInt64 accountNumber)) { return 0; }
|
||||
|
||||
UInt64 accountInstance = 1; UInt64 accountType = 1;
|
||||
|
||||
return (universe << 56) | (accountType << 52) | (accountInstance << 32) | (accountNumber << 1) | y;
|
||||
}
|
||||
|
||||
public static string SteamIDUInt64ToString(UInt64 uint64)
|
||||
{
|
||||
UInt64 y = uint64 & 0x1;
|
||||
UInt64 accountNumber = (uint64 >> 1) & 0x7fffffff;
|
||||
UInt64 universe = (uint64 >> 56) & 0xff;
|
||||
|
||||
return "STEAM_" + universe.ToString() + ":" + y.ToString() + ":" + accountNumber.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
static partial class VoipConfig
|
||||
{
|
||||
public const int MAX_COMPRESSED_SIZE = 120; //amount of bytes we expect each 60ms of audio to fit in
|
||||
|
||||
public static readonly TimeSpan SEND_INTERVAL = new TimeSpan(0,0,0,0,120);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
|
||||
public class VoipQueue : IDisposable
|
||||
{
|
||||
public const int BUFFER_COUNT = 8;
|
||||
protected int[] bufferLengths;
|
||||
protected byte[][] buffers;
|
||||
protected int newestBufferInd;
|
||||
protected bool firstRead;
|
||||
|
||||
public int EnqueuedTotalLength
|
||||
{
|
||||
get
|
||||
{
|
||||
int enqueuedTotalLength = 0;
|
||||
for (int i = 0; i < BUFFER_COUNT; i++)
|
||||
{
|
||||
enqueuedTotalLength += bufferLengths[i];
|
||||
}
|
||||
return enqueuedTotalLength;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] BufferToQueue
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public virtual byte QueueID
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public UInt16 LatestBufferID
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public bool CanSend
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public bool CanReceive
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public DateTime LastReadTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public VoipQueue(byte id, bool canSend, bool canReceive)
|
||||
{
|
||||
BufferToQueue = new byte[VoipConfig.MAX_COMPRESSED_SIZE];
|
||||
newestBufferInd = BUFFER_COUNT - 1;
|
||||
bufferLengths = new int[BUFFER_COUNT];
|
||||
buffers = new byte[BUFFER_COUNT][];
|
||||
for (int i = 0; i < BUFFER_COUNT; i++)
|
||||
{
|
||||
buffers[i] = new byte[VoipConfig.MAX_COMPRESSED_SIZE];
|
||||
}
|
||||
QueueID = id;
|
||||
CanSend = canSend;
|
||||
CanReceive = canReceive;
|
||||
LatestBufferID = BUFFER_COUNT-1;
|
||||
firstRead = true;
|
||||
|
||||
LastReadTime = DateTime.Now;
|
||||
}
|
||||
|
||||
public void EnqueueBuffer(int length)
|
||||
{
|
||||
if (length > byte.MaxValue) return;
|
||||
|
||||
newestBufferInd = (newestBufferInd + 1) % BUFFER_COUNT;
|
||||
|
||||
int enqueuedTotalLength = EnqueuedTotalLength;
|
||||
|
||||
bufferLengths[newestBufferInd] = length;
|
||||
BufferToQueue.CopyTo(buffers[newestBufferInd], 0);
|
||||
|
||||
if ((enqueuedTotalLength+length)>0) LatestBufferID++;
|
||||
}
|
||||
|
||||
public void RetrieveBuffer(int id,out int outSize,out byte[] outBuf)
|
||||
{
|
||||
lock (buffers)
|
||||
{
|
||||
if (id >= LatestBufferID - (BUFFER_COUNT - 1) && id <= LatestBufferID)
|
||||
{
|
||||
int index = (newestBufferInd - (LatestBufferID - id)); if (index < 0) index += BUFFER_COUNT;
|
||||
outSize = bufferLengths[index];
|
||||
outBuf = buffers[index];
|
||||
return;
|
||||
}
|
||||
}
|
||||
outSize = -1;
|
||||
outBuf = null;
|
||||
}
|
||||
|
||||
public virtual void Write(IWriteMessage msg)
|
||||
{
|
||||
if (!CanSend) throw new Exception("Called Write on a VoipQueue not set up for sending");
|
||||
|
||||
msg.Write((UInt16)LatestBufferID);
|
||||
for (int i = 0; i < BUFFER_COUNT; i++)
|
||||
{
|
||||
int index = (newestBufferInd + i + 1) % BUFFER_COUNT;
|
||||
|
||||
msg.Write((byte)bufferLengths[index]);
|
||||
msg.Write(buffers[index], 0, bufferLengths[index]);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool Read(IReadMessage msg)
|
||||
{
|
||||
if (!CanReceive) throw new Exception("Called Read on a VoipQueue not set up for receiving");
|
||||
|
||||
UInt16 incLatestBufferID = msg.ReadUInt16();
|
||||
if (firstRead || NetIdUtils.IdMoreRecent(incLatestBufferID,LatestBufferID))
|
||||
{
|
||||
firstRead = false;
|
||||
for (int i = 0; i < BUFFER_COUNT; i++)
|
||||
{
|
||||
bufferLengths[i] = msg.ReadByte();
|
||||
buffers[i] = msg.ReadBytes(bufferLengths[i]);
|
||||
}
|
||||
newestBufferInd = BUFFER_COUNT - 1;
|
||||
LatestBufferID = incLatestBufferID;
|
||||
LastReadTime = DateTime.Now;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < BUFFER_COUNT; i++)
|
||||
{
|
||||
byte len = msg.ReadByte();
|
||||
msg.BitPosition += len * 8;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Dispose() { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using Barotrauma.Networking;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Voting
|
||||
{
|
||||
private bool allowSubVoting, allowModeVoting;
|
||||
|
||||
public bool AllowVoteKick = true;
|
||||
|
||||
public bool AllowEndVoting = true;
|
||||
|
||||
private List<Pair<object, int>> GetVoteList(VoteType voteType, List<Client> voters)
|
||||
{
|
||||
List<Pair<object, int>> voteList = new List<Pair<object, int>>();
|
||||
|
||||
foreach (Client voter in voters)
|
||||
{
|
||||
object vote = voter.GetVote<object>(voteType);
|
||||
if (vote == null) continue;
|
||||
|
||||
var existingVotable = voteList.Find(v => v.First == vote || v.First.Equals(vote));
|
||||
if (existingVotable == null)
|
||||
{
|
||||
voteList.Add(new Pair<object, int>(vote, 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
existingVotable.Second++;
|
||||
}
|
||||
}
|
||||
return voteList;
|
||||
}
|
||||
|
||||
public T HighestVoted<T>(VoteType voteType, List<Client> voters)
|
||||
{
|
||||
if (voteType == VoteType.Sub && !AllowSubVoting) return default(T);
|
||||
if (voteType == VoteType.Mode && !AllowModeVoting) return default(T);
|
||||
|
||||
List<Pair<object, int>> voteList = GetVoteList(voteType, voters);
|
||||
|
||||
T selected = default(T);
|
||||
int highestVotes = 0;
|
||||
foreach (Pair<object, int> votable in voteList)
|
||||
{
|
||||
if (selected == null || votable.Second > highestVotes)
|
||||
{
|
||||
highestVotes = votable.Second;
|
||||
selected = (T)votable.First;
|
||||
}
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
public void ResetVotes(List<Client> connectedClients)
|
||||
{
|
||||
foreach (Client client in connectedClients)
|
||||
{
|
||||
client.ResetVotes();
|
||||
}
|
||||
|
||||
GameMain.NetworkMember.EndVoteCount = 0;
|
||||
GameMain.NetworkMember.EndVoteMax = 0;
|
||||
|
||||
#if CLIENT
|
||||
UpdateVoteTexts(connectedClients, VoteType.Mode);
|
||||
UpdateVoteTexts(connectedClients, VoteType.Sub);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
partial class WhiteListedPlayer
|
||||
{
|
||||
public string Name;
|
||||
public string IP;
|
||||
|
||||
public UInt16 UniqueIdentifier;
|
||||
}
|
||||
|
||||
partial class WhiteList
|
||||
{
|
||||
const string SavePath = "Data/whitelist.txt";
|
||||
|
||||
private List<WhiteListedPlayer> whitelistedPlayers;
|
||||
public List<WhiteListedPlayer> WhiteListedPlayers
|
||||
{
|
||||
get { return whitelistedPlayers; }
|
||||
}
|
||||
|
||||
public bool Enabled;
|
||||
|
||||
partial void InitProjSpecific();
|
||||
public WhiteList()
|
||||
{
|
||||
Enabled = false;
|
||||
whitelistedPlayers = new List<WhiteListedPlayer>();
|
||||
|
||||
InitProjSpecific();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user