diff --git a/Barotrauma/BarotraumaClient/ClientCode.projitems b/Barotrauma/BarotraumaClient/ClientCode.projitems
index 8b57735b9..7c88d6ccd 100644
--- a/Barotrauma/BarotraumaClient/ClientCode.projitems
+++ b/Barotrauma/BarotraumaClient/ClientCode.projitems
@@ -40,6 +40,7 @@
+
@@ -165,6 +166,10 @@
+
+
+
+
diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj
index a4375f0b3..dced51971 100644
--- a/Barotrauma/BarotraumaClient/LinuxClient.csproj
+++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj
@@ -93,6 +93,9 @@
..\..\Libraries\NuGet\nVLC.3.0.0\lib\net40\nVLC.dll
+
+ ..\..\Libraries\NuGet\NVorbis.0.8.6\lib\net35\NVorbis.dll
+
..\..\Libraries\NuGet\OpenTK.3.0.1\lib\net20\OpenTK.dll
diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj
index d21001bd7..2018c3506 100644
--- a/Barotrauma/BarotraumaClient/MacClient.csproj
+++ b/Barotrauma/BarotraumaClient/MacClient.csproj
@@ -1,4 +1,4 @@
-
+
ReleaseMac
@@ -91,6 +91,9 @@
..\..\Libraries\NuGet\nVLC.3.0.0\lib\net40\nVLC.dll
+
+ ..\..\Libraries\NuGet\NVorbis.0.8.6\lib\net35\NVorbis.dll
+
..\..\Libraries\NuGet\OpenTK.3.0.1\lib\net20\OpenTK.dll
@@ -316,8 +319,8 @@
-
- Icon.bmp
+
+ Icon.bmp
diff --git a/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs b/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs
index 6509147bc..b55a2d5c1 100644
--- a/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs
+++ b/Barotrauma/BarotraumaClient/Properties/AssemblyInfo.cs
@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("0.9.200.0")]
-[assembly: AssemblyFileVersion("0.9.1.0")]
+[assembly: AssemblyVersion("0.9.2.1")]
+[assembly: AssemblyFileVersion("0.9.2.1")]
diff --git a/Barotrauma/BarotraumaClient/Source/Camera.cs b/Barotrauma/BarotraumaClient/Source/Camera.cs
index fe79671b4..29bc5fdc9 100644
--- a/Barotrauma/BarotraumaClient/Source/Camera.cs
+++ b/Barotrauma/BarotraumaClient/Source/Camera.cs
@@ -185,9 +185,9 @@ namespace Barotrauma
position += amount;
}
- public void ClientWrite(NetOutgoingMessage msg)
+ public void ClientWrite(IWriteMessage msg)
{
- if (Character.Controlled != null && !Character.Controlled.IsDead) return;
+ if (Character.Controlled != null && !Character.Controlled.IsDead) { return; }
msg.Write((byte)ClientNetObject.SPECTATING_POS);
msg.Write(position.X);
diff --git a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs
index 004c074a0..c28135e75 100644
--- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs
+++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs
@@ -138,7 +138,7 @@ namespace Barotrauma
brokenItemsCheckTimer = 1.0f;
foreach (Item item in Item.ItemList)
{
- if (!item.Repairables.Any(r => item.Condition < r.ShowRepairUIThreshold)) { continue; }
+ if (!item.Repairables.Any(r => item.ConditionPercentage < r.ShowRepairUIThreshold)) { continue; }
if (Submarine.VisibleEntities != null && !Submarine.VisibleEntities.Contains(item)) { continue; }
Vector2 diff = item.WorldPosition - character.WorldPosition;
@@ -210,7 +210,7 @@ namespace Barotrauma
GUI.DrawString(spriteBatch, textPos, focusName, nameColor, Color.Black * 0.7f, 2);
textPos.Y += offset.Y;
- if (character.FocusedCharacter.CanInventoryBeAccessed)
+ if (character.FocusedCharacter.CanBeDragged)
{
GUI.DrawString(spriteBatch, textPos, GetCachedHudText("GrabHint", GameMain.Config.KeyBind(InputType.Grab).ToString()),
Color.LightGreen, Color.Black, 2, GUI.SmallFont);
diff --git a/Barotrauma/BarotraumaClient/Source/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterInfo.cs
index f678e8b3a..1b51261ea 100644
--- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterInfo.cs
+++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterInfo.cs
@@ -1,5 +1,5 @@
using Barotrauma.Extensions;
-using Lidgren.Network;
+using Barotrauma.Networking;
using System;
using System.Linq;
using System.Collections.Generic;
@@ -53,7 +53,7 @@ namespace Barotrauma
if (personalityTrait != null && TextManager.Language == "English")
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform),
- TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), personalityTrait.Name), font: font);
+ TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), TextManager.Get("personalitytrait." + personalityTrait.Name.Replace(" ", ""))), font: font);
}
//spacing
@@ -220,7 +220,7 @@ namespace Barotrauma
}
- public static CharacterInfo ClientRead(string configPath, NetBuffer inc)
+ public static CharacterInfo ClientRead(string configPath, IReadMessage inc)
{
ushort infoID = inc.ReadUInt16();
string newName = inc.ReadString();
diff --git a/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs
index 5ad1a4063..a4927a4b7 100644
--- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs
+++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs
@@ -1,5 +1,4 @@
using Barotrauma.Networking;
-using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
@@ -22,12 +21,12 @@ namespace Barotrauma
}
//freeze AI characters if more than 1 seconds have passed since last update from the server
- if (lastRecvPositionUpdateTime < NetTime.Now - 1.0f)
+ if (lastRecvPositionUpdateTime < Lidgren.Network.NetTime.Now - 1.0f)
{
AnimController.Frozen = true;
memState.Clear();
//hide after 2 seconds
- if (lastRecvPositionUpdateTime < NetTime.Now - 2.0f)
+ if (lastRecvPositionUpdateTime < Lidgren.Network.NetTime.Now - 2.0f)
{
Enabled = false;
return;
@@ -99,22 +98,22 @@ namespace Barotrauma
}
}
- public virtual void ClientWrite(NetBuffer msg, object[] extraData = null)
+ public virtual void ClientWrite(IWriteMessage msg, object[] extraData = null)
{
if (extraData != null)
{
switch ((NetEntityEvent.Type)extraData[0])
{
case NetEntityEvent.Type.InventoryState:
- msg.WriteRangedInteger(0, 3, 0);
+ msg.WriteRangedIntegerDeprecated(0, 3, 0);
Inventory.ClientWrite(msg, extraData);
break;
case NetEntityEvent.Type.Treatment:
- msg.WriteRangedInteger(0, 3, 1);
+ msg.WriteRangedIntegerDeprecated(0, 3, 1);
msg.Write(AnimController.Anim == AnimController.Animation.CPR);
break;
case NetEntityEvent.Type.Status:
- msg.WriteRangedInteger(0, 3, 2);
+ msg.WriteRangedIntegerDeprecated(0, 3, 2);
break;
}
}
@@ -132,7 +131,7 @@ namespace Barotrauma
msg.Write(inputCount);
for (int i = 0; i < inputCount; i++)
{
- msg.WriteRangedInteger(0, (int)InputNetFlags.MaxVal, (int)memInput[i].states);
+ msg.WriteRangedIntegerDeprecated(0, (int)InputNetFlags.MaxVal, (int)memInput[i].states);
msg.Write(memInput[i].intAim);
if (memInput[i].states.HasFlag(InputNetFlags.Select) ||
memInput[i].states.HasFlag(InputNetFlags.Deselect) ||
@@ -147,14 +146,14 @@ namespace Barotrauma
msg.WritePadBits();
}
- public virtual void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
+ public virtual void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
switch (type)
{
case ServerNetObject.ENTITY_POSITION:
bool facingRight = AnimController.Dir > 0.0f;
- lastRecvPositionUpdateTime = (float)NetTime.Now;
+ lastRecvPositionUpdateTime = (float)Lidgren.Network.NetTime.Now;
AnimController.Frozen = false;
Enabled = true;
@@ -222,8 +221,8 @@ namespace Barotrauma
}
Vector2 pos = new Vector2(
- msg.ReadFloat(),
- msg.ReadFloat());
+ msg.ReadSingle(),
+ msg.ReadSingle());
float MaxVel = NetConfig.MaxPhysicsBodyVelocity;
Vector2 linearVelocity = new Vector2(
msg.ReadRangedSingle(-MaxVel, MaxVel, 12),
@@ -235,7 +234,7 @@ namespace Barotrauma
float? angularVelocity = null;
if (!fixedRotation)
{
- rotation = msg.ReadFloat();
+ rotation = msg.ReadSingle();
float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
angularVelocity = msg.ReadRangedSingle(-MaxAngularVel, MaxAngularVel, 8);
angularVelocity = NetConfig.Quantize(angularVelocity.Value, -MaxAngularVel, MaxAngularVel, 8);
@@ -336,7 +335,7 @@ namespace Barotrauma
}
}
- public static Character ReadSpawnData(NetBuffer inc, bool spawn = true)
+ public static Character ReadSpawnData(IReadMessage inc, bool spawn = true)
{
DebugConsole.NewMessage("READING CHARACTER SPAWN DATA", Color.Cyan);
@@ -347,7 +346,7 @@ namespace Barotrauma
string speciesName = inc.ReadString();
string seed = inc.ReadString();
- Vector2 position = new Vector2(inc.ReadFloat(), inc.ReadFloat());
+ Vector2 position = new Vector2(inc.ReadSingle(), inc.ReadSingle());
bool enabled = inc.ReadBoolean();
@@ -414,8 +413,17 @@ namespace Barotrauma
return character;
}
+
+ private void ReadTraitorStatus(IReadMessage msg)
+ {
+ IsTraitor = msg.ReadBoolean();
+ if (IsTraitor)
+ {
+ TraitorCurrentObjective = msg.ReadString();
+ }
+ }
- private void ReadStatus(NetBuffer msg)
+ private void ReadStatus(IReadMessage msg)
{
bool isDead = msg.ReadBoolean();
if (isDead)
@@ -450,7 +458,7 @@ namespace Barotrauma
else
{
if (IsDead) Revive();
-
+
CharacterHealth.ClientRead(msg);
}
}
diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/Source/Characters/Health/CharacterHealth.cs
index 131923312..110bc3c49 100644
--- a/Barotrauma/BarotraumaClient/Source/Characters/Health/CharacterHealth.cs
+++ b/Barotrauma/BarotraumaClient/Source/Characters/Health/CharacterHealth.cs
@@ -1,6 +1,5 @@
using Barotrauma.Items.Components;
using Barotrauma.Networking;
-using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
@@ -1375,7 +1374,7 @@ namespace Barotrauma
(int)(limbHealth.HighlightArea.Height * scale));
}
- public void ClientRead(NetBuffer inc)
+ public void ClientRead(IReadMessage inc)
{
List> newAfflictions = new List>();
diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs b/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs
index af335ceb0..082d577b7 100644
--- a/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs
+++ b/Barotrauma/BarotraumaClient/Source/Characters/Limb.cs
@@ -441,7 +441,7 @@ namespace Barotrauma
float herpesStrength = character.CharacterHealth.GetAfflictionStrength("spaceherpes");
if (herpesStrength > 0.0f)
{
- DrawWearable(HerpesSprite, depthStep, spriteBatch, color * (herpesStrength / 100.0f), spriteEffect);
+ DrawWearable(HerpesSprite, depthStep, spriteBatch, color * Math.Min(herpesStrength / 10.0f, 1.0f), spriteEffect);
depthStep += 0.000001f;
}
}
diff --git a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs
index 429bb51f2..bf118207c 100644
--- a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs
+++ b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs
@@ -199,6 +199,7 @@ namespace Barotrauma
return client.HasPermission(ClientPermissions.Kick);
case "ban":
case "banip":
+ case "banendpoint":
return client.HasPermission(ClientPermissions.Ban);
case "unban":
case "unbanip":
@@ -317,7 +318,13 @@ namespace Barotrauma
private static void AssignRelayToServer(string names, bool relay)
{
- commands.First(c => c.names.Intersect(names.Split('|')).Count() > 0).RelayToServer = relay;
+ Command command = commands.Find(c => c.names.Intersect(names.Split('|')).Count() > 0);
+ if (command == null)
+ {
+ DebugConsole.Log("Could not assign to relay to server: " + names);
+ return;
+ }
+ command.RelayToServer = relay;
}
private static void InitProjectSpecific()
@@ -358,16 +365,23 @@ namespace Barotrauma
}
}));
- commands.Add(new Command("startclient", "", (string[] args) =>
+ commands.Add(new Command("startlidgrenclient", "", (string[] args) =>
{
if (args.Length == 0) return;
if (GameMain.Client == null)
{
- GameMain.Client = new GameClient("Name", args[0]);
+ GameMain.Client = new GameClient("Name", args[0], 0);
}
}));
+ commands.Add(new Command("startsteamp2pclient", "", (string[] args) =>
+ {
+ if (GameMain.Client == null)
+ {
+ GameMain.Client = new GameClient("Name", null, 76561198977850505); //this is juan's alt account, feel free to abuse this one
+ }
+ }));
commands.Add(new Command("enablecheats", "enablecheats: Enables cheat commands and disables Steam achievements during this play session.", (string[] args) =>
{
@@ -465,10 +479,16 @@ namespace Barotrauma
commands.Add(new Command("clientlist", "", (string[] args) => { }));
AssignRelayToServer("clientlist", true);
+ commands.Add(new Command("say", "", (string[] args) => { }));
+ AssignRelayToServer("say", true);
+ commands.Add(new Command("msg", "", (string[] args) => { }));
+ AssignRelayToServer("msg", true);
commands.Add(new Command("setmaxplayers|maxplayers", "", (string[] args) => { }));
AssignRelayToServer("setmaxplayers", true);
commands.Add(new Command("setpassword|password", "", (string[] args) => { }));
AssignRelayToServer("setpassword", true);
+ commands.Add(new Command("traitorlist", "", (string[] args) => { }));
+ AssignRelayToServer("traitorlist", true);
AssignOnExecute("control", (string[] args) =>
{
@@ -551,6 +571,7 @@ namespace Barotrauma
if (args.Length < 3)
{
ThrowError("Not enough arguments provided! At least three required.");
+ return;
}
if (!byte.TryParse(args[0], out byte r))
{
@@ -952,7 +973,36 @@ namespace Barotrauma
}
}, isCheat: false));
+ commands.Add(new Command("setentityproperties", "setentityproperties [property name] [value]: Sets the value of some property on all selected items/structures in the sub editor.", (string[] args) =>
+ {
+ if (args.Length != 2 || Screen.Selected != GameMain.SubEditorScreen) { return; }
+ foreach (MapEntity me in MapEntity.SelectedList)
+ {
+ if (me is ISerializableEntity serializableEntity)
+ {
+ if (serializableEntity.SerializableProperties == null)
+ {
+ continue;
+ }
+ if (!serializableEntity.SerializableProperties.TryGetValue(args[0].ToLowerInvariant(), out SerializableProperty property))
+ {
+ NewMessage("Property \"" + args[0] + "\" not found in the entity \"" + me.ToString() + "\".", Color.Orange);
+ continue;
+ }
+ if (!property.TrySetValue(me, args[1]))
+ {
+ NewMessage("Failed to set the value of \"" + args[0] + "\" to \"" + args[1] + "\" on the entity \"" + me.ToString() + "\".", Color.Orange);
+ }
+ }
+ }
+ }));
+
#if DEBUG
+ commands.Add(new Command("printreceivertransfers", "", (string[] args) =>
+ {
+ GameMain.Client.PrintReceiverTransters();
+ }));
+
commands.Add(new Command("checkmissingloca", "", (string[] args) =>
{
foreach (MapEntityPrefab me in MapEntityPrefab.List)
@@ -1201,6 +1251,8 @@ namespace Barotrauma
{
GameMain.Config.MusicVolume = 0.5f;
GameMain.Config.SoundVolume = 0.5f;
+ GameMain.Config.DynamicRangeCompressionEnabled = true;
+ GameMain.Config.VoipAttenuationEnabled = true;
NewMessage("Music and sound volume set to 0.5", Color.Green);
GameMain.Config.GraphicsWidth = 0;
@@ -1416,11 +1468,11 @@ namespace Barotrauma
);
AssignOnClientExecute(
- "banip",
+ "banendpoint|banip",
(string[] args) =>
{
if (GameMain.Client == 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) =>
{
@@ -1436,7 +1488,7 @@ namespace Barotrauma
}
GameMain.Client.SendConsoleCommand(
- "banip " +
+ "banendpoint " +
args[0] + " " +
(banDuration.HasValue ? banDuration.Value.TotalSeconds.ToString() : "0") + " " +
reason);
@@ -1779,6 +1831,21 @@ namespace Barotrauma
character.ReloadHead(id, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
}
}, isCheat: true));
+
+ commands.Add(new Command("spawnsub", "spawnsub [subname]: Spawn a submarine at the position of the cursor", (string[] args) =>
+ {
+ try
+ {
+ Submarine spawnedSub = Submarine.Load(args[0], false);
+ spawnedSub.SetPosition(GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition));
+ }
+ catch (Exception e)
+ {
+ string errorMsg = "Failed to spawn a submarine. Arguments: \"" + string.Join(" ", args) + "\".";
+ ThrowError(errorMsg, e);
+ GameAnalyticsManager.AddErrorEventOnce("DebugConsole.SpawnSubmarine:Error", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg + '\n' + e.Message + '\n' + e.StackTrace);
+ }
+ }, isCheat: true));
}
private static void ReloadWearables(Character character, int variant = 0)
diff --git a/Barotrauma/BarotraumaClient/Source/Events/Missions/Mission.cs b/Barotrauma/BarotraumaClient/Source/Events/Missions/Mission.cs
index 0174c2246..1ca52a559 100644
--- a/Barotrauma/BarotraumaClient/Source/Events/Missions/Mission.cs
+++ b/Barotrauma/BarotraumaClient/Source/Events/Missions/Mission.cs
@@ -4,16 +4,13 @@ namespace Barotrauma
{
partial class Mission
{
- public void ShowMessage(int index)
+ 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] : "";
- //TODO: reimplement
- //GameServer.Log(TextManager.Get("MissionInfo") + ": " + header + " - " + message, ServerLog.MessageType.ServerMessage);
-
new GUIMessageBox(header, message);
}
}
diff --git a/Barotrauma/BarotraumaClient/Source/Events/Missions/MissionMode.cs b/Barotrauma/BarotraumaClient/Source/Events/Missions/MissionMode.cs
index bdff63c82..759e34247 100644
--- a/Barotrauma/BarotraumaClient/Source/Events/Missions/MissionMode.cs
+++ b/Barotrauma/BarotraumaClient/Source/Events/Missions/MissionMode.cs
@@ -5,19 +5,14 @@ namespace Barotrauma
{
partial class MissionMode : GameMode
{
- public override void MsgBox()
+ public override void ShowStartMessage()
{
if (mission == null) return;
- var missionMsg = new GUIMessageBox(mission.Name, mission.Description, new Vector2(0.25f, 0.0f), new Point(400, 200))
+ new GUIMessageBox(mission.Name, mission.Description, new Vector2(0.25f, 0.0f), new Point(400, 200))
{
UserData = "missionstartmessage"
};
-
-#if SERVER
- Networking.GameServer.Log(TextManager.Get("Mission") + ": " + mission.Name, Networking.ServerLog.MessageType.ServerMessage);
- Networking.GameServer.Log(mission.Description, Networking.ServerLog.MessageType.ServerMessage);
-#endif
}
}
}
diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs
index 4585a78d4..b9a37894c 100644
--- a/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs
+++ b/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs
@@ -310,6 +310,16 @@ namespace Barotrauma
"Sounds (Ctrl+S to hide): ", Color.White, Color.Black * 0.5f, 0, SmallFont);
y += 15;
+ DrawString(spriteBatch, new Vector2(500, y),
+ "Current playback amplitude: " + GameMain.SoundManager.PlaybackAmplitude.ToString(), Color.White, Color.Black * 0.5f, 0, SmallFont);
+
+ y += 15;
+
+ DrawString(spriteBatch, new Vector2(500, y),
+ "Compressed dynamic range gain: " + GameMain.SoundManager.CompressionDynamicRangeGain.ToString(), Color.White, Color.Black * 0.5f, 0, SmallFont);
+
+ y += 15;
+
DrawString(spriteBatch, new Vector2(500, y),
"Loaded sounds: " + GameMain.SoundManager.LoadedSoundCount + " (" + GameMain.SoundManager.UniqueLoadedSoundCount + " unique)", Color.White, Color.Black * 0.5f, 0, SmallFont);
y += 15;
@@ -343,18 +353,21 @@ namespace Barotrauma
soundStr += " (looping)";
clr = Color.Yellow;
}
-
if (playingSoundChannel.IsStream)
{
soundStr += " (streaming)";
clr = Color.Lime;
}
-
if (!playingSoundChannel.IsPlaying)
{
soundStr += " (stopped)";
clr *= 0.5f;
}
+ else if (playingSoundChannel.Muffled)
+ {
+ soundStr += " (muffled)";
+ clr = Color.Lerp(clr, Color.LightGray, 0.5f);
+ }
}
DrawString(spriteBatch, new Vector2(500, y), soundStr, clr, Color.Black * 0.5f, 0, SmallFont);
@@ -640,6 +653,12 @@ namespace Barotrauma
return MouseOn;
}
+ public static bool HasSizeChanged(Point referenceResolution, float referenceUIScale, float referenceHUDScale)
+ {
+ return GameMain.GraphicsWidth != referenceResolution.X || GameMain.GraphicsHeight != referenceResolution.Y ||
+ referenceUIScale != Inventory.UIScale || referenceHUDScale != Scale;
+ }
+
public static void Update(float deltaTime)
{
HandlePersistingElements(deltaTime);
@@ -1461,6 +1480,9 @@ namespace Barotrauma
if (pauseMenuOpen)
{
+ Inventory.draggingItem = null;
+ Inventory.DraggingInventory = null;
+
PauseMenu = new GUIFrame(new RectTransform(Vector2.One, Canvas), style: null, color: Color.Black * 0.5f);
var pauseMenuInner = new GUIFrame(new RectTransform(new Vector2(0.13f, 0.35f), PauseMenu.RectTransform, Anchor.Center) { MinSize = new Point(200, 300) });
diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUIComponent.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUIComponent.cs
index 265331a13..8bfa3c290 100644
--- a/Barotrauma/BarotraumaClient/Source/GUI/GUIComponent.cs
+++ b/Barotrauma/BarotraumaClient/Source/GUI/GUIComponent.cs
@@ -67,6 +67,21 @@ namespace Barotrauma
}
// TODO: refactor?
+
+ public GUIComponent FindChild(Func predicate, bool recursive = false)
+ {
+ var matchingChild = Children.FirstOrDefault(predicate);
+ if (recursive && matchingChild == null)
+ {
+ foreach (GUIComponent child in Children)
+ {
+ matchingChild = child.FindChild(predicate, recursive);
+ if (matchingChild != null) return matchingChild;
+ }
+ }
+
+ return matchingChild;
+ }
public GUIComponent FindChild(object userData, bool recursive = false)
{
var matchingChild = Children.FirstOrDefault(c => c.userData == userData);
diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUIDropDown.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUIDropDown.cs
index c2f8351bc..4ff2c0079 100644
--- a/Barotrauma/BarotraumaClient/Source/GUI/GUIDropDown.cs
+++ b/Barotrauma/BarotraumaClient/Source/GUI/GUIDropDown.cs
@@ -1,11 +1,13 @@
-using Microsoft.Xna.Framework;
+using EventInput;
+using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
- public class GUIDropDown : GUIComponent
+ public class GUIDropDown : GUIComponent, IKeyboardSubscriber
{
public delegate bool OnSelectedHandler(GUIComponent selected, object obj = null);
public OnSelectedHandler OnSelected;
@@ -42,10 +44,22 @@ namespace Barotrauma
set { button.Enabled = value; }
}
- public GUIComponent Selected
+ public GUIComponent SelectedComponent
{
get { return listBox.SelectedComponent; }
}
+
+ public bool Selected
+ {
+ get
+ {
+ return Dropped;
+ }
+ set
+ {
+ Dropped = value;
+ }
+ }
public GUIListBox ListBox
{
@@ -69,6 +83,30 @@ namespace Barotrauma
}
}
+ public void ReceiveTextInput(char inputChar)
+ {
+ GUI.KeyboardDispatcher.Subscriber = null;
+ }
+ public void ReceiveTextInput(string text) { }
+ public void ReceiveCommandInput(char command) { }
+
+ public void ReceiveSpecialInput(Keys key)
+ {
+ switch (key)
+ {
+ case Keys.Up:
+ case Keys.Down:
+ listBox.ReceiveSpecialInput(key);
+ GUI.KeyboardDispatcher.Subscriber = this;
+ break;
+ case Keys.Enter:
+ case Keys.Space:
+ case Keys.Escape:
+ GUI.KeyboardDispatcher.Subscriber = null;
+ break;
+ }
+ }
+
private List
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaServer/Source/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaServer/Source/Characters/CharacterInfo.cs
index 8a75d42e0..467a4fe13 100644
--- a/Barotrauma/BarotraumaServer/Source/Characters/CharacterInfo.cs
+++ b/Barotrauma/BarotraumaServer/Source/Characters/CharacterInfo.cs
@@ -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);
diff --git a/Barotrauma/BarotraumaServer/Source/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaServer/Source/Characters/CharacterNetworking.cs
index 9cd263d20..bdf17c1b5 100644
--- a/Barotrauma/BarotraumaServer/Source/Characters/CharacterNetworking.cs
+++ b/Barotrauma/BarotraumaServer/Source/Characters/CharacterNetworking.cs
@@ -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));
}
}
-}
\ No newline at end of file
+}
diff --git a/Barotrauma/BarotraumaServer/Source/DebugConsole.cs b/Barotrauma/BarotraumaServer/Source/DebugConsole.cs
index b4f62239e..c68552dc6 100644
--- a/Barotrauma/BarotraumaServer/Source/DebugConsole.cs
+++ b/Barotrauma/BarotraumaServer/Source/DebugConsole.cs
@@ -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 QueuedCommands = new List();
+ 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)
{
diff --git a/Barotrauma/BarotraumaServer/Source/Events/Missions/Mission.cs b/Barotrauma/BarotraumaServer/Source/Events/Missions/Mission.cs
new file mode 100644
index 000000000..5922fb30c
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/Source/Events/Missions/Mission.cs
@@ -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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaServer/Source/GameMain.cs b/Barotrauma/BarotraumaServer/Source/GameMain.cs
index 740581cda..e00a75a9e 100644
--- a/Barotrauma/BarotraumaServer/Source/GameMain.cs
+++ b/Barotrauma/BarotraumaServer/Source/GameMain.cs
@@ -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);
diff --git a/Barotrauma/BarotraumaServer/Source/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaServer/Source/GameSession/GameModes/CampaignMode.cs
new file mode 100644
index 000000000..90e65ad86
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/Source/GameSession/GameModes/CampaignMode.cs
@@ -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);
+ }
+ }
+}
diff --git a/Barotrauma/BarotraumaServer/Source/GameSession/GameModes/CharacterCampaignData.cs b/Barotrauma/BarotraumaServer/Source/GameSession/GameModes/CharacterCampaignData.cs
index de82f0127..960917301 100644
--- a/Barotrauma/BarotraumaServer/Source/GameSession/GameModes/CharacterCampaignData.cs
+++ b/Barotrauma/BarotraumaServer/Source/GameSession/GameModes/CharacterCampaignData.cs
@@ -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;
}
}
diff --git a/Barotrauma/BarotraumaServer/Source/GameSession/GameModes/MissionMode.cs b/Barotrauma/BarotraumaServer/Source/GameSession/GameModes/MissionMode.cs
new file mode 100644
index 000000000..81fd38fb4
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/Source/GameSession/GameModes/MissionMode.cs
@@ -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);
+ }
+ }
+}
diff --git a/Barotrauma/BarotraumaServer/Source/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaServer/Source/GameSession/GameModes/MultiPlayerCampaign.cs
index 35fbb7ea0..ca74513f9 100644
--- a/Barotrauma/BarotraumaServer/Source/GameSession/GameModes/MultiPlayerCampaign.cs
+++ b/Barotrauma/BarotraumaServer/Source/GameSession/GameModes/MultiPlayerCampaign.cs
@@ -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();
diff --git a/Barotrauma/BarotraumaServer/Source/Items/Components/Door.cs b/Barotrauma/BarotraumaServer/Source/Items/Components/Door.cs
index 0dd7a2a65..bfa85f702 100644
--- a/Barotrauma/BarotraumaServer/Source/Items/Components/Door.cs
+++ b/Barotrauma/BarotraumaServer/Source/Items/Components/Door.cs
@@ -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);
diff --git a/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Deconstructor.cs
index 3d3aa6f4b..49db8e5ba 100644
--- a/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Deconstructor.cs
+++ b/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Deconstructor.cs
@@ -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);
diff --git a/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Engine.cs b/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Engine.cs
index da691fd3a..a5c5697c7 100644
--- a/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Engine.cs
+++ b/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Engine.cs
@@ -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;
diff --git a/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Fabricator.cs
index 9203d533a..f5f6f4bdb 100644
--- a/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Fabricator.cs
+++ b/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Fabricator.cs
@@ -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);
}
diff --git a/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Pump.cs
index abc219b61..f5f114a84 100644
--- a/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Pump.cs
+++ b/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Pump.cs
@@ -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);
}
}
diff --git a/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Reactor.cs
index 5d6439226..1fdfb5d1f 100644
--- a/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Reactor.cs
+++ b/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Reactor.cs
@@ -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);
diff --git a/Barotrauma/BarotraumaServer/Source/Items/Components/Power/PowerContainer.cs b/Barotrauma/BarotraumaServer/Source/Items/Components/Power/PowerContainer.cs
index 5815f7929..76fbd282e 100644
--- a/Barotrauma/BarotraumaServer/Source/Items/Components/Power/PowerContainer.cs
+++ b/Barotrauma/BarotraumaServer/Source/Items/Components/Power/PowerContainer.cs
@@ -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);
diff --git a/Barotrauma/BarotraumaServer/Source/Items/Components/Repairable.cs b/Barotrauma/BarotraumaServer/Source/Items/Components/Repairable.cs
index e0421a39f..443638156 100644
--- a/Barotrauma/BarotraumaServer/Source/Items/Components/Repairable.cs
+++ b/Barotrauma/BarotraumaServer/Source/Items/Components/Repairable.cs
@@ -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);
}
}
}
diff --git a/Barotrauma/BarotraumaServer/Source/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaServer/Source/Items/Components/Signal/ConnectionPanel.cs
index cb6f58536..75655d62e 100644
--- a/Barotrauma/BarotraumaServer/Source/Items/Components/Signal/ConnectionPanel.cs
+++ b/Barotrauma/BarotraumaServer/Source/Items/Components/Signal/ConnectionPanel.cs
@@ -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[] wires = new List[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);
}
}
-}
\ No newline at end of file
+}
diff --git a/Barotrauma/BarotraumaServer/Source/Items/Components/Signal/CustomInterface.cs b/Barotrauma/BarotraumaServer/Source/Items/Components/Signal/CustomInterface.cs
index 0d963239c..7786d2621 100644
--- a/Barotrauma/BarotraumaServer/Source/Items/Components/Signal/CustomInterface.cs
+++ b/Barotrauma/BarotraumaServer/Source/Items/Components/Signal/CustomInterface.cs
@@ -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++)
diff --git a/Barotrauma/BarotraumaServer/Source/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaServer/Source/Items/Components/Signal/Wire.cs
index 192b3eb90..92730a790 100644
--- a/Barotrauma/BarotraumaServer/Source/Items/Components/Signal/Wire.cs
+++ b/Barotrauma/BarotraumaServer/Source/Items/Components/Signal/Wire.cs
@@ -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);
diff --git a/Barotrauma/BarotraumaServer/Source/Items/Inventory.cs b/Barotrauma/BarotraumaServer/Source/Items/Inventory.cs
index dde6cbbad..af6943965 100644
--- a/Barotrauma/BarotraumaServer/Source/Items/Inventory.cs
+++ b/Barotrauma/BarotraumaServer/Source/Items/Inventory.cs
@@ -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- prevItems = new List
- (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);
}
diff --git a/Barotrauma/BarotraumaServer/Source/Items/Item.cs b/Barotrauma/BarotraumaServer/Source/Items/Item.cs
index 2092cba17..2c52c7bdc 100644
--- a/Barotrauma/BarotraumaServer/Source/Items/Item.cs
+++ b/Barotrauma/BarotraumaServer/Source/Items/Item.cs
@@ -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();
}
diff --git a/Barotrauma/BarotraumaServer/Source/Map/Hull.cs b/Barotrauma/BarotraumaServer/Source/Map/Hull.cs
index b94b26693..8f802cd06 100644
--- a/Barotrauma/BarotraumaServer/Source/Map/Hull.cs
+++ b/Barotrauma/BarotraumaServer/Source/Map/Hull.cs
@@ -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;
diff --git a/Barotrauma/BarotraumaServer/Source/Map/Structure.cs b/Barotrauma/BarotraumaServer/Source/Map/Structure.cs
index bdf6d9748..4cdc37634 100644
--- a/Barotrauma/BarotraumaServer/Source/Map/Structure.cs
+++ b/Barotrauma/BarotraumaServer/Source/Map/Structure.cs
@@ -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);
diff --git a/Barotrauma/BarotraumaServer/Source/Map/Submarine.cs b/Barotrauma/BarotraumaServer/Source/Map/Submarine.cs
index f96459998..4acc60be1 100644
--- a/Barotrauma/BarotraumaServer/Source/Map/Submarine.cs
+++ b/Barotrauma/BarotraumaServer/Source/Map/Submarine.cs
@@ -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();
}
}
diff --git a/Barotrauma/BarotraumaServer/Source/Networking/BanList.cs b/Barotrauma/BarotraumaServer/Source/Networking/BanList.cs
index 65551cb8a..35a48bc46 100644
--- a/Barotrauma/BarotraumaServer/Source/Networking/BanList.cs
+++ b/Barotrauma/BarotraumaServer/Source/Networking/BanList.cs
@@ -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
diff --git a/Barotrauma/BarotraumaServer/Source/Networking/ChatMessage.cs b/Barotrauma/BarotraumaServer/Source/Networking/ChatMessage.cs
index a063fb756..84044b2af 100644
--- a/Barotrauma/BarotraumaServer/Source/Networking/ChatMessage.cs
+++ b/Barotrauma/BarotraumaServer/Source/Networking/ChatMessage.cs
@@ -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
}
}
}
-}
\ No newline at end of file
+}
diff --git a/Barotrauma/BarotraumaServer/Source/Networking/Client.cs b/Barotrauma/BarotraumaServer/Source/Networking/Client.cs
index 8a91ceaef..f4f30b70b 100644
--- a/Barotrauma/BarotraumaServer/Source/Networking/Client.cs
+++ b/Barotrauma/BarotraumaServer/Source/Networking/Client.cs
@@ -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 permittedConsoleCommands)
diff --git a/Barotrauma/BarotraumaServer/Source/Networking/EntitySpawner.cs b/Barotrauma/BarotraumaServer/Source/Networking/EntitySpawner.cs
index 45eb98b16..07997e710 100644
--- a/Barotrauma/BarotraumaServer/Source/Networking/EntitySpawner.cs
+++ b/Barotrauma/BarotraumaServer/Source/Networking/EntitySpawner.cs
@@ -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;
diff --git a/Barotrauma/BarotraumaServer/Source/Networking/FileTransfer/FileSender.cs b/Barotrauma/BarotraumaServer/Source/Networking/FileTransfer/FileSender.cs
index 88ad64ba2..d205980e9 100644
--- a/Barotrauma/BarotraumaServer/Source/Networking/FileTransfer/FileSender.cs
+++ b/Barotrauma/BarotraumaServer/Source/Networking/FileTransfer/FileSender.cs
@@ -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 activeTransfers;
+ private readonly List activeTransfers;
- private int chunkLen;
+ private readonly int chunkLen;
- private NetPeer peer;
+ private readonly ServerPeer peer;
public List 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();
}
- 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(campaign.LastSaveID, (float)NetTime.Now);
+ client.LastCampaignSaveSendTime = new Pair(campaign.LastSaveID, (float)Lidgren.Network.NetTime.Now);
}
}
break;
diff --git a/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs
index 9bf787280..49b25cc3d 100644
--- a/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs
+++ b/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs
@@ -1,4 +1,5 @@
-using Barotrauma.Items.Components;
+#define ALLOW_BOT_TRAITORS
+using Barotrauma.Items.Components;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using RestSharp;
@@ -11,6 +12,7 @@ using System.IO.Compression;
using System.IO;
using Barotrauma.Steam;
using System.Xml.Linq;
+using System.Threading;
namespace Barotrauma.Networking
{
@@ -35,7 +37,8 @@ namespace Barotrauma.Networking
//is the server running
private bool started;
- private NetServer server;
+ private ServerPeer serverPeer;
+ public ServerPeer ServerPeer { get { return serverPeer; } }
private DateTime refreshMasterTimer;
private TimeSpan refreshMasterInterval = new TimeSpan(0, 0, 60);
@@ -64,6 +67,15 @@ namespace Barotrauma.Networking
private ServerEntityEventManager entityEventManager;
private FileSender fileSender;
+#if DEBUG
+ public void PrintSenderTransters()
+ {
+ foreach (var transfer in fileSender.ActiveTransfers)
+ {
+ DebugConsole.NewMessage(transfer.FileName + " " + transfer.Progress.ToString());
+ }
+ }
+#endif
public override List ConnectedClients
{
@@ -84,16 +96,16 @@ namespace Barotrauma.Networking
get { return updateInterval; }
}
+ public int Port => serverSettings?.Port ?? 0;
+
//only used when connected to steam
- public int QueryPort
- {
- get;
- set;
- }
+ public int QueryPort => serverSettings?.QueryPort ?? 0;
- public NetConnection OwnerConnection { get; private set; }
+ public NetworkConnection OwnerConnection { get; private set; }
+ private int? ownerKey;
+ private UInt64? ownerSteamId;
- public GameServer(string name, int port, int queryPort = 0, bool isPublic = false, string password = "", bool attemptUPnP = false, int maxPlayers = 10, int ownerKey = 0)
+ public GameServer(string name, int port, int queryPort = 0, bool isPublic = false, string password = "", bool attemptUPnP = false, int maxPlayers = 10, int? ownKey = null, UInt64? steamId = null)
{
name = name.Replace(":", "");
name = name.Replace(";", "");
@@ -104,35 +116,18 @@ namespace Barotrauma.Networking
this.name = name;
- this.ownerKey = ownerKey;
-
LastClientListUpdateID = 0;
- NetPeerConfiguration = new NetPeerConfiguration("barotrauma");
- NetPeerConfiguration.Port = port;
- Port = port;
- QueryPort = queryPort;
-
- if (attemptUPnP)
- {
- NetPeerConfiguration.EnableUPnP = true;
- }
-
serverSettings = new ServerSettings(this, name, port, queryPort, maxPlayers, isPublic, attemptUPnP);
if (!string.IsNullOrEmpty(password))
{
serverSettings.SetPassword(password);
}
-
- NetPeerConfiguration.MaximumConnections = maxPlayers * 2; //double the lidgren connections for unauthenticated players
- NetPeerConfiguration.DisableMessageType(NetIncomingMessageType.DebugMessage |
- NetIncomingMessageType.WarningMessage | NetIncomingMessageType.Receipt |
- NetIncomingMessageType.ErrorMessage | NetIncomingMessageType.Error |
- NetIncomingMessageType.UnconnectedData);
+ ownerKey = ownKey;
+
+ ownerSteamId = steamId;
- NetPeerConfiguration.EnableMessageType(NetIncomingMessageType.ConnectionApproval);
-
entityEventManager = new ServerEntityEventManager(this);
CoroutineManager.StartCoroutine(StartServer(isPublic));
@@ -144,16 +139,30 @@ namespace Barotrauma.Networking
try
{
Log("Starting the server...", ServerLog.MessageType.ServerMessage);
- server = new NetServer(NetPeerConfiguration);
- NetPeer = server;
+ if (!ownerSteamId.HasValue || ownerSteamId.Value == 0)
+ {
+ Log("Using Lidgren networking", ServerLog.MessageType.ServerMessage);
+ serverPeer = new LidgrenServerPeer(ownerKey, serverSettings);
+ }
+ else
+ {
+ Log("Using SteamP2P", ServerLog.MessageType.ServerMessage);
+ serverPeer = new SteamP2PServerPeer(ownerSteamId.Value, serverSettings);
+ }
- fileSender = new FileSender(this);
+ serverPeer.OnInitializationComplete = OnInitializationComplete;
+ serverPeer.OnMessageReceived = ReadDataMessage;
+ serverPeer.OnDisconnect = OnClientDisconnect;
+ serverPeer.OnShutdown = GameMain.Instance.CloseServer;
+ serverPeer.OnOwnerDetermined = OnOwnerDetermined;
+
+ fileSender = new FileSender(serverPeer, MsgConstants.MTU);
fileSender.OnEnded += FileTransferChanged;
fileSender.OnStarted += FileTransferChanged;
- server.Start();
+ serverPeer.Start();
- VoipServer = new VoipServer(server);
+ VoipServer = new VoipServer(serverPeer);
}
catch (Exception e)
{
@@ -166,33 +175,24 @@ namespace Barotrauma.Networking
if (error)
{
- if (server != null) server.Shutdown("Error while starting the server");
+ if (serverPeer != null) serverPeer.Close("Error while starting the server");
Environment.Exit(-1);
yield return CoroutineStatus.Success;
}
- if (NetPeerConfiguration.EnableUPnP)
- {
- InitUPnP();
- //DateTime upnpTimeout = DateTime.Now + new TimeSpan(0,0,5);
- while (DiscoveringUPnP())// && upnpTimeout>DateTime.Now)
+ if (serverPeer is LidgrenServerPeer)
+ {
+ if (SteamManager.USE_STEAM)
{
- yield return null;
+ registeredToMaster = SteamManager.CreateServer(this, isPublic);
+ }
+ if (isPublic && !GameMain.Config.UseSteamMatchmaking)
+ {
+ CoroutineManager.StartCoroutine(RegisterToMasterServer());
}
-
- FinishUPnP();
- }
-
- if (SteamManager.USE_STEAM)
- {
- registeredToMaster = SteamManager.CreateServer(this, isPublic);
- }
- if (isPublic && !GameMain.Config.UseSteamMatchmaking)
- {
- CoroutineManager.StartCoroutine(RegisterToMasterServer());
}
TickRate = serverSettings.TickRate;
@@ -208,6 +208,106 @@ namespace Barotrauma.Networking
yield return CoroutineStatus.Success;
}
+ private void OnOwnerDetermined(NetworkConnection connection)
+ {
+ OwnerConnection = connection;
+
+ var ownerClient = ConnectedClients.Find(c => c.Connection == connection);
+ if (ownerClient == null)
+ {
+ DebugConsole.ThrowError("Owner client not found! Can't set permissions");
+ return;
+ }
+ ownerClient.SetPermissions(ClientPermissions.All, DebugConsole.Commands);
+ UpdateClientPermissions(ownerClient);
+ }
+
+ public void NotifyCrash()
+ {
+ var tempList = ConnectedClients.Where(c => c.Connection != OwnerConnection).ToList();
+ foreach (var c in tempList)
+ {
+ DisconnectClient(c.Connection, DisconnectReason.ServerCrashed.ToString(), DisconnectReason.ServerCrashed.ToString());
+ }
+ if (OwnerConnection != null)
+ {
+ var conn = OwnerConnection; OwnerConnection = null;
+ DisconnectClient(conn, DisconnectReason.ServerCrashed.ToString(), DisconnectReason.ServerCrashed.ToString());
+ }
+ Thread.Sleep(500);
+ }
+
+ private void OnInitializationComplete(NetworkConnection connection)
+ {
+ string clName = connection.Name;
+ Client newClient = new Client(clName, GetNewClientID());
+ newClient.InitClientSync();
+ newClient.Connection = connection;
+ newClient.SteamID = connection.SteamID;
+ ConnectedClients.Add(newClient);
+
+ var previousPlayer = previousPlayers.Find(p => p.MatchesClient(newClient));
+ if (previousPlayer != null)
+ {
+ newClient.Karma = previousPlayer.Karma;
+ newClient.KarmaKickCount = previousPlayer.KarmaKickCount;
+ 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.EndpointMatches(cp.EndPoint));
+
+ 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());
+ }
+ }
+ }
+
+ private void OnClientDisconnect(NetworkConnection connection, string disconnectMsg)
+ {
+ Client connectedClient = connectedClients.Find(c => c.Connection == connection);
+
+ DisconnectClient(connectedClient, reason: disconnectMsg);
+ }
+
private IEnumerable RegisterToMasterServer()
{
if (restClient == null)
@@ -339,18 +439,7 @@ namespace Barotrauma.Networking
if (!started) return;
base.Update(deltaTime);
-
- foreach (UnauthenticatedClient unauthClient in unauthenticatedClients)
- {
- unauthClient.AuthTimer -= deltaTime;
- if (unauthClient.AuthTimer <= 0.0f)
- {
- unauthClient.Connection.Disconnect("Connection timed out");
- }
- }
-
- unauthenticatedClients.RemoveAll(uc => uc.AuthTimer <= 0.0f);
-
+
fileSender.Update(deltaTime);
KarmaManager.UpdateClients(ConnectedClients, deltaTime);
@@ -382,7 +471,7 @@ namespace Barotrauma.Networking
Client owner = connectedClients.Find(c =>
c.InGame && !c.NeedsMidRoundSync &&
c.Name == character.OwnerClientName &&
- c.IPMatches(character.OwnerClientIP));
+ c.EndpointMatches(character.OwnerClientEndPoint));
if (owner != null && (!serverSettings.AllowSpectating || !owner.SpectateOnly))
{
@@ -390,6 +479,11 @@ namespace Barotrauma.Networking
}
}
+ if (TraitorManager != null)
+ {
+ TraitorManager.Update(deltaTime);
+ }
+
bool isCrewDead =
connectedClients.All(c => c.Character == null || c.Character.IsDead || c.Character.IsUnconscious);
@@ -473,9 +567,7 @@ namespace Barotrauma.Networking
initiatedStartGame = false;
}
}
- else if (Screen.Selected == GameMain.NetLobbyScreen &&
- !gameStarted &&
- !initiatedStartGame)
+ else if (Screen.Selected == GameMain.NetLobbyScreen && !gameStarted && !initiatedStartGame)
{
if (serverSettings.AutoRestart)
{
@@ -545,96 +637,12 @@ namespace Barotrauma.Networking
KickClient(c, "DisconnectMessage.AFK");
}
- NetIncomingMessage inc = null;
- while ((inc = server.ReadMessage()) != null)
- {
- try
- {
- switch (inc.MessageType)
- {
- case NetIncomingMessageType.Data:
- ReadDataMessage(inc);
- break;
- case NetIncomingMessageType.StatusChanged:
- switch (inc.SenderConnection.Status)
- {
- case NetConnectionStatus.Disconnected:
- var connectedClient = connectedClients.Find(c => c.Connection == inc.SenderConnection);
- /*if (connectedClient != null && !disconnectedClients.Contains(connectedClient))
- {
- connectedClient.deleteDisconnectedTimer = NetConfig.DeleteDisconnectedTime;
- disconnectedClients.Add(connectedClient);
- }
- */
- if (connectedClient != null)
- {
- DisconnectClient(inc.SenderConnection, $"ServerMessage.HasDisconnected~[client]={connectedClient.Name}");
- }
- else
- {
- DisconnectClient(inc.SenderConnection, string.Empty);
- }
-
- break;
- }
- break;
- case NetIncomingMessageType.ConnectionApproval:
- var msgContent = inc.SenderConnection.RemoteHailMessage ?? inc;
- ClientPacketHeader packetHeader = (ClientPacketHeader)msgContent.ReadByte();
- if (packetHeader == ClientPacketHeader.REQUEST_AUTH ||
- packetHeader == ClientPacketHeader.REQUEST_STEAMAUTH)
- {
- HandleOwnership(msgContent, inc.SenderConnection);
- }
-
- DebugConsole.Log(packetHeader.ToString());
- if (inc.SenderConnection != OwnerConnection && serverSettings.BanList.IsBanned(inc.SenderEndPoint.Address, 0))
- {
- inc.SenderConnection.Deny(DisconnectReason.Banned.ToString());
- }
- else if (ConnectedClients.Count >= serverSettings.MaxPlayers)
- {
- inc.SenderConnection.Deny(DisconnectReason.ServerFull.ToString());
- }
- else
- {
- if (packetHeader == ClientPacketHeader.REQUEST_AUTH)
- {
- inc.SenderConnection.Approve();
- HandleClientAuthRequest(inc.SenderConnection);
- }
- else if (packetHeader == ClientPacketHeader.REQUEST_STEAMAUTH)
- {
- ReadClientSteamAuthRequest(msgContent, inc.SenderConnection, out ulong clientSteamID);
- if (inc.SenderConnection != OwnerConnection && serverSettings.BanList.IsBanned(null, clientSteamID))
- {
- inc.SenderConnection.Deny(DisconnectReason.Banned.ToString());
- }
- else
- {
- inc.SenderConnection.Approve();
- }
- }
- }
- break;
- }
- }
-
- catch (Exception e)
- {
- string errorMsg = "Server failed to read an incoming message. {" + e + "}\n" + e.StackTrace;
- GameAnalyticsManager.AddErrorEventOnce("GameServer.Update:ClientReadException" + e.TargetSite.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
- if (GameSettings.VerboseLogging)
- {
- DebugConsole.ThrowError(errorMsg);
- }
- }
- }
+ serverPeer.Update(deltaTime);
// if update interval has passed
if (updateTimer < DateTime.Now)
{
- if (server.ConnectionsCount > 0)
+ if (ConnectedClients.Count > 0)
{
foreach (Client c in ConnectedClients)
{
@@ -689,29 +697,13 @@ namespace Barotrauma.Networking
}
}
- private void ReadDataMessage(NetIncomingMessage inc)
+ private void ReadDataMessage(NetworkConnection sender, IReadMessage inc)
{
- var connectedClient = connectedClients.Find(c => c.Connection == inc.SenderConnection);
- if (inc.SenderConnection != OwnerConnection && serverSettings.BanList.IsBanned(inc.SenderEndPoint.Address, connectedClient == null ? 0 : connectedClient.SteamID))
- {
- KickClient(inc.SenderConnection, "You have been banned from the server.");
- return;
- }
+ var connectedClient = connectedClients.Find(c => c.Connection == sender);
ClientPacketHeader header = (ClientPacketHeader)inc.ReadByte();
switch (header)
{
- case ClientPacketHeader.REQUEST_AUTH:
- HandleOwnership(inc, inc.SenderConnection);
- HandleClientAuthRequest(inc.SenderConnection);
- break;
- case ClientPacketHeader.REQUEST_STEAMAUTH:
- HandleOwnership(inc, inc.SenderConnection);
- ReadClientSteamAuthRequest(inc, inc.SenderConnection, out _);
- break;
- case ClientPacketHeader.REQUEST_INIT:
- ClientInitRequest(inc);
- break;
case ClientPacketHeader.RESPONSE_STARTGAME:
if (connectedClient != null)
{
@@ -798,7 +790,7 @@ namespace Barotrauma.Networking
}
}
- private void HandleClientError(NetIncomingMessage inc, Client c)
+ private void HandleClientError(IReadMessage inc, Client c)
{
string errorStr = "Unhandled error report";
@@ -869,12 +861,13 @@ namespace Barotrauma.Networking
return userID;
}
- private void ClientReadLobby(NetIncomingMessage inc)
+ private void ClientReadLobby(IReadMessage inc)
{
- Client c = ConnectedClients.Find(x => x.Connection == inc.SenderConnection);
+ Client c = ConnectedClients.Find(x => x.Connection == inc.Sender);
if (c == null)
{
- inc.SenderConnection.Disconnect("You're not a connected client.");
+ //TODO: remove?
+ //inc.Sender.Disconnect("You're not a connected client.");
return;
}
@@ -930,12 +923,13 @@ namespace Barotrauma.Networking
}
}
- private void ClientReadIngame(NetIncomingMessage inc)
+ private void ClientReadIngame(IReadMessage inc)
{
- Client c = ConnectedClients.Find(x => x.Connection == inc.SenderConnection);
+ Client c = ConnectedClients.Find(x => x.Connection == inc.Sender);
if (c == null)
{
- inc.SenderConnection.Disconnect("You're not a connected client.");
+ //TODO: remove?
+ //inc.SenderConnection.Disconnect("You're not a connected client.");
return;
}
@@ -1037,7 +1031,7 @@ namespace Barotrauma.Networking
serverSettings.Voting.ServerRead(inc, c);
break;
case ClientNetObject.SPECTATING_POS:
- c.SpectatePos = new Vector2(inc.ReadFloat(), inc.ReadFloat());
+ c.SpectatePos = new Vector2(inc.ReadSingle(), inc.ReadSingle());
break;
default:
return;
@@ -1048,12 +1042,13 @@ namespace Barotrauma.Networking
}
}
- private void ClientReadServerCommand(NetIncomingMessage inc)
+ private void ClientReadServerCommand(IReadMessage inc)
{
- Client sender = ConnectedClients.Find(x => x.Connection == inc.SenderConnection);
+ Client sender = ConnectedClients.Find(x => x.Connection == inc.Sender);
if (sender == null)
{
- inc.SenderConnection.Disconnect("You're not a connected client.");
+ //TODO: remove?
+ //inc.SenderConnection.Disconnect("You're not a connected client.");
return;
}
@@ -1069,7 +1064,8 @@ namespace Barotrauma.Networking
//clients are allowed to end the round by talking with the watchman in multiplayer
//campaign even if they don't have the special permission
- if (command == ClientPermissions.ManageRound && inc.PeekBoolean() &&
+ bool peekBool = inc.ReadBoolean(); inc.BitPosition--;
+ if (command == ClientPermissions.ManageRound && peekBool &&
GameMain.GameSession?.GameMode is MultiPlayerCampaign mpCampaign)
{
if (!mpCampaign.AllowedToEndRound(sender.Character) && !sender.HasPermission(command))
@@ -1180,7 +1176,7 @@ namespace Barotrauma.Networking
}
}
- NetOutgoingMessage msg = server.CreateMessage();
+ IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ServerPacketHeader.CAMPAIGN_SETUP_INFO);
msg.Write((UInt16)saveFiles.Count());
foreach (string saveFile in saveFiles)
@@ -1188,7 +1184,7 @@ namespace Barotrauma.Networking
msg.Write(saveFile);
}
- server.SendMessage(msg, sender.Connection, NetDeliveryMethod.ReliableUnordered);
+ serverPeer.Send(msg, sender.Connection, DeliveryMethod.Reliable);
}
else
{
@@ -1262,7 +1258,7 @@ namespace Barotrauma.Networking
{
c.Character.ClientDisconnected = true;
}
-
+
ClientWriteLobby(c);
if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign &&
@@ -1274,16 +1270,16 @@ namespace Barotrauma.Networking
{
//the save was sent less than 5 second ago, don't attempt to resend yet
//(the client may have received it but hasn't acked us yet)
- if (c.LastCampaignSaveSendTime.Second > NetTime.Now - 5.0f)
+ if (c.LastCampaignSaveSendTime.Second > Lidgren.Network.NetTime.Now - 5.0f)
{
return;
- }
+ }
}
-
+
if (!fileSender.ActiveTransfers.Any(t => t.Connection == c.Connection && t.FileType == FileTransferType.CampaignSave))
{
fileSender.StartTransfer(c.Connection, FileTransferType.CampaignSave, GameMain.GameSession.SavePath);
- c.LastCampaignSaveSendTime = new Pair(campaign.LastSaveID, (float)NetTime.Now);
+ c.LastCampaignSaveSendTime = new Pair(campaign.LastSaveID, (float)Lidgren.Network.NetTime.Now);
}
}
}
@@ -1292,7 +1288,7 @@ namespace Barotrauma.Networking
///
/// Write info that the client needs when joining the server
///
- private void ClientWriteInitial(Client c, NetBuffer outmsg)
+ private void ClientWriteInitial(Client c, IWriteMessage outmsg)
{
if (GameSettings.VerboseLogging)
{
@@ -1314,39 +1310,7 @@ namespace Barotrauma.Networking
c.WritePermissions(outmsg);
}
-
- private const int COMPRESSION_THRESHOLD = 500;
- public void CompressOutgoingMessage(NetOutgoingMessage outmsg)
- {
- if (outmsg.LengthBytes > COMPRESSION_THRESHOLD)
- {
- byte[] data = outmsg.Data;
- using (MemoryStream stream = new MemoryStream())
- {
- stream.Write(data, 0, outmsg.LengthBytes);
- stream.Position = 0;
- using (MemoryStream compressed = new MemoryStream())
- {
- using (DeflateStream deflate = new DeflateStream(compressed, CompressionLevel.Fastest, false))
- {
- stream.CopyTo(deflate);
- }
-
- byte[] newData = compressed.ToArray();
-
- outmsg.Data = newData;
- outmsg.LengthBytes = newData.Length;
- outmsg.Position = outmsg.LengthBits;
- }
- }
- outmsg.Write((byte)1); //is compressed
- }
- else
- {
- outmsg.WritePadBits(); outmsg.Write((byte)0); //isn't compressed
- }
- }
-
+
private void ClientWriteIngame(Client c)
{
//don't send position updates to characters who are still midround syncing
@@ -1374,7 +1338,7 @@ namespace Barotrauma.Networking
float updateInterval = character.GetPositionUpdateInterval(c);
c.PositionUpdateLastSent.TryGetValue(character.ID, out float lastSent);
- if (lastSent > NetTime.Now - updateInterval) { continue; }
+ if (lastSent > Lidgren.Network.NetTime.Now - updateInterval) { continue; }
if (!c.PendingPositionUpdates.Contains(character)) c.PendingPositionUpdates.Enqueue(character);
}
@@ -1392,15 +1356,15 @@ namespace Barotrauma.Networking
if (item.PositionUpdateInterval == float.PositiveInfinity) { continue; }
float updateInterval = item.GetPositionUpdateInterval(c);
c.PositionUpdateLastSent.TryGetValue(item.ID, out float lastSent);
- if (lastSent > NetTime.Now - item.PositionUpdateInterval) { continue; }
+ if (lastSent > Lidgren.Network.NetTime.Now - item.PositionUpdateInterval) { continue; }
if (!c.PendingPositionUpdates.Contains(item)) c.PendingPositionUpdates.Enqueue(item);
}
}
- NetOutgoingMessage outmsg = server.CreateMessage();
+ IWriteMessage outmsg = new WriteOnlyMessage();
outmsg.Write((byte)ServerPacketHeader.UPDATE_INGAME);
- outmsg.Write((float)NetTime.Now);
+ outmsg.Write((float)Lidgren.Network.NetTime.Now);
outmsg.Write((byte)ServerNetObject.SYNC_IDS);
outmsg.Write(c.LastSentChatMsgID); //send this to client so they know which chat messages weren't received by the server
@@ -1426,7 +1390,7 @@ namespace Barotrauma.Networking
continue;
}
- NetBuffer tempBuffer = new NetBuffer();
+ IWriteMessage tempBuffer = new ReadWriteMessage();
tempBuffer.Write((byte)ServerNetObject.ENTITY_POSITION);
if (entity is Item)
{
@@ -1438,24 +1402,24 @@ namespace Barotrauma.Networking
}
//no more room in this packet
- if (outmsg.LengthBytes + tempBuffer.LengthBytes > NetPeerConfiguration.MaximumTransmissionUnit - 20)
+ if (outmsg.LengthBytes + tempBuffer.LengthBytes > MsgConstants.MTU - 20)
{
break;
}
- outmsg.Write(tempBuffer);
+ outmsg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes);
outmsg.WritePadBits();
- c.PositionUpdateLastSent[entity.ID] = (float)NetTime.Now;
+ c.PositionUpdateLastSent[entity.ID] = (float)Lidgren.Network.NetTime.Now;
c.PendingPositionUpdates.Dequeue();
}
positionUpdateBytes = outmsg.LengthBytes - positionUpdateBytes;
outmsg.Write((byte)ServerNetObject.END_OF_MESSAGE);
- if (outmsg.LengthBytes > NetPeerConfiguration.MaximumTransmissionUnit)
+ if (outmsg.LengthBytes > MsgConstants.MTU)
{
- string errorMsg = "Maximum packet size exceeded (" + outmsg.LengthBytes + " > " + NetPeerConfiguration.MaximumTransmissionUnit + ")\n";
+ string errorMsg = "Maximum packet size exceeded (" + outmsg.LengthBytes + " > " + MsgConstants.MTU + ")\n";
errorMsg +=
" Client list size: " + clientListBytes + " bytes\n" +
" Chat message size: " + chatMessageBytes + " bytes\n" +
@@ -1463,17 +1427,16 @@ namespace Barotrauma.Networking
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame1:PacketSizeExceeded" + outmsg.LengthBytes, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
}
-
- CompressOutgoingMessage(outmsg);
- server.SendMessage(outmsg, c.Connection, NetDeliveryMethod.Unreliable);
+
+ serverPeer.Send(outmsg, c.Connection, DeliveryMethod.Unreliable);
//---------------------------------------------------------------------------
for (int i = 0; i < NetConfig.MaxEventPacketsPerUpdate; i++)
{
- outmsg = server.CreateMessage();
+ outmsg = new WriteOnlyMessage();
outmsg.Write((byte)ServerPacketHeader.UPDATE_INGAME);
- outmsg.Write((float)NetTime.Now);
+ outmsg.Write((float)Lidgren.Network.NetTime.Now);
int eventManagerBytes = outmsg.LengthBytes;
entityEventManager.Write(c, outmsg, out List sentEvents);
@@ -1486,9 +1449,9 @@ namespace Barotrauma.Networking
outmsg.Write((byte)ServerNetObject.END_OF_MESSAGE);
- if (outmsg.LengthBytes > NetPeerConfiguration.MaximumTransmissionUnit)
+ if (outmsg.LengthBytes > MsgConstants.MTU)
{
- string errorMsg = "Maximum packet size exceeded (" + outmsg.LengthBytes + " > " + NetPeerConfiguration.MaximumTransmissionUnit + ")\n";
+ string errorMsg = "Maximum packet size exceeded (" + outmsg.LengthBytes + " > " + MsgConstants.MTU + ")\n";
errorMsg +=
" Event size: " + eventManagerBytes + " bytes\n";
@@ -1504,13 +1467,12 @@ namespace Barotrauma.Networking
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame2:PacketSizeExceeded" + outmsg.LengthBytes, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
}
-
- CompressOutgoingMessage(outmsg);
- server.SendMessage(outmsg, c.Connection, NetDeliveryMethod.Unreliable);
+
+ serverPeer.Send(outmsg, c.Connection, DeliveryMethod.Unreliable);
}
}
- private void WriteClientList(Client c, NetOutgoingMessage outmsg)
+ private void WriteClientList(Client c, IWriteMessage outmsg)
{
bool hasChanged = NetIdUtils.IdMoreRecent(LastClientListUpdateID, c.LastRecvClientListUpdate);
if (!hasChanged) return;
@@ -1522,6 +1484,7 @@ namespace Barotrauma.Networking
foreach (Client client in connectedClients)
{
outmsg.Write(client.ID);
+ outmsg.Write(client.SteamID);
outmsg.Write(client.Name);
outmsg.Write(client.Character == null || !gameStarted ? (ushort)0 : client.Character.ID);
outmsg.Write(client.Muted);
@@ -1534,7 +1497,7 @@ namespace Barotrauma.Networking
{
bool isInitialUpdate = false;
- NetOutgoingMessage outmsg = server.CreateMessage();
+ IWriteMessage outmsg = new WriteOnlyMessage();
outmsg.Write((byte)ServerPacketHeader.UPDATE_LOBBY);
outmsg.Write((byte)ServerNetObject.SYNC_IDS);
@@ -1546,11 +1509,11 @@ namespace Barotrauma.Networking
outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID);
- NetBuffer settingsBuf = new NetBuffer();
+ IWriteMessage settingsBuf = new ReadWriteMessage();
serverSettings.ServerWrite(settingsBuf, c);
outmsg.Write((UInt16)settingsBuf.LengthBytes);
- outmsg.Write(settingsBuf.Data,0,settingsBuf.LengthBytes);
+ outmsg.Write(settingsBuf.Buffer,0,settingsBuf.LengthBytes);
outmsg.Write(c.LastRecvLobbyUpdate < 1);
if (c.LastRecvLobbyUpdate < 1)
@@ -1571,9 +1534,9 @@ namespace Barotrauma.Networking
outmsg.Write(serverSettings.AllowSpectating);
- outmsg.WriteRangedInteger(0, 2, (int)serverSettings.TraitorsEnabled);
+ outmsg.WriteRangedIntegerDeprecated(0, 2, (int)serverSettings.TraitorsEnabled);
- outmsg.WriteRangedInteger(0, Enum.GetValues(typeof(MissionType)).Length - 1, (GameMain.NetLobbyScreen.MissionTypeIndex));
+ outmsg.WriteRangedIntegerDeprecated(0, Enum.GetValues(typeof(MissionType)).Length - 1, (GameMain.NetLobbyScreen.MissionTypeIndex));
outmsg.Write((byte)GameMain.NetLobbyScreen.SelectedModeIndex);
outmsg.Write(GameMain.NetLobbyScreen.LevelSeed);
@@ -1615,16 +1578,14 @@ namespace Barotrauma.Networking
WriteChatMessages(outmsg, c);
outmsg.Write((byte)ServerNetObject.END_OF_MESSAGE);
-
- CompressOutgoingMessage(outmsg);
-
+
if (isInitialUpdate)
{
//the initial update may be very large if the host has a large number
//of submarine files, so the message may have to be fragmented
//unreliable messages don't play nicely with fragmenting, so we'll send the message reliably
- server.SendMessage(outmsg, c.Connection, NetDeliveryMethod.ReliableUnordered);
+ serverPeer.Send(outmsg, c.Connection, DeliveryMethod.Reliable);
//and assume the message was received, so we don't have to keep resending
//these large initial messages until the client acknowledges receiving them
@@ -1634,21 +1595,21 @@ namespace Barotrauma.Networking
}
else
{
- if (outmsg.LengthBytes > NetPeerConfiguration.MaximumTransmissionUnit)
+ if (outmsg.LengthBytes > MsgConstants.MTU)
{
- DebugConsole.ThrowError("Maximum packet size exceeded (" + outmsg.LengthBytes + " > " + NetPeerConfiguration.MaximumTransmissionUnit + ")");
+ DebugConsole.ThrowError("Maximum packet size exceeded (" + outmsg.LengthBytes + " > " + MsgConstants.MTU + ")");
}
- server.SendMessage(outmsg, c.Connection, NetDeliveryMethod.Unreliable);
+ serverPeer.Send(outmsg, c.Connection, DeliveryMethod.Unreliable);
}
}
- private void WriteChatMessages(NetOutgoingMessage outmsg, Client c)
+ private void WriteChatMessages(IWriteMessage outmsg, Client c)
{
c.ChatMsgQueue.RemoveAll(cMsg => !NetIdUtils.IdMoreRecent(cMsg.NetStateID, c.LastRecvChatMsgID));
for (int i = 0; i < c.ChatMsgQueue.Count && i < ChatMessage.MaxMessagesPerPacket; i++)
{
- if (outmsg.LengthBytes + c.ChatMsgQueue[i].EstimateLengthBytesServer(c) > NetPeerConfiguration.MaximumTransmissionUnit - 5)
+ if (outmsg.LengthBytes + c.ChatMsgQueue[i].EstimateLengthBytesServer(c) > MsgConstants.MTU - 5)
{
//not enough room in this packet
return;
@@ -1704,7 +1665,7 @@ namespace Barotrauma.Networking
if (connectedClients.Any())
{
- NetOutgoingMessage msg = server.CreateMessage();
+ IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ServerPacketHeader.QUERY_STARTGAME);
msg.Write(selectedSub.Name);
@@ -1715,10 +1676,11 @@ namespace Barotrauma.Networking
msg.Write(selectedShuttle.MD5Hash.Hash);
connectedClients.ForEach(c => c.ReadyToStart = false);
-
- CompressOutgoingMessage(msg);
-
- server.SendMessage(msg, connectedClients.Select(c => c.Connection).ToList(), NetDeliveryMethod.ReliableUnordered, 0);
+
+ foreach (NetworkConnection conn in connectedClients.Select(c => c.Connection))
+ {
+ serverPeer.Send(msg, conn, DeliveryMethod.Reliable);
+ }
//give the clients a few seconds to request missing sub/shuttle files before starting the round
float waitForResponseTimer = 5.0f;
@@ -1870,7 +1832,7 @@ namespace Barotrauma.Networking
bots.Add(botInfo);
}
AssignBotJobs(bots, teamID);
-
+
WayPoint[] assignedWayPoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSubs[n]);
for (int i = 0; i < teamClients.Count; i++)
{
@@ -1884,11 +1846,12 @@ namespace Barotrauma.Networking
}
else
{
+ characterData.HasSpawned = true;
characterData.SpawnInventoryItems(spawnedCharacter.Info, spawnedCharacter.Inventory);
}
teamClients[i].Character = spawnedCharacter;
- spawnedCharacter.OwnerClientIP = teamClients[i].Connection.RemoteEndPoint.Address.ToString();
+ spawnedCharacter.OwnerClientEndPoint = teamClients[i].Connection.EndPointString;
spawnedCharacter.OwnerClientName = teamClients[i].Name;
}
@@ -1917,22 +1880,15 @@ namespace Barotrauma.Networking
if (serverSettings.TraitorsEnabled == YesNoMaybe.Yes ||
(serverSettings.TraitorsEnabled == YesNoMaybe.Maybe && Rand.Range(0.0f, 1.0f) < 0.5f))
{
- List characters = new List();
- foreach (Client client in ConnectedClients)
+ if (!(GameMain.GameSession?.GameMode is CampaignMode))
{
- if (client.Character != null) characters.Add(client.Character);
- }
-
- int max = Math.Max(serverSettings.TraitorUseRatio ? (int)Math.Round(characters.Count * serverSettings.TraitorRatio, 1) : 1, 1);
- int traitorCount = Rand.Range(1, max + 1);
- TraitorManager = new TraitorManager(this, traitorCount);
-
- if (TraitorManager.TraitorList.Count > 0)
- {
- for (int i = 0; i < TraitorManager.TraitorList.Count; i++)
+ List characters = new List();
+ foreach (Client client in ConnectedClients)
{
- Log(TraitorManager.TraitorList[i].Character.Name + " is the traitor and the target is " + TraitorManager.TraitorList[i].TargetCharacter.Name, ServerLog.MessageType.ServerMessage);
+ if (client.Character != null) characters.Add(client.Character);
}
+ TraitorManager = new TraitorManager();
+ TraitorManager.Start(this);
}
}
@@ -1968,7 +1924,7 @@ namespace Barotrauma.Networking
private void SendStartMessage(int seed, Submarine selectedSub, GameModePreset selectedMode, Client client)
{
- NetOutgoingMessage msg = server.CreateMessage();
+ IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ServerPacketHeader.STARTGAME);
msg.Write(seed);
@@ -2000,31 +1956,20 @@ namespace Barotrauma.Networking
msg.Write(serverSettings.AllowDisguises);
msg.Write(serverSettings.AllowRewiring);
- Traitor traitor = null;
- if (TraitorManager != null && TraitorManager.TraitorList.Count > 0)
- traitor = TraitorManager.TraitorList.Find(t => t.Character == client.Character);
- if (traitor != null)
- {
- msg.Write(true);
- msg.Write(traitor.TargetCharacter.Name);
- }
- else
- {
- msg.Write(false);
- }
-
msg.Write(serverSettings.AllowRagdollButton);
serverSettings.WriteMonsterEnabled(msg);
-
- CompressOutgoingMessage(msg);
-
- server.SendMessage(msg, client.Connection, NetDeliveryMethod.ReliableUnordered);
+
+ serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
}
public void EndGame()
{
- if (!gameStarted) { return; }
+ if (!gameStarted)
+ {
+ return;
+ }
+
if (GameSettings.VerboseLogging)
{
Log("Ending the round...\n" + Environment.StackTrace, ServerLog.MessageType.ServerMessage);
@@ -2035,13 +1980,20 @@ namespace Barotrauma.Networking
Log("Ending the round...", ServerLog.MessageType.ServerMessage);
}
- string endMessage = "The round has ended." + '\n';
+ var traitorEndMessage = TraitorManager?.GetEndMessage() ?? "";
+ var traitorEndMessageStart = traitorEndMessage.LastIndexOf('/') + 1;
- if (TraitorManager != null)
- {
- endMessage += TraitorManager.GetEndMessage();
- }
+ var roundSummary = TextManager.FormatServerMessage("RoundSummaryRoundHasEnded", new string[] {"[traitorinfo]"}, new string[] {"[endsummary.traitorinfo]" /*TraitorManager != null ? TraitorManager.GetEndMessage() : ""*/});
+ var roundSummaryStart = roundSummary.LastIndexOf('/') + 1;
+ string endMessage = string.Join("/", new[] {
+ traitorEndMessage.Substring(0, traitorEndMessageStart),
+ "[endsummary.traitorinfo]=" + traitorEndMessage.Substring(traitorEndMessageStart),
+ roundSummary.Substring(0, roundSummaryStart),
+ "[endsummary]=" + roundSummary.Substring(roundSummaryStart),
+ "[endsummary]\n\n[endsummary.traitorinfo]"
+ }.Where(s => !string.IsNullOrEmpty(s)));
+
Mission mission = GameMain.GameSession.Mission;
GameMain.GameSession.GameMode.End(endMessage);
@@ -2075,20 +2027,15 @@ namespace Barotrauma.Networking
if (connectedClients.Count > 0)
{
- NetOutgoingMessage msg = server.CreateMessage();
+ IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ServerPacketHeader.ENDGAME);
msg.Write(endMessage);
msg.Write(mission != null && mission.Completed);
msg.Write(GameMain.GameSession?.WinningTeam == null ? (byte)0 : (byte)GameMain.GameSession.WinningTeam);
-
- CompressOutgoingMessage(msg);
- if (server.ConnectionsCount > 0)
- {
- server.SendMessage(msg, server.Connections, NetDeliveryMethod.ReliableOrdered, 0);
- }
-
+
foreach (Client client in connectedClients)
{
+ serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
client.Character = null;
client.HasSpawned = false;
client.InGame = false;
@@ -2122,16 +2069,20 @@ namespace Barotrauma.Networking
//so the client will be informed what their actual name is
LastClientListUpdateID++;
- if (!Client.IsValidName(newName, this))
+ if (c.Connection != OwnerConnection)
{
- SendDirectChatMessage("Could not change your name to \"" + newName + "\" (the name contains disallowed symbols).", c, ChatMessageType.MessageBox);
- return false;
- }
- if (c.Connection != OwnerConnection && Homoglyphs.Compare(newName.ToLower(), Name.ToLower()))
- {
- SendDirectChatMessage("Could not change your name to \"" + newName + "\" (too similar to the server's name).", c, ChatMessageType.MessageBox);
- return false;
+ if (!Client.IsValidName(newName, serverSettings))
+ {
+ SendDirectChatMessage("Could not change your name to \"" + newName + "\" (the name contains disallowed symbols).", c, ChatMessageType.MessageBox);
+ return false;
+ }
+ if (Homoglyphs.Compare(newName.ToLower(), Name.ToLower()))
+ {
+ SendDirectChatMessage("Could not change your name to \"" + newName + "\" (too similar to the server's name).", c, ChatMessageType.MessageBox);
+ return false;
+ }
}
+
Client nameTaken = ConnectedClients.Find(c2 => c != c2 && Homoglyphs.Compare(c2.Name.ToLower(), newName.ToLower()));
if (nameTaken != null)
{
@@ -2141,6 +2092,7 @@ namespace Barotrauma.Networking
SendChatMessage("Player \"" + c.Name + "\" has changed their name to \"" + newName + "\".", ChatMessageType.Server);
c.Name = newName;
+ c.Connection.Name = newName;
return true;
}
@@ -2155,7 +2107,7 @@ namespace Barotrauma.Networking
KickClient(client, reason);
}
- public void KickClient(NetConnection conn, string reason)
+ public void KickClient(NetworkConnection conn, string reason)
{
if (conn == OwnerConnection) return;
@@ -2163,10 +2115,20 @@ namespace Barotrauma.Networking
KickClient(client, reason);
}
- public void KickClient(Client client, string reason)
+ public void KickClient(Client client, string reason, bool resetKarma = false)
{
if (client == null || client.Connection == OwnerConnection) return;
+ if (resetKarma)
+ {
+ var previousPlayer = previousPlayers.Find(p => p.MatchesClient(client));
+ if (previousPlayer != null)
+ {
+ previousPlayer.Karma = Math.Max(previousPlayer.Karma, 50.0f);
+ }
+ client.Karma = Math.Max(client.Karma, 50.0f);
+ }
+
string msg = DisconnectReason.Kicked.ToString();
string logMsg = $"ServerMessage.KickedFromServer~[client]={client.Name}";
DisconnectClient(client, logMsg, msg, reason);
@@ -2191,18 +2153,30 @@ namespace Barotrauma.Networking
public void BanClient(Client client, string reason, bool range = false, TimeSpan? duration = null)
{
- if (client == null) return;
- if (client.Connection == OwnerConnection) return;
+ if (client == null || client.Connection == OwnerConnection) { return; }
+
+ var previousPlayer = previousPlayers.Find(p => p.MatchesClient(client));
+ if (previousPlayer != null)
+ {
+ //reset karma to a neutral value, so if/when the ban is revoked the client wont get immediately punished by low karma again
+ previousPlayer.Karma = Math.Max(previousPlayer.Karma, 50.0f);
+ }
+ client.Karma = Math.Max(client.Karma, 50.0f);
string targetMsg = DisconnectReason.Banned.ToString();
DisconnectClient(client, $"ServerMessage.BannedFromServer~[client]={client.Name}", targetMsg, reason);
if (client.SteamID == 0 || range)
{
- string ip = client.Connection.RemoteEndPoint.Address.IsIPv4MappedToIPv6 ?
- client.Connection.RemoteEndPoint.Address.MapToIPv4().ToString() :
- client.Connection.RemoteEndPoint.Address.ToString();
- if (range) { ip = serverSettings.BanList.ToRange(ip); }
+ string ip = "";
+ if (client.Connection is LidgrenConnection lidgrenConn)
+ {
+ ip = lidgrenConn.IPEndPoint.Address.IsIPv4MappedToIPv6 ?
+ lidgrenConn.IPEndPoint.Address.MapToIPv4().ToString() :
+ lidgrenConn.IPEndPoint.Address.ToString();
+ if (range) { ip = serverSettings.BanList.ToRange(ip); }
+ }
+
serverSettings.BanList.BanPlayer(client.Name, ip, reason, duration);
}
if (client.SteamID > 0)
@@ -2223,11 +2197,12 @@ namespace Barotrauma.Networking
}
}
- public void DisconnectClient(NetConnection senderConnection, string msg = "", string targetmsg = "")
+ public void DisconnectClient(NetworkConnection senderConnection, string msg = "", string targetmsg = "")
{
if (senderConnection == OwnerConnection)
{
- DebugConsole.NewMessage("Owner disconnected: closing server", Color.Yellow);
+ DebugConsole.NewMessage("Owner disconnected: closing the server...", Color.Yellow);
+ Log("Owner disconnected: closing the server...", ServerLog.MessageType.ServerMessage);
GameMain.ShouldRun = false;
}
Client client = connectedClients.Find(x => x.Connection == senderConnection);
@@ -2258,7 +2233,7 @@ namespace Barotrauma.Networking
targetmsg += $"/\n/ServerMessage.Reason/: /{reason}";
}
- if (client.SteamID > 0) { SteamManager.StopAuthSession(client.SteamID); }
+ if (client.SteamID != 0) { SteamManager.StopAuthSession(client.SteamID); }
var previousPlayer = previousPlayers.Find(p => p.MatchesClient(client));
if (previousPlayer == null)
@@ -2268,20 +2243,21 @@ namespace Barotrauma.Networking
}
previousPlayer.Name = client.Name;
previousPlayer.Karma = client.Karma;
+ previousPlayer.KarmaKickCount = client.KarmaKickCount;
previousPlayer.KickVoters.Clear();
foreach (Client c in connectedClients)
{
if (client.HasKickVoteFrom(c)) { previousPlayer.KickVoters.Add(c); }
}
-
- client.Connection.Disconnect(targetmsg);
+
+ serverPeer.Disconnect(client.Connection, targetmsg);
client.Dispose();
connectedClients.Remove(client);
KarmaManager.OnClientDisconnected(client);
UpdateVoteStatus();
-
+
SendChatMessage(msg, ChatMessageType.Server);
UpdateCrewFrame();
@@ -2353,15 +2329,19 @@ namespace Barotrauma.Networking
default:
if (command != "")
{
- if (command == name.ToLowerInvariant())
+ if (command.ToLower() == name.ToLower())
{
//a private message to the host
+ if (OwnerConnection != null)
+ {
+ targetClient = connectedClients.Find(c => c.Connection == OwnerConnection);
+ }
}
else
{
targetClient = connectedClients.Find(c =>
- command == c.Name.ToLowerInvariant() ||
- (c.Character != null && command == c.Character.Name.ToLowerInvariant()));
+ command.ToLower() == c.Name.ToLower() ||
+ command.ToLower() == c.Character?.Name?.ToLower());
if (targetClient == null)
{
@@ -2370,13 +2350,7 @@ namespace Barotrauma.Networking
var chatMsg = ChatMessage.Create(
"", $"ServerMessage.PlayerNotFound~[player]={command}",
ChatMessageType.Error, null);
-
- chatMsg.NetStateID = senderClient.ChatMsgQueue.Count > 0 ?
- (ushort)(senderClient.ChatMsgQueue.Last().NetStateID + 1) :
- (ushort)(senderClient.LastRecvChatMsgID + 1);
-
- senderClient.ChatMsgQueue.Add(chatMsg);
- senderClient.LastChatMsgQueueID = chatMsg.NetStateID;
+ SendDirectChatMessage(chatMsg, senderClient);
}
else
{
@@ -2532,8 +2506,7 @@ namespace Barotrauma.Networking
if (type.Value != ChatMessageType.MessageBox)
{
string myReceivedMessage = type == ChatMessageType.Server || type == ChatMessageType.Error ? TextManager.GetServerMessage(message) : message;
- if (!string.IsNullOrWhiteSpace(myReceivedMessage) &&
- (targetClient == null || senderClient == null))
+ if (!string.IsNullOrWhiteSpace(myReceivedMessage))
{
AddChatMessage(myReceivedMessage, (ChatMessageType)type, senderName, senderCharacter);
}
@@ -2580,23 +2553,22 @@ namespace Barotrauma.Networking
(transfer.Status == FileTransferStatus.Sending || transfer.Status == FileTransferStatus.Finished) &&
recipient.LastCampaignSaveSendTime != null)
{
- recipient.LastCampaignSaveSendTime.Second = (float)NetTime.Now;
+ recipient.LastCampaignSaveSendTime.Second = (float)Lidgren.Network.NetTime.Now;
}
}
public void SendCancelTransferMsg(FileSender.FileTransferOut transfer)
{
- NetOutgoingMessage msg = server.CreateMessage();
+ IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ServerPacketHeader.FILE_TRANSFER);
msg.Write((byte)FileTransferMessageType.Cancel);
- msg.Write((byte)transfer.SequenceChannel);
- CompressOutgoingMessage(msg);
- server.SendMessage(msg, transfer.Connection, NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel);
+ msg.Write((byte)transfer.ID);
+ serverPeer.Send(msg, transfer.Connection, DeliveryMethod.ReliableOrdered);
}
public void UpdateVoteStatus()
{
- if (server.Connections.Count == 0 || connectedClients.Count == 0) return;
+ if (connectedClients.Count == 0) return;
Client.UpdateKickVotes(connectedClients);
@@ -2633,15 +2605,16 @@ namespace Barotrauma.Networking
{
if (!recipients.Any()) { return; }
- NetOutgoingMessage msg = server.CreateMessage();
+ IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ServerPacketHeader.UPDATE_LOBBY);
msg.Write((byte)ServerNetObject.VOTE);
serverSettings.Voting.ServerWrite(msg);
msg.Write((byte)ServerNetObject.END_OF_MESSAGE);
-
- CompressOutgoingMessage(msg);
-
- server.SendMessage(msg, recipients.Select(c => c.Connection).ToList(), NetDeliveryMethod.ReliableUnordered, 0);
+
+ foreach (var c in recipients)
+ {
+ serverPeer.Send(msg, c.Connection, DeliveryMethod.Reliable);
+ }
}
public void UpdateClientPermissions(Client client)
@@ -2660,24 +2633,23 @@ namespace Barotrauma.Networking
}
else
{
- serverSettings.ClientPermissions.RemoveAll(cp => client.IPMatches(cp.IP));
+ serverSettings.ClientPermissions.RemoveAll(cp => client.EndpointMatches(cp.EndPoint));
if (client.Permissions != ClientPermissions.None)
{
serverSettings.ClientPermissions.Add(new ServerSettings.SavedClientPermission(
client.Name,
- client.Connection.RemoteEndPoint.Address,
+ client.Connection.EndPointString,
client.Permissions,
client.PermittedConsoleCommands));
}
}
- var msg = server.CreateMessage();
+ IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ServerPacketHeader.PERMISSIONS);
client.WritePermissions(msg);
- CompressOutgoingMessage(msg);
//send the message to the client whose permissions are being modified and the clients who are allowed to modify permissions
- List recipients = new List() { client.Connection };
+ List recipients = new List() { client.Connection };
foreach (Client otherClient in connectedClients)
{
if (otherClient.HasPermission(ClientPermissions.ManagePermissions) && !recipients.Contains(otherClient.Connection))
@@ -2685,9 +2657,9 @@ namespace Barotrauma.Networking
recipients.Add(otherClient.Connection);
}
}
- if (recipients.Any())
+ foreach (NetworkConnection c in recipients)
{
- server.SendMessage(msg, recipients, NetDeliveryMethod.ReliableUnordered, 0);
+ serverPeer.Send(msg, c, DeliveryMethod.Reliable);
}
serverSettings.SaveClientPermissions();
@@ -2711,27 +2683,41 @@ namespace Barotrauma.Networking
if (client.GivenAchievements.Contains(achievementIdentifier)) return;
client.GivenAchievements.Add(achievementIdentifier);
- var msg = server.CreateMessage();
+ IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ServerPacketHeader.ACHIEVEMENT);
msg.Write(achievementIdentifier);
+
+ serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
+ }
- CompressOutgoingMessage(msg);
+ public void SendTraitorMessage(Client client, string message, TraitorMessageType messageType)
+ {
+ if (client == null) { return; }
+ if (!TraitorManager.IsTraitor(client.Character) && client.Connection != OwnerConnection)
+ {
+ return;
+ }
+ var msg = new WriteOnlyMessage();
+ msg.Write((byte)ServerPacketHeader.TRAITOR_MESSAGE);
+ msg.Write((byte)messageType);
+ msg.Write(message);
- server.SendMessage(msg, client.Connection, NetDeliveryMethod.ReliableUnordered);
+ serverPeer.Send(msg, client.Connection, DeliveryMethod.ReliableOrdered);
}
public void UpdateCheatsEnabled()
{
if (!connectedClients.Any()) { return; }
- var msg = server.CreateMessage();
+ IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ServerPacketHeader.CHEATS_ENABLED);
msg.Write(DebugConsole.CheatsEnabled);
msg.WritePadBits();
-
- CompressOutgoingMessage(msg);
-
- server.SendMessage(msg, connectedClients.Select(c => c.Connection).ToList(), NetDeliveryMethod.ReliableUnordered, 0);
+
+ foreach (Client c in connectedClients)
+ {
+ serverPeer.Send(msg, c.Connection, DeliveryMethod.Reliable);
+ }
}
public void SetClientCharacter(Client client, Character newCharacter)
@@ -2742,7 +2728,7 @@ namespace Barotrauma.Networking
if (client.Character != null)
{
client.Character.IsRemotePlayer = false;
- client.Character.OwnerClientIP = null;
+ client.Character.OwnerClientEndPoint = null;
client.Character.OwnerClientName = null;
}
@@ -2764,7 +2750,7 @@ namespace Barotrauma.Networking
newCharacter.LastNetworkUpdateID = client.Character.LastNetworkUpdateID;
}
- newCharacter.OwnerClientIP = client.Connection.RemoteEndPoint.Address.ToString();
+ newCharacter.OwnerClientEndPoint = client.Connection.EndPointString;
newCharacter.OwnerClientName = client.Name;
newCharacter.IsRemotePlayer = true;
newCharacter.Enabled = true;
@@ -2773,7 +2759,7 @@ namespace Barotrauma.Networking
}
}
- private void UpdateCharacterInfo(NetIncomingMessage message, Client sender)
+ private void UpdateCharacterInfo(IReadMessage message, Client sender)
{
sender.SpectateOnly = message.ReadBoolean() && (serverSettings.AllowSpectating || sender.Connection == OwnerConnection);
if (sender.SpectateOnly)
@@ -3037,73 +3023,71 @@ namespace Barotrauma.Networking
}
}
+ public Tuple FindPreviousClientData(Client client)
+ {
+ var player = previousPlayers.Find(p => p.MatchesClient(client));
+ if (player != null)
+ {
+ return Tuple.Create(player.SteamID, player.EndPoint);
+ }
+ return null;
+ }
+
public override void Disconnect()
{
- serverSettings.BanList.Save();
- serverSettings.SaveSettings();
- SteamManager.CloseServer();
-
- if (registeredToMaster)
+ if (started)
{
- if (restClient != null)
+ started = false;
+
+ serverSettings.BanList.Save();
+ serverSettings.SaveSettings();
+
+ if (registeredToMaster)
{
- var request = new RestRequest("masterserver2.php", Method.GET);
- request.AddParameter("action", "removeserver");
- request.AddParameter("serverport", Port);
- restClient.Execute(request);
- restClient = null;
+ if (restClient != null)
+ {
+ var request = new RestRequest("masterserver2.php", Method.GET);
+ request.AddParameter("action", "removeserver");
+ request.AddParameter("serverport", Port);
+ restClient.Execute(request);
+ restClient = null;
+ }
}
+
+ if (serverSettings.SaveServerLogs)
+ {
+ Log("Shutting down the server...", ServerLog.MessageType.ServerMessage);
+ serverSettings.ServerLog.Save();
+ }
+
+ GameAnalyticsManager.AddDesignEvent("GameServer:ShutDown");
+ serverPeer?.Close(DisconnectReason.ServerShutdown.ToString());
+
+ SteamManager.CloseServer();
}
-
- if (serverSettings.SaveServerLogs)
- {
- Log("Shutting down the server...", ServerLog.MessageType.ServerMessage);
- serverSettings.ServerLog.Save();
- }
-
- GameAnalyticsManager.AddDesignEvent("GameServer:ShutDown");
- server.Shutdown(DisconnectReason.ServerShutdown.ToString());
- }
-
- void InitUPnP()
- {
- server.UPnP.ForwardPort(NetPeerConfiguration.Port, "barotrauma");
- if (Steam.SteamManager.USE_STEAM)
- {
- server.UPnP.ForwardPort(QueryPort, "barotrauma");
- }
- }
-
- bool DiscoveringUPnP()
- {
- return server.UPnP.Status == UPnPStatus.Discovering;
- }
-
- void FinishUPnP()
- {
- //do nothing
}
}
partial class PreviousPlayer
{
public string Name;
- public string IP;
+ public string EndPoint;
public UInt64 SteamID;
public float Karma;
+ public int KarmaKickCount;
public readonly List KickVoters = new List();
public PreviousPlayer(Client c)
{
Name = c.Name;
- IP = c.Connection?.RemoteEndPoint?.Address?.ToString() ?? "";
+ EndPoint = c.Connection?.EndPointString ?? "";
SteamID = c.SteamID;
}
public bool MatchesClient(Client c)
{
if (c.SteamID > 0 && SteamID > 0) { return c.SteamID == SteamID; }
- return c.IPMatches(IP);
+ return c.EndpointMatches(EndPoint);
}
}
}
diff --git a/Barotrauma/BarotraumaServer/Source/Networking/GameServerLogin.cs b/Barotrauma/BarotraumaServer/Source/Networking/GameServerLogin.cs
deleted file mode 100644
index f45e1fc36..000000000
--- a/Barotrauma/BarotraumaServer/Source/Networking/GameServerLogin.cs
+++ /dev/null
@@ -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 unauthenticatedClients = new List();
-
- 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 contentPackageNames = new List();
- List contentPackageHashes = new List();
- 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 missingPackages = new List();
- 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 packageStrs = new List();
- 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> incompatiblePackages = new List>();
- 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(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 packageStrs = new List();
- 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 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());
- }
- }
- }
-
- 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);
- }
- }
- }
-}
diff --git a/Barotrauma/BarotraumaServer/Source/Networking/KarmaManager.cs b/Barotrauma/BarotraumaServer/Source/Networking/KarmaManager.cs
index fc0ea45a7..37c1f8cce 100644
--- a/Barotrauma/BarotraumaServer/Source/Networking/KarmaManager.cs
+++ b/Barotrauma/BarotraumaServer/Source/Networking/KarmaManager.cs
@@ -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 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)
{
diff --git a/Barotrauma/BarotraumaServer/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs b/Barotrauma/BarotraumaServer/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs
index 9c22f1d2e..2a1710789 100644
--- a/Barotrauma/BarotraumaServer/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs
+++ b/Barotrauma/BarotraumaServer/Source/Networking/NetEntityEvent/ServerEntityEventManager.cs
@@ -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
///
/// Writes all the events that the client hasn't received yet into the outgoing message
///
- 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
///
/// Writes all the events that the client hasn't received yet into the outgoing message
///
- public void Write(Client client, NetOutgoingMessage msg, out List sentEvents)
+ public void Write(Client client, IWriteMessage msg, out List sentEvents)
{
List 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
///
/// Read the events from the message, ignoring ones we've already received
///
- 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;
diff --git a/Barotrauma/BarotraumaServer/Source/Networking/OrderChatMessage.cs b/Barotrauma/BarotraumaServer/Source/Networking/OrderChatMessage.cs
index 8b4cc65a7..78d625989 100644
--- a/Barotrauma/BarotraumaServer/Source/Networking/OrderChatMessage.cs
+++ b/Barotrauma/BarotraumaServer/Source/Networking/OrderChatMessage.cs
@@ -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);
diff --git a/Barotrauma/BarotraumaServer/Source/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs b/Barotrauma/BarotraumaServer/Source/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs
new file mode 100644
index 000000000..2f78ec9e4
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/Source/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs
@@ -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 connectedClients;
+ private List pendingClients;
+
+ private List incomingLidgrenMessages;
+
+ public LidgrenServerPeer(int? ownKey, ServerSettings settings)
+ {
+ serverSettings = settings;
+
+ netServer = null;
+
+ connectedClients = new List();
+ pendingClients = new List();
+
+ incomingLidgrenMessages = new List();
+
+ 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 contentPackages = new List();
+ for (int i = 0; i < contentPackageCount; i++)
+ {
+ string packageName = inc.ReadString();
+ string packageHash = inc.ReadString();
+ contentPackages.Add(new ClientContentPackage(packageName, packageHash));
+ }
+
+ List missingPackages = new List();
+ 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 packageStrs = new List();
+ 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");
+ }
+ }
+}
diff --git a/Barotrauma/BarotraumaServer/Source/Networking/Primitives/Peers/Server/ServerPeer.cs b/Barotrauma/BarotraumaServer/Source/Networking/Primitives/Peers/Server/ServerPeer.cs
new file mode 100644
index 000000000..62ac3fc0b
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/Source/Networking/Primitives/Peers/Server/ServerPeer.cs
@@ -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);
+ }
+}
diff --git a/Barotrauma/BarotraumaServer/Source/Networking/Primitives/Peers/Server/SteamP2PServerPeer.cs b/Barotrauma/BarotraumaServer/Source/Networking/Primitives/Peers/Server/SteamP2PServerPeer.cs
new file mode 100644
index 000000000..afa84a137
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/Source/Networking/Primitives/Peers/Server/SteamP2PServerPeer.cs
@@ -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 connectedClients;
+ private List pendingClients;
+
+ private List incomingLidgrenMessages;
+
+ public SteamP2PServerPeer(UInt64 steamId, ServerSettings settings)
+ {
+ serverSettings = settings;
+
+ netServer = null;
+
+ connectedClients = new List();
+ pendingClients = new List();
+
+ incomingLidgrenMessages = new List();
+
+ 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 contentPackages = new List();
+ for (int i = 0; i < contentPackageCount; i++)
+ {
+ string packageName = inc.ReadString();
+ string packageHash = inc.ReadString();
+ contentPackages.Add(new ClientContentPackage(packageName, packageHash));
+ }
+
+ List missingPackages = new List();
+ 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 packageStrs = new List();
+ 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);
+ }
+ }
+}
diff --git a/Barotrauma/BarotraumaServer/Source/Networking/RespawnManager.cs b/Barotrauma/BarotraumaServer/Source/Networking/RespawnManager.cs
index 019db3603..c280a0463 100644
--- a/Barotrauma/BarotraumaServer/Source/Networking/RespawnManager.cs
+++ b/Barotrauma/BarotraumaServer/Source/Networking/RespawnManager.cs
@@ -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)
{
diff --git a/Barotrauma/BarotraumaServer/Source/Networking/ServerSettings.cs b/Barotrauma/BarotraumaServer/Source/Networking/ServerSettings.cs
index da1b96533..2a6fd1307 100644
--- a/Barotrauma/BarotraumaServer/Source/Networking/ServerSettings.cs
+++ b/Barotrauma/BarotraumaServer/Source/Networking/ServerSettings.cs
@@ -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)
diff --git a/Barotrauma/BarotraumaServer/Source/Networking/SteamManager.cs b/Barotrauma/BarotraumaServer/Source/Networking/SteamManager.cs
index d13e5e89c..61c864935 100644
--- a/Barotrauma/BarotraumaServer/Source/Networking/SteamManager.cs
+++ b/Barotrauma/BarotraumaServer/Source/Networking/SteamManager.cs
@@ -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)
diff --git a/Barotrauma/BarotraumaServer/Source/Networking/Voip/VoipServer.cs b/Barotrauma/BarotraumaServer/Source/Networking/Voip/VoipServer.cs
index 02a88e0cc..b923dc1d5 100644
--- a/Barotrauma/BarotraumaServer/Source/Networking/Voip/VoipServer.cs
+++ b/Barotrauma/BarotraumaServer/Source/Networking/Voip/VoipServer.cs
@@ -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 queues;
private Dictionary lastSendTime;
- public VoipServer(NetServer server)
+ public VoipServer(ServerPeer server)
{
this.netServer = server;
queues = new List();
@@ -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);
}
}
}
diff --git a/Barotrauma/BarotraumaServer/Source/Networking/Voting.cs b/Barotrauma/BarotraumaServer/Source/Networking/Voting.cs
index cc4402453..5a5962955 100644
--- a/Barotrauma/BarotraumaServer/Source/Networking/Voting.cs
+++ b/Barotrauma/BarotraumaServer/Source/Networking/Voting.cs
@@ -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;
diff --git a/Barotrauma/BarotraumaServer/Source/Networking/WhiteList.cs b/Barotrauma/BarotraumaServer/Source/Networking/WhiteList.cs
index c5e27a867..9ab32db6d 100644
--- a/Barotrauma/BarotraumaServer/Source/Networking/WhiteList.cs
+++ b/Barotrauma/BarotraumaServer/Source/Networking/WhiteList.cs
@@ -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++)
{
diff --git a/Barotrauma/BarotraumaServer/Source/Physics/PhysicsBody.cs b/Barotrauma/BarotraumaServer/Source/Physics/PhysicsBody.cs
index aceb6ad85..6874cab35 100644
--- a/Barotrauma/BarotraumaServer/Source/Physics/PhysicsBody.cs
+++ b/Barotrauma/BarotraumaServer/Source/Physics/PhysicsBody.cs
@@ -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;
diff --git a/Barotrauma/BarotraumaServer/Source/Program.cs b/Barotrauma/BarotraumaServer/Source/Program.cs
index a30172199..77e872c80 100644
--- a/Barotrauma/BarotraumaServer/Source/Program.cs
+++ b/Barotrauma/BarotraumaServer/Source/Program.cs
@@ -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.");
}
diff --git a/Barotrauma/BarotraumaServer/Source/Traitors/Goals/Goal.cs b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/Goal.cs
new file mode 100644
index 000000000..b74289e24
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/Goal.cs
@@ -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 StatusTextKeys => new [] { "[infotext]", "[status]" };
+ public virtual IEnumerable StatusTextValues => new [] { InfoText, TextManager.FormatServerMessage(StatusValueTextId) };
+
+ public virtual IEnumerable InfoTextKeys => new string[] { };
+ public virtual IEnumerable InfoTextValues => new string[] { };
+
+ public virtual IEnumerable CompletedTextKeys => new string[] { };
+ public virtual IEnumerable CompletedTextValues => new string[] { };
+
+ protected virtual string FormatText(Traitor traitor, string textId, IEnumerable keys, IEnumerable values) => TextManager.FormatServerMessageWithGenderPronouns(traitor?.Character?.Info?.Gender ?? Gender.None, textId, keys, values);
+
+ protected internal virtual string GetStatusText(Traitor traitor, string textId, IEnumerable keys, IEnumerable values) => FormatText(traitor, textId, keys, values);
+ protected internal virtual string GetInfoText(Traitor traitor, string textId, IEnumerable keys, IEnumerable values) => FormatText(traitor, textId, keys, values);
+ protected internal virtual string GetCompletedText(Traitor traitor, string textId, IEnumerable keys, IEnumerable 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()
+ {
+ }
+ }
+ }
+}
diff --git a/Barotrauma/BarotraumaServer/Source/Traitors/Goals/GoalDestroyItemsWithTag.cs b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/GoalDestroyItemsWithTag.cs
new file mode 100644
index 000000000..b0ffa4dbd
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/GoalDestroyItemsWithTag.cs
@@ -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 InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[percentage]", "[tag]" });
+ public override IEnumerable 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;
+ }
+ }
+ }
+}
diff --git a/Barotrauma/BarotraumaServer/Source/Traitors/Goals/GoalFindItem.cs b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/GoalFindItem.cs
new file mode 100644
index 000000000..11b42f2d1
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/GoalFindItem.cs
@@ -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 allowedContainerIdentifiers = new HashSet();
+
+ private ItemPrefab targetPrefab;
+ private Item targetContainer;
+ private Item target;
+ private HashSet
- existingItems = new HashSet
- ();
+ private string targetNameText;
+ private string targetContainerNameText;
+ private string targetHullNameText;
+
+ public override IEnumerable InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[identifier]", "[target]", "[targethullname]" });
+ public override IEnumerable 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() != 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);
+ }
+ }
+ }
+}
diff --git a/Barotrauma/BarotraumaServer/Source/Traitors/Goals/GoalFloodPercentOfSub.cs b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/GoalFloodPercentOfSub.cs
new file mode 100644
index 000000000..0cbe16075
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/GoalFloodPercentOfSub.cs
@@ -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 InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[percentage]" });
+ public override IEnumerable 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;
+ }
+ }
+
+ }
+}
diff --git a/Barotrauma/BarotraumaServer/Source/Traitors/Goals/GoalKillTarget.cs b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/GoalKillTarget.cs
new file mode 100644
index 000000000..38511ac3d
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/GoalKillTarget.cs
@@ -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 InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[targetname]" });
+ public override IEnumerable 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;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaServer/Source/Traitors/Goals/GoalRandom.cs b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/GoalRandom.cs
new file mode 100644
index 000000000..e05be93c0
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/GoalRandom.cs
@@ -0,0 +1,44 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Barotrauma
+{
+ partial class Traitor
+ {
+ public class GoalRandom : Goal
+ {
+ private readonly List allGoals;
+
+ private readonly List selectedGoals = new List();
+
+ public override IEnumerable InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[targetname]" });
+ public override IEnumerable 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;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaServer/Source/Traitors/Goals/GoalReachDistanceFromSub.cs b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/GoalReachDistanceFromSub.cs
new file mode 100644
index 000000000..e259e2182
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/GoalReachDistanceFromSub.cs
@@ -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 InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[distance]" });
+ public override IEnumerable 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;
+ }
+ }
+ }
+}
diff --git a/Barotrauma/BarotraumaServer/Source/Traitors/Goals/GoalReplaceInventory.cs b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/GoalReplaceInventory.cs
new file mode 100644
index 000000000..f737e1c04
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/GoalReplaceInventory.cs
@@ -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 sabotageContainerIds = new HashSet();
+ private readonly HashSet validReplacementIds = new HashSet();
+
+ private readonly float replaceAmount;
+
+ private bool isCompleted = false;
+ public override bool IsCompleted => isCompleted;
+
+ public override IEnumerable StatusTextKeys => base.StatusTextKeys.Concat(new string[] { "[percentage]" });
+ public override IEnumerable 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;
+ }
+ }
+ }
+}
diff --git a/Barotrauma/BarotraumaServer/Source/Traitors/Goals/GoalSabotageItems.cs b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/GoalSabotageItems.cs
new file mode 100644
index 000000000..d2cb82fb6
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/GoalSabotageItems.cs
@@ -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 InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[tag]", "[target]", "[threshold]" });
+ public override IEnumerable 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
- targetItems = new List
- ();
+ 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";
+ }
+ }
+ }
+}
diff --git a/Barotrauma/BarotraumaServer/Source/Traitors/Goals/HumanoidGoal.cs b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/HumanoidGoal.cs
new file mode 100644
index 000000000..ee9329989
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/HumanoidGoal.cs
@@ -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;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaServer/Source/Traitors/Goals/Modifiers/GoalHasDuration.cs b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/Modifiers/GoalHasDuration.cs
new file mode 100644
index 000000000..926403cee
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/Modifiers/GoalHasDuration.cs
@@ -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 InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[duration]" });
+
+ public override IEnumerable InfoTextValues => base.InfoTextValues.Concat(new string[] { $"{TimeSpan.FromSeconds(requiredDuration):g}" });
+
+ protected internal override string GetInfoText(Traitor traitor, string textId, IEnumerable keys, IEnumerable 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;
+ }
+ }
+ }
+}
+
diff --git a/Barotrauma/BarotraumaServer/Source/Traitors/Goals/Modifiers/GoalHasTimeLimit.cs b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/Modifiers/GoalHasTimeLimit.cs
new file mode 100644
index 000000000..2c1472f92
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/Modifiers/GoalHasTimeLimit.cs
@@ -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 InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[timelimit]" });
+ public override IEnumerable InfoTextValues => base.InfoTextValues.Concat(new string[] { $"{TimeSpan.FromSeconds(timeLimit):g}" });
+
+ protected internal override string GetInfoText(Traitor traitor, string textId, IEnumerable keys, IEnumerable 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;
+ }
+ }
+ }
+}
diff --git a/Barotrauma/BarotraumaServer/Source/Traitors/Goals/Modifiers/GoalIsOptional.cs b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/Modifiers/GoalIsOptional.cs
new file mode 100644
index 000000000..028766bbd
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/Modifiers/GoalIsOptional.cs
@@ -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 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 keys, IEnumerable 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;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaServer/Source/Traitors/Goals/Modifiers/Modifier.cs b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/Modifiers/Modifier.cs
new file mode 100644
index 000000000..825a8c785
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/Source/Traitors/Goals/Modifiers/Modifier.cs
@@ -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 StatusTextKeys => Goal.StatusTextKeys;
+ public override IEnumerable StatusTextValues => new [] { InfoText, TextManager.FormatServerMessage(StatusValueTextId) };
+
+ public override IEnumerable InfoTextKeys => Goal.InfoTextKeys;
+ public override IEnumerable InfoTextValues => Goal.InfoTextValues;
+
+ public override IEnumerable CompletedTextKeys => Goal.CompletedTextKeys;
+ public override IEnumerable CompletedTextValues => Goal.CompletedTextValues;
+
+ protected internal override string GetStatusText(Traitor traitor, string textId, IEnumerable keys, IEnumerable values) => Goal.GetStatusText(traitor, textId, keys, values);
+ protected internal override string GetInfoText(Traitor traitor, string textId, IEnumerable keys, IEnumerable values) => Goal.GetInfoText(traitor, textId, keys, values);
+ protected internal override string GetCompletedText(Traitor traitor, string textId, IEnumerable keys, IEnumerable 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;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaServer/Source/Traitors/Objective.cs b/Barotrauma/BarotraumaServer/Source/Traitors/Objective.cs
new file mode 100644
index 000000000..7833f4f17
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/Source/Traitors/Objective.cs
@@ -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 allGoals = new List();
+ private readonly List activeGoals = new List();
+ private readonly List pendingGoals = new List();
+ private readonly List completedGoals = new List();
+
+ 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 StartMessageKeys => new string[] { "[traitorgoalinfos]" };
+ public virtual IEnumerable 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 StartMessageServerKeys => StartMessageKeys.Concat(new string[] { "[traitorname]" });
+ public virtual IEnumerable 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 EndMessageKeys => new string[] { "[traitorname]", "[traitorgoalinfos]" };
+ public virtual IEnumerable 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() where T : Goal
+ {
+ return allGoals?.Any(g => g is T) ?? false;
+ }
+ }
+ }
+}
diff --git a/Barotrauma/BarotraumaServer/Source/Traitors/Traitor.cs b/Barotrauma/BarotraumaServer/Source/Traitors/Traitor.cs
new file mode 100644
index 000000000..79d739b48
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/Source/Traitors/Traitor.cs
@@ -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);
+ }
+ }
+}
diff --git a/Barotrauma/BarotraumaServer/Source/Traitors/TraitorManager.cs b/Barotrauma/BarotraumaServer/Source/Traitors/TraitorManager.cs
new file mode 100644
index 000000000..a22702d4e
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/Source/Traitors/TraitorManager.cs
@@ -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 Missions = new Dictionary();
+
+ 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 Traitors => Missions.Values.SelectMany(mission => mission.Traitors.Values);
+
+ private float startCountdown = 0.0f;
+ private GameServer server;
+
+ private readonly Dictionary traitorCountsBySteamId = new Dictionary();
+ private readonly Dictionary traitorCountsByEndPoint = new Dictionary();
+
+ public int GetTraitorCount(Tuple 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 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(ICollection collection, Func random, Func readSelectedWeight, Action 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;
+ }
+ }
+}
diff --git a/Barotrauma/BarotraumaServer/Source/Traitors/TraitorMission.cs b/Barotrauma/BarotraumaServer/Source/Traitors/TraitorMission.cs
new file mode 100644
index 000000000..d29884ce1
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/Source/Traitors/TraitorMission.cs
@@ -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 allObjectives = new List();
+ private readonly List pendingObjectives = new List();
+ private readonly List completedObjectives = new List();
+
+ public virtual bool IsCompleted => pendingObjectives.Count <= 0;
+
+ public readonly Dictionary Traitors = new Dictionary();
+
+ 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 GlobalEndMessageKeys => new string[] { "[traitorname]", "[traitorgoalinfos]" };
+ public virtual IEnumerable 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> FindTraitorCandidates(GameServer server, Character.TeamType team, params string[] traitorRoles)
+ {
+ var traitorCandidates = new List>();
+#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 FindCharacters()
+ {
+ List characters = new List();
+ 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 characters = FindCharacters();
+ List> 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>();
+ foreach (var traitor in Traitors.Values)
+ {
+ messages[traitor] = new List();
+ 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 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);
+ }
+ }
+ }
+}
diff --git a/Barotrauma/BarotraumaServer/Source/Traitors/TraitorMissionPrefab.cs b/Barotrauma/BarotraumaServer/Source/Traitors/TraitorMissionPrefab.cs
new file mode 100644
index 000000000..686086ec5
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/Source/Traitors/TraitorMissionPrefab.cs
@@ -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 List = new List();
+
+ 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 required = new HashSet();
+ private readonly HashSet optional = new HashSet();
+
+ 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 targetFilters = new Dictionary()
+ {
+ { "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 filters = new List();
+ 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 Goals = new List();
+
+ 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 Roles = new Dictionary();
+ */
+ 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 Objectives = new List();
+
+ 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;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml
index 9cd0afff7..93a775acf 100644
--- a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml
+++ b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml
@@ -1,4 +1,4 @@
-
+
@@ -82,6 +82,7 @@
+
@@ -91,6 +92,10 @@
+
+
+
+
@@ -104,6 +109,7 @@
+
diff --git a/Barotrauma/BarotraumaShared/Data/karmasettings.xml b/Barotrauma/BarotraumaShared/Data/karmasettings.xml
index 50ba0e140..bbbda55fc 100644
--- a/Barotrauma/BarotraumaShared/Data/karmasettings.xml
+++ b/Barotrauma/BarotraumaShared/Data/karmasettings.xml
@@ -14,7 +14,7 @@
damageenemykarmaincrease="0.1"
damagefriendlykarmadecrease="0.2"
extinguishfirekarmaincrease="1"
- allowedwiredisconnectionsperminute="5"
+ allowedwiredisconnectionsperminute="3"
wiredisconnectionkarmadecrease="6.0"
steersubkarmaincrease="0.15"
spamfilterkarmadecrease="15"
@@ -35,7 +35,7 @@
damageenemykarmaincrease="0.1"
damagefriendlykarmadecrease="0.3"
extinguishfirekarmaincrease="1"
- allowedwiredisconnectionsperminute="4"
+ allowedwiredisconnectionsperminute="1"
wiredisconnectionkarmadecrease="10.0"
steersubkarmaincrease="0.15"
spamfilterkarmadecrease="25"
@@ -56,7 +56,7 @@
damageenemykarmaincrease="0.1"
damagefriendlykarmadecrease="0.2"
extinguishfirekarmaincrease="1"
- allowedwiredisconnectionsperminute="5"
+ allowedwiredisconnectionsperminute="3"
wiredisconnectionkarmadecrease="6.0"
steersubkarmaincrease="0.15"
spamfilterkarmadecrease="15"
diff --git a/Barotrauma/BarotraumaShared/Data/permissionpresets.xml b/Barotrauma/BarotraumaShared/Data/permissionpresets.xml
index 648674540..d06d1d321 100644
--- a/Barotrauma/BarotraumaShared/Data/permissionpresets.xml
+++ b/Barotrauma/BarotraumaShared/Data/permissionpresets.xml
@@ -25,7 +25,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaShared/Mods/ExampleMod/Humpback2.sub b/Barotrauma/BarotraumaShared/Mods/ExampleMod/Humpback2.sub
new file mode 100644
index 000000000..38b34627a
Binary files /dev/null and b/Barotrauma/BarotraumaShared/Mods/ExampleMod/Humpback2.sub differ
diff --git a/Barotrauma/BarotraumaShared/Mods/ExampleMod/PreviewImage.png b/Barotrauma/BarotraumaShared/Mods/ExampleMod/PreviewImage.png
new file mode 100644
index 000000000..c76c81542
Binary files /dev/null and b/Barotrauma/BarotraumaShared/Mods/ExampleMod/PreviewImage.png differ
diff --git a/Barotrauma/BarotraumaShared/Mods/ExampleMod/Redcrawler/Animations/RedcrawlerRun.xml b/Barotrauma/BarotraumaShared/Mods/ExampleMod/Redcrawler/Animations/RedcrawlerRun.xml
new file mode 100644
index 000000000..6da1fe1c9
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/Mods/ExampleMod/Redcrawler/Animations/RedcrawlerRun.xml
@@ -0,0 +1,23 @@
+
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaShared/Mods/ExampleMod/Redcrawler/Animations/RedcrawlerSwimFast.xml b/Barotrauma/BarotraumaShared/Mods/ExampleMod/Redcrawler/Animations/RedcrawlerSwimFast.xml
new file mode 100644
index 000000000..db65d0370
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/Mods/ExampleMod/Redcrawler/Animations/RedcrawlerSwimFast.xml
@@ -0,0 +1,20 @@
+
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaShared/Mods/ExampleMod/Redcrawler/Animations/RedcrawlerSwimSlow.xml b/Barotrauma/BarotraumaShared/Mods/ExampleMod/Redcrawler/Animations/RedcrawlerSwimSlow.xml
new file mode 100644
index 000000000..ab9573b83
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/Mods/ExampleMod/Redcrawler/Animations/RedcrawlerSwimSlow.xml
@@ -0,0 +1,20 @@
+
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaShared/Mods/ExampleMod/Redcrawler/Animations/RedcrawlerWalk.xml b/Barotrauma/BarotraumaShared/Mods/ExampleMod/Redcrawler/Animations/RedcrawlerWalk.xml
new file mode 100644
index 000000000..b46d3a4c0
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/Mods/ExampleMod/Redcrawler/Animations/RedcrawlerWalk.xml
@@ -0,0 +1,23 @@
+
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaShared/Mods/ExampleMod/Redcrawler/Ragdolls/RedcrawlerDefaultRagdoll.xml b/Barotrauma/BarotraumaShared/Mods/ExampleMod/Redcrawler/Ragdolls/RedcrawlerDefaultRagdoll.xml
new file mode 100644
index 000000000..ef45bf03e
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/Mods/ExampleMod/Redcrawler/Ragdolls/RedcrawlerDefaultRagdoll.xml
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaShared/Mods/ExampleMod/Redcrawler/Redcrawler.xml b/Barotrauma/BarotraumaShared/Mods/ExampleMod/Redcrawler/Redcrawler.xml
new file mode 100644
index 000000000..1135fd55a
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/Mods/ExampleMod/Redcrawler/Redcrawler.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Barotrauma/BarotraumaShared/Mods/ExampleMod/Redcrawler/crawler.png b/Barotrauma/BarotraumaShared/Mods/ExampleMod/Redcrawler/crawler.png
new file mode 100644
index 000000000..31f9101d9
Binary files /dev/null and b/Barotrauma/BarotraumaShared/Mods/ExampleMod/Redcrawler/crawler.png differ
diff --git a/Barotrauma/BarotraumaShared/Mods/ExampleMod/filelist.xml b/Barotrauma/BarotraumaShared/Mods/ExampleMod/filelist.xml
new file mode 100644
index 000000000..82367efc8
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/Mods/ExampleMod/filelist.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaShared/Mods/info.txt b/Barotrauma/BarotraumaShared/Mods/info.txt
index 4a2d5754b..c48fff272 100644
--- a/Barotrauma/BarotraumaShared/Mods/info.txt
+++ b/Barotrauma/BarotraumaShared/Mods/info.txt
@@ -1,17 +1,14 @@
------------------------------------------------------------------------
-Modding info/tips:
+General
------------------------------------------------------------------------
- When doing an automatic update through the launcher, any old/unnecessary
- files in the Content folder will be deleted. This also includes any new
- files you may have added to the folder.
+ This folder is the place where mods installed from the Steam Workshop go.
+ You should also place your own mods in this folder, under a separate
+ subfolder (e.g. "Mods/MyMod").
- It is recommended to save all modifications to the ''Mods'' folder, or
- in the case of custom subs, to the 'Submarines'' folder.
-
-----------------------------------
+------------------------------------------------------------------------
Content Packages:
-----------------------------------
+------------------------------------------------------------------------
Content packages determine which configuration files the game will be using.
This includes the configuration files for items, map structures, characters,
@@ -20,19 +17,18 @@ Content Packages:
In the multiplayer mode, players are required to use the same content package
as the server or they won't be able to join.
- If you are making a mod for the game, it is recommended to create a new content
- package instead of just replacing existing files in the content folder.
- This way you and anyone else using the mod can easily change between different
- mods and the vanilla version, without having to manually replace files in the
- Content folder or keep backups of different files.
+ All mods published in the Steam Workshop need a content package.
- The content packages are configured in XML files which are saved in Data\ContentPackages.
+ If you just want to publish a custom submarine in the workshop, you
+ don't need to worry about content packages - you can just select the
+ submarine from the "Publish item" tab in the Workshop menu, and the
+ game automatically creates a folder and content package for your mod.
Example:
A very simple content package could be configured as follows:
-
+
@@ -50,178 +46,4 @@ Example:
a new event that spawns Cthulhu and removing the events that spawn monsters/items
which aren't included in the mod.
- It is also set to be used with the version 0.9.0.0 of the game.
-
-----------------------------------
-Creature modding:
-----------------------------------
-
- All the creatures/characters in the game are configured in XML files.
-
- The XML files should be formatted in the following way:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ------------------------------------------
-
- Elements:
-
- Character:
- - has to be the root element of the file
-
- Attributes:
- name: the name of the creature
- humanoid: true/false, if set to true the character is animated using
- a bipedal animator (like humans)
- needsair: true/false, does the character drown/suffocate without
- oxygen (false by default)
- drowningtime: how fast the character drowns (in seconds)
- doesbleed: if set to false, the character takes no bleeding damage
- health: self explanatory (100.0 by default)
-
- Ragdoll:
- - has to be a child element of the character element
-
- Attributes:
- headposition: how high from the ground the head of the character should
- be when the character is standing (50.0 by default)
- headangle: an angle which the head is rotated to when the character is walking
- (0.0 by default, meaning that the head will face straight forward)
- torsoposition: same as headposition but for torso (50.0 by default)
- torsoangle: an angle which the torso is rotated to when the character is walking
- (0.0 by default, meaning that it will face straight forward)
- waveamplitude, wavelength: if the character is not a humanoid, it will
- do a "wave-like" swimming movement with the selected amplitude and
- wavelength. To put it simply, amplitude affects how large up-and-down
- movement the character will do and wavelength affects how fast the
- character does the movement (both 0.0 by default)
- flip: should the entire character be "mirrored" over the y-axis when it
- switches its movement direction from left to right or vice versa,
- or should it just rotate along the z-axis (false by default)
- walkspeed, swimspeed: how fast the character should move on land and
- in water, (the actual speeds are also affected by the weight, shape
- and steerforces of individual limbs) (both 1.0 by default)
- swimspeed: how fast the character should move in water
-
- Limb:
- - an individual part of the ragdoll
- - has to be a child element of the ragdoll element
-
- Attributes:
- id: an integer that is used to distinguish between limbs when connecting them
- with joints. The first limb should have the id "0", the second "1" and so on.
- radius, width, height: used for setting the dimensions of the physics body
- of the limb. If only radius is set, the limb will be a circle with
- the selected radius. If width and height are set, it will be a rectangle.
- If radius and height are set, it will be a capsule.
- density: the mass of the limb will be area_of_the_limb * density (default 10.0)
- friction: the friction coefficient of the limb (0.3 by default)
- flip: true/false, if set to true the limb will be "flipped" from one side
- to another when the character turns around (as in, if a character is
- facing left and has an arm extended left, the arm will be extended
- to the right when the character faces to the right) (false by default)
- ignorecollisions: true/false, should the limb collide with walls (true by default)
- impacttolerance: if the limb receives an impact larger than this value, it takes
- damage (20.0 by default)
- type: determines how the limb should be animated and what kind of items can be
- equipped on the limb. Can be set to None, LeftHand, RightHand, LeftArm,
- RightArm, LeftLeg, RightLeg, LeftFoot, RightFoot, Head, Torso, Waist, Tail,
- Legs, RightThigh or LeftThigh
- pullpos: when animating the character, forces will be applied to this
- point of the physics body of the limb. Defaults to "0.0, 0.0" which
- is the center of the limb.
- refjoint: index of the joint that is used as the "center point" along
- the x-axis when doing a walking animation. For example, if the joint
- between a characters thigh and waist is set as refjoint, the feet of the
- character will be moved directly under said joint when the character is
- standing still.
- steerforce: how much force is applied to the limb when the character moves (0.0 by default)
- armorsector: an armored sector between two angles (in degrees). For example,
- -90,90 would make the front sector of the limb armored (0.0,0.0 by default)
- armor: how effective the armor is: damage is divided by this value if an attack hits the
- armored sector (1.0 by default)
-
- Sprite:
- - a child element of a limb element
-
- Attributes:
- texture: file path of the texture
- sourcerect: which part of the texture should be used. If either width or
- height are 0, they will be set to the width or height of the texture.
- (0,0,0,0 by default)
- origin: what point in the sprite is considered the "middle point". "0,0"
- is the upper left corner of the sprite and "1,1" the lower right
- corner. ("0.5, 0.5" by default)
- depth: Affects the order which sprites are drawn in. Sprites with a
- depth of 1.0 will be drawn under sprites that have the depth set
- to 0.9 for example. Note that setting several limbs to the same
- depth value may cause them to "flicker" on top of each other, so
- it's recommended that every sprite has a slightly different depth.
-
- Attack:
- - a child element of a limb element
-
- Attributes:
- type: affects the logic for moving the attacking limb. At the moment the only
- types are None, PinchCW and PinchCW
-
- PinchCW: the limb rotates clockwise when attacking (or counter-clockwise
- if the character is facing left). Useful for attacks like biting
- or slashing
- PinchCCW: the same as PinchCW, but in the limb is rotated in the
- opposite direction
- Hit: the limb will "punch" the target
-
- damage: damage done to other characters (0.0 by default)
- bleedingdamage: how much the attack affects the bleeding rate (0.0 by default)
- structuredamage: damage done to structures (0.0 by default)
- stun: how long the target is stunned (in seconds, 0.0 by default)
- range: how close the limb doing the attack has to be to the target to do damage
- (0.0 by default, but should be set to a higher value or otherwise it
- will only do damage if the limb is exactly at the position of the target,
- so practically never)
- duration: how long the attack lasts - if set to zero, it will be a "one-hit"
- attack, otherwise it will be active for a while and the damage values
- will be damage per second
- priority: can be used for adjusting how likely the character is to use specific
- attacks. For example, if a character has two attacks, first one
- having the priority 2.0 and the second 1.0, the character is twice as
- likely to use the first one.
-
- Joint:
- - a revolute joint connecting two limbs to each other
- - a child element of the ragdoll element
-
- Attributes:
- limb1, limb2: thes id of the limbs that should be connected
- limb1anchor, limb2anchor: the points where the joint is attached to on
- the limbs (0.0, 0.0 being the center)
- lowerlimit, upperlimit: how much the joint can turn. If both are set to 0.0,
- the joint can rotate freely.
-
-----------------------------------
-Editing items:
-----------------------------------
-
- (A more extensive tutorial coming up in the future)
-
- Items are also configured in XML files. An item consist of several ''components''
- which determine the functionality of the item. See the existing item files for
- examples on the components.
-
- TO BE CONTINUED
\ No newline at end of file
+ It is also set to be used with the version 0.9.1.0 of the game.
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaShared/SharedCode.projitems b/Barotrauma/BarotraumaShared/SharedCode.projitems
index 78b2b83f0..50f93972a 100644
--- a/Barotrauma/BarotraumaShared/SharedCode.projitems
+++ b/Barotrauma/BarotraumaShared/SharedCode.projitems
@@ -235,6 +235,13 @@
+
+
+
+
+
+
+
diff --git a/Barotrauma/BarotraumaShared/SharedCode.shproj.user b/Barotrauma/BarotraumaShared/SharedCode.shproj.user
index a11cd7f91..12a505406 100644
--- a/Barotrauma/BarotraumaShared/SharedCode.shproj.user
+++ b/Barotrauma/BarotraumaShared/SharedCode.shproj.user
@@ -1,6 +1,6 @@
- true
+ false
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaShared/SharedContent.projitems b/Barotrauma/BarotraumaShared/SharedContent.projitems
index ee46100fe..bdcdd6528 100644
--- a/Barotrauma/BarotraumaShared/SharedContent.projitems
+++ b/Barotrauma/BarotraumaShared/SharedContent.projitems
@@ -1,4 +1,4 @@
-
+
$(MSBuildAllProjects);$(MSBuildThisFileFullPath)
@@ -400,12 +400,18 @@
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
@@ -520,6 +526,9 @@
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
@@ -614,6 +623,9 @@
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
@@ -623,6 +635,12 @@
PreserveNewest
+
+ PreserveNewest
+
+
+ PreserveNewest
+
PreserveNewest
@@ -632,6 +650,9 @@
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
@@ -689,6 +710,33 @@
PreserveNewest
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
PreserveNewest
@@ -2126,6 +2174,9 @@
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
@@ -2354,6 +2405,9 @@
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
@@ -2361,11 +2415,11 @@
PreserveNewest
- Never
+ PreserveNewest
-
+
+ PreserveNewest
-
@@ -3539,4 +3593,4 @@
PreserveNewest
-
\ No newline at end of file
+
diff --git a/Barotrauma/BarotraumaShared/SharedContent.shproj.user b/Barotrauma/BarotraumaShared/SharedContent.shproj.user
index 5bc13ae87..7e04c94d7 100644
--- a/Barotrauma/BarotraumaShared/SharedContent.shproj.user
+++ b/Barotrauma/BarotraumaShared/SharedContent.shproj.user
@@ -1,6 +1,6 @@
- true
+ false
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs
index d432de18e..0d1742307 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/AITarget.cs
@@ -14,11 +14,6 @@ namespace Barotrauma
get;
private set;
}
-
- ///
- /// Use as a minimum or static sight range.
- ///
- public static float StaticSightRange = 3000;
private float soundRange;
private float sightRange;
@@ -75,7 +70,7 @@ namespace Barotrauma
public bool Enabled = true;
public float MinSoundRange, MinSightRange;
- public float MaxSoundRange = float.MaxValue, MaxSightRange = float.MaxValue;
+ public float MaxSoundRange = 100000, MaxSightRange = 100000;
public TargetType Type { get; private set; }
@@ -128,8 +123,8 @@ namespace Barotrauma
{
SightRange = element.GetAttributeFloat("sightrange", 0.0f);
SoundRange = element.GetAttributeFloat("soundrange", 0.0f);
- MinSightRange = element.GetAttributeFloat("minsightrange", SightRange);
- MinSoundRange = element.GetAttributeFloat("minsoundrange", SoundRange);
+ MinSightRange = element.GetAttributeFloat("minsightrange", 0f);
+ MinSoundRange = element.GetAttributeFloat("minsoundrange", 0f);
MaxSightRange = element.GetAttributeFloat("maxsightrange", SightRange);
MaxSoundRange = element.GetAttributeFloat("maxsoundrange", SoundRange);
FadeOutTime = element.GetAttributeFloat("fadeouttime", FadeOutTime);
@@ -142,15 +137,9 @@ namespace Barotrauma
}
}
- public AITarget(Entity e, float sightRange = -1, float soundRange = 0)
+ public AITarget(Entity e)
{
Entity = e;
- if (sightRange < 0)
- {
- sightRange = StaticSightRange;
- }
- SightRange = sightRange;
- SoundRange = soundRange;
List.Add(this);
}
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs
index 43428f673..10cd8374d 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs
@@ -109,9 +109,9 @@ namespace Barotrauma
private Dictionary targetMemories;
//the eyesight of the NPC (0.0 = blind, 1.0 = sees every target within sightRange)
- private float sight;
+ public float sight;
//how far the NPC can hear targets from (0.0 = deaf, 1.0 = hears every target within soundRange)
- private float hearing;
+ public float hearing;
private float colliderSize;
@@ -270,11 +270,14 @@ namespace Barotrauma
return null;
}
- public override void SelectTarget(AITarget target)
+ public override void SelectTarget(AITarget target) => SelectTarget(target, 100);
+
+ public void SelectTarget(AITarget target, float priority)
{
SelectedAiTarget = target;
selectedTargetMemory = GetTargetMemory(target);
- targetValue = 100.0f;
+ selectedTargetMemory.Priority = priority;
+ targetValue = priority;
}
public override void Update(float deltaTime)
@@ -985,7 +988,7 @@ namespace Barotrauma
var aiTarget = wallTarget.Structure.AiTarget;
if (aiTarget != null && SelectedAiTarget != aiTarget)
{
- SelectTarget(aiTarget);
+ SelectTarget(aiTarget, GetTargetMemory(SelectedAiTarget).Priority);
}
}
if (SelectedAiTarget.Entity is IDamageable damageTarget)
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs
index 479abd337..912084633 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs
@@ -370,7 +370,7 @@ namespace Barotrauma
if (item.CurrentHull != hull) { continue; }
if (AIObjectiveRepairItems.IsValidTarget(item, Character))
{
- if (item.Repairables.All(r => item.Condition > r.ShowRepairUIThreshold)) { continue; }
+ if (item.Repairables.All(r => item.ConditionPercentage > r.ShowRepairUIThreshold)) { continue; }
AddTargets(Character, item);
if (newOrder == null)
{
@@ -640,7 +640,7 @@ namespace Barotrauma
if (item.CurrentHull != hull) { continue; }
if (AIObjectiveRepairItems.IsValidTarget(item, character))
{
- if (item.Repairables.All(r => item.Condition > r.ShowRepairUIThreshold)) { continue; }
+ if (item.Repairables.All(r => item.ConditionPercentage > r.ShowRepairUIThreshold)) { continue; }
AddTargets(character, item);
}
}
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs
index 72c150ed6..5e05bc20b 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs
@@ -142,6 +142,9 @@ namespace Barotrauma
IsPathDirty = false;
}
+ public Func startNodeFilter;
+ public Func endNodeFilter;
+
protected override Vector2 DoSteeringSeek(Vector2 target, float weight)
{
bool needsNewPath = currentPath != null && currentPath.Unreachable || Vector2.DistanceSquared(target, currentTarget) > 1;
@@ -164,7 +167,7 @@ namespace Barotrauma
}
}
- var newPath = pathFinder.FindPath(pos, target, character.Submarine, "(Character: " + character.Name + ")");
+ var newPath = pathFinder.FindPath(pos, target, character.Submarine, "(Character: " + character.Name + ")", startNodeFilter, endNodeFilter);
bool useNewPath = currentPath == null || needsNewPath;
if (!useNewPath && currentPath != null && currentPath.CurrentNode != null && newPath.Nodes.Any() && !newPath.Unreachable)
{
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs
index 0ec20a4c9..34e1a4048 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs
@@ -14,7 +14,9 @@ namespace Barotrauma
private float waitUntilPathUnreachable;
private bool getDivingGearIfNeeded;
- public Func customCondition;
+ public Func requiredCondition;
+ public Func startNodeFilter;
+ public Func endNodeFilter;
public bool followControlledCharacter;
public bool mimic;
@@ -137,7 +139,12 @@ namespace Barotrauma
currTargetSimPos -= diff;
}
}
- character.AIController.SteeringManager.SteeringSeek(currTargetSimPos);
+ if (PathSteering != null)
+ {
+ PathSteering.startNodeFilter = startNodeFilter;
+ PathSteering.endNodeFilter = endNodeFilter;
+ }
+ SteeringManager.SteeringSeek(currTargetSimPos);
if (SteeringManager != PathSteering)
{
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 1, heading: VectorExtensions.Forward(character.AnimController.Collider.Rotation));
@@ -191,7 +198,7 @@ namespace Barotrauma
}
else if (closeEnough)
{
- if (customCondition == null || customCondition())
+ if (requiredCondition == null || requiredCondition())
{
if (Target is Item item)
{
@@ -218,7 +225,7 @@ namespace Barotrauma
private void CalculateCloseEnough()
{
- float interactionDistance = Target is Item i ? i.InteractDistance * 0.9f : 0;
+ float interactionDistance = Target is Item i ? i.InteractDistance + Math.Max(i.Rect.Width, i.Rect.Height) / 2 : 0;
CloseEnough = Math.Max(interactionDistance, CloseEnough);
}
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs
index c8d162c3b..94f81003f 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs
@@ -149,7 +149,14 @@ namespace Barotrauma
character?.Speak(TextManager.GetWithVariable("DialogCannotRepair", "[itemname]", Item.Name, true), null, 0.0f, "cannotrepair", 10.0f);
}
}
- repairable.CurrentFixer = abandon && repairable.CurrentFixer == character ? null : character;
+ if (abandon)
+ {
+ repairable.StopRepairing(character);
+ }
+ else
+ {
+ repairable.StartRepairing(character, Repairable.FixActions.Repair);
+ }
break;
}
}
@@ -161,7 +168,11 @@ namespace Barotrauma
constructor: () =>
{
previousCondition = -1;
- var objective = new AIObjectiveGoTo(Item, character, objectiveManager);
+ var objective = new AIObjectiveGoTo(Item, character, objectiveManager)
+ {
+ // Don't stop in ladders, because we can't interact with other items while holding the ladders.
+ endNodeFilter = node => node.Waypoint.Ladders == null
+ };
if (repairTool != null)
{
objective.CloseEnough = repairTool.Range * 0.75f;
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItems.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItems.cs
index cfbda6189..751b1271a 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItems.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItems.cs
@@ -43,7 +43,7 @@ namespace Barotrauma
if (Character.CharacterList.Any(c => c.CurrentHull == item.CurrentHull && !HumanAIController.IsFriendly(c))) { return false; }
if (!Objectives.ContainsKey(item))
{
- if (item.Repairables.All(r => item.Condition > r.ShowRepairUIThreshold)) { return false; }
+ if (item.Repairables.All(r => item.ConditionPercentage > r.ShowRepairUIThreshold)) { return false; }
}
if (RequireAdequateSkills)
{
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/PathFinder.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/PathFinder.cs
index 9f589cf8f..a23b44fbb 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/AI/PathFinder.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/PathFinder.cs
@@ -157,12 +157,13 @@ namespace Barotrauma
}
}
- public SteeringPath FindPath(Vector2 start, Vector2 end, Submarine hostSub = null, string errorMsgStr = null)
+ public SteeringPath FindPath(Vector2 start, Vector2 end, Submarine hostSub = null, string errorMsgStr = null, Func startNodeFilter = null, Func endNodeFilter = null)
{
float closestDist = 0.0f;
PathNode startNode = null;
foreach (PathNode node in nodes)
{
+ if (startNodeFilter != null && !startNodeFilter(node)) { continue; }
Vector2 nodePos = node.Position;
if (hostSub != null)
{
@@ -219,6 +220,7 @@ namespace Barotrauma
PathNode endNode = null;
foreach (PathNode node in nodes)
{
+ if (endNodeFilter != null && !endNodeFilter(node)) { continue; }
Vector2 nodePos = node.Position;
if (hostSub != null)
{
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Animation/AnimationParams.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Animation/AnimationParams.cs
index 53edbdc7d..8f5506eae 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Animation/AnimationParams.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Animation/AnimationParams.cs
@@ -96,8 +96,8 @@ namespace Barotrauma
public virtual AnimationType AnimationType { get; protected set; }
public static string GetDefaultFileName(string speciesName, AnimationType animType) => $"{speciesName.CapitaliseFirstInvariant()}{animType.ToString()}";
- public static string GetDefaultFile(string speciesName, AnimationType animType, ContentPackage contentPackage = null) =>
- $"{GetFolder(speciesName, contentPackage)}{GetDefaultFileName(speciesName, animType)}.xml";
+ public static string GetDefaultFile(string speciesName, AnimationType animType, ContentPackage contentPackage = null)
+ => Path.Combine(GetFolder(speciesName, contentPackage), $"{GetDefaultFileName(speciesName, animType)}.xml");
public static string GetFolder(string speciesName, ContentPackage contentPackage = null)
{
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Ragdoll/RagdollParams.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Ragdoll/RagdollParams.cs
index c7eb7cf4a..06ab8d665 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Ragdoll/RagdollParams.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Params/Ragdoll/RagdollParams.cs
@@ -66,7 +66,8 @@ namespace Barotrauma
.Concat(Joints.Select(j => j as RagdollSubParams)));
public static string GetDefaultFileName(string speciesName) => $"{speciesName.CapitaliseFirstInvariant()}DefaultRagdoll";
- public static string GetDefaultFile(string speciesName, ContentPackage contentPackage = null) => $"{GetFolder(speciesName, contentPackage)}{GetDefaultFileName(speciesName)}.xml";
+ public static string GetDefaultFile(string speciesName, ContentPackage contentPackage = null)
+ => Path.Combine(GetFolder(speciesName, contentPackage), $"{GetDefaultFileName(speciesName)}.xml");
private static readonly object[] dummyParams = new object[]
{
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs
index a87f4c7b9..8488c6b14 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/Animation/Ragdoll.cs
@@ -1324,7 +1324,7 @@ namespace Barotrauma
private void CheckBodyInRest(float deltaTime)
{
- if (Collider.LinearVelocity.LengthSquared() > 0.01f || character.SelectedBy != null || !character.IsDead)
+ if (InWater || Collider.LinearVelocity.LengthSquared() > 0.01f || character.SelectedBy != null || !character.IsDead)
{
bodyInRestTimer = 0.0f;
foreach (Limb limb in Limbs)
@@ -1383,10 +1383,12 @@ namespace Barotrauma
private bool CheckValidity(PhysicsBody body)
{
string errorMsg = null;
- string bodyName = body.UserData is Limb ? "Limb" : "Collider";
+ string bodyName = body.UserData is Limb limb ?
+ "Limb (" + limb.type + ")" :
+ "Collider";
if (!MathUtils.IsValid(body.SimPosition) || Math.Abs(body.SimPosition.X) > 1e10f || Math.Abs(body.SimPosition.Y) > 1e10f)
{
- errorMsg = bodyName+ " position invalid (" + body.SimPosition + ", character: " + character.Name + "), resetting the ragdoll.";
+ errorMsg = bodyName + " position invalid (" + body.SimPosition + ", character: " + character.Name + "), resetting the ragdoll.";
}
else if (!MathUtils.IsValid(body.LinearVelocity) || Math.Abs(body.LinearVelocity.X) > 1000f || Math.Abs(body.LinearVelocity.Y) > 1000f)
{
@@ -1426,10 +1428,10 @@ namespace Barotrauma
{
Collider.SetTransform(Vector2.Zero, 0.0f);
}
- foreach (Limb limb in Limbs)
+ foreach (Limb otherLimb in Limbs)
{
- limb.body.SetTransform(Collider.SimPosition, 0.0f);
- limb.body.ResetDynamics();
+ otherLimb.body.SetTransform(Collider.SimPosition, 0.0f);
+ otherLimb.body.ResetDynamics();
}
SetInitialLimbPositions();
return false;
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs
index 1ee5c62e8..37a406016 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/Character.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/Character.cs
@@ -104,6 +104,9 @@ namespace Barotrauma
public readonly bool IsHumanoid;
+ public bool IsTraitor;
+ public string TraitorCurrentObjective = "";
+
//the name of the species (e.q. human)
public readonly string SpeciesName;
@@ -1515,12 +1518,12 @@ namespace Barotrauma
bool leftHand = Inventory.IsInLimbSlot(item, InvSlotType.LeftHand);
bool selected = false;
- if (rightHand && SelectedItems[0] == null)
+ if (rightHand && (SelectedItems[0] == null || SelectedItems[0] == item))
{
selectedItems[0] = item;
selected = true;
}
- if (leftHand && SelectedItems[1] == null)
+ if (leftHand && (SelectedItems[1] == null || SelectedItems[1] == item))
{
selectedItems[1] = item;
selected = true;
@@ -1839,7 +1842,7 @@ namespace Barotrauma
{
DeselectCharacter();
}
- else if (focusedCharacter != null && IsKeyHit(InputType.Grab) && FocusedCharacter.CanInventoryBeAccessed)
+ else if (focusedCharacter != null && IsKeyHit(InputType.Grab) && FocusedCharacter.CanBeDragged)
{
SelectCharacter(focusedCharacter);
}
@@ -1954,6 +1957,10 @@ namespace Barotrauma
//disable AI characters that are far away from the sub and the controlled character
float distSqr = Vector2.DistanceSquared(Submarine.MainSub.WorldPosition, c.WorldPosition);
if (Controlled != null)
+ {
+ distSqr = Math.Min(distSqr, Vector2.DistanceSquared(Controlled.WorldPosition, c.WorldPosition));
+ }
+ else
{
distSqr = Math.Min(distSqr, Vector2.DistanceSquared(GameMain.GameScreen.Cam.GetPosition(), c.WorldPosition));
}
@@ -2198,16 +2205,15 @@ namespace Barotrauma
private void UpdateSightRange()
{
if (aiTarget == null) { return; }
- // TODO: the formula might need some tweaking
- float range = (float)Math.Sqrt(Mass) * 1000.0f + AnimController.Collider.LinearVelocity.Length() * 500.0f;
- aiTarget.SightRange = MathHelper.Clamp(range, 0, 15000.0f);
+ float range = (float)Math.Sqrt(Mass) * 250 + AnimController.Collider.LinearVelocity.Length() * 500;
+ aiTarget.SightRange = MathHelper.Clamp(range, 0, 10000);
}
private void UpdateSoundRange()
{
if (aiTarget == null) { return; }
- float range = Mass / 5 * AnimController.TargetMovement.Length() * Noise;
- aiTarget.SoundRange = MathHelper.Clamp(range, 0f, 5000f);
+ float range = ((float)Math.Sqrt(Mass) / 3) * (AnimController.TargetMovement.Length() * 2) * Noise;
+ aiTarget.SoundRange = MathHelper.Clamp(range, 0, 10000);
}
public void SetOrder(Order order, string orderOption, Character orderGiver, bool speak = true)
@@ -2598,6 +2604,7 @@ namespace Barotrauma
{
if (selectedItems[i] != null) selectedItems[i].Drop(this);
}
+ SelectedConstruction = null;
AnimController.ResetPullJoints();
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/Affliction.cs b/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/Affliction.cs
index d9cc9f827..fc826ad7f 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/Affliction.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/Affliction.cs
@@ -21,7 +21,7 @@ namespace Barotrauma
///
/// Probability for the affliction to be applied. Used by attacks.
///
- public float ApplyProbability;
+ public float ApplyProbability = 1.0f;
///
/// Which character gave this affliction
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/AfflictionSpaceHerpes.cs b/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/AfflictionSpaceHerpes.cs
index 8e1f3dd45..5ddc5d0a6 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/AfflictionSpaceHerpes.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/Health/Afflictions/AfflictionSpaceHerpes.cs
@@ -9,6 +9,10 @@ namespace Barotrauma
{
private float invertControlsCooldown = 60.0f;
private float stunCoolDown = 60.0f;
+ private float invertControlsTimer;
+
+ private float invertControlsToggleTimer;
+
public AfflictionSpaceHerpes(AfflictionPrefab prefab, float strength) : base(prefab, strength)
{
}
@@ -25,10 +29,29 @@ namespace Barotrauma
//invert controls every 126-234 seconds when strength is close to 0
//every 56-104 seconds when strength is close to 100
invertControlsCooldown = (180.0f - Strength) * Rand.Range(0.7f, 1.3f);
- var invertControlsAffliction = AfflictionPrefab.List.Find(ap => ap.Identifier == "invertcontrols");
- float invertControlsDuration = MathHelper.Lerp(10.0f, 60.0f, Strength / 100.0f) * Rand.Range(0.7f, 1.3f);
- characterHealth.ApplyAffliction(null, new Affliction(invertControlsAffliction, invertControlsDuration));
+ invertControlsTimer = MathHelper.Lerp(10.0f, 60.0f, Strength / 100.0f) * Rand.Range(0.7f, 1.3f);
}
+ else if (invertControlsTimer > 0.0f)
+ {
+ //randomly toggle inverted controls on/off every 5 seconds
+ invertControlsToggleTimer -= deltaTime;
+ if (invertControlsToggleTimer <= 0.0f)
+ {
+ invertControlsToggleTimer = 5.0f;
+ if (Rand.Range(0.0f, 1.0f) < 0.5f)
+ {
+ characterHealth.ReduceAffliction(null, "invertcontrols", 100);
+ }
+ else
+ {
+ var invertControlsAffliction = AfflictionPrefab.List.Find(ap => ap.Identifier == "invertcontrols");
+ characterHealth.ApplyAffliction(null, new Affliction(invertControlsAffliction, 5.0f));
+ }
+ }
+
+ invertControlsTimer -= deltaTime;
+ }
+
if (Strength > 50.0f)
{
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs
index 97ce677aa..59d72e9e1 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs
@@ -1,9 +1,9 @@
-using Lidgren.Network;
-using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
+using Barotrauma.Networking;
namespace Barotrauma
{
@@ -732,14 +732,14 @@ namespace Barotrauma
return allAfflictions;
}
- public void ServerWrite(NetBuffer msg)
+ public void ServerWrite(IWriteMessage msg)
{
List activeAfflictions = afflictions.FindAll(a => a.Strength > 0.0f && a.Strength >= a.Prefab.ActivationThreshold);
msg.Write((byte)activeAfflictions.Count);
foreach (Affliction affliction in activeAfflictions)
{
- msg.WriteRangedInteger(0, AfflictionPrefab.List.Count - 1, AfflictionPrefab.List.IndexOf(affliction.Prefab));
+ msg.WriteRangedIntegerDeprecated(0, AfflictionPrefab.List.Count - 1, AfflictionPrefab.List.IndexOf(affliction.Prefab));
msg.WriteRangedSingle(
MathHelper.Clamp(affliction.Strength, 0.0f, affliction.Prefab.MaxStrength),
0.0f, affliction.Prefab.MaxStrength, 8);
@@ -758,8 +758,8 @@ namespace Barotrauma
msg.Write((byte)limbAfflictions.Count);
foreach (var limbAffliction in limbAfflictions)
{
- msg.WriteRangedInteger(0, limbHealths.Count - 1, limbHealths.IndexOf(limbAffliction.First));
- msg.WriteRangedInteger(0, AfflictionPrefab.List.Count - 1, AfflictionPrefab.List.IndexOf(limbAffliction.Second.Prefab));
+ msg.WriteRangedIntegerDeprecated(0, limbHealths.Count - 1, limbHealths.IndexOf(limbAffliction.First));
+ msg.WriteRangedIntegerDeprecated(0, AfflictionPrefab.List.Count - 1, AfflictionPrefab.List.IndexOf(limbAffliction.Second.Prefab));
msg.WriteRangedSingle(
MathHelper.Clamp(limbAffliction.Second.Strength, 0.0f, limbAffliction.Second.Prefab.MaxStrength),
0.0f, limbAffliction.Second.Prefab.MaxStrength, 8);
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Jobs/Job.cs b/Barotrauma/BarotraumaShared/Source/Characters/Jobs/Job.cs
index ad0d59f13..deea9e2ab 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/Jobs/Job.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/Jobs/Job.cs
@@ -144,6 +144,15 @@ namespace Barotrauma
#if SERVER
if (GameMain.Server != null && Entity.Spawner != null)
{
+ if (GameMain.Server.EntityEventManager.UniqueEvents.Any(ev => ev.Entity == item))
+ {
+ string errorMsg = $"Error while spawning job items. Item {item.Name} created network events before the spawn event had been created.";
+ DebugConsole.ThrowError(errorMsg);
+ GameAnalyticsManager.AddErrorEventOnce("Job.InitializeJobItem:EventsBeforeSpawning", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
+ GameMain.Server.EntityEventManager.UniqueEvents.RemoveAll(ev => ev.Entity == item);
+ GameMain.Server.EntityEventManager.Events.RemoveAll(ev => ev.Entity == item);
+ }
+
Entity.Spawner.CreateNetworkEvent(item, false);
}
#endif
diff --git a/Barotrauma/BarotraumaShared/Source/ContentPackage.cs b/Barotrauma/BarotraumaShared/Source/ContentPackage.cs
index ebeb9dbda..414e8d982 100644
--- a/Barotrauma/BarotraumaShared/Source/ContentPackage.cs
+++ b/Barotrauma/BarotraumaShared/Source/ContentPackage.cs
@@ -36,7 +36,8 @@ namespace Barotrauma
Afflictions,
Buffs,
Tutorials,
- UIStyle
+ UIStyle,
+ TraitorMissions
}
public class ContentPackage
@@ -79,6 +80,7 @@ namespace Barotrauma
ContentType.LevelGenerationParameters,
ContentType.RandomEvents,
ContentType.Missions,
+ ContentType.TraitorMissions,
ContentType.BackgroundCreaturePrefabs,
ContentType.RuinConfig,
ContentType.NPCConversations,
@@ -96,7 +98,7 @@ namespace Barotrauma
public string Path
{
get;
- private set;
+ set;
}
public string SteamWorkshopUrl;
@@ -419,7 +421,7 @@ namespace Barotrauma
switch (contentFile.Type)
{
case ContentType.Submarine:
- return path == "Submarines";
+ return path == "Submarines" || path == "Mods";
default:
return path == "Mods";
}
@@ -460,8 +462,9 @@ namespace Barotrauma
return Files.Where(f => f.Type == type).Select(f => f.Path);
}
- public static void LoadAll(string folder)
+ public static void LoadAll()
{
+ string folder = ContentPackage.Folder;
if (!Directory.Exists(folder))
{
try
@@ -475,14 +478,23 @@ namespace Barotrauma
}
}
- string[] files = Directory.GetFiles(folder, "*.xml");
-
List.Clear();
+ string[] files = Directory.GetFiles(folder, "*.xml");
foreach (string filePath in files)
{
- ContentPackage package = new ContentPackage(filePath);
- List.Add(package);
+ List.Add(new ContentPackage(filePath));
+ }
+
+ string[] modDirectories = Directory.GetDirectories("Mods");
+ foreach (string modDirectory in modDirectories)
+ {
+ if (System.IO.Path.GetFileName(modDirectory.TrimEnd(System.IO.Path.DirectorySeparatorChar)) == "ExampleMod") { continue; }
+ string modFilePath = System.IO.Path.Combine(modDirectory, Steam.SteamManager.MetadataFileName);
+ if (File.Exists(modFilePath))
+ {
+ List.Add(new ContentPackage(modFilePath));
+ }
}
}
@@ -505,7 +517,7 @@ namespace Barotrauma
public class ContentFile
{
- public readonly string Path;
+ public string Path;
public ContentType Type;
public Workshop.Item WorkShopItem;
diff --git a/Barotrauma/BarotraumaShared/Source/CoroutineManager.cs b/Barotrauma/BarotraumaShared/Source/CoroutineManager.cs
index f0297a6a4..b9af5f57a 100644
--- a/Barotrauma/BarotraumaShared/Source/CoroutineManager.cs
+++ b/Barotrauma/BarotraumaShared/Source/CoroutineManager.cs
@@ -142,6 +142,7 @@ namespace Barotrauma
}
catch (Exception e)
{
+ handle.Exception = e;
DebugConsole.ThrowError("Coroutine \"" + handle.Name + "\" has thrown an exception", e);
}
}
@@ -182,7 +183,7 @@ namespace Barotrauma
{
if (handle.Thread.ThreadState.HasFlag(ThreadState.Stopped))
{
- if ((CoroutineStatus)handle.Coroutine.Current == CoroutineStatus.Failure)
+ if (handle.Exception!=null || (CoroutineStatus)handle.Coroutine.Current == CoroutineStatus.Failure)
{
DebugConsole.ThrowError("Coroutine \"" + handle.Name + "\" has failed");
}
diff --git a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs
index 71be5c15c..aa41e2699 100644
--- a/Barotrauma/BarotraumaShared/Source/DebugConsole.cs
+++ b/Barotrauma/BarotraumaShared/Source/DebugConsole.cs
@@ -438,7 +438,7 @@ namespace Barotrauma
});
}));
- commands.Add(new Command("banip", "banip [ip]: Ban the IP address from the server.", null));
+ commands.Add(new Command("banendpoint|banip", "banendpoint [endpoint]: Ban the IP address/SteamID from the server.", null));
commands.Add(new Command("teleportcharacter|teleport", "teleport [character name]: Teleport the specified character to the position of the cursor. If the name parameter is omitted, the controlled character will be teleported.", (string[] args) =>
{
@@ -674,6 +674,11 @@ namespace Barotrauma
},null));
#if DEBUG
+ commands.Add(new Command("crash", "crash: Crashes the game.", (string[] args) =>
+ {
+ throw new Exception("crash command issued");
+ }));
+
commands.Add(new Command("teleportsub", "teleportsub [start/end]: Teleport the submarine to the start or end of the level. WARNING: does not take outposts into account, so often leads to physics glitches. Only use for debugging.", (string[] args) =>
{
if (Submarine.MainSub == null || Level.Loaded == null) return;
@@ -960,6 +965,7 @@ namespace Barotrauma
}));
#if DEBUG
+ /*TODO: reimplement
commands.Add(new Command("simulatedlatency", "simulatedlatency [minimumlatencyseconds] [randomlatencyseconds]: applies a simulated latency to network messages. Useful for simulating real network conditions when testing the multiplayer locally.", (string[] args) =>
{
if (args.Count() < 2 || (GameMain.NetworkMember == null)) return;
@@ -1029,7 +1035,7 @@ namespace Barotrauma
}
#endif
NewMessage("Set packet duplication to " + (int)(duplicates * 100) + "%.", Color.White);
- }));
+ }));*/
#endif
//"dummy commands" that only exist so that the server can give clients permissions to use them
@@ -1502,8 +1508,9 @@ namespace Barotrauma
public static void NewMessage(string msg, Color color, bool isCommand = false)
{
if (string.IsNullOrEmpty((msg))) return;
-
+
var newMsg = new ColoredText(msg, color, isCommand);
+
lock (queuedMessages)
{
queuedMessages.Enqueue(new ColoredText(msg, color, isCommand));
diff --git a/Barotrauma/BarotraumaShared/Source/Events/EventManager.cs b/Barotrauma/BarotraumaShared/Source/Events/EventManager.cs
index 23317eb03..cb5365874 100644
--- a/Barotrauma/BarotraumaShared/Source/Events/EventManager.cs
+++ b/Barotrauma/BarotraumaShared/Source/Events/EventManager.cs
@@ -36,6 +36,8 @@ namespace Barotrauma
private List selectedEventSets;
private EventManagerSettings settings;
+
+ private readonly bool isClient;
public float CurrentIntensity
{
@@ -51,13 +53,15 @@ namespace Barotrauma
{
events = new List();
selectedEventSets = new List();
+
+ isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
}
public bool Enabled = true;
public void StartRound(Level level)
{
- if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) return;
+ if (isClient) { return; }
var suitableSettings = EventManagerSettings.List.FindAll(s =>
level.Difficulty >= s.MinLevelDifficulty &&
@@ -193,13 +197,13 @@ namespace Barotrauma
public void Update(float deltaTime)
{
- if (!Enabled) return;
+ if (!Enabled) { return; }
//clients only calculate the intensity but don't create any events
//(the intensity is used for controlling the background music)
CalculateCurrentIntensity(deltaTime);
-
- if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) return;
+
+ if (isClient) { return; }
roundDuration += deltaTime;
diff --git a/Barotrauma/BarotraumaShared/Source/Events/Missions/Mission.cs b/Barotrauma/BarotraumaShared/Source/Events/Missions/Mission.cs
index 7df6131cd..a518a9bc0 100644
--- a/Barotrauma/BarotraumaShared/Source/Events/Missions/Mission.cs
+++ b/Barotrauma/BarotraumaShared/Source/Events/Missions/Mission.cs
@@ -32,7 +32,7 @@ namespace Barotrauma
private set { failureMessage = value; }
}
- private string description;
+ protected string description;
public virtual string Description
{
get { return description; }
@@ -155,6 +155,13 @@ namespace Barotrauma
return false;
}
+ protected void ShowMessage(int index)
+ {
+ ShowMessageProjSpecific(index);
+ }
+
+ partial void ShowMessageProjSpecific(int index);
+
///
/// End the mission and give a reward if it was completed successfully
///
diff --git a/Barotrauma/BarotraumaShared/Source/Events/Missions/MissionPrefab.cs b/Barotrauma/BarotraumaShared/Source/Events/Missions/MissionPrefab.cs
index 49827886d..486e84442 100644
--- a/Barotrauma/BarotraumaShared/Source/Events/Missions/MissionPrefab.cs
+++ b/Barotrauma/BarotraumaShared/Source/Events/Missions/MissionPrefab.cs
@@ -5,7 +5,7 @@ using System.Xml.Linq;
namespace Barotrauma
{
- enum MissionType
+ public enum MissionType
{
Random,
None,
diff --git a/Barotrauma/BarotraumaShared/Source/Events/Missions/MonsterMission.cs b/Barotrauma/BarotraumaShared/Source/Events/Missions/MonsterMission.cs
index e37aeed7d..66a92c012 100644
--- a/Barotrauma/BarotraumaShared/Source/Events/Missions/MonsterMission.cs
+++ b/Barotrauma/BarotraumaShared/Source/Events/Missions/MonsterMission.cs
@@ -28,6 +28,9 @@ namespace Barotrauma
{
monsterFile = prefab.ConfigElement.GetAttributeString("monsterfile", "");
monsterCount = prefab.ConfigElement.GetAttributeInt("monstercount", 1);
+
+ description = description.Replace("[monster]",
+ TextManager.Get("character." + System.IO.Path.GetFileNameWithoutExtension(monsterFile)));
}
public override void Start(Level level)
@@ -66,9 +69,9 @@ namespace Barotrauma
if (activeMonsters.Any()) { return; }
-#if CLIENT
+
ShowMessage(state);
-#endif
+
state = 1;
break;
}
diff --git a/Barotrauma/BarotraumaShared/Source/Events/Missions/SalvageMission.cs b/Barotrauma/BarotraumaShared/Source/Events/Missions/SalvageMission.cs
index 7d5d16917..f0728eb71 100644
--- a/Barotrauma/BarotraumaShared/Source/Events/Missions/SalvageMission.cs
+++ b/Barotrauma/BarotraumaShared/Source/Events/Missions/SalvageMission.cs
@@ -94,16 +94,15 @@ namespace Barotrauma
if (item.ParentInventory != null) item.body.FarseerBody.IsKinematic = false;
if (item.CurrentHull?.Submarine == null) return;
-#if CLIENT
ShowMessage(state);
-#endif
+
state = 1;
break;
case 1:
if (!Submarine.MainSub.AtEndPosition && !Submarine.MainSub.AtStartPosition) return;
-#if CLIENT
+
ShowMessage(state);
-#endif
+
state = 2;
break;
}
diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/CampaignMode.cs
index 514256b4d..da06dc538 100644
--- a/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/CampaignMode.cs
+++ b/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/CampaignMode.cs
@@ -6,7 +6,7 @@ using System.Xml.Linq;
namespace Barotrauma
{
- abstract class CampaignMode : GameMode
+ abstract partial class CampaignMode : GameMode
{
public readonly CargoManager CargoManager;
@@ -111,9 +111,8 @@ namespace Barotrauma
base.Update(deltaTime);
if (!IsRunning) { return; }
-#if CLIENT
- if (GameMain.Client != null) { return; }
-#endif
+ if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
+
if (!watchmenSpawned)
{
if (Level.Loaded.StartOutpost != null) { startWatchman = SpawnWatchman(Level.Loaded.StartOutpost); }
@@ -128,7 +127,7 @@ namespace Barotrauma
foreach (Character character in Character.CharacterList)
{
#if SERVER
- if (string.IsNullOrEmpty(character.OwnerClientIP)) { continue; }
+ if (string.IsNullOrEmpty(character.OwnerClientEndPoint)) { continue; }
#else
if (!CrewManager.GetCharacters().Contains(character)) { continue; }
#endif
diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/CharacterCampaignData.cs b/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/CharacterCampaignData.cs
index a3eebbc8f..ae0e46e56 100644
--- a/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/CharacterCampaignData.cs
+++ b/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/CharacterCampaignData.cs
@@ -13,7 +13,7 @@ namespace Barotrauma
public readonly string Name;
- public string ClientIP
+ public string ClientEndPoint
{
get;
private set;
@@ -42,7 +42,7 @@ namespace Barotrauma
public CharacterCampaignData(XElement element)
{
Name = element.GetAttributeString("name", "Unnamed");
- ClientIP = element.GetAttributeString("ip", "");
+ ClientEndPoint = element.GetAttributeString("endpoint", null) ?? element.GetAttributeString("ip", "");
string steamID = element.GetAttributeString("steamid", "");
if (!string.IsNullOrEmpty(steamID))
{
@@ -69,7 +69,7 @@ namespace Barotrauma
{
XElement element = new XElement("CharacterCampaignData",
new XAttribute("name", Name),
- new XAttribute("ip", ClientIP),
+ new XAttribute("endpoint", ClientEndPoint),
new XAttribute("steamid", SteamID));
CharacterInfo?.Save(element);
diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/GameMode.cs b/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/GameMode.cs
index 0b3515ced..78936c9f8 100644
--- a/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/GameMode.cs
+++ b/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/GameMode.cs
@@ -64,7 +64,7 @@ namespace Barotrauma
isRunning = true;
}
- public virtual void MsgBox() { }
+ public virtual void ShowStartMessage() { }
public virtual void AddToGUIUpdateList()
{
diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/MultiPlayerCampaign.cs
index 72ce67d80..9c7f8c86d 100644
--- a/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/MultiPlayerCampaign.cs
+++ b/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/MultiPlayerCampaign.cs
@@ -3,7 +3,6 @@ using Microsoft.Xna.Framework;
using System;
using System.Linq;
using System.Xml.Linq;
-using Lidgren.Network;
using System.Collections.Generic;
using System.IO;
@@ -98,14 +97,11 @@ namespace Barotrauma
GameMain.GameSession.EndRound("");
+ //client character has spawned this round -> remove old data (and replace with an up-to-date one if the client still has an alive character)
+ characterData.RemoveAll(cd => cd.HasSpawned);
+
foreach (Client c in GameMain.Server.ConnectedClients)
{
- if (c.HasSpawned)
- {
- //client has spawned this round -> remove old data (and replace with new one if the client still has an alive character)
- characterData.RemoveAll(cd => cd.MatchesClient(c));
- }
-
if (c.Character?.Info != null && !c.Character.IsDead)
{
characterData.Add(new CharacterCampaignData(c));
diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/Source/GameSession/GameSession.cs
index 30bf32cab..2a478e78f 100644
--- a/Barotrauma/BarotraumaShared/Source/GameSession/GameSession.cs
+++ b/Barotrauma/BarotraumaShared/Source/GameSession/GameSession.cs
@@ -260,7 +260,7 @@ namespace Barotrauma
if (GameMode != null)
{
- GameMode.MsgBox();
+ GameMode.ShowStartMessage();
if (GameMode is MultiPlayerCampaign mpCampaign && GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
diff --git a/Barotrauma/BarotraumaShared/Source/GameSettings.cs b/Barotrauma/BarotraumaShared/Source/GameSettings.cs
index 8783df978..b036c1ed7 100644
--- a/Barotrauma/BarotraumaShared/Source/GameSettings.cs
+++ b/Barotrauma/BarotraumaShared/Source/GameSettings.cs
@@ -46,6 +46,8 @@ namespace Barotrauma
public bool PauseOnFocusLost { get; set; }
public bool MuteOnFocusLost { get; set; }
+ public bool DynamicRangeCompressionEnabled { get; set; }
+ public bool VoipAttenuationEnabled { get; set; }
public bool UseDirectionalVoiceChat { get; set; }
public enum VoiceMode
@@ -176,9 +178,9 @@ namespace Barotrauma
#if CLIENT
if (GameMain.SoundManager != null)
{
- GameMain.SoundManager.SetCategoryGainMultiplier("default", soundVolume);
- GameMain.SoundManager.SetCategoryGainMultiplier("ui", soundVolume);
- GameMain.SoundManager.SetCategoryGainMultiplier("waterambience", soundVolume);
+ GameMain.SoundManager.SetCategoryGainMultiplier("default", soundVolume, 0);
+ GameMain.SoundManager.SetCategoryGainMultiplier("ui", soundVolume, 0);
+ GameMain.SoundManager.SetCategoryGainMultiplier("waterambience", soundVolume, 0);
}
#endif
}
@@ -191,7 +193,7 @@ namespace Barotrauma
{
musicVolume = MathHelper.Clamp(value, 0.0f, 1.0f);
#if CLIENT
- GameMain.SoundManager?.SetCategoryGainMultiplier("music", musicVolume);
+ GameMain.SoundManager?.SetCategoryGainMultiplier("music", musicVolume, 0);
#endif
}
}
@@ -203,7 +205,7 @@ namespace Barotrauma
{
voiceChatVolume = MathHelper.Clamp(value, 0.0f, 1.0f);
#if CLIENT
- GameMain.SoundManager?.SetCategoryGainMultiplier("voip", voiceChatVolume * 20.0f);
+ GameMain.SoundManager?.SetCategoryGainMultiplier("voip", voiceChatVolume * 20.0f, 0);
#endif
}
}
@@ -286,7 +288,7 @@ namespace Barotrauma
public GameSettings()
{
- ContentPackage.LoadAll(ContentPackage.Folder);
+ ContentPackage.LoadAll();
CompletedTutorialNames = new List();
LoadDefaultConfig();
@@ -443,6 +445,11 @@ namespace Barotrauma
LoadAudioSettings(doc);
LoadControls(doc);
LoadContentPackages(doc);
+
+#if DEBUG
+ WindowMode = WindowMode.Windowed;
+#endif
+
UnsavedSettings = false;
}
@@ -797,6 +804,8 @@ namespace Barotrauma
new XAttribute("voicechatvolume", voiceChatVolume),
new XAttribute("microphonevolume", microphoneVolume),
new XAttribute("muteonfocuslost", MuteOnFocusLost),
+ new XAttribute("dynamicrangecompressionenabled", DynamicRangeCompressionEnabled),
+ new XAttribute("voipattenuationenabled", VoipAttenuationEnabled),
new XAttribute("usedirectionalvoicechat", UseDirectionalVoiceChat),
new XAttribute("voicesetting", VoiceSetting),
new XAttribute("voicecapturedevice", VoiceCaptureDevice ?? ""),
@@ -910,7 +919,7 @@ namespace Barotrauma
}
AutoCheckUpdates = doc.Root.GetAttributeBool("autocheckupdates", AutoCheckUpdates);
sendUserStatistics = doc.Root.GetAttributeBool("senduserstatistics", sendUserStatistics);
- QuickStartSubmarineName = doc.Root.GetAttributeString("quickstartsubmarine", "");
+ QuickStartSubmarineName = doc.Root.GetAttributeString("quickstartsub", QuickStartSubmarineName);
useSteamMatchmaking = doc.Root.GetAttributeBool("usesteammatchmaking", useSteamMatchmaking);
requireSteamAuthentication = doc.Root.GetAttributeBool("requiresteamauthentication", requireSteamAuthentication);
EnableSplashScreen = doc.Root.GetAttributeBool("enablesplashscreen", EnableSplashScreen);
@@ -997,8 +1006,11 @@ namespace Barotrauma
{
SoundVolume = audioSettings.GetAttributeFloat("soundvolume", SoundVolume);
MusicVolume = audioSettings.GetAttributeFloat("musicvolume", MusicVolume);
+ DynamicRangeCompressionEnabled = audioSettings.GetAttributeBool("dynamicrangecompressionenabled", DynamicRangeCompressionEnabled);
+ VoipAttenuationEnabled = audioSettings.GetAttributeBool("voipattenuationenabled", VoipAttenuationEnabled);
VoiceChatVolume = audioSettings.GetAttributeFloat("voicechatvolume", VoiceChatVolume);
MuteOnFocusLost = audioSettings.GetAttributeBool("muteonfocuslost", MuteOnFocusLost);
+
UseDirectionalVoiceChat = audioSettings.GetAttributeBool("usedirectionalvoicechat", UseDirectionalVoiceChat);
VoiceCaptureDevice = audioSettings.GetAttributeString("voicecapturedevice", VoiceCaptureDevice);
NoiseGateThreshold = audioSettings.GetAttributeFloat("noisegatethreshold", NoiseGateThreshold);
@@ -1108,6 +1120,8 @@ namespace Barotrauma
ChatOpen = true;
soundVolume = 0.5f;
musicVolume = 0.3f;
+ DynamicRangeCompressionEnabled = true;
+ VoipAttenuationEnabled = true;
voiceChatVolume = 0.5f;
microphoneVolume = 1.0f;
AutoCheckUpdates = true;
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs
index fcfdc692e..77eb57a86 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/DockingPort.cs
@@ -82,7 +82,7 @@ namespace Barotrauma.Items.Components
}
}
}
-
+
public DockingPort(Item item, XElement element)
: base(item, element)
{
@@ -160,7 +160,7 @@ namespace Barotrauma.Items.Components
if (DockingTarget != null)
{
Undock();
- }
+ }
if (target.item.Submarine == item.Submarine)
{
@@ -168,7 +168,7 @@ namespace Barotrauma.Items.Components
DockingTarget = null;
return;
}
-
+
target.InitializeLinks();
if (!item.linkedTo.Contains(target.item)) item.linkedTo.Add(target.item);
@@ -193,7 +193,7 @@ namespace Barotrauma.Items.Components
Math.Sign(DockingTarget.item.WorldPosition.X - item.WorldPosition.X) :
Math.Sign(DockingTarget.item.WorldPosition.Y - item.WorldPosition.Y);
DockingTarget.DockingDir = -DockingDir;
-
+
if (door != null && DockingTarget.door != null)
{
WayPoint myWayPoint = WayPoint.WayPointList.Find(wp => door.LinkedGap == wp.ConnectedGap);
@@ -205,7 +205,7 @@ namespace Barotrauma.Items.Components
targetWayPoint.linkedTo.Add(myWayPoint);
}
}
-
+
CreateJoint(false);
#if SERVER
@@ -246,7 +246,7 @@ namespace Barotrauma.Items.Components
else if (DockingTarget.item.Submarine.PhysicsBody.Mass < item.Submarine.PhysicsBody.Mass ||
item.Submarine.IsOutpost)
{
- DockingTarget.item.Submarine.SubBody.SetPosition(item.Submarine.SubBody.Position - ConvertUnits.ToDisplayUnits(jointDiff));
+ DockingTarget.item.Submarine.SubBody.SetPosition(DockingTarget.item.Submarine.SubBody.Position - ConvertUnits.ToDisplayUnits(jointDiff));
}
ConnectWireBetweenPorts();
@@ -401,7 +401,7 @@ namespace Barotrauma.Items.Components
}
private void CreateHulls()
- {
+ {
var hullRects = new Rectangle[] { item.WorldRect, DockingTarget.item.WorldRect };
var subs = new Submarine[] { item.Submarine, DockingTarget.item.Submarine };
@@ -416,18 +416,18 @@ namespace Barotrauma.Items.Components
{
DockingTarget.CreateDoorBody();
}
-
+
if (IsHorizontal)
{
if (hullRects[0].Center.X > hullRects[1].Center.X)
{
hullRects = new Rectangle[] { DockingTarget.item.WorldRect, item.WorldRect };
- subs = new Submarine[] { DockingTarget.item.Submarine,item.Submarine };
+ subs = new Submarine[] { DockingTarget.item.Submarine, item.Submarine };
}
hullRects[0] = new Rectangle(hullRects[0].Center.X, hullRects[0].Y, ((int)DockedDistance / 2), hullRects[0].Height);
hullRects[1] = new Rectangle(hullRects[1].Center.X - ((int)DockedDistance / 2), hullRects[1].Y, ((int)DockedDistance / 2), hullRects[1].Height);
-
+
//expand hulls if needed, so there's no empty space between the sub's hulls and docking port hulls
int leftSubRightSide = int.MinValue, rightSubLeftSide = int.MaxValue;
foreach (Hull hull in Hull.hullList)
@@ -478,7 +478,7 @@ namespace Barotrauma.Items.Components
hullRects[1].Width += rightHullDiff;
}
}
-
+
for (int i = 0; i < 2; i++)
{
hullRects[i].Location -= MathUtils.ToPoint((subs[i].WorldPosition - subs[i].HiddenSubPosition));
@@ -503,7 +503,7 @@ namespace Barotrauma.Items.Components
hullRects = new Rectangle[] { DockingTarget.item.WorldRect, item.WorldRect };
subs = new Submarine[] { DockingTarget.item.Submarine, item.Submarine };
}
-
+
hullRects[0] = new Rectangle(hullRects[0].X, hullRects[0].Y + (int)(-hullRects[0].Height + DockedDistance) / 2, hullRects[0].Width, ((int)DockedDistance / 2));
hullRects[1] = new Rectangle(hullRects[1].X, hullRects[1].Y - hullRects[1].Height / 2, hullRects[1].Width, ((int)DockedDistance / 2));
@@ -954,7 +954,7 @@ namespace Barotrauma.Items.Components
#endif
}
- public void ServerWrite(Lidgren.Network.NetBuffer msg, Client c, object[] extraData = null)
+ public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
msg.Write(docked);
@@ -965,7 +965,7 @@ namespace Barotrauma.Items.Components
}
}
- public void ClientRead(ServerNetObject type, Lidgren.Network.NetBuffer msg, float sendingTime)
+ public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
bool isDocked = msg.ReadBoolean();
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs
index 7a34357e8..82c04b75a 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs
@@ -313,8 +313,7 @@ namespace Barotrauma.Items.Components
PredictedState = null;
}
}
-
- LinkedGap.Open = openState;
+ LinkedGap.Open = isBroken ? 1.0f : openState;
}
if (isClosing)
@@ -371,7 +370,7 @@ namespace Barotrauma.Items.Components
{
LinkedGap.AutoOrient();
}
- LinkedGap.Open = openState;
+ LinkedGap.Open = isBroken ? 1.0f : openState;
LinkedGap.PassAmbientLight = Window != Rectangle.Empty;
}
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs
index c896f1e39..a893e97ea 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs
@@ -1,6 +1,5 @@
using Barotrauma.Networking;
using FarseerPhysics;
-using Lidgren.Network;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Xml.Linq;
@@ -286,14 +285,21 @@ namespace Barotrauma.Items.Components
item.SetTransform(rightHand.SimPosition, 0.0f);
}
- bool alreadySelected = character.HasEquippedItem(item);
- if (picker.TrySelectItem(item) || picker.HasEquippedItem(item))
+ bool alreadyEquipped = character.HasEquippedItem(item);
+ bool canSelect = picker.TrySelectItem(item);
+
+ if (canSelect || picker.HasEquippedItem(item))
{
+ if (!canSelect)
+ {
+ character.DeselectItem(item);
+ }
+
item.body.Enabled = true;
IsActive = true;
#if SERVER
- if (!alreadySelected) GameServer.Log(character.LogName + " equipped " + item.Name, ServerLog.MessageType.ItemInteraction);
+ if (!alreadyEquipped) GameServer.Log(character.LogName + " equipped " + item.Name, ServerLog.MessageType.ItemInteraction);
#endif
}
}
@@ -557,7 +563,7 @@ namespace Barotrauma.Items.Components
DeattachFromWall();
}
}
-
+
public override XElement Save(XElement parentElement)
{
if (!attachable)
@@ -581,8 +587,8 @@ namespace Barotrauma.Items.Components
return saveElement;
}
-
- public override void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
+
+ public override void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
base.ServerWrite(msg, c, extraData);
if (!attachable || body == null) { return; }
@@ -592,11 +598,11 @@ namespace Barotrauma.Items.Components
msg.Write(body.SimPosition.Y);
}
- public override void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
+ public override void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
base.ClientRead(type, msg, sendingTime);
bool shouldBeAttached = msg.ReadBoolean();
- Vector2 simPosition = new Vector2(msg.ReadFloat(), msg.ReadFloat());
+ Vector2 simPosition = new Vector2(msg.ReadSingle(), msg.ReadSingle());
if (!attachable)
{
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/LevelResource.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/LevelResource.cs
index d557dd19d..547ebde12 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/LevelResource.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/LevelResource.cs
@@ -1,5 +1,4 @@
using Barotrauma.Networking;
-using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
@@ -116,7 +115,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(deattachTimer);
}
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/MeleeWeapon.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/MeleeWeapon.cs
index 1b7e9af30..631a520f2 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/MeleeWeapon.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/MeleeWeapon.cs
@@ -56,11 +56,9 @@ namespace Barotrauma.Items.Components
public MeleeWeapon(Item item, XElement element)
: base(item, element)
{
- //throwForce = ToolBox.GetAttributeFloat(element, "throwforce", 1.0f);
-
foreach (XElement subElement in element.Elements())
{
- if (subElement.Name.ToString().ToLowerInvariant() != "attack") continue;
+ if (subElement.Name.ToString().ToLowerInvariant() != "attack") { continue; }
attack = new Attack(subElement, item.Name + ", MeleeWeapon");
}
item.IsShootable = true;
@@ -70,23 +68,22 @@ namespace Barotrauma.Items.Components
public override bool Use(float deltaTime, Character character = null)
{
- if (character == null || reloadTimer > 0.0f) return false;
- if (Item.RequireAimToUse && !character.IsKeyDown(InputType.Aim) || hitting) return false;
+ if (character == null || reloadTimer > 0.0f) { return false; }
+ if (Item.RequireAimToUse && !character.IsKeyDown(InputType.Aim) || hitting) { return false; }
//don't allow hitting if the character is already hitting with another weapon
for (int i = 0; i < 2; i++ )
{
- if (character.SelectedItems[i] == null || character.SelectedItems[i] == Item) continue;
+ if (character.SelectedItems[i] == null || character.SelectedItems[i] == Item) { continue; }
var otherWeapon = character.SelectedItems[i].GetComponent();
- if (otherWeapon == null) continue;
-
- if (otherWeapon.hitting) return false;
+ if (otherWeapon == null) { continue; }
+ if (otherWeapon.hitting) { return false; }
}
SetUser(character);
- if (hitPos < MathHelper.PiOver4) return false;
+ if (hitPos < MathHelper.PiOver4) { return false; }
reloadTimer = reload;
@@ -94,21 +91,20 @@ namespace Barotrauma.Items.Components
item.body.FarseerBody.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall;
item.body.FarseerBody.OnCollision += OnCollision;
- foreach (Limb l in character.AnimController.Limbs)
+ if (!character.AnimController.InWater)
{
- //item.body.FarseerBody.IgnoreCollisionWith(l.body.FarseerBody);
-
- if (character.AnimController.InWater) continue;
- if (l.type == LimbType.LeftFoot || l.type == LimbType.LeftThigh || l.type == LimbType.LeftLeg) continue;
-
- if (l.type == LimbType.Head || l.type == LimbType.Torso)
+ foreach (Limb l in character.AnimController.Limbs)
{
- l.body.ApplyLinearImpulse(new Vector2(character.AnimController.Dir * 7.0f, -4.0f));
+ if (l.type == LimbType.LeftFoot || l.type == LimbType.LeftThigh || l.type == LimbType.LeftLeg) { continue; }
+ if (l.type == LimbType.Head || l.type == LimbType.Torso)
+ {
+ l.body.ApplyLinearImpulse(new Vector2(character.AnimController.Dir * 7.0f, -4.0f));
+ }
+ else
+ {
+ l.body.ApplyLinearImpulse(new Vector2(character.AnimController.Dir * 5.0f, -2.0f));
+ }
}
- else
- {
- l.body.ApplyLinearImpulse(new Vector2(character.AnimController.Dir * 5.0f, -2.0f));
- }
}
hitting = true;
@@ -121,7 +117,6 @@ namespace Barotrauma.Items.Components
public override void Drop(Character dropper)
{
base.Drop(dropper);
-
hitting = false;
hitPos = 0.0f;
}
@@ -133,17 +128,17 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
- if (!item.body.Enabled) return;
- if (!picker.HasSelectedItem(item)) IsActive = false;
+ if (!item.body.Enabled) { return; }
+ if (!picker.HasSelectedItem(item)) { IsActive = false; }
reloadTimer -= deltaTime;
if (reloadTimer < 0) { reloadTimer = 0; }
- if (!picker.IsKeyDown(InputType.Aim) && !hitting) hitPos = 0.0f;
+ if (!picker.IsKeyDown(InputType.Aim) && !hitting) { hitPos = 0.0f; }
ApplyStatusEffects(ActionType.OnActive, deltaTime, picker);
- if (item.body.Dir != picker.AnimController.Dir) Flip();
+ if (item.body.Dir != picker.AnimController.Dir) { Flip(); }
AnimController ac = picker.AnimController;
@@ -236,16 +231,16 @@ namespace Barotrauma.Items.Components
if (f2.Body.UserData is Limb)
{
targetLimb = (Limb)f2.Body.UserData;
- if (targetLimb.IsSevered || targetLimb.character == null) return false;
+ if (targetLimb.IsSevered || targetLimb.character == null) { return false; }
targetCharacter = targetLimb.character;
- if (targetCharacter == picker) return false;
+ if (targetCharacter == picker){ return false; }
if (AllowHitMultiple)
{
- if (hitTargets.Contains(targetCharacter)) return false;
+ if (hitTargets.Contains(targetCharacter)) { return false; }
}
else
{
- if (hitTargets.Any(t => t is Character)) return false;
+ if (hitTargets.Any(t => t is Character)) { return false; }
}
hitTargets.Add(targetCharacter);
}
@@ -256,11 +251,11 @@ namespace Barotrauma.Items.Components
targetLimb = targetCharacter.AnimController.GetLimb(LimbType.Torso); //Otherwise armor can be bypassed in strange ways
if (AllowHitMultiple)
{
- if (hitTargets.Contains(targetCharacter)) return false;
+ if (hitTargets.Contains(targetCharacter)) { return false; }
}
else
{
- if (hitTargets.Any(t => t is Character)) return false;
+ if (hitTargets.Any(t => t is Character)) { return false; }
}
hitTargets.Add(targetCharacter);
}
@@ -269,11 +264,11 @@ namespace Barotrauma.Items.Components
targetStructure = (Structure)f2.Body.UserData;
if (AllowHitMultiple)
{
- if (hitTargets.Contains(targetStructure)) return true;
+ if (hitTargets.Contains(targetStructure)) { return true; }
}
else
{
- if (hitTargets.Any(t => t is Structure)) return true;
+ if (hitTargets.Any(t => t is Structure)) { return true; }
}
hitTargets.Add(targetStructure);
}
@@ -303,8 +298,8 @@ namespace Barotrauma.Items.Components
return false;
}
}
-
- if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) return true;
+
+ if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return true; }
#if SERVER
if (GameMain.Server != null && targetCharacter != null) //TODO: Log structure hits
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Pickable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Pickable.cs
index 15844682f..5a770bceb 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Pickable.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Pickable.cs
@@ -3,7 +3,6 @@ using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
-using Lidgren.Network;
namespace Barotrauma.Items.Components
{
@@ -233,12 +232,12 @@ namespace Barotrauma.Items.Components
}
}
- public virtual void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
+ public virtual void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
msg.Write(activePicker == null ? (ushort)0 : activePicker.ID);
}
- public virtual void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
+ public virtual void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
ushort pickerID = msg.ReadUInt16();
if (pickerID == 0)
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs
index 54f8c1372..50fd0fa90 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs
@@ -54,6 +54,9 @@ namespace Barotrauma.Items.Components
[Serialize(false, false)]
public bool RepairMultiple { get; set; }
+ [Serialize(false, false)]
+ public bool RepairThroughHoles { get; set; }
+
[Serialize(0.0f, false)]
public float FireProbability { get; set; }
@@ -146,10 +149,24 @@ namespace Barotrauma.Items.Components
}
}
- Vector2 targetPosition = item.WorldPosition;
- targetPosition += new Vector2(
- (float)Math.Cos(item.body.Rotation),
- (float)Math.Sin(item.body.Rotation)) * Range * item.body.Dir;
+ Vector2 rayStart;
+ Vector2 sourcePos = character?.AnimController == null ? item.SimPosition : character.AnimController.AimSourceSimPos;
+ Vector2 barrelPos = item.SimPosition + ConvertUnits.ToSimUnits(TransformedBarrelPos);
+ //make sure there's no obstacles between the base of the item (or the shoulder of the character) and the end of the barrel
+ if (Submarine.PickBody(sourcePos, barrelPos, collisionCategory: Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking) == null)
+ {
+ //no obstacles -> we start the raycast at the end of the barrel
+ rayStart = ConvertUnits.ToSimUnits(item.WorldPosition + TransformedBarrelPos);
+ }
+ else
+ {
+ rayStart = ConvertUnits.ToSimUnits(item.WorldPosition);
+ }
+
+ Vector2 rayEnd = rayStart +
+ ConvertUnits.ToSimUnits(new Vector2(
+ (float)Math.Cos(item.body.Rotation),
+ (float)Math.Sin(item.body.Rotation)) * Range * item.body.Dir);
List ignoredBodies = new List();
foreach (Limb limb in character.AnimController.Limbs)
@@ -161,11 +178,8 @@ namespace Barotrauma.Items.Components
IsActive = true;
activeTimer = 0.1f;
-
- Vector2 rayStart = ConvertUnits.ToSimUnits(item.WorldPosition);
- Vector2 rayEnd = ConvertUnits.ToSimUnits(targetPosition);
-
- debugRayStartPos = item.WorldPosition;
+
+ debugRayStartPos = ConvertUnits.ToDisplayUnits(rayStart);
debugRayEndPos = ConvertUnits.ToDisplayUnits(rayEnd);
if (character.Submarine == null)
@@ -203,7 +217,7 @@ namespace Barotrauma.Items.Components
float lastPickedFraction = 0.0f;
if (RepairMultiple)
{
- var bodies = Submarine.PickBodies(rayStart, rayEnd, ignoredBodies, collisionCategories, ignoreSensors: false, allowInsideFixture: true);
+ var bodies = Submarine.PickBodies(rayStart, rayEnd, ignoredBodies, collisionCategories, ignoreSensors: RepairThroughHoles, allowInsideFixture: true);
lastPickedFraction = Submarine.LastPickedFraction;
Type lastHitType = null;
hitCharacters.Clear();
@@ -243,7 +257,7 @@ namespace Barotrauma.Items.Components
{
FixBody(user, deltaTime, degreeOfSuccess,
Submarine.PickBody(rayStart, rayEnd,
- ignoredBodies, collisionCategories, ignoreSensors: false,
+ ignoredBodies, collisionCategories, ignoreSensors: RepairThroughHoles,
customPredicate: (Fixture f) => { return f?.Body?.UserData != null; },
allowInsideFixture: true));
lastPickedFraction = Submarine.LastPickedFraction;
@@ -324,6 +338,7 @@ namespace Barotrauma.Items.Components
}
else if (targetBody.UserData is Character targetCharacter)
{
+ if (targetCharacter.Removed) { return false; }
targetCharacter.LastDamageSource = item;
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, new List() { targetCharacter });
FixCharacterProjSpecific(user, deltaTime, targetCharacter);
@@ -331,6 +346,7 @@ namespace Barotrauma.Items.Components
}
else if (targetBody.UserData is Limb targetLimb)
{
+ if (targetLimb.character == null || targetLimb.character.Removed) { return false; }
targetLimb.character.LastDamageSource = item;
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, new List() { targetLimb.character, targetLimb });
FixCharacterProjSpecific(user, deltaTime, targetLimb.character);
@@ -506,9 +522,9 @@ namespace Barotrauma.Items.Components
if (propertyName != "stuck") { continue; }
if (door.SerializableProperties == null || !door.SerializableProperties.TryGetValue(propertyName, out SerializableProperty property)) { continue; }
object value = property.GetValue(target);
- if (value.GetType() == typeof(float))
+ if (door.Stuck > 0)
{
- var progressBar = user.UpdateHUDProgressBar(door, door.Item.WorldPosition, (float)value / 100, Color.DarkGray * 0.5f, Color.White);
+ var progressBar = user.UpdateHUDProgressBar(door, door.Item.WorldPosition, door.Stuck / 100, Color.DarkGray * 0.5f, Color.White);
if (progressBar != null) { progressBar.Size = new Vector2(60.0f, 20.0f); }
}
}
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Throwable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Throwable.cs
index 45f4c2585..e3f4217c7 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Throwable.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Throwable.cs
@@ -6,12 +6,10 @@ namespace Barotrauma.Items.Components
{
class Throwable : Holdable
{
- float throwForce;
+ private float throwForce, throwPos;
+ private bool throwing, throwDone;
- float throwPos;
-
- bool throwing;
- bool throwDone;
+ private bool midAir;
[Serialize(1.0f, false)]
public float ThrowForce
@@ -57,7 +55,17 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
- if (!item.body.Enabled) return;
+ if (!item.body.Enabled) { return; }
+ if (midAir)
+ {
+ if (item.body.LinearVelocity.LengthSquared() < 0.01f)
+ {
+ item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform;
+ midAir = false;
+ }
+ return;
+ }
+
if (picker == null || picker.Removed || !picker.HasSelectedItem(item))
{
IsActive = false;
@@ -113,6 +121,10 @@ namespace Barotrauma.Items.Components
item.Drop(thrower, createNetworkEvent: GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer);
item.body.ApplyLinearImpulse(throwVector * throwForce * item.body.Mass * 3.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
+ //disable platform collisions until the item comes back to rest again
+ item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
+ midAir = true;
+
ac.GetLimb(LimbType.Head).body.ApplyLinearImpulse(throwVector * 10.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
ac.GetLimb(LimbType.Torso).body.ApplyLinearImpulse(throwVector * 10.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs
index 93b1b199d..c07b58a9f 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemComponent.cs
@@ -82,13 +82,14 @@ namespace Barotrauma.Items.Components
}
}
#endif
- if (AITarget != null) AITarget.Enabled = value;
isActive = value;
}
}
private bool drawable = true;
+ public List IsActiveConditionals;
+
public bool Drawable
{
get { return drawable; }
@@ -208,11 +209,6 @@ namespace Barotrauma.Items.Components
set;
}
- public AITarget AITarget
- {
- get;
- private set;
- }
///
/// How useful the item is in combat? Used by AI to decide which item it should use as a weapon. For the sake of clarity, use a value between 0 and 100 (not enforced).
@@ -267,6 +263,15 @@ namespace Barotrauma.Items.Components
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
+ case "activeconditional":
+ case "isactive":
+ IsActiveConditionals = IsActiveConditionals ?? new List();
+ foreach (XAttribute attribute in subElement.Attributes())
+ {
+ if (attribute.Name.ToString().ToLowerInvariant() == "targetitemcomponent") { continue; }
+ IsActiveConditionals.Add(new PropertyConditional(attribute));
+ }
+ break;
case "requireditem":
case "requireditems":
RelatedItem ri = RelatedItem.Load(subElement, item.Name);
@@ -308,12 +313,6 @@ namespace Barotrauma.Items.Components
effectList.Add(statusEffect);
- break;
- case "aitarget":
- AITarget = new AITarget(item, subElement)
- {
- Enabled = isActive
- };
break;
default:
if (LoadElemProjSpecific(subElement)) break;
@@ -474,12 +473,6 @@ namespace Barotrauma.Items.Components
delayedCorrectionCoroutine = null;
}
- if (AITarget != null)
- {
- AITarget.Remove();
- AITarget = null;
- }
-
RemoveComponentSpecific();
}
@@ -496,11 +489,6 @@ namespace Barotrauma.Items.Components
loopingSoundChannel = null;
}
#endif
- if (AITarget != null)
- {
- AITarget.Remove();
- AITarget = null;
- }
ShallowRemoveComponentSpecific();
}
@@ -788,6 +776,7 @@ namespace Barotrauma.Items.Components
public virtual void Reset()
{
SerializableProperties = SerializableProperty.DeserializeProperties(this, originalElement);
+ if (this is Pickable) { canBePicked = true; }
ParseMsg();
OverrideRequiredItems(originalElement);
}
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs
index a59edf726..4c0291752 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs
@@ -1,5 +1,4 @@
using Barotrauma.Networking;
-using Lidgren.Network;
using System;
using System.Linq;
using System.Xml.Linq;
@@ -84,6 +83,8 @@ namespace Barotrauma.Items.Components
progressState = Math.Min(progressTimer / deconstructTime, 1.0f);
if (progressTimer > deconstructTime)
{
+ int emptySlots = outputContainer.Inventory.Items.Where(i => i == null).Count();
+
foreach (DeconstructItem deconstructProduct in targetItem.Prefab.DeconstructItems)
{
float percentageHealth = targetItem.Condition / targetItem.Prefab.Health;
@@ -100,13 +101,14 @@ namespace Barotrauma.Items.Components
itemPrefab.Health * deconstructProduct.OutCondition;
//container full, drop the items outside the deconstructor
- if (outputContainer.Inventory.Items.All(i => i != null))
+ if (emptySlots <= 0)
{
Entity.Spawner.AddToSpawnQueue(itemPrefab, item.Position, item.Submarine, condition);
}
else
{
Entity.Spawner.AddToSpawnQueue(itemPrefab, outputContainer.Inventory, condition);
+ emptySlots--;
}
}
@@ -196,6 +198,7 @@ namespace Barotrauma.Items.Components
if (inputContainer.Inventory.Items.All(i => i == null)) { active = false; }
IsActive = active;
+ currPowerConsumption = IsActive ? powerConsumption : 0.0f;
#if SERVER
if (user != null)
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Engine.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Engine.cs
index 74d56b690..1bfa76d6d 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Engine.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Engine.cs
@@ -3,7 +3,6 @@ using System;
using System.Globalization;
using System.Xml.Linq;
using Barotrauma.Networking;
-using Lidgren.Network;
namespace Barotrauma.Items.Components
{
@@ -98,11 +97,17 @@ namespace Barotrauma.Items.Components
UpdatePropellerDamage(deltaTime);
+ if (item.AiTarget != null)
+ {
+ var aiTarget = item.AiTarget;
+ aiTarget.SoundRange = MathHelper.Lerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, currForce.Length() / maxForce);
+ }
if (item.CurrentHull != null)
{
- item.CurrentHull.AiTarget.SoundRange = Math.Max(currForce.Length(), item.CurrentHull.AiTarget.SoundRange);
+ var aiTarget = item.CurrentHull.AiTarget;
+ float noise = MathHelper.Lerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, currForce.Length() / maxForce);
+ aiTarget.SoundRange = Math.Max(noise, aiTarget.SoundRange);
}
-
#if CLIENT
for (int i = 0; i < 5; i++)
{
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs
index 59d850056..73e41e196 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs
@@ -1,5 +1,4 @@
using Barotrauma.Networking;
-using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Pump.cs
index 7998e5b1d..2f358e309 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Pump.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Pump.cs
@@ -12,6 +12,8 @@ namespace Barotrauma.Items.Components
private float maxFlow;
private float? targetLevel;
+
+ private float controlLockTimer;
private bool hasPower;
@@ -57,18 +59,24 @@ namespace Barotrauma.Items.Components
currFlow = 0.0f;
hasPower = false;
+ controlLockTimer -= deltaTime;
if (targetLevel != null)
{
float hullPercentage = 0.0f;
- if (item.CurrentHull != null) hullPercentage = (item.CurrentHull.WaterVolume / item.CurrentHull.Volume) * 100.0f;
+ if (item.CurrentHull != null) { hullPercentage = (item.CurrentHull.WaterVolume / item.CurrentHull.Volume) * 100.0f; }
FlowPercentage = ((float)targetLevel - hullPercentage) * 10.0f;
+
+ if (controlLockTimer <= 0.0f)
+ {
+ targetLevel = null;
+ }
}
currPowerConsumption = powerConsumption * Math.Abs(flowPercentage / 100.0f);
//pumps consume more power when in a bad condition
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / item.MaxCondition);
- if (voltage < minVoltage) return;
+ if (voltage < minVoltage) { return; }
UpdateProjSpecific(deltaTime);
@@ -109,6 +117,7 @@ namespace Barotrauma.Items.Components
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out float tempSpeed))
{
flowPercentage = MathHelper.Clamp(tempSpeed, -100.0f, 100.0f);
+ controlLockTimer = 0.1f;
}
}
else if (connection.Name == "set_targetlevel")
@@ -116,6 +125,7 @@ namespace Barotrauma.Items.Components
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out float tempTarget))
{
targetLevel = MathHelper.Clamp((tempTarget + 100.0f) / 2.0f, 0.0f, 100.0f);
+ controlLockTimer = 0.1f;
}
}
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs
index f0244b06b..31c995ea5 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs
@@ -1,5 +1,4 @@
using Barotrauma.Networking;
-using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -271,7 +270,7 @@ namespace Barotrauma.Items.Components
//calculate how much external power there is in the grid
//(power coming from somewhere else than this reactor, e.g. batteries)
- float externalPower = CurrPowerConsumption - pt.CurrPowerConsumption;
+ float externalPower = Math.Max(CurrPowerConsumption - pt.CurrPowerConsumption, 0);
//reduce the external power from the load to prevent overloading the grid
load = Math.Max(load, pt.PowerLoad - externalPower);
}
@@ -288,10 +287,17 @@ namespace Barotrauma.Items.Components
if (item.CurrentHull != null)
{
- //the sound can be heard from 20 000 display units away when running at full power
- item.CurrentHull.SoundRange = Math.Max(
- (-currPowerConsumption / MaxPowerOutput) * 20000.0f,
- item.CurrentHull.AiTarget.SoundRange);
+ var aiTarget = item.CurrentHull.AiTarget;
+ float range = Math.Abs(currPowerConsumption) / MaxPowerOutput;
+ float noise = MathHelper.Lerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, range);
+ aiTarget.SoundRange = Math.Max(aiTarget.SoundRange, noise);
+ }
+
+ if (item.AiTarget != null)
+ {
+ var aiTarget = item.AiTarget;
+ float range = Math.Abs(currPowerConsumption) / MaxPowerOutput;
+ aiTarget.SoundRange = MathHelper.Lerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, range);
}
}
@@ -439,6 +445,8 @@ namespace Barotrauma.Items.Components
{
base.UpdateBroken(deltaTime, cam);
+ item.SendSignal(0, ((int)(temperature * 100.0f)).ToString(), "temperature_out", null);
+
currPowerConsumption = 0.0f;
Temperature -= deltaTime * 1000.0f;
targetFissionRate = Math.Max(targetFissionRate - deltaTime * 10.0f, 0.0f);
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Sonar.cs
index 726272639..646a953cd 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Sonar.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Sonar.cs
@@ -114,9 +114,9 @@ namespace Barotrauma.Items.Components
if (value == Mode.Passive)
{
currentPingIndex = -1;
- if (item.CurrentHull != null)
+ if (item.AiTarget != null)
{
- item.CurrentHull.AiTarget.SectorDegrees = 360.0f;
+ item.AiTarget.SectorDegrees = 360.0f;
}
}
#if CLIENT
@@ -168,15 +168,10 @@ namespace Barotrauma.Items.Components
var activePing = activePings[currentPingIndex];
if (activePing.State > 1.0f)
{
- if (item.CurrentHull != null)
- {
- item.CurrentHull.AiTarget.SoundRange = Math.Max(Range * activePing.State / zoom, item.CurrentHull.AiTarget.SoundRange);
- item.CurrentHull.AiTarget.SectorDegrees = activePing.IsDirectional ? DirectionalPingSector : 360.0f;
- item.CurrentHull.AiTarget.SectorDir = new Vector2(pingDirection.X, -pingDirection.Y);
- }
if (item.AiTarget != null)
{
- item.AiTarget.SoundRange = Math.Max(Range * activePing.State / zoom, item.AiTarget.SoundRange);
+ float range = MathUtils.InverseLerp(item.AiTarget.MinSoundRange, item.AiTarget.MaxSoundRange, Range * activePing.State / zoom);
+ item.AiTarget.SoundRange = MathHelper.Lerp(item.AiTarget.MinSoundRange, item.AiTarget.MaxSoundRange, range);
item.AiTarget.SectorDegrees = activePing.IsDirectional ? DirectionalPingSector : 360.0f;
item.AiTarget.SectorDir = new Vector2(pingDirection.X, -pingDirection.Y);
}
@@ -200,9 +195,9 @@ namespace Barotrauma.Items.Components
}
else
{
- if (item.CurrentHull != null)
+ if (item.AiTarget != null)
{
- item.CurrentHull.AiTarget.SectorDegrees = 360.0f;
+ item.AiTarget.SectorDegrees = 360.0f;
}
currentPingIndex = -1;
aiPingCheckPending = false;
@@ -345,7 +340,7 @@ namespace Barotrauma.Items.Components
}
}
- public void ServerRead(ClientNetObject type, Lidgren.Network.NetBuffer msg, Client c)
+ public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
{
bool isActive = msg.ReadBoolean();
bool directionalPing = useDirectionalPing;
@@ -388,7 +383,7 @@ namespace Barotrauma.Items.Components
#endif
}
- public void ServerWrite(Lidgren.Network.NetBuffer msg, Client c, object[] extraData = null)
+ public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
msg.Write(currentMode == Mode.Active);
if (currentMode == Mode.Active)
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs
index 9821a38ce..d92f80dd6 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs
@@ -522,7 +522,7 @@ namespace Barotrauma.Items.Components
}
}
- public void ServerRead(ClientNetObject type, Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c)
+ public void ServerRead(ClientNetObject type, IReadMessage msg, Barotrauma.Networking.Client c)
{
bool autoPilot = msg.ReadBoolean();
bool dockingButtonClicked = msg.ReadBoolean();
@@ -537,8 +537,8 @@ namespace Barotrauma.Items.Components
if (maintainPos)
{
newPosToMaintain = new Vector2(
- msg.ReadFloat(),
- msg.ReadFloat());
+ msg.ReadSingle(),
+ msg.ReadSingle());
}
else
{
@@ -547,7 +547,7 @@ namespace Barotrauma.Items.Components
}
else
{
- newSteeringInput = new Vector2(msg.ReadFloat(), msg.ReadFloat());
+ newSteeringInput = new Vector2(msg.ReadSingle(), msg.ReadSingle());
}
if (!item.CanClientAccess(c)) return;
@@ -587,7 +587,7 @@ namespace Barotrauma.Items.Components
unsentChanges = true;
}
- public void ServerWrite(Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c, object[] extraData = null)
+ public void ServerWrite(IWriteMessage msg, Barotrauma.Networking.Client c, object[] extraData = null)
{
msg.Write(autoPilot);
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs
index f74e81242..8008375d6 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs
@@ -1,5 +1,4 @@
using Barotrauma.Networking;
-using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs
index b1b2350cb..90dfc7663 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerTransfer.cs
@@ -64,6 +64,13 @@ namespace Barotrauma.Items.Components
set;
}
+ [Serialize(false, false)]
+ public bool Overload
+ {
+ get;
+ set;
+ }
+
//can the component transfer power
private bool canTransfer;
public bool CanTransfer
@@ -115,6 +122,8 @@ namespace Barotrauma.Items.Components
{
base.UpdateBroken(deltaTime, cam);
+ Overload = false;
+
if (!isBroken)
{
powerLoad = 0.0f;
@@ -128,7 +137,8 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
RefreshConnections();
- if (!CanTransfer) return;
+
+ if (!CanTransfer) { return; }
if (isBroken)
{
@@ -143,6 +153,8 @@ namespace Barotrauma.Items.Components
return;
}
+ Overload = false;
+
//reset and recalculate the power generated/consumed
//by the constructions connected to the grid
fullPower = 0.0f;
@@ -156,10 +168,11 @@ namespace Barotrauma.Items.Components
foreach (Powered p in connectedList)
{
PowerTransfer pt = p as PowerTransfer;
- if (pt == null || pt.updateCount == 0) continue;
+ if (pt == null || pt.updateCount == 0) { continue; }
- if (pt is RelayComponent != this is RelayComponent) continue;
+ if (pt is RelayComponent != this is RelayComponent) { continue; }
+ pt.Overload = false;
pt.powerLoad += (fullLoad - pt.powerLoad) / inertia;
pt.currPowerConsumption += (-fullPower - pt.currPowerConsumption) / inertia;
@@ -173,38 +186,38 @@ namespace Barotrauma.Items.Components
pt.Item.SendSignal(0, "", "power", null, voltage);
pt.Item.SendSignal(0, "", "power_out", null, voltage);
-#if CLIENT
- //damage the item if voltage is too high
- //(except if running as a client)
- if (GameMain.Client != null) continue;
-#endif
-
//items in a bad condition are more sensitive to overvoltage
- float maxOverVoltage = MathHelper.Lerp(OverloadVoltage * 0.75f, OverloadVoltage, item.Condition / item.MaxCondition);
+ float maxOverVoltage = MathHelper.Lerp(OverloadVoltage * 0.75f, OverloadVoltage, pt.item.Condition / pt.item.MaxCondition);
maxOverVoltage = Math.Max(OverloadVoltage, 1.0f);
//if the item can't be fixed, don't allow it to break
- if (!item.Repairables.Any() || !CanBeOverloaded) continue;
+ if (!pt.item.Repairables.Any() || !pt.CanBeOverloaded) { continue; }
//relays don't blow up if the power is higher than load, only if the output is high enough
//(i.e. enough power passing through the relay)
- if (this is RelayComponent) continue;
+ if (pt is RelayComponent) { continue; }
- if (-pt.currPowerConsumption < Math.Max(pt.powerLoad, 200.0f) * maxOverVoltage) continue;
+ if (-pt.currPowerConsumption < Math.Max(pt.powerLoad, 200.0f) * maxOverVoltage) { continue; }
+ pt.Overload = true;
+#if CLIENT
+ //damage the item if voltage is too high
+ //(except if running as a client)
+ if (GameMain.Client != null) { continue; }
+#endif
float prevCondition = pt.item.Condition;
pt.item.Condition -= deltaTime * 10.0f;
if (pt.item.Condition <= 0.0f && prevCondition > 0.0f)
{
#if CLIENT
- SoundPlayer.PlaySound("zap", item.WorldPosition, hullGuess: item.CurrentHull);
+ SoundPlayer.PlaySound("zap", item.WorldPosition, hullGuess: pt.item.CurrentHull);
Vector2 baseVel = Rand.Vector(300.0f);
for (int i = 0; i < 10; i++)
{
var particle = GameMain.ParticleManager.CreateParticle("spark", pt.item.WorldPosition,
- baseVel + Rand.Vector(100.0f), 0.0f, item.CurrentHull);
+ baseVel + Rand.Vector(100.0f), 0.0f, pt.item.CurrentHull);
if (particle != null) particle.Size *= Rand.Range(0.5f, 1.0f);
}
@@ -214,8 +227,8 @@ namespace Barotrauma.Items.Components
GameMain.GameSession.EventManager.CurrentIntensity : 0.5f;
//higher probability for fires if the current intensity is low
- if (FireProbability > 0.0f &&
- Rand.Range(0.0f, 1.0f) < MathHelper.Lerp(FireProbability, FireProbability * 0.1f, currentIntensity))
+ if (pt.FireProbability > 0.0f &&
+ Rand.Range(0.0f, 1.0f) < MathHelper.Lerp(pt.FireProbability, pt.FireProbability * 0.1f, currentIntensity))
{
new FireSource(pt.item.WorldPosition);
}
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs
index 22e00938a..3699aa7bd 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs
@@ -409,11 +409,11 @@ namespace Barotrauma.Items.Components
private bool OnProjectileCollision(Fixture target, Vector2 collisionNormal)
{
- if (User != null && User.Removed) User = null;
+ if (User != null && User.Removed) { User = null; }
- if (IgnoredBodies.Contains(target.Body)) return false;
+ if (IgnoredBodies.Contains(target.Body)) { return false; }
- if (target.UserData is Item) return false;
+ if (target.UserData is Item) { return false; }
if (target.CollisionCategories == Physics.CollisionCharacter && !(target.Body.UserData is Limb))
{
@@ -447,10 +447,21 @@ namespace Barotrauma.Items.Components
if (attack != null) { attackResult = attack.DoDamage(User, structure, item.WorldPosition, 1.0f); }
}
- if (character != null) character.LastDamageSource = item;
- ApplyStatusEffects(ActionType.OnUse, 1.0f, character, target.Body.UserData as Limb, user: user);
- ApplyStatusEffects(ActionType.OnImpact, 1.0f, character, target.Body.UserData as Limb, user: user);
-
+ if (character != null) { character.LastDamageSource = item; }
+
+ if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
+ {
+ ApplyStatusEffects(ActionType.OnUse, 1.0f, character, target.Body.UserData as Limb, user: user);
+ ApplyStatusEffects(ActionType.OnImpact, 1.0f, character, target.Body.UserData as Limb, user: user);
+#if SERVER
+ if (GameMain.NetworkMember.IsServer)
+ {
+ GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnUse });
+ GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnImpact });
+ }
+#endif
+ }
+
item.body.FarseerBody.OnCollision -= OnProjectileCollision;
item.body.CollisionCategories = Physics.CollisionItem;
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Repairable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Repairable.cs
index 183fed5da..012e970f9 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Repairable.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Repairable.cs
@@ -1,5 +1,4 @@
using Barotrauma.Networking;
-using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
@@ -10,12 +9,15 @@ namespace Barotrauma.Items.Components
partial class Repairable : ItemComponent, IServerSerializable, IClientSerializable
{
public static float SkillIncreasePerRepair = 5.0f;
+ public static float SkillIncreasePerSabotage = 3.0f;
private string header;
private float deteriorationTimer;
+ private float deteriorateAlwaysResetTimer;
bool wasBroken;
+ bool wasGoodCondition;
public float LastActiveTime;
@@ -40,14 +42,21 @@ namespace Barotrauma.Items.Components
set;
}
- [Serialize(50.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, ToolTip = "The item won't deteriorate spontaneously if the condition is below this value. For example, if set to 10, the condition will spontaneously drop to 10 and then stop dropping (unless the item is damaged further by external factors).")]
+ [Serialize(50.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, ToolTip = "The item won't deteriorate spontaneously if the condition is below this value. For example, if set to 10, the condition will spontaneously drop to 10 and then stop dropping (unless the item is damaged further by external factors). Percentages of max condition.")]
public float MinDeteriorationCondition
{
get;
set;
}
- [Serialize(80.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, ToolTip = "The condition of the item has to be below this before the repair UI becomes usable.")]
+ [Serialize(0f, true)]
+ public float MinSabotageCondition
+ {
+ get;
+ set;
+ }
+
+ [Serialize(80.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, ToolTip = "The condition of the item has to be below this before the repair UI becomes usable. Percentages of max condition.")]
public float ShowRepairUIThreshold
{
get;
@@ -76,16 +85,20 @@ namespace Barotrauma.Items.Components
set;
}
- private Character currentFixer;
- public Character CurrentFixer
+ public Character CurrentFixer { get; private set; }
+
+ public enum FixActions : int
{
- get { return currentFixer; }
- set
- {
- if (currentFixer == value || item.IsFullCondition) return;
- if (currentFixer != null) currentFixer.AnimController.Anim = AnimController.Animation.None;
- currentFixer = value;
- }
+ None = 0,
+ Repair = 1,
+ Sabotage = 2
+ }
+
+ private FixActions currentFixerAction = FixActions.None;
+ public FixActions CurrentFixerAction
+ {
+ get => currentFixerAction;
+ private set { currentFixerAction = value; }
}
public Repairable(Item item, XElement element)
@@ -105,18 +118,41 @@ namespace Barotrauma.Items.Components
public override void OnItemLoaded()
{
deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay);
-
-#if SERVER
- //let the clients know the initial deterioration delay
- item.CreateServerEvent(this);
-#endif
}
partial void InitProjSpecific(XElement element);
- public void StartRepairing(Character character)
+ public bool StartRepairing(Character character, FixActions action)
{
- CurrentFixer = character;
+ if (character == null || character.IsDead || action == FixActions.None)
+ {
+ DebugConsole.ThrowError("Invalid repair command!");
+ return false;
+ }
+ else
+ {
+ CurrentFixer = character;
+ CurrentFixerAction = action;
+ return true;
+ }
+ }
+
+ public bool StopRepairing(Character character)
+ {
+ if (CurrentFixer == character)
+ {
+ CurrentFixer.AnimController.Anim = AnimController.Animation.None;
+ CurrentFixer = null;
+ currentFixerAction = FixActions.None;
+#if SERVER
+ item.CreateServerEvent(this);
+#endif
+ return true;
+ }
+ else
+ {
+ return false;
+ }
}
public override void UpdateBroken(float deltaTime, Camera cam)
@@ -129,7 +165,7 @@ namespace Barotrauma.Items.Components
deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay);
item.Condition = item.Prefab.Health;
#if SERVER
- //let the clients know the initial deterioration delay
+ //let the clients know the deterioration delay
item.CreateServerEvent(this);
#endif
}
@@ -140,6 +176,18 @@ namespace Barotrauma.Items.Components
if (CurrentFixer == null)
{
+ if (deteriorateAlwaysResetTimer > 0.0f)
+ {
+ deteriorateAlwaysResetTimer -= deltaTime;
+ if (deteriorateAlwaysResetTimer <= 0.0f)
+ {
+ DeteriorateAlways = false;
+#if SERVER
+ //let the clients know the deterioration delay
+ item.CreateServerEvent(this);
+#endif
+ }
+ }
if (!ShouldDeteriorate()) { return; }
if (item.Condition > 0.0f)
{
@@ -155,7 +203,7 @@ namespace Barotrauma.Items.Components
return;
}
- if (item.Condition > MinDeteriorationCondition)
+ if (item.ConditionPercentage > MinDeteriorationCondition)
{
item.Condition -= DeteriorationSpeed * deltaTime;
}
@@ -163,10 +211,9 @@ namespace Barotrauma.Items.Components
return;
}
- if (Item.IsFullCondition || CurrentFixer.SelectedConstruction != item || !currentFixer.CanInteractWith(item))
+ if (CurrentFixer != null && (CurrentFixer.SelectedConstruction != item || !CurrentFixer.CanInteractWith(item) || CurrentFixer.IsDead))
{
- currentFixer.AnimController.Anim = AnimController.Animation.None;
- currentFixer = null;
+ StopRepairing(CurrentFixer);
return;
}
@@ -177,44 +224,89 @@ namespace Barotrauma.Items.Components
float successFactor = requiredSkills.Count == 0 ? 1.0f : 0.0f;
//item must have been below the repair threshold for the player to get an achievement or XP for repairing it
- if (item.Condition < ShowRepairUIThreshold)
+ if (item.ConditionPercentage < ShowRepairUIThreshold)
{
wasBroken = true;
}
+ if (item.ConditionPercentage > MinSabotageCondition)
+ {
+ wasGoodCondition = true;
+ }
float fixDuration = MathHelper.Lerp(FixDurationLowSkill, FixDurationHighSkill, successFactor);
- if (fixDuration <= 0.0f)
+ if (currentFixerAction == FixActions.Repair)
{
- item.Condition = item.MaxCondition;
+ if (fixDuration <= 0.0f)
+ {
+ item.Condition = item.MaxCondition;
+ }
+ else
+ {
+ float conditionIncrease = deltaTime / (fixDuration / item.MaxCondition);
+ item.Condition += conditionIncrease;
+#if SERVER
+ GameMain.Server.KarmaManager.OnItemRepaired(CurrentFixer, this, conditionIncrease);
+#endif
+ }
+
+ if (item.IsFullCondition)
+ {
+ if (wasBroken)
+ {
+ foreach (Skill skill in requiredSkills)
+ {
+ float characterSkillLevel = CurrentFixer.GetSkillLevel(skill.Identifier);
+ CurrentFixer.Info.IncreaseSkillLevel(skill.Identifier,
+ SkillIncreasePerRepair / Math.Max(characterSkillLevel, 1.0f),
+ CurrentFixer.WorldPosition + Vector2.UnitY * 100.0f);
+ }
+
+ SteamAchievementManager.OnItemRepaired(item, CurrentFixer);
+ deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay);
+ wasBroken = false;
+ }
+ StopRepairing(CurrentFixer);
+ }
+ }
+ else if (currentFixerAction == FixActions.Sabotage)
+ {
+ if (fixDuration <= 0.0f)
+ {
+ item.Condition = item.MaxCondition * (MinSabotageCondition / 100);
+ }
+ else
+ {
+ float conditionDecrease = deltaTime / (fixDuration / item.MaxCondition);
+ item.Condition -= conditionDecrease;
+ }
+
+ if (item.ConditionPercentage <= MinSabotageCondition)
+ {
+ if (wasGoodCondition)
+ {
+ foreach (Skill skill in requiredSkills)
+ {
+ float characterSkillLevel = CurrentFixer.GetSkillLevel(skill.Identifier);
+ CurrentFixer.Info.IncreaseSkillLevel(skill.Identifier,
+ SkillIncreasePerSabotage / Math.Max(characterSkillLevel, 1.0f),
+ CurrentFixer.WorldPosition + Vector2.UnitY * 100.0f);
+ }
+
+ deteriorationTimer = 0.0f;
+ deteriorateAlwaysResetTimer = item.Condition / DeteriorationSpeed;
+ DeteriorateAlways = true;
+ item.Condition = item.MaxCondition * (MinSabotageCondition / 100);
+ wasGoodCondition = false;
+ }
+ StopRepairing(CurrentFixer);
+ }
}
else
{
- float conditionIncrease = deltaTime / (fixDuration / item.MaxCondition);
- item.Condition += conditionIncrease;
-#if SERVER
- GameMain.Server.KarmaManager.OnItemRepaired(CurrentFixer, this, conditionIncrease);
-#endif
- }
-
- if (wasBroken && item.IsFullCondition)
- {
- foreach (Skill skill in requiredSkills)
- {
- float characterSkillLevel = CurrentFixer.GetSkillLevel(skill.Identifier);
- CurrentFixer.Info.IncreaseSkillLevel(skill.Identifier,
- SkillIncreasePerRepair / Math.Max(characterSkillLevel, 1.0f),
- CurrentFixer.WorldPosition + Vector2.UnitY * 100.0f);
- }
- SteamAchievementManager.OnItemRepaired(item, currentFixer);
- deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay);
- wasBroken = false;
-#if SERVER
- item.CreateServerEvent(this);
-#endif
+ throw new NotImplementedException(currentFixerAction.ToString());
}
}
-
partial void UpdateProjSpecific(float deltaTime);
private bool ShouldDeteriorate()
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Connection.cs
index 541036a31..cb9a09a9d 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Connection.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Connection.cs
@@ -218,6 +218,16 @@ namespace Barotrauma.Items.Components
public void SetWire(int index, Wire wire)
{
+ Wire previousWire = wires[index];
+ if (wire != previousWire && previousWire != null)
+ {
+ var otherConnection = previousWire.OtherConnection(this);
+ if (otherConnection != null)
+ {
+ otherConnection.recipientsDirty = true;
+ }
+ }
+
wires[index] = wire;
recipientsDirty = true;
if (wire != null)
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs
index 781568b39..1416ce582 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/ConnectionPanel.cs
@@ -1,6 +1,5 @@
using Barotrauma.Networking;
using FarseerPhysics;
-using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -19,7 +18,9 @@ namespace Barotrauma.Items.Components
/// Wires that have been disconnected from the panel, but not removed completely (visible at the bottom of the connection panel).
///
public readonly HashSet DisconnectedWires = new HashSet();
-
+
+ private List disconnectedWireIds;
+
[Serialize(false, true), Editable(ToolTip = "Locked connection panels cannot be rewired in-game.")]
public bool Locked
{
@@ -64,6 +65,19 @@ namespace Barotrauma.Items.Components
{
c.ConnectLinked();
}
+
+ if (disconnectedWireIds != null)
+ {
+ foreach (ushort disconnectedWireId in disconnectedWireIds)
+ {
+ if (!(Entity.FindEntityByID(disconnectedWireId) is Item wireItem)) { continue; }
+ Wire wire = wireItem.GetComponent();
+ if (wire != null)
+ {
+ DisconnectedWires.Add(wire);
+ }
+ }
+ }
}
public override void OnItemLoaded()
@@ -193,6 +207,8 @@ namespace Barotrauma.Items.Components
{
loadedConnections[i].wireId.CopyTo(Connections[i].wireId, 0);
}
+
+ disconnectedWireIds = element.GetAttributeUshortArray("disconnectedwires", new ushort[0]).ToList();
}
public override XElement Save(XElement parentElement)
@@ -204,6 +220,11 @@ namespace Barotrauma.Items.Components
c.Save(componentElement);
}
+ if (DisconnectedWires.Count > 0)
+ {
+ componentElement.Add(new XAttribute("disconnectedwires", string.Join(",", DisconnectedWires.Select(w => w.Item.ID))));
+ }
+
return componentElement;
}
@@ -214,6 +235,14 @@ namespace Barotrauma.Items.Components
protected override void RemoveComponentSpecific()
{
+ foreach (Wire wire in DisconnectedWires.ToList())
+ {
+ if (wire.OtherConnection(null) == null) //wire not connected to anything else
+ {
+ wire.Item.Drop(null);
+ }
+ }
+
DisconnectedWires.Clear();
foreach (Connection c in Connections)
{
@@ -233,7 +262,7 @@ namespace Barotrauma.Items.Components
}
}
- public void ClientWrite(NetBuffer msg, object[] extraData = null)
+ public void ClientWrite(IWriteMessage msg, object[] extraData = null)
{
foreach (Connection connection in Connections)
{
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/CustomInterface.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/CustomInterface.cs
index a91d38da2..5cd9ab35b 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/CustomInterface.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/CustomInterface.cs
@@ -1,5 +1,4 @@
using Barotrauma.Networking;
-using Lidgren.Network;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/LightComponent.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/LightComponent.cs
index 6ce8d7502..47d479c7b 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/LightComponent.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/LightComponent.cs
@@ -2,7 +2,6 @@
using System;
using System.Xml.Linq;
using Barotrauma.Networking;
-using Lidgren.Network;
#if CLIENT
using Microsoft.Xna.Framework.Graphics;
using Barotrauma.Lights;
@@ -251,10 +250,6 @@ namespace Barotrauma.Items.Components
light.Range = range;
#endif
}
- if (AITarget != null)
- {
- UpdateAITarget(AITarget);
- }
if (item.AiTarget != null)
{
UpdateAITarget(item.AiTarget);
@@ -267,6 +262,7 @@ namespace Barotrauma.Items.Components
public override void UpdateBroken(float deltaTime, Camera cam)
{
light.Color = Color.Transparent;
+ lightBrightness = 0.0f;
}
protected override void RemoveComponentSpecific()
@@ -298,7 +294,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(IsOn);
}
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/RelayComponent.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/RelayComponent.cs
index 3ea9643ad..e9db3ebc7 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/RelayComponent.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/RelayComponent.cs
@@ -1,5 +1,4 @@
using Barotrauma.Networking;
-using Lidgren.Network;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
@@ -104,12 +103,12 @@ namespace Barotrauma.Items.Components
IsOn = on;
}
- public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
+ public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
msg.Write(isOn);
}
- public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
+ public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
SetState(msg.ReadBoolean(), true);
}
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs
index 6b4c8736e..3ad02ff77 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/Wire.cs
@@ -1,5 +1,4 @@
using Barotrauma.Networking;
-using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -282,7 +281,7 @@ namespace Barotrauma.Items.Components
Structure attachTarget = Structure.GetAttachTarget(item.WorldPosition);
canPlaceNode = attachTarget != null;
- sub = attachTarget?.Submarine;
+ sub = sub ?? attachTarget?.Submarine;
newNodePos = sub == null ?
item.WorldPosition :
item.WorldPosition - sub.Position - sub.HiddenSubPosition;
@@ -333,7 +332,8 @@ namespace Barotrauma.Items.Components
}
else
{
- newNodePos = RoundNode(item.Position, item.CurrentHull) - sub.HiddenSubPosition;
+ newNodePos = RoundNode(item.Position, item.CurrentHull);
+ if (sub != null) { newNodePos -= sub.HiddenSubPosition; }
canPlaceNode = true;
}
@@ -724,7 +724,7 @@ namespace Barotrauma.Items.Components
base.RemoveComponentSpecific();
}
- public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
+ public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
int eventIndex = msg.ReadRangedInteger(0, (int)Math.Ceiling(MaxNodeCount / (float)MaxNodesPerNetworkEvent));
int nodeCount = msg.ReadRangedInteger(0, MaxNodesPerNetworkEvent);
@@ -738,7 +738,7 @@ namespace Barotrauma.Items.Components
for (int i = 0; i < nodeCount; i++)
{
- nodePositions[nodeStartIndex + i] = new Vector2(msg.ReadFloat(), msg.ReadFloat());
+ nodePositions[nodeStartIndex + i] = new Vector2(msg.ReadSingle(), msg.ReadSingle());
}
if (nodePositions.Any(n => !MathUtils.IsValid(n)))
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs
index e839e8c8b..1d4fb631f 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs
@@ -1,6 +1,5 @@
using Barotrauma.Networking;
using FarseerPhysics;
-using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -674,7 +673,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)
{
Item item = (Item)extraData[2];
msg.Write(item.Removed ? (ushort)0 : item.ID);
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs b/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs
index 85d352153..5848bbfc7 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Inventory.cs
@@ -1,6 +1,5 @@
using Barotrauma.Items.Components;
using Barotrauma.Networking;
-using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -375,7 +374,7 @@ namespace Barotrauma
}
}
- public void SharedWrite(NetBuffer msg, object[] extraData = null)
+ public void SharedWrite(IWriteMessage msg, object[] extraData = null)
{
for (int i = 0; i < capacity; i++)
{
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs
index 3ac7e5fe3..1606e1a78 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs
@@ -3,7 +3,6 @@ using Barotrauma.Networking;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
-using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
@@ -127,6 +126,24 @@ namespace Barotrauma
}
}
+ public delegate bool InventoryFilter(Inventory inventory);
+ public Inventory FindParentInventory(InventoryFilter filter)
+ {
+ if (parentInventory != null)
+ {
+ if (filter(parentInventory))
+ {
+ return parentInventory;
+ }
+ var owner = parentInventory.Owner as Item;
+ if (owner != null)
+ {
+ return owner.FindParentInventory(filter);
+ }
+ }
+ return null;
+ }
+
private Item container;
public Item Container
{
@@ -597,12 +614,15 @@ namespace Barotrauma
case "fabricate":
case "fabricable":
case "fabricableitem":
+ case "upgrade":
break;
case "staticbody":
StaticBodyConfig = subElement;
break;
case "aitarget":
aiTarget = new AITarget(this, subElement);
+ aiTarget.SoundRange = aiTarget.MinSoundRange;
+ aiTarget.SightRange = aiTarget.MinSightRange;
break;
default:
ItemComponent ic = ItemComponent.Load(subElement, this, itemPrefab.ConfigFile);
@@ -674,9 +694,6 @@ namespace Barotrauma
}
InitProjSpecific();
-
- InsertToList();
- ItemList.Add(this);
if (callOnItemLoaded)
{
@@ -686,6 +703,9 @@ namespace Barotrauma
}
}
+ InsertToList();
+ ItemList.Add(this);
+
DebugConsole.Log("Created " + Name + " (" + ID + ")");
}
@@ -988,7 +1008,24 @@ namespace Barotrauma
}
return false;
}
-
+
+ private bool ConditionalMatches(PropertyConditional conditional)
+ {
+ if (string.IsNullOrEmpty(conditional.TargetItemComponentName))
+ {
+ if (!conditional.Matches(this)) { return false; }
+ }
+ else
+ {
+ foreach (ItemComponent component in components)
+ {
+ if (component.Name != conditional.TargetItemComponentName) { continue; }
+ if (!conditional.Matches(component)) { return false; }
+ }
+ }
+ return true;
+ }
+
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb limb = null, bool isNetworkEvent = false)
{
if (!hasStatusEffectsOfType[(int)type]) { return; }
@@ -1103,6 +1140,7 @@ namespace Barotrauma
public override void Update(float deltaTime, Camera cam)
{
+ base.Update(deltaTime, cam);
//aitarget goes silent/invisible if the components don't keep it active
if (aiTarget != null)
{
@@ -1131,7 +1169,11 @@ namespace Barotrauma
foreach (ItemComponent ic in components)
{
- if (ic.Parent != null) ic.IsActive = ic.Parent.IsActive;
+ if (ic.Parent != null) { ic.IsActive = ic.Parent.IsActive; }
+ if (ic.IsActiveConditionals != null)
+ {
+ ic.IsActive = ic.IsActiveConditionals.All(conditional => ConditionalMatches(conditional));
+ }
#if CLIENT
if (!ic.WasUsed)
@@ -1827,7 +1869,7 @@ namespace Barotrauma
return allProperties;
}
- private void WritePropertyChange(NetBuffer msg, object[] extraData, bool inGameEditableOnly)
+ private void WritePropertyChange(IWriteMessage msg, object[] extraData, bool inGameEditableOnly)
{
var allProperties = inGameEditableOnly ? GetProperties() : GetProperties();
SerializableProperty property = extraData[1] as SerializableProperty;
@@ -1836,7 +1878,7 @@ namespace Barotrauma
var propertyOwner = allProperties.Find(p => p.Second == property);
if (allProperties.Count > 1)
{
- msg.WriteRangedInteger(0, allProperties.Count - 1, allProperties.FindIndex(p => p.Second == property));
+ msg.WriteRangedIntegerDeprecated(0, allProperties.Count - 1, allProperties.FindIndex(p => p.Second == property));
}
object value = property.GetValue(propertyOwner.First);
@@ -1908,7 +1950,7 @@ namespace Barotrauma
}
}
- private void ReadPropertyChange(NetBuffer msg, bool inGameEditableOnly, Client sender = null)
+ private void ReadPropertyChange(IReadMessage msg, bool inGameEditableOnly, Client sender = null)
{
var allProperties = inGameEditableOnly ? GetProperties() : GetProperties();
if (allProperties.Count == 0) { return; }
@@ -1940,7 +1982,7 @@ namespace Barotrauma
}
else if (type == typeof(float))
{
- float val = msg.ReadFloat();
+ float val = msg.ReadSingle();
if (allowEditing) property.TrySetValue(parentObject, val);
}
else if (type == typeof(int))
@@ -1960,17 +2002,17 @@ namespace Barotrauma
}
else if (type == typeof(Vector2))
{
- Vector2 val = new Vector2(msg.ReadFloat(), msg.ReadFloat());
+ Vector2 val = new Vector2(msg.ReadSingle(), msg.ReadSingle());
if (allowEditing) property.TrySetValue(parentObject, val);
}
else if (type == typeof(Vector3))
{
- Vector3 val = new Vector3(msg.ReadFloat(), msg.ReadFloat(), msg.ReadFloat());
+ Vector3 val = new Vector3(msg.ReadSingle(), msg.ReadSingle(), msg.ReadSingle());
if (allowEditing) property.TrySetValue(parentObject, val);
}
else if (type == typeof(Vector4))
{
- Vector4 val = new Vector4(msg.ReadFloat(), msg.ReadFloat(), msg.ReadFloat(), msg.ReadFloat());
+ Vector4 val = new Vector4(msg.ReadSingle(), msg.ReadSingle(), msg.ReadSingle(), msg.ReadSingle());
if (allowEditing) property.TrySetValue(parentObject, val);
}
else if (type == typeof(Point))
@@ -2038,7 +2080,7 @@ namespace Barotrauma
{
return null;
}
-
+
Rectangle rect = element.GetAttributeRect("rect", Rectangle.Empty);
if (rect.Width == 0 && rect.Height == 0)
{
@@ -2090,7 +2132,7 @@ namespace Barotrauma
foreach (XElement subElement in element.Elements())
{
ItemComponent component = unloadedComponents.Find(x => x.Name == subElement.Name.ToString());
- if (component == null) continue;
+ if (component == null) { continue; }
component.Load(subElement);
unloadedComponents.Remove(component);
@@ -2104,6 +2146,11 @@ namespace Barotrauma
item.SetActiveSprite();
+ if (submarine?.GameVersion != null)
+ {
+ SerializableProperty.UpgradeGameVersion(item, item.Prefab.ConfigElement, submarine.GameVersion);
+ }
+
foreach (ItemComponent component in item.components)
{
component.OnItemLoaded();
@@ -2164,6 +2211,7 @@ namespace Barotrauma
SerializableProperties = SerializableProperty.DeserializeProperties(this, Prefab.ConfigElement);
Sprite.ReloadXML();
SpriteDepth = Sprite.Depth;
+ condition = Prefab.Health;
components.ForEach(c => c.Reset());
}
@@ -2249,4 +2297,4 @@ namespace Barotrauma
partial void RemoveProjSpecific();
}
-}
\ No newline at end of file
+}
diff --git a/Barotrauma/BarotraumaShared/Source/Map/Hull.cs b/Barotrauma/BarotraumaShared/Source/Map/Hull.cs
index 2e8333b2c..602c23f6c 100644
--- a/Barotrauma/BarotraumaShared/Source/Map/Hull.cs
+++ b/Barotrauma/BarotraumaShared/Source/Map/Hull.cs
@@ -1,7 +1,6 @@
using Barotrauma.Networking;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
-using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -229,7 +228,13 @@ namespace Barotrauma
surface = rect.Y - rect.Height;
- aiTarget = new AITarget(this);
+ aiTarget = new AITarget(this)
+ {
+ MinSightRange = 2000,
+ MaxSightRange = 5000,
+ MaxSoundRange = 5000,
+ SoundRange = 0
+ };
hullList.Add(this);
@@ -418,13 +423,14 @@ namespace Barotrauma
public override void Update(float deltaTime, Camera cam)
{
+ base.Update(deltaTime, cam);
UpdateProjSpecific(deltaTime, cam);
Oxygen -= OxygenDeteriorationSpeed * deltaTime;
FireSource.UpdateAll(FireSources, deltaTime);
- aiTarget.SightRange = Submarine == null ? 0.0f : Math.Max(Submarine.Velocity.Length() * 2000.0f, AITarget.StaticSightRange);
+ aiTarget.SightRange = Submarine == null ? aiTarget.MinSightRange : Submarine.Velocity.Length() / 2 * aiTarget.MaxSightRange;
aiTarget.SoundRange -= deltaTime * 1000.0f;
if (!update)
diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs
index 5c93cc301..28c012bf4 100644
--- a/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs
+++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/Level.cs
@@ -5,7 +5,6 @@ using Barotrauma.RuinGeneration;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Factories;
-using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -1673,7 +1672,7 @@ namespace Barotrauma
loaded = null;
}
- public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
+ public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
foreach (LevelWall levelWall in extraWalls)
{
diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObject.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObject.cs
index 723dcdd31..08432da37 100644
--- a/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObject.cs
+++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObject.cs
@@ -1,6 +1,5 @@
using Barotrauma.Networking;
using FarseerPhysics;
-using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -122,7 +121,7 @@ namespace Barotrauma
return "LevelObject (" + ActivePrefab.Name + ")";
}
- public void ServerWrite(NetBuffer msg, Client c)
+ public void ServerWrite(IWriteMessage msg, Client c)
{
for (int j = 0; j < Triggers.Count; j++)
{
diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObjectManager.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObjectManager.cs
index 19f6b09e5..3528cfee9 100644
--- a/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObjectManager.cs
+++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObjectManager.cs
@@ -3,7 +3,6 @@ using Barotrauma.Particles;
#endif
using Barotrauma.Networking;
using FarseerPhysics;
-using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -407,10 +406,10 @@ namespace Barotrauma
partial void RemoveProjSpecific();
- public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
+ public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
LevelObject obj = extraData[0] as LevelObject;
- msg.WriteRangedInteger(0, objects.Count, objects.IndexOf(obj));
+ msg.WriteRangedIntegerDeprecated(0, objects.Count, objects.IndexOf(obj));
obj.ServerWrite(msg, c);
}
}
diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelTrigger.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelTrigger.cs
index 0bcb4a4e7..0d27b15b7 100644
--- a/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelTrigger.cs
+++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelTrigger.cs
@@ -2,7 +2,6 @@
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
-using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -598,7 +597,7 @@ namespace Barotrauma
return vel.ClampLength(ConvertUnits.ToDisplayUnits(ForceVelocityLimit)) * currentForceFluctuation;
}
- public void ServerWrite(NetBuffer msg, Client c)
+ public void ServerWrite(IWriteMessage msg, Client c)
{
if (ForceFluctuationStrength > 0.0f)
{
diff --git a/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs
index 97e93dd95..53c6e4171 100644
--- a/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs
+++ b/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs
@@ -339,6 +339,11 @@ namespace Barotrauma
hull.Update(deltaTime, cam);
}
+ foreach (Structure structure in Structure.WallList)
+ {
+ structure.Update(deltaTime, cam);
+ }
+
foreach (Gap gap in Gap.GapList)
{
gap.Update(deltaTime, cam);
diff --git a/Barotrauma/BarotraumaShared/Source/Map/MapEntityPrefab.cs b/Barotrauma/BarotraumaShared/Source/Map/MapEntityPrefab.cs
index 7e308f7f4..732d6f999 100644
--- a/Barotrauma/BarotraumaShared/Source/Map/MapEntityPrefab.cs
+++ b/Barotrauma/BarotraumaShared/Source/Map/MapEntityPrefab.cs
@@ -40,6 +40,18 @@ namespace Barotrauma
get { return name; }
}
+ public string GetItemNameTextId()
+ {
+ var textId = $"entityname.{Identifier}";
+ return TextManager.ContainsTag(textId) ? textId : null;
+ }
+
+ public string GetHullNameTextId()
+ {
+ var textId = $"roomname.{Identifier}";
+ return TextManager.ContainsTag(textId) ? textId : null;
+ }
+
//Used to differentiate between items when saving/loading
//Allows changing the name of an item without breaking existing subs or having multiple items with the same name
public string Identifier
diff --git a/Barotrauma/BarotraumaShared/Source/Map/Structure.cs b/Barotrauma/BarotraumaShared/Source/Map/Structure.cs
index 0211c3d84..7808fb626 100644
--- a/Barotrauma/BarotraumaShared/Source/Map/Structure.cs
+++ b/Barotrauma/BarotraumaShared/Source/Map/Structure.cs
@@ -4,7 +4,6 @@ using FarseerPhysics;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
using FarseerPhysics.Factories;
-using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
@@ -373,7 +372,12 @@ namespace Barotrauma
// Only add ai targets automatically to submarine/outpost walls
if (aiTarget == null && HasBody && Tags.Contains("wall") && submarine != null && !Prefab.NoAITarget)
{
- aiTarget = new AITarget(this);
+ aiTarget = new AITarget(this)
+ {
+ MinSightRange = 2000,
+ MaxSightRange = 5000,
+ MaxSoundRange = 0
+ };
}
InsertToList();
@@ -460,15 +464,16 @@ namespace Barotrauma
{
if (IsHorizontal)
{
- xsections = (int)Math.Ceiling((float)rect.Width / WallSectionSize);
+ //equivalent to (int)Math.Ceiling((double)rect.Width / WallSectionSize) without the potential for floating point indeterminism
+ xsections = (rect.Width + WallSectionSize - 1) / WallSectionSize;
Sections = new WallSection[xsections];
- width = (int)WallSectionSize;
+ width = WallSectionSize;
}
else
{
- ysections = (int)Math.Ceiling((float)rect.Height / WallSectionSize);
+ ysections = (rect.Height + WallSectionSize - 1) / WallSectionSize;
Sections = new WallSection[ysections];
- height = (int)WallSectionSize;
+ height = WallSectionSize;
}
}
@@ -1184,21 +1189,32 @@ namespace Barotrauma
ID = (ushort)int.Parse(element.Attribute("ID").Value)
};
+ SerializableProperty.DeserializeProperties(s, element);
+
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString())
{
case "section":
int index = subElement.GetAttributeInt("i", -1);
- if (index == -1) continue;
- s.Sections[index].damage = subElement.GetAttributeFloat("damage", 0.0f);
+ if (index == -1) { continue; }
+
+ if (index < 0 || index >= s.SectionCount)
+ {
+ string errorMsg = $"Error while loading structure \"{s.Name}\". Section damage index out of bounds. Index: {index}, section count: {s.SectionCount}.";
+ DebugConsole.ThrowError(errorMsg);
+ GameAnalyticsManager.AddErrorEventOnce("Structure.Load:SectionIndexOutOfBounds", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
+ }
+ else
+ {
+ s.Sections[index].damage = subElement.GetAttributeFloat("damage", 0.0f);
+ }
break;
}
}
if (element.GetAttributeBool("flippedx", false)) s.FlipX(false);
if (element.GetAttributeBool("flippedy", false)) s.FlipY(false);
- SerializableProperty.DeserializeProperties(s, element);
//structures with a body drop a shadow by default
if (element.Attribute("usedropshadow") == null)
@@ -1277,5 +1293,14 @@ namespace Barotrauma
{
SerializableProperties = SerializableProperty.DeserializeProperties(this, Prefab.ConfigElement);
}
+
+ public override void Update(float deltaTime, Camera cam)
+ {
+ base.Update(deltaTime, cam);
+ if (aiTarget != null)
+ {
+ aiTarget.SightRange = Submarine == null ? aiTarget.MinSightRange : Submarine.Velocity.Length() / 2 * aiTarget.MaxSightRange;
+ }
+ }
}
}
diff --git a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs
index 6d25e9c13..8331c8af6 100644
--- a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs
+++ b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs
@@ -3,7 +3,6 @@ using Barotrauma.Networking;
using Barotrauma.RuinGeneration;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
-using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -1143,7 +1142,6 @@ namespace Barotrauma
savedSubmarines.Add(sub);
}
-
public static void RefreshSavedSub(string filePath)
{
string fullPath = Path.GetFullPath(filePath);
@@ -1154,12 +1152,15 @@ namespace Barotrauma
savedSubmarines[i].Dispose();
}
}
- var sub = new Submarine(filePath);
- if (!sub.IsFileCorrupted)
+ if (File.Exists(filePath))
{
- savedSubmarines.Add(sub);
+ var sub = new Submarine(filePath);
+ if (!sub.IsFileCorrupted)
+ {
+ savedSubmarines.Add(sub);
+ }
+ savedSubmarines = savedSubmarines.OrderBy(s => s.filePath ?? "").ToList();
}
- savedSubmarines = savedSubmarines.OrderBy(s => s.filePath ?? "").ToList();
}
public static void RefreshSavedSubs()
@@ -1210,6 +1211,15 @@ namespace Barotrauma
}
}
+ var contentPackageSubs = ContentPackage.GetFilesOfType(GameMain.Config.SelectedContentPackages, ContentType.Submarine);
+ foreach (string subPath in contentPackageSubs)
+ {
+ if (!filePaths.Any(fp => Path.GetFullPath(fp) == Path.GetFullPath(subPath)))
+ {
+ filePaths.Add(subPath);
+ }
+ }
+
foreach (string path in filePaths)
{
var sub = new Submarine(path);
diff --git a/Barotrauma/BarotraumaShared/Source/Networking/ChatMessage.cs b/Barotrauma/BarotraumaShared/Source/Networking/ChatMessage.cs
index b89da7dfc..984a42c28 100644
--- a/Barotrauma/BarotraumaShared/Source/Networking/ChatMessage.cs
+++ b/Barotrauma/BarotraumaShared/Source/Networking/ChatMessage.cs
@@ -1,5 +1,4 @@
using Barotrauma.Items.Components;
-using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
@@ -7,9 +6,9 @@ using System.Text;
namespace Barotrauma.Networking
{
- enum ChatMessageType
+ public enum ChatMessageType
{
- Default, Error, Dead, Server, Radio, Private, Console, MessageBox, Order, ServerLog
+ Default, Error, Dead, Server, Radio, Private, Console, MessageBox, Order, ServerLog, ServerMessageBox
}
partial class ChatMessage
diff --git a/Barotrauma/BarotraumaShared/Source/Networking/Client.cs b/Barotrauma/BarotraumaShared/Source/Networking/Client.cs
index 192b4eb19..8eab04a50 100644
--- a/Barotrauma/BarotraumaShared/Source/Networking/Client.cs
+++ b/Barotrauma/BarotraumaShared/Source/Networking/Client.cs
@@ -1,4 +1,4 @@
-using Lidgren.Network;
+using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -12,7 +12,8 @@ namespace Barotrauma.Networking
public string Name;
public byte ID;
-
+ public UInt64 SteamID;
+
public Character.TeamType TeamID;
private Character character;
@@ -38,6 +39,12 @@ namespace Barotrauma.Networking
HasSpawned = true;
#if CLIENT
GameMain.GameSession?.CrewManager?.SetPlayerVoiceIconState(this, muted, mutedLocally);
+
+ if (character == GameMain.Client.Character && GameMain.Client.SpawnAsTraitor)
+ {
+ character.IsTraitor = true;
+ character.TraitorCurrentObjective = GameMain.Client.TraitorFirstObjective;
+ }
#endif
}
}
@@ -179,7 +186,7 @@ namespace Barotrauma.Networking
}
}
- public void WritePermissions(NetBuffer msg)
+ public void WritePermissions(IWriteMessage msg)
{
msg.Write(ID);
msg.Write((UInt16)Permissions);
@@ -192,7 +199,7 @@ namespace Barotrauma.Networking
}
}
}
- public static void ReadPermissions(NetBuffer inc, out ClientPermissions permissions, out List permittedCommands)
+ public static void ReadPermissions(IReadMessage inc, out ClientPermissions permissions, out List permittedCommands)
{
UInt16 permissionsInt = inc.ReadUInt16();
@@ -221,7 +228,7 @@ namespace Barotrauma.Networking
}
}
- public void ReadPermissions(NetIncomingMessage inc)
+ public void ReadPermissions(IReadMessage inc)
{
ClientPermissions permissions = ClientPermissions.None;
List permittedCommands = new List();
diff --git a/Barotrauma/BarotraumaShared/Source/Networking/ClientPermissions.cs b/Barotrauma/BarotraumaShared/Source/Networking/ClientPermissions.cs
index 95adfe26a..42163b1c3 100644
--- a/Barotrauma/BarotraumaShared/Source/Networking/ClientPermissions.cs
+++ b/Barotrauma/BarotraumaShared/Source/Networking/ClientPermissions.cs
@@ -7,7 +7,7 @@ using System.Xml.Linq;
namespace Barotrauma.Networking
{
[Flags]
- enum ClientPermissions
+ public enum ClientPermissions
{
None = 0x0,
ManageRound = 0x1,
diff --git a/Barotrauma/BarotraumaShared/Source/Networking/INetSerializable.cs b/Barotrauma/BarotraumaShared/Source/Networking/INetSerializable.cs
index ddef41f2a..9313fec02 100644
--- a/Barotrauma/BarotraumaShared/Source/Networking/INetSerializable.cs
+++ b/Barotrauma/BarotraumaShared/Source/Networking/INetSerializable.cs
@@ -1,6 +1,4 @@
-using Lidgren.Network;
-
-namespace Barotrauma.Networking
+namespace Barotrauma.Networking
{
interface INetSerializable { }
@@ -10,10 +8,10 @@ namespace Barotrauma.Networking
interface IClientSerializable : INetSerializable
{
#if CLIENT
- void ClientWrite(NetBuffer msg, object[] extraData = null);
+ void ClientWrite(IWriteMessage msg, object[] extraData = null);
#endif
#if SERVER
- void ServerRead(ClientNetObject type, NetBuffer msg, Client c);
+ void ServerRead(ClientNetObject type, IReadMessage msg, Client c);
#endif
}
@@ -23,10 +21,10 @@ namespace Barotrauma.Networking
interface IServerSerializable : INetSerializable
{
#if SERVER
- void ServerWrite(NetBuffer msg, Client c, object[] extraData = null);
+ void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null);
#endif
#if CLIENT
- void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime);
+ void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime);
#endif
}
}
diff --git a/Barotrauma/BarotraumaShared/Source/Networking/KarmaManager.cs b/Barotrauma/BarotraumaShared/Source/Networking/KarmaManager.cs
index dacacbe4d..18692dfb5 100644
--- a/Barotrauma/BarotraumaShared/Source/Networking/KarmaManager.cs
+++ b/Barotrauma/BarotraumaShared/Source/Networking/KarmaManager.cs
@@ -53,12 +53,12 @@ namespace Barotrauma
public float ExtinguishFireKarmaIncrease { get; set; }
- private float allowedWireDisconnectionsPerMinute;
- [Serialize(5.0f, true)]
- public float AllowedWireDisconnectionsPerMinute
+ private int allowedWireDisconnectionsPerMinute;
+ [Serialize(5, true)]
+ public int AllowedWireDisconnectionsPerMinute
{
get { return allowedWireDisconnectionsPerMinute; }
- set { allowedWireDisconnectionsPerMinute = Math.Max(0.0f, value); }
+ set { allowedWireDisconnectionsPerMinute = Math.Max(0, value); }
}
[Serialize(6.0f, true)]
@@ -76,6 +76,9 @@ namespace Barotrauma
[Serialize(1.0f, true)]
public float KickBanThreshold { get; set; }
+ [Serialize(0, true)]
+ public int KicksBeforeBan { get; set; }
+
[Serialize(10.0f, true)]
public float KarmaNotificationInterval { get; set; }
diff --git a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEvent.cs b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEvent.cs
index d93b9258e..4d642e749 100644
--- a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEvent.cs
+++ b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEvent.cs
@@ -1,5 +1,4 @@
-using Lidgren.Network;
-using System;
+using System;
namespace Barotrauma.Networking
{
diff --git a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs
index ed4b325f2..b335e23c0 100644
--- a/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs
+++ b/Barotrauma/BarotraumaShared/Source/Networking/NetEntityEvent/NetEntityEventManager.cs
@@ -1,5 +1,4 @@
-using Lidgren.Network;
-using System;
+using System;
using System.Collections.Generic;
namespace Barotrauma.Networking
@@ -11,10 +10,10 @@ namespace Barotrauma.Networking
///
/// Write the events to the outgoing message. The recipient parameter is only needed for ServerEntityEventManager
///
- protected void Write(NetOutgoingMessage msg, List eventsToSync, out List sentEvents, Client recipient = null)
+ protected void Write(IWriteMessage msg, List eventsToSync, out List sentEvents, Client recipient = null)
{
//write into a temporary buffer so we can write the number of events before the actual data
- NetBuffer tempBuffer = new NetBuffer();
+ IWriteMessage tempBuffer = new WriteOnlyMessage();
sentEvents = new List();
@@ -22,7 +21,7 @@ namespace Barotrauma.Networking
foreach (NetEntityEvent e in eventsToSync)
{
//write into a temporary buffer so we can write the length before the actual data
- NetBuffer tempEventBuffer = new NetBuffer();
+ IWriteMessage tempEventBuffer = new WriteOnlyMessage();
try
{
WriteEvent(tempEventBuffer, e, recipient);
@@ -67,7 +66,7 @@ namespace Barotrauma.Networking
tempBuffer.Write(e.EntityID);
tempBuffer.Write((byte)tempEventBuffer.LengthBytes);
- tempBuffer.Write(tempEventBuffer);
+ tempBuffer.Write(tempEventBuffer.Buffer, 0, tempEventBuffer.LengthBytes);
tempBuffer.WritePadBits();
sentEvents.Add(e);
@@ -78,10 +77,10 @@ namespace Barotrauma.Networking
{
msg.Write(eventsToSync[0].ID);
msg.Write((byte)eventCount);
- msg.Write(tempBuffer);
+ msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes);
}
}
- protected abstract void WriteEvent(NetBuffer buffer, NetEntityEvent entityEvent, Client recipient = null);
+ protected abstract void WriteEvent(IWriteMessage buffer, NetEntityEvent entityEvent, Client recipient = null);
}
}
diff --git a/Barotrauma/BarotraumaShared/Source/Networking/NetworkMember.cs b/Barotrauma/BarotraumaShared/Source/Networking/NetworkMember.cs
index b4d1b39a3..f85e882a8 100644
--- a/Barotrauma/BarotraumaShared/Source/Networking/NetworkMember.cs
+++ b/Barotrauma/BarotraumaShared/Source/Networking/NetworkMember.cs
@@ -1,5 +1,4 @@
using Barotrauma.Items.Components;
-using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -9,9 +8,6 @@ namespace Barotrauma.Networking
{
enum ClientPacketHeader
{
- REQUEST_AUTH, //ask the server if a password is needed, if so we'll get nonce for encryption
- REQUEST_STEAMAUTH, //the same as REQUEST_AUTH, but in addition we want to authenticate the player's Steam ID
- REQUEST_INIT, //ask the server to give you initialization
UPDATE_LOBBY, //update state in lobby
UPDATE_INGAME, //update state ingame
@@ -64,7 +60,9 @@ namespace Barotrauma.Networking
QUERY_STARTGAME, //ask the clients whether they're ready to start
STARTGAME, //start a new round
- ENDGAME
+ ENDGAME,
+
+ TRAITOR_MESSAGE
}
enum ServerNetObject
{
@@ -78,6 +76,14 @@ namespace Barotrauma.Networking
ENTITY_EVENT_INITIAL,
}
+ enum TraitorMessageType
+ {
+ Server,
+ ServerMessageBox,
+ Objective,
+ Console
+ }
+
enum VoteType
{
Unknown,
@@ -94,6 +100,7 @@ namespace Barotrauma.Networking
Banned,
Kicked,
ServerShutdown,
+ ServerCrashed,
ServerFull,
AuthenticationRequired,
SteamAuthenticationRequired,
@@ -132,13 +139,7 @@ namespace Barotrauma.Networking
#if DEBUG
public Dictionary messageCount = new Dictionary();
#endif
-
- public NetPeer NetPeer
- {
- get;
- protected set;
- }
-
+
protected string name;
protected ServerSettings serverSettings;
@@ -154,12 +155,6 @@ namespace Barotrauma.Networking
public bool ShowNetStats;
- public int Port
- {
- get;
- set;
- }
-
public int TickRate
{
get { return serverSettings.TickRate; }
@@ -205,13 +200,7 @@ namespace Barotrauma.Networking
{
get { return serverSettings; }
}
-
- public NetPeerConfiguration NetPeerConfiguration
- {
- get;
- protected set;
- }
-
+
public bool CanUseRadio(Character sender)
{
if (sender == null) return false;
diff --git a/Barotrauma/BarotraumaShared/Source/Networking/OrderChatMessage.cs b/Barotrauma/BarotraumaShared/Source/Networking/OrderChatMessage.cs
index 85523d2fa..9892bb379 100644
--- a/Barotrauma/BarotraumaShared/Source/Networking/OrderChatMessage.cs
+++ b/Barotrauma/BarotraumaShared/Source/Networking/OrderChatMessage.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;
-using Lidgren.Network;
namespace Barotrauma.Networking
{
diff --git a/Barotrauma/BarotraumaShared/Source/Networking/Primitives/Enums.cs b/Barotrauma/BarotraumaShared/Source/Networking/Primitives/Enums.cs
new file mode 100644
index 000000000..7d9151a33
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/Source/Networking/Primitives/Enums.cs
@@ -0,0 +1,37 @@
+using System;
+
+namespace Barotrauma.Networking
+{
+ public enum DeliveryMethod : byte
+ {
+ Unreliable = 0x0,
+ Reliable = 0x1,
+ ReliableOrdered = 0x2
+ }
+
+ public enum ConnectionInitialization : byte
+ {
+ //used by all peer implementations
+ SteamTicketAndVersion = 0x1,
+ Password = 0x2,
+ Success = 0x0,
+
+ //used only by SteamP2P implementations
+ ConnectionStarted = 0x3
+ }
+
+ [Flags]
+ public enum PacketHeader : byte
+ {
+ //used by all peer implementations
+ None = 0x0,
+ IsCompressed = 0x1,
+ IsConnectionInitializationStep = 0x2,
+
+ //used only by SteamP2P implementations
+ IsDisconnectMessage = 0x4,
+ IsServerMessage = 0x8,
+ IsHeartbeatMessage = 0x10
+ }
+}
+
diff --git a/Barotrauma/BarotraumaShared/Source/Networking/Primitives/Message/IReadMessage.cs b/Barotrauma/BarotraumaShared/Source/Networking/Primitives/Message/IReadMessage.cs
new file mode 100644
index 000000000..8a6d8f9e4
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/Source/Networking/Primitives/Message/IReadMessage.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Barotrauma.Networking
+{
+ public interface IReadMessage
+ {
+ bool ReadBoolean();
+ void ReadPadBits();
+ byte ReadByte();
+ UInt16 ReadUInt16();
+ Int16 ReadInt16();
+ UInt32 ReadUInt32();
+ Int32 ReadInt32();
+ UInt64 ReadUInt64();
+ Int64 ReadInt64();
+ Single ReadSingle();
+ Double ReadDouble();
+ UInt32 ReadVariableUInt32();
+ String ReadString();
+ int ReadRangedInteger(int min, int max);
+ Single ReadRangedSingle(Single min, Single max, int bitCount);
+ byte[] ReadBytes(int numberOfBytes);
+
+ int BitPosition { get; set; }
+ int BytePosition { get; }
+ byte[] Buffer { get; }
+ int LengthBits { get; set; }
+ int LengthBytes { get; }
+
+ NetworkConnection Sender { get; }
+ }
+}
diff --git a/Barotrauma/BarotraumaShared/Source/Networking/Primitives/Message/IWriteMessage.cs b/Barotrauma/BarotraumaShared/Source/Networking/Primitives/Message/IWriteMessage.cs
new file mode 100644
index 000000000..4e5e87453
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/Source/Networking/Primitives/Message/IWriteMessage.cs
@@ -0,0 +1,33 @@
+using System;
+
+namespace Barotrauma.Networking
+{
+ public interface IWriteMessage
+ {
+ void Write(bool val);
+ void WritePadBits();
+ void Write(byte val);
+ void Write(Int16 val);
+ void Write(UInt16 val);
+ void Write(Int32 val);
+ void Write(UInt32 val);
+ void Write(Int64 val);
+ void Write(UInt64 val);
+ void Write(Single val);
+ void Write(Double val);
+ void WriteVariableUInt32(UInt32 val);
+ void Write(string val);
+ void WriteRangedIntegerDeprecated(int min, int max, int val); //TODO: remove this, val should be first parameter >:(
+ void WriteRangedInteger(int val, int min, int max);
+ void WriteRangedSingle(Single val, Single min, Single max, int bitCount);
+ void Write(byte[] val, int startIndex, int length);
+
+ void PrepareForSending(ref byte[] outBuf, out bool isCompressed, out int outLength);
+
+ int BitPosition { get; set; }
+ int BytePosition { get; }
+ byte[] Buffer { get; }
+ int LengthBits { get; set; }
+ int LengthBytes { get; }
+ }
+}
diff --git a/Barotrauma/BarotraumaShared/Source/Networking/Primitives/Message/Message.cs b/Barotrauma/BarotraumaShared/Source/Networking/Primitives/Message/Message.cs
new file mode 100644
index 000000000..d9b84ab1d
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/Source/Networking/Primitives/Message/Message.cs
@@ -0,0 +1,955 @@
+using Lidgren.Network;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Barotrauma.Networking
+{
+ public static class MsgConstants
+ {
+ public const int MTU = 1200;
+ public const int CompressionThreshold = 1000;
+ public const int InitialBufferSize = 256;
+ public const int BufferOverAllocateAmount = 4;
+ }
+
+ ///
+ /// Utility struct for writing Singles
+ ///
+ [StructLayout(LayoutKind.Explicit)]
+ public struct SingleUIntUnion
+ {
+ ///
+ /// Value as a 32 bit float
+ ///
+ [FieldOffset(0)]
+ public float SingleValue;
+
+ ///
+ /// Value as an unsigned 32 bit integer
+ ///
+ [FieldOffset(0)]
+ public uint UIntValue;
+ }
+
+ internal static class MsgWriter
+ {
+ internal static void Write(ref byte[] buf, ref int bitPos, bool val)
+ {
+#if DEBUG
+ int resetPos = bitPos;
+#endif
+
+ EnsureBufferSize(ref buf, bitPos + 1);
+
+ int bytePos = bitPos / 8;
+ int bitOffset = bitPos % 8;
+ byte bitFlag = (byte)(1 << bitOffset);
+ byte bitMask = (byte)((~bitFlag) & 0xff);
+ buf[bytePos] &= bitMask;
+ if (val) buf[bytePos] |= bitFlag;
+ bitPos++;
+
+#if DEBUG
+ bool testVal = MsgReader.ReadBoolean(buf, ref resetPos);
+ if (testVal != val || resetPos != bitPos)
+ {
+ DebugConsole.ThrowError("Boolean written incorrectly! " + testVal + ", " + val + "; " + resetPos + ", " + bitPos);
+ }
+#endif
+ }
+
+ internal static void WritePadBits(ref byte[] buf, ref int bitPos)
+ {
+ int bitOffset = bitPos % 8;
+ bitPos += ((8 - bitOffset) % 8);
+ EnsureBufferSize(ref buf, bitPos);
+ }
+
+ internal static void Write(ref byte[] buf, ref int bitPos, byte val)
+ {
+ EnsureBufferSize(ref buf, bitPos + 8);
+ NetBitWriter.WriteByte(val, 8, buf, bitPos);
+ bitPos += 8;
+ }
+
+ internal static void Write(ref byte[] buf, ref int bitPos, UInt16 val)
+ {
+ EnsureBufferSize(ref buf, bitPos + 16);
+ NetBitWriter.WriteUInt16(val, 16, buf, bitPos);
+ bitPos += 16;
+ }
+
+ internal static void Write(ref byte[] buf, ref int bitPos, Int16 val)
+ {
+ EnsureBufferSize(ref buf, bitPos + 16);
+ NetBitWriter.WriteUInt16((UInt16)val, 16, buf, bitPos);
+ bitPos += 16;
+ }
+
+ internal static void Write(ref byte[] buf, ref int bitPos, UInt32 val)
+ {
+ EnsureBufferSize(ref buf, bitPos + 32);
+ NetBitWriter.WriteUInt32(val, 32, buf, bitPos);
+ bitPos += 32;
+ }
+
+ internal static void Write(ref byte[] buf, ref int bitPos, Int32 val)
+ {
+ EnsureBufferSize(ref buf, bitPos + 32);
+ NetBitWriter.WriteUInt32((UInt32)val, 32, buf, bitPos);
+ bitPos += 32;
+ }
+
+ internal static void Write(ref byte[] buf, ref int bitPos, UInt64 val)
+ {
+ EnsureBufferSize(ref buf, bitPos + 64);
+ NetBitWriter.WriteUInt64(val, 64, buf, bitPos);
+ bitPos += 64;
+ }
+
+ internal static void Write(ref byte[] buf, ref int bitPos, Int64 val)
+ {
+ EnsureBufferSize(ref buf, bitPos + 64);
+ NetBitWriter.WriteUInt64((UInt64)val, 64, buf, bitPos);
+ bitPos += 64;
+ }
+
+ internal static void Write(ref byte[] buf, ref int bitPos, Single val)
+ {
+ // Use union to avoid BitConverter.GetBytes() which allocates memory on the heap
+ SingleUIntUnion su;
+ su.UIntValue = 0; // must initialize every member of the union to avoid warning
+ su.SingleValue = val;
+
+ EnsureBufferSize(ref buf, bitPos + 32);
+
+ NetBitWriter.WriteUInt32(su.UIntValue, 32, buf, bitPos);
+ bitPos += 32;
+ }
+
+ internal static void Write(ref byte[] buf, ref int bitPos, Double val)
+ {
+ EnsureBufferSize(ref buf, bitPos + 64);
+
+ byte[] bytes = BitConverter.GetBytes(val);
+ WriteBytes(ref buf, ref bitPos, bytes, 0, bytes.Length);
+ bitPos += 64;
+ }
+ internal static void Write(ref byte[] buf, ref int bitPos, string val)
+ {
+ if (string.IsNullOrEmpty(val))
+ {
+ WriteVariableUInt32(ref buf, ref bitPos, (uint)0);
+ return;
+ }
+
+ byte[] bytes = Encoding.UTF8.GetBytes(val);
+ WriteVariableUInt32(ref buf, ref bitPos, (uint)bytes.Length);
+ WriteBytes(ref buf, ref bitPos, bytes, 0, bytes.Length);
+ }
+
+ internal static int WriteVariableUInt32(ref byte[] buf, ref int bitPos, uint value)
+ {
+ int retval = 1;
+ uint num1 = (uint)value;
+ while (num1 >= 0x80)
+ {
+ Write(ref buf, ref bitPos, (byte)(num1 | 0x80));
+ num1 = num1 >> 7;
+ retval++;
+ }
+ Write(ref buf, ref bitPos, (byte)num1);
+ return retval;
+ }
+
+ internal static void WriteRangedInteger(ref byte[] buf, ref int bitPos, int val, int min, int max)
+ {
+ uint range = (uint)(max - min);
+ int numberOfBits = NetUtility.BitsToHoldUInt(range);
+
+ EnsureBufferSize(ref buf, bitPos + numberOfBits);
+
+ uint rvalue = (uint)(val - min);
+ NetBitWriter.WriteUInt32(rvalue, numberOfBits, buf, bitPos);
+ bitPos += numberOfBits;
+ }
+
+ internal static void WriteRangedSingle(ref byte[] buf, ref int bitPos, Single val, Single min, Single max, int numberOfBits)
+ {
+ float range = max - min;
+ float unit = ((val - min) / range);
+ int maxVal = (1 << numberOfBits) - 1;
+
+ EnsureBufferSize(ref buf, bitPos + numberOfBits);
+
+ NetBitWriter.WriteUInt32((UInt32)((float)maxVal * unit), numberOfBits, buf, bitPos);
+ bitPos += numberOfBits;
+ }
+
+ internal static void WriteBytes(ref byte[] buf, ref int bitPos, byte[] val, int pos, int length)
+ {
+ EnsureBufferSize(ref buf, bitPos + length * 8);
+ NetBitWriter.WriteBytes(val, pos, length, buf, bitPos);
+ bitPos += length * 8;
+ }
+
+ internal static void EnsureBufferSize(ref byte[] buf, int numberOfBits)
+ {
+ int byteLen = ((numberOfBits + 7) >> 3);
+ if (buf == null)
+ {
+ buf = new byte[byteLen + MsgConstants.BufferOverAllocateAmount];
+ return;
+ }
+ if (buf.Length < byteLen)
+ {
+ Array.Resize(ref buf, byteLen + MsgConstants.BufferOverAllocateAmount);
+ }
+ }
+ }
+
+ internal static class MsgReader
+ {
+ internal static bool ReadBoolean(byte[] buf, ref int bitPos)
+ {
+ byte retval = NetBitWriter.ReadByte(buf, 1, bitPos);
+ bitPos++;
+ return (retval > 0 ? true : false);
+ }
+
+ internal static void ReadPadBits(byte[] buf, ref int bitPos)
+ {
+ int bitOffset = bitPos % 8;
+ bitPos += (8 - bitOffset) % 8;
+ }
+
+ internal static byte ReadByte(byte[] buf, ref int bitPos)
+ {
+ byte retval = NetBitWriter.ReadByte(buf, 8, bitPos);
+ bitPos += 8;
+ return retval;
+ }
+
+ internal static UInt16 ReadUInt16(byte[] buf, ref int bitPos)
+ {
+ uint retval = NetBitWriter.ReadUInt16(buf, 16, bitPos);
+ bitPos += 16;
+ return (ushort)retval;
+ }
+
+ internal static Int16 ReadInt16(byte[] buf, ref int bitPos)
+ {
+ return (Int16)ReadUInt16(buf, ref bitPos);
+ }
+
+ internal static UInt32 ReadUInt32(byte[] buf, ref int bitPos)
+ {
+ uint retval = NetBitWriter.ReadUInt32(buf, 32, bitPos);
+ bitPos += 32;
+ return retval;
+ }
+
+ internal static Int32 ReadInt32(byte[] buf, ref int bitPos)
+ {
+ return (Int32)ReadUInt32(buf, ref bitPos);
+ }
+
+ internal static UInt64 ReadUInt64(byte[] buf, ref int bitPos)
+ {
+ ulong low = NetBitWriter.ReadUInt32(buf, 32, bitPos);
+ bitPos += 32;
+ ulong high = NetBitWriter.ReadUInt32(buf, 32, bitPos);
+ ulong retval = low + (high << 32);
+ bitPos += 32;
+ return retval;
+ }
+
+ internal static Int64 ReadInt64(byte[] buf, ref int bitPos)
+ {
+ return (Int64)ReadUInt64(buf, ref bitPos);
+ }
+
+ internal static Single ReadSingle(byte[] buf, ref int bitPos)
+ {
+ if ((bitPos & 7) == 0) // read directly
+ {
+ float retval = BitConverter.ToSingle(buf, bitPos >> 3);
+ bitPos += 32;
+ return retval;
+ }
+
+ byte[] bytes = ReadBytes(buf, ref bitPos, 4);
+ return BitConverter.ToSingle(bytes, 0);
+ }
+
+ internal static Double ReadDouble(byte[] buf, ref int bitPos)
+ {
+ if ((bitPos & 7) == 0) // read directly
+ {
+ // read directly
+ double retval = BitConverter.ToDouble(buf, bitPos >> 3);
+ bitPos += 64;
+ return retval;
+ }
+
+ byte[] bytes = ReadBytes(buf, ref bitPos, 8);
+ return BitConverter.ToDouble(bytes, 0);
+ }
+
+ internal static UInt32 ReadVariableUInt32(byte[] buf, ref int bitPos)
+ {
+ int bitLength = buf.Length * 8;
+
+ int num1 = 0;
+ int num2 = 0;
+ while (bitLength - bitPos >= 8)
+ {
+ byte num3 = ReadByte(buf, ref bitPos);
+ num1 |= (num3 & 0x7f) << num2;
+ num2 += 7;
+ if ((num3 & 0x80) == 0)
+ return (uint)num1;
+ }
+
+ // ouch; failed to find enough bytes; malformed variable length number?
+ return (uint)num1;
+ }
+
+ internal static String ReadString(byte[] buf, ref int bitPos)
+ {
+ int bitLength = buf.Length * 8;
+ int byteLen = (int)ReadVariableUInt32(buf, ref bitPos);
+
+ if (byteLen <= 0) { return String.Empty; }
+
+ if ((ulong)(bitLength - bitPos) < ((ulong)byteLen * 8))
+ {
+ // not enough data
+ return null;
+ }
+
+ if ((bitPos & 7) == 0)
+ {
+ // read directly
+ string retval = System.Text.Encoding.UTF8.GetString(buf, bitPos >> 3, byteLen);
+ bitPos += (8 * byteLen);
+ return retval;
+ }
+
+ byte[] bytes = ReadBytes(buf, ref bitPos, byteLen);
+ return System.Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length);
+ }
+
+ internal static int ReadRangedInteger(byte[] buf, ref int bitPos, int min, int max)
+ {
+ uint range = (uint)(max - min);
+ int numBits = NetUtility.BitsToHoldUInt(range);
+
+ uint rvalue = NetBitWriter.ReadUInt32(buf, numBits, bitPos);
+ bitPos += numBits;
+
+ return (int)(min + rvalue);
+ }
+
+ internal static Single ReadRangedSingle(byte[] buf, ref int bitPos, Single min, Single max, int bitCount)
+ {
+ int maxInt = (1 << bitCount) - 1;
+ int intVal = ReadRangedInteger(buf, ref bitPos, 0, maxInt);
+ Single range = max - min;
+ return min + (range * ((Single)intVal) / ((Single)maxInt));
+ }
+
+ internal static byte[] ReadBytes(byte[] buf, ref int bitPos, int numberOfBytes)
+ {
+ byte[] retval = new byte[numberOfBytes];
+ NetBitWriter.ReadBytes(buf, numberOfBytes, bitPos, retval, 0);
+ bitPos += (8 * numberOfBytes);
+ return retval;
+ }
+ }
+
+ public class WriteOnlyMessage : IWriteMessage
+ {
+ private byte[] buf = new byte[MsgConstants.InitialBufferSize];
+ private int seekPos = 0;
+ private int lengthBits = 0;
+
+ public int BitPosition
+ {
+ get
+ {
+ return seekPos;
+ }
+ set
+ {
+ seekPos = value;
+ }
+ }
+
+ public int BytePosition
+ {
+ get
+ {
+ return seekPos / 8;
+ }
+ }
+
+ public byte[] Buffer
+ {
+ get
+ {
+ return buf;
+ }
+ }
+
+ public int LengthBits
+ {
+ get
+ {
+ lengthBits = seekPos > lengthBits ? seekPos : lengthBits;
+ return lengthBits;
+ }
+ set
+ {
+ lengthBits = value;
+ seekPos = seekPos > lengthBits ? lengthBits : seekPos;
+ }
+ }
+
+ public int LengthBytes
+ {
+ get
+ {
+ return (LengthBits + ((8 - (LengthBits % 8)) % 8)) / 8;
+ }
+ }
+
+ public void Write(bool val)
+ {
+ MsgWriter.Write(ref buf, ref seekPos, val);
+ }
+
+ public void WritePadBits()
+ {
+ MsgWriter.WritePadBits(ref buf, ref seekPos);
+ }
+
+ public void Write(byte val)
+ {
+ MsgWriter.Write(ref buf, ref seekPos, val);
+ }
+
+ public void Write(UInt16 val)
+ {
+ MsgWriter.Write(ref buf, ref seekPos, val);
+ }
+
+ public void Write(Int16 val)
+ {
+ MsgWriter.Write(ref buf, ref seekPos, val);
+ }
+
+ public void Write(UInt32 val)
+ {
+ MsgWriter.Write(ref buf, ref seekPos, val);
+ }
+
+ public void Write(Int32 val)
+ {
+ MsgWriter.Write(ref buf, ref seekPos, val);
+ }
+
+ public void Write(UInt64 val)
+ {
+ MsgWriter.Write(ref buf, ref seekPos, val);
+ }
+
+ public void Write(Int64 val)
+ {
+ MsgWriter.Write(ref buf, ref seekPos, val);
+ }
+
+ public void Write(Single val)
+ {
+ MsgWriter.Write(ref buf, ref seekPos, val);
+ }
+
+ public void Write(Double val)
+ {
+ MsgWriter.Write(ref buf, ref seekPos, val);
+ }
+
+ public void WriteVariableUInt32(UInt32 val)
+ {
+ MsgWriter.WriteVariableUInt32(ref buf, ref seekPos, val);
+ }
+
+ public void Write(String val)
+ {
+ MsgWriter.Write(ref buf, ref seekPos, val);
+ }
+
+ public void WriteRangedIntegerDeprecated(int min, int max, int val)
+ {
+ MsgWriter.WriteRangedInteger(ref buf, ref seekPos, val, min, max);
+ }
+
+ public void WriteRangedInteger(int val, int min, int max)
+ {
+ MsgWriter.WriteRangedInteger(ref buf, ref seekPos, val, min, max);
+ }
+
+ public void WriteRangedSingle(Single val, Single min, Single max, int bitCount)
+ {
+ MsgWriter.WriteRangedSingle(ref buf, ref seekPos, val, min, max, bitCount);
+ }
+
+ public void Write(byte[] val, int startPos, int length)
+ {
+ MsgWriter.WriteBytes(ref buf, ref seekPos, val, startPos, length);
+ }
+
+ public void PrepareForSending(ref byte[] outBuf, out bool isCompressed, out int length)
+ {
+ if (LengthBytes <= MsgConstants.CompressionThreshold)
+ {
+ isCompressed = false;
+ if (LengthBytes > outBuf.Length) { Array.Resize(ref outBuf, LengthBytes); }
+ Array.Copy(buf, outBuf, LengthBytes);
+ length = LengthBytes;
+ }
+ else
+ {
+ using (MemoryStream output = new MemoryStream())
+ {
+ using (DeflateStream dstream = new DeflateStream(output, CompressionLevel.Fastest))
+ {
+ dstream.Write(buf, 0, LengthBytes);
+ }
+
+ byte[] compressedBuf = output.ToArray();
+ //don't send the data as compressed if the data takes up more space after compression
+ //(which may happen when sending a sub/save file that's already been compressed with a better compression ratio)
+ if (compressedBuf.Length >= outBuf.Length)
+ {
+ isCompressed = false;
+ if (LengthBytes > outBuf.Length) { Array.Resize(ref outBuf, LengthBytes); }
+ Array.Copy(buf, outBuf, LengthBytes);
+ length = LengthBytes;
+ }
+ else
+ {
+ isCompressed = true;
+ if (compressedBuf.Length > outBuf.Length) { Array.Resize(ref outBuf, compressedBuf.Length); }
+ Array.Copy(compressedBuf, outBuf, compressedBuf.Length);
+ length = compressedBuf.Length;
+ DebugConsole.NewMessage("Compressed message: " + LengthBytes + " to " + length);
+ }
+ }
+ }
+ }
+ }
+
+ public class ReadOnlyMessage : IReadMessage
+ {
+ private byte[] buf;
+ private int seekPos = 0;
+ private int lengthBits = 0;
+
+ public int BitPosition
+ {
+ get
+ {
+ return seekPos;
+ }
+ set
+ {
+ seekPos = value;
+ }
+ }
+
+ public int BytePosition
+ {
+ get
+ {
+ return seekPos / 8;
+ }
+ }
+
+ public byte[] Buffer
+ {
+ get
+ {
+ return buf;
+ }
+ }
+
+ public int LengthBits
+ {
+ get
+ {
+ lengthBits = seekPos > lengthBits ? seekPos : lengthBits;
+ return lengthBits;
+ }
+ set
+ {
+ lengthBits = value;
+ seekPos = seekPos > lengthBits ? lengthBits : seekPos;
+ }
+ }
+
+ public int LengthBytes
+ {
+ get
+ {
+ return lengthBits / 8;
+ }
+ }
+
+ public NetworkConnection Sender { get; private set; }
+
+ public ReadOnlyMessage(byte[] inBuf, bool isCompressed, int startPos, int inLength, NetworkConnection sender)
+ {
+ Sender = sender;
+ if (isCompressed)
+ {
+ byte[] decompressedData;
+ using (MemoryStream input = new MemoryStream(inBuf, startPos, inLength))
+ {
+ using (MemoryStream output = new MemoryStream())
+ {
+ using (DeflateStream dstream = new DeflateStream(input, CompressionMode.Decompress))
+ {
+ dstream.CopyTo(output);
+ }
+ decompressedData = output.ToArray();
+ }
+ }
+ buf = new byte[decompressedData.Length];
+ Array.Copy(decompressedData, 0, buf, 0, decompressedData.Length);
+ lengthBits = decompressedData.Length * 8;
+ DebugConsole.NewMessage("Decompressing message: " + inLength + " to " + LengthBytes);
+ }
+ else
+ {
+ buf = new byte[inBuf.Length];
+ Array.Copy(inBuf, startPos, buf, 0, inLength);
+ lengthBits = inLength * 8;
+ }
+ seekPos = 0;
+ }
+
+ public bool ReadBoolean()
+ {
+ return MsgReader.ReadBoolean(buf, ref seekPos);
+ }
+
+ public void ReadPadBits()
+ {
+ MsgReader.ReadPadBits(buf, ref seekPos);
+ }
+
+ public byte ReadByte()
+ {
+ return MsgReader.ReadByte(buf, ref seekPos);
+ }
+
+ public UInt16 ReadUInt16()
+ {
+ return MsgReader.ReadUInt16(buf, ref seekPos);
+ }
+
+ public Int16 ReadInt16()
+ {
+ return MsgReader.ReadInt16(buf, ref seekPos);
+ }
+
+ public UInt32 ReadUInt32()
+ {
+ return MsgReader.ReadUInt32(buf, ref seekPos);
+ }
+
+ public Int32 ReadInt32()
+ {
+ return MsgReader.ReadInt32(buf, ref seekPos);
+ }
+
+ public UInt64 ReadUInt64()
+ {
+ return MsgReader.ReadUInt64(buf, ref seekPos);
+ }
+
+ public Int64 ReadInt64()
+ {
+ return MsgReader.ReadInt64(buf, ref seekPos);
+ }
+
+ public Single ReadSingle()
+ {
+ return MsgReader.ReadSingle(buf, ref seekPos);
+ }
+
+ public Double ReadDouble()
+ {
+ return MsgReader.ReadDouble(buf, ref seekPos);
+ }
+
+ public UInt32 ReadVariableUInt32()
+ {
+ return MsgReader.ReadVariableUInt32(buf, ref seekPos);
+ }
+
+ public String ReadString()
+ {
+ return MsgReader.ReadString(buf, ref seekPos);
+ }
+
+ public int ReadRangedInteger(int min, int max)
+ {
+ return MsgReader.ReadRangedInteger(buf, ref seekPos, min, max);
+ }
+
+ public Single ReadRangedSingle(Single min, Single max, int bitCount)
+ {
+ return MsgReader.ReadRangedSingle(buf, ref seekPos, min, max, bitCount);
+ }
+
+ public byte[] ReadBytes(int numberOfBytes)
+ {
+ return MsgReader.ReadBytes(buf, ref seekPos, numberOfBytes);
+ }
+ }
+
+ public class ReadWriteMessage : IWriteMessage, IReadMessage
+ {
+ private byte[] buf = new byte[MsgConstants.InitialBufferSize];
+ private int seekPos = 0;
+ private int lengthBits = 0;
+
+ public int BitPosition
+ {
+ get
+ {
+ return seekPos;
+ }
+ set
+ {
+ seekPos = value;
+ }
+ }
+
+ public int BytePosition
+ {
+ get
+ {
+ return seekPos / 8;
+ }
+ }
+
+ public byte[] Buffer
+ {
+ get
+ {
+ return buf;
+ }
+ }
+
+ public int LengthBits
+ {
+ get
+ {
+ lengthBits = seekPos > lengthBits ? seekPos : lengthBits;
+ return lengthBits;
+ }
+ set
+ {
+ lengthBits = value;
+ seekPos = seekPos > lengthBits ? lengthBits : seekPos;
+ }
+ }
+
+ public int LengthBytes
+ {
+ get
+ {
+ return (LengthBits + ((8 - (LengthBits % 8)) % 8)) / 8;
+ }
+ }
+
+ public NetworkConnection Sender { get { return null; } }
+
+ public void Write(bool val)
+ {
+ MsgWriter.Write(ref buf, ref seekPos, val);
+ }
+
+ public void WritePadBits()
+ {
+ MsgWriter.WritePadBits(ref buf, ref seekPos);
+ }
+
+ public void Write(byte val)
+ {
+ MsgWriter.Write(ref buf, ref seekPos, val);
+ }
+
+ public void Write(UInt16 val)
+ {
+ MsgWriter.Write(ref buf, ref seekPos, val);
+ }
+
+ public void Write(Int16 val)
+ {
+ MsgWriter.Write(ref buf, ref seekPos, val);
+ }
+
+ public void Write(UInt32 val)
+ {
+ MsgWriter.Write(ref buf, ref seekPos, val);
+ }
+
+ public void Write(Int32 val)
+ {
+ MsgWriter.Write(ref buf, ref seekPos, val);
+ }
+
+ public void Write(UInt64 val)
+ {
+ MsgWriter.Write(ref buf, ref seekPos, val);
+ }
+
+ public void Write(Int64 val)
+ {
+ MsgWriter.Write(ref buf, ref seekPos, val);
+ }
+
+ public void Write(Single val)
+ {
+ MsgWriter.Write(ref buf, ref seekPos, val);
+ }
+
+ public void Write(Double val)
+ {
+ MsgWriter.Write(ref buf, ref seekPos, val);
+ }
+
+ public void WriteVariableUInt32(UInt32 val)
+ {
+ MsgWriter.WriteVariableUInt32(ref buf, ref seekPos, val);
+ }
+
+ public void Write(String val)
+ {
+ MsgWriter.Write(ref buf, ref seekPos, val);
+ }
+
+ public void WriteRangedIntegerDeprecated(int min, int max, int val)
+ {
+ MsgWriter.WriteRangedInteger(ref buf, ref seekPos, val, min, max);
+ }
+
+ public void WriteRangedInteger(int val, int min, int max)
+ {
+ MsgWriter.WriteRangedInteger(ref buf, ref seekPos, val, min, max);
+ }
+
+ public void WriteRangedSingle(Single val, Single min, Single max, int bitCount)
+ {
+ MsgWriter.WriteRangedSingle(ref buf, ref seekPos, val, min, max, bitCount);
+ }
+
+ public void Write(byte[] val, int startPos, int length)
+ {
+ MsgWriter.WriteBytes(ref buf, ref seekPos, val, startPos, length);
+ }
+
+ public bool ReadBoolean()
+ {
+ return MsgReader.ReadBoolean(buf, ref seekPos);
+ }
+
+ public void ReadPadBits()
+ {
+ MsgReader.ReadPadBits(buf, ref seekPos);
+ }
+
+ public byte ReadByte()
+ {
+ return MsgReader.ReadByte(buf, ref seekPos);
+ }
+
+ public UInt16 ReadUInt16()
+ {
+ return MsgReader.ReadUInt16(buf, ref seekPos);
+ }
+
+ public Int16 ReadInt16()
+ {
+ return MsgReader.ReadInt16(buf, ref seekPos);
+ }
+
+ public UInt32 ReadUInt32()
+ {
+ return MsgReader.ReadUInt32(buf, ref seekPos);
+ }
+
+ public Int32 ReadInt32()
+ {
+ return MsgReader.ReadInt32(buf, ref seekPos);
+ }
+
+ public UInt64 ReadUInt64()
+ {
+ return MsgReader.ReadUInt64(buf, ref seekPos);
+ }
+
+ public Int64 ReadInt64()
+ {
+ return MsgReader.ReadInt64(buf, ref seekPos);
+ }
+
+ public Single ReadSingle()
+ {
+ return MsgReader.ReadSingle(buf, ref seekPos);
+ }
+
+ public Double ReadDouble()
+ {
+ return MsgReader.ReadDouble(buf, ref seekPos);
+ }
+
+ public UInt32 ReadVariableUInt32()
+ {
+ return MsgReader.ReadVariableUInt32(buf, ref seekPos);
+ }
+
+ public String ReadString()
+ {
+ return MsgReader.ReadString(buf, ref seekPos);
+ }
+
+ public int ReadRangedInteger(int min, int max)
+ {
+ return MsgReader.ReadRangedInteger(buf, ref seekPos, min, max);
+ }
+
+ public Single ReadRangedSingle(Single min, Single max, int bitCount)
+ {
+ return MsgReader.ReadRangedSingle(buf, ref seekPos, min, max, bitCount);
+ }
+
+ public byte[] ReadBytes(int numberOfBytes)
+ {
+ return MsgReader.ReadBytes(buf, ref seekPos, numberOfBytes);
+ }
+
+ public void PrepareForSending(ref byte[] outBuf, out bool isCompressed, out int outLength)
+ {
+ throw new InvalidOperationException("ReadWriteMessages are not to be sent");
+ }
+ }
+}
diff --git a/Barotrauma/BarotraumaShared/Source/Networking/Primitives/NetworkConnection/LidgrenConnection.cs b/Barotrauma/BarotraumaShared/Source/Networking/Primitives/NetworkConnection/LidgrenConnection.cs
new file mode 100644
index 000000000..1825dd108
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/Source/Networking/Primitives/NetworkConnection/LidgrenConnection.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Net;
+using Lidgren.Network;
+
+namespace Barotrauma.Networking
+{
+ public class LidgrenConnection : NetworkConnection
+ {
+ public NetConnection NetConnection { get; private set; }
+
+ public IPEndPoint IPEndPoint => NetConnection.RemoteEndPoint;
+
+ public string IPString
+ {
+ get
+ {
+ return IPEndPoint.Address.IsIPv4MappedToIPv6 ? IPEndPoint.Address.MapToIPv4().ToString() : IPEndPoint.Address.ToString();
+ }
+ }
+
+ public UInt16 Port
+ {
+ get
+ {
+ return (UInt16)IPEndPoint.Port;
+ }
+ }
+
+ public LidgrenConnection(string name, NetConnection netConnection, UInt64 steamId)
+ {
+ Name = name;
+ NetConnection = netConnection;
+ SteamID = steamId;
+ EndPointString = IPString;
+ }
+ }
+}
diff --git a/Barotrauma/BarotraumaShared/Source/Networking/Primitives/NetworkConnection/NetworkConnection.cs b/Barotrauma/BarotraumaShared/Source/Networking/Primitives/NetworkConnection/NetworkConnection.cs
new file mode 100644
index 000000000..f1e377a7d
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/Source/Networking/Primitives/NetworkConnection/NetworkConnection.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net;
+
+namespace Barotrauma.Networking
+{
+ public enum NetworkConnectionStatus
+ {
+ Connected = 0x1,
+ Disconnected = 0x2
+ }
+
+ public abstract class NetworkConnection
+ {
+ public string Name;
+
+ public UInt64 SteamID
+ {
+ get;
+ protected set;
+ }
+
+ public string EndPointString
+ {
+ get;
+ protected set;
+ }
+
+ public NetworkConnectionStatus Status = NetworkConnectionStatus.Disconnected;
+ }
+}
diff --git a/Barotrauma/BarotraumaShared/Source/Networking/Primitives/NetworkConnection/SteamP2PConnection.cs b/Barotrauma/BarotraumaShared/Source/Networking/Primitives/NetworkConnection/SteamP2PConnection.cs
new file mode 100644
index 000000000..5b27c958d
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/Source/Networking/Primitives/NetworkConnection/SteamP2PConnection.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Barotrauma.Networking
+{
+ public class SteamP2PConnection : NetworkConnection
+ {
+ public double Timeout = 0.0;
+
+ public SteamP2PConnection(string name, UInt64 steamId)
+ {
+ SteamID = steamId;
+ EndPointString = SteamID.ToString();
+ Name = name;
+ Heartbeat();
+ }
+
+ public void Decay(float deltaTime)
+ {
+ Timeout -= deltaTime;
+ }
+
+ public void Heartbeat()
+ {
+ Timeout = 20.0;
+ }
+ }
+}
diff --git a/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs b/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs
index 6bcd22d24..00bf1cd69 100644
--- a/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs
+++ b/Barotrauma/BarotraumaShared/Source/Networking/RespawnManager.cs
@@ -282,7 +282,7 @@ namespace Barotrauma.Networking
{
RespawnCharactersProjSpecific();
}
-
+
public Vector2 FindSpawnPos()
{
if (Level.Loaded == null || Submarine.MainSub == null) { return Vector2.Zero; }
@@ -310,12 +310,11 @@ namespace Barotrauma.Networking
//make sure there aren't any walls too close
var tooCloseCells = Level.Loaded.GetTooCloseCells(potentialSpawnPos.Position.ToVector2(), Math.Max(minWidth, minHeight));
if (tooCloseCells.Any()) { continue; }
-
+
//make sure the spawnpoint is far enough from other subs
foreach (Submarine sub in Submarine.Loaded)
{
if (sub == RespawnShuttle || RespawnShuttle.DockedTo.Contains(sub)) { continue; }
-
float minDist = Math.Max(Math.Max(minWidth, minHeight) + Math.Max(sub.Borders.Width, sub.Borders.Height), 10000.0f);
if (Vector2.DistanceSquared(sub.WorldPosition, potentialSpawnPos.Position.ToVector2()) < minDist * minDist)
{
diff --git a/Barotrauma/BarotraumaShared/Source/Networking/ServerLog.cs b/Barotrauma/BarotraumaShared/Source/Networking/ServerLog.cs
index 70ff5dcba..02606590c 100644
--- a/Barotrauma/BarotraumaShared/Source/Networking/ServerLog.cs
+++ b/Barotrauma/BarotraumaShared/Source/Networking/ServerLog.cs
@@ -6,7 +6,7 @@ using System.Linq;
namespace Barotrauma.Networking
{
- partial class ServerLog
+ public partial class ServerLog
{
private struct LogMessage
{
diff --git a/Barotrauma/BarotraumaShared/Source/Networking/ServerSettings.cs b/Barotrauma/BarotraumaShared/Source/Networking/ServerSettings.cs
index fec6d1e0f..479d87f3b 100644
--- a/Barotrauma/BarotraumaShared/Source/Networking/ServerSettings.cs
+++ b/Barotrauma/BarotraumaShared/Source/Networking/ServerSettings.cs
@@ -1,5 +1,4 @@
-using Lidgren.Network;
-using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@@ -14,21 +13,21 @@ using System.Xml.Linq;
namespace Barotrauma.Networking
{
- enum SelectionMode
+ public enum SelectionMode
{
Manual = 0, Random = 1, Vote = 2
}
- enum YesNoMaybe
+ public enum YesNoMaybe
{
No = 0, Maybe = 1, Yes = 2
}
- enum BotSpawnMode
+ public enum BotSpawnMode
{
Normal, Fill
}
-
+
partial class ServerSettings : ISerializableEntity
{
public const string SettingsFile = "serversettings.xml";
@@ -57,25 +56,17 @@ namespace Barotrauma.Networking
public class SavedClientPermission
{
- public readonly string IP;
+ public readonly string EndPoint;
public readonly ulong SteamID;
public readonly string Name;
public List