Merge remote-tracking branch 'upstream/dev' into develop

This commit is contained in:
EvilFactory
2022-12-09 17:33:44 -03:00
416 changed files with 12674 additions and 5862 deletions
@@ -152,7 +152,9 @@ namespace Barotrauma.Networking
public bool IsBanned(AccountId accountId, out string reason)
{
RemoveExpired();
var bannedPlayer = bannedPlayers.Find(bp => bp.AddressOrAccountId.TryGet(out AccountId id) && accountId.Equals(id));
var bannedPlayer =
bannedPlayers.Find(bp => bp.AddressOrAccountId.TryGet(out AccountId id) && accountId.Equals(id)) ??
bannedPlayers.Find(bp => bp.AddressOrAccountId.TryGet(out Address adr) && adr is SteamP2PAddress steamAdr && steamAdr.SteamId.Equals(accountId));
reason = bannedPlayer?.Reason ?? string.Empty;
return bannedPlayer != null;
}
@@ -210,9 +210,9 @@ namespace Barotrauma.Networking
return length;
}
public virtual void ServerWrite(IWriteMessage msg, Client c)
public virtual void ServerWrite(in SegmentTableWriter<ServerNetSegment> segmentTable, IWriteMessage msg, Client c)
{
msg.WriteByte((byte)ServerNetObject.CHAT_MESSAGE);
segmentTable.StartNewSegment(ServerNetSegment.ChatMessage);
msg.WriteUInt16(NetStateID);
msg.WriteRangedInteger((int)Type, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
msg.WriteByte((byte)ChangeType);
@@ -40,6 +40,8 @@ namespace Barotrauma.Networking
public float ChatSpamTimer;
public int ChatSpamCount;
public string RejectedName;
public int RoundsSincePlayedAsTraitor;
public float KickAFKTimer;
@@ -69,6 +71,9 @@ namespace Barotrauma.Networking
public DateTime JoinTime;
public static readonly TimeSpan NameChangeCoolDown = new TimeSpan(hours: 0, minutes: 0, seconds: 30);
public DateTime LastNameChangeTime;
private CharacterInfo characterInfo;
public CharacterInfo CharacterInfo
{
@@ -342,9 +342,9 @@ namespace Barotrauma.Networking
#endif
if (!started) { return; }
if (OwnerConnection != null && ChildServerRelay.HasShutDown)
if (ChildServerRelay.HasShutDown)
{
Quit();
GameMain.Instance.CloseServer();
return;
}
@@ -1041,12 +1041,11 @@ namespace Barotrauma.Networking
return;
}
ClientNetObject objHeader;
while ((objHeader = (ClientNetObject)inc.ReadByte()) != ClientNetObject.END_OF_MESSAGE)
SegmentTableReader<ClientNetSegment>.Read(inc, (segment, inc) =>
{
switch (objHeader)
switch (segment)
{
case ClientNetObject.SYNC_IDS:
case ClientNetSegment.SyncIds:
//TODO: might want to use a clever class for this
c.LastRecvLobbyUpdate = NetIdUtils.Clamp(inc.ReadUInt16(), c.LastRecvLobbyUpdate, GameMain.NetLobbyScreen.LastUpdateID);
if (c.HasPermission(ClientPermissions.ManageSettings) &&
@@ -1085,19 +1084,21 @@ namespace Barotrauma.Networking
}
}
break;
case ClientNetObject.CHAT_MESSAGE:
case ClientNetSegment.ChatMessage:
ChatMessage.ServerRead(inc, c);
break;
case ClientNetObject.VOTE:
case ClientNetSegment.Vote:
Voting.ServerRead(inc, c);
break;
default:
return;
return SegmentTableReader<ClientNetSegment>.BreakSegmentReading.Yes;
}
//don't read further messages if the client has been disconnected (kicked due to spam for example)
if (!connectedClients.Contains(c)) break;
}
return connectedClients.Contains(c)
? SegmentTableReader<ClientNetSegment>.BreakSegmentReading.No
: SegmentTableReader<ClientNetSegment>.BreakSegmentReading.Yes;
});
}
private void ClientReadIngame(IReadMessage inc)
@@ -1122,13 +1123,12 @@ namespace Barotrauma.Networking
}
}
ClientNetObject objHeader;
while ((objHeader = (ClientNetObject)inc.ReadByte()) != ClientNetObject.END_OF_MESSAGE)
SegmentTableReader<ClientNetSegment>.Read(inc, (segment, inc) =>
{
switch (objHeader)
switch (segment)
{
case ClientNetObject.SYNC_IDS:
//TODO: might want to use a clever class for this
case ClientNetSegment.SyncIds:
//TODO: switch this to INetSerializableStruct
UInt16 lastRecvChatMsgID = inc.ReadUInt16();
UInt16 lastRecvEntityEventID = inc.ReadUInt16();
@@ -1231,10 +1231,10 @@ namespace Barotrauma.Networking
}
break;
case ClientNetObject.CHAT_MESSAGE:
case ClientNetSegment.ChatMessage:
ChatMessage.ServerRead(inc, c);
break;
case ClientNetObject.CHARACTER_INPUT:
case ClientNetSegment.CharacterInput:
if (c.Character != null)
{
c.Character.ServerReadInput(inc, c);
@@ -1244,22 +1244,24 @@ namespace Barotrauma.Networking
DebugConsole.AddWarning($"Received character inputs from a client who's not controlling a character ({c.Name}).");
}
break;
case ClientNetObject.ENTITY_STATE:
case ClientNetSegment.EntityState:
entityEventManager.Read(inc, c);
break;
case ClientNetObject.VOTE:
case ClientNetSegment.Vote:
Voting.ServerRead(inc, c);
break;
case ClientNetObject.SPECTATING_POS:
case ClientNetSegment.SpectatingPos:
c.SpectatePos = new Vector2(inc.ReadSingle(), inc.ReadSingle());
break;
default:
return;
return SegmentTableReader<ClientNetSegment>.BreakSegmentReading.Yes;
}
//don't read further messages if the client has been disconnected (kicked due to spam for example)
if (!connectedClients.Contains(c)) { break; }
}
return connectedClients.Contains(c)
? SegmentTableReader<ClientNetSegment>.BreakSegmentReading.No
: SegmentTableReader<ClientNetSegment>.BreakSegmentReading.Yes;
});
}
private void ReadCrewMessage(IReadMessage inc, Client sender)
@@ -1410,7 +1412,8 @@ namespace Barotrauma.Networking
if (mpCampaign != null && Level.IsLoadedFriendlyOutpost && save)
{
mpCampaign.SavePlayers();
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
mpCampaign.UpdateStoreStock();
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
}
else
@@ -1714,78 +1717,78 @@ namespace Barotrauma.Networking
IWriteMessage outmsg = new WriteOnlyMessage();
outmsg.WriteByte((byte)ServerPacketHeader.UPDATE_INGAME);
outmsg.WriteSingle((float)NetTime.Now);
outmsg.WriteByte((byte)ServerNetObject.SYNC_IDS);
outmsg.WriteUInt16(c.LastSentChatMsgID); //send this to client so they know which chat messages weren't received by the server
outmsg.WriteUInt16(c.LastSentEntityEventID);
if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign && campaign.Preset == GameMain.NetLobbyScreen.SelectedMode)
using (var segmentTable = SegmentTableWriter<ServerNetSegment>.StartWriting(outmsg))
{
outmsg.WriteBoolean(true);
outmsg.WritePadBits();
campaign.ServerWrite(outmsg, c);
}
else
{
outmsg.WriteBoolean(false);
outmsg.WritePadBits();
}
segmentTable.StartNewSegment(ServerNetSegment.SyncIds);
outmsg.WriteUInt16(c.LastSentChatMsgID); //send this to client so they know which chat messages weren't received by the server
outmsg.WriteUInt16(c.LastSentEntityEventID);
int clientListBytes = outmsg.LengthBytes;
WriteClientList(c, outmsg);
clientListBytes = outmsg.LengthBytes - clientListBytes;
int chatMessageBytes = outmsg.LengthBytes;
WriteChatMessages(outmsg, c);
chatMessageBytes = outmsg.LengthBytes - chatMessageBytes;
//write as many position updates as the message can fit (only after midround syncing is done)
int positionUpdateBytes = outmsg.LengthBytes;
while (!c.NeedsMidRoundSync && c.PendingPositionUpdates.Count > 0)
{
var entity = c.PendingPositionUpdates.Peek();
if (!(entity is IServerPositionSync entityPositionSync) ||
entity.Removed ||
(entity is Item item && float.IsInfinity(item.PositionUpdateInterval)))
if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign && campaign.Preset == GameMain.NetLobbyScreen.SelectedMode)
{
outmsg.WriteBoolean(true);
outmsg.WritePadBits();
campaign.ServerWrite(outmsg, c);
}
else
{
outmsg.WriteBoolean(false);
outmsg.WritePadBits();
}
int clientListBytes = outmsg.LengthBytes;
WriteClientList(segmentTable, c, outmsg);
clientListBytes = outmsg.LengthBytes - clientListBytes;
int chatMessageBytes = outmsg.LengthBytes;
WriteChatMessages(segmentTable, outmsg, c);
chatMessageBytes = outmsg.LengthBytes - chatMessageBytes;
//write as many position updates as the message can fit (only after midround syncing is done)
int positionUpdateBytes = outmsg.LengthBytes;
while (!c.NeedsMidRoundSync && c.PendingPositionUpdates.Count > 0)
{
var entity = c.PendingPositionUpdates.Peek();
if (!(entity is IServerPositionSync entityPositionSync) ||
entity.Removed ||
(entity is Item item && float.IsInfinity(item.PositionUpdateInterval)))
{
c.PendingPositionUpdates.Dequeue();
continue;
}
IWriteMessage tempBuffer = new ReadWriteMessage();
tempBuffer.WriteBoolean(entity is Item); tempBuffer.WritePadBits();
tempBuffer.WriteUInt32(entity is MapEntity me ? me.Prefab.UintIdentifier : (UInt32)0);
entityPositionSync.ServerWritePosition(tempBuffer, c);
//no more room in this packet
if (outmsg.LengthBytes + tempBuffer.LengthBytes > MsgConstants.MTU - 100)
{
break;
}
segmentTable.StartNewSegment(ServerNetSegment.EntityPosition);
outmsg.WritePadBits(); //padding is required here to make sure any padding bits within tempBuffer are read correctly
outmsg.WriteBytes(tempBuffer.Buffer, 0, tempBuffer.LengthBytes);
outmsg.WritePadBits();
c.PositionUpdateLastSent[entity] = (float)NetTime.Now;
c.PendingPositionUpdates.Dequeue();
continue;
}
positionUpdateBytes = outmsg.LengthBytes - positionUpdateBytes;
IWriteMessage tempBuffer = new ReadWriteMessage();
tempBuffer.WriteBoolean(entity is Item); tempBuffer.WritePadBits();
tempBuffer.WriteUInt32(entity is MapEntity me ? me.Prefab.UintIdentifier : (UInt32)0);
entityPositionSync.ServerWritePosition(tempBuffer, c);
//no more room in this packet
if (outmsg.LengthBytes + tempBuffer.LengthBytes > MsgConstants.MTU - 100)
if (outmsg.LengthBytes > MsgConstants.MTU)
{
break;
string errorMsg = "Maximum packet size exceeded (" + outmsg.LengthBytes + " > " + MsgConstants.MTU + ")\n";
errorMsg +=
" Client list size: " + clientListBytes + " bytes\n" +
" Chat message size: " + chatMessageBytes + " bytes\n" +
" Position update size: " + positionUpdateBytes + " bytes\n\n";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame1:PacketSizeExceeded" + outmsg.LengthBytes, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
}
outmsg.WriteByte((byte)ServerNetObject.ENTITY_POSITION);
outmsg.WritePadBits(); //padding is required here to make sure any padding bits within tempBuffer are read correctly
outmsg.WriteBytes(tempBuffer.Buffer, 0, tempBuffer.LengthBytes);
outmsg.WritePadBits();
c.PositionUpdateLastSent[entity] = (float)NetTime.Now;
c.PendingPositionUpdates.Dequeue();
}
positionUpdateBytes = outmsg.LengthBytes - positionUpdateBytes;
outmsg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
if (outmsg.LengthBytes > MsgConstants.MTU)
{
string errorMsg = "Maximum packet size exceeded (" + outmsg.LengthBytes + " > " + MsgConstants.MTU + ")\n";
errorMsg +=
" Client list size: " + clientListBytes + " bytes\n" +
" Chat message size: " + chatMessageBytes + " bytes\n" +
" Position update size: " + positionUpdateBytes + " bytes\n\n";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame1:PacketSizeExceeded" + outmsg.LengthBytes, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
}
serverPeer.Send(outmsg, c.Connection, DeliveryMethod.Unreliable);
@@ -1798,46 +1801,50 @@ namespace Barotrauma.Networking
outmsg.WriteByte((byte)ServerPacketHeader.UPDATE_INGAME);
outmsg.WriteSingle((float)Lidgren.Network.NetTime.Now);
int eventManagerBytes = outmsg.LengthBytes;
entityEventManager.Write(c, outmsg, out List<NetEntityEvent> sentEvents);
eventManagerBytes = outmsg.LengthBytes - eventManagerBytes;
if (sentEvents.Count == 0)
using (var segmentTable = SegmentTableWriter<ServerNetSegment>.StartWriting(outmsg))
{
break;
}
int eventManagerBytes = outmsg.LengthBytes;
entityEventManager.Write(segmentTable, c, outmsg, out List<NetEntityEvent> sentEvents);
eventManagerBytes = outmsg.LengthBytes - eventManagerBytes;
outmsg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
if (outmsg.LengthBytes > MsgConstants.MTU)
{
string errorMsg = "Maximum packet size exceeded (" + outmsg.LengthBytes + " > " + MsgConstants.MTU + ")\n";
errorMsg +=
" Event size: " + eventManagerBytes + " bytes\n";
if (sentEvents != null && sentEvents.Count > 0)
if (sentEvents.Count == 0)
{
errorMsg += "Sent events: \n";
foreach (var entityEvent in sentEvents)
{
errorMsg += " - " + (entityEvent.Entity?.ToString() ?? "null") + "\n";
}
break;
}
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame2:PacketSizeExceeded" + outmsg.LengthBytes, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
if (outmsg.LengthBytes > MsgConstants.MTU)
{
string errorMsg = "Maximum packet size exceeded (" + outmsg.LengthBytes + " > " +
MsgConstants.MTU + ")\n";
errorMsg +=
" Event size: " + eventManagerBytes + " bytes\n";
if (sentEvents != null && sentEvents.Count > 0)
{
errorMsg += "Sent events: \n";
foreach (var entityEvent in sentEvents)
{
errorMsg += " - " + (entityEvent.Entity?.ToString() ?? "null") + "\n";
}
}
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce(
"GameServer.ClientWriteIngame2:PacketSizeExceeded" + outmsg.LengthBytes,
GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
}
}
serverPeer.Send(outmsg, c.Connection, DeliveryMethod.Unreliable);
}
}
private void WriteClientList(Client c, IWriteMessage outmsg)
private void WriteClientList(in SegmentTableWriter<ServerNetSegment> segmentTable, Client c, IWriteMessage outmsg)
{
bool hasChanged = NetIdUtils.IdMoreRecent(LastClientListUpdateID, c.LastRecvClientListUpdate);
if (!hasChanged) { return; }
outmsg.WriteByte((byte)ServerNetObject.CLIENT_LIST);
segmentTable.StartNewSegment(ServerNetSegment.ClientList);
outmsg.WriteUInt16(LastClientListUpdateID);
GameMain.LuaCs.Hook.Call("writeClientList", c, outmsg);
@@ -1883,133 +1890,135 @@ namespace Barotrauma.Networking
IWriteMessage outmsg = new WriteOnlyMessage();
outmsg.WriteByte((byte)ServerPacketHeader.UPDATE_LOBBY);
outmsg.WriteByte((byte)ServerNetObject.SYNC_IDS);
int settingsBytes = outmsg.LengthBytes;
int initialUpdateBytes = 0;
if (ServerSettings.UnsentFlags() != ServerSettings.NetFlags.None)
bool messageTooLarge;
using (var segmentTable = SegmentTableWriter<ServerNetSegment>.StartWriting(outmsg))
{
GameMain.NetLobbyScreen.LastUpdateID++;
}
IWriteMessage settingsBuf = null;
if (NetIdUtils.IdMoreRecent(GameMain.NetLobbyScreen.LastUpdateID, c.LastRecvLobbyUpdate))
{
outmsg.WriteBoolean(true);
outmsg.WritePadBits();
segmentTable.StartNewSegment(ServerNetSegment.SyncIds);
outmsg.WriteUInt16(GameMain.NetLobbyScreen.LastUpdateID);
int settingsBytes = outmsg.LengthBytes;
int initialUpdateBytes = 0;
settingsBuf = new ReadWriteMessage();
ServerSettings.ServerWrite(settingsBuf, c);
outmsg.WriteUInt16((UInt16)settingsBuf.LengthBytes);
outmsg.WriteBytes(settingsBuf.Buffer, 0, settingsBuf.LengthBytes);
outmsg.WriteBoolean(c.LastRecvLobbyUpdate < 1);
if (c.LastRecvLobbyUpdate < 1)
if (ServerSettings.UnsentFlags() != ServerSettings.NetFlags.None)
{
isInitialUpdate = true;
initialUpdateBytes = outmsg.LengthBytes;
ClientWriteInitial(c, outmsg);
initialUpdateBytes = outmsg.LengthBytes - initialUpdateBytes;
GameMain.NetLobbyScreen.LastUpdateID++;
}
outmsg.WriteString(GameMain.NetLobbyScreen.SelectedSub.Name);
outmsg.WriteString(GameMain.NetLobbyScreen.SelectedSub.MD5Hash.ToString());
outmsg.WriteBoolean(IsUsingRespawnShuttle());
var selectedShuttle = GameStarted && RespawnManager != null && RespawnManager.UsingShuttle ?
RespawnManager.RespawnShuttle.Info :
GameMain.NetLobbyScreen.SelectedShuttle;
outmsg.WriteString(selectedShuttle.Name);
outmsg.WriteString(selectedShuttle.MD5Hash.ToString());
outmsg.WriteBoolean(ServerSettings.AllowSubVoting);
outmsg.WriteBoolean(ServerSettings.AllowModeVoting);
outmsg.WriteBoolean(ServerSettings.VoiceChatEnabled);
outmsg.WriteBoolean(ServerSettings.AllowSpectating);
outmsg.WriteRangedInteger((int)ServerSettings.TraitorsEnabled, 0, 2);
outmsg.WriteRangedInteger((int)GameMain.NetLobbyScreen.MissionType, 0, (int)MissionType.All);
outmsg.WriteByte((byte)GameMain.NetLobbyScreen.SelectedModeIndex);
outmsg.WriteString(GameMain.NetLobbyScreen.LevelSeed);
outmsg.WriteSingle(ServerSettings.SelectedLevelDifficulty);
outmsg.WriteByte((byte)ServerSettings.BotCount);
outmsg.WriteBoolean(ServerSettings.BotSpawnMode == BotSpawnMode.Fill);
outmsg.WriteBoolean(ServerSettings.AutoRestart);
if (ServerSettings.AutoRestart)
IWriteMessage settingsBuf = null;
if (NetIdUtils.IdMoreRecent(GameMain.NetLobbyScreen.LastUpdateID, c.LastRecvLobbyUpdate))
{
outmsg.WriteSingle(autoRestartTimerRunning ? ServerSettings.AutoRestartTimer : 0.0f);
outmsg.WriteBoolean(true);
outmsg.WritePadBits();
outmsg.WriteUInt16(GameMain.NetLobbyScreen.LastUpdateID);
settingsBuf = new ReadWriteMessage();
ServerSettings.ServerWrite(settingsBuf, c);
outmsg.WriteUInt16((UInt16)settingsBuf.LengthBytes);
outmsg.WriteBytes(settingsBuf.Buffer, 0, settingsBuf.LengthBytes);
outmsg.WriteBoolean(c.LastRecvLobbyUpdate < 1);
if (c.LastRecvLobbyUpdate < 1)
{
isInitialUpdate = true;
initialUpdateBytes = outmsg.LengthBytes;
ClientWriteInitial(c, outmsg);
initialUpdateBytes = outmsg.LengthBytes - initialUpdateBytes;
}
outmsg.WriteString(GameMain.NetLobbyScreen.SelectedSub.Name);
outmsg.WriteString(GameMain.NetLobbyScreen.SelectedSub.MD5Hash.ToString());
outmsg.WriteBoolean(IsUsingRespawnShuttle());
var selectedShuttle = GameStarted && RespawnManager != null && RespawnManager.UsingShuttle ?
RespawnManager.RespawnShuttle.Info :
GameMain.NetLobbyScreen.SelectedShuttle;
outmsg.WriteString(selectedShuttle.Name);
outmsg.WriteString(selectedShuttle.MD5Hash.ToString());
outmsg.WriteBoolean(ServerSettings.AllowSubVoting);
outmsg.WriteBoolean(ServerSettings.AllowModeVoting);
outmsg.WriteBoolean(ServerSettings.VoiceChatEnabled);
outmsg.WriteBoolean(ServerSettings.AllowSpectating);
outmsg.WriteRangedInteger((int)ServerSettings.TraitorsEnabled, 0, 2);
outmsg.WriteRangedInteger((int)GameMain.NetLobbyScreen.MissionType, 0, (int)MissionType.All);
outmsg.WriteByte((byte)GameMain.NetLobbyScreen.SelectedModeIndex);
outmsg.WriteString(GameMain.NetLobbyScreen.LevelSeed);
outmsg.WriteSingle(ServerSettings.SelectedLevelDifficulty);
outmsg.WriteByte((byte)ServerSettings.BotCount);
outmsg.WriteBoolean(ServerSettings.BotSpawnMode == BotSpawnMode.Fill);
outmsg.WriteBoolean(ServerSettings.AutoRestart);
if (ServerSettings.AutoRestart)
{
outmsg.WriteSingle(autoRestartTimerRunning ? ServerSettings.AutoRestartTimer : 0.0f);
}
}
}
else
{
outmsg.WriteBoolean(false);
outmsg.WritePadBits();
}
settingsBytes = outmsg.LengthBytes - settingsBytes;
int campaignBytes = outmsg.LengthBytes;
var campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign;
if (outmsg.LengthBytes < MsgConstants.MTU - 500 &&
campaign != null && campaign.Preset == GameMain.NetLobbyScreen.SelectedMode)
{
outmsg.WriteBoolean(true);
outmsg.WritePadBits();
campaign.ServerWrite(outmsg, c);
}
else
{
outmsg.WriteBoolean(false);
outmsg.WritePadBits();
}
campaignBytes = outmsg.LengthBytes - campaignBytes;
outmsg.WriteUInt16(c.LastSentChatMsgID); //send this to client so they know which chat messages weren't received by the server
int clientListBytes = outmsg.LengthBytes;
if (outmsg.LengthBytes < MsgConstants.MTU - 500)
{
WriteClientList(c, outmsg);
}
clientListBytes = outmsg.LengthBytes - clientListBytes;
int chatMessageBytes = outmsg.LengthBytes;
WriteChatMessages(outmsg, c);
chatMessageBytes = outmsg.LengthBytes - chatMessageBytes;
outmsg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
bool messageTooLarge = outmsg.LengthBytes > MsgConstants.MTU;
if (messageTooLarge && !isInitialUpdate)
{
string warningMsg = "Maximum packet size exceeded, will send using reliable mode (" + outmsg.LengthBytes + " > " + MsgConstants.MTU + ")\n";
warningMsg +=
" Client list size: " + clientListBytes + " bytes\n" +
" Chat message size: " + chatMessageBytes + " bytes\n" +
" Campaign size: " + campaignBytes + " bytes\n" +
" Settings size: " + settingsBytes + " bytes\n";
if (initialUpdateBytes > 0)
else
{
outmsg.WriteBoolean(false);
outmsg.WritePadBits();
}
settingsBytes = outmsg.LengthBytes - settingsBytes;
int campaignBytes = outmsg.LengthBytes;
var campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign;
if (outmsg.LengthBytes < MsgConstants.MTU - 500 &&
campaign != null && campaign.Preset == GameMain.NetLobbyScreen.SelectedMode)
{
outmsg.WriteBoolean(true);
outmsg.WritePadBits();
campaign.ServerWrite(outmsg, c);
}
else
{
outmsg.WriteBoolean(false);
outmsg.WritePadBits();
}
campaignBytes = outmsg.LengthBytes - campaignBytes;
outmsg.WriteUInt16(c.LastSentChatMsgID); //send this to client so they know which chat messages weren't received by the server
int clientListBytes = outmsg.LengthBytes;
if (outmsg.LengthBytes < MsgConstants.MTU - 500)
{
WriteClientList(segmentTable, c, outmsg);
}
clientListBytes = outmsg.LengthBytes - clientListBytes;
int chatMessageBytes = outmsg.LengthBytes;
WriteChatMessages(segmentTable, outmsg, c);
chatMessageBytes = outmsg.LengthBytes - chatMessageBytes;
messageTooLarge = outmsg.LengthBytes > MsgConstants.MTU;
if (messageTooLarge && !isInitialUpdate)
{
string warningMsg = "Maximum packet size exceeded, will send using reliable mode (" + outmsg.LengthBytes + " > " + MsgConstants.MTU + ")\n";
warningMsg +=
" Initial update size: " + settingsBuf.LengthBytes + " bytes\n";
}
if (settingsBuf != null)
{
warningMsg +=
" Settings buffer size: " + settingsBuf.LengthBytes + " bytes\n";
}
" Client list size: " + clientListBytes + " bytes\n" +
" Chat message size: " + chatMessageBytes + " bytes\n" +
" Campaign size: " + campaignBytes + " bytes\n" +
" Settings size: " + settingsBytes + " bytes\n";
if (initialUpdateBytes > 0)
{
warningMsg +=
" Initial update size: " + initialUpdateBytes + " bytes\n";
}
if (settingsBuf != null)
{
warningMsg +=
" Settings buffer size: " + settingsBuf.LengthBytes + " bytes\n";
}
#if DEBUG || UNSTABLE
DebugConsole.ThrowError(warningMsg);
DebugConsole.ThrowError(warningMsg);
#else
if (GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.AddWarning(warningMsg); }
if (GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.AddWarning(warningMsg); }
#endif
GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame1:ClientWriteLobby" + outmsg.LengthBytes, GameAnalyticsManager.ErrorSeverity.Warning, warningMsg);
GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame1:ClientWriteLobby" + outmsg.LengthBytes, GameAnalyticsManager.ErrorSeverity.Warning, warningMsg);
}
}
if (isInitialUpdate || messageTooLarge)
@@ -2036,7 +2045,7 @@ namespace Barotrauma.Networking
}
}
private void WriteChatMessages(IWriteMessage outmsg, Client c)
private void WriteChatMessages(in SegmentTableWriter<ServerNetSegment> segmentTable, 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++)
@@ -2046,7 +2055,7 @@ namespace Barotrauma.Networking
//not enough room in this packet
return;
}
c.ChatMsgQueue[i].ServerWrite(outmsg, c);
c.ChatMsgQueue[i].ServerWrite(segmentTable, outmsg, c);
}
}
@@ -2543,6 +2552,7 @@ namespace Barotrauma.Networking
msg.WriteInt32(ServerSettings.MaximumMoneyTransferRequest);
msg.WriteBoolean(IsUsingRespawnShuttle());
msg.WriteByte((byte)ServerSettings.LosMode);
msg.WriteByte((byte)ServerSettings.ShowEnemyHealthBars);
msg.WriteBoolean(includesFinalize); msg.WritePadBits();
ServerSettings.WriteMonsterEnabled(msg);
@@ -2746,9 +2756,24 @@ namespace Barotrauma.Networking
CharacterTeamType newTeam = (CharacterTeamType)inc.ReadByte();
if (c == null || string.IsNullOrEmpty(newName) || !NetIdUtils.IdMoreRecent(nameId, c.NameId)) { return false; }
var timeSinceNameChange = DateTime.Now - c.LastNameChangeTime;
if (timeSinceNameChange < Client.NameChangeCoolDown)
{
//only send once per second at most to prevent using this for spamming
if (timeSinceNameChange.TotalSeconds > 1)
{
var coolDownRemaining = Client.NameChangeCoolDown - timeSinceNameChange;
SendDirectChatMessage($"ServerMessage.NameChangeFailedCooldownActive~[seconds]={(int)coolDownRemaining.TotalSeconds}", c);
}
c.NameId = nameId;
c.RejectedName = newName;
return false;
}
if (!newJob.IsEmpty)
{
if (!JobPrefab.Prefabs.TryGet(newJob, out JobPrefab newJobPrefab) || newJobPrefab.HiddenJob)
if (!JobPrefab.Prefabs.TryGet(newJob, out JobPrefab newJobPrefab) || newJobPrefab.HiddenJob)
{
newJob = Identifier.Empty;
}
@@ -2773,26 +2798,25 @@ namespace Barotrauma.Networking
public bool TryChangeClientName(Client c, string newName)
{
newName = Client.SanitizeName(newName);
//update client list even if the name cannot be changed to the one sent by the client,
//so the client will be informed what their actual name is
LastClientListUpdateID++;
if (newName == c.Name || string.IsNullOrEmpty(newName)) { return false; }
if (IsNameValid(c, newName))
if (newName != c.Name && !string.IsNullOrEmpty(newName) && IsNameValid(c, newName))
{
c.LastNameChangeTime = DateTime.Now;
string oldName = c.Name;
c.Name = newName;
c.RejectedName = string.Empty;
SendChatMessage($"ServerMessage.NameChangeSuccessful~[oldname]={oldName}~[newname]={newName}", ChatMessageType.Server);
LastClientListUpdateID++;
return true;
}
else
{
//update client list even if the name cannot be changed to the one sent by the client,
//so the client will be informed what their actual name is
LastClientListUpdateID++;
return false;
}
}
private bool IsNameValid(Client c, string newName)
{
newName = Client.SanitizeName(newName);
@@ -3365,9 +3389,11 @@ namespace Barotrauma.Networking
IWriteMessage msg = new WriteOnlyMessage();
msg.WriteByte((byte)ServerPacketHeader.UPDATE_LOBBY);
msg.WriteByte((byte)ServerNetObject.VOTE);
Voting.ServerWrite(msg);
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
using (var segmentTable = SegmentTableWriter<ServerNetSegment>.StartWriting(msg))
{
segmentTable.StartNewSegment(ServerNetSegment.Vote);
Voting.ServerWrite(msg);
}
foreach (var c in recipients)
{
@@ -3971,6 +3997,7 @@ namespace Barotrauma.Networking
public void Quit()
{
if (started)
{
started = false;
@@ -123,7 +123,7 @@ namespace Barotrauma.Networking
//remove old events that have been sent to all clients, they are redundant now
// keep at least one event in the list (lastSentToAll == e.ID) so we can use it to keep track of the latest ID
// and events less than 15 seconds old to give disconnected clients a bit of time to reconnect without getting desynced
if (Timing.TotalTime > GameMain.GameSession.RoundStartTime + NetConfig.RoundStartSyncDuration)
if (GameMain.GameSession.RoundDuration > NetConfig.RoundStartSyncDuration)
{
events.RemoveAll(e =>
(NetIdUtils.IdMoreRecent(lastSentToAll, e.ID) || !inGameClientsPresent) &&
@@ -168,9 +168,10 @@ namespace Barotrauma.Networking
if (!bufferedEvent.Character.IsIncapacitated &&
NetIdUtils.IdMoreRecent(bufferedEvent.CharacterStateID, bufferedEvent.Character.LastProcessedID))
{
DebugConsole.Log($"Delaying reading entity event sent by a client until the character state has been processed. Event's character state: {bufferedEvent.CharacterStateID}, last processed character state: {bufferedEvent.Character.LastProcessedID}");
continue;
}
try
{
ReadEvent(bufferedEvent.Data, bufferedEvent.TargetEntity, bufferedEvent.Sender);
@@ -216,7 +217,7 @@ namespace Barotrauma.Networking
if (Timing.TotalTime - lastWarningTime > 5.0 &&
Timing.TotalTime - lastSentToAnyoneTime > 10.0 &&
Timing.TotalTime > GameMain.GameSession.RoundStartTime + NetConfig.RoundStartSyncDuration)
GameMain.GameSession.RoundDuration > NetConfig.RoundStartSyncDuration)
{
lastWarningTime = Timing.TotalTime;
GameServer.Log("WARNING: ServerEntityEventManager is lagging behind! Last sent id: " + lastSentToAnyone.ToString() + ", latest create id: " + ID.ToString(), ServerLog.MessageType.ServerMessage);
@@ -228,7 +229,7 @@ namespace Barotrauma.Networking
ServerEntityEvent firstEventToResend = events.Find(e => e.ID == (ushort)(lastSentToAll + 1));
if (firstEventToResend != null &&
Timing.TotalTime > GameMain.GameSession.RoundStartTime + NetConfig.RoundStartSyncDuration &&
GameMain.GameSession.RoundDuration > NetConfig.RoundStartSyncDuration &&
((lastSentToAnyoneTime - firstEventToResend.CreateTime) > NetConfig.OldReceivedEventKickTime || (Timing.TotalTime - firstEventToResend.CreateTime) > NetConfig.OldEventKickTime))
{
// This event is 10 seconds older than the last one we've successfully sent,
@@ -294,15 +295,15 @@ namespace Barotrauma.Networking
/// <summary>
/// Writes all the events that the client hasn't received yet into the outgoing message
/// </summary>
public void Write(Client client, IWriteMessage msg)
public void Write(in SegmentTableWriter<ServerNetSegment> segmentTable, Client client, IWriteMessage msg)
{
Write(client, msg, out _);
Write(segmentTable, client, msg, out _);
}
/// <summary>
/// Writes all the events that the client hasn't received yet into the outgoing message
/// </summary>
public void Write(Client client, IWriteMessage msg, out List<NetEntityEvent> sentEvents)
public void Write(in SegmentTableWriter<ServerNetSegment> segmentTable, Client client, IWriteMessage msg, out List<NetEntityEvent> sentEvents)
{
List<NetEntityEvent> eventsToSync = GetEventsToSync(client);
@@ -314,7 +315,7 @@ namespace Barotrauma.Networking
//too many events for one packet
//(normal right after a round has just started, don't show a warning if it's been less than 10 seconds)
if (eventsToSync.Count > 200 && GameMain.GameSession != null && Timing.TotalTime > GameMain.GameSession.RoundStartTime + 10.0)
if (eventsToSync.Count > 200 && GameMain.GameSession != null && GameMain.GameSession.RoundDuration > 10.0)
{
if (eventsToSync.Count > 200 && !client.NeedsMidRoundSync && Timing.TotalTime > lastEventCountHighWarning + 2.0)
{
@@ -344,7 +345,7 @@ namespace Barotrauma.Networking
if (client.NeedsMidRoundSync)
{
msg.WriteByte((byte)ServerNetObject.ENTITY_EVENT_INITIAL);
segmentTable.StartNewSegment(ServerNetSegment.EntityEventInitial);
msg.WriteUInt16(client.UnreceivedEntityEventCount);
msg.WriteUInt16(client.FirstNewEventID);
@@ -352,7 +353,7 @@ namespace Barotrauma.Networking
}
else
{
msg.WriteByte((byte)ServerNetObject.ENTITY_EVENT);
segmentTable.StartNewSegment(ServerNetSegment.EntityEvent);
Write(msg, eventsToSync, out sentEvents, client);
}
@@ -1,13 +1,13 @@
using Barotrauma.Steam;
using Barotrauma.Steam;
using System;
namespace Barotrauma.Networking
{
partial class OrderChatMessage : ChatMessage
{
public override void ServerWrite(IWriteMessage msg, Client c)
public override void ServerWrite(in SegmentTableWriter<ServerNetSegment> segmentTable, IWriteMessage msg, Client c)
{
msg.WriteByte((byte)ServerNetObject.CHAT_MESSAGE);
segmentTable.StartNewSegment(ServerNetSegment.ChatMessage);
msg.WriteUInt16(NetStateID);
msg.WriteRangedInteger((int)ChatMessageType.Order, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
msg.WriteString(SenderName);
@@ -255,7 +255,9 @@ namespace Barotrauma.Networking
structToSend = new ServerPeerContentPackageOrderPacket
{
ServerName = GameMain.Server.ServerName,
ContentPackages = ContentPackageManager.EnabledPackages.All.Where(cp => cp.HasMultiplayerSyncedContent || cp.Files.All(f => f is SubmarineFile))
ContentPackages = ContentPackageManager.EnabledPackages.All
.Where(cp => cp.Files.Any())
.Where(cp => cp.HasMultiplayerSyncedContent || cp.Files.All(f => f is SubmarineFile))
.Select(contentPackage => new ServerContentPackage(contentPackage, timeNow))
.ToImmutableArray()
};
@@ -47,7 +47,7 @@ namespace Barotrauma.Networking
}
}
private bool IsRespawnPromptPendingForClient(Client c)
private static bool IsRespawnPromptPendingForClient(Client c)
{
if (!UseRespawnPrompt || !(GameMain.GameSession.GameMode is MultiPlayerCampaign campaign)) { return false; }
@@ -70,7 +70,7 @@ namespace Barotrauma.Networking
return false;
}
private List<CharacterInfo> GetBotsToRespawn()
private static List<CharacterInfo> GetBotsToRespawn()
{
if (GameMain.Server.ServerSettings.BotSpawnMode == BotSpawnMode.Normal)
{
@@ -113,7 +113,7 @@ namespace Barotrauma.Networking
return ShouldStartRespawnCountdown(characterToRespawnCount);
}
private int GetMinCharactersToRespawn()
private static int GetMinCharactersToRespawn()
{
return Math.Max((int)(GameMain.Server.ConnectedClients.Count * GameMain.Server.ServerSettings.MinRespawnRatio), 1);
}
@@ -485,7 +485,7 @@ namespace Barotrauma.Networking
}
}
if (!(GameMain.GameSession.GameMode is CampaignMode))
if (GameMain.GameSession.GameMode is not CampaignMode)
{
if (scooterPrefab != null)
{
@@ -272,7 +272,6 @@ namespace Barotrauma.Networking
XDocument doc = new XDocument(new XElement("serversettings"));
doc.Root.SetAttributeValue("name", ServerName);
doc.Root.SetAttributeValue("public", IsPublic);
doc.Root.SetAttributeValue("port", Port);
#if USE_STEAM
doc.Root.SetAttributeValue("queryport", QueryPort);
@@ -2,20 +2,18 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Text;
using MoonSharp.Interpreter;
namespace Barotrauma.Networking
{
class VoipServer
{
private ServerPeer netServer;
private List<VoipQueue> queues;
private Dictionary<VoipQueue,DateTime> lastSendTime;
private readonly ServerPeer netServer;
private readonly List<VoipQueue> queues;
private readonly Dictionary<VoipQueue,DateTime> lastSendTime;
public VoipServer(ServerPeer server)
{
this.netServer = server;
netServer = server;
queues = new List<VoipQueue>();
lastSendTime = new Dictionary<VoipQueue, DateTime>();
}
@@ -47,17 +45,19 @@ namespace Barotrauma.Networking
}
Client sender = clients.Find(c => c.VoipQueue == queue);
if (sender == null) { return; }
foreach (Client recipient in clients)
{
if (recipient == sender) { continue; }
if (!CanReceive(sender, recipient)) { continue; }
if (!CanReceive(sender, recipient, out float distanceFactor)) { continue; }
IWriteMessage msg = new WriteOnlyMessage();
msg.WriteByte((byte)ServerPacketHeader.VOICE);
msg.WriteByte((byte)queue.QueueID);
msg.WriteRangedSingle(distanceFactor, 0.0f, 1.0f, 8);
queue.Write(msg);
netServer.Send(msg, recipient.Connection, DeliveryMethod.Unreliable);
@@ -65,9 +65,15 @@ namespace Barotrauma.Networking
}
}
private bool CanReceive(Client sender, Client recipient)
private static bool CanReceive(Client sender, Client recipient, out float distanceFactor)
{
if (Screen.Selected != GameMain.GameScreen) { return true; }
if (Screen.Selected != GameMain.GameScreen)
{
distanceFactor = 0.0f;
return true;
}
distanceFactor = 0.0f;
//no-one can hear muted players
if (sender.Muted) { return false; }
@@ -75,42 +81,46 @@ namespace Barotrauma.Networking
bool recipientSpectating = recipient.Character == null || recipient.Character.IsDead;
bool senderSpectating = sender.Character == null || sender.Character.IsDead;
//TODO: only allow spectators to hear the voice chat if close enough to the speaker?
//non-spectators cannot hear spectators
if (senderSpectating && !recipientSpectating) { return false; }
//both spectating, no need to do radio/distance checks
if (recipientSpectating && senderSpectating) { return true; }
//spectators can hear non-spectators
if (!senderSpectating && recipientSpectating) { return true; }
//non-spectators cannot hear spectators, and spectators can always hear spectators
if (senderSpectating)
{
return recipientSpectating;
}
//sender can't speak
if (sender.Character != null && sender.Character.SpeechImpediment >= 100.0f) { return false; }
//check if the message can be sent via radio
WifiComponent recipientRadio = null;
if (!sender.VoipQueue.ForceLocal &&
ChatMessage.CanUseRadio(sender.Character, out WifiComponent senderRadio) &&
ChatMessage.CanUseRadio(recipient.Character, out WifiComponent recipientRadio))
ChatMessage.CanUseRadio(sender.Character, out WifiComponent senderRadio) &&
(recipientSpectating || ChatMessage.CanUseRadio(recipient.Character, out recipientRadio)))
{
var should = GameMain.LuaCs.Hook.Call<bool?>("canUseVoiceRadio", new object[] { sender, recipient });
var canUse = GameMain.LuaCs.Hook.Call<bool?>("canUseVoiceRadio", new object[] { sender, recipient });
if (should != null)
if (canUse != null)
{
return should.Value;
}
if (recipientRadio.CanReceive(senderRadio)) { return true; }
}
var should2 = GameMain.LuaCs.Hook.Call<float?>("changeLocalVoiceRange", sender, recipient);
float range = 1.0f;
float range = GameMain.LuaCs.Hook.Call<float?>("changeLocalVoiceRange", sender, recipient) ?? 1.0f;
if (should2 != null)
range = should2.Value;
//otherwise do a distance check
return ChatMessage.GetGarbleAmount(recipient.Character, sender.Character, ChatMessage.SpeakRange) < range;
if (recipientSpectating)
{
if (recipient.SpectatePos == null) { return true; }
distanceFactor = MathHelper.Clamp(Vector2.Distance(sender.Character.WorldPosition, recipient.SpectatePos.Value) / ChatMessage.SpeakRange, 0.0f, 1.0f);
return distanceFactor < 1.0f;
}
else
{
//otherwise do a distance check
float garbleAmount = ChatMessage.GetGarbleAmount(recipient.Character, sender.Character, ChatMessage.SpeakRange);
distanceFactor = garbleAmount;
return garbleAmount < range;
}
}
}
}