(a00338777) v0.9.2.1

This commit is contained in:
Joonas Rikkonen
2019-08-26 19:58:19 +03:00
parent 0f63da27b2
commit 80698b58b0
311 changed files with 11763 additions and 4507 deletions
@@ -1,10 +1,10 @@
using Lidgren.Network;
using Barotrauma.Networking;
namespace Barotrauma
{
partial class CharacterInfo
{
public void ServerWrite(NetBuffer msg)
public void ServerWrite(IWriteMessage msg)
{
msg.Write(ID);
msg.Write(Name);
@@ -1,5 +1,4 @@
using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -8,7 +7,7 @@ namespace Barotrauma
{
partial class Character
{
public string OwnerClientIP;
public string OwnerClientEndPoint;
public string OwnerClientName;
public bool ClientDisconnected;
public float KillDisconnectedTimer;
@@ -138,7 +137,7 @@ namespace Barotrauma
}
}
public virtual void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
public virtual void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
{
if (GameMain.Server == null) return;
@@ -255,7 +254,7 @@ namespace Barotrauma
msg.ReadPadBits();
}
public virtual void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
public virtual void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
if (GameMain.Server == null) return;
@@ -264,20 +263,20 @@ namespace Barotrauma
switch ((NetEntityEvent.Type)extraData[0])
{
case NetEntityEvent.Type.InventoryState:
msg.WriteRangedInteger(0, 3, 0);
msg.WriteRangedIntegerDeprecated(0, 3, 0);
Inventory.SharedWrite(msg, extraData);
break;
case NetEntityEvent.Type.Control:
msg.WriteRangedInteger(0, 3, 1);
msg.WriteRangedIntegerDeprecated(0, 3, 1);
Client owner = ((Client)extraData[1]);
msg.Write(owner == null ? (byte)0 : owner.ID);
break;
case NetEntityEvent.Type.Status:
msg.WriteRangedInteger(0, 3, 2);
msg.WriteRangedIntegerDeprecated(0, 3, 2);
WriteStatus(msg);
break;
case NetEntityEvent.Type.UpdateSkills:
msg.WriteRangedInteger(0, 3, 3);
msg.WriteRangedIntegerDeprecated(0, 3, 3);
if (Info?.Job == null)
{
msg.Write((byte)0);
@@ -302,7 +301,7 @@ namespace Barotrauma
{
msg.Write(ID);
NetBuffer tempBuffer = new NetBuffer();
IWriteMessage tempBuffer = new WriteOnlyMessage();
if (this == c.Character)
{
@@ -383,7 +382,7 @@ namespace Barotrauma
tempBuffer.WriteRangedSingle(AnimController.Collider.LinearVelocity.X, -MaxVel, MaxVel, 12);
tempBuffer.WriteRangedSingle(AnimController.Collider.LinearVelocity.Y, -MaxVel, MaxVel, 12);
bool fixedRotation = AnimController.Collider.FarseerBody.FixedRotation;
bool fixedRotation = AnimController.Collider.FarseerBody.FixedRotation || !AnimController.Collider.PhysEnabled;
tempBuffer.Write(fixedRotation);
if (!fixedRotation)
{
@@ -403,19 +402,19 @@ namespace Barotrauma
tempBuffer.WritePadBits();
msg.Write((byte)tempBuffer.LengthBytes);
msg.Write(tempBuffer);
msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes);
}
}
private void WriteStatus(NetBuffer msg)
private void WriteStatus(IWriteMessage msg)
{
msg.Write(IsDead);
if (IsDead)
{
msg.WriteRangedInteger(0, Enum.GetValues(typeof(CauseOfDeathType)).Length - 1, (int)CauseOfDeath.Type);
msg.WriteRangedIntegerDeprecated(0, Enum.GetValues(typeof(CauseOfDeathType)).Length - 1, (int)CauseOfDeath.Type);
if (CauseOfDeath.Type == CauseOfDeathType.Affliction)
{
msg.WriteRangedInteger(0, AfflictionPrefab.List.Count - 1, AfflictionPrefab.List.IndexOf(CauseOfDeath.Affliction));
msg.WriteRangedIntegerDeprecated(0, AfflictionPrefab.List.Count - 1, AfflictionPrefab.List.IndexOf(CauseOfDeath.Affliction));
}
if (AnimController?.LimbJoints == null)
@@ -446,7 +445,7 @@ namespace Barotrauma
}
}
public void WriteSpawnData(NetBuffer msg)
public void WriteSpawnData(IWriteMessage msg)
{
if (GameMain.Server == null) return;
@@ -497,4 +496,4 @@ namespace Barotrauma
DebugConsole.Log("Character spawn message length: " + (msg.LengthBytes - msgLength));
}
}
}
}
@@ -7,6 +7,8 @@ using System.ComponentModel;
using FarseerPhysics;
using Barotrauma.Items.Components;
using System.Threading;
using System.IO;
using System.Text;
namespace Barotrauma
{
@@ -49,6 +51,7 @@ namespace Barotrauma
}
public static List<string> QueuedCommands = new List<string>();
public static Thread InputThread;
public static void Update()
{
@@ -60,6 +63,30 @@ namespace Barotrauma
QueuedCommands.RemoveAt(0);
}
}
if (InputThread == null)
{
lock (queuedMessages)
{
while (queuedMessages.Count > 0)
{
var msg = queuedMessages.Dequeue();
Messages.Add(msg);
if (GameSettings.SaveDebugConsoleLogs)
{
unsavedMessages.Add(msg);
if (unsavedMessages.Count >= messagesPerFile)
{
SaveLogs();
unsavedMessages.Clear();
}
}
}
if (Messages.Count > MaxMessages)
{
Messages.RemoveRange(0, Messages.Count - MaxMessages);
}
}
}
}
@@ -91,6 +118,7 @@ namespace Barotrauma
while (queuedMessages.Count > 0)
{
ColoredText msg = queuedMessages.Dequeue();
Messages.Add(msg);
if (GameSettings.SaveDebugConsoleLogs)
{
unsavedMessages.Add(msg);
@@ -113,6 +141,10 @@ namespace Barotrauma
}
RewriteInputToCommandLine(input);
}
if (Messages.Count > MaxMessages)
{
Messages.RemoveRange(0, Messages.Count - MaxMessages);
}
}
//read player input
@@ -185,6 +217,25 @@ namespace Barotrauma
{
//don't have anything to do here yet
}
#if !DEBUG
catch (Exception exception)
{
StreamWriter sw = new StreamWriter("inputthreadcrash.log");
StringBuilder sb = new StringBuilder();
sb.AppendLine("Barotrauma Dedicated Server input thread crash report (generated on " + DateTime.Now + ")");
sb.AppendLine("\n");
sb.AppendLine("Exception: " + exception.Message);
sb.AppendLine("Target site: " + exception.TargetSite.ToString());
sb.AppendLine("Stack trace: ");
sb.AppendLine(exception.StackTrace);
sw.WriteLine(sb.ToString());
sw.Close();
GameMain.ShouldRun = false;
}
#endif
}
private static void RewriteInputToCommandLine(string input)
@@ -692,11 +743,11 @@ namespace Barotrauma
NewMessage(GameMain.Server.KarmaManager.TestMode ? "Karma test mode enabled." : "Karma test mode disabled.", Color.LightGreen);
});
AssignOnExecute("banip", (string[] args) =>
AssignOnExecute("banendpoint", (string[] args) =>
{
if (GameMain.Server == null || args.Length == 0) return;
ShowQuestionPrompt("Reason for banning the ip \"" + args[0] + "\"?", (reason) =>
ShowQuestionPrompt("Reason for banning the endpoint \"" + args[0] + "\"?", (reason) =>
{
ShowQuestionPrompt("Enter the duration of the ban (leave empty to ban permanently, or use the format \"[days] d [hours] h\")", (duration) =>
{
@@ -711,7 +762,7 @@ namespace Barotrauma
banDuration = parsedBanDuration;
}
var clients = GameMain.Server.ConnectedClients.FindAll(c => c.IPMatches(args[0]));
var clients = GameMain.Server.ConnectedClients.FindAll(c => c.EndpointMatches(args[0]));
if (clients.Count == 0)
{
GameMain.Server.ServerSettings.BanList.BanPlayer("Unnamed", args[0], reason, banDuration);
@@ -816,7 +867,7 @@ namespace Barotrauma
NewMessage("***************", Color.Cyan);
foreach (Client c in GameMain.Server.ConnectedClients)
{
NewMessage("- " + c.ID.ToString() + ": " + c.Name + (c.Character != null ? " playing " + c.Character.LogName : "") + ", " + c.Connection.RemoteEndPoint.Address.ToString(), Color.Cyan);
NewMessage("- " + c.ID.ToString() + ": " + c.Name + (c.Character != null ? " playing " + c.Character.LogName : "") + ", " + c.Connection.EndPointString, Color.Cyan);
}
NewMessage("***************", Color.Cyan);
}));
@@ -825,7 +876,7 @@ namespace Barotrauma
GameMain.Server.SendConsoleMessage("***************", client);
foreach (Client c in GameMain.Server.ConnectedClients)
{
GameMain.Server.SendConsoleMessage("- " + c.ID.ToString() + ": " + c.Name + ", " + c.Connection.RemoteEndPoint.Address.ToString(), client);
GameMain.Server.SendConsoleMessage("- " + c.ID.ToString() + ": " + c.Name + ", " + c.Connection.EndPointString, client);
}
GameMain.Server.SendConsoleMessage("***************", client);
});
@@ -865,22 +916,51 @@ namespace Barotrauma
{
if (GameMain.Server == null) return;
TraitorManager traitorManager = GameMain.Server.TraitorManager;
if (traitorManager == null) return;
foreach (Traitor t in traitorManager.TraitorList)
if (traitorManager == null || traitorManager.Traitors == null || !traitorManager.Traitors.Any())
{
NewMessage("- Traitor " + t.Character.Name + "'s target is " + t.TargetCharacter.Name + ".", Color.Cyan);
NewMessage("There are no traitors at the moment.", Color.Cyan);
return;
}
NewMessage("The code words are: " + traitorManager.codeWords + ", response: " + traitorManager.codeResponse + ".", Color.Cyan);
foreach (Traitor t in traitorManager.Traitors)
{
if (t.CurrentObjective != null)
{
NewMessage(string.Format("- Traitor {0}'s current goals are:\n{1}", t.Character.Name, t.CurrentObjective.GoalInfos), Color.Cyan);
}
else
{
NewMessage(string.Format("- Traitor {0} has no current objective.", t.Character.Name), Color.Cyan);
}
}
//NewMessage("The code words are: " + traitorManager.CodeWords + ", response: " + traitorManager.CodeResponse + ".", Color.Cyan);
}));
AssignOnClientRequestExecute("traitorlist", (Client client, Vector2 cursorPos, string[] args) =>
{
TraitorManager traitorManager = GameMain.Server.TraitorManager;
if (traitorManager == null) return;
foreach (Traitor t in traitorManager.TraitorList)
if (traitorManager == null || traitorManager.Traitors == null || !traitorManager.Traitors.Any())
{
GameMain.Server.SendConsoleMessage("- Traitor " + t.Character.Name + "'s target is " + t.TargetCharacter.Name + ".", client);
GameMain.Server.SendTraitorMessage(client,"There are no traitors at the moment.", TraitorMessageType.Console);
return;
}
GameMain.Server.SendConsoleMessage("The code words are: " + traitorManager.codeWords + ", response: " + traitorManager.codeResponse + ".", client);
foreach (Traitor t in traitorManager.Traitors)
{
if (t.CurrentObjective != null)
{
var traitorGoals = TextManager.FormatServerMessage(t.CurrentObjective.GoalInfos);
var traitorGoalsStart = traitorGoals.LastIndexOf('/') + 1;
GameMain.Server.SendTraitorMessage(client, string.Join("/", new[] {
traitorGoals.Substring(0, traitorGoalsStart),
$"[traitorgoals]={traitorGoals.Substring(traitorGoalsStart)}",
$"[traitorname]={t.Character.Name}",
"Traitor [traitorname]'s current goals are:\n[traitorgoals]"
}.Where(s => !string.IsNullOrEmpty(s))), TraitorMessageType.Console);
}
else
{
GameMain.Server.SendTraitorMessage(client, string.Format("- Traitor {0} has no current objective.", t.Character.Name), TraitorMessageType.Console);
}
}
//GameMain.Server.SendTraitorMessage(client, "The code words are: " + traitorManager.CodeWords + ", response: " + traitorManager.CodeResponse + ".", TraitorMessageType.Console);
});
commands.Add(new Command("setpassword|setserverpassword|password", "setpassword [password]: Changes the password of the server that's being hosted.", (string[] args) =>
@@ -930,6 +1010,7 @@ namespace Barotrauma
NewMessage("*****************", Color.Lime);
NewMessage("RESTARTING SERVER", Color.Lime);
NewMessage("*****************", Color.Lime);
GameServer.Log("Console command \"restart\" executed: closing the server...", ServerLog.MessageType.ServerMessage);
GameMain.Instance.CloseServer();
GameMain.Instance.StartServer();
}));
@@ -939,18 +1020,36 @@ namespace Barotrauma
GameMain.ShouldRun = false;
}));
commands.Add(new Command("say", "say [message]: Send a chat message that displays \"HOST\" as the sender.", (string[] args) =>
commands.Add(new Command("say", "say [message]: Send a global chat message. When issued through the server command line, displays \"HOST\" as the sender.", (string[] args) =>
{
string text = string.Join(" ", args);
text = "HOST: " + text;
GameMain.Server.SendChatMessage(text, ChatMessageType.Server);
}));
AssignOnClientRequestExecute("say",
(Client client, Vector2 cursorPos, string[] args) =>
{
string text = string.Join(" ", args);
text = client.Name+": " + text;
if (GameMain.Server.OwnerConnection != null &&
client.Connection == GameMain.Server.OwnerConnection)
{
text = "[HOST] " + text;
}
GameMain.Server.SendChatMessage(text, ChatMessageType.Server);
});
commands.Add(new Command("msg", "msg [message]: Send a chat message with no sender specified.", (string[] args) =>
{
string text = string.Join(" ", args);
GameMain.Server.SendChatMessage(text, ChatMessageType.Server);
}));
AssignOnClientRequestExecute("msg",
(Client client, Vector2 cursorPos, string[] args) =>
{
string text = string.Join(" ", args);
GameMain.Server.SendChatMessage(text, ChatMessageType.Server);
});
commands.Add(new Command("servername", "servername [name]: Change the name of the server.", (string[] args) =>
{
@@ -1117,6 +1216,11 @@ namespace Barotrauma
}));
#if DEBUG
commands.Add(new Command("printsendertransfers", "", (string[] args) =>
{
GameMain.Server.PrintSenderTransters();
}));
commands.Add(new Command("eventdata", "", (string[] args) =>
{
if (args.Length == 0) return;
@@ -1154,11 +1258,11 @@ namespace Barotrauma
);
AssignOnClientRequestExecute(
"banip",
"banendpoint|banip",
(Client client, Vector2 cursorPos, string[] args) =>
{
if (args.Length < 1) return;
var clients = GameMain.Server.ConnectedClients.FindAll(c => c.IPMatches(args[0]));
var clients = GameMain.Server.ConnectedClients.FindAll(c => c.EndpointMatches(args[0]));
TimeSpan? duration = null;
if (args.Length > 1)
{
@@ -0,0 +1,17 @@
using Barotrauma.Networking;
namespace Barotrauma
{
partial class Mission
{
partial void ShowMessageProjSpecific(int index)
{
if (index >= Headers.Count && index >= Messages.Count) return;
string header = index < Headers.Count ? Headers[index] : "";
string message = index < Messages.Count ? Messages[index] : "";
GameServer.Log(TextManager.Get("MissionInfo") + ": " + header + " - " + message, ServerLog.MessageType.ServerMessage);
}
}
}
+12 -3
View File
@@ -92,6 +92,7 @@ namespace Barotrauma
public void Init()
{
MissionPrefab.Init();
TraitorMissionPrefab.Init();
MapEntityPrefab.Init();
MapGenerationParams.Init();
LevelGenerationParams.LoadPresets();
@@ -169,6 +170,7 @@ namespace Barotrauma
bool enableUpnp = false;
int maxPlayers = 10;
int ownerKey = 0;
UInt64 steamId = 0;
XDocument doc = XMLExtensions.TryLoadXml(ServerSettings.SettingsFile);
if (doc?.Root == null)
@@ -224,6 +226,10 @@ namespace Barotrauma
int.TryParse(CommandLineArgs[i + 1], out ownerKey);
i++;
break;
case "-steamid":
UInt64.TryParse(CommandLineArgs[i + 1], out steamId);
i++;
break;
}
}
@@ -235,12 +241,14 @@ namespace Barotrauma
password,
enableUpnp,
maxPlayers,
ownerKey);
ownerKey,
steamId);
}
public void CloseServer()
{
Server.Disconnect();
Server?.Disconnect();
ShouldRun = false;
Server = null;
}
@@ -279,8 +287,9 @@ namespace Barotrauma
while (Timing.Accumulator >= Timing.Step)
{
DebugConsole.Update();
if (Screen.Selected != null) Screen.Selected.Update((float)Timing.Step);
Screen.Selected?.Update((float)Timing.Step);
Server.Update((float)Timing.Step);
if (Server == null) { break; }
SteamManager.Update((float)Timing.Step);
CoroutineManager.Update((float)Timing.Step, (float)Timing.Step);
@@ -0,0 +1,15 @@
using Barotrauma.Networking;
namespace Barotrauma
{
abstract partial class CampaignMode : GameMode
{
public override void ShowStartMessage()
{
if (Mission == null) return;
Networking.GameServer.Log(TextManager.Get("Mission") + ": " + Mission.Name, Networking.ServerLog.MessageType.ServerMessage);
Networking.GameServer.Log(Mission.Description, Networking.ServerLog.MessageType.ServerMessage);
}
}
}
@@ -4,9 +4,11 @@ namespace Barotrauma
{
partial class CharacterCampaignData
{
public bool HasSpawned;
partial void InitProjSpecific(Client client)
{
ClientIP = client.Connection.RemoteEndPoint.Address.ToString();
ClientEndPoint = client.Connection.EndPointString;
SteamID = client.SteamID;
CharacterInfo = client.CharacterInfo;
}
@@ -19,7 +21,7 @@ namespace Barotrauma
}
else
{
return ClientIP == client.Connection.RemoteEndPoint.Address.ToString();
return ClientEndPoint == client.Connection.EndPointString;
}
}
@@ -0,0 +1,13 @@
namespace Barotrauma
{
partial class MissionMode : GameMode
{
public override void ShowStartMessage()
{
if (mission == null) return;
Networking.GameServer.Log(TextManager.Get("Mission") + ": " + mission.Name, Networking.ServerLog.MessageType.ServerMessage);
Networking.GameServer.Log(mission.Description, Networking.ServerLog.MessageType.ServerMessage);
}
}
}
@@ -1,5 +1,4 @@
using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -153,7 +152,7 @@ namespace Barotrauma
return assignedJobs;
}
public void ServerWrite(NetBuffer msg, Client c)
public void ServerWrite(IWriteMessage msg, Client c)
{
System.Diagnostics.Debug.Assert(map.Locations.Count < UInt16.MaxValue);
@@ -191,7 +190,7 @@ namespace Barotrauma
}
}
public void ServerRead(NetBuffer msg, Client sender)
public void ServerRead(IReadMessage msg, Client sender)
{
UInt16 selectedLocIndex = msg.ReadUInt16();
byte selectedMissionIndex = msg.ReadByte();
@@ -22,7 +22,7 @@ namespace Barotrauma.Items.Components
}
}
public override void ServerWrite(Lidgren.Network.NetBuffer msg, Client c, object[] extraData = null)
public override void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
base.ServerWrite(msg, c, extraData);
@@ -1,5 +1,4 @@
using Barotrauma.Networking;
using Lidgren.Network;
using System.Linq;
using System.Xml.Linq;
@@ -7,7 +6,7 @@ namespace Barotrauma.Items.Components
{
partial class Deconstructor : Powered, IServerSerializable, IClientSerializable
{
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
{
bool active = msg.ReadBoolean();
@@ -19,7 +18,7 @@ namespace Barotrauma.Items.Components
}
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
msg.Write(IsActive);
msg.Write(progressTimer);
@@ -3,19 +3,18 @@ using System;
using System.Globalization;
using System.Xml.Linq;
using Barotrauma.Networking;
using Lidgren.Network;
namespace Barotrauma.Items.Components
{
partial class Engine : Powered, IServerSerializable, IClientSerializable
{
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
//force can only be adjusted at 10% intervals -> no need for more accuracy than this
msg.WriteRangedInteger(-10, 10, (int)(targetForce / 10.0f));
msg.WriteRangedIntegerDeprecated(-10, 10, (int)(targetForce / 10.0f));
}
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
{
float newTargetForce = msg.ReadRangedInteger(-10, 10) * 10.0f;
@@ -1,5 +1,4 @@
using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -10,7 +9,7 @@ namespace Barotrauma.Items.Components
{
partial class Fabricator : Powered, IServerSerializable, IClientSerializable
{
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
{
int itemIndex = msg.ReadRangedInteger(-1, fabricationRecipes.Count - 1);
@@ -32,10 +31,10 @@ namespace Barotrauma.Items.Components
}
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
int itemIndex = fabricatedItem == null ? -1 : fabricationRecipes.IndexOf(fabricatedItem);
msg.WriteRangedInteger(-1, fabricationRecipes.Count - 1, itemIndex);
msg.WriteRangedIntegerDeprecated(-1, fabricationRecipes.Count - 1, itemIndex);
UInt16 userID = fabricatedItem == null || user == null ? (UInt16)0 : user.ID;
msg.Write(userID);
}
@@ -8,7 +8,7 @@ namespace Barotrauma.Items.Components
{
partial class Pump : Powered, IServerSerializable, IClientSerializable
{
public void ServerRead(ClientNetObject type, Lidgren.Network.NetBuffer msg, Client c)
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
{
float newFlowPercentage = msg.ReadRangedInteger(-10, 10) * 10.0f;
bool newIsActive = msg.ReadBoolean();
@@ -32,10 +32,10 @@ namespace Barotrauma.Items.Components
item.CreateServerEvent(this);
}
public void ServerWrite(Lidgren.Network.NetBuffer msg, Client c, object[] extraData = null)
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
//flowpercentage can only be adjusted at 10% intervals -> no need for more accuracy than this
msg.WriteRangedInteger(-10, 10, (int)(flowPercentage / 10.0f));
msg.WriteRangedIntegerDeprecated(-10, 10, (int)(flowPercentage / 10.0f));
msg.Write(IsActive);
}
}
@@ -1,5 +1,4 @@
using Barotrauma.Networking;
using Lidgren.Network;
using System;
namespace Barotrauma.Items.Components
@@ -11,7 +10,7 @@ namespace Barotrauma.Items.Components
private float? nextServerLogWriteTime;
private float lastServerLogWriteTime;
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
{
bool autoTemp = msg.ReadBoolean();
bool shutDown = msg.ReadBoolean();
@@ -42,7 +41,7 @@ namespace Barotrauma.Items.Components
unsentChanges = true;
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
msg.Write(autoTemp);
msg.Write(shutDown);
@@ -1,5 +1,4 @@
using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Xml.Linq;
@@ -8,7 +7,7 @@ namespace Barotrauma.Items.Components
{
partial class PowerContainer : Powered, IDrawableComponent, IServerSerializable, IClientSerializable
{
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
{
float newRechargeSpeed = msg.ReadRangedInteger(0, 10) / 10.0f * maxRechargeSpeed;
@@ -21,9 +20,9 @@ namespace Barotrauma.Items.Components
item.CreateServerEvent(this);
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
msg.WriteRangedInteger(0, 10, (int)(rechargeSpeed / MaxRechargeSpeed * 10));
msg.WriteRangedIntegerDeprecated(0, 10, (int)(rechargeSpeed / MaxRechargeSpeed * 10));
float chargeRatio = MathHelper.Clamp(charge / capacity, 0.0f, 1.0f);
msg.WriteRangedSingle(chargeRatio, 0.0f, 1.0f, 8);
@@ -1,19 +1,45 @@
using Barotrauma.Networking;
using Lidgren.Network;
namespace Barotrauma.Items.Components
{
partial class Repairable : ItemComponent, IServerSerializable, IClientSerializable
{
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
void InitProjSpecific()
{
if (c.Character == null) return;
StartRepairing(c.Character);
//let the clients know the initial deterioration delay
item.CreateServerEvent(this);
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
{
if (c.Character == null) return;
var requestedFixAction = (FixActions)msg.ReadRangedInteger(0, 2);
if (requestedFixAction != FixActions.None)
{
if (!c.Character.IsTraitor && requestedFixAction == FixActions.Sabotage)
{
if (GameSettings.VerboseLogging)
{
DebugConsole.Log($"Non traitor \"{c.Character.Name}\" attempted to sabotage item.");
}
requestedFixAction = FixActions.Repair;
}
if (CurrentFixer == null || CurrentFixer == c.Character && requestedFixAction != currentFixerAction)
{
StartRepairing(c.Character, requestedFixAction);
item.CreateServerEvent(this);
}
}
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
msg.Write(deteriorationTimer);
msg.Write(deteriorateAlwaysResetTimer);
msg.Write(DeteriorateAlways);
msg.Write(CurrentFixer == c.Character);
msg.WriteRangedInteger((int)currentFixerAction, 0, 2);
}
}
}
@@ -1,6 +1,5 @@
using Barotrauma.Networking;
using FarseerPhysics;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -11,7 +10,7 @@ namespace Barotrauma.Items.Components
{
partial class ConnectionPanel : ItemComponent, IServerSerializable, IClientSerializable
{
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
{
List<Wire>[] wires = new List<Wire>[Connections.Count];
@@ -175,9 +174,9 @@ namespace Barotrauma.Items.Components
}
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
ClientWrite(msg, extraData);
}
}
}
}
@@ -1,5 +1,4 @@
using Barotrauma.Networking;
using Lidgren.Network;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
@@ -8,7 +7,7 @@ namespace Barotrauma.Items.Components
{
partial class CustomInterface : ItemComponent, IClientSerializable, IServerSerializable
{
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
{
bool[] elementStates = new bool[customInterfaceElementList.Count];
for (int i = 0; i < customInterfaceElementList.Count; i++)
@@ -44,7 +43,7 @@ namespace Barotrauma.Items.Components
item.CreateServerEvent(this);
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
//extradata contains an array of buttons clicked by a client (or nothing if nothing was clicked)
for (int i = 0; i < customInterfaceElementList.Count; i++)
@@ -1,5 +1,4 @@
using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -23,14 +22,14 @@ namespace Barotrauma.Items.Components
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
int eventIndex = (int)extraData[2];
int nodeStartIndex = eventIndex * MaxNodesPerNetworkEvent;
int nodeCount = MathHelper.Clamp(nodes.Count - nodeStartIndex, 0, MaxNodesPerNetworkEvent);
msg.WriteRangedInteger(0, (int)Math.Ceiling(MaxNodeCount / (float)MaxNodesPerNetworkEvent), eventIndex);
msg.WriteRangedInteger(0, MaxNodesPerNetworkEvent, nodeCount);
msg.WriteRangedIntegerDeprecated(0, (int)Math.Ceiling(MaxNodeCount / (float)MaxNodesPerNetworkEvent), eventIndex);
msg.WriteRangedIntegerDeprecated(0, MaxNodesPerNetworkEvent, nodeCount);
for (int i = nodeStartIndex; i < nodeStartIndex + nodeCount; i++)
{
msg.Write(nodes[i].X);
@@ -1,6 +1,5 @@
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
@@ -9,7 +8,7 @@ namespace Barotrauma
{
partial class Inventory : IServerSerializable, IClientSerializable
{
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
{
List<Item> prevItems = new List<Item>(Items);
ushort[] newItemIDs = new ushort[capacity];
@@ -142,7 +141,7 @@ namespace Barotrauma
}
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
SharedWrite(msg, extraData);
}
@@ -1,6 +1,5 @@
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
@@ -9,7 +8,7 @@ namespace Barotrauma
{
partial class Item : MapEntity, IDamageable, ISerializableEntity, IServerSerializable, IClientSerializable
{
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
string errorMsg = "";
if (extraData == null || extraData.Length == 0 || !(extraData[0] is NetEntityEvent.Type))
@@ -26,7 +25,7 @@ namespace Barotrauma
{
errorMsg = "Failed to write a network event for the item \"" + Name + "\" - event type not set.";
}
msg.WriteRangedInteger(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1, (int)NetEntityEvent.Type.Invalid);
msg.WriteRangedIntegerDeprecated(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1, (int)NetEntityEvent.Type.Invalid);
DebugConsole.Log(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Item.ServerWrite:InvalidData" + Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
@@ -35,7 +34,7 @@ namespace Barotrauma
int initialWritePos = msg.LengthBits;
NetEntityEvent.Type eventType = (NetEntityEvent.Type)extraData[0];
msg.WriteRangedInteger(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1, (int)eventType);
msg.WriteRangedIntegerDeprecated(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1, (int)eventType);
switch (eventType)
{
case NetEntityEvent.Type.ComponentState:
@@ -55,7 +54,7 @@ namespace Barotrauma
errorMsg = "Failed to write a component state event for the item \"" + Name + "\" - component \"" + components[componentIndex] + "\" is not server serializable.";
break;
}
msg.WriteRangedInteger(0, components.Count - 1, componentIndex);
msg.WriteRangedIntegerDeprecated(0, components.Count - 1, componentIndex);
(components[componentIndex] as IServerSerializable).ServerWrite(msg, c, extraData);
break;
case NetEntityEvent.Type.InventoryState:
@@ -75,7 +74,7 @@ namespace Barotrauma
errorMsg = "Failed to write an inventory state event for the item \"" + Name + "\" - component \"" + components[containerIndex] + "\" is not server serializable.";
break;
}
msg.WriteRangedInteger(0, components.Count - 1, containerIndex);
msg.WriteRangedIntegerDeprecated(0, components.Count - 1, containerIndex);
(components[containerIndex] as ItemContainer).Inventory.ServerWrite(msg, c);
break;
case NetEntityEvent.Type.Status:
@@ -92,7 +91,7 @@ namespace Barotrauma
byte targetLimbIndex = targetLimb != null && targetCharacter != null ? (byte)Array.IndexOf(targetCharacter.AnimController.Limbs, targetLimb) : (byte)255;
msg.Write((byte)components.IndexOf(targetComponent));
msg.WriteRangedInteger(0, Enum.GetValues(typeof(ActionType)).Length - 1, (int)actionType);
msg.WriteRangedIntegerDeprecated(0, Enum.GetValues(typeof(ActionType)).Length - 1, (int)actionType);
msg.Write(targetID);
msg.Write(targetLimbIndex);
}
@@ -107,7 +106,7 @@ namespace Barotrauma
Character targetCharacter = FindEntityByID(targetID) as Character;
byte targetLimbIndex = targetLimb != null && targetCharacter != null ? (byte)Array.IndexOf(targetCharacter.AnimController.Limbs, targetLimb) : (byte)255;
msg.WriteRangedInteger(0, Enum.GetValues(typeof(ActionType)).Length - 1, (int)actionType);
msg.WriteRangedIntegerDeprecated(0, Enum.GetValues(typeof(ActionType)).Length - 1, (int)actionType);
msg.Write((byte)(targetComponent == null ? 255 : components.IndexOf(targetComponent)));
msg.Write(targetID);
msg.Write(targetLimbIndex);
@@ -131,16 +130,16 @@ namespace Barotrauma
if (!string.IsNullOrEmpty(errorMsg))
{
//something went wrong - rewind the write position and write invalid event type to prevent creating an unreadable event
msg.ReadBits(msg.Data, 0, initialWritePos);
msg.BitPosition = initialWritePos;
msg.LengthBits = initialWritePos;
msg.WriteRangedInteger(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1, (int)NetEntityEvent.Type.Invalid);
msg.WriteRangedIntegerDeprecated(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1, (int)NetEntityEvent.Type.Invalid);
DebugConsole.Log(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Item.ServerWrite:" + errorMsg, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
}
}
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
{
NetEntityEvent.Type eventType =
(NetEntityEvent.Type)msg.ReadRangedInteger(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1);
@@ -198,7 +197,7 @@ namespace Barotrauma
}
}
public void WriteSpawnData(NetBuffer msg)
public void WriteSpawnData(IWriteMessage msg)
{
if (GameMain.Server == null) return;
@@ -329,14 +328,14 @@ namespace Barotrauma
}
}
public void ServerWritePosition(NetBuffer msg, Client c, object[] extraData = null)
public void ServerWritePosition(IWriteMessage msg, Client c, object[] extraData = null)
{
msg.Write(ID);
NetBuffer tempBuffer = new NetBuffer();
IWriteMessage tempBuffer = new WriteOnlyMessage();
body.ServerWrite(tempBuffer, c, extraData);
msg.Write((byte)tempBuffer.LengthBytes);
msg.Write(tempBuffer);
msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes);
msg.WritePadBits();
}
@@ -1,5 +1,4 @@
using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -49,7 +48,7 @@ namespace Barotrauma
}
}
public void ServerWrite(NetBuffer message, Client c, object[] extraData = null)
public void ServerWrite(IWriteMessage message, Client c, object[] extraData = null)
{
message.WriteRangedSingle(MathHelper.Clamp(waterVolume / Volume, 0.0f, 1.5f), 0.0f, 1.5f, 8);
message.WriteRangedSingle(MathHelper.Clamp(OxygenPercentage, 0.0f, 100.0f), 0.0f, 100.0f, 8);
@@ -57,7 +56,7 @@ namespace Barotrauma
message.Write(FireSources.Count > 0);
if (FireSources.Count > 0)
{
message.WriteRangedInteger(0, 16, Math.Min(FireSources.Count, 16));
message.WriteRangedIntegerDeprecated(0, 16, Math.Min(FireSources.Count, 16));
for (int i = 0; i < Math.Min(FireSources.Count, 16); i++)
{
var fireSource = FireSources[i];
@@ -73,7 +72,7 @@ namespace Barotrauma
}
//used when clients use the water/fire console commands
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
{
float newWaterVolume = msg.ReadRangedSingle(0.0f, 1.5f, 8) * Volume;
@@ -1,5 +1,4 @@
using Barotrauma.Networking;
using Lidgren.Network;
namespace Barotrauma
{
@@ -10,8 +9,9 @@ namespace Barotrauma
GameMain.Server.KarmaManager.OnStructureHealthChanged(this, attacker, damageAmount);
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
msg.Write((byte)Sections.Length);
for (int i = 0; i < Sections.Length; i++)
{
msg.WriteRangedSingle(Sections[i].damage / Health, 0.0f, 1.0f, 8);
@@ -1,17 +1,16 @@
using Barotrauma.Networking;
using Lidgren.Network;
namespace Barotrauma
{
partial class Submarine
{
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
msg.Write(ID);
NetBuffer tempBuffer = new NetBuffer();
IWriteMessage tempBuffer = new WriteOnlyMessage();
subBody.Body.ServerWrite(tempBuffer, c, extraData);
msg.Write((byte)tempBuffer.LengthBytes);
msg.Write(tempBuffer);
msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes);
msg.WritePadBits();
}
}
@@ -1,5 +1,4 @@
using Lidgren.Network;
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -118,10 +117,23 @@ namespace Barotrauma.Networking
public bool IsBanned(IPAddress IP, ulong steamID)
{
bannedPlayers.RemoveAll(bp => bp.ExpirationTime.HasValue && DateTime.Now > bp.ExpirationTime.Value);
if (IPAddress.IsLoopback(IP)) { return false; }
return bannedPlayers.Any(bp => bp.CompareTo(IP) || (steamID > 0 && bp.SteamID == steamID));
}
public bool IsBanned(IPAddress IP)
{
if (IPAddress.IsLoopback(IP)) { return false; }
bannedPlayers.RemoveAll(bp => bp.ExpirationTime.HasValue && DateTime.Now > bp.ExpirationTime.Value);
return bannedPlayers.Any(bp => bp.CompareTo(IP));
}
public bool IsBanned(ulong steamID)
{
bannedPlayers.RemoveAll(bp => bp.ExpirationTime.HasValue && DateTime.Now > bp.ExpirationTime.Value);
return bannedPlayers.Any(bp => (steamID > 0 && bp.SteamID == steamID));
}
public void BanPlayer(string name, IPAddress ip, string reason, TimeSpan? duration)
{
string ipStr = ip.IsIPv4MappedToIPv6 ? ip.MapToIPv4().ToString() : ip.ToString();
@@ -262,7 +274,7 @@ namespace Barotrauma.Networking
}
}
public void ServerAdminWrite(NetBuffer outMsg, Client c)
public void ServerAdminWrite(IWriteMessage outMsg, Client c)
{
if (!c.HasPermission(ClientPermissions.Ban))
{
@@ -273,7 +285,7 @@ namespace Barotrauma.Networking
outMsg.Write(c.Connection == GameMain.Server.OwnerConnection);
outMsg.WritePadBits();
outMsg.WriteVariableInt32(bannedPlayers.Count);
outMsg.WriteVariableUInt32((UInt32)bannedPlayers.Count);
for (int i = 0; i < bannedPlayers.Count; i++)
{
BannedPlayer bannedPlayer = bannedPlayers[i];
@@ -289,14 +301,14 @@ namespace Barotrauma.Networking
}
}
public bool ServerAdminRead(NetBuffer incMsg, Client c)
public bool ServerAdminRead(IReadMessage incMsg, Client c)
{
if (!c.HasPermission(ClientPermissions.Ban))
{
UInt16 removeCount = incMsg.ReadUInt16();
incMsg.Position += removeCount * 4 * 8;
incMsg.BitPosition += removeCount * 4 * 8;
UInt16 rangeBanCount = incMsg.ReadUInt16();
incMsg.Position += rangeBanCount * 4 * 8;
incMsg.BitPosition += rangeBanCount * 4 * 8;
return false;
}
else
@@ -1,5 +1,4 @@
using Barotrauma.Items.Components;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
@@ -9,7 +8,7 @@ namespace Barotrauma.Networking
{
partial class ChatMessage
{
public static void ServerRead(NetIncomingMessage msg, Client c)
public static void ServerRead(IReadMessage msg, Client c)
{
c.KickAFKTimer = 0.0f;
@@ -61,22 +60,24 @@ namespace Barotrauma.Networking
}
float similarity = 0.0f;
//don't do message similarity checks on order messages
if (orderMsg == null)
for (int i = 0; i < c.LastSentChatMessages.Count; i++)
{
for (int i = 0; i < c.LastSentChatMessages.Count; i++)
float closeFactor = 1.0f / (c.LastSentChatMessages.Count - i);
if (string.IsNullOrEmpty(txt))
{
float closeFactor = 1.0f / (c.LastSentChatMessages.Count - i);
if (string.IsNullOrEmpty(txt))
{
similarity += closeFactor;
}
else
{
int levenshteinDist = ToolBox.LevenshteinDistance(txt, c.LastSentChatMessages[i]);
similarity += Math.Max((txt.Length - levenshteinDist) / (float)txt.Length * closeFactor, 0.0f);
}
similarity += closeFactor;
}
else
{
int levenshteinDist = ToolBox.LevenshteinDistance(txt, c.LastSentChatMessages[i]);
similarity += Math.Max((txt.Length - levenshteinDist) / (float)txt.Length * closeFactor, 0.0f);
}
}
//order/report messages can be sent a little faster than normal messages without triggering the spam filter
if (orderMsg != null)
{
similarity *= 0.25f;
}
bool isOwner = GameMain.Server.OwnerConnection != null && c.Connection == GameMain.Server.OwnerConnection;
@@ -153,7 +154,7 @@ namespace Barotrauma.Networking
return length;
}
public virtual void ServerWrite(NetOutgoingMessage msg, Client c)
public virtual void ServerWrite(IWriteMessage msg, Client c)
{
msg.Write((byte)ServerNetObject.CHAT_MESSAGE);
msg.Write(NetStateID);
@@ -168,4 +169,4 @@ namespace Barotrauma.Networking
}
}
}
}
}
@@ -1,5 +1,4 @@
using Lidgren.Network;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -7,8 +6,6 @@ namespace Barotrauma.Networking
{
partial class Client : IDisposable
{
public ulong SteamID;
public bool VoiceEnabled = true;
public UInt16 LastRecvClientListUpdate = 0;
@@ -61,9 +58,11 @@ namespace Barotrauma.Networking
public float DeleteDisconnectedTimer;
public CharacterInfo CharacterInfo;
public NetConnection Connection { get; set; }
public NetworkConnection Connection { get; set; }
public bool SpectateOnly;
public int KarmaKickCount;
private float karma = 100.0f;
public float Karma
@@ -108,28 +107,32 @@ namespace Barotrauma.Networking
NeedsMidRoundSync = false;
}
public static bool IsValidName(string name, GameServer server)
public static bool IsValidName(string name, ServerSettings serverSettings)
{
char[] disallowedChars = new char[] { ';', ',', '<', '>', '/', '\\', '[', ']', '"', '?' };
if (name.Any(c => disallowedChars.Contains(c))) return false;
foreach (char character in name)
{
if (!server.ServerSettings.AllowedClientNameChars.Any(charRange => (int)character >= charRange.First && (int)character <= charRange.Second)) return false;
if (!serverSettings.AllowedClientNameChars.Any(charRange => (int)character >= charRange.First && (int)character <= charRange.Second)) return false;
}
return true;
}
public bool IPMatches(string ip)
public bool EndpointMatches(string endpoint)
{
if (Connection?.RemoteEndPoint == null) { return false; }
if (Connection.RemoteEndPoint.Address.IsIPv4MappedToIPv6 &&
Connection.RemoteEndPoint.Address.MapToIPv4().ToString() == ip)
if (Connection is LidgrenConnection lidgrenConn)
{
return true;
if (lidgrenConn.IPEndPoint?.Address == null) { return false; }
if ((lidgrenConn.IPEndPoint?.Address.IsIPv4MappedToIPv6 ?? false) &&
lidgrenConn.IPEndPoint?.Address.MapToIPv4().ToString() == endpoint)
{
return true;
}
}
return Connection.RemoteEndPoint.Address.ToString() == ip;
return Connection.EndPointString == endpoint;
}
public void SetPermissions(ClientPermissions permissions, List<DebugConsole.Command> permittedConsoleCommands)
@@ -13,7 +13,7 @@ namespace Barotrauma
}
}
public void ServerWrite(Lidgren.Network.NetBuffer message, Client client, object[] extraData = null)
public void ServerWrite(IWriteMessage message, Client client, object[] extraData = null)
{
if (GameMain.Server == null) return;
@@ -1,5 +1,4 @@
using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
@@ -12,11 +11,11 @@ namespace Barotrauma.Networking
{
public class FileTransferOut
{
private byte[] data;
private readonly byte[] data;
private DateTime startingTime;
private readonly DateTime startingTime;
private NetConnection connection;
private readonly NetworkConnection connection;
public FileTransferStatus Status;
@@ -40,7 +39,7 @@ namespace Barotrauma.Networking
public float Progress
{
get { return SentOffset / (float)Data.Length; }
get { return KnownReceivedOffset / (float)Data.Length; }
}
public float WaitTimer
@@ -54,20 +53,24 @@ namespace Barotrauma.Networking
get { return data; }
}
public bool Acknowledged;
public int SentOffset
{
get;
set;
}
public NetConnection Connection
public int KnownReceivedOffset;
public NetworkConnection Connection
{
get { return connection; }
}
public int SequenceChannel;
public int ID;
public FileTransferOut(NetConnection recipient, FileTransferType fileType, string filePath)
public FileTransferOut(NetworkConnection recipient, FileTransferType fileType, string filePath)
{
connection = recipient;
@@ -75,6 +78,10 @@ namespace Barotrauma.Networking
FilePath = filePath;
FileName = Path.GetFileName(filePath);
Acknowledged = false;
SentOffset = 0;
KnownReceivedOffset = 0;
Status = FileTransferStatus.NotStarted;
startingTime = DateTime.Now;
@@ -105,26 +112,26 @@ namespace Barotrauma.Networking
public FileTransferDelegate OnStarted;
public FileTransferDelegate OnEnded;
private List<FileTransferOut> activeTransfers;
private readonly List<FileTransferOut> activeTransfers;
private int chunkLen;
private readonly int chunkLen;
private NetPeer peer;
private readonly ServerPeer peer;
public List<FileTransferOut> ActiveTransfers
{
get { return activeTransfers; }
}
public FileSender(NetworkMember networkMember)
public FileSender(ServerPeer serverPeer, int mtu)
{
peer = networkMember.NetPeer;
chunkLen = peer.Configuration.MaximumTransmissionUnit - 100;
peer = serverPeer;
chunkLen = mtu - 100;
activeTransfers = new List<FileTransferOut>();
}
public FileTransferOut StartTransfer(NetConnection recipient, FileTransferType fileType, string filePath)
public FileTransferOut StartTransfer(NetworkConnection recipient, FileTransferType fileType, string filePath)
{
if (activeTransfers.Count >= MaxTransferCount)
{
@@ -147,11 +154,11 @@ namespace Barotrauma.Networking
{
transfer = new FileTransferOut(recipient, fileType, filePath)
{
SequenceChannel = 1
ID = 1
};
while (activeTransfers.Any(t => t.Connection == recipient && t.SequenceChannel == transfer.SequenceChannel))
while (activeTransfers.Any(t => t.Connection == recipient && t.ID == transfer.ID))
{
transfer.SequenceChannel++;
transfer.ID++;
}
activeTransfers.Add(transfer);
}
@@ -168,10 +175,10 @@ namespace Barotrauma.Networking
public void Update(float deltaTime)
{
activeTransfers.RemoveAll(t => t.Connection.Status != NetConnectionStatus.Connected);
activeTransfers.RemoveAll(t => t.Connection.Status != NetworkConnectionStatus.Connected);
var endedTransfers = activeTransfers.FindAll(t =>
t.Connection.Status != NetConnectionStatus.Connected ||
t.Connection.Status != NetworkConnectionStatus.Connected ||
t.Status == FileTransferStatus.Finished ||
t.Status == FileTransferStatus.Canceled ||
t.Status == FileTransferStatus.Error);
@@ -187,78 +194,95 @@ namespace Barotrauma.Networking
transfer.WaitTimer -= deltaTime;
if (transfer.WaitTimer > 0.0f) continue;
if (!transfer.Connection.CanSendImmediately(NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel)) continue;
transfer.WaitTimer = 0.05f;// transfer.Connection.AverageRoundtripTime;
// send another part of the file
long remaining = transfer.Data.Length - transfer.SentOffset;
int sendByteCount = (remaining > chunkLen ? chunkLen : (int)remaining);
NetOutgoingMessage message;
IWriteMessage message;
//first message; send length, chunk length, file name etc
if (transfer.SentOffset == 0)
try
{
message = peer.CreateMessage();
message.Write((byte)ServerPacketHeader.FILE_TRANSFER);
//if the recipient is the owner of the server (= a client running the server from the main exe)
//we don't need to send anything, the client can just read the file directly
if (transfer.Connection == GameMain.Server.OwnerConnection)
//first message; send length, file name etc
//wait for acknowledgement before sending data
if (!transfer.Acknowledged)
{
message.Write((byte)FileTransferMessageType.TransferOnSameMachine);
message.Write((byte)transfer.FileType);
message.Write(transfer.FilePath);
GameMain.Server.CompressOutgoingMessage(message);
transfer.Connection.SendMessage(message, NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel);
transfer.Status = FileTransferStatus.Finished;
message = new WriteOnlyMessage();
message.Write((byte)ServerPacketHeader.FILE_TRANSFER);
//if the recipient is the owner of the server (= a client running the server from the main exe)
//we don't need to send anything, the client can just read the file directly
if (transfer.Connection == GameMain.Server.OwnerConnection)
{
message.Write((byte)FileTransferMessageType.TransferOnSameMachine);
message.Write((byte)transfer.ID);
message.Write((byte)transfer.FileType);
message.Write(transfer.FilePath);
peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable);
transfer.Status = FileTransferStatus.Finished;
}
else
{
message.Write((byte)FileTransferMessageType.Initiate);
message.Write((byte)transfer.ID);
message.Write((byte)transfer.FileType);
//message.Write((ushort)chunkLen);
message.Write(transfer.Data.Length);
message.Write(transfer.FileName);
peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable);
transfer.Status = FileTransferStatus.Sending;
if (GameSettings.VerboseLogging)
{
DebugConsole.Log("Sending file transfer initiation message: ");
DebugConsole.Log(" File: " + transfer.FileName);
DebugConsole.Log(" Size: " + transfer.Data.Length);
DebugConsole.Log(" ID: " + transfer.ID);
}
}
return;
}
else
message = new WriteOnlyMessage();
message.Write((byte)ServerPacketHeader.FILE_TRANSFER);
message.Write((byte)FileTransferMessageType.Data);
message.Write((byte)transfer.ID);
message.Write(transfer.SentOffset);
byte[] sendBytes = new byte[sendByteCount];
Array.Copy(transfer.Data, transfer.SentOffset, sendBytes, 0, sendByteCount);
message.Write((ushort)sendByteCount);
message.Write(sendBytes, 0, sendByteCount);
transfer.SentOffset += sendByteCount;
if (transfer.SentOffset > transfer.KnownReceivedOffset + chunkLen * 5 ||
transfer.SentOffset >= transfer.Data.Length)
{
message.Write((byte)FileTransferMessageType.Initiate);
message.Write((byte)transfer.FileType);
message.Write((ushort)chunkLen);
message.Write((ulong)transfer.Data.Length);
message.Write(transfer.FileName);
GameMain.Server.CompressOutgoingMessage(message);
transfer.Connection.SendMessage(message, NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel);
transfer.Status = FileTransferStatus.Sending;
if (GameSettings.VerboseLogging)
{
DebugConsole.Log("Sending file transfer initiation message: ");
DebugConsole.Log(" File: " + transfer.FileName);
DebugConsole.Log(" Size: " + transfer.Data.Length);
DebugConsole.Log(" Sequence channel: " + transfer.SequenceChannel);
}
transfer.SentOffset = transfer.KnownReceivedOffset;
}
peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable);
}
message = peer.CreateMessage(1 + 1 + sendByteCount);
message.Write((byte)ServerPacketHeader.FILE_TRANSFER);
message.Write((byte)FileTransferMessageType.Data);
byte[] sendBytes = new byte[sendByteCount];
Array.Copy(transfer.Data, transfer.SentOffset, sendBytes, 0, sendByteCount);
message.Write(sendBytes);
GameMain.Server.CompressOutgoingMessage(message);
transfer.Connection.SendMessage(message, NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel);
transfer.SentOffset += sendByteCount;
catch (Exception e)
{
DebugConsole.ThrowError("FileSender threw an exception when trying to send data", e);
GameAnalyticsManager.AddErrorEventOnce(
"FileSender.Update:Exception",
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"FileSender threw an exception when trying to send data:\n" + e.Message + "\n" + e.StackTrace);
transfer.Status = FileTransferStatus.Error;
break;
}
if (GameSettings.VerboseLogging)
{
DebugConsole.Log("Sending " + sendByteCount + " bytes of the file " + transfer.FileName + " (" + transfer.SentOffset + "/" + transfer.Data.Length + " sent)");
}
if (remaining - sendByteCount <= 0)
{
transfer.Status = FileTransferStatus.Finished;
}
}
}
@@ -272,18 +296,35 @@ namespace Barotrauma.Networking
GameMain.Server.SendCancelTransferMsg(transfer);
}
public void ReadFileRequest(NetIncomingMessage inc, Client client)
public void ReadFileRequest(IReadMessage inc, Client client)
{
byte messageType = inc.ReadByte();
if (messageType == (byte)FileTransferMessageType.Cancel)
{
byte sequenceChannel = inc.ReadByte();
var matchingTransfer = activeTransfers.Find(t => t.Connection == inc.SenderConnection && t.SequenceChannel == sequenceChannel);
byte transferId = inc.ReadByte();
var matchingTransfer = activeTransfers.Find(t => t.Connection == inc.Sender && t.ID == transferId);
if (matchingTransfer != null) CancelTransfer(matchingTransfer);
return;
}
else if (messageType == (byte)FileTransferMessageType.Data)
{
byte transferId = inc.ReadByte();
var matchingTransfer = activeTransfers.Find(t => t.Connection == inc.Sender && t.ID == transferId);
if (matchingTransfer != null)
{
matchingTransfer.Acknowledged = true;
int offset = inc.ReadInt32();
matchingTransfer.KnownReceivedOffset = offset > matchingTransfer.KnownReceivedOffset ? offset : matchingTransfer.KnownReceivedOffset;
if (matchingTransfer.SentOffset < matchingTransfer.KnownReceivedOffset) { matchingTransfer.SentOffset = matchingTransfer.KnownReceivedOffset; }
if (matchingTransfer.KnownReceivedOffset >= matchingTransfer.Data.Length)
{
matchingTransfer.Status = FileTransferStatus.Finished;
}
}
}
byte fileType = inc.ReadByte();
switch (fileType)
@@ -295,17 +336,17 @@ namespace Barotrauma.Networking
if (requestedSubmarine != null)
{
StartTransfer(inc.SenderConnection, FileTransferType.Submarine, requestedSubmarine.FilePath);
StartTransfer(inc.Sender, FileTransferType.Submarine, requestedSubmarine.FilePath);
}
break;
case (byte)FileTransferType.CampaignSave:
if (GameMain.GameSession != null &&
!ActiveTransfers.Any(t => t.Connection == inc.SenderConnection && t.FileType == FileTransferType.CampaignSave))
!ActiveTransfers.Any(t => t.Connection == inc.Sender && t.FileType == FileTransferType.CampaignSave))
{
StartTransfer(inc.SenderConnection, FileTransferType.CampaignSave, GameMain.GameSession.SavePath);
StartTransfer(inc.Sender, FileTransferType.CampaignSave, GameMain.GameSession.SavePath);
if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign)
{
client.LastCampaignSaveSendTime = new Pair<ushort, float>(campaign.LastSaveID, (float)NetTime.Now);
client.LastCampaignSaveSendTime = new Pair<ushort, float>(campaign.LastSaveID, (float)Lidgren.Network.NetTime.Now);
}
}
break;
File diff suppressed because it is too large Load Diff
@@ -1,539 +0,0 @@
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Barotrauma.Networking
{
class UnauthenticatedClient
{
public readonly NetConnection Connection;
public readonly ulong SteamID;
public Facepunch.Steamworks.ServerAuth.Status? SteamAuthStatus = null;
public readonly int Nonce;
public int FailedAttempts;
public float AuthTimer;
public UnauthenticatedClient(NetConnection connection, int nonce, ulong steamID = 0)
{
Connection = connection;
SteamID = steamID;
Nonce = nonce;
AuthTimer = 10.0f;
FailedAttempts = 0;
}
}
partial class GameServer : NetworkMember
{
private Int32 ownerKey = 0;
List<UnauthenticatedClient> unauthenticatedClients = new List<UnauthenticatedClient>();
private void ReadClientSteamAuthRequest(NetIncomingMessage inc, NetConnection senderConnection, out ulong clientSteamID)
{
clientSteamID = 0;
if (!Steam.SteamManager.USE_STEAM)
{
DebugConsole.Log("Received a Steam auth request from " + senderConnection.RemoteEndPoint + ". Steam authentication not required, handling auth normally.");
//not using steam, handle auth normally
HandleClientAuthRequest(senderConnection, 0);
return;
}
if (senderConnection == OwnerConnection)
{
//the client is the owner of the server, no need for authentication
//(it would fail with a "duplicate request" error anyway)
HandleClientAuthRequest(senderConnection, 0);
return;
}
clientSteamID = inc.ReadUInt64();
int authTicketLength = inc.ReadInt32();
inc.ReadBytes(authTicketLength, out byte[] authTicketData);
DebugConsole.Log("Received a Steam auth request");
DebugConsole.Log(" Steam ID: "+ clientSteamID);
DebugConsole.Log(" Auth ticket length: " + authTicketLength);
DebugConsole.Log(" Auth ticket data: " +
((authTicketData == null) ? "null" : ToolBox.LimitString(string.Concat(authTicketData.Select(b => b.ToString("X2"))), 16)));
if (senderConnection != OwnerConnection &&
serverSettings.BanList.IsBanned(senderConnection.RemoteEndPoint.Address, clientSteamID))
{
return;
}
ulong steamID = clientSteamID;
if (unauthenticatedClients.Any(uc => uc.Connection == inc.SenderConnection))
{
var steamAuthedClient = unauthenticatedClients.Find(uc =>
uc.Connection == inc.SenderConnection &&
uc.SteamID == steamID &&
uc.SteamAuthStatus == Facepunch.Steamworks.ServerAuth.Status.OK);
if (steamAuthedClient != null)
{
DebugConsole.Log("Client already authenticated, sending AUTH_RESPONSE again...");
HandleClientAuthRequest(inc.SenderConnection, steamID);
}
DebugConsole.Log("Steam authentication already pending...");
return;
}
if (authTicketData == null)
{
DebugConsole.Log("Invalid request");
return;
}
unauthenticatedClients.RemoveAll(uc => uc.Connection == senderConnection);
int nonce = CryptoRandom.Instance.Next();
var unauthClient = new UnauthenticatedClient(senderConnection, nonce, clientSteamID)
{
AuthTimer = 20
};
unauthenticatedClients.Add(unauthClient);
if (!Steam.SteamManager.StartAuthSession(authTicketData, clientSteamID))
{
unauthenticatedClients.Remove(unauthClient);
if (GameMain.Config.RequireSteamAuthentication)
{
unauthClient.Connection.Disconnect(DisconnectReason.SteamAuthenticationFailed.ToString());
Log("Disconnected unauthenticated client (Steam ID: " + steamID + "). Steam authentication failed.", ServerLog.MessageType.ServerMessage);
}
else
{
DebugConsole.Log("Steam authentication failed, skipping to basic auth...");
HandleClientAuthRequest(senderConnection);
return;
}
}
return;
}
public void OnAuthChange(ulong steamID, ulong ownerID, Facepunch.Steamworks.ServerAuth.Status status)
{
DebugConsole.Log("************ OnAuthChange");
DebugConsole.Log(" Steam ID: " + steamID);
DebugConsole.Log(" Owner ID: " + ownerID);
DebugConsole.Log(" Status: " + status);
UnauthenticatedClient unauthClient = unauthenticatedClients.Find(uc => uc.SteamID == ownerID);
if (unauthClient != null)
{
unauthClient.SteamAuthStatus = status;
switch (status)
{
case Facepunch.Steamworks.ServerAuth.Status.OK:
////steam authentication done, check password next
Log("Successfully authenticated client via Steam (Steam ID: " + steamID + ").", ServerLog.MessageType.ServerMessage);
HandleClientAuthRequest(unauthClient.Connection, unauthClient.SteamID);
break;
default:
unauthenticatedClients.Remove(unauthClient);
if (GameMain.Config.RequireSteamAuthentication)
{
Log("Disconnected unauthenticated client (Steam ID: " + steamID + "). Steam authentication failed, (" + status + ").", ServerLog.MessageType.ServerMessage);
unauthClient.Connection.Disconnect(DisconnectReason.SteamAuthenticationFailed.ToString() + "/ (" + status.ToString() + ")");
}
else
{
DebugConsole.Log("Steam authentication failed (" + status.ToString() + "), skipping to basic auth...");
HandleClientAuthRequest(unauthClient.Connection);
return;
}
break;
}
return;
}
else
{
DebugConsole.Log(" No unauthenticated clients found with the Steam ID " + steamID);
}
//kick connected client if status becomes invalid (e.g. VAC banned, not connected to steam)
/*if (status != Facepunch.Steamworks.ServerAuth.Status.OK && GameMain.Config.RequireSteamAuthentication)
{
var connectedClient = connectedClients.Find(c => c.SteamID == ownerID);
if (connectedClient != null)
{
Log("Disconnecting client " + connectedClient.Name + " (Steam ID: " + steamID + "). Steam authentication no longer valid (" + status + ").", ServerLog.MessageType.ServerMessage);
KickClient(connectedClient, $"DisconnectMessage.SteamAuthNoLongerValid~[status]={status.ToString()}");
}
}*/
}
private bool IsServerOwner(NetIncomingMessage inc, NetConnection senderConnection)
{
string address = senderConnection.RemoteEndPoint.Address.MapToIPv4().ToString();
int incKey = inc.ReadInt32();
if (ownerKey == 0)
{
return false; //ownership key has been destroyed or has never existed
}
if (address.ToString() != "127.0.0.1")
{
return false; //not localhost
}
if (incKey != ownerKey)
{
return false; //incorrect owner key, how did this even happen
}
return true;
}
private void HandleOwnership(NetIncomingMessage inc, NetConnection senderConnection)
{
DebugConsole.Log("HandleOwnership (" + senderConnection.RemoteEndPoint.Address + ")");
if (IsServerOwner(inc, senderConnection))
{
ownerKey = 0; //destroy owner key so nobody else can take ownership of the server
OwnerConnection = senderConnection;
DebugConsole.NewMessage("Successfully set up server owner", Color.Lime);
}
}
private void HandleClientAuthRequest(NetConnection connection, ulong steamID = 0)
{
DebugConsole.Log("HandleClientAuthRequest (steamID " + steamID + ")");
if (GameMain.Config.RequireSteamAuthentication && connection != OwnerConnection && steamID == 0)
{
DebugConsole.Log("Disconnecting " + connection.RemoteEndPoint + ", Steam authentication required.");
connection.Disconnect(DisconnectReason.SteamAuthenticationRequired.ToString());
return;
}
//client wants to know if server requires password
if (ConnectedClients.Find(c => c.Connection == connection) != null)
{
//this client has already been authenticated
return;
}
UnauthenticatedClient unauthClient = unauthenticatedClients.Find(uc => uc.Connection == connection);
if (unauthClient == null)
{
DebugConsole.Log("Unauthed client, generating a nonce...");
//new client, generate nonce and add to unauth queue
if (ConnectedClients.Count >= serverSettings.MaxPlayers)
{
//server is full, can't allow new connection
connection.Disconnect(DisconnectReason.ServerFull.ToString());
if (steamID > 0) { Steam.SteamManager.StopAuthSession(steamID); }
return;
}
int nonce = CryptoRandom.Instance.Next();
unauthClient = new UnauthenticatedClient(connection, nonce, steamID);
unauthenticatedClients.Add(unauthClient);
}
unauthClient.AuthTimer = 10.0f;
//if the client is already in the queue, getting another unauth request means that our response was lost; resend
NetOutgoingMessage nonceMsg = server.CreateMessage();
nonceMsg.Write((byte)ServerPacketHeader.AUTH_RESPONSE);
if (serverSettings.HasPassword && connection != OwnerConnection)
{
nonceMsg.Write(true); //true = password
nonceMsg.Write((Int32)unauthClient.Nonce); //here's nonce, encrypt with this
}
else
{
nonceMsg.Write(false); //false = no password
}
CompressOutgoingMessage(nonceMsg);
DebugConsole.Log("Sending auth response...");
server.SendMessage(nonceMsg, connection, NetDeliveryMethod.Unreliable);
}
private void ClientInitRequest(NetIncomingMessage inc)
{
DebugConsole.Log("Received client init request");
if (ConnectedClients.Find(c => c.Connection == inc.SenderConnection) != null)
{
//this client was already authenticated
//another init request means they didn't get any update packets yet
DebugConsole.Log("Client already connected, ignoring...");
return;
}
UnauthenticatedClient unauthClient = unauthenticatedClients.Find(uc => uc.Connection == inc.SenderConnection);
if (unauthClient == null)
{
//client did not ask for nonce first, can't authorize
inc.SenderConnection.Disconnect(DisconnectReason.AuthenticationRequired.ToString());
if (unauthClient.SteamID > 0) { Steam.SteamManager.StopAuthSession(unauthClient.SteamID); }
return;
}
if (serverSettings.HasPassword && inc.SenderConnection != OwnerConnection)
{
//decrypt message and compare password
string clPw = inc.ReadString();
if (!serverSettings.IsPasswordCorrect(clPw, unauthClient.Nonce))
{
unauthClient.FailedAttempts++;
if (unauthClient.FailedAttempts > 3)
{
//disconnect and ban after too many failed attempts
serverSettings.BanList.BanPlayer("Unnamed", unauthClient.Connection.RemoteEndPoint.Address, "DisconnectMessage.TooManyFailedLogins", duration: null);
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.TooManyFailedLogins, "");
Log(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " has been banned from the server (too many wrong passwords)", ServerLog.MessageType.Error);
DebugConsole.NewMessage(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " has been banned from the server (too many wrong passwords)", Color.Red);
return;
}
else
{
//not disconnecting the player here, because they'll still use the same connection and nonce if they try logging in again
NetOutgoingMessage reject = server.CreateMessage();
reject.Write((byte)ServerPacketHeader.AUTH_FAILURE);
reject.Write("Wrong password! You have " + Convert.ToString(4 - unauthClient.FailedAttempts) + " more attempts before you're banned from the server.");
Log(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " failed to join the server (incorrect password)", ServerLog.MessageType.Error);
DebugConsole.NewMessage(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " failed to join the server (incorrect password)", Color.Red);
CompressOutgoingMessage(reject);
server.SendMessage(reject, unauthClient.Connection, NetDeliveryMethod.Unreliable);
unauthClient.AuthTimer = 10.0f;
return;
}
}
}
string clVersion = inc.ReadString();
UInt16 contentPackageCount = inc.ReadUInt16();
List<string> contentPackageNames = new List<string>();
List<string> contentPackageHashes = new List<string>();
for (int i = 0; i < contentPackageCount; i++)
{
string packageName = inc.ReadString();
string packageHash = inc.ReadString();
contentPackageNames.Add(packageName);
contentPackageHashes.Add(packageHash);
if (contentPackageCount == 0)
{
DebugConsole.Log("Client is using content package " +
(packageName ?? "null") + " (" + (packageHash ?? "null" + ")"));
}
}
if (contentPackageCount == 0)
{
DebugConsole.Log("Client did not list any content packages.");
}
string clName = Client.SanitizeName(inc.ReadString());
if (string.IsNullOrWhiteSpace(clName))
{
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.NoName, "");
Log(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " couldn't join the server (no name given)", ServerLog.MessageType.Error);
DebugConsole.NewMessage(inc.SenderConnection.RemoteEndPoint.Address.ToString() + " couldn't join the server (no name given)", Color.Red);
return;
}
bool? isCompatibleVersion = IsCompatible(clVersion, GameMain.Version.ToString());
if (isCompatibleVersion.HasValue && !isCompatibleVersion.Value)
{
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.InvalidVersion,
$"DisconnectMessage.InvalidVersion~[version]={GameMain.Version.ToString()}~[clientversion]={clVersion}");
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible game version)", ServerLog.MessageType.Error);
DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible game version)", Color.Red);
return;
}
//check if the client is missing any of the content packages the server requires
List<ContentPackage> missingPackages = new List<ContentPackage>();
foreach (ContentPackage contentPackage in GameMain.SelectedPackages)
{
if (!contentPackage.HasMultiplayerIncompatibleContent) continue;
bool packageFound = false;
for (int i = 0; i < contentPackageCount; i++)
{
if (contentPackageNames[i] == contentPackage.Name && contentPackageHashes[i] == contentPackage.MD5hash.Hash)
{
packageFound = true;
break;
}
}
if (!packageFound) missingPackages.Add(contentPackage);
}
if (missingPackages.Count == 1)
{
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.MissingContentPackage, $"DisconnectMessage.MissingContentPackage~[missingcontentpackage]={GetPackageStr(missingPackages[0])}");
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (missing content package " + GetPackageStr(missingPackages[0]) + ")", ServerLog.MessageType.Error);
return;
}
else if (missingPackages.Count > 1)
{
List<string> packageStrs = new List<string>();
missingPackages.ForEach(cp => packageStrs.Add(GetPackageStr(cp)));
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.MissingContentPackage, $"DisconnectMessage.MissingContentPackages~[missingcontentpackages]={string.Join(", ", packageStrs)}");
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (missing content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
return;
}
string GetPackageStr(ContentPackage contentPackage)
{
return "\"" + contentPackage.Name + "\" (hash " + contentPackage.MD5hash.ShortHash + ")";
}
//check if the client is using any contentpackages that are not compatible with the server
List<Pair<string, string>> incompatiblePackages = new List<Pair<string, string>>();
for (int i = 0; i < contentPackageNames.Count; i++)
{
if (!GameMain.Config.SelectedContentPackages.Any(cp => cp.Name == contentPackageNames[i] && cp.MD5hash.Hash == contentPackageHashes[i]))
{
incompatiblePackages.Add(new Pair<string, string>(contentPackageNames[i], contentPackageHashes[i]));
}
}
if (incompatiblePackages.Count == 1)
{
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.IncompatibleContentPackage,
$"DisconnectMessage.IncompatibleContentPackage~[incompatiblecontentpackage]={GetPackageStr2(incompatiblePackages[0])}");
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible content package " + GetPackageStr2(incompatiblePackages[0]) + ")", ServerLog.MessageType.Error);
return;
}
else if (incompatiblePackages.Count > 1)
{
List<string> packageStrs = new List<string>();
incompatiblePackages.ForEach(cp => packageStrs.Add(GetPackageStr2(cp)));
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.IncompatibleContentPackage,
$"DisconnectMessage.IncompatibleContentPackages~[incompatiblecontentpackages]={string.Join(", ", packageStrs)}");
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
return;
}
string GetPackageStr2(Pair<string, string> nameAndHash)
{
return "\"" + nameAndHash.First + "\" (hash " + Md5Hash.GetShortHash(nameAndHash.Second) + ")";
}
if (inc.SenderConnection != OwnerConnection && !serverSettings.Whitelist.IsWhiteListed(clName, inc.SenderConnection.RemoteEndPoint.Address))
{
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.NotOnWhitelist, "");
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (not in whitelist)", ServerLog.MessageType.Error);
DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (not in whitelist)", Color.Red);
return;
}
if (!Client.IsValidName(clName, this))
{
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.InvalidName, "");
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (invalid name)", ServerLog.MessageType.Error);
DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (invalid name)", Color.Red);
return;
}
if (inc.SenderConnection != OwnerConnection && Homoglyphs.Compare(clName.ToLower(), Name.ToLower()))
{
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.NameTaken, "");
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (name taken by the server)", ServerLog.MessageType.Error);
DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (name taken by the server)", Color.Red);
return;
}
Client nameTaken = ConnectedClients.Find(c => Homoglyphs.Compare(c.Name.ToLower(), clName.ToLower()));
if (nameTaken != null)
{
if (nameTaken.Connection.RemoteEndPoint.Address.ToString() == inc.SenderEndPoint.Address.ToString())
{
//both name and IP address match, replace this player's connection
nameTaken.Connection.Disconnect(DisconnectReason.SessionTaken.ToString());
nameTaken.Connection = unauthClient.Connection;
nameTaken.InitClientSync(); //reinitialize sync ids because this is a new connection
unauthenticatedClients.Remove(unauthClient);
unauthClient = null;
return;
}
else
{
//can't authorize this client
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.NameTaken, "");
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (name already taken)", ServerLog.MessageType.Error);
DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (name already taken)", Color.Red);
return;
}
}
//new client
Client newClient = new Client(clName, GetNewClientID());
newClient.InitClientSync();
newClient.Connection = unauthClient.Connection;
newClient.SteamID = unauthClient.SteamID;
unauthenticatedClients.Remove(unauthClient);
unauthClient = null;
ConnectedClients.Add(newClient);
var previousPlayer = previousPlayers.Find(p => p.MatchesClient(newClient));
if (previousPlayer != null)
{
newClient.Karma = previousPlayer.Karma;
foreach (Client c in previousPlayer.KickVoters)
{
if (!connectedClients.Contains(c)) { continue; }
newClient.AddKickVote(c);
}
}
LastClientListUpdateID++;
if (newClient.Connection == OwnerConnection)
{
newClient.GivePermission(ClientPermissions.All);
newClient.PermittedConsoleCommands.AddRange(DebugConsole.Commands);
GameMain.Server.UpdateClientPermissions(newClient);
GameMain.Server.SendConsoleMessage("Granted all permissions to " + newClient.Name + ".", newClient);
}
GameMain.Server.SendChatMessage($"ServerMessage.JoinedServer~[client]={clName}", ChatMessageType.Server, null);
serverSettings.ServerDetailsChanged = true;
if (previousPlayer != null && previousPlayer.Name != newClient.Name)
{
GameMain.Server.SendChatMessage($"ServerMessage.PreviousClientName~[client]={clName}~[previousname]={previousPlayer.Name}", ChatMessageType.Server, null);
previousPlayer.Name = newClient.Name;
}
var savedPermissions = serverSettings.ClientPermissions.Find(cp =>
cp.SteamID > 0 ?
cp.SteamID == newClient.SteamID :
newClient.IPMatches(cp.IP));
if (savedPermissions != null)
{
newClient.SetPermissions(savedPermissions.Permissions, savedPermissions.PermittedCommands);
}
else
{
var defaultPerms = PermissionPreset.List.Find(p => p.Name == "None");
if (defaultPerms != null)
{
newClient.SetPermissions(defaultPerms.Permissions, defaultPerms.PermittedCommands);
}
else
{
newClient.SetPermissions(ClientPermissions.None, new List<DebugConsole.Command>());
}
}
}
private void DisconnectUnauthClient(NetIncomingMessage inc, UnauthenticatedClient unauthClient, DisconnectReason reason, string message)
{
inc.SenderConnection.Disconnect(reason.ToString() + "/ " + TextManager.GetServerMessage(message));
if (unauthClient.SteamID > 0) { Steam.SteamManager.StopAuthSession(unauthClient.SteamID); }
if (unauthClient != null)
{
unauthenticatedClients.Remove(unauthClient);
}
}
}
}
@@ -66,7 +66,15 @@ namespace Barotrauma
foreach (Client bannedClient in bannedClients)
{
GameMain.Server.BanClient(bannedClient, $"KarmaBanned~[banthreshold]={(int)KickBanThreshold}", duration: TimeSpan.FromSeconds(GameMain.Server.ServerSettings.AutoBanTime));
if (bannedClient.KarmaKickCount < KicksBeforeBan)
{
GameMain.Server.KickClient(bannedClient, $"KarmaKicked~[banthreshold]={(int)KickBanThreshold}", resetKarma: true);
}
else
{
GameMain.Server.BanClient(bannedClient, $"KarmaBanned~[banthreshold]={(int)KickBanThreshold}", duration: TimeSpan.FromSeconds(GameMain.Server.ServerSettings.AutoBanTime));
}
bannedClient.KarmaKickCount++;
}
}
@@ -79,7 +87,7 @@ namespace Barotrauma
if (TestMode)
{
string msg =
karmaChange < 0 ? $"You karma has decreased to {client.Karma}" : $"You karma has increased to {client.Karma}";
karmaChange < 0 ? $"Your karma has decreased to {client.Karma}" : $"Your karma has increased to {client.Karma}";
if (!string.IsNullOrEmpty(debugKarmaChangeReason))
{
msg += $". Reason: {debugKarmaChangeReason}";
@@ -100,17 +108,17 @@ namespace Barotrauma
private void UpdateClient(Client client, float deltaTime)
{
if (client.Karma > KarmaDecayThreshold)
if (client.Character != null && !client.Character.Removed && !client.Character.IsDead)
{
client.Karma -= KarmaDecay * deltaTime;
}
else if (client.Karma < KarmaIncreaseThreshold)
{
client.Karma += KarmaIncrease * deltaTime;
}
if (client.Karma > KarmaDecayThreshold)
{
client.Karma -= KarmaDecay * deltaTime;
}
else if (client.Karma < KarmaIncreaseThreshold)
{
client.Karma += KarmaIncrease * deltaTime;
}
if (client.Character != null && !client.Character.Removed)
{
//increase the strength of the herpes affliction in steps instead of linearly
//otherwise clients could determine their exact karma value from the strength
float herpesStrength = 0.0f;
@@ -129,6 +137,10 @@ namespace Barotrauma
else if (existingAffliction != null)
{
existingAffliction.Strength = herpesStrength;
if (herpesStrength <= 0.0f)
{
client.Character.CharacterHealth.ReduceAffliction(null, "invertcontrols", 100.0f);
}
}
//check if the client has disconnected an excessive number of wires
@@ -182,19 +194,29 @@ namespace Barotrauma
if (target.IsDead || target.Removed) { return; }
bool isEnemy = target.AIController is EnemyAIController || target.TeamID != attacker.TeamID;
if (GameMain.Server.TraitorManager != null)
if (GameMain.Server.TraitorManager?.Traitors != null)
{
if (GameMain.Server.TraitorManager.TraitorList.Any(t => t.Character == target))
if (GameMain.Server.TraitorManager.Traitors.Any(t => t.Character == target))
{
//traitors always count as enemies
isEnemy = true;
}
if (GameMain.Server.TraitorManager.TraitorList.Any(t => t.Character == attacker && t.TargetCharacter == target))
if (GameMain.Server.TraitorManager.Traitors.Any(t =>
t.Character == attacker &&
t.CurrentObjective != null &&
t.CurrentObjective.IsEnemy(target)))
{
//target counts as an enemy to the traitor
isEnemy = true;
}
}
//attacking/healing clowns has a smaller effect on karma
if (target.HasEquippedItem("clownmask") &&
target.HasEquippedItem("clowncostume"))
{
damage *= 0.5f;
}
if (appliedAfflictions != null)
{
@@ -205,7 +227,7 @@ namespace Barotrauma
}
}
if (target.AIController is EnemyAIController || target.TeamID != attacker.TeamID)
if (isEnemy)
{
if (damage > 0)
{
@@ -242,6 +264,19 @@ namespace Barotrauma
if (damageAmount > 0)
{
if (StructureDamageKarmaDecrease <= 0.0f) { return; }
if (GameMain.Server.TraitorManager?.Traitors != null)
{
if (GameMain.Server.TraitorManager.Traitors.Any(t =>
t.Character == attacker &&
t.CurrentObjective != null &&
t.CurrentObjective.HasGoalsOfType<Traitor.GoalFloodPercentOfSub>()))
{
//traitor tasked to flood the sub -> damaging structures is ok
return;
}
}
Client client = GameMain.Server.ConnectedClients.Find(c => c.Character == attacker);
if (client != null)
{
@@ -327,6 +362,13 @@ namespace Barotrauma
Client client = GameMain.Server.ConnectedClients.Find(c => c.Character == target);
if (client == null) { return; }
//all penalties/rewards are halved when wearing a clown costume
if (target.HasEquippedItem("clownmask") &&
target.HasEquippedItem("clowncostume"))
{
amount *= 0.5f;
}
client.Karma += amount;
if (TestMode)
{
@@ -1,5 +1,4 @@
using Barotrauma.Extensions;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -32,7 +31,7 @@ namespace Barotrauma.Networking
#endif
}
public void Write(NetBuffer msg, Client recipient)
public void Write(IWriteMessage msg, Client recipient)
{
serializable.ServerWrite(msg, recipient, Data);
}
@@ -66,7 +65,7 @@ namespace Barotrauma.Networking
public readonly Client Sender;
public readonly UInt16 CharacterStateID;
public readonly NetBuffer Data;
public readonly ReadWriteMessage Data;
public readonly Character Character;
@@ -74,7 +73,7 @@ namespace Barotrauma.Networking
public bool IsProcessed;
public BufferedEvent(Client sender, Character senderCharacter, UInt16 characterStateID, IClientSerializable targetEntity, NetBuffer data)
public BufferedEvent(Client sender, Character senderCharacter, UInt16 characterStateID, IClientSerializable targetEntity, ReadWriteMessage data)
{
this.Sender = sender;
this.Character = senderCharacter;
@@ -300,7 +299,7 @@ namespace Barotrauma.Networking
/// <summary>
/// Writes all the events that the client hasn't received yet into the outgoing message
/// </summary>
public void Write(Client client, NetOutgoingMessage msg)
public void Write(Client client, IWriteMessage msg)
{
Write(client, msg, out _);
}
@@ -308,7 +307,7 @@ namespace Barotrauma.Networking
/// <summary>
/// Writes all the events that the client hasn't received yet into the outgoing message
/// </summary>
public void Write(Client client, NetOutgoingMessage msg, out List<NetEntityEvent> sentEvents)
public void Write(Client client, IWriteMessage msg, out List<NetEntityEvent> sentEvents)
{
List<NetEntityEvent> eventsToSync = null;
if (client.NeedsMidRoundSync)
@@ -371,7 +370,7 @@ namespace Barotrauma.Networking
foreach (NetEntityEvent entityEvent in sentEvents)
{
(entityEvent as ServerEntityEvent).Sent = true;
client.EntityEventLastSent[entityEvent.ID] = NetTime.Now;
client.EntityEventLastSent[entityEvent.ID] = Lidgren.Network.NetTime.Now;
}
}
@@ -399,9 +398,10 @@ namespace Barotrauma.Networking
//find the first event that hasn't been sent in roundtriptime or at all
client.EntityEventLastSent.TryGetValue(eventList[i].ID, out double lastSent);
float minInterval = Math.Max(client.Connection.AverageRoundtripTime, (float)server.UpdateInterval.TotalSeconds * 2);
float avgRoundtripTime = 0.01f; //TODO: reimplement client.Connection.AverageRoundtripTime
float minInterval = Math.Max(avgRoundtripTime, (float)server.UpdateInterval.TotalSeconds * 2);
if (lastSent > NetTime.Now - Math.Min(minInterval, 0.5f))
if (lastSent > Lidgren.Network.NetTime.Now - Math.Min(minInterval, 0.5f))
{
continue;
}
@@ -444,7 +444,7 @@ namespace Barotrauma.Networking
/// <summary>
/// Read the events from the message, ignoring ones we've already received
/// </summary>
public void Read(NetIncomingMessage msg, Client sender = null)
public void Read(IReadMessage msg, Client sender = null)
{
UInt16 firstEventID = msg.ReadUInt16();
int eventCount = msg.ReadByte();
@@ -472,7 +472,7 @@ namespace Barotrauma.Networking
{
DebugConsole.NewMessage("Received msg " + thisEventID, Color.Red);
}
msg.Position += msgLength * 8;
msg.BitPosition += msgLength * 8;
}
else if (entity == null)
{
@@ -486,7 +486,7 @@ namespace Barotrauma.Networking
Microsoft.Xna.Framework.Color.Orange);
}
sender.LastSentEntityEventID++;
msg.Position += msgLength * 8;
msg.BitPosition += msgLength * 8;
}
else
{
@@ -497,8 +497,10 @@ namespace Barotrauma.Networking
UInt16 characterStateID = msg.ReadUInt16();
NetBuffer buffer = new NetBuffer();
buffer.Write(msg.ReadBytes(msgLength - 2));
ReadWriteMessage buffer = new ReadWriteMessage();
byte[] temp = msg.ReadBytes(msgLength - 2);
buffer.Write(temp, 0, msgLength - 2);
buffer.BitPosition = 0;
BufferEvent(new BufferedEvent(sender, sender.Character, characterStateID, entity, buffer));
sender.LastSentEntityEventID++;
@@ -507,7 +509,7 @@ namespace Barotrauma.Networking
}
}
protected override void WriteEvent(NetBuffer buffer, NetEntityEvent entityEvent, Client recipient = null)
protected override void WriteEvent(IWriteMessage buffer, NetEntityEvent entityEvent, Client recipient = null)
{
var serverEvent = entityEvent as ServerEntityEvent;
if (serverEvent == null) return;
@@ -515,7 +517,7 @@ namespace Barotrauma.Networking
serverEvent.Write(buffer, recipient);
}
protected void ReadEvent(NetBuffer buffer, INetSerializable entity, Client sender = null)
protected void ReadEvent(IReadMessage buffer, INetSerializable entity, Client sender = null)
{
var clientEntity = entity as IClientSerializable;
if (clientEntity == null) return;
@@ -1,13 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
using Lidgren.Network;
namespace Barotrauma.Networking
{
partial class OrderChatMessage : ChatMessage
{
public override void ServerWrite(NetOutgoingMessage msg, Client c)
public override void ServerWrite(IWriteMessage msg, Client c)
{
msg.Write((byte)ServerNetObject.CHAT_MESSAGE);
msg.Write(NetStateID);
@@ -0,0 +1,674 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Linq;
using Lidgren.Network;
using Facepunch.Steamworks;
namespace Barotrauma.Networking
{
class LidgrenServerPeer : ServerPeer
{
private ServerSettings serverSettings;
private NetPeerConfiguration netPeerConfiguration;
private NetServer netServer;
private Facepunch.Steamworks.Server steamServer;
private class PendingClient
{
public string Name;
public int OwnerKey;
public NetConnection Connection;
public ConnectionInitialization InitializationStep;
public double UpdateTime;
public double TimeOut;
public int Retries;
public UInt64? SteamID;
public Int32? PasswordSalt;
public bool AuthSessionStarted;
public PendingClient(NetConnection conn)
{
OwnerKey = 0;
Connection = conn;
InitializationStep = ConnectionInitialization.SteamTicketAndVersion;
Retries = 0;
SteamID = null;
PasswordSalt = null;
UpdateTime = Timing.TotalTime;
TimeOut = 20.0;
AuthSessionStarted = false;
}
}
private List<LidgrenConnection> connectedClients;
private List<PendingClient> pendingClients;
private List<NetIncomingMessage> incomingLidgrenMessages;
public LidgrenServerPeer(int? ownKey, ServerSettings settings)
{
serverSettings = settings;
netServer = null;
connectedClients = new List<LidgrenConnection>();
pendingClients = new List<PendingClient>();
incomingLidgrenMessages = new List<NetIncomingMessage>();
steamServer = null;
ownerKey = ownKey;
}
public override void Start()
{
if (netServer != null) { return; }
netPeerConfiguration = new NetPeerConfiguration("barotrauma");
netPeerConfiguration.AcceptIncomingConnections = true;
netPeerConfiguration.AutoExpandMTU = false;
netPeerConfiguration.MaximumConnections = serverSettings.MaxPlayers * 2;
netPeerConfiguration.EnableUPnP = serverSettings.EnableUPnP;
netPeerConfiguration.Port = serverSettings.Port;
netPeerConfiguration.DisableMessageType(NetIncomingMessageType.DebugMessage |
NetIncomingMessageType.WarningMessage | NetIncomingMessageType.Receipt |
NetIncomingMessageType.ErrorMessage | NetIncomingMessageType.Error |
NetIncomingMessageType.UnconnectedData);
netPeerConfiguration.EnableMessageType(NetIncomingMessageType.ConnectionApproval);
netServer = new NetServer(netPeerConfiguration);
netServer.Start();
if (serverSettings.EnableUPnP)
{
InitUPnP();
while (DiscoveringUPnP()) { }
FinishUPnP();
}
}
public override void Close(string msg=null)
{
if (netServer == null) { return; }
for (int i=pendingClients.Count-1;i>=0;i--)
{
RemovePendingClient(pendingClients[i], msg ?? DisconnectReason.ServerShutdown.ToString());
}
for (int i=connectedClients.Count-1;i>=0;i--)
{
Disconnect(connectedClients[i], msg ?? DisconnectReason.ServerShutdown.ToString());
}
netServer.Shutdown(msg ?? DisconnectReason.ServerShutdown.ToString());
pendingClients.Clear();
connectedClients.Clear();
netServer = null;
if (steamServer != null)
{
steamServer.Auth.OnAuthChange = null;
}
steamServer = null;
OnShutdown?.Invoke();
}
public override void Update(float deltaTime)
{
if (netServer == null) { return; }
if (OnOwnerDetermined != null && OwnerConnection != null)
{
OnOwnerDetermined?.Invoke(OwnerConnection);
OnOwnerDetermined = null;
}
netServer.ReadMessages(incomingLidgrenMessages);
//process incoming connections first
foreach (NetIncomingMessage inc in incomingLidgrenMessages.Where(m => m.MessageType == NetIncomingMessageType.ConnectionApproval))
{
HandleConnection(inc);
}
try
{
//after processing connections, go ahead with the rest of the messages
foreach (NetIncomingMessage inc in incomingLidgrenMessages.Where(m => m.MessageType != NetIncomingMessageType.ConnectionApproval))
{
switch (inc.MessageType)
{
case NetIncomingMessageType.Data:
HandleDataMessage(inc);
break;
case NetIncomingMessageType.StatusChanged:
HandleStatusChanged(inc);
break;
}
}
}
catch (Exception e)
{
string errorMsg = "Server failed to read an incoming message. {" + e + "}\n" + e.StackTrace;
GameAnalyticsManager.AddErrorEventOnce("LidgrenServerPeer.Update:ClientReadException" + e.TargetSite.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
#if DEBUG
DebugConsole.ThrowError(errorMsg);
#else
if (GameSettings.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
#endif
}
for (int i = 0; i < pendingClients.Count; i++)
{
PendingClient pendingClient = pendingClients[i];
UpdatePendingClient(pendingClient, deltaTime);
if (i >= pendingClients.Count || pendingClients[i] != pendingClient) { i--; }
}
incomingLidgrenMessages.Clear();
}
private void InitUPnP()
{
if (netServer == null) { return; }
netServer.UPnP.ForwardPort(netPeerConfiguration.Port, "barotrauma");
if (Steam.SteamManager.USE_STEAM)
{
netServer.UPnP.ForwardPort(serverSettings.QueryPort, "barotrauma");
}
}
private bool DiscoveringUPnP()
{
if (netServer == null) { return false; }
return netServer.UPnP.Status == UPnPStatus.Discovering;
}
private void FinishUPnP()
{
//do nothing
}
private void HandleConnection(NetIncomingMessage inc)
{
if (netServer == null) { return; }
if (connectedClients.Count >= serverSettings.MaxPlayers)
{
inc.SenderConnection.Deny(DisconnectReason.ServerFull.ToString());
return;
}
if (serverSettings.BanList.IsBanned(inc.SenderConnection.RemoteEndPoint.Address, 0))
{
//IP banned: deny immediately
//TODO: use TextManager
inc.SenderConnection.Deny(DisconnectReason.Banned.ToString()+"/ IP banned");
return;
}
PendingClient pendingClient = pendingClients.Find(c => c.Connection == inc.SenderConnection);
if (pendingClient == null)
{
pendingClient = new PendingClient(inc.SenderConnection);
pendingClients.Add(pendingClient);
}
inc.SenderConnection.Approve();
}
private void HandleDataMessage(NetIncomingMessage inc)
{
if (netServer == null) { return; }
PendingClient pendingClient = pendingClients.Find(c => c.Connection == inc.SenderConnection);
byte incByte = inc.ReadByte();
bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0;
bool isConnectionInitializationStep = (incByte & (byte)PacketHeader.IsConnectionInitializationStep) != 0;
if (isConnectionInitializationStep && pendingClient != null)
{
ReadConnectionInitializationStep(pendingClient, inc);
}
else if (!isConnectionInitializationStep)
{
LidgrenConnection conn = connectedClients.Find(c => c.NetConnection == inc.SenderConnection);
if (conn == null)
{
if (pendingClient != null)
{
RemovePendingClient(pendingClient, DisconnectReason.AuthenticationRequired.ToString()+"/ Received data message from unauthenticated client");
}
else if (inc.SenderConnection.Status != NetConnectionStatus.Disconnected &&
inc.SenderConnection.Status != NetConnectionStatus.Disconnecting)
{
inc.SenderConnection.Disconnect(DisconnectReason.AuthenticationRequired.ToString() + "/ Received data message from unauthenticated client");
}
return;
}
if (pendingClient != null) { pendingClients.Remove(pendingClient); }
if (serverSettings.BanList.IsBanned(conn.IPEndPoint.Address, conn.SteamID))
{
Disconnect(conn, DisconnectReason.Banned.ToString()+"/ Received data message from banned client");
return;
}
UInt16 length = inc.ReadUInt16();
//DebugConsole.NewMessage(isCompressed + " " + isConnectionInitializationStep + " " + (int)incByte + " " + length);
IReadMessage msg = new ReadOnlyMessage(inc.Data, isCompressed, inc.PositionInBytes, length, conn);
OnMessageReceived?.Invoke(conn, msg);
}
}
private void HandleStatusChanged(NetIncomingMessage inc)
{
if (netServer == null) { return; }
switch (inc.SenderConnection.Status)
{
case NetConnectionStatus.Disconnected:
string disconnectMsg;
LidgrenConnection conn = connectedClients.Find(c => c.NetConnection == inc.SenderConnection);
if (conn != null)
{
if (conn == OwnerConnection)
{
DebugConsole.NewMessage("Owner disconnected: closing the server...");
GameServer.Log("Owner disconnected: closing the server...", ServerLog.MessageType.ServerMessage);
Close(DisconnectReason.ServerShutdown.ToString() + "/ Owner disconnected");
}
else
{
disconnectMsg = $"ServerMessage.HasDisconnected~[client]={conn.Name}";
Disconnect(conn, disconnectMsg);
}
}
else
{
PendingClient pendingClient = pendingClients.Find(c => c.Connection == inc.SenderConnection);
if (pendingClient != null)
{
disconnectMsg = $"ServerMessage.HasDisconnected~[client]={pendingClient.Name}";
RemovePendingClient(pendingClient, disconnectMsg);
}
}
break;
}
}
private void ReadConnectionInitializationStep(PendingClient pendingClient, NetIncomingMessage inc)
{
if (netServer == null) { return; }
pendingClient.TimeOut = 20.0;
ConnectionInitialization initializationStep = (ConnectionInitialization)inc.ReadByte();
//DebugConsole.NewMessage(initializationStep+" "+pendingClient.InitializationStep);
if (pendingClient.InitializationStep != initializationStep) return;
switch (initializationStep)
{
case ConnectionInitialization.SteamTicketAndVersion:
string name = Client.SanitizeName(inc.ReadString());
int ownKey = inc.ReadInt32();
UInt64 steamId = inc.ReadUInt64();
UInt16 ticketLength = inc.ReadUInt16();
byte[] ticket = inc.ReadBytes(ticketLength);
if (!Client.IsValidName(name, serverSettings))
{
if (OwnerConnection != null ||
!IPAddress.IsLoopback(pendingClient.Connection.RemoteEndPoint.Address.MapToIPv4()) &&
ownerKey == null || ownKey == 0 && ownKey != ownerKey)
{
RemovePendingClient(pendingClient, DisconnectReason.InvalidName.ToString() + "/ The name \"" + name + "\" is invalid");
return;
}
}
string version = inc.ReadString();
bool isCompatibleVersion = NetworkMember.IsCompatible(version, GameMain.Version.ToString()) ?? false;
if (!isCompatibleVersion)
{
RemovePendingClient(pendingClient,
$"DisconnectMessage.InvalidVersion~[version]={GameMain.Version.ToString()}~[clientversion]={version}");
GameServer.Log(name + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible game version)", ServerLog.MessageType.Error);
DebugConsole.NewMessage(name + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible game version)", Microsoft.Xna.Framework.Color.Red);
return;
}
Int32 contentPackageCount = inc.ReadVariableInt32();
List<ClientContentPackage> contentPackages = new List<ClientContentPackage>();
for (int i = 0; i < contentPackageCount; i++)
{
string packageName = inc.ReadString();
string packageHash = inc.ReadString();
contentPackages.Add(new ClientContentPackage(packageName, packageHash));
}
List<ContentPackage> missingPackages = new List<ContentPackage>();
foreach (ContentPackage contentPackage in GameMain.SelectedPackages)
{
if (!contentPackage.HasMultiplayerIncompatibleContent) continue;
bool packageFound = false;
for (int i = 0; i < contentPackageCount; i++)
{
if (contentPackages[i].Name == contentPackage.Name && contentPackages[i].Hash == contentPackage.MD5hash.Hash)
{
packageFound = true;
break;
}
}
if (!packageFound) missingPackages.Add(contentPackage);
}
if (missingPackages.Count == 1)
{
RemovePendingClient(pendingClient,
$"DisconnectMessage.MissingContentPackage~[missingcontentpackage]={GetPackageStr(missingPackages[0])}");
GameServer.Log(name + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (missing content package " + GetPackageStr(missingPackages[0]) + ")", ServerLog.MessageType.Error);
return;
}
else if (missingPackages.Count > 1)
{
List<string> packageStrs = new List<string>();
missingPackages.ForEach(cp => packageStrs.Add(GetPackageStr(cp)));
RemovePendingClient(pendingClient,
$"DisconnectMessage.MissingContentPackages~[missingcontentpackages]={string.Join(", ", packageStrs)}");
GameServer.Log(name + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (missing content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
return;
}
if (pendingClient.SteamID == null)
{
bool requireSteamAuth = GameMain.Config.RequireSteamAuthentication;
#if DEBUG
requireSteamAuth = false;
#endif
//steam auth cannot be done (SteamManager not initialized or no ticket given),
//but it's not required either -> let the client join without auth
if ((!Steam.SteamManager.IsInitialized || ticket.Length == 0) &&
!requireSteamAuth)
{
pendingClient.Name = name;
pendingClient.OwnerKey = ownKey;
pendingClient.InitializationStep = ConnectionInitialization.Success;
}
else
{
ServerAuth.StartAuthSessionResult authSessionStartState = Steam.SteamManager.StartAuthSession(ticket, steamId);
if (authSessionStartState != ServerAuth.StartAuthSessionResult.OK)
{
RemovePendingClient(pendingClient, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam auth session failed to start: " + authSessionStartState.ToString());
return;
}
pendingClient.SteamID = steamId;
pendingClient.Name = name;
pendingClient.OwnerKey = ownKey;
pendingClient.AuthSessionStarted = true;
}
}
else //TODO: could remove since this seems impossible
{
if (pendingClient.SteamID != steamId)
{
RemovePendingClient(pendingClient, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ SteamID mismatch");
return;
}
}
break;
case ConnectionInitialization.Password:
int pwLength = inc.ReadByte();
byte[] incPassword = new byte[pwLength];
inc.ReadBytes(incPassword, 0, pwLength);
if (pendingClient.PasswordSalt == null)
{
DebugConsole.ThrowError("Received password message from client without salt");
return;
}
if (serverSettings.IsPasswordCorrect(incPassword, pendingClient.PasswordSalt.Value))
{
pendingClient.InitializationStep = ConnectionInitialization.Success;
}
else
{
pendingClient.Retries++;
if (pendingClient.Retries >= 3)
{
string banMsg = "Failed to enter correct password too many times";
if (pendingClient.SteamID != null)
{
serverSettings.BanList.BanPlayer(pendingClient.Name, pendingClient.SteamID.Value, banMsg, null);
}
serverSettings.BanList.BanPlayer(pendingClient.Name, pendingClient.Connection.RemoteEndPoint.Address, banMsg, null);
RemovePendingClient(pendingClient, DisconnectReason.Banned.ToString()+" /"+banMsg);
return;
}
}
pendingClient.UpdateTime = Timing.TotalTime;
break;
}
}
protected struct ClientContentPackage
{
public string Name;
public string Hash;
public ClientContentPackage(string name, string hash)
{
Name = name; Hash = hash;
}
}
private string GetPackageStr(ContentPackage contentPackage)
{
return "\"" + contentPackage.Name + "\" (hash " + contentPackage.MD5hash.ShortHash + ")";
}
private void UpdatePendingClient(PendingClient pendingClient, float deltaTime)
{
if (netServer == null) { return; }
if (serverSettings.BanList.IsBanned(pendingClient.Connection.RemoteEndPoint.Address, pendingClient.SteamID ?? 0))
{
RemovePendingClient(pendingClient, DisconnectReason.Banned.ToString());
return;
}
//DebugConsole.NewMessage("pending client status: " + pendingClient.InitializationStep);
if (connectedClients.Count >= serverSettings.MaxPlayers)
{
RemovePendingClient(pendingClient, DisconnectReason.ServerFull.ToString());
}
if (pendingClient.InitializationStep == ConnectionInitialization.Success)
{
LidgrenConnection newConnection = new LidgrenConnection(pendingClient.Name, pendingClient.Connection, pendingClient.SteamID ?? 0);
newConnection.Status = NetworkConnectionStatus.Connected;
connectedClients.Add(newConnection);
pendingClients.Remove(pendingClient);
if (OwnerConnection == null &&
IPAddress.IsLoopback(pendingClient.Connection.RemoteEndPoint.Address.MapToIPv4()) &&
ownerKey != null && pendingClient.OwnerKey != 0 && pendingClient.OwnerKey == ownerKey)
{
ownerKey = null;
OwnerConnection = newConnection;
}
OnInitializationComplete?.Invoke(newConnection);
}
pendingClient.TimeOut -= deltaTime;
if (pendingClient.TimeOut < 0.0)
{
RemovePendingClient(pendingClient, Lidgren.Network.NetConnection.NoResponseMessage);
}
if (Timing.TotalTime < pendingClient.UpdateTime) { return; }
pendingClient.UpdateTime = Timing.TotalTime + 1.0;
NetOutgoingMessage outMsg = netServer.CreateMessage();
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)pendingClient.InitializationStep);
switch (pendingClient.InitializationStep)
{
case ConnectionInitialization.Password:
outMsg.Write(pendingClient.PasswordSalt == null); outMsg.WritePadBits();
if (pendingClient.PasswordSalt == null)
{
pendingClient.PasswordSalt = CryptoRandom.Instance.Next();
outMsg.Write(pendingClient.PasswordSalt.Value);
}
else
{
outMsg.Write(pendingClient.Retries);
}
break;
}
NetSendResult result = netServer.SendMessage(outMsg, pendingClient.Connection, NetDeliveryMethod.ReliableUnordered);
//DebugConsole.NewMessage("sent update to pending client: "+result);
}
private void RemovePendingClient(PendingClient pendingClient, string reason)
{
if (netServer == null) { return; }
if (pendingClients.Contains(pendingClient))
{
pendingClients.Remove(pendingClient);
if (pendingClient.AuthSessionStarted)
{
Steam.SteamManager.StopAuthSession(pendingClient.SteamID.Value);
pendingClient.SteamID = null;
pendingClient.AuthSessionStarted = false;
}
pendingClient.Connection.Disconnect(reason);
}
}
public override void InitializeSteamServerCallbacks(Server steamSrvr)
{
steamServer = steamSrvr;
steamServer.Auth.OnAuthChange = OnAuthChange;
}
private void OnAuthChange(ulong steamID, ulong ownerID, ServerAuth.Status status)
{
if (netServer == null) { return; }
PendingClient pendingClient = pendingClients.Find(c => c.SteamID == steamID);
DebugConsole.NewMessage(steamID + " validation: " + status+", "+(pendingClient!=null));
if (pendingClient == null)
{
if (status != ServerAuth.Status.OK)
{
LidgrenConnection connection = connectedClients.Find(c => c.SteamID == steamID);
if (connection != null)
{
Disconnect(connection, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam authentication status changed: " + status.ToString());
}
}
return;
}
if (serverSettings.BanList.IsBanned(pendingClient.Connection.RemoteEndPoint.Address, steamID))
{
RemovePendingClient(pendingClient, DisconnectReason.Banned.ToString() + "/ SteamID banned");
return;
}
if (status == ServerAuth.Status.OK)
{
pendingClient.InitializationStep = serverSettings.HasPassword ? ConnectionInitialization.Password : ConnectionInitialization.Success;
pendingClient.UpdateTime = Timing.TotalTime;
}
else
{
RemovePendingClient(pendingClient, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam authentication failed: " + status.ToString());
return;
}
}
public override void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod)
{
if (netServer == null) { return; }
if (!(conn is LidgrenConnection lidgrenConn)) return;
if (!connectedClients.Contains(lidgrenConn))
{
DebugConsole.ThrowError("Tried to send message to unauthenticated connection: " + lidgrenConn.IPString);
return;
}
NetDeliveryMethod lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable;
switch (deliveryMethod)
{
case DeliveryMethod.Unreliable:
lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable;
break;
case DeliveryMethod.Reliable:
lidgrenDeliveryMethod = NetDeliveryMethod.ReliableUnordered;
break;
case DeliveryMethod.ReliableOrdered:
lidgrenDeliveryMethod = NetDeliveryMethod.ReliableOrdered;
break;
}
NetOutgoingMessage lidgrenMsg = netServer.CreateMessage();
byte[] msgData = new byte[msg.LengthBytes];
msg.PrepareForSending(ref msgData, out bool isCompressed, out int length);
lidgrenMsg.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None));
lidgrenMsg.Write((UInt16)length);
lidgrenMsg.Write(msgData, 0, length);
netServer.SendMessage(lidgrenMsg, lidgrenConn.NetConnection, lidgrenDeliveryMethod);
}
public override void Disconnect(NetworkConnection conn,string msg=null)
{
if (netServer == null) { return; }
if (!(conn is LidgrenConnection lidgrenConn)) { return; }
if (connectedClients.Contains(lidgrenConn))
{
lidgrenConn.Status = NetworkConnectionStatus.Disconnected;
connectedClients.Remove(lidgrenConn);
OnDisconnect?.Invoke(conn, msg);
Steam.SteamManager.StopAuthSession(conn.SteamID);
}
lidgrenConn.NetConnection.Disconnect(msg ?? "Disconnected");
}
}
}
@@ -0,0 +1,34 @@
using Facepunch.Steamworks;
using System;
using System.Collections.Generic;
using System.Text;
namespace Barotrauma.Networking
{
abstract class ServerPeer
{
public delegate void MessageCallback(NetworkConnection connection, IReadMessage message);
public delegate void DisconnectCallback(NetworkConnection connection, string reason);
public delegate void InitializationCompleteCallback(NetworkConnection connection);
public delegate void ShutdownCallback();
public delegate void OwnerDeterminedCallback(NetworkConnection connection);
public MessageCallback OnMessageReceived;
public DisconnectCallback OnDisconnect;
public InitializationCompleteCallback OnInitializationComplete;
public ShutdownCallback OnShutdown;
public OwnerDeterminedCallback OnOwnerDetermined;
protected int? ownerKey;
public NetworkConnection OwnerConnection { get; protected set; }
public abstract void InitializeSteamServerCallbacks(Facepunch.Steamworks.Server steamSrvr);
public abstract void Start();
public abstract void Close(string msg = null);
public abstract void Update(float deltaTime);
public abstract void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod);
public abstract void Disconnect(NetworkConnection conn, string msg = null);
}
}
@@ -0,0 +1,652 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Linq;
using System.Threading;
using Lidgren.Network;
using Facepunch.Steamworks;
namespace Barotrauma.Networking
{
class SteamP2PServerPeer : ServerPeer
{
private ServerSettings serverSettings;
private NetPeerConfiguration netPeerConfiguration;
private NetServer netServer;
private NetConnection netConnection;
public UInt64 OwnerSteamID
{
get;
private set;
}
private class PendingClient
{
public string Name;
public ConnectionInitialization InitializationStep;
public double UpdateTime;
public double TimeOut;
public int Retries;
public UInt64 SteamID;
public Int32? PasswordSalt;
public bool AuthSessionStarted;
public PendingClient(UInt64 steamId)
{
InitializationStep = ConnectionInitialization.SteamTicketAndVersion;
Retries = 0;
SteamID = steamId;
PasswordSalt = null;
UpdateTime = Timing.TotalTime;
TimeOut = 20.0;
AuthSessionStarted = false;
}
public void Heartbeat()
{
TimeOut = 5.0;
}
}
private List<SteamP2PConnection> connectedClients;
private List<PendingClient> pendingClients;
private List<NetIncomingMessage> incomingLidgrenMessages;
public SteamP2PServerPeer(UInt64 steamId, ServerSettings settings)
{
serverSettings = settings;
netServer = null;
connectedClients = new List<SteamP2PConnection>();
pendingClients = new List<PendingClient>();
incomingLidgrenMessages = new List<NetIncomingMessage>();
ownerKey = null;
OwnerSteamID = steamId;
}
public override void Start()
{
if (netServer != null) { return; }
netPeerConfiguration = new NetPeerConfiguration("barotrauma")
{
AcceptIncomingConnections = true,
AutoExpandMTU = false,
MaximumConnections = 1, //only allow owner to connect
EnableUPnP = false,
Port = Steam.SteamManager.STEAMP2P_OWNER_PORT
};
netPeerConfiguration.DisableMessageType(NetIncomingMessageType.DebugMessage |
NetIncomingMessageType.WarningMessage | NetIncomingMessageType.Receipt |
NetIncomingMessageType.ErrorMessage | NetIncomingMessageType.Error |
NetIncomingMessageType.UnconnectedData);
netPeerConfiguration.EnableMessageType(NetIncomingMessageType.ConnectionApproval);
netServer = new NetServer(netPeerConfiguration);
netServer.Start();
}
public override void Close(string msg = null)
{
if (netServer == null) { return; }
if (OwnerConnection != null) OwnerConnection.Status = NetworkConnectionStatus.Disconnected;
for (int i = pendingClients.Count - 1; i >= 0; i--)
{
RemovePendingClient(pendingClients[i], msg ?? DisconnectReason.ServerShutdown.ToString());
}
for (int i = connectedClients.Count - 1; i >= 0; i--)
{
Disconnect(connectedClients[i], msg ?? DisconnectReason.ServerShutdown.ToString());
}
netServer.Shutdown(msg ?? DisconnectReason.ServerShutdown.ToString());
pendingClients.Clear();
connectedClients.Clear();
netServer = null;
OnShutdown?.Invoke();
}
public override void Update(float deltaTime)
{
if (netServer == null) { return; }
if (OnOwnerDetermined != null && OwnerConnection != null)
{
OnOwnerDetermined?.Invoke(OwnerConnection);
OnOwnerDetermined = null;
}
netServer.ReadMessages(incomingLidgrenMessages);
//backwards for loop so we can remove elements while iterating
for (int i = connectedClients.Count - 1; i >= 0; i--)
{
connectedClients[i].Decay(deltaTime);
if (connectedClients[i].Timeout < 0.0)
{
Disconnect(connectedClients[i], "Timed out");
}
}
//process incoming connections first
foreach (NetIncomingMessage inc in incomingLidgrenMessages.Where(m => m.MessageType == NetIncomingMessageType.ConnectionApproval))
{
HandleConnection(inc);
}
try
{
//after processing connections, go ahead with the rest of the messages
foreach (NetIncomingMessage inc in incomingLidgrenMessages.Where(m => m.MessageType != NetIncomingMessageType.ConnectionApproval))
{
switch (inc.MessageType)
{
case NetIncomingMessageType.Data:
HandleDataMessage(inc);
break;
case NetIncomingMessageType.StatusChanged:
HandleStatusChanged(inc);
break;
}
}
}
catch (Exception e)
{
string errorMsg = "Server failed to read an incoming message. {" + e + "}\n" + e.StackTrace;
GameAnalyticsManager.AddErrorEventOnce("SteamP2PServerPeer.Update:ClientReadException" + e.TargetSite.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
#if DEBUG
DebugConsole.ThrowError(errorMsg);
#else
if (GameSettings.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
#endif
}
for (int i = 0; i < pendingClients.Count; i++)
{
PendingClient pendingClient = pendingClients[i];
UpdatePendingClient(pendingClient);
if (i >= pendingClients.Count || pendingClients[i] != pendingClient) { i--; }
}
incomingLidgrenMessages.Clear();
}
private void HandleConnection(NetIncomingMessage inc)
{
if (netServer == null) { return; }
if (netConnection != null && inc.SenderConnection != netConnection)
{
inc.SenderConnection.Deny(DisconnectReason.SessionTaken.ToString()+"/ Owner is already connected");
return;
}
if (IPAddress.IsLoopback(inc.SenderConnection.RemoteEndPoint.Address.MapToIPv4()))
{
inc.SenderConnection.Approve();
netConnection = inc.SenderConnection;
return;
}
inc.SenderConnection.Deny(DisconnectReason.Kicked.ToString()+"/ Incoming connection is not loopback");
}
private void HandleDataMessage(NetIncomingMessage inc)
{
if (netServer == null) { return; }
if (inc.SenderConnection != netConnection) { return; }
UInt64 senderSteamId = inc.ReadUInt64();
byte incByte = inc.ReadByte();
bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0;
bool isConnectionInitializationStep = (incByte & (byte)PacketHeader.IsConnectionInitializationStep) != 0;
bool isDisconnectMessage = (incByte & (byte)PacketHeader.IsDisconnectMessage) != 0;
bool isServerMessage = (incByte & (byte)PacketHeader.IsServerMessage) != 0;
bool isHeartbeatMessage = (incByte & (byte)PacketHeader.IsHeartbeatMessage) != 0;
if (isServerMessage)
{
DebugConsole.ThrowError("got server message from" + senderSteamId.ToString());
return;
}
if (senderSteamId != OwnerSteamID) //sender is remote, handle disconnects and heartbeats
{
PendingClient pendingClient = pendingClients.Find(c => c.SteamID == senderSteamId);
SteamP2PConnection connectedClient = connectedClients.Find(c => c.SteamID == senderSteamId);
pendingClient?.Heartbeat();
connectedClient?.Heartbeat();
if (serverSettings.BanList.IsBanned(senderSteamId))
{
if (pendingClient != null)
{
RemovePendingClient(pendingClient, DisconnectReason.Banned.ToString()+"/ Banned");
}
else if (connectedClient != null)
{
Disconnect(connectedClient, DisconnectReason.Banned.ToString() + "/ Banned");
}
return;
}
else if (isDisconnectMessage)
{
if (pendingClient != null)
{
string disconnectMsg = $"ServerMessage.HasDisconnected~[client]={pendingClient.Name}";
RemovePendingClient(pendingClient, disconnectMsg);
}
else if (connectedClient != null)
{
string disconnectMsg = $"ServerMessage.HasDisconnected~[client]={connectedClient.Name}";
Disconnect(connectedClient, disconnectMsg, false);
}
return;
}
else if (isHeartbeatMessage)
{
//message exists solely as a heartbeat, ignore its contents
return;
}
else if (isConnectionInitializationStep)
{
if (pendingClient != null)
{
ReadConnectionInitializationStep(pendingClient, new ReadOnlyMessage(inc.Data, false, inc.PositionInBytes, inc.LengthBytes - inc.PositionInBytes, null));
}
else
{
ConnectionInitialization initializationStep = (ConnectionInitialization)inc.ReadByte();
if (initializationStep == ConnectionInitialization.ConnectionStarted)
{
pendingClients.Add(new PendingClient(senderSteamId));
}
}
}
else if (connectedClient != null)
{
UInt16 length = inc.ReadUInt16();
IReadMessage msg = new ReadOnlyMessage(inc.Data, isCompressed, inc.PositionInBytes, length, connectedClient);
OnMessageReceived?.Invoke(connectedClient, msg);
}
}
else //sender is owner
{
if (OwnerConnection != null) { (OwnerConnection as SteamP2PConnection).Heartbeat(); }
if (isDisconnectMessage)
{
DebugConsole.ThrowError("Received disconnect message from owner");
return;
}
if (isServerMessage)
{
DebugConsole.ThrowError("Received server message from owner");
return;
}
if (isConnectionInitializationStep)
{
if (OwnerConnection == null)
{
string ownerName = inc.ReadString();
OwnerConnection = new SteamP2PConnection(ownerName, OwnerSteamID);
OwnerConnection.Status = NetworkConnectionStatus.Connected;
OnInitializationComplete?.Invoke(OwnerConnection);
}
return;
}
if (isHeartbeatMessage)
{
return;
}
else
{
UInt16 length = inc.ReadUInt16();
IReadMessage msg = new ReadOnlyMessage(inc.Data, isCompressed, inc.PositionInBytes, length, OwnerConnection);
OnMessageReceived?.Invoke(OwnerConnection, msg);
}
}
}
private void HandleStatusChanged(NetIncomingMessage inc)
{
if (netServer == null) { return; }
DebugConsole.NewMessage(inc.SenderConnection.Status.ToString());
switch (inc.SenderConnection.Status)
{
case NetConnectionStatus.Connected:
NetOutgoingMessage outMsg = netServer.CreateMessage();
outMsg.Write(OwnerSteamID);
outMsg.Write((byte)(PacketHeader.IsConnectionInitializationStep | PacketHeader.IsServerMessage));
netServer.SendMessage(outMsg, netConnection, NetDeliveryMethod.ReliableUnordered);
break;
case NetConnectionStatus.Disconnected:
DebugConsole.NewMessage("Owner disconnected: closing the server...");
GameServer.Log("Owner disconnected: closing the server...", ServerLog.MessageType.ServerMessage);
Close(DisconnectReason.ServerShutdown.ToString() + "/ Owner disconnected");
break;
}
}
private void ReadConnectionInitializationStep(PendingClient pendingClient, IReadMessage inc)
{
if (netServer == null) { return; }
pendingClient.TimeOut = 20.0;
ConnectionInitialization initializationStep = (ConnectionInitialization)inc.ReadByte();
//DebugConsole.NewMessage(initializationStep+" "+pendingClient.InitializationStep);
if (pendingClient.InitializationStep != initializationStep) return;
switch (initializationStep)
{
case ConnectionInitialization.SteamTicketAndVersion:
string name = Client.SanitizeName(inc.ReadString());
UInt64 steamId = inc.ReadUInt64();
UInt16 ticketLength = inc.ReadUInt16();
inc.BitPosition += ticketLength * 8; //skip ticket, owner handles steam authentication
if (!Client.IsValidName(name, serverSettings))
{
RemovePendingClient(pendingClient, DisconnectReason.InvalidName.ToString() + "/ The name \"" + name + "\" is invalid");
return;
}
string version = inc.ReadString();
bool isCompatibleVersion = NetworkMember.IsCompatible(version, GameMain.Version.ToString()) ?? false;
if (!isCompatibleVersion)
{
RemovePendingClient(pendingClient,
$"DisconnectMessage.InvalidVersion~[version]={GameMain.Version.ToString()}~[clientversion]={version}");
GameServer.Log(name + " (" + pendingClient.SteamID.ToString() + ") couldn't join the server (incompatible game version)", ServerLog.MessageType.Error);
DebugConsole.NewMessage(name + " (" + pendingClient.SteamID.ToString() + ") couldn't join the server (incompatible game version)", Microsoft.Xna.Framework.Color.Red);
return;
}
int contentPackageCount = (int)inc.ReadVariableUInt32();
List<ClientContentPackage> contentPackages = new List<ClientContentPackage>();
for (int i = 0; i < contentPackageCount; i++)
{
string packageName = inc.ReadString();
string packageHash = inc.ReadString();
contentPackages.Add(new ClientContentPackage(packageName, packageHash));
}
List<ContentPackage> missingPackages = new List<ContentPackage>();
foreach (ContentPackage contentPackage in GameMain.SelectedPackages)
{
if (!contentPackage.HasMultiplayerIncompatibleContent) continue;
bool packageFound = false;
for (int i = 0; i < (int)contentPackageCount; i++)
{
if (contentPackages[i].Name == contentPackage.Name && contentPackages[i].Hash == contentPackage.MD5hash.Hash)
{
packageFound = true;
break;
}
}
if (!packageFound) missingPackages.Add(contentPackage);
}
if (missingPackages.Count == 1)
{
RemovePendingClient(pendingClient,
$"DisconnectMessage.MissingContentPackage~[missingcontentpackage]={GetPackageStr(missingPackages[0])}");
GameServer.Log(name + " (" + pendingClient.SteamID.ToString() + ") couldn't join the server (missing content package " + GetPackageStr(missingPackages[0]) + ")", ServerLog.MessageType.Error);
return;
}
else if (missingPackages.Count > 1)
{
List<string> packageStrs = new List<string>();
missingPackages.ForEach(cp => packageStrs.Add(GetPackageStr(cp)));
RemovePendingClient(pendingClient,
$"DisconnectMessage.MissingContentPackages~[missingcontentpackages]={string.Join(", ", packageStrs)}");
GameServer.Log(name + " (" + pendingClient.SteamID.ToString() + ") couldn't join the server (missing content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
return;
}
if (!pendingClient.AuthSessionStarted)
{
pendingClient.InitializationStep = serverSettings.HasPassword ? ConnectionInitialization.Password: ConnectionInitialization.Success;
pendingClient.Name = name;
pendingClient.AuthSessionStarted = true;
}
break;
case ConnectionInitialization.Password:
int pwLength = inc.ReadByte();
byte[] incPassword = inc.ReadBytes(pwLength);
if (pendingClient.PasswordSalt == null)
{
DebugConsole.ThrowError("Received password message from client without salt");
return;
}
if (serverSettings.IsPasswordCorrect(incPassword, pendingClient.PasswordSalt.Value))
{
pendingClient.InitializationStep = ConnectionInitialization.Success;
}
else
{
pendingClient.Retries++;
if (pendingClient.Retries >= 3)
{
string banMsg = "Failed to enter correct password too many times";
serverSettings.BanList.BanPlayer(pendingClient.Name, pendingClient.SteamID, banMsg, null);
RemovePendingClient(pendingClient, DisconnectReason.Banned.ToString()+"/ "+banMsg);
return;
}
}
pendingClient.UpdateTime = Timing.TotalTime;
break;
}
}
protected struct ClientContentPackage
{
public string Name;
public string Hash;
public ClientContentPackage(string name, string hash)
{
Name = name; Hash = hash;
}
}
private string GetPackageStr(ContentPackage contentPackage)
{
return "\"" + contentPackage.Name + "\" (hash " + contentPackage.MD5hash.ShortHash + ")";
}
private void UpdatePendingClient(PendingClient pendingClient)
{
if (netServer == null) { return; }
if (serverSettings.BanList.IsBanned(pendingClient.SteamID))
{
RemovePendingClient(pendingClient, DisconnectReason.Banned.ToString()+"/ Initialization interrupted by ban");
return;
}
//DebugConsole.NewMessage("pending client status: " + pendingClient.InitializationStep);
if (connectedClients.Count >= serverSettings.MaxPlayers-1)
{
RemovePendingClient(pendingClient, DisconnectReason.ServerFull.ToString());
}
if (pendingClient.InitializationStep == ConnectionInitialization.Success)
{
SteamP2PConnection newConnection = new SteamP2PConnection(pendingClient.Name, pendingClient.SteamID);
newConnection.Status = NetworkConnectionStatus.Connected;
connectedClients.Add(newConnection);
pendingClients.Remove(pendingClient);
OnInitializationComplete?.Invoke(newConnection);
}
pendingClient.TimeOut -= Timing.Step;
if (pendingClient.TimeOut < 0.0)
{
RemovePendingClient(pendingClient, Lidgren.Network.NetConnection.NoResponseMessage);
}
if (Timing.TotalTime < pendingClient.UpdateTime) { return; }
pendingClient.UpdateTime = Timing.TotalTime + 1.0;
NetOutgoingMessage outMsg = netServer.CreateMessage();
outMsg.Write(pendingClient.SteamID);
outMsg.Write((byte)(PacketHeader.IsConnectionInitializationStep |
PacketHeader.IsServerMessage));
outMsg.Write((byte)pendingClient.InitializationStep);
switch (pendingClient.InitializationStep)
{
case ConnectionInitialization.Password:
outMsg.Write(pendingClient.PasswordSalt == null); outMsg.WritePadBits();
if (pendingClient.PasswordSalt == null)
{
pendingClient.PasswordSalt = CryptoRandom.Instance.Next();
outMsg.Write(pendingClient.PasswordSalt.Value);
}
else
{
outMsg.Write(pendingClient.Retries);
}
break;
}
if (netConnection != null)
{
NetSendResult result = netServer.SendMessage(outMsg, netConnection, NetDeliveryMethod.ReliableUnordered);
}
}
private void RemovePendingClient(PendingClient pendingClient, string reason)
{
if (netServer == null) { return; }
if (pendingClients.Contains(pendingClient))
{
SendDisconnectMessage(pendingClient.SteamID, reason);
pendingClients.Remove(pendingClient);
if (pendingClient.AuthSessionStarted)
{
Steam.SteamManager.StopAuthSession(pendingClient.SteamID);
pendingClient.SteamID = 0;
pendingClient.AuthSessionStarted = false;
}
}
}
public override void InitializeSteamServerCallbacks(Server steamSrvr)
{
throw new InvalidOperationException("Called InitializeSteamServerCallbacks on SteamP2PServerPeer!");
}
public override void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod)
{
if (netServer == null) { return; }
if (!(conn is SteamP2PConnection steamp2pConn)) return;
if (!connectedClients.Contains(steamp2pConn) && conn != OwnerConnection)
{
DebugConsole.ThrowError("Tried to send message to unauthenticated connection: " + steamp2pConn.SteamID.ToString());
return;
}
NetDeliveryMethod lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable;
switch (deliveryMethod)
{
case DeliveryMethod.Unreliable:
lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable;
break;
case DeliveryMethod.Reliable:
lidgrenDeliveryMethod = NetDeliveryMethod.ReliableUnordered;
break;
case DeliveryMethod.ReliableOrdered:
lidgrenDeliveryMethod = NetDeliveryMethod.ReliableOrdered;
break;
}
NetOutgoingMessage lidgrenMsg = netServer.CreateMessage();
byte[] msgData = new byte[1500];
msg.PrepareForSending(ref msgData, out bool isCompressed, out int length);
lidgrenMsg.Write(conn.SteamID);
lidgrenMsg.Write((byte)((isCompressed ? PacketHeader.IsCompressed : PacketHeader.None) | PacketHeader.IsServerMessage));
lidgrenMsg.Write((UInt16)length);
lidgrenMsg.Write(msgData, 0, length);
netServer.SendMessage(lidgrenMsg, netConnection, lidgrenDeliveryMethod);
}
private void SendDisconnectMessage(UInt64 steamId, string msg)
{
if (netServer == null) { return; }
if (string.IsNullOrWhiteSpace(msg)) { return; }
NetOutgoingMessage lidgrenMsg = netServer.CreateMessage();
lidgrenMsg.Write(steamId);
lidgrenMsg.Write((byte)(PacketHeader.IsDisconnectMessage | PacketHeader.IsServerMessage));
lidgrenMsg.Write(msg);
netServer.SendMessage(lidgrenMsg, netConnection, NetDeliveryMethod.ReliableUnordered);
}
private void Disconnect(NetworkConnection conn, string msg, bool sendDisconnectMessage)
{
if (netServer == null) { return; }
if (!(conn is SteamP2PConnection steamp2pConn)) { return; }
if (connectedClients.Contains(steamp2pConn))
{
if (sendDisconnectMessage) SendDisconnectMessage(steamp2pConn.SteamID, msg);
steamp2pConn.Status = NetworkConnectionStatus.Disconnected;
connectedClients.Remove(steamp2pConn);
OnDisconnect?.Invoke(conn, msg);
Steam.SteamManager.StopAuthSession(conn.SteamID);
}
else if (steamp2pConn == OwnerConnection)
{
netConnection.Disconnect(msg);
}
}
public override void Disconnect(NetworkConnection conn, string msg = null)
{
Disconnect(conn, msg, true);
}
}
}
@@ -27,8 +27,8 @@ namespace Barotrauma.Networking
.ToList();
}
int currPlayerCount = GameMain.Server.ConnectedClients.Count(c =>
c.InGame &&
int currPlayerCount = GameMain.Server.ConnectedClients.Count(c =>
c.InGame &&
(!c.SpectateOnly || (!GameMain.Server.ServerSettings.AllowSpectating && GameMain.Server.OwnerConnection != c.Connection)));
var existingBots = Character.CharacterList
@@ -68,7 +68,7 @@ namespace Barotrauma.Networking
{
RespawnCountdownStarted = respawnPending;
RespawnTime = DateTime.Now + new TimeSpan(0,0,0,0, (int)(GameMain.Server.ServerSettings.RespawnInterval * 1000.0f));
GameMain.Server.CreateEntityEvent(this);
GameMain.Server.CreateEntityEvent(this);
}
if (!RespawnCountdownStarted) { return; }
@@ -180,7 +180,7 @@ namespace Barotrauma.Networking
partial void UpdateTransportingProjSpecific(float deltaTime)
{
if (!ReturnCountdownStarted)
{
//if there are no living chracters inside, transporting can be stopped immediately
@@ -231,7 +231,7 @@ namespace Barotrauma.Networking
var botsToSpawn = GetBotsToRespawn();
characterInfos.AddRange(botsToSpawn);
GameMain.Server.AssignJobs(clients);
foreach (Client c in clients)
{
@@ -257,7 +257,7 @@ namespace Barotrauma.Networking
var character = Character.Create(characterInfos[i], shuttleSpawnPoints[i].WorldPosition, characterInfos[i].Name, !bot, bot);
character.TeamID = Character.TeamType.Team1;
if (bot)
{
GameServer.Log(string.Format("Respawning bot {0} as {1}", character.Info.Name, characterInfos[i].Job.Name), ServerLog.MessageType.Spawning);
@@ -265,18 +265,18 @@ namespace Barotrauma.Networking
else
{
//tell the respawning client they're no longer a traitor
if (GameMain.Server.TraitorManager != null && clients[i].Character != null)
if (GameMain.Server.TraitorManager?.Traitors != null && clients[i].Character != null)
{
if (GameMain.Server.TraitorManager.TraitorList.Any(t => t.Character == clients[i].Character))
if (GameMain.Server.TraitorManager.Traitors.Any(t => t.Character == clients[i].Character))
{
GameMain.Server.SendDirectChatMessage(TextManager.Get("traitorrespawnmessage"), clients[i], ChatMessageType.MessageBox);
GameMain.Server.SendDirectChatMessage(TextManager.FormatServerMessage("TraitorRespawnMessage"), clients[i], ChatMessageType.ServerMessageBox);
}
}
clients[i].Character = character;
character.OwnerClientIP = clients[i].Connection.RemoteEndPoint.Address.ToString();
character.OwnerClientEndPoint = clients[i].Connection.EndPointString;
character.OwnerClientName = clients[i].Name;
GameServer.Log(string.Format("Respawning {0} ({1}) as {2}", clients[i].Name, clients[i].Connection?.RemoteEndPoint?.Address, characterInfos[i].Job.Name), ServerLog.MessageType.Spawning);
GameServer.Log(string.Format("Respawning {0} ({1}) as {2}", clients[i].Name, clients[i].Connection?.EndPointString, characterInfos[i].Job.Name), ServerLog.MessageType.Spawning);
}
if (divingSuitPrefab != null && oxyPrefab != null && RespawnShuttle != null)
@@ -325,9 +325,9 @@ namespace Barotrauma.Networking
}
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
msg.WriteRangedInteger(0, Enum.GetNames(typeof(State)).Length, (int)CurrentState);
msg.WriteRangedIntegerDeprecated(0, Enum.GetNames(typeof(State)).Length, (int)CurrentState);
switch (CurrentState)
{
@@ -1,5 +1,4 @@
using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -20,7 +19,7 @@ namespace Barotrauma.Networking
LoadClientPermissions();
}
private void WriteNetProperties(NetBuffer outMsg)
private void WriteNetProperties(IWriteMessage outMsg)
{
outMsg.Write((UInt16)netProperties.Keys.Count);
foreach (UInt32 key in netProperties.Keys)
@@ -30,7 +29,7 @@ namespace Barotrauma.Networking
}
}
public void ServerAdminWrite(NetBuffer outMsg, Client c)
public void ServerAdminWrite(IWriteMessage outMsg, Client c)
{
//outMsg.Write(isPublic);
//outMsg.Write(EnableUPnP);
@@ -43,11 +42,15 @@ namespace Barotrauma.Networking
Whitelist.ServerAdminWrite(outMsg, c);
}
public void ServerWrite(NetBuffer outMsg,Client c)
public void ServerWrite(IWriteMessage outMsg,Client c)
{
outMsg.Write(ServerName);
outMsg.Write(ServerMessageText);
outMsg.WriteRangedInteger(1, 60, TickRate);
outMsg.Write((byte)MaxPlayers);
outMsg.Write(HasPassword);
outMsg.Write(isPublic);
outMsg.WritePadBits();
outMsg.WriteRangedIntegerDeprecated(1, 60, TickRate);
WriteExtraCargo(outMsg);
@@ -67,7 +70,7 @@ namespace Barotrauma.Networking
}
}
public void ServerRead(NetIncomingMessage incMsg,Client c)
public void ServerRead(IReadMessage incMsg,Client c)
{
if (!c.HasPermission(Networking.ClientPermissions.ManageSettings)) return;
@@ -112,7 +115,7 @@ namespace Barotrauma.Networking
else
{
UInt32 size = incMsg.ReadVariableUInt32();
incMsg.Position += 8 * size;
incMsg.BitPosition += (int)(8 * size);
}
}
@@ -145,7 +148,7 @@ namespace Barotrauma.Networking
if (botSpawnMode > 1) botSpawnMode = 0;
BotSpawnMode = (BotSpawnMode)botSpawnMode;
float levelDifficulty = incMsg.ReadFloat();
float levelDifficulty = incMsg.ReadSingle();
if (levelDifficulty >= 0.0f) SelectedLevelDifficulty = levelDifficulty;
UseRespawnShuttle = incMsg.ReadBoolean();
@@ -177,24 +180,16 @@ namespace Barotrauma.Networking
doc.Root.SetAttributeValue("name", ServerName);
doc.Root.SetAttributeValue("public", isPublic);
doc.Root.SetAttributeValue("port", GameMain.Server.NetPeerConfiguration.Port);
doc.Root.SetAttributeValue("port", Port);
if (Steam.SteamManager.USE_STEAM) doc.Root.SetAttributeValue("queryport", QueryPort);
doc.Root.SetAttributeValue("maxplayers", maxPlayers);
doc.Root.SetAttributeValue("enableupnp", GameMain.Server.NetPeerConfiguration.EnableUPnP);
doc.Root.SetAttributeValue("enableupnp", EnableUPnP);
doc.Root.SetAttributeValue("autorestart", autoRestart);
doc.Root.SetAttributeValue("SubSelection", SubSelectionMode.ToString());
doc.Root.SetAttributeValue("ModeSelection", ModeSelectionMode.ToString());
doc.Root.SetAttributeValue("LevelDifficulty", ((int)selectedLevelDifficulty).ToString());
doc.Root.SetAttributeValue("TraitorsEnabled", TraitorsEnabled.ToString());
/*doc.Root.SetAttributeValue("BotCount", BotCount);
doc.Root.SetAttributeValue("MaxBotCount", MaxBotCount);*/
doc.Root.SetAttributeValue("BotSpawnMode", BotSpawnMode.ToString());
doc.Root.SetAttributeValue("AllowedRandomMissionTypes", string.Join(",", AllowedRandomMissionTypes));
doc.Root.SetAttributeValue("AllowedClientNameChars", string.Join(",", AllowedClientNameChars.Select(c => c.First + "-" + c.Second)));
doc.Root.SetAttributeValue("ServerMessage", ServerMessageText);
@@ -239,18 +234,22 @@ namespace Barotrauma.Networking
selectedLevelDifficulty = doc.Root.GetAttributeFloat("LevelDifficulty", 20.0f);
GameMain.NetLobbyScreen.SetLevelDifficulty(selectedLevelDifficulty);
var traitorsEnabled = TraitorsEnabled;
Enum.TryParse(doc.Root.GetAttributeString("TraitorsEnabled", "No"), out traitorsEnabled);
TraitorsEnabled = traitorsEnabled;
GameMain.NetLobbyScreen.SetTraitorsEnabled(traitorsEnabled);
var botSpawnMode = BotSpawnMode.Normal;
Enum.TryParse(doc.Root.GetAttributeString("BotSpawnMode", "Normal"), out botSpawnMode);
BotSpawnMode = botSpawnMode;
//"65-90", "97-122", "48-59" = upper and lower case english alphabet and numbers
string[] allowedClientNameCharsStr = doc.Root.GetAttributeStringArray("AllowedClientNameChars", new string[] { "65-90", "97-122", "48-59" });
string[] allowedClientNameCharsStr = doc.Root.GetAttributeStringArray("AllowedClientNameChars",
new string[] {
"32-33",
"38-46",
"48-57",
"65-90",
"91",
"93",
"95-122",
"192-255",
"384-591",
"1024-1279"
});
foreach (string allowedClientNameCharRange in allowedClientNameCharsStr)
{
string[] splitRange = allowedClientNameCharRange.Split('-');
@@ -329,7 +328,7 @@ namespace Barotrauma.Networking
foreach (XElement clientElement in doc.Root.Elements())
{
string clientName = clientElement.GetAttributeString("name", "");
string clientIP = clientElement.GetAttributeString("ip", "");
string clientEndPoint = clientElement.GetAttributeString("endpoint", null) ?? clientElement.GetAttributeString("ip", "");
string steamIdStr = clientElement.GetAttributeString("steamid", "");
if (string.IsNullOrWhiteSpace(clientName))
@@ -337,7 +336,7 @@ namespace Barotrauma.Networking
DebugConsole.ThrowError("Error in " + ClientPermissionsFile + " - all clients must have a name and an IP address.");
continue;
}
if (string.IsNullOrWhiteSpace(clientIP) && string.IsNullOrWhiteSpace(steamIdStr))
if (string.IsNullOrWhiteSpace(clientEndPoint) && string.IsNullOrWhiteSpace(steamIdStr))
{
DebugConsole.ThrowError("Error in " + ClientPermissionsFile + " - all clients must have an IP address or a Steam ID.");
continue;
@@ -410,7 +409,7 @@ namespace Barotrauma.Networking
}
else
{
ClientPermissions.Add(new SavedClientPermission(clientName, clientIP, permissions, permittedCommands));
ClientPermissions.Add(new SavedClientPermission(clientName, clientEndPoint, permissions, permittedCommands));
}
}
}
@@ -480,7 +479,7 @@ namespace Barotrauma.Networking
}
else
{
clientElement.Add(new XAttribute("ip", clientPermission.IP));
clientElement.Add(new XAttribute("endpoint", clientPermission.EndPoint));
}
if (matchingPreset == null)
@@ -29,7 +29,8 @@ namespace Barotrauma.Steam
RefreshServerDetails(server);
instance.server.Auth.OnAuthChange = server.OnAuthChange;
server.ServerPeer.InitializeSteamServerCallbacks(instance.server);
Instance.server.LogOnAnonymous();
return true;
@@ -66,23 +67,24 @@ namespace Barotrauma.Steam
Instance.server.SetKey("traitors", server.ServerSettings.TraitorsEnabled.ToString());
Instance.server.SetKey("gamestarted", server.GameStarted.ToString());
Instance.server.SetKey("gamemode", server.ServerSettings.GameModeIdentifier);
instance.server.DedicatedServer = true;
return true;
}
public static bool StartAuthSession(byte[] authTicketData, ulong clientSteamID)
public static ServerAuth.StartAuthSessionResult StartAuthSession(byte[] authTicketData, ulong clientSteamID)
{
if (instance == null || !instance.isInitialized || instance.server == null) return false;
if (instance == null || !instance.isInitialized || instance.server == null) return ServerAuth.StartAuthSessionResult.ServerNotConnectedToSteam;
DebugConsole.Log("SteamManager authenticating Steam client " + clientSteamID);
if (!instance.server.Auth.StartSession(authTicketData, clientSteamID))
ServerAuth.StartAuthSessionResult startResult = instance.server.Auth.StartSession(authTicketData, clientSteamID);
if (startResult != ServerAuth.StartAuthSessionResult.OK)
{
DebugConsole.Log("Authentication failed");
return false;
DebugConsole.Log("Authentication failed: failed to start auth session (" + startResult.ToString() + ")");
}
return true;
return startResult;
}
public static void StopAuthSession(ulong clientSteamID)
@@ -1,5 +1,4 @@
using Barotrauma.Items.Components;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -9,11 +8,11 @@ namespace Barotrauma.Networking
{
class VoipServer
{
private NetServer netServer;
private ServerPeer netServer;
private List<VoipQueue> queues;
private Dictionary<VoipQueue,DateTime> lastSendTime;
public VoipServer(NetServer server)
public VoipServer(ServerPeer server)
{
this.netServer = server;
queues = new List<VoipQueue>();
@@ -54,15 +53,13 @@ namespace Barotrauma.Networking
if (!CanReceive(sender, recipient)) { continue; }
NetOutgoingMessage msg = netServer.CreateMessage();
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ServerPacketHeader.VOICE);
msg.Write((byte)queue.QueueID);
queue.Write(msg);
GameMain.Server.CompressOutgoingMessage(msg);
netServer.SendMessage(msg, recipient.Connection, NetDeliveryMethod.Unreliable);
netServer.Send(msg, recipient.Connection, DeliveryMethod.Unreliable);
}
}
}
@@ -1,5 +1,4 @@
using Barotrauma.Networking;
using Lidgren.Network;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -19,7 +18,7 @@ namespace Barotrauma
set { allowModeVoting = value; }
}
public void ServerRead(NetIncomingMessage inc, Client sender)
public void ServerRead(IReadMessage inc, Client sender)
{
if (GameMain.Server == null || sender == null) return;
@@ -86,7 +85,7 @@ namespace Barotrauma
GameMain.Server.UpdateVoteStatus();
}
public void ServerWrite(NetBuffer msg)
public void ServerWrite(IWriteMessage msg)
{
if (GameMain.Server == null) return;
@@ -1,5 +1,4 @@
using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
@@ -127,7 +126,7 @@ namespace Barotrauma.Networking
whitelistedPlayers.Add(new WhiteListedPlayer(name, ip));
}
public void ServerAdminWrite(NetBuffer outMsg, Client c)
public void ServerAdminWrite(IWriteMessage outMsg, Client c)
{
if (!c.HasPermission(ClientPermissions.ManageSettings))
{
@@ -139,7 +138,7 @@ namespace Barotrauma.Networking
outMsg.Write(Enabled);
outMsg.WritePadBits();
outMsg.WriteVariableInt32(whitelistedPlayers.Count);
outMsg.WriteVariableUInt32((UInt32)whitelistedPlayers.Count);
for (int i = 0; i < whitelistedPlayers.Count; i++)
{
WhiteListedPlayer whitelistedPlayer = whitelistedPlayers[i];
@@ -154,13 +153,13 @@ namespace Barotrauma.Networking
}
}
public bool ServerAdminRead(NetBuffer incMsg, Client c)
public bool ServerAdminRead(IReadMessage incMsg, Client c)
{
if (!c.HasPermission(ClientPermissions.ManageSettings))
{
bool enabled = incMsg.ReadBoolean(); incMsg.ReadPadBits();
UInt16 removeCount = incMsg.ReadUInt16();
incMsg.Position += removeCount * 4 * 8;
incMsg.BitPosition += removeCount * 4 * 8;
UInt16 addCount = incMsg.ReadUInt16();
for (int i = 0; i < addCount; i++)
{
@@ -1,5 +1,4 @@
using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
@@ -7,7 +6,7 @@ namespace Barotrauma
{
partial class PhysicsBody
{
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
float MaxVel = NetConfig.MaxPhysicsBodyVelocity;
float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
+19 -6
View File
@@ -24,17 +24,29 @@ namespace Barotrauma
static void Main(string[] args)
{
GameMain game = null;
Thread inputThread = null;
#if !DEBUG
try
{
#endif
game = new GameMain(args);
inputThread = new Thread(new ThreadStart(DebugConsole.UpdateCommandLine));
inputThread.Start();
DebugConsole.InputThread = null;
#if !DEBUG
if (!args.Contains("-ownerkey") && !args.Contains("-steamid"))
{
#endif
DebugConsole.InputThread = new Thread(new ThreadStart(DebugConsole.UpdateCommandLine));
DebugConsole.InputThread.IsBackground = true;
DebugConsole.InputThread.Start();
#if !DEBUG
}
else
{
Console.WriteLine("Server launched through client, command line IO disabled");
}
#endif
game.Run();
inputThread.Abort(); inputThread.Join();
DebugConsole.InputThread?.Abort(); DebugConsole.InputThread?.Join();
if (GameSettings.SendUserStatistics) GameAnalytics.OnQuit();
SteamManager.ShutDown();
#if !DEBUG
@@ -42,7 +54,8 @@ namespace Barotrauma
catch (Exception e)
{
CrashDump(game, "servercrashreport.log", e);
inputThread.Abort(); inputThread.Join();
GameMain.Server?.NotifyCrash();
DebugConsole.InputThread?.Abort(); DebugConsole.InputThread?.Join();
}
#endif
}
@@ -115,7 +128,7 @@ namespace Barotrauma
if (GameSettings.SendUserStatistics)
{
GameAnalytics.AddErrorEvent(EGAErrorSeverity.Error, crashReport);
GameAnalytics.AddErrorEvent(EGAErrorSeverity.Critical, crashReport);
GameAnalytics.OnQuit();
Console.Write("A crash report (\"crashreport.log\") was saved in the root folder of the game and sent to the developers.");
}
@@ -0,0 +1,64 @@
using Barotrauma.Networking;
using System.Collections.Generic;
using System.Linq;
using Microsoft.SqlServer.Server;
namespace Barotrauma
{
partial class Traitor
{
public abstract class Goal
{
public Traitor Traitor { get; private set; }
public TraitorMission Mission { get; internal set; }
public virtual string StatusTextId { get; set; } = "TraitorGoalStatusTextFormat";
public virtual string InfoTextId { get; set; } = null;
public virtual string CompletedTextId { get; set; } = null;
public virtual string StatusValueTextId => IsCompleted ? "complete" : "inprogress";
public virtual IEnumerable<string> StatusTextKeys => new [] { "[infotext]", "[status]" };
public virtual IEnumerable<string> StatusTextValues => new [] { InfoText, TextManager.FormatServerMessage(StatusValueTextId) };
public virtual IEnumerable<string> InfoTextKeys => new string[] { };
public virtual IEnumerable<string> InfoTextValues => new string[] { };
public virtual IEnumerable<string> CompletedTextKeys => new string[] { };
public virtual IEnumerable<string> CompletedTextValues => new string[] { };
protected virtual string FormatText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values) => TextManager.FormatServerMessageWithGenderPronouns(traitor?.Character?.Info?.Gender ?? Gender.None, textId, keys, values);
protected internal virtual string GetStatusText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values) => FormatText(traitor, textId, keys, values);
protected internal virtual string GetInfoText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values) => FormatText(traitor, textId, keys, values);
protected internal virtual string GetCompletedText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values) => FormatText(traitor, textId, keys, values);
public virtual string StatusText => GetStatusText(Traitor, StatusTextId, StatusTextKeys, StatusTextValues);
public virtual string InfoText => GetInfoText(Traitor, InfoTextId, InfoTextKeys, InfoTextValues);
public virtual string CompletedText => CompletedTextId != null ? GetCompletedText(Traitor, CompletedTextId, CompletedTextKeys, CompletedTextValues) : StatusText;
public abstract bool IsCompleted { get; }
public virtual bool IsStarted => Traitor != null;
public virtual bool CanBeCompleted => !(Traitor?.Character?.IsDead ?? true);
public virtual bool IsEnemy(Character character) => false;
public virtual bool Start(Traitor traitor)
{
Traitor = traitor;
return true;
}
public virtual void Update(float deltaTime)
{
}
protected Goal()
{
}
}
}
}
@@ -0,0 +1,98 @@
using Barotrauma.Networking;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public sealed class GoalDestroyItemsWithTag : Goal
{
private readonly string tag;
private readonly bool matchIdentifier;
private readonly bool matchTag;
private readonly bool matchInventory;
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[percentage]", "[tag]" });
public override IEnumerable<string> InfoTextValues => base.InfoTextValues.Concat(new string[] { string.Format("{0:0}", DestroyPercent * 100.0f), tagPrefabName ?? "" });
private readonly float destroyPercent;
private float DestroyPercent => destroyPercent;
private bool isCompleted = false;
public override bool IsCompleted => isCompleted;
private int totalCount = 0;
private int targetCount = 0;
private string tagPrefabName = null;
private int CountMatchingItems()
{
int result = 0;
foreach (var item in Item.ItemList)
{
if (!matchInventory && item.FindParentInventory(inventory => inventory.Owner is Character && inventory.Owner != Traitor.Character) != null)
{
continue;
}
if (item.Submarine == null)
{
if (!(item.ParentInventory?.Owner is Character)) { continue; }
}
else
{
if (item.Submarine.TeamID != Traitor.Character.TeamID) { continue; }
}
if (item.Condition <= 0.0f)
{
continue;
}
var identifierMatches = matchIdentifier && item.prefab.Identifier == tag;
if (identifierMatches && tagPrefabName == null)
{
var textId = item.Prefab.GetItemNameTextId();
tagPrefabName = textId != null ? TextManager.FormatServerMessage(textId) : item.Prefab.Name;
}
if (identifierMatches || (matchTag && item.HasTag(tag)))
{
++result;
}
}
return result;
}
public override void Update(float deltaTime)
{
base.Update(deltaTime);
isCompleted = CountMatchingItems() <= targetCount;
}
public override bool Start(Traitor traitor)
{
if (!base.Start(traitor))
{
return false;
}
totalCount = CountMatchingItems();
if (totalCount <= 0)
{
return false;
}
targetCount = (int)((1.0f - destroyPercent) * totalCount - 0.5f);
return true;
}
public GoalDestroyItemsWithTag(string tag, float destroyPercent, bool matchTag, bool matchIdentifier, bool matchInventory) : base()
{
InfoTextId = "TraitorGoalDestroyItems";
this.tag = tag;
this.destroyPercent = destroyPercent;
this.matchTag = matchTag;
this.matchIdentifier = matchIdentifier;
this.matchInventory = matchInventory;
}
}
}
}
@@ -0,0 +1,164 @@
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public class GoalFindItem : HumanoidGoal
{
private readonly string identifier;
private readonly bool preferNew;
private readonly bool allowNew;
private readonly bool allowExisting;
private readonly HashSet<string> allowedContainerIdentifiers = new HashSet<string>();
private ItemPrefab targetPrefab;
private Item targetContainer;
private Item target;
private HashSet<Item> existingItems = new HashSet<Item>();
private string targetNameText;
private string targetContainerNameText;
private string targetHullNameText;
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[identifier]", "[target]", "[targethullname]" });
public override IEnumerable<string> InfoTextValues => base.InfoTextValues.Concat(new string[] { targetNameText ?? "", targetContainerNameText ?? "", targetHullNameText ?? "" });
public override bool IsCompleted => target != null && target.ParentInventory == Traitor.Character.Inventory;
public override bool CanBeCompleted {
get
{
if (!base.CanBeCompleted)
{
return false;
}
if (target == null)
{
return true;
}
if (target.Removed)
{
return false;
}
if (target.Submarine == null)
{
if (!(target.ParentInventory?.Owner is Character))
{
return false;
}
}
else
{
if (target.Submarine.TeamID != Traitor.Character.TeamID)
{
return false;
}
}
return true;
}
}
public override bool IsEnemy(Character character) => base.IsEnemy(character) || (target != null && target.FindParentInventory(inventory => inventory == character.Inventory) != null);
protected ItemPrefab FindItemPrefab(string identifier)
{
return (ItemPrefab)MapEntityPrefab.List.Find(prefab => prefab is ItemPrefab && prefab.Identifier == identifier);
}
protected Item FindRandomContainer(bool includeNew, bool includeExisting)
{
int itemsCount = Item.ItemList.Count;
int startIndex = TraitorMission.Random(itemsCount);
Item fallback = null;
for (int i = 0; i < itemsCount; ++i)
{
var item = Item.ItemList[(i + startIndex) % itemsCount];
if (item.Submarine == null || item.Submarine.TeamID != Traitor.Character.TeamID)
{
continue;
}
if (item.GetComponent<ItemContainer>() != null && allowedContainerIdentifiers.Contains(item.prefab.Identifier))
{
if ((includeNew && !item.OwnInventory.IsFull()) || (includeExisting && item.OwnInventory.FindItemByIdentifier(targetPrefab.Identifier) != null))
{
return item;
}
}
}
return null;
}
public override bool Start(Traitor traitor)
{
if (!base.Start(traitor))
{
return false;
}
targetPrefab = FindItemPrefab(identifier);
if (targetPrefab == null)
{
return false;
}
var targetPrefabTextId = targetPrefab.GetItemNameTextId();
targetNameText = targetPrefabTextId != null ? TextManager.FormatServerMessage(targetPrefabTextId) : targetPrefab.Name;
targetContainer = null;
if (preferNew)
{
targetContainer = FindRandomContainer(true, false);
}
if (targetContainer == null)
{
targetContainer = FindRandomContainer(allowNew, allowExisting);
}
if (targetContainer == null)
{
return false;
}
var containerPrefabTextId = targetContainer.Prefab.GetItemNameTextId();
targetContainerNameText = containerPrefabTextId != null ? TextManager.FormatServerMessage(containerPrefabTextId) : targetContainer.Prefab.Name;
var targetHullTextId = targetContainer.CurrentHull != null ? targetContainer.CurrentHull.prefab.GetHullNameTextId() : null;
targetHullNameText = targetHullTextId != null ? TextManager.FormatServerMessage(targetHullTextId) : targetContainer?.CurrentHull?.DisplayName ?? "";
if (allowNew && !targetContainer.OwnInventory.IsFull())
{
existingItems.Clear();
foreach (var item in targetContainer.OwnInventory.Items)
{
existingItems.Add(item);
}
Entity.Spawner.AddToSpawnQueue(targetPrefab, targetContainer.OwnInventory);
target = null;
}
else if (allowExisting)
{
target = targetContainer.OwnInventory.FindItemByIdentifier(targetPrefab.Identifier);
}
return true;
}
public override void Update(float deltaTime)
{
base.Update(deltaTime);
if (target == null)
{
target = targetContainer.OwnInventory.Items.FirstOrDefault(item => item != null && item.Prefab.Identifier == identifier && !existingItems.Contains(item));
if (target != null)
{
existingItems.Clear();
}
}
}
public GoalFindItem(string identifier, bool preferNew, bool allowNew, bool allowExisting, params string[] allowedContainerIdentifiers)
{
this.identifier = identifier;
this.preferNew = preferNew;
this.allowNew = allowNew;
this.allowExisting = allowExisting;
this.allowedContainerIdentifiers.UnionWith(allowedContainerIdentifiers);
}
}
}
}
@@ -0,0 +1,45 @@
using Barotrauma.Networking;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public sealed class GoalFloodPercentOfSub : Goal
{
private readonly float minimumFloodingAmount;
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[percentage]" });
public override IEnumerable<string> InfoTextValues => base.InfoTextValues.Concat(new string[] { string.Format("{0:0}", minimumFloodingAmount * 100.0f) });
private bool isCompleted = false;
public override bool IsCompleted => isCompleted;
public override void Update(float deltaTime)
{
base.Update(deltaTime);
var validHullsCount = 0;
var floodingAmount = 0.0f;
foreach (Hull hull in Hull.hullList)
{
if (hull.Submarine == null || hull.Submarine.IsOutpost || hull.Submarine.TeamID != Traitor.Character.TeamID) { continue; }
++validHullsCount;
floodingAmount += hull.WaterVolume / hull.Volume;
}
if (validHullsCount > 0)
{
floodingAmount /= validHullsCount;
}
isCompleted = floodingAmount >= minimumFloodingAmount;
}
public GoalFloodPercentOfSub(float minimumFloodingAmount) : base()
{
InfoTextId = "TraitorGoalFloodPercentOfSub";
this.minimumFloodingAmount = minimumFloodingAmount;
}
}
}
}
@@ -0,0 +1,45 @@
using Barotrauma.Networking;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public sealed class GoalKillTarget : Goal
{
public TraitorMission.CharacterFilter Filter { get; private set; }
public Character Target { get; private set; }
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[targetname]" });
public override IEnumerable<string> InfoTextValues => base.InfoTextValues.Concat(new string[] { Target?.Name ?? "(unknown)" });
private bool isCompleted = false;
public override bool IsCompleted => isCompleted;
public override bool IsEnemy(Character character) => base.IsEnemy(character) || (!isCompleted && character == Target);
public override void Update(float deltaTime)
{
base.Update(deltaTime);
isCompleted = Target?.IsDead ?? false;
}
public override bool Start(Traitor traitor)
{
if (!base.Start(traitor))
{
return false;
}
Target = traitor.Mission.FindKillTarget(traitor.Character, Filter);
return Target != null && !Target.IsDead;
}
public GoalKillTarget(TraitorMission.CharacterFilter filter) : base()
{
InfoTextId = "TraitorGoalKillTargetInfo";
Filter = filter;
}
}
}
}
@@ -0,0 +1,44 @@
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public class GoalRandom : Goal
{
private readonly List<Goal> allGoals;
private readonly List<Goal> selectedGoals = new List<Goal>();
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[targetname]" });
public override IEnumerable<string> InfoTextValues => base.InfoTextValues.Concat(new string[] { Target?.Name ?? "(unknown)" });
private bool isCompleted = false;
public override bool IsCompleted => isCompleted;
public override bool IsEnemy(Character character) => base.IsEnemy(character) || (!isCompleted && character == Target);
public override void Update(float deltaTime)
{
base.Update(deltaTime);
isCompleted = Target?.IsDead ?? false;
}
public override bool Start(Traitor traitor)
{
if (!base.Start(traitor))
{
return false;
}
Target = traitor.Mission.FindKillTarget(traitor.Character, Filter);
return Target != null && !Target.IsDead;
}
public GoalRandom(params Goal[] goals, int count)
{
this.goals = goals;
}
}
}
}
@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FarseerPhysics;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
partial class Traitor
{
public sealed class GoalReachDistanceFromSub : Goal
{
private readonly float requiredDistance;
private readonly float requiredDistanceSqr;
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[distance]" });
public override IEnumerable<string> InfoTextValues => base.InfoTextValues.Concat(new string[] { $"{requiredDistance:0.00}" });
public override bool IsCompleted
{
get
{
if (Traitor == null || Traitor.Character == null || Traitor.Character.Submarine == null)
{
return false;
}
var characterPosition = Traitor.Character.WorldPosition;
var submarinePosition = Traitor.Character.Submarine.WorldPosition;
var distance = Vector2.DistanceSquared(characterPosition, submarinePosition);
return distance >= requiredDistanceSqr;
}
}
public GoalReachDistanceFromSub(float requiredDistance) : base()
{
InfoTextId = "TraitorGoalReachDistanceFromSub";
this.requiredDistance = requiredDistance;
requiredDistanceSqr = requiredDistance * requiredDistance;
}
}
}
}
@@ -0,0 +1,70 @@
using Barotrauma.Networking;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public class GoalReplaceInventory : HumanoidGoal
{
private readonly HashSet<string> sabotageContainerIds = new HashSet<string>();
private readonly HashSet<string> validReplacementIds = new HashSet<string>();
private readonly float replaceAmount;
private bool isCompleted = false;
public override bool IsCompleted => isCompleted;
public override IEnumerable<string> StatusTextKeys => base.StatusTextKeys.Concat(new string[] { "[percentage]" });
public override IEnumerable<string> StatusTextValues => base.StatusTextValues.Concat(new string[] { string.Format("{0:0}", replaceAmount * 100.0f) });
public override void Update(float deltaTime)
{
base.Update(deltaTime);
int totalAmount = 0, replacedAmount = 0;
foreach (var item in Item.ItemList)
{
if (item.Submarine == null || item.Submarine.TeamID != Traitor.Character.TeamID)
{
continue;
}
if (item.FindParentInventory(inventory => inventory.Owner is Character) != null)
{
continue;
}
if (sabotageContainerIds.Contains(item.prefab.Identifier))
{
++totalAmount;
if (item.OwnInventory.Items.Length <= 0 || item.OwnInventory.Items.All(containedItem => containedItem != null && !validReplacementIds.Contains(containedItem.Prefab.Identifier)))
{
continue;
}
++replacedAmount;
}
}
isCompleted = replacedAmount >= (int)(replaceAmount * totalAmount + 0.5f);
}
public override bool Start(Traitor traitor)
{
if (!base.Start(traitor))
{
return false;
}
if (sabotageContainerIds.Count <= 0 || validReplacementIds.Count <= 0)
{
return false;
}
return true;
}
public GoalReplaceInventory(string[] containerIds, string[] replacementIds, float replaceAmount)
{
sabotageContainerIds.UnionWith(containerIds);
validReplacementIds.UnionWith(replacementIds);
this.replaceAmount = replaceAmount;
}
}
}
}
@@ -0,0 +1,62 @@
using Barotrauma.Networking;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public sealed class GoalSabotageItems : HumanoidGoal
{
private readonly string tag;
private readonly float conditionThreshold;
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[tag]", "[target]", "[threshold]" });
public override IEnumerable<string> InfoTextValues => base.InfoTextValues.Concat(new string[] { tag ?? "", targetItemPrefabName ?? "", string.Format("{0:0}", conditionThreshold) });
private bool isCompleted = false;
public override bool IsCompleted => isCompleted;
private readonly List<Item> targetItems = new List<Item>();
private string targetItemPrefabName = null;
public override bool Start(Traitor traitor)
{
if (!base.Start(traitor))
{
return false;
}
foreach (var item in Item.ItemList)
{
if (item.Submarine == null || item.Submarine.TeamID != Traitor.Character.TeamID)
{
continue;
}
if (item.Condition > conditionThreshold && (item.Prefab?.Identifier == tag || item.HasTag(tag)))
{
targetItems.Add(item);
}
}
if (targetItems.Count > 0)
{
var textId = targetItems[0].Prefab.GetItemNameTextId();
targetItemPrefabName = TextManager.FormatServerMessage(textId) ?? targetItems[0].Prefab.Name;
}
return targetItems.Count > 0;
}
public override void Update(float deltaTime)
{
base.Update(deltaTime);
isCompleted = targetItems.All(item => item.Condition <= conditionThreshold);
}
public GoalSabotageItems(string tag, float conditionThreshold) : base()
{
this.tag = tag;
this.conditionThreshold = conditionThreshold;
InfoTextId = "TraitorGoalSabotageInfo";
}
}
}
}
@@ -0,0 +1,17 @@
namespace Barotrauma
{
partial class Traitor
{
public abstract class HumanoidGoal : Goal
{
public override bool Start(Traitor traitor)
{
if (!base.Start(traitor))
{
return false;
}
return Traitor?.Character?.IsHumanoid ?? false;
}
}
}
}
@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public sealed class GoalHasDuration : Modifier
{
private readonly float requiredDuration;
private readonly bool countTotalDuration;
private readonly string durationInfoTextId;
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[duration]" });
public override IEnumerable<string> InfoTextValues => base.InfoTextValues.Concat(new string[] { $"{TimeSpan.FromSeconds(requiredDuration):g}" });
protected internal override string GetInfoText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values)
{
var infoText = base.GetInfoText(traitor, textId, keys, values);
return !string.IsNullOrEmpty(durationInfoTextId) ? TextManager.FormatServerMessage(durationInfoTextId, new[] { "[infotext]", "[duration]" }, new[] { infoText, $"{TimeSpan.FromSeconds(requiredDuration):g}" }) : infoText;
}
private bool isCompleted = false;
public override bool IsCompleted => isCompleted;
private float remainingDuration = float.NaN;
public override void Update(float deltaTime)
{
base.Update(deltaTime);
if (Goal.IsCompleted)
{
if (!float.IsNaN(remainingDuration))
{
remainingDuration -= deltaTime;
}
else
{
remainingDuration = requiredDuration;
}
isCompleted |= remainingDuration <= 0.0f;
}
else if (!countTotalDuration)
{
remainingDuration = float.NaN;
}
}
public GoalHasDuration(Goal goal, float requiredDuration, bool countTotalDuration, string durationInfoTextId) : base(goal)
{
this.requiredDuration = requiredDuration;
this.countTotalDuration = countTotalDuration;
this.durationInfoTextId = durationInfoTextId;
}
}
}
}
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public sealed class GoalHasTimeLimit : Modifier
{
private readonly float timeLimit;
private readonly string timeLimitInfoTextId;
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[timelimit]" });
public override IEnumerable<string> InfoTextValues => base.InfoTextValues.Concat(new string[] { $"{TimeSpan.FromSeconds(timeLimit):g}" });
protected internal override string GetInfoText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values)
{
var infoText = base.GetInfoText(traitor, textId, keys, values);
return !string.IsNullOrEmpty(timeLimitInfoTextId) ? TextManager.FormatServerMessage(timeLimitInfoTextId, new[] { "[infotext]", "[timelimit]" }, new[] { infoText, $"{TimeSpan.FromSeconds(timeLimit):g}" }) : infoText;
}
public override bool CanBeCompleted => base.CanBeCompleted && (!IsStarted || timeRemaining > 0.0f);
private float timeRemaining;
public override void Update(float deltaTime)
{
base.Update(deltaTime);
timeRemaining = System.Math.Max(0.0f, timeRemaining - deltaTime);
}
public override bool Start(Traitor traitor)
{
if (!base.Start(traitor))
{
return false;
}
timeRemaining = timeLimit;
return true;
}
public GoalHasTimeLimit(Goal goal, float timeLimit, string timeLimitInfoTextId) : base(goal)
{
this.timeLimit = timeLimit;
this.timeLimitInfoTextId = timeLimitInfoTextId;
}
}
}
}
@@ -0,0 +1,38 @@
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public sealed class GoalIsOptional : Modifier
{
private readonly string optionalInfoTextId;
public override string StatusValueTextId => (base.IsStarted && !base.CanBeCompleted) ? "failed" : base.StatusValueTextId;
public override IEnumerable<string> StatusTextValues
{
get {
var values = base.StatusTextValues.ToArray();
values[1] = TextManager.GetServerMessage(StatusValueTextId);
return values;
}
}
public override bool IsCompleted => base.IsCompleted || (base.IsStarted && !base.CanBeCompleted);
public override bool CanBeCompleted => true;
protected internal override string GetInfoText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values)
{
var infoText = base.GetInfoText(traitor, textId, keys, values);
return !string.IsNullOrEmpty(optionalInfoTextId) ? TextManager.FormatServerMessage(optionalInfoTextId, new[] { "[infotext]" }, new[] { infoText }) : infoText;
}
public GoalIsOptional(Goal goal, string optionalInfoTextId) : base(goal)
{
this.optionalInfoTextId = optionalInfoTextId;
}
}
}
}
@@ -0,0 +1,80 @@
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public abstract class Modifier : Goal
{
protected Goal Goal { get; }
public override string StatusValueTextId => Goal.StatusValueTextId;
public override string StatusTextId
{
get => Goal.StatusTextId;
set => Goal.StatusTextId = value;
}
public override string InfoTextId
{
get => Goal.InfoTextId;
set => Goal.InfoTextId = value;
}
public override string CompletedTextId
{
get => Goal.CompletedTextId;
set => Goal.CompletedTextId = value;
}
public override IEnumerable<string> StatusTextKeys => Goal.StatusTextKeys;
public override IEnumerable<string> StatusTextValues => new [] { InfoText, TextManager.FormatServerMessage(StatusValueTextId) };
public override IEnumerable<string> InfoTextKeys => Goal.InfoTextKeys;
public override IEnumerable<string> InfoTextValues => Goal.InfoTextValues;
public override IEnumerable<string> CompletedTextKeys => Goal.CompletedTextKeys;
public override IEnumerable<string> CompletedTextValues => Goal.CompletedTextValues;
protected internal override string GetStatusText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values) => Goal.GetStatusText(traitor, textId, keys, values);
protected internal override string GetInfoText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values) => Goal.GetInfoText(traitor, textId, keys, values);
protected internal override string GetCompletedText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values) => Goal.GetCompletedText(traitor, textId, keys, values);
public override string StatusText => GetStatusText(Traitor, StatusTextId, StatusTextKeys, StatusTextValues);
public override string InfoText => GetInfoText(Traitor, InfoTextId, InfoTextKeys, InfoTextValues);
public override string CompletedText => CompletedTextId != null ? GetCompletedText(Traitor, CompletedTextId, CompletedTextKeys, CompletedTextValues) : StatusText;
public override bool IsCompleted => Goal.IsCompleted;
public override bool IsStarted => base.IsStarted && Goal.IsStarted;
public override bool CanBeCompleted => base.CanBeCompleted && Goal.CanBeCompleted;
public override bool IsEnemy(Character character) => base.IsEnemy(character) || Goal.IsEnemy(character);
public override void Update(float deltaTime)
{
base.Update(deltaTime);
Goal.Update(deltaTime);
}
public override bool Start(Traitor traitor)
{
if (!base.Start(traitor))
{
return false;
}
if (!Goal.Start(traitor))
{
return false;
}
return true;
}
protected Modifier(Goal goal) : base()
{
Goal = goal;
}
}
}
}
@@ -0,0 +1,198 @@
using Barotrauma.Networking;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public class Objective
{
public Traitor Traitor { get; private set; }
private int shuffleGoalsCount;
private readonly List<Goal> allGoals = new List<Goal>();
private readonly List<Goal> activeGoals = new List<Goal>();
private readonly List<Goal> pendingGoals = new List<Goal>();
private readonly List<Goal> completedGoals = new List<Goal>();
public bool IsCompleted => pendingGoals.Count <= 0;
public bool IsPartiallyCompleted => completedGoals.Count > 0;
public bool IsStarted { get; private set; } = false;
public bool CanBeCompleted => !IsStarted || pendingGoals.All(goal => goal.CanBeCompleted);
public bool IsEnemy(Character character) => pendingGoals.Any(goal => goal.IsEnemy(character));
public string InfoText { get; private set; }
public virtual string GoalInfoFormatId { get; set; } = "TraitorObjectiveGoalInfoFormat";
public string GoalInfos =>
string.Join("/",
string.Join("/", activeGoals.Select((goal, index) =>
{
var statusText = goal.StatusText;
var startIndex = statusText.LastIndexOf('/') + 1;
return $"{statusText.Substring(0, startIndex)}[{index}.st]={statusText.Substring(startIndex)}/[{index}.sl]={TextManager.FormatServerMessage(GoalInfoFormatId, new string[] { "[statustext]" }, new string[] { $"[{index}.st]" })}";
}).ToArray()),
string.Join("", activeGoals.Select((goal, index) => $"[{index}.sl]").ToArray()));
public string AllGoalInfos =>
string.Join("/",
string.Join("/", allGoals.Select((goal, index) =>
{
var statusText = goal.StatusText;
var startIndex = statusText.LastIndexOf('/') + 1;
return $"{statusText.Substring(0, startIndex)}[{index}.st]={statusText.Substring(startIndex)}/[{index}.sl]={TextManager.FormatServerMessage(GoalInfoFormatId, new string[] { "[statustext]" }, new string[] { $"[{index}.st]" })}";
}).ToArray()),
string.Join("", allGoals.Select((goal, index) => $"[{index}.sl]").ToArray()));
public virtual string StartMessageTextId { get; set; } = "TraitorObjectiveStartMessage";
public virtual IEnumerable<string> StartMessageKeys => new string[] { "[traitorgoalinfos]" };
public virtual IEnumerable<string> StartMessageValues => new string[] { GoalInfos };
public virtual string StartMessageText => TextManager.FormatServerMessageWithGenderPronouns(Traitor?.Character?.Info?.Gender ?? Gender.None, StartMessageTextId, StartMessageKeys, StartMessageValues);
public virtual string StartMessageServerTextId { get; set; } = "TraitorObjectiveStartMessageServer";
public virtual IEnumerable<string> StartMessageServerKeys => StartMessageKeys.Concat(new string[] { "[traitorname]" });
public virtual IEnumerable<string> StartMessageServerValues => StartMessageValues.Concat(new string[] { Traitor?.Character?.Name ?? "(unknown)" });
public virtual string StartMessageServerText => TextManager.FormatServerMessageWithGenderPronouns(Traitor?.Character?.Info?.Gender ?? Gender.None, StartMessageServerTextId, StartMessageServerKeys, StartMessageServerValues);
public virtual string EndMessageSuccessTextId { get; set; } = "TraitorObjectiveEndMessageSuccess";
public virtual string EndMessageSuccessDeadTextId { get; set; } = "TraitorObjectiveEndMessageSuccessDead";
public virtual string EndMessageSuccessDetainedTextId { get; set; } = "TraitorObjectiveEndMessageSuccessDetained";
public virtual string EndMessageFailureTextId { get; set; } = "TraitorObjectiveEndMessageFailure";
public virtual string EndMessageFailureDeadTextId { get; set; } = "TraitorObjectiveEndMessageFailureDead";
public virtual string EndMessageFailureDetainedTextId { get; set; } = "TraitorObjectiveEndMessageFailureDetained";
public virtual IEnumerable<string> EndMessageKeys => new string[] { "[traitorname]", "[traitorgoalinfos]" };
public virtual IEnumerable<string> EndMessageValues => new string[] { Traitor?.Character?.Name ?? "(unknown)", GoalInfos };
public virtual string EndMessageText
{
get
{
var traitorIsDead = Traitor.Character.IsDead;
var traitorIsDetained = Traitor.Character.LockHands;
var messageId = IsCompleted
? (traitorIsDead ? EndMessageSuccessDeadTextId : traitorIsDetained ? EndMessageSuccessDetainedTextId : EndMessageSuccessTextId)
: (traitorIsDead ? EndMessageFailureDeadTextId : traitorIsDetained ? EndMessageFailureDetainedTextId : EndMessageFailureTextId);
return TextManager.FormatServerMessageWithGenderPronouns(Traitor?.Character?.Info?.Gender ?? Gender.None, messageId, EndMessageKeys.ToArray(), EndMessageValues.ToArray());
}
}
public bool Start(Traitor traitor)
{
Traitor = traitor;
activeGoals.Clear();
pendingGoals.Clear();
completedGoals.Clear();
var allGoalsCount = allGoals.Count;
var indices = allGoals.Select((goal, index) => index).ToArray();
if (shuffleGoalsCount > 0)
{
for (var i = allGoalsCount; i > 1;)
{
int j = TraitorMission.Random(i--);
var temp = indices[j];
indices[j] = indices[i];
indices[i] = temp;
}
}
for (var i = 0; i < allGoalsCount; ++i)
{
var goal = allGoals[indices[i]];
if (goal.Start(traitor))
{
activeGoals.Add(goal);
pendingGoals.Add(goal);
if (shuffleGoalsCount > 0 && pendingGoals.Count >= shuffleGoalsCount)
{
break;
}
}
else
{
completedGoals.Add(goal);
}
}
if (pendingGoals.Count <= 0)
{
return false;
}
IsStarted = true;
traitor.SendChatMessageBox(StartMessageText);
traitor.UpdateCurrentObjective(GoalInfos);
return true;
}
public void StartMessage()
{
Traitor.SendChatMessage(StartMessageText);
}
public void End(bool displayMessage)
{
if (displayMessage)
{
Traitor.SendChatMessageBox(EndMessageText);
}
}
public void EndMessage()
{
Traitor.SendChatMessage(EndMessageText);
}
public void Update(float deltaTime)
{
if (!IsStarted)
{
return;
}
for (int i = 0; i < pendingGoals.Count;)
{
var goal = pendingGoals[i];
goal.Update(deltaTime);
if (!goal.IsCompleted)
{
++i;
}
else
{
completedGoals.Add(goal);
pendingGoals.RemoveAt(i);
if (GameMain.Server != null)
{
Traitor.SendChatMessage(goal.CompletedText);
if (pendingGoals.Count > 0)
{
Traitor.SendChatMessageBox(goal.CompletedText);
}
Traitor.UpdateCurrentObjective(GoalInfos);
}
}
}
}
public Objective(string infoText, int shuffleGoalsCount, params Goal[] goals)
{
InfoText = infoText;
this.shuffleGoalsCount = shuffleGoalsCount;
allGoals.AddRange(goals);
}
public bool HasGoalsOfType<T>() where T : Goal
{
return allGoals?.Any(g => g is T) ?? false;
}
}
}
}
@@ -0,0 +1,66 @@
using Barotrauma.Networking;
using Lidgren.Network;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public readonly Character Character;
public string Role { get; private set; }
public TraitorMission Mission { get; private set; }
public Objective CurrentObjective => Mission.GetCurrentObjective(this);
public Traitor(TraitorMission mission, string role, Character character)
{
Mission = mission;
Role = role;
Character = character;
Character.IsTraitor = true;
GameMain.NetworkMember.CreateEntityEvent(Character, new object[] { NetEntityEvent.Type.Status });
}
public delegate void MessageSender(string message);
public void Greet(GameServer server, string codeWords, string codeResponse, MessageSender messageSender)
{
string greetingMessage = TextManager.FormatServerMessage(Mission.StartText, new string[] {
"[codewords]", "[coderesponse]"
}, new string[] {
codeWords, codeResponse
});
messageSender(greetingMessage);
// boxSender(greetingMessage);
// SendChatMessage(greetingMessage);
// SendChatMessageBox(greetingMessage);
Client traitorClient = server.ConnectedClients.Find(c => c.Character == Character);
Client ownerClient = server.ConnectedClients.Find(c => c.Connection == server.OwnerConnection);
if (traitorClient != ownerClient && ownerClient != null && ownerClient.Character == null)
{
GameMain.Server.SendTraitorMessage(ownerClient, CurrentObjective.StartMessageServerText, TraitorMessageType.ServerMessageBox);
}
}
public void SendChatMessage(string serverText)
{
Client traitorClient = GameMain.Server.ConnectedClients.Find(c => c.Character == Character);
GameMain.Server.SendTraitorMessage(traitorClient, serverText, TraitorMessageType.Server);
}
public void SendChatMessageBox(string serverText)
{
Client traitorClient = GameMain.Server.ConnectedClients.Find(c => c.Character == Character);
GameMain.Server.SendTraitorMessage(traitorClient, serverText, TraitorMessageType.ServerMessageBox);
}
public void UpdateCurrentObjective(string objectiveText)
{
Client traitorClient = GameMain.Server.ConnectedClients.Find(c => c.Character == Character);
Character.TraitorCurrentObjective = objectiveText;
GameMain.Server.SendTraitorMessage(traitorClient, Character.TraitorCurrentObjective, TraitorMessageType.Objective);
}
}
}
@@ -0,0 +1,208 @@
// #define DISABLE_MISSIONS
using System;
using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class TraitorManager
{
public readonly Dictionary<Character.TeamType, Traitor.TraitorMission> Missions = new Dictionary<Character.TeamType, Traitor.TraitorMission>();
public string GetCodeWords(Character.TeamType team) => Missions.TryGetValue(team, out var mission) ? mission.CodeWords : "";
public string GetCodeResponse(Character.TeamType team) => Missions.TryGetValue(team, out var mission) ? mission.CodeResponse : "";
public IEnumerable<Traitor> Traitors => Missions.Values.SelectMany(mission => mission.Traitors.Values);
private float startCountdown = 0.0f;
private GameServer server;
private readonly Dictionary<ulong, int> traitorCountsBySteamId = new Dictionary<ulong, int>();
private readonly Dictionary<string, int> traitorCountsByEndPoint = new Dictionary<string, int>();
public int GetTraitorCount(Tuple<ulong, string> steamIdAndEndPoint)
{
if (steamIdAndEndPoint.Item1 > 0 && traitorCountsBySteamId.TryGetValue(steamIdAndEndPoint.Item1, out var steamIdResult))
{
return steamIdResult;
}
return traitorCountsByEndPoint.TryGetValue(steamIdAndEndPoint.Item2, out var endPointResult) ? endPointResult : 0;
}
public void SetTraitorCount(Tuple<ulong, string> steamIdAndEndPoint, int count)
{
if (steamIdAndEndPoint.Item1 > 0)
{
traitorCountsBySteamId[steamIdAndEndPoint.Item1] = count;
}
traitorCountsByEndPoint[steamIdAndEndPoint.Item2] = count;
}
public bool IsTraitor(Character character)
{
if (Traitors == null)
{
return false;
}
return Traitors.Any(traitor => traitor.Character == character);
}
public TraitorManager()
{
}
public void Start(GameServer server)
{
#if DISABLE_MISSIONS
return;
#endif
if (server == null) return;
Traitor.TraitorMission.InitializeRandom();
this.server = server;
//TODO: configure countdowns in xml
startCountdown = MathHelper.Lerp(90.0f, 180.0f, (float)Traitor.TraitorMission.RandomDouble());
traitorCountsBySteamId.Clear();
traitorCountsByEndPoint.Clear();
}
public void Update(float deltaTime)
{
#if DISABLE_MISSIONS
return;
#endif
if (Missions.Any())
{
bool missionCompleted = false;
bool gameShouldEnd = false;
Character.TeamType winningTeam = Character.TeamType.None;
foreach (var mission in Missions)
{
mission.Value.Update(deltaTime, () =>
{
switch (mission.Key)
{
case Character.TeamType.Team1:
winningTeam = (winningTeam == Character.TeamType.None) ? Character.TeamType.Team2 : Character.TeamType.None;
break;
case Character.TeamType.Team2:
winningTeam = (winningTeam == Character.TeamType.None) ? Character.TeamType.Team1 : Character.TeamType.None;
break;
default:
break;
}
gameShouldEnd = true;
});
if (!gameShouldEnd && mission.Value.IsCompleted)
{
missionCompleted = true;
foreach (var traitor in mission.Value.Traitors.Values)
{
traitor.UpdateCurrentObjective("");
}
}
}
if (gameShouldEnd)
{
GameMain.GameSession.WinningTeam = winningTeam;
GameMain.Server.EndGame();
return;
}
if (missionCompleted)
{
Missions.Clear();
//TODO: configure countdowns in xml
startCountdown = MathHelper.Lerp(90.0f, 180.0f, (float)Traitor.TraitorMission.RandomDouble());
}
}
else if (startCountdown > 0.0f && server.GameStarted)
{
startCountdown -= deltaTime;
if (startCountdown <= 0.0f)
{
int playerCharactersCount = server.ConnectedClients.Sum(client => client.Character != null && !client.Character.IsDead ? 1 : 0);
if (playerCharactersCount < server.ServerSettings.TraitorsMinPlayerCount)
{
startCountdown = 60.0f;
return;
}
if (GameMain.GameSession.Mission is CombatMission)
{
var teamIds = new[] { Character.TeamType.Team1, Character.TeamType.Team2 };
foreach (var teamId in teamIds)
{
var mission = TraitorMissionPrefab.RandomPrefab()?.Instantiate();
if (mission != null)
{
Missions.Add(teamId, mission);
}
}
var canBeStartedCount = Missions.Sum(mission => mission.Value.CanBeStarted(server, this, mission.Key, "traitor") ? 1 : 0);
if (canBeStartedCount >= Missions.Count)
{
var startSuccessCount = Missions.Sum(mission => mission.Value.Start(server, this, mission.Key, "traitor") ? 1 : 0);
if (startSuccessCount >= Missions.Count)
{
return;
}
}
}
else
{
var mission = TraitorMissionPrefab.RandomPrefab()?.Instantiate();
if (mission != null) {
if (mission.CanBeStarted(server, this, Character.TeamType.None, "traitor"))
{
if (mission.Start(server, this, Character.TeamType.None, "traitor"))
{
Missions.Add(Character.TeamType.None, mission);
return;
}
}
}
}
Missions.Clear();
startCountdown = 60.0f;
}
}
}
public string GetEndMessage()
{
#if DISABLE_MISSIONS
return "";
#endif
if (GameMain.Server == null || !Missions.Any()) return "";
return string.Join("\n\n", Missions.Select(mission => mission.Value.GlobalEndMessage));
}
public static T WeightedRandom<T>(ICollection<T> collection, Func<int, int> random, Func<T, int> readSelectedWeight, Action<T, int> writeSelectedWeight, int entryWeight, int selectionWeight) where T : class
{
var count = collection.Count;
if (count <= 0)
{
return null;
}
var maxCount = entryWeight + collection.Max(readSelectedWeight);
var totalWeight = collection.Sum(entry => maxCount - readSelectedWeight(entry));
var selected = random(totalWeight);
foreach (var entry in collection)
{
var weight = readSelectedWeight(entry);
selected -= maxCount;
selected += weight;
if (selected <= 0)
{
writeSelectedWeight(entry, weight + selectionWeight);
return entry;
}
}
return null;
}
}
}
@@ -0,0 +1,320 @@
//#define SERVER_IS_TRAITOR
//#define ALLOW_SOLO_TRAITOR
using System;
using Barotrauma.Networking;
using Lidgren.Network;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
partial class Traitor
{
public class TraitorMission
{
private static System.Random random = null;
public static void InitializeRandom() => random = new System.Random((int)DateTime.UtcNow.Ticks);
// All traitor related functionality should use the following interface for generating random values
public static int Random(int n) => random.Next(n);
// All traitor related functionality should use the following interface for generating random values
public static double RandomDouble() => random.NextDouble();
private static string wordsTxt = Path.Combine("Content", "CodeWords.txt");
private readonly List<Objective> allObjectives = new List<Objective>();
private readonly List<Objective> pendingObjectives = new List<Objective>();
private readonly List<Objective> completedObjectives = new List<Objective>();
public virtual bool IsCompleted => pendingObjectives.Count <= 0;
public readonly Dictionary<string, Traitor> Traitors = new Dictionary<string, Traitor>();
public string StartText { get; private set; }
public string CodeWords { get; private set; }
public string CodeResponse { get; private set; }
public string EndMessage {
get
{
if (!Traitors.TryGetValue("traitor", out Traitor traitor))
{
return "";
}
if (pendingObjectives.Count <= 0)
{
if (completedObjectives.Count <= 0) return "";
return completedObjectives[completedObjectives.Count - 1].EndMessageText;
}
else
{
return pendingObjectives[0].EndMessageText;
}
}
}
public string GlobalEndMessageSuccessTextId { get; private set; }
public string GlobalEndMessageSuccessDeadTextId { get; private set; }
public string GlobalEndMessageSuccessDetainedTextId { get; private set; }
public string GlobalEndMessageFailureTextId { get; private set; }
public string GlobalEndMessageFailureDeadTextId { get; private set; }
public string GlobalEndMessageFailureDetainedTextId { get; private set; }
private readonly string objectiveGoalInfoFormat = "[index]. [goalinfos]\n";
public virtual IEnumerable<string> GlobalEndMessageKeys => new string[] { "[traitorname]", "[traitorgoalinfos]" };
public virtual IEnumerable<string> GlobalEndMessageValues {
get {
var isSuccess = completedObjectives.Count >= allObjectives.Count;
return new string[] {
(Traitors.TryGetValue("traitor", out var traitor) ? traitor.Character?.Name : null) ?? "(unknown)",
(isSuccess ? completedObjectives.LastOrDefault() : pendingObjectives.FirstOrDefault())?.GoalInfos ?? ""
};
}
}
public string GlobalEndMessage
{
get
{
if (!Traitors.TryGetValue("traitor", out Traitor traitor))
{
return "";
}
if (allObjectives.Count > 0)
{
var isSuccess = completedObjectives.Count >= allObjectives.Count;
var traitorIsDead = traitor.Character.IsDead;
var traitorIsDetained = traitor.Character.LockHands;
var messageId = isSuccess
? (traitorIsDead ? GlobalEndMessageSuccessDeadTextId : traitorIsDetained ? GlobalEndMessageSuccessDetainedTextId : GlobalEndMessageSuccessTextId)
: (traitorIsDead ? GlobalEndMessageFailureDeadTextId : traitorIsDetained ? GlobalEndMessageFailureDetainedTextId : GlobalEndMessageFailureTextId);
return TextManager.FormatServerMessageWithGenderPronouns(traitor.Character?.Info?.Gender ?? Gender.None, messageId, GlobalEndMessageKeys.ToArray(), GlobalEndMessageValues.ToArray());
}
return "";
}
}
public Objective GetCurrentObjective(Traitor traitor)
{
return pendingObjectives.Count > 0 ? pendingObjectives[0] : null;
}
protected List<Tuple<Client, Character>> FindTraitorCandidates(GameServer server, Character.TeamType team, params string[] traitorRoles)
{
var traitorCandidates = new List<Tuple<Client, Character>>();
#if SERVER_IS_TRAITOR
if (server.Character != null)
{
traitorCandidates.Add(server.Character);
}
else
#endif
{
traitorCandidates.AddRange(server.ConnectedClients.FindAll(c => c.Character != null && !c.Character.IsDead && (team == Character.TeamType.None || c.Character.TeamID == team)).ConvertAll(client => Tuple.Create(client, client.Character)));
}
return traitorCandidates;
}
protected List<Character> FindCharacters()
{
List<Character> characters = new List<Character>();
foreach (var character in Character.CharacterList)
{
characters.Add(character);
}
return characters;
}
public virtual bool CanBeStarted(GameServer server, TraitorManager traitorManager, Character.TeamType team, params string[] traitorRoles)
{
var traitorCandidates = FindTraitorCandidates(server, team, traitorRoles);
if (traitorCandidates.Count <= 0)
{
return false;
}
var characters = FindCharacters();
#if !ALLOW_SOLO_TRAITOR
if (characters.Count < 2)
{
return false;
}
#endif
return true;
}
public virtual bool Start(GameServer server, TraitorManager traitorManager, Character.TeamType team, params string[] traitorRoles)
{
List<Character> characters = FindCharacters();
List<Tuple<Client, Character>> traitorCandidates = FindTraitorCandidates(server, team, traitorRoles);
if (traitorCandidates.Count <= 0)
{
return false;
}
#if !ALLOW_SOLO_TRAITOR
if (characters.Count < 2)
{
return false;
}
#endif
CodeWords = ToolBox.GetRandomLine(wordsTxt) + ", " + ToolBox.GetRandomLine(wordsTxt);
CodeResponse = ToolBox.GetRandomLine(wordsTxt) + ", " + ToolBox.GetRandomLine(wordsTxt);
Traitors.Clear();
foreach (var role in traitorRoles)
{
var candidate = TraitorManager.WeightedRandom(traitorCandidates, Random, t =>
{
var previousClient = server.FindPreviousClientData(t.Item1);
return Math.Max(
previousClient != null ? traitorManager.GetTraitorCount(previousClient) : 0,
traitorManager.GetTraitorCount(Tuple.Create(t.Item1.SteamID, t.Item1.Connection?.EndPointString ?? "")));
}, (t, c) =>
{
traitorManager.SetTraitorCount(Tuple.Create(t.Item1.SteamID, t.Item1.Connection?.EndPointString ?? ""), c);
}, 2, 3);
traitorCandidates.Remove(candidate);
var traitor = new Traitor(this, role, candidate.Item2);
Traitors.Add(role, traitor);
}
var messages = new Dictionary<Traitor, List<string>>();
foreach (var traitor in Traitors.Values)
{
messages[traitor] = new List<string>();
if (traitor.CurrentObjective == null) { continue; }
traitor.Greet(server, CodeWords, CodeResponse, message => messages[traitor].Add(message));
}
messages.ForEach(traitor => traitor.Value.ForEach(message => traitor.Key.SendChatMessage(message)));
Update(0.0f, GameMain.Server.EndGame);
messages.ForEach(traitor => traitor.Value.ForEach(message => traitor.Key.SendChatMessageBox(message)));
#if SERVER
foreach (var traitor in Traitors.Values)
{
GameServer.Log(string.Format("{0} is the traitor and the current goals are:\n{1}", traitor.Character.Name, traitor.CurrentObjective?.GoalInfos != null ? TextManager.GetServerMessage(traitor.CurrentObjective?.GoalInfos) : "(empty)"), ServerLog.MessageType.ServerMessage);
}
#endif
return true;
}
public delegate void TraitorWinHandler();
public virtual void Update(float deltaTime, TraitorWinHandler winHandler)
{
if (pendingObjectives.Count <= 0 || Traitors.Count <= 0)
{
return;
}
foreach (var traitor in Traitors.Values)
{
if (traitor.Character.IsDead)
{
traitor.UpdateCurrentObjective("");
}
}
int previousCompletedCount = completedObjectives.Count;
int startedCount = 0;
while (pendingObjectives.Count > 0)
{
var objective = pendingObjectives[0];
if (!objective.IsStarted)
{
if (!objective.Start(Traitors["traitor"]))
{
pendingObjectives.RemoveAt(0);
completedObjectives.Add(objective);
if (pendingObjectives.Count > 0)
{
objective.EndMessage();
}
continue;
}
++startedCount;
}
objective.Update(deltaTime);
if (objective.IsCompleted)
{
pendingObjectives.RemoveAt(0);
completedObjectives.Add(objective);
if (pendingObjectives.Count > 0)
{
objective.EndMessage();
}
continue;
}
if (!objective.CanBeCompleted)
{
objective.EndMessage();
objective.End(true);
pendingObjectives.Clear();
}
break;
}
int completedMax = completedObjectives.Count - 1;
for (int i = previousCompletedCount; i <= completedMax; ++i)
{
var objective = completedObjectives[i];
objective.End(i < completedMax || pendingObjectives.Count > 0);
}
if (pendingObjectives.Count > 0)
{
if (startedCount > 0)
{
pendingObjectives[0].StartMessage();
}
}
else if (completedObjectives.Count >= allObjectives.Count)
{
foreach (var traitor in Traitors)
{
SteamAchievementManager.OnTraitorWin(traitor.Value.Character);
}
winHandler();
}
}
public delegate bool CharacterFilter(Character character);
public Character FindKillTarget(Character traitor, CharacterFilter filter)
{
if (traitor == null) { return null; }
List<Character> validCharacters = Character.CharacterList.FindAll(c =>
c.TeamID == traitor.TeamID &&
c != traitor &&
!c.IsDead &&
(filter == null || filter(c)));
if (validCharacters.Count > 0)
{
return validCharacters[Random(validCharacters.Count)];
}
#if ALLOW_SOLO_TRAITOR
return traitor;
#else
return null;
#endif
}
public TraitorMission(string startText, string globalEndMessageSuccessTextId, string globalEndMessageSuccessDeadTextId, string globalEndMessageSuccessDetainedTextId, string globalEndMessageFailureTextId, string globalEndMessageFailureDeadTextId, string globalEndMessageFailureDetainedTextId, params Objective[] objectives)
{
StartText = startText;
GlobalEndMessageSuccessTextId = globalEndMessageSuccessTextId;
GlobalEndMessageSuccessDeadTextId = globalEndMessageSuccessDeadTextId;
GlobalEndMessageSuccessDetainedTextId = globalEndMessageSuccessDetainedTextId;
GlobalEndMessageFailureTextId = globalEndMessageFailureTextId;
GlobalEndMessageFailureDeadTextId = globalEndMessageFailureDeadTextId;
GlobalEndMessageFailureDetainedTextId = globalEndMessageFailureDetainedTextId;
allObjectives.AddRange(objectives);
pendingObjectives.AddRange(objectives);
}
}
}
}
@@ -0,0 +1,475 @@
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using System.Linq;
using Barotrauma.Extensions;
using Barotrauma.Networking;
namespace Barotrauma {
class TraitorMissionPrefab
{
public class TraitorMissionEntry
{
public readonly TraitorMissionPrefab Prefab;
public int SelectedWeight;
public TraitorMissionEntry(XElement element)
{
Prefab = new TraitorMissionPrefab(element);
SelectedWeight = 0;
}
}
public static readonly List<TraitorMissionEntry> List = new List<TraitorMissionEntry>();
public static void Init()
{
var files = GameMain.Instance.GetFilesOfType(ContentType.TraitorMissions);
foreach (string file in files)
{
XDocument doc = XMLExtensions.TryLoadXml(file);
if (doc?.Root == null) continue;
foreach (XElement element in doc.Root.Elements())
{
List.Add(new TraitorMissionEntry(element));
}
}
}
public static TraitorMissionPrefab RandomPrefab()
{
return TraitorManager.WeightedRandom(List, Traitor.TraitorMission.Random, entry => entry.SelectedWeight, (entry, weight) => entry.SelectedWeight = weight, 2, 3)?.Prefab;
}
private class AttributeChecker : IDisposable
{
private readonly XElement element;
private readonly HashSet<string> required = new HashSet<string>();
private readonly HashSet<string> optional = new HashSet<string>();
public void Optional(params string[] names)
{
optional.UnionWith(names);
}
public void Required(params string[] names)
{
required.UnionWith(names);
}
public void Dispose()
{
foreach (var requiredName in required)
{
if (element.Attributes().All(attribute => attribute.Name != requiredName))
{
GameServer.Log($"Required attribute \"{requiredName}\" is missing in \"{element.Name}\"", ServerLog.MessageType.Error);
}
}
foreach (var attribute in element.Attributes())
{
var attributeName = attribute.Name.ToString();
if (!required.Contains(attributeName) && !optional.Contains(attributeName))
{
GameServer.Log($"Unsupported attribute \"{attributeName}\" in \"{element.Name}\"", ServerLog.MessageType.Error);
}
}
}
public AttributeChecker(XElement element)
{
this.element = element;
}
}
public class Goal
{
public readonly string Type;
public readonly XElement Config;
public Goal(string type, XElement config)
{
Type = type;
Config = config;
}
private delegate bool TargetFilter(string value, Character character);
private static Dictionary<string, TargetFilter> targetFilters = new Dictionary<string, TargetFilter>()
{
{ "job", (value, character) => value.Equals(character.Info.Job.Prefab.Identifier, StringComparison.OrdinalIgnoreCase) },
};
public Traitor.Goal Instantiate()
{
Traitor.Goal goal = null;
using (var checker = new AttributeChecker(Config))
{
checker.Required("type");
var goalType = Config.GetAttributeString("type", "");
switch (goalType.ToLowerInvariant())
{
case "killtarget":
{
checker.Optional(targetFilters.Keys.ToArray());
List<Traitor.TraitorMission.CharacterFilter> filters = new List<Traitor.TraitorMission.CharacterFilter>();
foreach (var attribute in Config.Attributes())
{
if (targetFilters.TryGetValue(attribute.Name.ToString().ToLower(System.Globalization.CultureInfo.InvariantCulture), out var filter))
{
filters.Add((character) => filter(attribute.Value, character));
}
}
goal = new Traitor.GoalKillTarget((character) => filters.All(f => f(character)));
break;
}
case "destroyitems":
{
checker.Required("tag");
checker.Optional("percentage", "matchIdentifier", "matchTag", "matchInventory");
var tag = Config.GetAttributeString("tag", null);
if (tag != null)
{
goal = new Traitor.GoalDestroyItemsWithTag(
tag,
Config.GetAttributeFloat("percentage", 100.0f) / 100.0f,
Config.GetAttributeBool("matchIdentifier", true),
Config.GetAttributeBool("matchTag", true),
Config.GetAttributeBool("matchInventory", false));
}
break;
}
case "sabotage":
{
checker.Required("tag");
checker.Optional("threshold");
var tag = Config.GetAttributeString("tag", null);
if (tag != null)
{
goal = new Traitor.GoalSabotageItems(tag, Config.GetAttributeFloat("threshold", 20.0f));
}
break;
}
case "floodsub":
checker.Optional("percentage");
goal = new Traitor.GoalFloodPercentOfSub(Config.GetAttributeFloat("percentage", 100.0f) / 100.0f);
break;
case "finditem":
checker.Required("identifier");
checker.Optional("preferNew", "allowNew", "allowExisting", "allowedContainers");
goal = new Traitor.GoalFindItem(Config.GetAttributeString("identifier", null), Config.GetAttributeBool("preferNew", true), Config.GetAttributeBool("allowNew", true), Config.GetAttributeBool("allowExisting", true), Config.GetAttributeStringArray("allowedContainers", new string[] {"steelcabinet", "mediumsteelcabinet", "suppliescabinet"}));
break;
case "replaceinventory":
checker.Required("containers", "replacements");
checker.Optional("percentage");
goal = new Traitor.GoalReplaceInventory(Config.GetAttributeStringArray("containers", new string[] { }), Config.GetAttributeStringArray("replacements", new string[] { }), Config.GetAttributeFloat("percentage", 100.0f) / 100.0f);
break;
case "reachdistancefromsub":
checker.Optional("distance");
goal = new Traitor.GoalReachDistanceFromSub(Config.GetAttributeFloat("distance", 10000.0f));
break;
default:
GameServer.Log($"Unrecognized goal type \"{goalType}\".", ServerLog.MessageType.Error);
break;
}
}
if (goal == null)
{
return null;
}
foreach (var element in Config.Elements())
{
switch (element.Name.ToString().ToLowerInvariant())
{
case "modifier":
{
using (var checker = new AttributeChecker(element))
{
checker.Required("type");
var modifierType = element.GetAttributeString("type", "");
switch (modifierType)
{
case "duration":
{
checker.Optional("cumulative", "duration", "infotext");
var isCumulative = element.GetAttributeBool("cumulative", false);
goal = new Traitor.GoalHasDuration(goal, element.GetAttributeFloat("duration", 5.0f), isCumulative, element.GetAttributeString("infotext", isCumulative ? "TraitorGoalWithCumulativeDurationInfoText" : "TraitorGoalWithDurationInfoText"));
break;
}
case "timelimit":
checker.Optional("timelimit", "infotext");
goal = new Traitor.GoalHasTimeLimit(goal, element.GetAttributeFloat("timelimit", 180.0f), element.GetAttributeString("infotext", "TraitorGoalWithTimeLimitInfoText"));
break;
case "optional":
checker.Optional("infotext");
goal = new Traitor.GoalIsOptional(goal, element.GetAttributeString("infotext", "TraitorGoalIsOptionalInfoText"));
break;
default:
GameServer.Log($"Unrecognized modifier type \"{modifierType}\".", ServerLog.MessageType.Error);
break;
}
}
break;
}
}
}
foreach (var element in Config.Elements())
{
var elementName = element.Name.ToString().ToLowerInvariant();
switch (elementName)
{
case "modifier":
// loaded above
break;
case "infotext":
{
using (var checker = new AttributeChecker(element))
{
checker.Required("id");
var id = element.GetAttributeString("id", null);
if (id != null)
{
goal.InfoTextId = id;
}
}
break;
}
case "completedtext":
{
using (var checker = new AttributeChecker(element))
{
checker.Required("id");
var id = element.GetAttributeString("id", null);
if (id != null)
{
goal.CompletedTextId = id;
}
}
break;
}
default:
GameServer.Log($"Unrecognized element \"{element.Name}\" in goal.", ServerLog.MessageType.Error);
break;
}
}
return goal;
}
}
public class Objective
{
public string InfoText { get; internal set; }
public string StartMessageTextId { get; internal set; }
public string StartMessageServerTextId { get; internal set; }
public string EndMessageSuccessTextId { get; internal set; }
public string EndMessageSuccessDeadTextId { get; internal set; }
public string EndMessageSuccessDetainedTextId { get; internal set; }
public string EndMessageFailureTextId { get; internal set; }
public string EndMessageFailureDeadTextId { get; internal set; }
public string EndMessageFailureDetainedTextId { get; internal set; }
public int ShuffleGoalsCount { get; internal set; }
public readonly List<Goal> Goals = new List<Goal>();
public Traitor.Objective Instantiate()
{
var result = new Traitor.Objective(InfoText, ShuffleGoalsCount, Goals.ConvertAll(goal => {
var instance = goal.Instantiate();
if (instance == null)
{
GameServer.Log($"Failed to instantiate goal \"{goal.Type}\".", ServerLog.MessageType.Error);
}
return instance;
}).FindAll(goal => goal != null).ToArray());
if (StartMessageTextId != null)
{
result.StartMessageTextId = StartMessageTextId;
}
if (StartMessageServerTextId != null)
{
result.StartMessageServerTextId = StartMessageServerTextId;
}
if (EndMessageSuccessTextId != null)
{
result.EndMessageSuccessTextId = EndMessageSuccessTextId;
}
if (EndMessageSuccessDeadTextId != null)
{
result.EndMessageSuccessDeadTextId = EndMessageSuccessDeadTextId;
}
if (EndMessageSuccessDetainedTextId != null)
{
result.EndMessageSuccessDetainedTextId = EndMessageSuccessDetainedTextId;
}
if (EndMessageFailureTextId != null)
{
result.EndMessageFailureTextId = EndMessageFailureTextId;
}
if (EndMessageFailureDeadTextId != null)
{
result.EndMessageFailureDeadTextId = EndMessageFailureDeadTextId;
}
if (EndMessageFailureDetainedTextId != null)
{
result.EndMessageFailureDetainedTextId = EndMessageFailureDetainedTextId;
}
return result;
}
}
/*
public class Role
{
public string Job;
}
public readonly Dictionary<string, Role> Roles = new Dictionary<string, Role>();
*/
public readonly string Identifier;
public readonly string StartText;
public readonly string EndMessageSuccessText;
public readonly string EndMessageSuccessDeadText;
public readonly string EndMessageSuccessDetainedText;
public readonly string EndMessageFailureText;
public readonly string EndMessageFailureDeadText;
public readonly string EndMessageFailureDetainedText;
public readonly List<Objective> Objectives = new List<Objective>();
public Traitor.TraitorMission Instantiate()
{
return new Traitor.TraitorMission(
StartText ?? "TraitorMissionStartMessage",
EndMessageSuccessText ?? "TraitorObjectiveEndMessageSuccess",
EndMessageSuccessDeadText ?? "TraitorObjectiveEndMessageSuccessDead",
EndMessageSuccessDetainedText ?? "TraitorObjectiveEndMessageSuccessDetained",
EndMessageFailureText ?? "TraitorObjectiveEndMessageFailure",
EndMessageFailureDeadText ?? "TraitorObjectiveEndMessageFailureDead",
EndMessageFailureDetainedText ?? "TraitorObjectiveEndMessageFailureDetained",
Objectives.ConvertAll(objective => objective.Instantiate()).ToArray());
}
protected Goal LoadGoal(XElement goalRoot)
{
var goalType = goalRoot.GetAttributeString("type", "");
return new Goal(goalType, goalRoot);
}
protected Objective LoadObjective(XElement objectiveRoot)
{
var result = new Objective();
result.ShuffleGoalsCount = objectiveRoot.GetAttributeInt("shuffleGoalsCount", -1);
foreach (var element in objectiveRoot.Elements())
{
using (var checker = new AttributeChecker(element))
{
switch (element.Name.ToString().ToLowerInvariant())
{
case "infotext":
checker.Required("id");
result.InfoText = element.GetAttributeString("id", null);
break;
case "startmessage":
checker.Required("id");
result.StartMessageTextId = element.GetAttributeString("id", null);
break;
case "startmessageserver":
checker.Required("id");
result.StartMessageServerTextId = element.GetAttributeString("id", null);
break;
case "endmessagesuccess":
checker.Required("id");
result.EndMessageSuccessTextId = element.GetAttributeString("id", null);
break;
case "endmessagesuccessdead":
checker.Required("id");
result.EndMessageSuccessDeadTextId = element.GetAttributeString("id", null);
break;
case "endmessagesuccessdetained":
checker.Required("id");
result.EndMessageSuccessDetainedTextId = element.GetAttributeString("id", null);
break;
case "endmessagefailure":
checker.Required("id");
result.EndMessageFailureTextId = element.GetAttributeString("id", null);
break;
case "endmessagefailuredead":
checker.Required("id");
result.EndMessageFailureDeadTextId = element.GetAttributeString("id", null);
break;
case "endmessagefailuredetained":
checker.Required("id");
result.EndMessageFailureDetainedTextId = element.GetAttributeString("id", null);
break;
case "goal":
{
var goal = LoadGoal(element);
if (goal != null)
{
result.Goals.Add(goal);
}
break;
}
default:
GameServer.Log($"Unrecognized element \"{element.Name}\"under Objective.", ServerLog.MessageType.Error);
break;
}
}
}
return result;
}
public TraitorMissionPrefab(XElement missionRoot)
{
Identifier = missionRoot.GetAttributeString("identifier", null);
foreach (var element in missionRoot.Elements())
{
using (var checker = new AttributeChecker(element))
{
switch (element.Name.ToString().ToLowerInvariant())
{
case "startinfotext":
checker.Required("id");
StartText = element.GetAttributeString("id", null);
break;
case "endmessagesuccess":
checker.Required("id");
EndMessageSuccessText = element.GetAttributeString("id", null);
break;
case "endmessagesuccessdead":
checker.Required("id");
EndMessageSuccessDeadText = element.GetAttributeString("id", null);
break;
case "endmessagesuccessdetained":
checker.Required("id");
EndMessageSuccessDetainedText = element.GetAttributeString("id", null);
break;
case "endmessagefailure":
checker.Required("id");
EndMessageFailureText = element.GetAttributeString("id", null);
break;
case "endmessagefailuredead":
checker.Required("id");
EndMessageFailureDeadText = element.GetAttributeString("id", null);
break;
case "endmessagefailuredetained":
checker.Required("id");
EndMessageFailureDetainedText = element.GetAttributeString("id", null);
break;
case "objective":
{
var objective = LoadObjective(element);
if (objective != null)
{
Objectives.Add(objective);
}
break;
}
default:
GameServer.Log($"Unrecognized element \"{element.Name}\"under TraitorMission.", ServerLog.MessageType.Error);
break;
}
}
}
}
}
}