Merge branch 'dev' of https://github.com/Regalis11/Barotrauma.git into unstable-tests

This commit is contained in:
Evil Factory
2022-04-08 12:52:28 -03:00
990 changed files with 44338 additions and 38589 deletions
@@ -38,7 +38,7 @@ namespace Barotrauma.Networking
public bool CompareTo(string endpointCompare)
{
if (string.IsNullOrEmpty(EndPoint) || string.IsNullOrEmpty(EndPoint)) { return false; }
if (string.IsNullOrEmpty(EndPoint) || string.IsNullOrEmpty(endpointCompare)) { return false; }
if (!IsRangeBan)
{
return endpointCompare == EndPoint;
@@ -1,5 +1,4 @@
using Microsoft.Xna.Framework;
using System;
using System;
using System.Text;
using MoonSharp.Interpreter;
@@ -18,31 +17,48 @@ namespace Barotrauma.Networking
Character orderTargetCharacter = null;
Entity orderTargetEntity = null;
OrderChatMessage orderMsg = null;
OrderTarget orderTargetPosition = null;
Order.OrderTargetType orderTargetType = Order.OrderTargetType.Entity;
int? wallSectionIndex = null;
Order order = null;
bool isNewOrder = false;
if (type == ChatMessageType.Order)
{
var orderMessageInfo = OrderChatMessage.ReadOrder(msg);
if (orderMessageInfo.OrderIndex < 0 || orderMessageInfo.OrderIndex >= Order.PrefabList.Count)
if (orderMessageInfo.OrderIdentifier == Identifier.Empty)
{
DebugConsole.ThrowError($"Invalid order message from client \"{c.Name}\" - order index out of bounds ({orderMessageInfo.OrderIndex}).");
DebugConsole.ThrowError($"Invalid order message from client \"{c.Name}\" - order identifier is empty.");
if (NetIdUtils.IdMoreRecent(ID, c.LastSentChatMsgID)) { c.LastSentChatMsgID = ID; }
return;
}
isNewOrder = orderMessageInfo.IsNewOrder;
orderTargetCharacter = orderMessageInfo.TargetCharacter;
orderTargetEntity = orderMessageInfo.TargetEntity;
orderTargetPosition = orderMessageInfo.TargetPosition;
OrderTarget orderTargetPosition = orderMessageInfo.TargetPosition;
orderTargetType = orderMessageInfo.TargetType;
wallSectionIndex = orderMessageInfo.WallSectionIndex;
var orderPrefab = orderMessageInfo.OrderPrefab ?? Order.PrefabList[orderMessageInfo.OrderIndex];
string orderOption = orderMessageInfo.OrderOption ??
(orderMessageInfo.OrderOptionIndex == null || orderMessageInfo.OrderOptionIndex < 0 || orderMessageInfo.OrderOptionIndex >= orderPrefab.Options.Length ?
"" : orderPrefab.Options[orderMessageInfo.OrderOptionIndex.Value]);
orderMsg = new OrderChatMessage(orderPrefab, orderOption, orderMessageInfo.Priority, orderTargetPosition ?? orderTargetEntity as ISpatialEntity, orderTargetCharacter, c.Character, isNewOrder: orderMessageInfo.IsNewOrder)
var orderPrefab = orderMessageInfo.OrderPrefab ?? OrderPrefab.Prefabs[orderMessageInfo.OrderIdentifier];
Identifier orderOption = orderMessageInfo.OrderOption;
if (orderOption.IsEmpty)
{
WallSectionIndex = wallSectionIndex
};
orderOption = orderMessageInfo.OrderOptionIndex == null || orderMessageInfo.OrderOptionIndex < 0 || orderMessageInfo.OrderOptionIndex >= orderPrefab.Options.Length ?
Identifier.Empty : orderPrefab.Options[orderMessageInfo.OrderOptionIndex.Value];
}
if (orderTargetType == Order.OrderTargetType.Position)
{
order = new Order(orderPrefab, orderOption, orderTargetPosition, orderGiver: c.Character)
.WithManualPriority(orderMessageInfo.Priority);
}
else if (orderTargetType == Order.OrderTargetType.WallSection)
{
order = new Order(orderPrefab, orderOption, orderTargetEntity as Structure, wallSectionIndex, orderGiver: c.Character)
.WithManualPriority(orderMessageInfo.Priority);
}
else
{
order = new Order(orderPrefab, orderOption, orderTargetEntity, orderPrefab.GetTargetItemComponent(orderTargetEntity as Item), orderGiver: c.Character)
.WithManualPriority(orderMessageInfo.Priority);
}
orderMsg = new OrderChatMessage(order, orderTargetCharacter, c.Character);
txt = orderMsg.Text;
}
else
@@ -96,11 +112,11 @@ namespace Barotrauma.Networking
if (c.ChatSpamCount > 3)
{
//kick for spamming too much
GameMain.Server.KickClient(c, TextManager.Get("SpamFilterKicked"));
GameMain.Server.KickClient(c, TextManager.Get("SpamFilterKicked").Value);
}
else
{
ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked"), ChatMessageType.Server, null);
ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked").Value, ChatMessageType.Server, null);
c.ChatSpamTimer = 10.0f;
GameMain.Server.SendDirectChatMessage(denyMsg, c);
}
@@ -111,7 +127,7 @@ namespace Barotrauma.Networking
if (c.ChatSpamTimer > 0.0f && !isOwner && !GameMain.Lua.game.disableSpamFilter)
{
ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked"), ChatMessageType.Server, null);
ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked").Value, ChatMessageType.Server, null);
c.ChatSpamTimer = 10.0f;
GameMain.Server.SendDirectChatMessage(denyMsg, c);
return;
@@ -132,16 +148,6 @@ namespace Barotrauma.Networking
{
HumanAIController.ReportProblem(orderMsg.Sender, orderMsg.Order);
}
Order order = orderTargetType switch
{
Order.OrderTargetType.Entity =>
new Order(orderMsg.Order, orderTargetEntity, orderMsg.Order?.GetTargetItemComponent(orderTargetEntity as Item), orderGiver: orderMsg.Sender),
Order.OrderTargetType.Position =>
new Order(orderMsg.Order, orderTargetPosition, orderGiver: orderMsg.Sender),
Order.OrderTargetType.WallSection when orderTargetEntity is Structure s && wallSectionIndex.HasValue =>
new Order(orderMsg.Order, s, wallSectionIndex, orderGiver: orderMsg.Sender),
_ => throw new NotImplementedException()
};
if (order != null)
{
if (order.TargetAllCharacters)
@@ -168,7 +174,7 @@ namespace Barotrauma.Networking
}
else if (orderTargetCharacter != null)
{
orderTargetCharacter.SetOrder(order, orderMsg.OrderOption, orderMsg.OrderPriority, orderMsg.Sender);
orderTargetCharacter.SetOrder(order, isNewOrder);
}
}
GameMain.Server.SendOrderChatMessage(orderMsg);
@@ -1,4 +1,7 @@
using System.IO.Pipes;
using System;
using System.IO.Pipes;
using System.Text;
using System.Threading;
namespace Barotrauma.Networking
{
@@ -14,6 +17,12 @@ namespace Barotrauma.Networking
PrivateStart();
}
public static void NotifyCrash(string msg)
{
errorsToWrite.Enqueue(msg);
Thread.Sleep(1000);
}
public static void ShutDown()
{
PrivateShutDown();
@@ -57,8 +57,8 @@ namespace Barotrauma.Networking
public bool ReadyToStart;
public List<Pair<JobPrefab, int>> JobPreferences;
public Pair<JobPrefab, int> AssignedJob;
public List<JobVariant> JobPreferences;
public JobVariant AssignedJob;
public float DeleteDisconnectedTimer;
@@ -104,7 +104,7 @@ namespace Barotrauma.Networking
partial void InitProjSpecific()
{
JobPreferences = new List<Pair<JobPrefab, int>>();
JobPreferences = new List<JobVariant>();
VoipQueue = new VoipQueue(ID, true, true);
GameMain.Server.VoipServer.RegisterQueue(VoipQueue);
@@ -149,7 +149,7 @@ namespace Barotrauma.Networking
foreach (char character in name)
{
if (!serverSettings.AllowedClientNameChars.Any(charRange => (int)character >= charRange.First && (int)character <= charRange.Second)) { return false; }
if (!serverSettings.AllowedClientNameChars.Any(charRange => (int)character >= charRange.Start && (int)character <= charRange.End)) { return false; }
}
return true;
@@ -1,47 +1,55 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using Barotrauma.Networking;
namespace Barotrauma
{
partial class EntitySpawner : Entity, IServerSerializable
{
public void CreateNetworkEvent(Entity entity, bool remove)
public void CreateNetworkEvent(SpawnOrRemove spawnOrRemove)
{
CreateNetworkEventProjSpecific(entity, remove);
CreateNetworkEventProjSpecific(spawnOrRemove);
}
partial void CreateNetworkEventProjSpecific(Entity entity, bool remove)
partial void CreateNetworkEventProjSpecific(SpawnOrRemove spawnOrRemove)
{
if (GameMain.Server != null && entity != null)
if (GameMain.Server == null || spawnOrRemove?.Entity == null) { return; }
GameMain.Server.CreateEntityEvent(this, spawnOrRemove);
if (spawnOrRemove.Entity is Character { Info: { } } character)
{
GameMain.Server.CreateEntityEvent(this, new object[] { new SpawnOrRemove(entity, remove) });
foreach (var statKey in character.Info.SavedStatValues.Keys)
{
GameMain.NetworkMember.CreateEntityEvent(character, new Character.UpdatePermanentStatsEventData(statKey));
}
}
}
public void ServerWrite(IWriteMessage message, Client client, object[] extraData = null)
public void ServerEventWrite(IWriteMessage message, Client client, NetEntityEvent.IData extraData = null)
{
if (GameMain.Server == null) return;
if (GameMain.Server is null) { return; }
if (!(extraData is SpawnOrRemove entities)) { throw new Exception($"Malformed {nameof(EntitySpawner)} event: expected {nameof(SpawnOrRemove)}"); }
SpawnOrRemove entities = (SpawnOrRemove)extraData[0];
message.Write(entities.Remove);
if (entities.Remove)
message.Write(entities is RemoveEntity);
if (entities is RemoveEntity)
{
message.Write(entities.OriginalID);
message.Write(entities.ID);
}
else
{
if (entities.Entity is Item)
switch (entities.Entity)
{
message.Write((byte)SpawnableType.Item);
DebugConsole.Log("Writing item spawn data " + entities.Entity.ToString() + " (original ID: " + entities.OriginalID + ", current ID: " + entities.Entity.ID + ")");
((Item)entities.Entity).WriteSpawnData(message, entities.OriginalID, entities.OriginalInventoryID, entities.OriginalItemContainerIndex);
}
else if (entities.Entity is Character)
{
message.Write((byte)SpawnableType.Character);
DebugConsole.Log("Writing character spawn data: " + entities.Entity.ToString() + " (original ID: " + entities.OriginalID + ", current ID: " + entities.Entity.ID + ")");
((Character)entities.Entity).WriteSpawnData(message, entities.OriginalID, restrictMessageSize: true);
case Item item:
message.Write((byte)SpawnableType.Item);
DebugConsole.Log(
$"Writing item spawn data {item} (ID: {entities.ID})");
item.WriteSpawnData(message, entities.ID, entities.InventoryID, entities.ItemContainerIndex, entities.SlotIndex);
break;
case Character character:
message.Write((byte)SpawnableType.Character);
DebugConsole.Log(
$"Writing character spawn data: {character} (ID: {entities.ID})");
character.WriteSpawnData(message, entities.ID, restrictMessageSize: true);
break;
}
}
}
@@ -36,12 +36,25 @@ namespace Barotrauma.Networking
get { return KnownReceivedOffset / (float)Data.Length; }
}
private float waitTimer;
public float WaitTimer
{
get;
set;
get => waitTimer;
set
{
if (value > 0.0f)
{
//setting a wait timer means that network conditions
//aren't ideal, slow down the packet rate
PacketsPerUpdate = Math.Max(PacketsPerUpdate / 4.0f, 1.0f);
}
waitTimer = value;
}
}
public const int MaxPacketsPerUpdate = 4;
public float PacketsPerUpdate { get; set; } = 1.0f;
public byte[] Data { get; }
public bool Acknowledged;
@@ -112,15 +125,12 @@ namespace Barotrauma.Networking
public float StallPacketsTime { get; set; }
#endif
public List<FileTransferOut> ActiveTransfers
{
get { return activeTransfers; }
}
public IReadOnlyList<FileTransferOut> ActiveTransfers => activeTransfers;
public FileSender(ServerPeer serverPeer, int mtu)
{
peer = serverPeer;
chunkLen = mtu - 100;
chunkLen = mtu - 200;
activeTransfers = new List<FileTransferOut>();
}
@@ -163,13 +173,14 @@ namespace Barotrauma.Networking
}
OnStarted(transfer);
GameMain.Server.LastClientListUpdateID++;
return transfer;
}
public void Update(float deltaTime)
{
activeTransfers.RemoveAll(t => t.Connection.Status != NetworkConnectionStatus.Connected);
int numRemoved = activeTransfers.RemoveAll(t => t.Connection.Status != NetworkConnectionStatus.Connected);
var endedTransfers = activeTransfers.FindAll(t =>
t.Connection.Status != NetworkConnectionStatus.Connected ||
@@ -186,20 +197,19 @@ namespace Barotrauma.Networking
foreach (FileTransferOut transfer in activeTransfers)
{
transfer.WaitTimer -= deltaTime;
for (int i = 0; i < 10; i++)
{
if (transfer.WaitTimer > 0.0f) { break; }
Send(transfer);
}
if (transfer.WaitTimer > 0.0f) { continue; }
Send(transfer);
}
if (numRemoved > 0 || endedTransfers.Count > 0)
{
GameMain.Server.LastClientListUpdateID++;
}
}
private void Send(FileTransferOut transfer)
{
// send another part of the file
long remaining = transfer.Data.Length - transfer.SentOffset;
int sendByteCount = (remaining > chunkLen ? chunkLen : (int)remaining);
IWriteMessage message;
try
@@ -234,7 +244,7 @@ namespace Barotrauma.Networking
transfer.Status = FileTransferStatus.Sending;
if (GameSettings.VerboseLogging)
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.Log("Sending file transfer initiation message: ");
DebugConsole.Log(" File: " + transfer.FileName);
@@ -246,28 +256,44 @@ namespace Barotrauma.Networking
return;
}
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 * 10 ||
transfer.SentOffset >= transfer.Data.Length)
for (int i = 0; i < Math.Floor(transfer.PacketsPerUpdate); i++)
{
transfer.SentOffset = transfer.KnownReceivedOffset;
transfer.WaitTimer = 0.5f;
}
long remaining = transfer.Data.Length - transfer.SentOffset;
int sendByteCount = (remaining > chunkLen ? chunkLen : (int)remaining);
message = new WriteOnlyMessage();
message.Write((byte)ServerPacketHeader.FILE_TRANSFER);
message.Write((byte)FileTransferMessageType.Data);
peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable);
message.Write((byte)transfer.ID);
message.Write(transfer.SentOffset);
message.Write((ushort)sendByteCount);
int chunkDestPos = message.BytePosition;
message.BitPosition += sendByteCount * 8;
message.LengthBits = Math.Max(message.LengthBits, message.BitPosition);
Array.Copy(transfer.Data, transfer.SentOffset, message.Buffer, chunkDestPos, sendByteCount);
transfer.SentOffset += sendByteCount;
if (transfer.SentOffset >= transfer.Data.Length)
{
transfer.SentOffset = transfer.KnownReceivedOffset;
transfer.WaitTimer = 1.0f;
}
peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable, compressPastThreshold: false);
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.Log($"Sending {sendByteCount} bytes of the file {transfer.FileName} ({transfer.SentOffset / 1000}/{transfer.Data.Length / 1000} kB sent)");
}
//try to increase the packet rate so large files get sent faster,
//this gets reset when packet loss or disorder sets in
transfer.PacketsPerUpdate = Math.Min(FileTransferOut.MaxPacketsPerUpdate,
transfer.PacketsPerUpdate + 0.05f);
}
#if DEBUG
transfer.WaitTimer = Math.Max(transfer.WaitTimer, StallPacketsTime);
#endif
@@ -283,11 +309,6 @@ namespace Barotrauma.Networking
transfer.Status = FileTransferStatus.Error;
return;
}
if (GameSettings.VerboseLogging)
{
DebugConsole.Log($"Sending {sendByteCount} bytes of the file {transfer.FileName} ({transfer.SentOffset / 1000}/{transfer.Data.Length / 1000} kB sent)");
}
}
public void CancelTransfer(FileTransferOut transfer)
@@ -302,9 +323,9 @@ namespace Barotrauma.Networking
public void ReadFileRequest(IReadMessage inc, Client client)
{
byte messageType = inc.ReadByte();
FileTransferMessageType messageType = (FileTransferMessageType)inc.ReadByte();
if (messageType == (byte)FileTransferMessageType.Cancel)
if (messageType == FileTransferMessageType.Cancel)
{
byte transferId = inc.ReadByte();
var matchingTransfer = activeTransfers.Find(t => t.Connection == inc.Sender && t.ID == transferId);
@@ -312,42 +333,51 @@ namespace Barotrauma.Networking
return;
}
else if (messageType == (byte)FileTransferMessageType.Data)
else if (messageType == 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;
int expecting = inc.ReadInt32(); //the offset the client is waiting for
int lastSeen = Math.Min(matchingTransfer.SentOffset, inc.ReadInt32()); //the last offset the client got from us
matchingTransfer.KnownReceivedOffset = Math.Max(expecting, matchingTransfer.KnownReceivedOffset);
if (matchingTransfer.SentOffset < matchingTransfer.KnownReceivedOffset)
{
matchingTransfer.WaitTimer = 0.0f;
matchingTransfer.SentOffset = matchingTransfer.KnownReceivedOffset;
}
if (lastSeen - matchingTransfer.KnownReceivedOffset >= chunkLen * 10 ||
matchingTransfer.SentOffset >= matchingTransfer.Data.Length)
{
matchingTransfer.SentOffset = matchingTransfer.KnownReceivedOffset;
matchingTransfer.WaitTimer = 1.0f;
}
if (matchingTransfer.KnownReceivedOffset >= matchingTransfer.Data.Length)
{
matchingTransfer.Status = FileTransferStatus.Finished;
}
}
return;
}
byte fileType = inc.ReadByte();
FileTransferType fileType = (FileTransferType)inc.ReadByte();
switch (fileType)
{
case (byte)FileTransferType.Submarine:
case FileTransferType.Submarine:
string fileName = inc.ReadString();
string fileHash = inc.ReadString();
var requestedSubmarine = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == fileName && s.MD5Hash.Hash == fileHash);
var requestedSubmarine = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == fileName && s.MD5Hash.StringRepresentation == fileHash);
if (requestedSubmarine != null)
{
StartTransfer(inc.Sender, FileTransferType.Submarine, requestedSubmarine.FilePath);
}
break;
case (byte)FileTransferType.CampaignSave:
case FileTransferType.CampaignSave:
if (GameMain.GameSession != null &&
!ActiveTransfers.Any(t => t.Connection == inc.Sender && t.FileType == FileTransferType.CampaignSave))
{
@@ -357,6 +387,23 @@ namespace Barotrauma.Networking
client.LastCampaignSaveSendTime = new Pair<ushort, float>(campaign.LastSaveID, (float)Lidgren.Network.NetTime.Now);
}
}
break;
case FileTransferType.Mod:
string modName = inc.ReadString();
Md5Hash modHash = Md5Hash.StringAsHash(inc.ReadString());
if (!GameMain.Server.ServerSettings.AllowModDownloads) { return; }
if (!(GameMain.Server.ModSender is { Ready: true })) { return; }
ContentPackage mod = ContentPackageManager.AllPackages.FirstOrDefault(p => p.Hash.Equals(modHash));
if (mod is null) { return; }
string modCompressedPath = ModSender.GetCompressedModPath(mod);
if (!File.Exists(modCompressedPath)) { return; }
StartTransfer(inc.Sender, FileTransferType.Mod, modCompressedPath);
break;
}
}
@@ -0,0 +1,59 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Barotrauma.Networking
{
class ModSender : IDisposable
{
public const string UploadFolder = "TempMods_Upload";
public const string Extension = ".barodir.gz";
public bool Ready { get; private set; } = false;
public ModSender()
{
DeleteDir();
Directory.CreateDirectory(UploadFolder);
TaskPool.Add(
"ModSender",
Task.WhenAll(
ContentPackageManager.EnabledPackages.All
.Where(p => p != ContentPackageManager.VanillaCorePackage && p.HasMultiplayerSyncedContent)
.Select(CompressMod)),
(t) => Ready = true);
}
public static string GetCompressedModPath(ContentPackage mod)
{
string dir = mod.Dir;
string resultFileName
= dir.StartsWith(ContentPackage.LocalModsDir)
? $"Local_{mod.Name}"
: $"Workshop_{mod.Name}_{mod.SteamWorkshopId}";
resultFileName = ToolBox.RemoveInvalidFileNameChars(resultFileName.Replace('\\', '_').Replace('/', '_'));
resultFileName = $"{resultFileName}{Extension}";
return Path.Combine(UploadFolder, resultFileName);
}
public async Task CompressMod(ContentPackage mod)
{
await Task.Yield();
string dir = mod.Dir;
SaveUtil.CompressDirectory(dir, GetCompressedModPath(mod), fileName => { });
}
private void DeleteDir()
{
if (Directory.Exists(UploadFolder)) { Directory.Delete(UploadFolder, recursive: true); }
}
public bool IsDisposed { get; private set; } = false;
public void Dispose()
{
IsDisposed = true;
DeleteDir();
}
}
}
File diff suppressed because it is too large Load Diff
@@ -128,7 +128,7 @@ namespace Barotrauma
clientMemory.PreviousNotifiedKarma >= KickBanThreshold + KarmaNotificationInterval &&
client.Karma < KickBanThreshold + KarmaNotificationInterval)
{
GameMain.Server.SendDirectChatMessage(TextManager.Get("KarmaBanWarning"), client);
GameMain.Server.SendDirectChatMessage(TextManager.Get("KarmaBanWarning").Value, client);
GameServer.Log(GameServer.ClientLogName(client) + " has been warned for having dangerously low karma.", ServerLog.MessageType.Karma);
clientMemory.PreviousNotifiedKarma = client.Karma;
clientMemory.PreviousKarmaNotificationTime = Timing.TotalTime;
@@ -170,7 +170,7 @@ namespace Barotrauma
existingAffliction.Strength = herpesStrength;
if (herpesStrength <= 0.0f)
{
client.Character.CharacterHealth.ReduceAffliction(null, "invertcontrols", 100.0f);
client.Character.CharacterHealth.ReduceAfflictionOnAllLimbs("invertcontrols".ToIdentifier(), 100.0f);
}
}
@@ -283,7 +283,7 @@ namespace Barotrauma
if (foundItem == null) { return; }
bool isIdCard = foundItem.prefab.Identifier == "idcard";
bool isIdCard = ((MapEntity)foundItem).Prefab.Identifier == "idcard";
bool isWeapon = foundItem.GetComponent<RangedWeapon>() != null || foundItem.GetComponent<MeleeWeapon>() != null;
if (isIdCard)
@@ -394,8 +394,8 @@ namespace Barotrauma
}
//attacking/healing clowns has a smaller effect on karma
if (target.HasEquippedItem("clownmask") &&
target.HasEquippedItem("clowncostume"))
if (target.HasEquippedItem("clownmask".ToIdentifier()) &&
target.HasEquippedItem("clowncostume".ToIdentifier()))
{
damage *= 0.5f;
stun *= 0.5f;
@@ -604,8 +604,8 @@ namespace Barotrauma
if (client == null) { return; }
//all penalties/rewards are halved when wearing a clown costume
if (target.HasEquippedItem("clownmask") &&
target.HasEquippedItem("clowncostume"))
if (target.HasEquippedItem("clownmask".ToIdentifier()) &&
target.HasEquippedItem("clowncostume".ToIdentifier()))
{
amount *= 0.5f;
}
@@ -38,7 +38,7 @@ namespace Barotrauma.Networking
public void Write(IWriteMessage msg, Client recipient)
{
serializable.ServerWrite(msg, recipient, Data);
serializable.ServerEventWrite(msg, recipient, Data);
}
}
@@ -111,7 +111,7 @@ namespace Barotrauma.Networking
lastWarningTime = -10.0;
}
public void CreateEvent(IServerSerializable entity, object[] extraData = null)
public void CreateEvent(IServerSerializable entity, NetEntityEvent.IData extraData = null)
{
if (!ValidateEntity(entity)) { return; }
@@ -179,7 +179,7 @@ namespace Barotrauma.Networking
catch (Exception e)
{
string entityName = bufferedEvent.TargetEntity == null ? "null" : bufferedEvent.TargetEntity.ToString();
if (GameSettings.VerboseLogging)
if (GameSettings.CurrentConfig.VerboseLogging)
{
string errorMsg = "Failed to read server event for entity \"" + entityName + "\"!";
GameServer.Log(errorMsg + "\n" + e.StackTrace.CleanupStackTrace(), ServerLog.MessageType.Error);
@@ -291,12 +291,6 @@ namespace Barotrauma.Networking
bufferedEvents.Add(bufferedEvent);
}
public void RefreshEntityIDs()
{
events.ForEach(e => e.RefreshEntityID());
uniqueEvents.ForEach(e => e.RefreshEntityID());
}
/// <summary>
/// Writes all the events that the client hasn't received yet into the outgoing message
/// </summary>
@@ -310,15 +304,7 @@ namespace Barotrauma.Networking
/// </summary>
public void Write(Client client, IWriteMessage msg, out List<NetEntityEvent> sentEvents)
{
List<NetEntityEvent> eventsToSync = null;
if (client.NeedsMidRoundSync)
{
eventsToSync = GetEventsToSync(client);
}
else
{
eventsToSync = GetEventsToSync(client);
}
List<NetEntityEvent> eventsToSync = GetEventsToSync(client);
if (eventsToSync.Count == 0)
{
@@ -347,7 +333,7 @@ namespace Barotrauma.Networking
count++;
if (count > 3) { break; }
}
if (GameSettings.VerboseLogging)
if (GameSettings.CurrentConfig.VerboseLogging)
{
GameServer.Log(warningMsg, ServerLog.MessageType.Error);
}
@@ -460,6 +446,7 @@ namespace Barotrauma.Networking
/// </summary>
public void Read(IReadMessage msg, Client sender = null)
{
msg.ReadPadBits();
UInt16 firstEventID = msg.ReadUInt16();
int eventCount = msg.ReadByte();
@@ -470,7 +457,6 @@ namespace Barotrauma.Networking
if (entityID == Entity.NullEntityID)
{
msg.ReadPadBits();
if (thisEventID == (UInt16)(sender.LastSentEntityEventID + 1)) sender.LastSentEntityEventID++;
continue;
}
@@ -482,7 +468,7 @@ namespace Barotrauma.Networking
//skip the event if we've already received it
if (thisEventID != (UInt16)(sender.LastSentEntityEventID + 1))
{
if (GameSettings.VerboseLogging)
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.NewMessage("Received msg " + thisEventID + ", expecting " + sender.LastSentEntityEventID, Color.Red);
}
@@ -490,10 +476,10 @@ namespace Barotrauma.Networking
}
else if (entity == null)
{
//entity not found -> consider the even read and skip over it
//entity not found -> consider the event read and skip over it
//(can happen, for example, when a client uses a medical item repeatedly
//and creates an event for it before receiving the event about it being removed)
if (GameSettings.VerboseLogging)
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.NewMessage(
"Received msg " + thisEventID + ", entity " + entityID + " not found",
@@ -504,7 +490,7 @@ namespace Barotrauma.Networking
}
else
{
if (GameSettings.VerboseLogging)
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.NewMessage("Received msg " + thisEventID, Microsoft.Xna.Framework.Color.Green);
}
@@ -519,7 +505,6 @@ namespace Barotrauma.Networking
sender.LastSentEntityEventID++;
}
msg.ReadPadBits();
}
}
@@ -536,7 +521,7 @@ namespace Barotrauma.Networking
var clientEntity = entity as IClientSerializable;
if (clientEntity == null) return;
clientEntity.ServerRead(ClientNetObject.ENTITY_STATE, buffer, sender);
clientEntity.ServerEventRead(buffer, sender);
}
public void Clear()
@@ -133,7 +133,7 @@ namespace Barotrauma.Networking
#if DEBUG
DebugConsole.ThrowError(errorMsg);
#else
if (GameSettings.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
if (GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
#endif
}
@@ -339,7 +339,7 @@ namespace Barotrauma.Networking
}
}
public override void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod)
public override void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod, bool compressPastThreshold = true)
{
if (netServer == null) { return; }
@@ -366,7 +366,7 @@ namespace Barotrauma.Networking
NetOutgoingMessage lidgrenMsg = netServer.CreateMessage();
byte[] msgData = new byte[msg.LengthBytes];
msg.PrepareForSending(ref msgData, out bool isCompressed, out int length);
msg.PrepareForSending(ref msgData, compressPastThreshold, out bool isCompressed, out int length);
lidgrenMsg.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None));
lidgrenMsg.Write((UInt16)length);
lidgrenMsg.Write(msgData, 0, length);
@@ -435,7 +435,7 @@ namespace Barotrauma.Networking
{
if (pendingClient.SteamID == null)
{
bool requireSteamAuth = GameMain.Config.RequireSteamAuthentication;
bool requireSteamAuth = GameSettings.CurrentConfig.RequireSteamAuthentication;
#if DEBUG
requireSteamAuth = false;
#endif
@@ -123,7 +123,7 @@ namespace Barotrauma.Networking
return;
}
string language = inc.ReadString();
LanguageIdentifier language = inc.ReadIdentifier().ToLanguageIdentifier();
pendingClient.Connection.Language = language;
Client nameTaken = GameMain.Server.ConnectedClients.Find(c => Homoglyphs.Compare(c.Name.ToLower(), name.ToLower()));
@@ -253,12 +253,14 @@ namespace Barotrauma.Networking
case ConnectionInitialization.ContentPackageOrder:
outMsg.Write(GameMain.Server.ServerName);
var mpContentPackages = GameMain.Config.AllEnabledPackages.Where(cp => cp.HasMultiplayerIncompatibleContent).ToList();
var mpContentPackages = ContentPackageManager.EnabledPackages.All.Where(cp => cp.HasMultiplayerSyncedContent).ToList();
outMsg.WriteVariableUInt32((UInt32)mpContentPackages.Count);
for (int i = 0; i < mpContentPackages.Count; i++)
{
outMsg.Write(mpContentPackages[i].Name);
outMsg.Write(mpContentPackages[i].MD5hash.Hash);
byte[] hashBytes = mpContentPackages[i].Hash.ByteRepresentation;
outMsg.WriteVariableUInt32((UInt32)hashBytes.Length);
outMsg.Write(hashBytes, 0, hashBytes.Length);
outMsg.Write(mpContentPackages[i].SteamWorkshopId);
UInt32 installTimeDiffSeconds = (UInt32)((mpContentPackages[i].InstallTime ?? DateTime.UtcNow) - DateTime.UtcNow).TotalSeconds;
outMsg.Write(installTimeDiffSeconds);
@@ -301,7 +303,7 @@ namespace Barotrauma.Networking
}
}
public abstract void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod);
public abstract void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod, bool compressPastThreshold = true);
public abstract void Disconnect(NetworkConnection conn, string msg = null);
}
}
@@ -106,7 +106,7 @@ namespace Barotrauma.Networking
#if DEBUG
DebugConsole.ThrowError(errorMsg);
#else
if (GameSettings.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
if (GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
#endif
}
@@ -223,7 +223,7 @@ namespace Barotrauma.Networking
string ownerName = inc.ReadString();
OwnerConnection = new SteamP2PConnection(ownerName, OwnerSteamID)
{
Language = GameMain.Config.Language
Language = GameSettings.CurrentConfig.Language
};
OwnerConnection.SetOwnerSteamIDIfUnknown(OwnerSteamID);
@@ -250,7 +250,7 @@ namespace Barotrauma.Networking
throw new InvalidOperationException("Called InitializeSteamServerCallbacks on SteamP2PServerPeer!");
}
public override void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod)
public override void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod, bool compressPastThreshold = true)
{
if (!started) { return; }
@@ -263,7 +263,7 @@ namespace Barotrauma.Networking
IWriteMessage msgToSend = new WriteOnlyMessage();
byte[] msgData = new byte[16];
msg.PrepareForSending(ref msgData, out bool isCompressed, out int length);
msg.PrepareForSending(ref msgData, compressPastThreshold, out bool isCompressed, out int length);
msgToSend.Write(conn.SteamID);
msgToSend.Write((byte)deliveryMethod);
msgToSend.Write((byte)((isCompressed ? PacketHeader.IsCompressed : PacketHeader.None) | PacketHeader.IsServerMessage));
@@ -248,7 +248,7 @@ namespace Barotrauma.Networking
}
var shuttleGaps = Gap.GapList.FindAll(g => g.Submarine == RespawnShuttle && g.ConnectedWall != null);
shuttleGaps.ForEach(g => Spawner.AddToRemoveQueue(g));
shuttleGaps.ForEach(g => Spawner.AddEntityToRemoveQueue(g));
var dockingPorts = Item.ItemList.FindAll(i => i.Submarine == RespawnShuttle && i.GetComponent<DockingPort>() != null);
dockingPorts.ForEach(d => d.GetComponent<DockingPort>().Undock());
@@ -376,7 +376,7 @@ namespace Barotrauma.Networking
{
if (campaign?.GetClientCharacterData(c) == null || c.CharacterInfo.Job == null)
{
c.CharacterInfo.Job = new Job(c.AssignedJob.First, Rand.RandSync.Unsynced, c.AssignedJob.Second);
c.CharacterInfo.Job = new Job(c.AssignedJob.Prefab, Rand.RandSync.Unsynced, c.AssignedJob.Variant);
}
}
@@ -390,17 +390,17 @@ namespace Barotrauma.Networking
if ((shuttlePos != null && Level.Loaded.GetRealWorldDepth(shuttlePos.Value.Y) > Level.DefaultRealWorldCrushDepth) ||
Level.Loaded.GetRealWorldDepth(Submarine.MainSub.WorldPosition.Y) > Level.DefaultRealWorldCrushDepth)
{
divingSuitPrefab = ItemPrefab.Prefabs.FirstOrDefault(it => it.Tags.Any(t => t.Equals("respawnsuitdeep", StringComparison.OrdinalIgnoreCase)));
divingSuitPrefab = ItemPrefab.Prefabs.FirstOrDefault(it => it.Tags.Any(t => t == "respawnsuitdeep"));
}
if (divingSuitPrefab == null)
{
divingSuitPrefab =
ItemPrefab.Prefabs.FirstOrDefault(it => it.Tags.Any(t => t.Equals("respawnsuit", StringComparison.OrdinalIgnoreCase))) ??
ItemPrefab.Find(null, "divingsuit");
ItemPrefab.Prefabs.FirstOrDefault(it => it.Tags.Any(t => t == "respawnsuit")) ??
ItemPrefab.Find(null, "divingsuit".ToIdentifier());
}
ItemPrefab oxyPrefab = ItemPrefab.Find(null, "oxygentank");
ItemPrefab scooterPrefab = ItemPrefab.Find(null, "underwaterscooter");
ItemPrefab batteryPrefab = ItemPrefab.Find(null, "batterycell");
ItemPrefab oxyPrefab = ItemPrefab.Find(null, "oxygentank".ToIdentifier());
ItemPrefab scooterPrefab = ItemPrefab.Find(null, "underwaterscooter".ToIdentifier());
ItemPrefab batteryPrefab = ItemPrefab.Find(null, "batterycell".ToIdentifier());
var cargoSp = WayPoint.WayPointList.Find(wp => wp.Submarine == respawnSub && wp.SpawnType == SpawnType.Cargo);
@@ -468,11 +468,11 @@ namespace Barotrauma.Networking
if (divingSuitPrefab != null && oxyPrefab != null)
{
var divingSuit = new Item(divingSuitPrefab, pos, respawnSub);
Spawner.CreateNetworkEvent(divingSuit, false);
Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(divingSuit));
respawnItems.Add(divingSuit);
var oxyTank = new Item(oxyPrefab, pos, respawnSub);
Spawner.CreateNetworkEvent(oxyTank, false);
Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(oxyTank));
divingSuit.Combine(oxyTank, user: null);
respawnItems.Add(oxyTank);
}
@@ -480,10 +480,10 @@ namespace Barotrauma.Networking
if (scooterPrefab != null && batteryPrefab != null)
{
var scooter = new Item(scooterPrefab, pos, respawnSub);
Spawner.CreateNetworkEvent(scooter, false);
Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(scooter));
var battery = new Item(batteryPrefab, pos, respawnSub);
Spawner.CreateNetworkEvent(battery, false);
Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(battery));
scooter.Combine(battery, user: null);
respawnItems.Add(scooter);
@@ -543,13 +543,13 @@ namespace Barotrauma.Networking
if (characterInfo?.Job == null) { return; }
foreach (Skill skill in characterInfo.Job.Skills)
{
var skillPrefab = characterInfo.Job.Prefab.Skills.Find(s => skill.Identifier.Equals(s.Identifier, StringComparison.OrdinalIgnoreCase));
var skillPrefab = characterInfo.Job.Prefab.Skills.Find(s => skill.Identifier == s.Identifier);
if (skillPrefab == null) { continue; }
skill.Level = MathHelper.Lerp(skill.Level, skillPrefab.LevelRange.Start, SkillReductionOnCampaignMidroundRespawn);
}
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.WriteRangedInteger((int)CurrentState, 0, Enum.GetNames(typeof(State)).Length);
@@ -268,7 +268,7 @@ namespace Barotrauma.Networking
doc.Root.SetAttributeValue("HiddenSubs", string.Join(",", HiddenSubs));
doc.Root.SetAttributeValue("AllowedRandomMissionTypes", string.Join(",", AllowedRandomMissionTypes));
doc.Root.SetAttributeValue("AllowedClientNameChars", string.Join(",", AllowedClientNameChars.Select(c => c.First + "-" + c.Second)));
doc.Root.SetAttributeValue("AllowedClientNameChars", string.Join(",", AllowedClientNameChars.Select(c => $"{c.Start}-{c.End}")));
SerializableProperty.SerializeProperties(this, doc.Root, true);
@@ -307,13 +307,13 @@ namespace Barotrauma.Networking
if (string.IsNullOrEmpty(doc.Root.GetAttributeString("losmode", "")))
{
LosMode = GameMain.Config.LosMode;
LosMode = GameSettings.CurrentConfig.Graphics.LosMode;
}
AutoRestart = doc.Root.GetAttributeBool("autorestart", false);
Voting.AllowSubVoting = SubSelectionMode == SelectionMode.Vote;
Voting.AllowModeVoting = ModeSelectionMode == SelectionMode.Vote;
AllowSubVoting = SubSelectionMode == SelectionMode.Vote;
AllowModeVoting = ModeSelectionMode == SelectionMode.Vote;
selectedLevelDifficulty = doc.Root.GetAttributeFloat("LevelDifficulty", 20.0f);
GameMain.NetLobbyScreen.SetLevelDifficulty(selectedLevelDifficulty);
@@ -321,11 +321,16 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen.SetTraitorsEnabled(traitorsEnabled);
HiddenSubs.UnionWith(doc.Root.GetAttributeStringArray("HiddenSubs", Array.Empty<string>()));
if (HiddenSubs.Any())
{
UpdateFlag(NetFlags.HiddenSubs);
}
SelectedSubmarine = SelectNonHiddenSubmarine(SelectedSubmarine);
string[] defaultAllowedClientNameChars =
new string[] {
new string[]
{
"32-33",
"38-46",
"48-57",
@@ -370,7 +375,12 @@ namespace Barotrauma.Networking
}
}
if (min > -1 && max > -1) { AllowedClientNameChars.Add(new Pair<int, int>(min, max)); }
if (min > max)
{
//swap min and max
(min, max) = (max, min);
}
if (min > -1 && max > -1) { AllowedClientNameChars.Add(new Range<int>(min, max)); }
}
AllowedRandomMissionTypes = new List<MissionType>();
@@ -399,12 +409,7 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen.SetBotSpawnMode(BotSpawnMode);
GameMain.NetLobbyScreen.SetBotCount(BotCount);
List<string> monsterNames = CharacterPrefab.Prefabs.Select(p => p.Identifier).ToList();
MonsterEnabled = new Dictionary<string, bool>();
foreach (string s in monsterNames)
{
if (!MonsterEnabled.ContainsKey(s)) MonsterEnabled.Add(s, true);
}
MonsterEnabled ??= CharacterPrefab.Prefabs.Select(p => (p.Identifier, true)).ToDictionary();
}
public string SelectNonHiddenSubmarine(string current = null)
@@ -1,113 +0,0 @@
using System.Linq;
namespace Barotrauma.Steam
{
partial class SteamManager
{
#region Server
private static void InitializeProjectSpecific() { isInitialized = true; }
public static bool CreateServer(Networking.GameServer server, bool isPublic)
{
isInitialized = true;
Steamworks.SteamServerInit options = new Steamworks.SteamServerInit("Barotrauma", "Barotrauma")
{
GamePort = (ushort)server.Port,
QueryPort = isPublic ? (ushort)server.QueryPort : (ushort)0,
Mode = isPublic ? Steamworks.InitServerMode.Authentication : Steamworks.InitServerMode.NoAuthentication,
IpAddress = server.ServerSettings.ListenIPAddress
};
//options.QueryShareGamePort();
Steamworks.SteamServer.Init(AppID, options, false);
if (!Steamworks.SteamServer.IsValid)
{
Steamworks.SteamServer.Shutdown();
DebugConsole.ThrowError("Initializing Steam server failed.");
return false;
}
RefreshServerDetails(server);
server.ServerPeer.InitializeSteamServerCallbacks();
Steamworks.SteamServer.LogOnAnonymous();
return true;
}
public static bool RefreshServerDetails(Networking.GameServer server)
{
if (!isInitialized || !Steamworks.SteamServer.IsValid)
{
return false;
}
var contentPackages = GameMain.Config.AllEnabledPackages.Where(cp => cp.HasMultiplayerIncompatibleContent);
// These server state variables may be changed at any time. Note that there is no longer a mechanism
// to send the player count. The player count is maintained by steam and you should use the player
// creation/authentication functions to maintain your player count.
Steamworks.SteamServer.ServerName = server.ServerName;
Steamworks.SteamServer.MaxPlayers = server.ServerSettings.MaxPlayers;
Steamworks.SteamServer.Passworded = server.ServerSettings.HasPassword;
Steamworks.SteamServer.MapName = GameMain.NetLobbyScreen?.SelectedSub?.DisplayName ?? "";
Steamworks.SteamServer.SetKey("haspassword", server.ServerSettings.HasPassword.ToString());
Steamworks.SteamServer.SetKey("message", GameMain.Server.ServerSettings.ServerMessageText);
Steamworks.SteamServer.SetKey("version", GameMain.Version.ToString());
Steamworks.SteamServer.SetKey("playercount", GameMain.Server.ConnectedClients.Count.ToString());
Steamworks.SteamServer.SetKey("contentpackage", string.Join(",", contentPackages.Select(cp => cp.Name)));
Steamworks.SteamServer.SetKey("contentpackagehash", string.Join(",", contentPackages.Select(cp => cp.MD5hash.Hash)));
Steamworks.SteamServer.SetKey("contentpackageid", string.Join(",", contentPackages.Select(cp => cp.SteamWorkshopId)));
Steamworks.SteamServer.SetKey("usingwhitelist", (server.ServerSettings.Whitelist != null && server.ServerSettings.Whitelist.Enabled).ToString());
Steamworks.SteamServer.SetKey("modeselectionmode", server.ServerSettings.ModeSelectionMode.ToString());
Steamworks.SteamServer.SetKey("subselectionmode", server.ServerSettings.SubSelectionMode.ToString());
Steamworks.SteamServer.SetKey("voicechatenabled", server.ServerSettings.VoiceChatEnabled.ToString());
Steamworks.SteamServer.SetKey("allowspectating", server.ServerSettings.AllowSpectating.ToString());
Steamworks.SteamServer.SetKey("allowrespawn", server.ServerSettings.AllowRespawn.ToString());
Steamworks.SteamServer.SetKey("traitors", server.ServerSettings.TraitorsEnabled.ToString());
Steamworks.SteamServer.SetKey("gamestarted", server.GameStarted.ToString());
Steamworks.SteamServer.SetKey("gamemode", server.ServerSettings.GameModeIdentifier);
Steamworks.SteamServer.SetKey("playstyle", server.ServerSettings.PlayStyle.ToString());
Steamworks.SteamServer.DedicatedServer = true;
return true;
}
public static Steamworks.BeginAuthResult StartAuthSession(byte[] authTicketData, ulong clientSteamID)
{
if (!isInitialized || !Steamworks.SteamServer.IsValid) return Steamworks.BeginAuthResult.ServerNotConnectedToSteam;
DebugConsole.Log("SteamManager authenticating Steam client " + clientSteamID);
Steamworks.BeginAuthResult startResult = Steamworks.SteamServer.BeginAuthSession(authTicketData, clientSteamID);
if (startResult != Steamworks.BeginAuthResult.OK)
{
DebugConsole.Log("Authentication failed: failed to start auth session (" + startResult.ToString() + ")");
}
return startResult;
}
public static void StopAuthSession(ulong clientSteamID)
{
if (!isInitialized || !Steamworks.SteamServer.IsValid) return;
DebugConsole.Log("SteamManager ending auth session with Steam client " + clientSteamID);
Steamworks.SteamServer.EndSession(clientSteamID);
}
public static bool CloseServer()
{
if (!isInitialized || !Steamworks.SteamServer.IsValid) return false;
Steamworks.SteamServer.Shutdown();
return true;
}
#endregion
}
}
@@ -7,59 +7,168 @@ namespace Barotrauma
{
partial class Voting
{
public bool AllowSubVoting
public interface IVote
{
get { return allowSubVoting; }
set { allowSubVoting = value; }
}
public bool AllowModeVoting
{
get { return allowModeVoting; }
set { allowModeVoting = value; }
}
public Client VoteStarter { get; }
public VoteType VoteType { get; }
public float Timer { get; set; }
public struct SubmarineVote
public VoteState State { get; set; }
public void Finish(Voting voting, bool passed);
}
public class SubmarineVote : IVote
{
public Client VoteStarter;
public Client VoteStarter { get; }
public VoteType VoteType { get; }
public float Timer { get; set; }
public VoteState State { get; set; }
public SubmarineInfo Sub;
public VoteType VoteType;
public float Timer;
public int DeliveryFee;
public VoteState State;
public SubmarineVote(Client starter, SubmarineInfo subInfo, int deliveryFee, VoteType voteType)
{
Sub = subInfo;
DeliveryFee = deliveryFee;
VoteType = voteType;
State = VoteState.Started;
VoteStarter = starter;
}
public void Finish(Voting voting, bool passed)
{
if (passed)
{
GameMain.Server?.SwitchSubmarine();
}
voting.StopSubmarineVote(passed);
}
}
public static SubmarineVote SubVote;
public static IVote ActiveVote;
public class TransferVote : IVote
{
public Client VoteStarter { get; }
public VoteType VoteType { get; }
public float Timer { get; set; }
public VoteState State { get; set; }
//null = bank
public readonly Client From, To;
public readonly int TransferAmount;
public TransferVote(Client starter, Client from, int transferAmount, Client to)
{
VoteStarter = starter;
From = from;
To = to;
TransferAmount = transferAmount;
State = VoteState.Started;
VoteType = VoteType.TransferMoney;
}
public void Finish(Voting voting, bool passed)
{
if (passed)
{
Wallet fromWallet = From == null ? (GameMain.GameSession.GameMode as MultiPlayerCampaign)?.Bank : From.Character?.Wallet;
if (fromWallet.TryDeduct(TransferAmount))
{
Wallet toWallet = To == null ? (GameMain.GameSession.GameMode as MultiPlayerCampaign)?.Bank : To.Character?.Wallet;
toWallet.Give(TransferAmount);
}
}
voting.StopMoneyTransferVote(passed);
}
}
private static readonly Queue<IVote> pendingVotes = new Queue<IVote>();
private void StartSubmarineVote(SubmarineInfo subInfo, VoteType voteType, Client sender)
{
SubVote.Sub = subInfo;
SubVote.DeliveryFee = voteType == VoteType.SwitchSub ? GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation) : 0;
SubVote.VoteType = voteType;
SubVote.State = VoteState.Started;
SubVote.VoteStarter = sender;
VoteRunning = true;
sender.SetVote(voteType, 2);
if (ActiveVote == null)
{
sender.SetVote(voteType, 2);
}
var subVote = new SubmarineVote(
sender,
subInfo,
voteType == VoteType.SwitchSub ? GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation) : 0,
voteType);
StartOrEnqueueVote(subVote);
GameMain.Server.UpdateVoteStatus(checkActiveVote: false);
}
public void StopSubmarineVote(bool passed)
{
VoteRunning = false;
SubVote.State = passed ? VoteState.Passed : VoteState.Failed;
if (!(ActiveVote is SubmarineVote)) { return; }
StopActiveVote(passed);
}
GameMain.Server.UpdateVoteStatus();
public void StopMoneyTransferVote(bool passed)
{
if (!(ActiveVote is TransferVote)) { return; }
StopActiveVote(passed);
}
public void StopActiveVote(bool passed)
{
ActiveVote.State = passed ? VoteState.Passed : VoteState.Failed;
GameMain.Server.UpdateVoteStatus(checkActiveVote: false);
GameMain.NetworkMember.SubmarineVoteYesCount = GameMain.NetworkMember.SubmarineVoteNoCount = GameMain.NetworkMember.SubmarineVoteMax = 0;
for (int i = 0; i < GameMain.NetworkMember.ConnectedClients.Count; i++)
{
GameMain.NetworkMember.ConnectedClients[i].SetVote(SubVote.VoteType, 0);
GameMain.NetworkMember.ConnectedClients[i].SetVote(ActiveVote.VoteType, 0);
}
SubVote.Sub = null;
SubVote.DeliveryFee = 0;
SubVote.VoteType = VoteType.Unknown;
SubVote.Timer = 0.0f;
SubVote.State = VoteState.None;
SubVote.VoteStarter = null;
ActiveVote = null;
if (pendingVotes.Any())
{
ActiveVote = pendingVotes.Dequeue();
ActiveVote.VoteStarter?.SetVote(ActiveVote.VoteType, 2);
}
}
public void StartTransferVote(Client starter, Client from, int transferAmount, Client to)
{
if (ActiveVote == null)
{
starter.SetVote(VoteType.TransferMoney, 2);
}
StartOrEnqueueVote(new TransferVote(starter, from, transferAmount, to));
GameMain.Server.UpdateVoteStatus(checkActiveVote: false);
}
private void StartOrEnqueueVote(IVote vote)
{
if (ActiveVote == null)
{
ActiveVote = vote;
}
else
{
pendingVotes.Enqueue(vote);
}
}
public void Update(float deltaTime)
{
if (ActiveVote == null) { return; }
ActiveVote.Timer += deltaTime;
if (ActiveVote.Timer >= GameMain.NetworkMember.ServerSettings.VoteTimeout)
{
// Do not take unanswered into account for total
int yes = GameMain.Server.ConnectedClients.Count(c => c.InGame && c.GetVote<int>(ActiveVote.VoteType) == 2);
int no = GameMain.Server.ConnectedClients.Count(c => c.InGame && c.GetVote<int>(ActiveVote.VoteType) == 1);
ActiveVote.Finish(this, passed: yes / (float)(yes + no) >= GameMain.NetworkMember.ServerSettings.VoteRequiredRatio);
}
}
public void ServerRead(IReadMessage inc, Client sender)
@@ -85,7 +194,7 @@ namespace Barotrauma
string hash = equalityCheckVal > 0 ? string.Empty : inc.ReadString();
SubmarineInfo sub = equalityCheckVal > 0 ?
SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Type == SubmarineType.Player && s.EqualityCheckVal == equalityCheckVal) :
SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Type == SubmarineType.Player && s.MD5Hash.Hash == hash);
SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Type == SubmarineType.Player && s.MD5Hash.StringRepresentation == hash);
sender.SetVote(voteType, sub);
break;
case VoteType.Mode:
@@ -97,10 +206,6 @@ namespace Barotrauma
case VoteType.EndRound:
if (!sender.HasSpawned) { return; }
sender.SetVote(voteType, inc.ReadBoolean());
GameMain.NetworkMember.EndVoteCount = GameMain.Server.ConnectedClients.Count(c => c.HasSpawned && c.GetVote<bool>(VoteType.EndRound));
GameMain.NetworkMember.EndVoteMax = GameMain.Server.ConnectedClients.Count(c => c.HasSpawned);
break;
case VoteType.Kick:
byte kickedClientID = inc.ReadByte();
@@ -126,24 +231,34 @@ namespace Barotrauma
case VoteType.PurchaseAndSwitchSub:
case VoteType.PurchaseSub:
case VoteType.SwitchSub:
case VoteType.TransferMoney:
bool startVote = inc.ReadBoolean();
if (startVote)
{
string subName = inc.ReadString();
SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName);
if (GameMain.GameSession?.Campaign is MultiPlayerCampaign campaign && (campaign.CanPurchaseSub(subInfo) || GameMain.GameSession.IsSubmarineOwned(subInfo)))
if (voteType == VoteType.TransferMoney)
{
StartSubmarineVote(subInfo, voteType, sender);
int amount = inc.ReadInt32();
int fromClientId = inc.ReadByte();
int toClientId = inc.ReadByte();
pendingVotes.Enqueue(new TransferVote(sender,
GameMain.Server.ConnectedClients.Find(c => c.ID == fromClientId),
amount,
GameMain.Server.ConnectedClients.Find(c => c.ID == toClientId)));
}
else
{
string subName = inc.ReadString();
SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName);
if (GameMain.GameSession?.Campaign is MultiPlayerCampaign campaign && (campaign.CanPurchaseSub(subInfo, sender) || GameMain.GameSession.IsSubmarineOwned(subInfo)))
{
StartSubmarineVote(subInfo, voteType, sender);
}
}
}
else
{
sender.SetVote(voteType, (int)inc.ReadByte());
}
GameMain.Server.SubmarineVoteYesCount = GameMain.Server.ConnectedClients.Count(c => c.GetVote<int>(SubVote.VoteType) == 2);
GameMain.Server.SubmarineVoteNoCount = GameMain.Server.ConnectedClients.Count(c => c.GetVote<int>(SubVote.VoteType) == 1);
GameMain.Server.SubmarineVoteMax = GameMain.Server.ConnectedClients.Count(c => c.InGame);
break;
}
@@ -154,10 +269,10 @@ namespace Barotrauma
public void ServerWrite(IWriteMessage msg)
{
if (GameMain.Server == null) return;
if (GameMain.Server == null) { return; }
msg.Write(allowSubVoting);
if (allowSubVoting)
msg.Write(GameMain.Server.ServerSettings.AllowSubVoting);
if (GameMain.Server.ServerSettings.AllowSubVoting)
{
IReadOnlyDictionary<SubmarineInfo, int> voteList = GetVoteCounts<SubmarineInfo>(VoteType.Sub, GameMain.Server.ConnectedClients);
msg.Write((byte)voteList.Count);
@@ -167,8 +282,8 @@ namespace Barotrauma
msg.Write(vote.Key.Name);
}
}
msg.Write(AllowModeVoting);
if (allowModeVoting)
msg.Write(GameMain.Server.ServerSettings.AllowModeVoting);
if (GameMain.Server.ServerSettings.AllowModeVoting)
{
IReadOnlyDictionary<GameModePreset, int> voteList = GetVoteCounts<GameModePreset>(VoteType.Mode, GameMain.Server.ConnectedClients);
msg.Write((byte)voteList.Count);
@@ -178,60 +293,78 @@ namespace Barotrauma
msg.Write(vote.Key.Identifier);
}
}
msg.Write(AllowEndVoting);
if (AllowEndVoting)
msg.Write(GameMain.Server.ServerSettings.AllowEndVoting);
if (GameMain.Server.ServerSettings.AllowEndVoting)
{
msg.Write((byte)GameMain.Server.ConnectedClients.Count(c => c.HasSpawned && c.GetVote<bool>(VoteType.EndRound)));
msg.Write((byte)GameMain.Server.ConnectedClients.Count(c => c.HasSpawned));
}
msg.Write(AllowVoteKick);
msg.Write(GameMain.Server.ServerSettings.AllowVoteKick);
msg.Write((byte)SubVote.State);
if (SubVote.State != VoteState.None)
msg.Write((byte)(ActiveVote?.State ?? VoteState.None));
if (ActiveVote != null)
{
msg.Write((byte)SubVote.VoteType);
if (SubVote.VoteType != VoteType.Unknown)
{
var yesClients = GameMain.Server.ConnectedClients.FindAll(c => c.GetVote<int>(SubVote.VoteType) == 2);
msg.Write((byte)ActiveVote.VoteType);
if (ActiveVote.State != VoteState.None && ActiveVote.VoteType != VoteType.Unknown)
{
var yesClients = GameMain.Server.ConnectedClients.FindAll(c => c.InGame && c.GetVote<int>(ActiveVote.VoteType) == 2);
msg.Write((byte)yesClients.Count);
foreach (Client c in yesClients)
{
msg.Write(c.ID);
}
var noClients = GameMain.Server.ConnectedClients.FindAll(c => c.GetVote<int>(SubVote.VoteType) == 1);
var noClients = GameMain.Server.ConnectedClients.FindAll(c => c.InGame && c.GetVote<int>(ActiveVote.VoteType) == 1);
msg.Write((byte)noClients.Count);
foreach (Client c in noClients)
{
msg.Write(c.ID);
}
msg.Write((byte)GameMain.Server.SubmarineVoteMax);
msg.Write((byte)GameMain.Server.ConnectedClients.Count(c => c.InGame));
switch (SubVote.State)
switch (ActiveVote.State)
{
case VoteState.Started:
msg.Write(SubVote.Sub.Name);
msg.Write(SubVote.VoteStarter.ID);
msg.Write((byte)GameMain.Server.ServerSettings.SubmarineVoteTimeout);
msg.Write(ActiveVote.VoteStarter.ID);
msg.Write((byte)GameMain.Server.ServerSettings.VoteTimeout);
switch (ActiveVote.VoteType)
{
case VoteType.PurchaseSub:
case VoteType.PurchaseAndSwitchSub:
case VoteType.SwitchSub:
msg.Write((ActiveVote as SubmarineVote).Sub.Name);
break;
case VoteType.TransferMoney:
var transferVote = (ActiveVote as TransferVote);
msg.Write(transferVote.From?.ID ?? 0);
msg.Write(transferVote.To?.ID ?? 0);
msg.Write(transferVote.TransferAmount);
break;
}
break;
case VoteState.Running:
// Nothing specific
break;
case VoteState.Passed:
case VoteState.Failed:
msg.Write(SubVote.State == VoteState.Passed);
msg.Write(SubVote.Sub.Name);
if (SubVote.State == VoteState.Passed)
msg.Write(ActiveVote.State == VoteState.Passed);
switch (ActiveVote.VoteType)
{
msg.Write((short)SubVote.DeliveryFee);
case VoteType.PurchaseSub:
case VoteType.PurchaseAndSwitchSub:
case VoteType.SwitchSub:
msg.Write((ActiveVote as SubmarineVote).Sub.Name);
msg.Write((short)(ActiveVote as SubmarineVote).DeliveryFee);
break;
}
break;
}
}
}
}
}
var readyClients = GameMain.Server.ConnectedClients.FindAll(c => c.GetVote<bool>(VoteType.StartRound));
msg.Write((byte)readyClients.Count);